aicodeman 1.18.2 → 1.18.4

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.
Files changed (80) hide show
  1. package/README.md +14 -0
  2. package/dist/hooks-config.d.ts +27 -0
  3. package/dist/hooks-config.d.ts.map +1 -1
  4. package/dist/hooks-config.js +44 -0
  5. package/dist/hooks-config.js.map +1 -1
  6. package/dist/web/public/admin-ui.js.gz +0 -0
  7. package/dist/web/public/api-client.c9b1cddc.js.gz +0 -0
  8. package/dist/web/public/{app.445f1584.js → app.e2cb9065.js} +1 -1
  9. package/dist/web/public/{app.445f1584.js.br → app.e2cb9065.js.br} +0 -0
  10. package/dist/web/public/{app.445f1584.js.gz → app.e2cb9065.js.gz} +0 -0
  11. package/dist/web/public/approvals-ui.js +24 -15
  12. package/dist/web/public/approvals-ui.js.br +0 -0
  13. package/dist/web/public/approvals-ui.js.gz +0 -0
  14. package/dist/web/public/{constants.822a486d.js → constants.04b7b284.js} +34 -20
  15. package/dist/web/public/constants.04b7b284.js.br +0 -0
  16. package/dist/web/public/constants.04b7b284.js.gz +0 -0
  17. package/dist/web/public/cron-ui.js.gz +0 -0
  18. package/dist/web/public/entrance-animations.js.gz +0 -0
  19. package/dist/web/public/home-sessions.js.gz +0 -0
  20. package/dist/web/public/i18n.86757605.js.gz +0 -0
  21. package/dist/web/public/image-input.ee16ad88.js.gz +0 -0
  22. package/dist/web/public/index.html +13 -6
  23. package/dist/web/public/index.html.br +0 -0
  24. package/dist/web/public/index.html.gz +0 -0
  25. package/dist/web/public/input-cjk.63794d0b.js.gz +0 -0
  26. package/dist/web/public/keyboard-accessory.0454a3e9.js.gz +0 -0
  27. package/dist/web/public/mobile-handlers.bc45f976.js.gz +0 -0
  28. package/dist/web/public/mobile-overview.js.gz +0 -0
  29. package/dist/web/public/mobile.1eef31fe.css.gz +0 -0
  30. package/dist/web/public/notification-manager.5d229063.js.gz +0 -0
  31. package/dist/web/public/orchestrator-panel.js.gz +0 -0
  32. package/dist/web/public/panels-ui.1e4cb4b2.js.gz +0 -0
  33. package/dist/web/public/ralph-panel.6de2d0f8.js.gz +0 -0
  34. package/dist/web/public/ralph-wizard.13a1831e.js.gz +0 -0
  35. package/dist/web/public/readmymind-ui.js.gz +0 -0
  36. package/dist/web/public/respawn-ui.ff0dae4c.js.gz +0 -0
  37. package/dist/web/public/sanitize-html.bc7078d6.js.gz +0 -0
  38. package/dist/web/public/session-lineage.js +35 -1
  39. package/dist/web/public/session-lineage.js.br +0 -0
  40. package/dist/web/public/session-lineage.js.gz +0 -0
  41. package/dist/web/public/{session-ui.20be58fa.js → session-ui.c67565f2.js} +3 -3
  42. package/dist/web/public/session-ui.c67565f2.js.br +0 -0
  43. package/dist/web/public/session-ui.c67565f2.js.gz +0 -0
  44. package/dist/web/public/settings-ui.eacb4f92.js.gz +0 -0
  45. package/dist/web/public/styles.d2fa3a17.css +1 -0
  46. package/dist/web/public/styles.d2fa3a17.css.br +0 -0
  47. package/dist/web/public/styles.d2fa3a17.css.gz +0 -0
  48. package/dist/web/public/subagent-windows.2a192c3e.js.gz +0 -0
  49. package/dist/web/public/sw.js.gz +0 -0
  50. package/dist/web/public/terminal-ui.c182a68d.js.gz +0 -0
  51. package/dist/web/public/ultracode-panel.js.gz +0 -0
  52. package/dist/web/public/ultracode-windows.js.gz +0 -0
  53. package/dist/web/public/upload.html.gz +0 -0
  54. package/dist/web/public/vendor/dompurify.min.js.gz +0 -0
  55. package/dist/web/public/vendor/marked.min.js.gz +0 -0
  56. package/dist/web/public/vendor/xterm-addon-fit.min.js.gz +0 -0
  57. package/dist/web/public/vendor/xterm-addon-serialize.min.js.gz +0 -0
  58. package/dist/web/public/vendor/xterm-addon-unicode11.min.js.gz +0 -0
  59. package/dist/web/public/vendor/xterm-addon-webgl.min.js.gz +0 -0
  60. package/dist/web/public/vendor/xterm-predictive-echo.6cec0fba.js.gz +0 -0
  61. package/dist/web/public/vendor/xterm-zerolag-input.137ad9f0.js.gz +0 -0
  62. package/dist/web/public/vendor/xterm.css.gz +0 -0
  63. package/dist/web/public/vendor/xterm.min.js.gz +0 -0
  64. package/dist/web/public/voice-input.4ec670b0.js.gz +0 -0
  65. package/dist/web/public/voice-pcm-worklet.js.gz +0 -0
  66. package/dist/web/public/webview-tabs.js.gz +0 -0
  67. package/dist/web/routes/session-routes.d.ts.map +1 -1
  68. package/dist/web/routes/session-routes.js +18 -1
  69. package/dist/web/routes/session-routes.js.map +1 -1
  70. package/package.json +1 -1
  71. package/skills/codeman/SKILL.md +57 -19
  72. package/skills/codeman/preamble.sh +158 -0
  73. package/skills/codeman/reference/recipes.md +1 -1
  74. package/dist/web/public/constants.822a486d.js.br +0 -0
  75. package/dist/web/public/constants.822a486d.js.gz +0 -0
  76. package/dist/web/public/session-ui.20be58fa.js.br +0 -0
  77. package/dist/web/public/session-ui.20be58fa.js.gz +0 -0
  78. package/dist/web/public/styles.70bf9c2a.css +0 -1
  79. package/dist/web/public/styles.70bf9c2a.css.br +0 -0
  80. package/dist/web/public/styles.70bf9c2a.css.gz +0 -0
@@ -41,7 +41,32 @@ the preamble to a file once and source it afterwards, rather than re-pasting a
41
41
  hundred-odd lines at the top of every call (a half-re-pasted preamble used to be the
42
42
  single most likely way to break a run).
43
43
 
44
- Run this block once per Codeman session:
44
+ **Codeman seeds the preamble file for you** when it spawns a claude session (server
45
+ 1.18.3+), so the bootstrap is usually nothing at all: these are the two lines every
46
+ later call opens with, and your first REAL call performs them anyway:
47
+
48
+ ```bash
49
+ . "${XDG_CACHE_HOME:-$HOME/.cache}/codeman-agent-$CODEMAN_SESSION_ID.sh" 2>/dev/null
50
+ [ "${CODEMAN_PREAMBLE:-}" = 1.18.3 ] || { echo "preamble missing or stale; run the full §0 block"; exit 1; }
51
+ ```
52
+
53
+ ⚠️ **Never spend a Bash call on this check alone.** §1's block opens with this same
54
+ loader, so when §1 is the job, start there: the check rides the spawn call for free,
55
+ and a standalone "preamble OK" call buys nothing while costing a full model turn
56
+ (measured live: a lone check plus the deliberation around it added ~6 s to a 28 s
57
+ two-worker run). §0 is done the moment any job call passes its opening check. Only
58
+ when a call reports missing or stale, run the full block below once — and run it
59
+ **verbatim**: paste it as-is, never re-type it, trim it, or "extract the parts you
60
+ need". A hand-assembled
61
+ preamble is the documented failure mode of this skill: one live run rebuilt it
62
+ "minimally" and lost the `X-Codeman-Parent-Session` header (every worker spawned with
63
+ no lineage arc in the web UI) and the fast-path functions (the spawn fell back to a
64
+ serial quick-start loop plus pid polls), turning a ten-second job into a fifty-second
65
+ one. If your harness directs temporary files into a scratchpad directory, that
66
+ directive covers task scratch, not this file: it is a per-session cache that every
67
+ later call re-sources by this exact path, so keep the path below. If you must relocate
68
+ it anyway, copy the block's content byte-for-byte unchanged and source your path in
69
+ every later call instead.
45
70
 
46
71
  ```bash
47
72
  test "${CODEMAN_MUX:-}" = 1 || { echo "Not inside a Codeman-managed session; refusing to act."; exit 1; }
@@ -50,8 +75,8 @@ PRE="${XDG_CACHE_HOME:-$HOME/.cache}/codeman-agent-$CODEMAN_SESSION_ID.sh"
50
75
  mkdir -p "$(dirname "$PRE")"
51
76
  # Rewrite unless the file already ends with THIS version's stamp, so a stale or a
52
77
  # half-written file self-heals here instead of costing you a round trip to rm it.
53
- grep -qs '^CODEMAN_PREAMBLE=1.18.2$' "$PRE" || (umask 077; cat > "$PRE" <<'PREAMBLE'
54
- # ---- Codeman agent preamble 1.18.2 (written by the SKILL.md §0 bootstrap) ----
78
+ grep -qs '^CODEMAN_PREAMBLE=1.18.3$' "$PRE" || (umask 077; cat > "$PRE" <<'PREAMBLE'
79
+ # ---- Codeman agent preamble 1.18.3 (seeded by Codeman at session spawn; the SKILL.md §0 bootstrap rewrites it when missing or stale) ----
55
80
  API="${CODEMAN_API_URL:?CODEMAN_API_URL not set; refusing to guess}"
56
81
  SELF="${CODEMAN_SESSION_ID:?CODEMAN_SESSION_ID not set}"
57
82
  # Credentials, cheapest first. Your session has usually INHERITED the server's
@@ -105,8 +130,10 @@ _composer_up() { # <sid> <timeoutMs> -> "true"/"false". `shift+tab` is the one
105
130
  # composer draws, and pid!=null proved startup, never readiness.
106
131
  spawn_worker() {
107
132
  local name="${1:?spawn_worker needs a case name}" mode="${2:-claude}" q sid cp r
133
+ # parentSessionId doubles the CURL header, so a spawn_worker copied off the shared
134
+ # curl (or a body someone rebuilt from this recipe) still carries its lineage.
108
135
  q=$("${CURL[@]}" -X POST "$API/api/v1/quick-start" -H 'Content-Type: application/json' \
109
- -d "$(jq -nc --arg n "$name" --arg m "$mode" '{caseName:$n,mode:$m}')")
136
+ -d "$(jq -nc --arg n "$name" --arg m "$mode" --arg p "$SELF" '{caseName:$n,mode:$m,parentSessionId:$p}')")
110
137
  sid=$(jq -r 'if .success then .data.sessionId else empty end' <<<"$q")
111
138
  # NOT retryable in a loop: every quick-start failure code is terminal (§5.1).
112
139
  [ -n "$sid" ] || { jq -c '{error,errorCode}' <<<"$q" >&2; return 1; }
@@ -206,18 +233,14 @@ last_text() {
206
233
  # The stamp is the LAST line on purpose (a truncated write leaves it unset) and is kept
207
234
  # bare on purpose: the write condition above anchors on it with $, so an inline comment
208
235
  # here would fail that match and rewrite this file on every single bootstrap.
209
- CODEMAN_PREAMBLE=1.18.2
236
+ CODEMAN_PREAMBLE=1.18.3
210
237
  PREAMBLE
211
238
  )
212
- . "$PRE"; [ "${CODEMAN_PREAMBLE:-}" = 1.18.2 ] || { echo "preamble at $PRE is stale or truncated: rm it and re-run this block"; exit 1; }
239
+ . "$PRE"; [ "${CODEMAN_PREAMBLE:-}" = 1.18.3 ] || { echo "preamble at $PRE is stale or truncated: rm it and re-run this block"; exit 1; }
213
240
  ```
214
241
 
215
- Every later Bash call that touches the API starts with these two lines instead:
216
-
217
- ```bash
218
- . "${XDG_CACHE_HOME:-$HOME/.cache}/codeman-agent-$CODEMAN_SESSION_ID.sh" 2>/dev/null
219
- [ "${CODEMAN_PREAMBLE:-}" = 1.18.2 ] || { echo "preamble missing or stale; re-run the §0 bootstrap"; exit 1; }
220
- ```
242
+ Every later Bash call that touches the API starts with the same two loader lines from
243
+ the top of this section.
221
244
 
222
245
  Why it is built this way, all of it load-bearing:
223
246
 
@@ -254,13 +277,18 @@ plain-text 401: see §6 and [the symptom gallery](reference/endpoints.md#symptom
254
277
  block is the whole thing. Run it, report, and stop reading. §2 onward is for jobs this
255
278
  does not cover; you are not being careless by not reading them.**
256
279
 
257
- Fill in the case names and the prompts. Everything below is `spawn_workers` /
258
- `sendwait` / `last_text` / `delete_session` from the §0 preamble, so there is nothing
259
- to assemble and no per-call body to hand-build.
280
+ Fill in the case names and the prompts, then run it as your FIRST Bash call: no
281
+ standalone preamble check before it (line one below IS that check), and no
282
+ reconnaissance. `ls ~/codeman-cases` answers nothing this block needs: invented
283
+ fresh names need no lookup, and `spawn_worker` refuses a name that already exists
284
+ rather than silently reusing it. Everything below is `spawn_workers` / `sendwait` /
285
+ `last_text` / `delete_session` from the §0 preamble, so there is nothing to assemble
286
+ and no per-call body to hand-build.
260
287
 
261
288
  ```bash
262
- . "${XDG_CACHE_HOME:-$HOME/.cache}/codeman-agent-$CODEMAN_SESSION_ID.sh" # §0
263
- N=(alpha beta) # one FRESH case name per worker
289
+ . "${XDG_CACHE_HOME:-$HOME/.cache}/codeman-agent-$CODEMAN_SESSION_ID.sh" 2>/dev/null # §0 loader
290
+ [ "${CODEMAN_PREAMBLE:-}" = 1.18.3 ] || { echo "preamble missing or stale; run the full §0 block"; exit 1; }
291
+ N=(alpha beta) # INVENT one fresh case name per worker; never list cases first
264
292
  T=('reply with one line: the absolute path of your working directory'
265
293
  'reply with one line: your model name') # tasks, same order as N
266
294
 
@@ -287,12 +315,22 @@ done; rm -rf "$D"
287
315
 
288
316
  Measured against a live 1.18.0 server: two cold workers spawned and ready in **6.3 s**,
289
317
  both turns dispatched and both answers read in **4.0 s** more. If your run takes minutes,
290
- the time went into deliberation, not the API. The three things that actually cost time:
318
+ the time went into deliberation, not the API. The four things that actually cost time:
291
319
 
292
320
  - **Spawning serially.** One worker per Bash call is one model turn per worker. `&` plus
293
321
  `wait`, as above, makes N workers cost about what one costs.
322
+ - **Reconnaissance turns before the spawn.** A standalone preamble check, an
323
+ `ls ~/codeman-cases`, a `list_sessions` "to see what is there": each is a whole
324
+ model turn spent learning something this block already handles (line one performs
325
+ the preamble check, invented names need no listing, and `spawn_worker` refuses
326
+ collisions). A live two-worker run spent ~12 s of its 28 s total on exactly two
327
+ such turns; the API work in between was under 10 s.
294
328
  - **Re-deriving the happy path** from §5.1 + §5.2 + §5.3 + §5.10. That is what the
295
- preamble functions exist to end. Compose them; do not rebuild them.
329
+ preamble functions exist to end. Compose them; do not rebuild them. The tells that
330
+ you are rebuilding anyway: a `for` loop around `quick-start`, a poll on `.data.pid`,
331
+ a bespoke `ready()` or `spawn()` of your own. Each is a worse copy of a function
332
+ already sitting in your preamble; the live run that wrote them spawned serially,
333
+ polled pid for nothing, and shipped its workers without lineage.
296
334
  - **Verifying what is already checked for you.** Two verifications specifically are not
297
335
  worth a call here, because `spawn_worker` carries them: the hooks check (it refuses a
298
336
  name that resolved to a hook-less directory with one local grep, so a worker it hands
@@ -0,0 +1,158 @@
1
+ # ---- Codeman agent preamble 1.18.3 (seeded by Codeman at session spawn; the SKILL.md §0 bootstrap rewrites it when missing or stale) ----
2
+ API="${CODEMAN_API_URL:?CODEMAN_API_URL not set; refusing to guess}"
3
+ SELF="${CODEMAN_SESSION_ID:?CODEMAN_SESSION_ID not set}"
4
+ # Credentials, cheapest first. Your session has usually INHERITED the server's
5
+ # CODEMAN_PASSWORD already (§6 explains why, and what to do when it has not);
6
+ # the data dir's .env is the documented fallback, the same one `codeman attach`
7
+ # reads. The data dir is wherever the hook-secret file lives. Values may be
8
+ # quoted or `export`-prefixed.
9
+ ENV_FILE="${CODEMAN_HOOK_SECRET_FILE:+${CODEMAN_HOOK_SECRET_FILE%hook-secret}.env}"
10
+ envval() { sed -n "s/^\(export \)\{0,1\}$1=//p" "$ENV_FILE" | tail -1 | sed 's/^"\(.*\)"$/\1/; s/^'\''\(.*\)'\''$/\1/'; }
11
+ if [ -z "${CODEMAN_PASSWORD:-}" ] && [ -n "$ENV_FILE" ] && [ -f "$ENV_FILE" ]; then
12
+ CODEMAN_USERNAME=$(envval CODEMAN_USERNAME)
13
+ CODEMAN_PASSWORD=$(envval CODEMAN_PASSWORD)
14
+ fi
15
+ AUTH=(); [ -n "${CODEMAN_PASSWORD:-}" ] && AUTH=(-u "${CODEMAN_USERNAME:-admin}:$CODEMAN_PASSWORD")
16
+ # -k: harmless on http, required on https (self-signed cert).
17
+ # X-Codeman-Parent-Session: tags workers YOU spawn as your children, so the web UI can
18
+ # draw the lineage. Set once here and every present and future create call carries it;
19
+ # it is ignored on every other endpoint. Purely cosmetic (see §5.1) and it can never
20
+ # fail a spawn, so there is no case where you would want to leave it off.
21
+ CURL=(curl -sk "${AUTH[@]}" -H "X-Codeman-Parent-Session: $SELF")
22
+ CID=codeman-agent-1 # FIXED literal, never "agent-$$": see below
23
+
24
+ # Fail-CLOSED session delete. The DELETE lives INSIDE the guard on purpose: the older
25
+ # `is_self "$SID" || curl -X DELETE ...` shape failed OPEN, because an undefined
26
+ # is_self exits 127 and the `||` branch then ran the delete completely unguarded.
27
+ # Undefined delete_session is "command not found", which deletes nothing.
28
+ delete_session() {
29
+ local id="${1:-}"
30
+ [ -n "$id" ] || { echo "refusing: empty session id"; return 1; }
31
+ [ "${#SELF}" -ge 8 ] || { echo "refusing: \$SELF unset or too short to prove this is not me"; return 1; }
32
+ # ids appear in full AND 8-char form (Docker exports a truncated $SELF; mux names and
33
+ # UI surfaces carry 8-char ids), so compare by prefix in BOTH directions. Equality or
34
+ # a one-directional check each miss a real combination, and the miss deletes you.
35
+ case "$id" in "$SELF"*) echo "refusing: $id is me"; return 1 ;; esac
36
+ case "$SELF" in "$id"*) echo "refusing: $id is me"; return 1 ;; esac
37
+ "${CURL[@]}" -X DELETE "$API/api/v1/sessions/$id"
38
+ }
39
+
40
+ # ---- fast path: the four verbs, already written. §1 composes them. ----
41
+ _composer_up() { # <sid> <timeoutMs> -> "true"/"false". `shift+tab` is the one token
42
+ "${CURL[@]}" -G "$API/api/v1/sessions/$1/wait-output" \
43
+ --data-urlencode 'match=shift+tab' --data-urlencode 'from=buffer' \
44
+ --data-urlencode "timeout=$2" | jq -r '.data.wait.matched // false'
45
+ }
46
+ # spawn_worker <caseName> [mode] -> session id on stdout, diagnostics on stderr.
47
+ # quick-start AND readiness in one call, with a strict contract: NON-EMPTY stdout means
48
+ # a READY claude worker in a hook-carrying case. Anything less is rc 1 with EMPTY
49
+ # stdout, and the half-spawned session is deleted here rather than handed back, because
50
+ # a worker that never drew its composer would eat the task prompt with its trust
51
+ # dialog. There is deliberately no pid poll: wait-output already blocks until the
52
+ # composer draws, and pid!=null proved startup, never readiness.
53
+ spawn_worker() {
54
+ local name="${1:?spawn_worker needs a case name}" mode="${2:-claude}" q sid cp r
55
+ # parentSessionId doubles the CURL header, so a spawn_worker copied off the shared
56
+ # curl (or a body someone rebuilt from this recipe) still carries its lineage.
57
+ q=$("${CURL[@]}" -X POST "$API/api/v1/quick-start" -H 'Content-Type: application/json' \
58
+ -d "$(jq -nc --arg n "$name" --arg m "$mode" --arg p "$SELF" '{caseName:$n,mode:$m,parentSessionId:$p}')")
59
+ sid=$(jq -r 'if .success then .data.sessionId else empty end' <<<"$q")
60
+ # NOT retryable in a loop: every quick-start failure code is terminal (§5.1).
61
+ [ -n "$sid" ] || { jq -c '{error,errorCode}' <<<"$q" >&2; return 1; }
62
+ [ "$mode" = claude ] || { printf '%s\n' "$sid"; return 0; } # only claude draws a composer
63
+ # quick-start RESOLVES the name before creating: a linked case or an existing dir
64
+ # wins over a fresh scratch case, so "created => hooks" is only true after this one
65
+ # local grep (the same marker the server itself checks for). No marker means sendwait
66
+ # would false-resolve on flapping idle, possibly inside the user's REAL repo: refuse
67
+ # rather than run the job there.
68
+ cp=$(jq -r '.data.casePath // empty' <<<"$q")
69
+ grep -qs '/api/hook-event' "$cp/.claude/settings.local.json" || {
70
+ echo "case '$name' resolved to '$cp', which has no Codeman hooks (linked or pre-existing?): pick an unused name, or work §5.1+§5.5 by hand" >&2
71
+ delete_session "$sid" >/dev/null; return 1; }
72
+ # Short composer wait FIRST, then the trust-dialog probe: a case still showing the
73
+ # dialog can never pass the composer wait, so probing early keeps a cold case from
74
+ # paying the whole long wait before the fallback even runs (§5.2). A warm case
75
+ # matches in under a second and never reaches the probe.
76
+ r=$(_composer_up "$sid" 5000)
77
+ if [ "$r" != true ]; then
78
+ if "${CURL[@]}" -G "$API/api/v1/sessions/$sid/wait-output" \
79
+ --data-urlencode 'match=trust' --data-urlencode 'from=buffer' --data-urlencode 'timeout=2000' \
80
+ | jq -e '.data.wait.matched' >/dev/null; then
81
+ # Codeman's own auto-accept gives up after 90 s / 3 tries; this is that bounded fallback.
82
+ "${CURL[@]}" -X POST "$API/api/v1/sessions/$sid/input" -H 'Content-Type: application/json' \
83
+ -d "$(jq -nc --arg c "$CID-$sid" '{input:"\r",useMux:true,clientId:$c,seq:1}')" >/dev/null
84
+ fi
85
+ r=$(_composer_up "$sid" 45000)
86
+ fi
87
+ [ "$r" = true ] || { echo "worker $sid never drew a composer; deleted it. Retry by hand via the §5.2 ladder (its billed stage-4 probe included)" >&2
88
+ delete_session "$sid" >/dev/null; return 1; }
89
+ printf '%s\n' "$sid"
90
+ }
91
+ # spawn_workers <caseName>... -> one "<caseName> <sessionId>" line per worker, in order;
92
+ # the sessionId column is EMPTY for a spawn that failed (stderr has why). CONCURRENT:
93
+ # N workers cost about what one costs. Spawning them one Bash call at a time is the
94
+ # single biggest avoidable delay in this skill. Names must be UNIQUE: two workers in
95
+ # one case directory co-edit the same tree (§4), so a repeat is an error here, not a race.
96
+ spawn_workers() {
97
+ local d n i=0
98
+ [ "$#" -gt 0 ] || { echo "spawn_workers: no case names given" >&2; return 1; }
99
+ [ -z "$(printf '%s\n' "$@" | sort | uniq -d)" ] || { echo "spawn_workers: duplicate case names" >&2; return 1; }
100
+ d=$(mktemp -d "${TMPDIR:-/tmp}/codeman-spawn.XXXXXX") || return 1
101
+ for n in "$@"; do ( spawn_worker "$n" > "$d/$i" ) & i=$((i+1)); done
102
+ wait
103
+ i=0; for n in "$@"; do printf '%s %s\n' "$n" "$(cat "$d/$i" 2>/dev/null)"; i=$((i+1)); done
104
+ rm -rf "$d"
105
+ }
106
+ # sendwait <sid> <prompt> [seq] -> blocks until that worker's turn ENDS (~10 min ceiling
107
+ # across its two waits). One billed turn. The \r and the per-worker clientId are applied
108
+ # here, which is why you never hand-build this body. seq defaults to the CURRENT EPOCH
109
+ # SECOND so that every new prompt is a new frame: the server drops any (clientId,seq)
110
+ # pair it has already applied, so a fixed default would make every later prompt to that
111
+ # worker a silent no-op that still "succeeds" and reports the previous turn's state.
112
+ # Pass seq explicitly for exactly one reason: resending a possibly-delivered frame as a
113
+ # deliberate duplicate, at the SAME number (§5.3).
114
+ # Delivery is SELF-HEALING: an Ink repaint occasionally eats the Enter, leaving the
115
+ # typed prompt stranded on the composer while a long wait runs its whole timeout
116
+ # (observed live). So the first wait is short; on its timeout a bare \r goes out (the
117
+ # missing Enter when the prompt is stranded, a no-op when the turn is genuinely
118
+ # running), then the ORIGINAL frame is resent unchanged, which the server takes as a
119
+ # tagged duplicate: it re-waits without retyping (§5.3). Trustworthy only for a claude
120
+ # worker spawn_worker handed back (hooks vetted); hook-less workspaces and other modes
121
+ # resolve on flapping idle: markers instead (§5.5).
122
+ sendwait() {
123
+ local sid="${1:?}" p="${2:?}" seq="${3:-$(date +%s)}" body r
124
+ body=$(jq -nc --arg p "$p" --arg c "$CID-$sid" --argjson s "$seq" \
125
+ '{input:($p+"\r"),useMux:true,clientId:$c,seq:$s,wait:true,waitTimeout:20000}')
126
+ r=$("${CURL[@]}" -X POST "$API/api/v1/sessions/$sid/input" \
127
+ -H 'Content-Type: application/json' --data-binary "$body")
128
+ if jq -e '.data.delivered and .data.wait.timedOut' <<<"$r" >/dev/null 2>&1; then
129
+ "${CURL[@]}" -X POST "$API/api/v1/sessions/$sid/input" -H 'Content-Type: application/json' \
130
+ -d "$(jq -nc --arg c "$CID-$sid" --argjson s "$(date +%s)" \
131
+ '{input:"\r",useMux:true,clientId:$c,seq:$s}')" >/dev/null
132
+ r=$("${CURL[@]}" -X POST "$API/api/v1/sessions/$sid/input" \
133
+ -H 'Content-Type: application/json' --data-binary "$(jq -c '.waitTimeout=580000' <<<"$body")")
134
+ fi
135
+ printf '%s\n' "$r"
136
+ }
137
+ # last_text <sid> [prev] -> that worker's last assistant message. Polled, because the
138
+ # transcript write LAGS the stop signal, and "some text exists" is not "THIS turn's
139
+ # text exists": right after a SECOND turn on the same worker the endpoint still serves
140
+ # the previous answer for a beat (observed live). When reading consecutive turns, pass
141
+ # the previous answer as [prev]: the poll then holds out for text that differs from it,
142
+ # falling back to whatever it last saw if the budget runs dry, so an honestly repeated
143
+ # answer still comes back. Non-zero exit means the worker really never wrote one.
144
+ last_text() {
145
+ local t="" prev="${2:-}"
146
+ for _ in $(seq 1 15); do
147
+ t=$("${CURL[@]}" "$API/api/v1/sessions/$1/last-response" | jq -r '.data.text // empty')
148
+ [ -n "$t" ] && [ "$t" != "$prev" ] && { printf '%s\n' "$t"; return 0; }
149
+ sleep 1
150
+ done
151
+ [ -n "$t" ] && { printf '%s\n' "$t"; return 0; }
152
+ return 1
153
+ }
154
+
155
+ # The stamp is the LAST line on purpose (a truncated write leaves it unset) and is kept
156
+ # bare on purpose: the write condition above anchors on it with $, so an inline comment
157
+ # here would fail that match and rewrite this file on every single bootstrap.
158
+ CODEMAN_PREAMBLE=1.18.3
@@ -21,7 +21,7 @@ by sourcing the preamble file the §0 bootstrap wrote, and checking its version
21
21
 
22
22
  ```bash
23
23
  . "${XDG_CACHE_HOME:-$HOME/.cache}/codeman-agent-$CODEMAN_SESSION_ID.sh" 2>/dev/null
24
- [ "${CODEMAN_PREAMBLE:-}" = 1.18.2 ] || { echo "preamble missing or stale; re-run the §0 bootstrap"; exit 1; }
24
+ [ "${CODEMAN_PREAMBLE:-}" = 1.18.3 ] || { echo "preamble missing or stale; re-run the §0 bootstrap"; exit 1; }
25
25
  ```
26
26
 
27
27
  Do **not** re-paste the preamble body into each call. Sourcing it is what retires the