aicodeman 1.14.0 → 1.14.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +12 -0
- package/dist/cli.d.ts.map +1 -1
- package/dist/cli.js +225 -28
- package/dist/cli.js.map +1 -1
- package/dist/config/webview-limits.d.ts +17 -1
- package/dist/config/webview-limits.d.ts.map +1 -1
- package/dist/config/webview-limits.js +18 -2
- package/dist/config/webview-limits.js.map +1 -1
- package/dist/hooks-config.d.ts +44 -0
- package/dist/hooks-config.d.ts.map +1 -1
- package/dist/hooks-config.js +153 -2
- package/dist/hooks-config.js.map +1 -1
- package/dist/web/ports/config-port.d.ts +2 -0
- package/dist/web/ports/config-port.d.ts.map +1 -1
- package/dist/web/public/admin-ui.js.gz +0 -0
- package/dist/web/public/api-client.c9b1cddc.js.gz +0 -0
- package/dist/web/public/{app.b24f795e.js → app.94825d85.js} +9 -9
- package/dist/web/public/app.94825d85.js.br +0 -0
- package/dist/web/public/app.94825d85.js.gz +0 -0
- package/dist/web/public/constants.e7175b95.js.gz +0 -0
- package/dist/web/public/cron-ui.js.gz +0 -0
- package/dist/web/public/entrance-animations.js.gz +0 -0
- package/dist/web/public/i18n.da34cfaf.js.gz +0 -0
- package/dist/web/public/image-input.ee16ad88.js.gz +0 -0
- package/dist/web/public/index.html +18 -7
- package/dist/web/public/index.html.br +0 -0
- package/dist/web/public/index.html.gz +0 -0
- package/dist/web/public/input-cjk.63794d0b.js.gz +0 -0
- package/dist/web/public/keyboard-accessory.9936227d.js.gz +0 -0
- package/dist/web/public/mobile-handlers.32fdd57f.js.gz +0 -0
- package/dist/web/public/mobile-overview.js.gz +0 -0
- package/dist/web/public/mobile.32cacde5.css.gz +0 -0
- package/dist/web/public/notification-manager.5d229063.js.gz +0 -0
- package/dist/web/public/orchestrator-panel.js.gz +0 -0
- package/dist/web/public/panels-ui.1d4203e0.js.gz +0 -0
- package/dist/web/public/ralph-panel.6de2d0f8.js.gz +0 -0
- package/dist/web/public/ralph-wizard.13a1831e.js.gz +0 -0
- package/dist/web/public/respawn-ui.ff0dae4c.js.gz +0 -0
- package/dist/web/public/sanitize-html.bc7078d6.js.gz +0 -0
- package/dist/web/public/session-ui.1356a4f4.js.gz +0 -0
- package/dist/web/public/{settings-ui.ea63d5db.js → settings-ui.b41d33a5.js} +2 -2
- package/dist/web/public/settings-ui.b41d33a5.js.br +0 -0
- package/dist/web/public/settings-ui.b41d33a5.js.gz +0 -0
- package/dist/web/public/styles.c4e58e13.css +1 -0
- package/dist/web/public/styles.c4e58e13.css.br +0 -0
- package/dist/web/public/styles.c4e58e13.css.gz +0 -0
- package/dist/web/public/subagent-windows.4cc8b005.js.gz +0 -0
- package/dist/web/public/sw.js.gz +0 -0
- package/dist/web/public/{terminal-ui.d5ae6dcf.js → terminal-ui.bc6819a9.js} +1 -1
- package/dist/web/public/terminal-ui.bc6819a9.js.br +0 -0
- package/dist/web/public/{terminal-ui.d5ae6dcf.js.gz → terminal-ui.bc6819a9.js.gz} +0 -0
- package/dist/web/public/ultracode-panel.js.gz +0 -0
- package/dist/web/public/ultracode-windows.js.gz +0 -0
- package/dist/web/public/upload.html.gz +0 -0
- package/dist/web/public/vendor/dompurify.min.js.gz +0 -0
- package/dist/web/public/vendor/marked.min.js.gz +0 -0
- package/dist/web/public/vendor/xterm-addon-fit.min.js.gz +0 -0
- package/dist/web/public/vendor/xterm-addon-serialize.min.js.gz +0 -0
- package/dist/web/public/vendor/xterm-addon-unicode11.min.js.gz +0 -0
- package/dist/web/public/vendor/xterm-addon-webgl.min.js.gz +0 -0
- package/dist/web/public/vendor/xterm-zerolag-input.137ad9f0.js.gz +0 -0
- package/dist/web/public/vendor/xterm.css.gz +0 -0
- package/dist/web/public/vendor/xterm.min.js.gz +0 -0
- package/dist/web/public/voice-input.085e9e73.js.gz +0 -0
- package/dist/web/public/webview-tabs.js +6 -2
- package/dist/web/public/webview-tabs.js.br +0 -0
- package/dist/web/public/webview-tabs.js.gz +0 -0
- package/dist/web/routes/session-routes.d.ts.map +1 -1
- package/dist/web/routes/session-routes.js +16 -1
- package/dist/web/routes/session-routes.js.map +1 -1
- package/dist/web/routes/webview-routes.d.ts.map +1 -1
- package/dist/web/routes/webview-routes.js +50 -3
- package/dist/web/routes/webview-routes.js.map +1 -1
- package/dist/web/schemas.d.ts +1 -0
- package/dist/web/schemas.d.ts.map +1 -1
- package/dist/web/schemas.js +8 -0
- package/dist/web/schemas.js.map +1 -1
- package/dist/web/server.d.ts +1 -0
- package/dist/web/server.d.ts.map +1 -1
- package/dist/web/server.js +7 -0
- package/dist/web/server.js.map +1 -1
- package/package.json +1 -1
- package/skills/codeman/SKILL.md +94 -24
- package/skills/codeman/reference/endpoints.md +21 -6
- package/skills/codeman/reference/recipes.md +55 -25
- package/dist/web/public/app.b24f795e.js.br +0 -0
- package/dist/web/public/app.b24f795e.js.gz +0 -0
- package/dist/web/public/settings-ui.ea63d5db.js.br +0 -0
- package/dist/web/public/settings-ui.ea63d5db.js.gz +0 -0
- package/dist/web/public/styles.b10e5edd.css +0 -1
- package/dist/web/public/styles.b10e5edd.css.br +0 -0
- package/dist/web/public/styles.b10e5edd.css.gz +0 -0
- package/dist/web/public/terminal-ui.d5ae6dcf.js.br +0 -0
package/skills/codeman/SKILL.md
CHANGED
|
@@ -17,7 +17,24 @@ sessions. Every recipe below was verified live. Full endpoint tables and
|
|
|
17
17
|
troubleshooting: [reference/endpoints.md](reference/endpoints.md). Worked multi-worker
|
|
18
18
|
flows: [reference/recipes.md](reference/recipes.md).
|
|
19
19
|
|
|
20
|
-
## 0. Guard
|
|
20
|
+
## 0. Guard, and the one thing that breaks every recipe below
|
|
21
|
+
|
|
22
|
+
⚠️ **Your shell state does not survive between tool calls.** Each Bash call starts a
|
|
23
|
+
fresh shell, so `$API`, `$SELF`, the `CURL` array and `delete_session` are all gone by
|
|
24
|
+
the next call, and `$$` is a different pid. Three consequences, all of which have
|
|
25
|
+
teeth:
|
|
26
|
+
|
|
27
|
+
- **Re-run this entire preamble at the top of every Bash call that touches the API.**
|
|
28
|
+
Running it once and assuming it stuck is the single most likely way to break a run.
|
|
29
|
+
- **Never re-paste only half of it.** The delete guard below is written so that a
|
|
30
|
+
missing definition deletes nothing, but that only holds if you never hand-roll a
|
|
31
|
+
`DELETE` of your own.
|
|
32
|
+
- **Never put `$$` in a `clientId`.** It changes per call, so the "resend the identical
|
|
33
|
+
request" loop in §3 would stop being a duplicate and would **retype the prompt**,
|
|
34
|
+
submitting the turn twice. Use a fixed literal (`codeman-agent-1` below).
|
|
35
|
+
|
|
36
|
+
Only real environment variables (`CODEMAN_*`) survive, which is why this preamble
|
|
37
|
+
rebuilds everything else from them.
|
|
21
38
|
|
|
22
39
|
```bash
|
|
23
40
|
test "${CODEMAN_MUX:-}" = 1 || { echo "Not inside a Codeman-managed session; refusing to act."; exit 1; }
|
|
@@ -39,13 +56,35 @@ if [ -z "${CODEMAN_PASSWORD:-}" ]; then # stock installs: install.sh puts it
|
|
|
39
56
|
UNIT="$HOME/.config/systemd/user/codeman-web.service"
|
|
40
57
|
PLIST="$HOME/Library/LaunchAgents/com.codeman.web.plist"
|
|
41
58
|
if [ -f "$UNIT" ]; then
|
|
42
|
-
|
|
59
|
+
# install.sh backslash-escapes " and \ in the unit value; undo it or a password
|
|
60
|
+
# containing either recovers wrong and auth fails.
|
|
61
|
+
CODEMAN_PASSWORD=$(sed -n 's/^Environment="CODEMAN_PASSWORD=\(.*\)"$/\1/p' "$UNIT" | head -1 | sed 's/\\\(["\\]\)/\1/g')
|
|
43
62
|
elif [ -f "$PLIST" ]; then
|
|
44
|
-
|
|
63
|
+
# install.sh XML-escapes the plist value; undo it (& LAST, mirroring escape order).
|
|
64
|
+
CODEMAN_PASSWORD=$(awk '/<key>CODEMAN_PASSWORD<\/key>/{getline; print}' "$PLIST" | sed -n 's/.*<string>\(.*\)<\/string>.*/\1/p' \
|
|
65
|
+
| sed -e 's/</</g' -e 's/>/>/g' -e 's/&/\&/g')
|
|
45
66
|
fi
|
|
46
67
|
fi
|
|
47
68
|
AUTH=(); [ -n "${CODEMAN_PASSWORD:-}" ] && AUTH=(-u "${CODEMAN_USERNAME:-admin}:$CODEMAN_PASSWORD")
|
|
48
69
|
CURL=(curl -sk "${AUTH[@]}") # -k: harmless on http, required on https (self-signed cert)
|
|
70
|
+
|
|
71
|
+
# Fail-CLOSED session delete. The DELETE lives INSIDE the guard on purpose: the older
|
|
72
|
+
# `is_self "$SID" || curl -X DELETE ...` shape failed OPEN, because an undefined
|
|
73
|
+
# is_self exits 127 and the `||` branch then ran the delete completely unguarded.
|
|
74
|
+
# Undefined delete_session is "command not found", which deletes nothing.
|
|
75
|
+
delete_session() {
|
|
76
|
+
local id="${1:-}"
|
|
77
|
+
[ -n "$id" ] || { echo "refusing: empty session id"; return 1; }
|
|
78
|
+
[ "${#SELF}" -ge 8 ] || { echo "refusing: \$SELF unset or too short to prove this is not me"; return 1; }
|
|
79
|
+
# ids appear in full AND 8-char form (Docker exports a truncated $SELF; mux names and
|
|
80
|
+
# UI surfaces carry 8-char ids), so compare by prefix in BOTH directions. Equality or
|
|
81
|
+
# a one-directional check each miss a real combination, and the miss deletes you.
|
|
82
|
+
case "$id" in "$SELF"*) echo "refusing: $id is me"; return 1 ;; esac
|
|
83
|
+
case "$SELF" in "$id"*) echo "refusing: $id is me"; return 1 ;; esac
|
|
84
|
+
"${CURL[@]}" -X DELETE "$API/api/v1/sessions/$id"
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
CID=codeman-agent-1 # FIXED literal, never "agent-$$" (see §0)
|
|
49
88
|
```
|
|
50
89
|
|
|
51
90
|
- If `CODEMAN_MUX` is not `1`, **stop and say so**. Do not guess an API URL; a server
|
|
@@ -67,20 +106,16 @@ CURL=(curl -sk "${AUTH[@]}") # -k: harmless on http, required on https (self-s
|
|
|
67
106
|
|
|
68
107
|
You are yourself a session on this server, and the API has **no undo**.
|
|
69
108
|
|
|
70
|
-
- **Never act on your own session
|
|
109
|
+
- **Never act on your own session, and know that `delete_session` is the ONLY guard.**
|
|
71
110
|
The server has no self-protection: a session that DELETEs its own id succeeds and
|
|
72
|
-
dies silently (verified live).
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
One-directional or equality checks each miss a real combination (full `$SELF` vs
|
|
81
|
-
a target you transcribed in 8-char form, or truncated `$SELF` vs a full target)
|
|
82
|
-
and the miss deletes you. Check `is_self` before every `DELETE`, kill, respawn,
|
|
83
|
-
or input call.
|
|
111
|
+
dies silently (verified live). **Always delete through `delete_session "$SID"` from
|
|
112
|
+
§0; never write a bare `curl -X DELETE` and never reintroduce the
|
|
113
|
+
`is_self … || curl -X DELETE …` shape.** That older form failed open: with the
|
|
114
|
+
function undefined (a half-re-pasted preamble, see §0) bash returns 127, the `||`
|
|
115
|
+
branch fires, and the delete runs with no self-check at all. Wrapping the request
|
|
116
|
+
inside the guard is what makes a lost preamble delete nothing instead of deleting
|
|
117
|
+
you. Apply the same prefix-both-directions reasoning before any kill, respawn, or
|
|
118
|
+
input call you write by hand.
|
|
84
119
|
- **Mutating calls you may make unprompted** (this is an allowlist):
|
|
85
120
|
`POST /api/v1/quick-start`, `POST /api/v1/sessions/:id/input`, and
|
|
86
121
|
`DELETE /api/v1/sessions/:id` **only** for a session you created in this
|
|
@@ -162,15 +197,25 @@ the composer is not) and always pays it in full before the fallback runs — the
|
|
|
162
197
|
budget belongs to stage 3, after the dialog is answered:
|
|
163
198
|
|
|
164
199
|
```bash
|
|
165
|
-
|
|
166
|
-
|
|
200
|
+
# ALWAYS check .success: on failure `.data.sessionId` is null, jq -r prints the string
|
|
201
|
+
# "null", and the flow below then burns its full readiness budget against
|
|
202
|
+
# /api/v1/sessions/null before reporting jq noise instead of the actual cause.
|
|
203
|
+
Q=$("${CURL[@]}" -X POST "$API/api/v1/quick-start" -H 'Content-Type: application/json' \
|
|
204
|
+
-d '{"caseName":"worker-1","mode":"claude"}')
|
|
205
|
+
SID=$(jq -r 'if .success then .data.sessionId else empty end' <<<"$Q")
|
|
206
|
+
if [ -z "$SID" ]; then
|
|
207
|
+
# SESSION_BUSY here is the 50-session cap, not the waiter cap; FORBIDDEN/CONFLICT/
|
|
208
|
+
# OPERATION_FAILED/INVALID_INPUT are the others. None are retryable in a loop.
|
|
209
|
+
jq -c '{error, errorCode}' <<<"$Q"; echo "quick-start failed; stopping."
|
|
210
|
+
exit 1
|
|
211
|
+
fi
|
|
167
212
|
for _ in $(seq 1 30); do # bounded: a bad SID would otherwise poll forever
|
|
168
213
|
[ "$("${CURL[@]}" "$API/api/v1/sessions/$SID" | jq '.data.pid')" != null ] && break; sleep 1
|
|
169
214
|
done
|
|
170
215
|
# ⚠️ pid != null proves STARTUP only, never life: a worker that later dies inside
|
|
171
216
|
# its pane keeps status "idle" and a pid (the local tmux attach client, not the
|
|
172
217
|
# worker). The death check is wait?until=exit, below.
|
|
173
|
-
CID
|
|
218
|
+
SEQ=1 # $CID came from the §0 preamble; do NOT rebuild it from $$
|
|
174
219
|
# the composer's status bar ("bypass permissions on") is the ready marker — Codeman
|
|
175
220
|
# spawns claude in bypass mode. Single-token matches only: TUI text is space-less.
|
|
176
221
|
R=$("${CURL[@]}" -G "$API/api/v1/sessions/$SID/wait-output" \
|
|
@@ -245,15 +290,40 @@ SEQ=$((SEQ+1))
|
|
|
245
290
|
The typed line shows `${M}_…`, the real output shows `DONE_… rc=<exit code>`, and the
|
|
246
291
|
snippet carries the exit code back to you.
|
|
247
292
|
|
|
248
|
-
**Read a worker's
|
|
249
|
-
`
|
|
293
|
+
**Read a worker's answer.** For `claude` and `codex` workers this is the read path:
|
|
294
|
+
`last-response` returns the agent's final message as clean text, taken from the
|
|
295
|
+
transcript rather than the screen, so it carries none of the TUI's box-drawing or
|
|
296
|
+
repaint noise.
|
|
297
|
+
|
|
298
|
+
```bash
|
|
299
|
+
for _ in $(seq 1 10); do # the transcript write LAGS the stop signal
|
|
300
|
+
TXT=$("${CURL[@]}" "$API/api/v1/sessions/$SID/last-response" | jq -r '.data.text')
|
|
301
|
+
[ -n "$TXT" ] && break; sleep 1
|
|
302
|
+
done
|
|
303
|
+
printf '%s\n' "$TXT"
|
|
304
|
+
```
|
|
305
|
+
|
|
306
|
+
`.data` is `{text, timestamp}`. ⚠️ **Poll it, do not read it once.** `text` is written
|
|
307
|
+
from the transcript file, which is flushed slightly *after* the `stop` hook fires, so a
|
|
308
|
+
single read taken the instant send-and-wait returns comes back `""` even though the
|
|
309
|
+
turn finished (verified live: empty on the first call, full text seconds later). `text`
|
|
310
|
+
is also `""` before the worker's first completed turn, and always `""` for modes with
|
|
311
|
+
no transcript (`shell`, `opencode`, `gemini`, `antigravity`, verified live), which is
|
|
312
|
+
why the loop above is bounded rather than open-ended. Fall back to the terminal buffer there, tail in **bytes**
|
|
313
|
+
(`textOutput` in `GET .../output` stays empty for interactive sessions; don't use it):
|
|
250
314
|
|
|
251
315
|
```bash
|
|
252
316
|
"${CURL[@]}" "$API/api/v1/sessions/$SID/terminal?tail=3000" | jq -r '.data.terminalBuffer' \
|
|
253
317
|
| sed -e 's/\x1b\[[0-9;?]*[a-zA-Z]//g' -e 's/\x1b([B0]//g' | grep -v '^[[:space:]]*$' | tail -30
|
|
254
318
|
```
|
|
255
319
|
|
|
256
|
-
|
|
320
|
+
⚠️ Do not use that pipeline to read a **claude/codex** answer. A full-screen TUI draws
|
|
321
|
+
with cursor moves, so the stripped buffer is largely one long line: `tail -30` has
|
|
322
|
+
almost nothing to split on and you get a wall of repaint noise with the answer buried
|
|
323
|
+
in it (verified live, side by side with `last-response` returning the exact prose).
|
|
324
|
+
The terminal buffer is for *diagnosis* (is my prompt sitting unsubmitted?), not for
|
|
325
|
+
reading answers. Avoid `?full=1` (entire tmux scrollback, a context bomb) unless doing
|
|
326
|
+
a post-mortem.
|
|
257
327
|
|
|
258
328
|
**Detect a dead worker cheaply**: `GET .../wait?until=exit&timeout=60000` answers
|
|
259
329
|
immediately (`signal:"exit"`, `immediate:true`) if the PTY is gone — including a
|
|
@@ -262,10 +332,10 @@ as `status:"idle"` with a pid (that pid is the local tmux attach client, not the
|
|
|
262
332
|
worker). The wait routes are the only liveness check; a worker dying while a wait
|
|
263
333
|
is parked resolves it within ~3 s. A session deleted mid-wait resolves in ~1 s.
|
|
264
334
|
|
|
265
|
-
**Clean up** — only ids you created,
|
|
335
|
+
**Clean up** — only ids you created, one at a time, always through the §0 helper:
|
|
266
336
|
|
|
267
337
|
```bash
|
|
268
|
-
|
|
338
|
+
delete_session "$SID"
|
|
269
339
|
```
|
|
270
340
|
|
|
271
341
|
Everything else (endpoint tables, per-mode signal table, error codes, capacity
|
|
@@ -14,7 +14,7 @@ Every JSON response: `{"success":true,"data":…}` or
|
|
|
14
14
|
| `INVALID_INPUT` | 400 | malformed request; the message names the bad field |
|
|
15
15
|
| `UNAUTHORIZED` | 401 | auth required or failed (send `-u user:password`). ⚠️ The 401 body is plain text, NOT this envelope — `jq` dies with a parse error, see the guard in SKILL.md |
|
|
16
16
|
| `NOT_FOUND` | 404 | no such session, or one this caller does not own |
|
|
17
|
-
| `SESSION_BUSY` | 409 | this session's waiter cap (16, combined signal+output) is full |
|
|
17
|
+
| `SESSION_BUSY` | 409 | on a **wait**: this session's waiter cap (16, combined signal+output) is full. On **quick-start**: the 50-session cap is full, so clean up before starting more |
|
|
18
18
|
| `CONFLICT` / `ALREADY_EXISTS` | 409 | conflicts with current state |
|
|
19
19
|
| `OPERATION_FAILED` | 422 | well-formed but could not be completed |
|
|
20
20
|
| `RATE_LIMITED` | 429 | per-owner or process-wide waiter pool is full — back off; switching sessions will not help |
|
|
@@ -32,16 +32,19 @@ Every JSON response: `{"success":true,"data":…}` or
|
|
|
32
32
|
| unified list incl. history | `GET /api/v1/sessions/unified` → `.data.sessions[]` (NOT `.data[]`), and it folds in transcript history from the whole machine — never use it to verify cleanup; `GET /api/v1/sessions` is the cleanup check |
|
|
33
33
|
| start case + session in one call | `POST /api/v1/quick-start` |
|
|
34
34
|
| send input | `POST /api/v1/sessions/:id/input` |
|
|
35
|
-
| read
|
|
35
|
+
| **read a worker's answer** (claude/codex) | `GET /api/v1/sessions/:id/last-response` → `.data.{text,timestamp}` — clean transcript text, no TUI noise. ⚠️ **Poll it**: the transcript flush lags the `stop` signal, so a read taken the instant send-and-wait returns is `""` (verified live). Also `""` before the first completed turn, and always `""` for `shell`/`opencode`/`gemini`/`antigravity` (no transcript) |
|
|
36
|
+
| read terminal (tail is in **BYTES**, raw ANSI) | `GET /api/v1/sessions/:id/terminal?tail=3000` → `.data.terminalBuffer` — for *diagnosis* (unsubmitted prompt?), not for reading answers |
|
|
36
37
|
| full tmux scrollback (context bomb; post-mortems only) | `GET /api/v1/sessions/:id/terminal?full=1` |
|
|
37
|
-
| background agents
|
|
38
|
+
| background agents, one session | `GET /api/v1/sessions/:id/subagents` |
|
|
39
|
+
| background agents, global list | `GET /api/v1/subagents` (admin-only in multi-user mode) |
|
|
38
40
|
| server status / version | `GET /api/v1/status` → `.data.version` |
|
|
39
|
-
| delete one session (yours, `
|
|
41
|
+
| delete one session (yours only, via `delete_session`) | `DELETE /api/v1/sessions/:id` — never call it bare; the fail-closed helper in SKILL.md §0 is the only self-protection that exists |
|
|
40
42
|
|
|
41
43
|
⚠️ `GET /api/v1/sessions/:id/output` → `.data.textOutput` looks like the obvious read
|
|
42
44
|
but stays **empty for interactive tmux-backed sessions** (it is fed only by the legacy
|
|
43
|
-
JSON-stream path). Verified empty on live claude and shell sessions.
|
|
44
|
-
`terminal?tail=`
|
|
45
|
+
JSON-stream path). Verified empty on live claude and shell sessions. Use
|
|
46
|
+
`last-response` for claude/codex answers; only fall back to `terminal?tail=` for
|
|
47
|
+
hook-less modes, or to diagnose a prompt that was never submitted, and strip ANSI:
|
|
45
48
|
|
|
46
49
|
```bash
|
|
47
50
|
… | jq -r '.data.terminalBuffer' | sed -e 's/\x1b\[[0-9;?]*[a-zA-Z]//g' -e 's/\x1b([B0]//g'
|
|
@@ -53,6 +56,18 @@ JSON-stream path). Verified empty on live claude and shell sessions. Read
|
|
|
53
56
|
`.data.{sessionId, caseName, casePath}`. Creates the case directory (a real directory
|
|
54
57
|
on the user's disk) if missing — do not retry it in a loop, and remember the name.
|
|
55
58
|
|
|
59
|
+
⚠️ **Branch on `.success` before reading `.data.sessionId`.** On any failure the field
|
|
60
|
+
is absent, `jq -r` prints the literal string `null`, and every later call then targets
|
|
61
|
+
`/api/v1/sessions/null`, burning the full readiness budget and reporting jq noise
|
|
62
|
+
instead of the real cause. Failure modes here are `SESSION_BUSY` (the **50-session
|
|
63
|
+
cap**, not the waiter cap), `FORBIDDEN`, `CONFLICT`, `OPERATION_FAILED` and
|
|
64
|
+
`INVALID_INPUT`; none of them are retryable in a loop.
|
|
65
|
+
|
|
66
|
+
⚠️ `caseName` resolves through the linked-cases registry first, so a name that happens
|
|
67
|
+
to match a case the user linked in lands in that **real repo**, not a fresh scratch
|
|
68
|
+
directory. Pick distinctive scratch names, and use a linked name deliberately when you
|
|
69
|
+
do want a worker in an existing checkout.
|
|
70
|
+
|
|
56
71
|
`POST /api/v1/sessions/:id/input` body:
|
|
57
72
|
`{"input":"one line\r","useMux":true,"clientId":"agent-1","seq":1}` plus optionally
|
|
58
73
|
`"wait"` / `"waitTimeout"` (below).
|
|
@@ -1,10 +1,17 @@
|
|
|
1
1
|
# Worked orchestration flows
|
|
2
2
|
|
|
3
|
-
Loaded on demand from the `codeman` skill. Every flow assumes the
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
**
|
|
7
|
-
|
|
3
|
+
Loaded on demand from the `codeman` skill. Every flow assumes the SKILL.md §0 preamble
|
|
4
|
+
is in scope (`$API`, `$SELF`, `$CID`, `"${CURL[@]}"`, `delete_session`).
|
|
5
|
+
|
|
6
|
+
⚠️ **That preamble does not survive between tool calls**, so re-run it at the top of
|
|
7
|
+
every Bash call that uses these flows, in full. Re-pasting only part of it is the
|
|
8
|
+
failure mode the fail-closed `delete_session` exists to contain, and a `clientId` you
|
|
9
|
+
rebuild from `$$` changes per call, which turns the duplicate-resend loop in Flow 1
|
|
10
|
+
into a second typed prompt.
|
|
11
|
+
|
|
12
|
+
Track every session id you create; delete them (and only them) when done. The two
|
|
13
|
+
silent killers: **every input ends with `\r`**, and **markers must be split** so the
|
|
14
|
+
typed-line echo does not match them.
|
|
8
15
|
|
|
9
16
|
## Flow 1: claude worker, end to end
|
|
10
17
|
|
|
@@ -13,11 +20,15 @@ the turn to finish, read the answer, clean up. Verified live: the stop hook reso
|
|
|
13
20
|
the send-and-wait within seconds of the turn ending.
|
|
14
21
|
|
|
15
22
|
```bash
|
|
16
|
-
# 1. start (returns before the CLI inside is ready)
|
|
17
|
-
|
|
18
|
-
|
|
23
|
+
# 1. start (returns before the CLI inside is ready). ALWAYS check .success: on failure
|
|
24
|
+
# .data.sessionId is null, jq -r yields the string "null", and every step below
|
|
25
|
+
# then runs against /api/v1/sessions/null and reports jq noise, not the cause.
|
|
26
|
+
Q=$("${CURL[@]}" -X POST "$API/api/v1/quick-start" -H 'Content-Type: application/json' \
|
|
27
|
+
-d '{"caseName":"worker-tests","mode":"claude"}')
|
|
28
|
+
SID=$(jq -r 'if .success then .data.sessionId else empty end' <<<"$Q")
|
|
29
|
+
[ -n "$SID" ] || { jq -c '{error, errorCode}' <<<"$Q"; echo "quick-start failed"; exit 1; }
|
|
19
30
|
CREATED+=("$SID") # the cleanup list
|
|
20
|
-
CID
|
|
31
|
+
SEQ=1 # $CID is the fixed literal from §0; never rebuild it from $$
|
|
21
32
|
|
|
22
33
|
# 2. readiness. "wait for idle" or "wait for ❯" is NOT readiness: a fresh session
|
|
23
34
|
# reports idle before anything spawned, and the first-run trust dialog contains ❯.
|
|
@@ -84,13 +95,23 @@ case "$(jq -r '.data.wait.signal' <<<"$R")" in
|
|
|
84
95
|
null) jq -e '.data.wait.ended' <<<"$R" >/dev/null && echo "worker deleted mid-wait" ;;
|
|
85
96
|
esac
|
|
86
97
|
|
|
87
|
-
# 5. read the answer
|
|
88
|
-
#
|
|
89
|
-
|
|
90
|
-
|
|
98
|
+
# 5. read the answer. For a claude worker this is last-response: clean transcript text,
|
|
99
|
+
# no TUI repaint noise. Do NOT scrape the terminal for this — a full-screen TUI
|
|
100
|
+
# draws with cursor moves, so the stripped buffer is nearly one long line and the
|
|
101
|
+
# answer arrives buried in redraw garbage.
|
|
102
|
+
# POLL it: the transcript flush lags the stop signal, so a single read taken the
|
|
103
|
+
# instant step 3 returned comes back "" even though the turn finished (verified live).
|
|
104
|
+
for _ in $(seq 1 10); do
|
|
105
|
+
TXT=$("${CURL[@]}" "$API/api/v1/sessions/$SID/last-response" | jq -r '.data.text')
|
|
106
|
+
[ -n "$TXT" ] && break; sleep 1
|
|
107
|
+
done
|
|
108
|
+
printf '%s\n' "$TXT"
|
|
109
|
+
# (.data is {text,timestamp}; text is also "" before the first completed turn and
|
|
110
|
+
# always "" for shell/opencode/gemini/antigravity, which have no transcript — use
|
|
111
|
+
# the terminal tail there, and here only to diagnose an unsubmitted prompt.)
|
|
91
112
|
|
|
92
|
-
# 6. clean up — exact id, own list only,
|
|
93
|
-
|
|
113
|
+
# 6. clean up — exact id, own list only, through the fail-closed §0 helper
|
|
114
|
+
delete_session "$SID"
|
|
94
115
|
```
|
|
95
116
|
|
|
96
117
|
Increment `SEQ` for every *new* input to the same worker. Reuse the same `SEQ` only to
|
|
@@ -104,8 +125,10 @@ live), so send-and-wait can burn its whole timeout. The reliable pattern is a sp
|
|
|
104
125
|
unique marker plus `wait-output from=buffer`:
|
|
105
126
|
|
|
106
127
|
```bash
|
|
107
|
-
|
|
108
|
-
-d '{"caseName":"builder","mode":"shell"}'
|
|
128
|
+
Q=$("${CURL[@]}" -X POST "$API/api/v1/quick-start" -H 'Content-Type: application/json' \
|
|
129
|
+
-d '{"caseName":"builder","mode":"shell"}')
|
|
130
|
+
SID=$(jq -r 'if .success then .data.sessionId else empty end' <<<"$Q")
|
|
131
|
+
[ -n "$SID" ] || { jq -c '{error, errorCode}' <<<"$Q"; echo "quick-start failed"; exit 1; }
|
|
109
132
|
CREATED+=("$SID")
|
|
110
133
|
for _ in $(seq 1 30); do
|
|
111
134
|
[ "$("${CURL[@]}" "$API/api/v1/sessions/$SID" | jq '.data.pid')" != null ] && break; sleep 1
|
|
@@ -115,7 +138,7 @@ done
|
|
|
115
138
|
# An unsplit marker matches the echo of your own keystrokes before the build runs.
|
|
116
139
|
N="${RANDOM}_$$"; MARK="DONE_$N"
|
|
117
140
|
"${CURL[@]}" -X POST "$API/api/v1/sessions/$SID/input" -H 'Content-Type: application/json' \
|
|
118
|
-
-d '{"input":"M=DONE; npm run build; echo ${M}_'"$N"' rc=$?\r","useMux":true,"clientId":"build-
|
|
141
|
+
-d '{"input":"M=DONE; npm run build; echo ${M}_'"$N"' rc=$?\r","useMux":true,"clientId":"codeman-build-1","seq":1}'
|
|
119
142
|
|
|
120
143
|
for TRY in $(seq 1 30); do # BOUNDED (30 min): a \r-less send makes an uncapped loop infinite
|
|
121
144
|
R=$("${CURL[@]}" -G "$API/api/v1/sessions/$SID/wait-output" \
|
|
@@ -136,8 +159,10 @@ waiter cap is 16 and abandoned concurrent waits pile up against it.
|
|
|
136
159
|
```bash
|
|
137
160
|
declare -A WORKER MARKS
|
|
138
161
|
for task in lint typecheck unit; do
|
|
139
|
-
|
|
140
|
-
-d '{"caseName":"fan-'"$task"'","mode":"shell"}'
|
|
162
|
+
Q=$("${CURL[@]}" -X POST "$API/api/v1/quick-start" -H 'Content-Type: application/json' \
|
|
163
|
+
-d '{"caseName":"fan-'"$task"'","mode":"shell"}')
|
|
164
|
+
SID=$(jq -r 'if .success then .data.sessionId else empty end' <<<"$Q")
|
|
165
|
+
[ -n "$SID" ] || { jq -c '{error, errorCode}' <<<"$Q"; echo "$task: spawn failed"; continue; }
|
|
141
166
|
WORKER[$task]=$SID; CREATED+=("$SID")
|
|
142
167
|
done
|
|
143
168
|
for task in "${!WORKER[@]}"; do
|
|
@@ -147,7 +172,7 @@ for task in "${!WORKER[@]}"; do
|
|
|
147
172
|
done
|
|
148
173
|
N="${task}_${RANDOM}"; MARKS[$task]="DONE_$N"
|
|
149
174
|
"${CURL[@]}" -X POST "$API/api/v1/sessions/$SID/input" -H 'Content-Type: application/json' \
|
|
150
|
-
-d '{"input":"M=DONE; npm run '"$task"'; echo ${M}_'"$N"' rc=$?\r","useMux":true,"clientId":"fan-'
|
|
175
|
+
-d '{"input":"M=DONE; npm run '"$task"'; echo ${M}_'"$N"' rc=$?\r","useMux":true,"clientId":"codeman-fan-'"$task"'","seq":1}'
|
|
151
176
|
done
|
|
152
177
|
for task in "${!WORKER[@]}"; do # sequential gather; each wait blocks until that worker is done
|
|
153
178
|
for TRY in $(seq 1 30); do # BOUNDED per worker, same reasoning as Flow 2
|
|
@@ -171,8 +196,8 @@ other was still running):
|
|
|
171
196
|
|
|
172
197
|
```bash
|
|
173
198
|
sendwait() { # $1=sid $2=prompt $3=seq — assumes the worker passed Flow 1's readiness
|
|
174
|
-
local body; body=$(jq -n --arg p "$2" --argjson s "$3" \
|
|
175
|
-
'{input:($p+"\r"),useMux:true,clientId
|
|
199
|
+
local body; body=$(jq -n --arg p "$2" --argjson s "$3" --arg c "codeman-fan-$1" \
|
|
200
|
+
'{input:($p+"\r"),useMux:true,clientId:$c,seq:$s,wait:true,waitTimeout:600000}')
|
|
176
201
|
"${CURL[@]}" -X POST "$API/api/v1/sessions/$1/input" \
|
|
177
202
|
-H 'Content-Type: application/json' --data-binary "$body" > "/tmp/fan-$1.json"
|
|
178
203
|
}
|
|
@@ -200,7 +225,7 @@ declare -A TOK
|
|
|
200
225
|
for i in 1 2; do
|
|
201
226
|
TOK[$i]="${RANDOM}_$i"
|
|
202
227
|
BODY=$(jq -n --arg p "do task $i; when completely done print the word WORKDONE immediately followed by _${TOK[$i]}" \
|
|
203
|
-
--arg c "fan
|
|
228
|
+
--arg c "codeman-fan-$i" --argjson s 2 '{input:($p+"\r"),useMux:true,clientId:$c,seq:$s}')
|
|
204
229
|
"${CURL[@]}" -X POST "$API/api/v1/sessions/${SIDS[$i]}/input" \
|
|
205
230
|
-H 'Content-Type: application/json' --data-binary "$BODY"
|
|
206
231
|
done
|
|
@@ -237,12 +262,17 @@ At the end of the conversation (or on abort), delete exactly what you created:
|
|
|
237
262
|
|
|
238
263
|
```bash
|
|
239
264
|
for id in "${CREATED[@]}"; do
|
|
240
|
-
|
|
265
|
+
delete_session "$id"
|
|
241
266
|
done
|
|
242
267
|
```
|
|
243
268
|
|
|
244
269
|
- Only ids from your own `CREATED` list. Never enumerate `/api/v1/sessions` and
|
|
245
270
|
delete by pattern; other sessions belong to the user.
|
|
271
|
+
- Always go through `delete_session`. It refuses an empty id, refuses when `$SELF` is
|
|
272
|
+
unset or too short to prove the target is not you, and prefix-checks in both
|
|
273
|
+
directions. A hand-written `curl -X DELETE`, or the old
|
|
274
|
+
`is_self "$id" || curl -X DELETE …`, has none of that: an undefined `is_self` exits
|
|
275
|
+
127 and the `||` branch deletes unguarded.
|
|
246
276
|
- If you created a *case* purely as scratch and the user confirmed it is disposable,
|
|
247
277
|
`DELETE /api/v1/cases/:name` removes it — but that recursively deletes the
|
|
248
278
|
directory from disk, so never do it without the user's explicit go-ahead for that
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|