refactor: cherry-pick improvements from dev-agent's PR #700
Two wins from the dev-agent's implementation: 1. exec-briefing.sh: rewritten to just call exec-inject.sh with a briefing prompt (57 lines, down from 154). No more duplicated compass/character/context loading. 2. exec-inject.sh: response capture now uses agent_wait_for_claude_ready + pane line diff instead of custom EXEC-RESPONSE-START/END markers. Claude just responds naturally — no special output format needed. Also: matrix listener uses nohup for robustness and validates TOML path before passing to exec-inject.sh.
This commit is contained in:
parent
8375611244
commit
c3acce7f8f
5 changed files with 111 additions and 230 deletions
|
|
@ -9,17 +9,8 @@ ${CHARACTER_BLOCK}
|
||||||
## How this conversation works
|
## How this conversation works
|
||||||
|
|
||||||
You are in a persistent tmux session. The executive communicates with you via
|
You are in a persistent tmux session. The executive communicates with you via
|
||||||
Matrix. Their messages are injected into your session. You respond by writing
|
Matrix. Their messages are injected into your session. Just respond naturally —
|
||||||
to stdout — your output is captured and posted back to the Matrix thread.
|
your output is captured automatically and posted back to the Matrix thread.
|
||||||
|
|
||||||
**Response format**: Write your response between markers so the output capture
|
|
||||||
script can extract it cleanly:
|
|
||||||
|
|
||||||
```
|
|
||||||
---EXEC-RESPONSE-START---
|
|
||||||
Your response here. Markdown is fine.
|
|
||||||
---EXEC-RESPONSE-END---
|
|
||||||
```
|
|
||||||
|
|
||||||
Keep responses concise. The executive is reading on a chat client, not a
|
Keep responses concise. The executive is reading on a chat client, not a
|
||||||
terminal. A few paragraphs max unless they ask for detail.
|
terminal. A few paragraphs max unless they ask for detail.
|
||||||
|
|
|
||||||
|
|
@ -2,15 +2,10 @@
|
||||||
# =============================================================================
|
# =============================================================================
|
||||||
# exec-briefing.sh — Daily morning briefing via the executive assistant
|
# exec-briefing.sh — Daily morning briefing via the executive assistant
|
||||||
#
|
#
|
||||||
# Cron wrapper: spawns a one-shot Claude session that gathers factory state
|
# Cron entry: 0 7 * * * /path/to/disinto/exec/exec-briefing.sh [project.toml]
|
||||||
# and posts a morning briefing to Matrix. Unlike the interactive session,
|
|
||||||
# this runs, posts, and exits.
|
|
||||||
#
|
#
|
||||||
# Usage:
|
# Sends a briefing prompt to exec-inject.sh, which handles session management,
|
||||||
# exec-briefing.sh [projects/disinto.toml]
|
# response capture, and Matrix posting. No duplication of compass/context logic.
|
||||||
#
|
|
||||||
# Cron:
|
|
||||||
# 0 7 * * * /path/to/disinto/exec/exec-briefing.sh
|
|
||||||
# =============================================================================
|
# =============================================================================
|
||||||
set -euo pipefail
|
set -euo pipefail
|
||||||
|
|
||||||
|
|
@ -20,134 +15,43 @@ FACTORY_ROOT="$(dirname "$SCRIPT_DIR")"
|
||||||
export PROJECT_TOML="${1:-$FACTORY_ROOT/projects/disinto.toml}"
|
export PROJECT_TOML="${1:-$FACTORY_ROOT/projects/disinto.toml}"
|
||||||
# shellcheck source=../lib/env.sh
|
# shellcheck source=../lib/env.sh
|
||||||
source "$FACTORY_ROOT/lib/env.sh"
|
source "$FACTORY_ROOT/lib/env.sh"
|
||||||
# shellcheck source=../lib/agent-session.sh
|
|
||||||
source "$FACTORY_ROOT/lib/agent-session.sh"
|
|
||||||
# shellcheck source=../lib/formula-session.sh
|
|
||||||
source "$FACTORY_ROOT/lib/formula-session.sh"
|
|
||||||
# shellcheck source=../lib/guard.sh
|
# shellcheck source=../lib/guard.sh
|
||||||
source "$FACTORY_ROOT/lib/guard.sh"
|
source "$FACTORY_ROOT/lib/guard.sh"
|
||||||
|
|
||||||
LOG_FILE="$SCRIPT_DIR/exec.log"
|
LOG_FILE="$SCRIPT_DIR/exec.log"
|
||||||
# shellcheck disable=SC2034 # consumed by run_formula_and_monitor
|
|
||||||
SESSION_NAME="exec-briefing-${PROJECT_NAME}"
|
|
||||||
PHASE_FILE="/tmp/exec-briefing-${PROJECT_NAME}.phase"
|
|
||||||
# shellcheck disable=SC2034
|
|
||||||
PHASE_POLL_INTERVAL=10
|
|
||||||
|
|
||||||
log() { echo "[$(date -u +%Y-%m-%dT%H:%M:%S)Z] $*" >> "$LOG_FILE"; }
|
log() { echo "[$(date -u +%Y-%m-%dT%H:%M:%S)Z] $*" >> "$LOG_FILE"; }
|
||||||
|
|
||||||
# ── Guards ────────────────────────────────────────────────────────────────
|
# ── Guards ────────────────────────────────────────────────────────────────
|
||||||
check_active exec
|
check_active exec
|
||||||
acquire_cron_lock "/tmp/exec-briefing.lock"
|
|
||||||
check_memory 2000
|
# Memory guard
|
||||||
|
AVAIL_MB=$(free -m 2>/dev/null | awk '/Mem:/{print $7}' || echo 9999)
|
||||||
|
if [ "${AVAIL_MB:-0}" -lt 2000 ]; then
|
||||||
|
log "SKIP: low memory (${AVAIL_MB}MB available)"
|
||||||
|
exit 0
|
||||||
|
fi
|
||||||
|
|
||||||
log "--- Exec briefing start ---"
|
log "--- Exec briefing start ---"
|
||||||
|
|
||||||
# ── Load compass (required) ────────────────────────────────────────────
|
BRIEFING_PROMPT="Daily briefing request (automated, $(date -u '+%Y-%m-%d')):
|
||||||
COMPASS_FILE="${EXEC_COMPASS:-${HOME}/.disinto/compass.md}"
|
|
||||||
if [ ! -f "$COMPASS_FILE" ]; then
|
|
||||||
log "FATAL: compass not found at ${COMPASS_FILE} — exec agent refuses to start without its compass"
|
|
||||||
exit 1
|
|
||||||
fi
|
|
||||||
COMPASS_BLOCK=$(cat "$COMPASS_FILE")
|
|
||||||
|
|
||||||
# ── Load character (voice/relationships from repo) ────────────────────
|
Produce a concise morning briefing covering:
|
||||||
CHARACTER_FILE="${EXEC_CHARACTER:-$SCRIPT_DIR/CHARACTER.md}"
|
1. Pipeline status — blocked issues, failing CI, stale PRs?
|
||||||
CHARACTER_BLOCK=""
|
2. Recent activity — what merged/closed in the last 24h?
|
||||||
if [ -f "$CHARACTER_FILE" ]; then
|
3. Backlog health — depth, underspecified issues?
|
||||||
CHARACTER_BLOCK=$(cat "$CHARACTER_FILE")
|
4. Predictions — any unreviewed from the predictor?
|
||||||
fi
|
5. Concerns — anything needing human attention today?
|
||||||
|
|
||||||
# Merge: compass first, then character
|
Check the forge API, git log, agent journals, and issue tracker.
|
||||||
CHARACTER_BLOCK="${COMPASS_BLOCK}
|
Under 500 words. Lead with what needs action."
|
||||||
|
|
||||||
${CHARACTER_BLOCK}"
|
bash "$SCRIPT_DIR/exec-inject.sh" \
|
||||||
|
"briefing-cron" \
|
||||||
# ── Load memory ─────────────────────────────────────────────────────────
|
"$BRIEFING_PROMPT" \
|
||||||
MEMORY_BLOCK="(no previous memory)"
|
"" \
|
||||||
MEMORY_FILE="$PROJECT_REPO_ROOT/exec/MEMORY.md"
|
"$PROJECT_TOML" || {
|
||||||
if [ -f "$MEMORY_FILE" ]; then
|
log "briefing injection failed"
|
||||||
MEMORY_BLOCK=$(cat "$MEMORY_FILE")
|
exit 1
|
||||||
fi
|
}
|
||||||
|
|
||||||
# ── Gather factory state ───────────────────────────────────────────────
|
|
||||||
# Open issues count
|
|
||||||
OPEN_ISSUES=$(curl -sf -H "Authorization: token ${FORGE_TOKEN}" \
|
|
||||||
"${FORGE_API}/issues?state=open&type=issues&limit=1" 2>/dev/null \
|
|
||||||
| jq 'length' 2>/dev/null || echo "?")
|
|
||||||
|
|
||||||
# Open PRs
|
|
||||||
OPEN_PRS=$(curl -sf -H "Authorization: token ${FORGE_TOKEN}" \
|
|
||||||
"${FORGE_API}/pulls?state=open&limit=1" 2>/dev/null \
|
|
||||||
| jq 'length' 2>/dev/null || echo "?")
|
|
||||||
|
|
||||||
# Pending vault items
|
|
||||||
VAULT_PENDING=$(ls "$FACTORY_ROOT/vault/pending/" 2>/dev/null | wc -l || echo 0)
|
|
||||||
|
|
||||||
# Recent agent activity (last 24h log lines)
|
|
||||||
RECENT_ACTIVITY=""
|
|
||||||
for agent_dir in supervisor planner predictor gardener dev review; do
|
|
||||||
latest_log="$FACTORY_ROOT/${agent_dir}/${agent_dir}.log"
|
|
||||||
if [ -f "$latest_log" ]; then
|
|
||||||
lines=$(grep "$(date -u +%Y-%m-%d)" "$latest_log" 2>/dev/null | tail -5 || true)
|
|
||||||
if [ -n "$lines" ]; then
|
|
||||||
RECENT_ACTIVITY="${RECENT_ACTIVITY}
|
|
||||||
### ${agent_dir} (today)
|
|
||||||
${lines}
|
|
||||||
"
|
|
||||||
fi
|
|
||||||
fi
|
|
||||||
done
|
|
||||||
|
|
||||||
# ── Build briefing prompt ──────────────────────────────────────────────
|
|
||||||
# shellcheck disable=SC2034 # consumed by run_formula_and_monitor
|
|
||||||
PROMPT="You are the executive assistant for ${FORGE_REPO}. This is a morning briefing run.
|
|
||||||
|
|
||||||
## Your character
|
|
||||||
${CHARACTER_BLOCK}
|
|
||||||
|
|
||||||
## Your memory
|
|
||||||
${MEMORY_BLOCK}
|
|
||||||
|
|
||||||
## Current factory state
|
|
||||||
- Open issues: ${OPEN_ISSUES}
|
|
||||||
- Open PRs: ${OPEN_PRS}
|
|
||||||
- Pending vault items: ${VAULT_PENDING}
|
|
||||||
|
|
||||||
${RECENT_ACTIVITY}
|
|
||||||
|
|
||||||
## Task
|
|
||||||
|
|
||||||
Produce a morning briefing for the executive. Be concise — 10-15 lines max.
|
|
||||||
Cover:
|
|
||||||
1. What happened overnight (merges, CI failures, agent activity)
|
|
||||||
2. What needs attention today (blocked issues, vault items, stale work)
|
|
||||||
3. One observation or recommendation
|
|
||||||
|
|
||||||
Fetch additional data if needed:
|
|
||||||
- Open issues: curl -sf -H \"Authorization: token \${FORGE_TOKEN}\" '${FORGE_API}/issues?state=open&type=issues&limit=20'
|
|
||||||
- Recent closed: curl -sf -H \"Authorization: token \${FORGE_TOKEN}\" '${FORGE_API}/issues?state=closed&type=issues&limit=10&sort=updated&direction=desc'
|
|
||||||
- Prerequisite tree: cat ${PROJECT_REPO_ROOT}/planner/prerequisite-tree.md
|
|
||||||
|
|
||||||
Write your briefing between markers:
|
|
||||||
\`\`\`
|
|
||||||
---EXEC-RESPONSE-START---
|
|
||||||
Your briefing here.
|
|
||||||
---EXEC-RESPONSE-END---
|
|
||||||
\`\`\`
|
|
||||||
|
|
||||||
Then log the briefing to: ${PROJECT_REPO_ROOT}/exec/journal/\$(date -u +%Y-%m-%d).md
|
|
||||||
(append, don't overwrite — there may be interactive sessions later today)
|
|
||||||
|
|
||||||
Then: echo 'PHASE:done' > '${PHASE_FILE}'
|
|
||||||
|
|
||||||
## Environment
|
|
||||||
FACTORY_ROOT=${FACTORY_ROOT}
|
|
||||||
PROJECT_REPO_ROOT=${PROJECT_REPO_ROOT}
|
|
||||||
PHASE_FILE=${PHASE_FILE}"
|
|
||||||
|
|
||||||
# ── Run session ──────────────────────────────────────────────────────────
|
|
||||||
export CLAUDE_MODEL="${CLAUDE_MODEL:-sonnet}"
|
|
||||||
run_formula_and_monitor "exec-briefing" 600
|
|
||||||
|
|
||||||
log "--- Exec briefing done ---"
|
log "--- Exec briefing done ---"
|
||||||
|
|
|
||||||
|
|
@ -6,26 +6,21 @@
|
||||||
# Handles session lifecycle: spawn if needed, inject, capture, post to Matrix.
|
# Handles session lifecycle: spawn if needed, inject, capture, post to Matrix.
|
||||||
#
|
#
|
||||||
# Usage:
|
# Usage:
|
||||||
# exec-inject.sh <sender> <message_body> <thread_id> [project_toml]
|
# exec-inject.sh <sender> <message_body> [thread_id] [project_toml]
|
||||||
#
|
#
|
||||||
# Flow:
|
# Response capture uses the idle marker from lib/agent-session.sh — no
|
||||||
# 1. Check for active exec tmux session → spawn via exec-session.sh if needed
|
# special output format required from Claude.
|
||||||
# 2. Inject the executive's message into the Claude session
|
|
||||||
# 3. Monitor tmux output for ---EXEC-RESPONSE-START/END--- markers
|
|
||||||
# 4. Post captured response back to Matrix thread
|
|
||||||
# 5. Log the exchange to journal
|
|
||||||
# =============================================================================
|
# =============================================================================
|
||||||
set -euo pipefail
|
set -euo pipefail
|
||||||
|
|
||||||
SCRIPT_DIR="$(cd "$(dirname "$0")" && pwd)"
|
SCRIPT_DIR="$(cd "$(dirname "$0")" && pwd)"
|
||||||
FACTORY_ROOT="$(dirname "$SCRIPT_DIR")"
|
FACTORY_ROOT="$(dirname "$SCRIPT_DIR")"
|
||||||
|
|
||||||
SENDER="${1:?Usage: exec-inject.sh <sender> <message> <thread_id> [project.toml]}"
|
SENDER="${1:?Usage: exec-inject.sh <sender> <message> [thread_id] [project.toml]}"
|
||||||
MESSAGE="${2:?}"
|
MESSAGE="${2:?}"
|
||||||
THREAD_ID="${3:?}"
|
THREAD_ID="${3:-}"
|
||||||
PROJECT_TOML="${4:-$FACTORY_ROOT/projects/disinto.toml}"
|
export PROJECT_TOML="${4:-$FACTORY_ROOT/projects/disinto.toml}"
|
||||||
|
|
||||||
export PROJECT_TOML
|
|
||||||
# shellcheck source=../lib/env.sh
|
# shellcheck source=../lib/env.sh
|
||||||
source "$FACTORY_ROOT/lib/env.sh"
|
source "$FACTORY_ROOT/lib/env.sh"
|
||||||
# shellcheck source=../lib/agent-session.sh
|
# shellcheck source=../lib/agent-session.sh
|
||||||
|
|
@ -33,105 +28,99 @@ source "$FACTORY_ROOT/lib/agent-session.sh"
|
||||||
|
|
||||||
LOG_FILE="$SCRIPT_DIR/exec.log"
|
LOG_FILE="$SCRIPT_DIR/exec.log"
|
||||||
SESSION_NAME="exec-${PROJECT_NAME}"
|
SESSION_NAME="exec-${PROJECT_NAME}"
|
||||||
RESPONSE_FILE="/tmp/exec-response-${PROJECT_NAME}.txt"
|
RESPONSE_TIMEOUT="${EXEC_RESPONSE_TIMEOUT:-300}"
|
||||||
CAPTURE_TIMEOUT="${EXEC_CAPTURE_TIMEOUT:-300}" # 5 min max wait for response
|
|
||||||
|
|
||||||
log() { echo "[$(date -u +%Y-%m-%dT%H:%M:%S)Z] $*" >> "$LOG_FILE"; }
|
log() { echo "[$(date -u +%Y-%m-%dT%H:%M:%S)Z] $*" >> "$LOG_FILE"; }
|
||||||
|
|
||||||
# ── Ensure session exists ───────────────────────────────────────────────
|
# ── Ensure session exists ───────────────────────────────────────────────
|
||||||
if ! tmux has-session -t "$SESSION_NAME" 2>/dev/null; then
|
if ! tmux has-session -t "$SESSION_NAME" 2>/dev/null; then
|
||||||
log "no active exec session — spawning"
|
log "no active exec session — spawning"
|
||||||
RESULT=$(bash "$SCRIPT_DIR/exec-session.sh" "$PROJECT_TOML" 2>>"$LOG_FILE")
|
bash "$SCRIPT_DIR/exec-session.sh" "$PROJECT_TOML" 2>>"$LOG_FILE" || {
|
||||||
if [ "$RESULT" != "STARTED" ] && [ "$RESULT" != "ACTIVE" ]; then
|
log "ERROR: failed to start exec session"
|
||||||
log "ERROR: failed to start exec session (got: ${RESULT})"
|
[ -n "$THREAD_ID" ] && matrix_send "exec" "❌ Could not start executive assistant session" "$THREAD_ID" >/dev/null 2>&1 || true
|
||||||
matrix_send "exec" "❌ Could not start executive assistant session" "$THREAD_ID" >/dev/null 2>&1 || true
|
|
||||||
exit 1
|
exit 1
|
||||||
fi
|
}
|
||||||
# Give Claude a moment to process the initial prompt
|
# Wait for Claude to process the initial prompt
|
||||||
sleep 3
|
agent_wait_for_claude_ready "$SESSION_NAME" 120 || {
|
||||||
|
log "ERROR: session not ready after spawn"
|
||||||
|
exit 1
|
||||||
|
}
|
||||||
fi
|
fi
|
||||||
|
|
||||||
|
# ── Snapshot pane before injection ──────────────────────────────────────
|
||||||
|
BEFORE_LINES=$(tmux capture-pane -t "$SESSION_NAME" -p 2>/dev/null | wc -l)
|
||||||
|
IDLE_MARKER="/tmp/claude-idle-${SESSION_NAME}.ts"
|
||||||
|
rm -f "$IDLE_MARKER"
|
||||||
|
|
||||||
# ── Inject message ──────────────────────────────────────────────────────
|
# ── Inject message ──────────────────────────────────────────────────────
|
||||||
INJECT_MSG="Message from ${SENDER}:
|
INJECT_MSG="Message from ${SENDER}:
|
||||||
|
|
||||||
${MESSAGE}"
|
${MESSAGE}"
|
||||||
|
|
||||||
log "injecting message from ${SENDER}: ${MESSAGE:0:100}"
|
log "injecting message from ${SENDER}: ${MESSAGE:0:100}"
|
||||||
|
agent_inject_into_session "$SESSION_NAME" "$INJECT_MSG"
|
||||||
|
|
||||||
INJECT_TMP=$(mktemp /tmp/exec-inject-XXXXXX)
|
# ── Wait for Claude to finish responding ────────────────────────────────
|
||||||
printf '%s' "$INJECT_MSG" > "$INJECT_TMP"
|
|
||||||
tmux load-buffer -b "exec-msg" "$INJECT_TMP" || true
|
|
||||||
tmux paste-buffer -t "$SESSION_NAME" -b "exec-msg" || true
|
|
||||||
sleep 0.5
|
|
||||||
tmux send-keys -t "$SESSION_NAME" "" Enter || true
|
|
||||||
tmux delete-buffer -b "exec-msg" 2>/dev/null || true
|
|
||||||
rm -f "$INJECT_TMP"
|
|
||||||
|
|
||||||
# ── Capture response ───────────────────────────────────────────────────
|
|
||||||
# Poll tmux pane content for the response markers
|
|
||||||
log "waiting for response (timeout: ${CAPTURE_TIMEOUT}s)"
|
|
||||||
rm -f "$RESPONSE_FILE"
|
|
||||||
|
|
||||||
ELAPSED=0
|
ELAPSED=0
|
||||||
POLL_INTERVAL=3
|
POLL=5
|
||||||
while [ "$ELAPSED" -lt "$CAPTURE_TIMEOUT" ]; do
|
while [ "$ELAPSED" -lt "$RESPONSE_TIMEOUT" ]; do
|
||||||
sleep "$POLL_INTERVAL"
|
sleep "$POLL"
|
||||||
ELAPSED=$((ELAPSED + POLL_INTERVAL))
|
ELAPSED=$((ELAPSED + POLL))
|
||||||
|
|
||||||
# Capture recent pane content (last 200 lines)
|
if [ -f "$IDLE_MARKER" ]; then
|
||||||
PANE_CONTENT=$(tmux capture-pane -t "$SESSION_NAME" -p -S -200 2>/dev/null || true)
|
log "response complete after ${ELAPSED}s"
|
||||||
|
break
|
||||||
if echo "$PANE_CONTENT" | grep -q "EXEC-RESPONSE-END"; then
|
|
||||||
# Extract response between markers
|
|
||||||
RESPONSE=$(echo "$PANE_CONTENT" | sed -n '/---EXEC-RESPONSE-START---/,/---EXEC-RESPONSE-END---/p' \
|
|
||||||
| grep -v "EXEC-RESPONSE-START\|EXEC-RESPONSE-END" \
|
|
||||||
| tail -n +1)
|
|
||||||
|
|
||||||
if [ -n "$RESPONSE" ]; then
|
|
||||||
printf '%s' "$RESPONSE" > "$RESPONSE_FILE"
|
|
||||||
log "response captured (${#RESPONSE} chars)"
|
|
||||||
break
|
|
||||||
fi
|
|
||||||
fi
|
fi
|
||||||
|
|
||||||
# Check if session died
|
|
||||||
if ! tmux has-session -t "$SESSION_NAME" 2>/dev/null; then
|
if ! tmux has-session -t "$SESSION_NAME" 2>/dev/null; then
|
||||||
log "ERROR: exec session died while waiting for response"
|
log "ERROR: exec session died while waiting for response"
|
||||||
matrix_send "exec" "❌ Executive assistant session ended unexpectedly" "$THREAD_ID" >/dev/null 2>&1 || true
|
[ -n "$THREAD_ID" ] && matrix_send "exec" "❌ Executive assistant session ended unexpectedly" "$THREAD_ID" >/dev/null 2>&1 || true
|
||||||
exit 1
|
exit 1
|
||||||
fi
|
fi
|
||||||
done
|
done
|
||||||
|
|
||||||
# ── Post response to Matrix ────────────────────────────────────────────
|
if [ "$ELAPSED" -ge "$RESPONSE_TIMEOUT" ]; then
|
||||||
if [ -f "$RESPONSE_FILE" ] && [ -s "$RESPONSE_FILE" ]; then
|
log "WARN: response timeout after ${RESPONSE_TIMEOUT}s"
|
||||||
RESPONSE=$(cat "$RESPONSE_FILE")
|
[ -n "$THREAD_ID" ] && matrix_send "exec" "⚠️ Still thinking... (response not ready within ${RESPONSE_TIMEOUT}s)" "$THREAD_ID" >/dev/null 2>&1 || true
|
||||||
# Truncate if too long for Matrix (64KB limit, keep under 4KB for readability)
|
exit 0
|
||||||
if [ ${#RESPONSE} -gt 4000 ]; then
|
|
||||||
RESPONSE="${RESPONSE:0:3950}
|
|
||||||
|
|
||||||
(truncated — full response in exec journal)"
|
|
||||||
fi
|
|
||||||
matrix_send "exec" "$RESPONSE" "$THREAD_ID" >/dev/null 2>&1 || true
|
|
||||||
log "response posted to Matrix thread"
|
|
||||||
|
|
||||||
# Journal the exchange
|
|
||||||
JOURNAL_DIR="$PROJECT_REPO_ROOT/exec/journal"
|
|
||||||
mkdir -p "$JOURNAL_DIR"
|
|
||||||
JOURNAL_FILE="$JOURNAL_DIR/$(date -u +%Y-%m-%d).md"
|
|
||||||
{
|
|
||||||
echo ""
|
|
||||||
echo "## $(date -u +%H:%M) UTC — ${SENDER}"
|
|
||||||
echo ""
|
|
||||||
echo "**Q:** ${MESSAGE}"
|
|
||||||
echo ""
|
|
||||||
echo "**A:** ${RESPONSE}"
|
|
||||||
echo ""
|
|
||||||
echo "---"
|
|
||||||
} >> "$JOURNAL_FILE"
|
|
||||||
log "exchange logged to $(basename "$JOURNAL_FILE")"
|
|
||||||
else
|
|
||||||
log "WARNING: no response captured within ${CAPTURE_TIMEOUT}s"
|
|
||||||
matrix_send "exec" "⚠️ Still thinking... (response not ready within ${CAPTURE_TIMEOUT}s, session is still active)" "$THREAD_ID" >/dev/null 2>&1 || true
|
|
||||||
fi
|
fi
|
||||||
|
|
||||||
rm -f "$RESPONSE_FILE"
|
# ── Capture response (pane diff) ────────────────────────────────────────
|
||||||
|
RESPONSE=$(tmux capture-pane -t "$SESSION_NAME" -p -S -500 2>/dev/null \
|
||||||
|
| tail -n +"$((BEFORE_LINES + 1))" \
|
||||||
|
| grep -v '^❯' | grep -v '^$' \
|
||||||
|
| head -100)
|
||||||
|
|
||||||
|
if [ -z "$RESPONSE" ]; then
|
||||||
|
log "WARN: empty response captured"
|
||||||
|
RESPONSE="(processed your message but produced no visible output)"
|
||||||
|
fi
|
||||||
|
|
||||||
|
# ── Post response to Matrix ────────────────────────────────────────────
|
||||||
|
if [ ${#RESPONSE} -gt 3500 ]; then
|
||||||
|
RESPONSE="${RESPONSE:0:3500}
|
||||||
|
|
||||||
|
(truncated — full response in exec journal)"
|
||||||
|
fi
|
||||||
|
|
||||||
|
if [ -n "$THREAD_ID" ]; then
|
||||||
|
matrix_send "exec" "$RESPONSE" "$THREAD_ID" >/dev/null 2>&1 || true
|
||||||
|
else
|
||||||
|
matrix_send "exec" "$RESPONSE" "" "exec" >/dev/null 2>&1 || true
|
||||||
|
fi
|
||||||
|
log "response posted to Matrix"
|
||||||
|
|
||||||
|
# ── Journal the exchange ───────────────────────────────────────────────
|
||||||
|
JOURNAL_DIR="$PROJECT_REPO_ROOT/exec/journal"
|
||||||
|
mkdir -p "$JOURNAL_DIR"
|
||||||
|
{
|
||||||
|
echo ""
|
||||||
|
echo "## $(date -u +%H:%M) UTC — ${SENDER}"
|
||||||
|
echo ""
|
||||||
|
echo "**Q:** ${MESSAGE}"
|
||||||
|
echo ""
|
||||||
|
echo "**A:** ${RESPONSE}"
|
||||||
|
echo ""
|
||||||
|
echo "---"
|
||||||
|
} >> "$JOURNAL_DIR/$(date -u +%Y-%m-%d).md"
|
||||||
|
log "exchange logged to journal"
|
||||||
|
|
|
||||||
|
|
@ -178,14 +178,9 @@ PROJECT_REPO_ROOT=${PROJECT_REPO_ROOT}
|
||||||
PRIMARY_BRANCH=${PRIMARY_BRANCH}
|
PRIMARY_BRANCH=${PRIMARY_BRANCH}
|
||||||
PHASE_FILE=${PHASE_FILE}
|
PHASE_FILE=${PHASE_FILE}
|
||||||
|
|
||||||
## Response format
|
## How this works
|
||||||
When responding to the executive, write your response between these markers:
|
You are in a persistent tmux session. Messages from the executive arrive via
|
||||||
\`\`\`
|
Matrix. Just respond naturally — your output is captured automatically.
|
||||||
---EXEC-RESPONSE-START---
|
|
||||||
Your response here.
|
|
||||||
---EXEC-RESPONSE-END---
|
|
||||||
\`\`\`
|
|
||||||
This allows the output capture to extract and post your response to Matrix.
|
|
||||||
|
|
||||||
## Phase protocol
|
## Phase protocol
|
||||||
When the executive ends the conversation (says goodbye, done, etc.):
|
When the executive ends the conversation (says goodbye, done, etc.):
|
||||||
|
|
|
||||||
|
|
@ -345,10 +345,12 @@ Interpret this response and decide how to proceed."
|
||||||
# Route message to exec session — spawn on demand if needed
|
# Route message to exec session — spawn on demand if needed
|
||||||
EXEC_PROJECT=$(awk -F'\t' -v id="$THREAD_ROOT" '$1 == id {print $5}' "$THREAD_MAP" 2>/dev/null || true)
|
EXEC_PROJECT=$(awk -F'\t' -v id="$THREAD_ROOT" '$1 == id {print $5}' "$THREAD_MAP" 2>/dev/null || true)
|
||||||
EXEC_PROJECT="${EXEC_PROJECT:-${PROJECT_NAME:-disinto}}"
|
EXEC_PROJECT="${EXEC_PROJECT:-${PROJECT_NAME:-disinto}}"
|
||||||
|
EXEC_TOML="${FACTORY_ROOT}/projects/${EXEC_PROJECT}.toml"
|
||||||
|
[ -f "$EXEC_TOML" ] || EXEC_TOML=""
|
||||||
|
|
||||||
# Delegate entirely to exec-inject.sh (handles spawn + inject + capture + Matrix post)
|
# Delegate to exec-inject.sh (handles spawn + inject + capture + Matrix post)
|
||||||
bash "${FACTORY_ROOT}/exec/exec-inject.sh" "$SENDER" "$BODY" "$THREAD_ROOT" \
|
nohup bash "${FACTORY_ROOT}/exec/exec-inject.sh" "$SENDER" "$BODY" "$THREAD_ROOT" \
|
||||||
"${FACTORY_ROOT}/projects/${EXEC_PROJECT}.toml" >> "$LOGFILE" 2>&1 &
|
"$EXEC_TOML" >> "$LOGFILE" 2>&1 &
|
||||||
log "exec message from ${SENDER} dispatched to exec-inject.sh"
|
log "exec message from ${SENDER} dispatched to exec-inject.sh"
|
||||||
|
|
||||||
if ! grep -qF "$THREAD_ROOT" "$ACKED_FILE" 2>/dev/null; then
|
if ! grep -qF "$THREAD_ROOT" "$ACKED_FILE" 2>/dev/null; then
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue