@humwick/runner 0.130.5 → 0.130.6

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 (211) hide show
  1. package/agent-bin/humwick +2 -2
  2. package/agent-bin/humwick-browser +4 -0
  3. package/hooks/fff-search-redirect/fff-search-redirect.cjs +1 -1
  4. package/hooks/web-tools-redirect/web-tools-redirect.cjs +2 -2
  5. package/package.json +7 -10
  6. package/plugins/browser/skills/playwright-cli/SKILL.md +8 -6
  7. package/plugins/claude/.claude-plugin/plugin.json +3 -3
  8. package/plugins/claude/claude-statusline-probe.cjs +26 -16
  9. package/plugins/claude/hooks/gate-run-guard.provisioned.mjs +47 -17
  10. package/plugins/claude/hooks/gate-run-guard.sh +3 -3
  11. package/plugins/claude/hooks/gate-run-guard.ts +5 -5
  12. package/plugins/claude/hooks/kill-guard.sh +4 -4
  13. package/plugins/claude/hooks/kill-guard.ts +1 -1
  14. package/plugins/claude/hooks/permission-request.sh +1 -1
  15. package/plugins/claude/hooks/post-compact.sh +1 -1
  16. package/plugins/claude/hooks/post-tool-use.sh +3 -3
  17. package/plugins/claude/hooks/pre-compact.sh +2 -2
  18. package/plugins/claude/hooks/read-only-bash-guard.sh +4 -4
  19. package/plugins/claude/hooks/relay-status.sh +32 -32
  20. package/plugins/claude/hooks/session-end.sh +2 -2
  21. package/plugins/claude/hooks/session-start.sh +3 -3
  22. package/plugins/claude/hooks/shell-trust.ts +1 -1
  23. package/plugins/claude/hooks/stop-failure.sh +2 -2
  24. package/plugins/claude/hooks/stop.sh +6 -6
  25. package/plugins/claude/hooks/subagent-skill-policy.cjs +8 -6
  26. package/plugins/claude/hooks/subagent-stop.sh +3 -3
  27. package/plugins/claude/hooks/user-prompt-submit.sh +1 -1
  28. package/plugins/claude/hooks/wait-loop-guard.ts +3 -3
  29. package/plugins/claude/monitors/monitors.json +2 -2
  30. package/plugins/claude/monitors/relay-monitor.provisioned.mjs +96 -62
  31. package/plugins/claude/monitors/relay-monitor.ts +5 -5
  32. package/plugins/claude/skills/label/SKILL.md +3 -3
  33. package/plugins/claude/skills/message/SKILL.md +5 -5
  34. package/plugins/claude/skills/react/SKILL.md +1 -1
  35. package/plugins/claude/skills/read-message/SKILL.md +4 -4
  36. package/plugins/claude/skills/reply/SKILL.md +5 -5
  37. package/plugins/claude/skills/send-claimable/SKILL.md +5 -5
  38. package/plugins/claude/skills/slice-first/SKILL.md +5 -3
  39. package/plugins/claude/skills/slice-first/references/plan-recipe.md +30 -29
  40. package/plugins/claude/skills/status/SKILL.md +1 -1
  41. package/plugins/claude/skills/tags/SKILL.md +5 -5
  42. package/plugins/claude/skills/task/SKILL.md +10 -10
  43. package/plugins/claude-output-style/.claude-plugin/plugin.json +1 -1
  44. package/plugins/claude-output-style/output-styles/{agent-relay.md → humwick.md} +1 -1
  45. package/plugins/codex/skills/guide/SKILL.md +1 -1
  46. package/plugins/codex/skills/label/SKILL.md +3 -3
  47. package/plugins/codex/skills/message/SKILL.md +5 -5
  48. package/plugins/codex/skills/react/SKILL.md +1 -1
  49. package/plugins/codex/skills/read-message/SKILL.md +4 -4
  50. package/plugins/codex/skills/reply/SKILL.md +5 -5
  51. package/plugins/codex/skills/send-claimable/SKILL.md +5 -5
  52. package/plugins/codex/skills/slice-first/SKILL.md +5 -3
  53. package/plugins/codex/skills/slice-first/references/plan-recipe.md +30 -29
  54. package/plugins/codex/skills/status/SKILL.md +1 -1
  55. package/plugins/codex/skills/tags/SKILL.md +5 -5
  56. package/plugins/codex/skills/task/SKILL.md +11 -10
  57. package/src/adapter.ts +95 -92
  58. package/src/adapters/claude-api.ts +8 -11
  59. package/src/adapters/claude-context-command.ts +1 -1
  60. package/src/adapters/claude-credentials.ts +7 -3
  61. package/src/adapters/claude-delivery-lanes.ts +2 -2
  62. package/src/adapters/claude-delivery.ts +16 -16
  63. package/src/adapters/claude-effort.ts +2 -2
  64. package/src/adapters/claude-exit.ts +2 -2
  65. package/src/adapters/claude-headless-mirror.ts +18 -2
  66. package/src/adapters/claude-headless-refusal.ts +3 -3
  67. package/src/adapters/claude-headless.ts +11 -10
  68. package/src/adapters/claude-launch-exit.ts +1 -1
  69. package/src/adapters/claude-monitor-delivery-plan.ts +36 -16
  70. package/src/adapters/claude-permission.ts +5 -5
  71. package/src/adapters/claude-prompt-gates.ts +9 -9
  72. package/src/adapters/claude-prompt-hook.ts +2 -2
  73. package/src/adapters/claude-session-capture.ts +6 -6
  74. package/src/adapters/claude-session-probe.ts +25 -25
  75. package/src/adapters/claude-shutdown.ts +11 -11
  76. package/src/adapters/claude-status-detectors.ts +31 -31
  77. package/src/adapters/claude-subagents.ts +7 -7
  78. package/src/adapters/claude-submit-confirmation.ts +6 -6
  79. package/src/adapters/claude-tmux.ts +43 -43
  80. package/src/adapters/claude-token-usage-capture.ts +5 -5
  81. package/src/adapters/claude-tool-policy.ts +7 -7
  82. package/src/adapters/claude-transcript.ts +24 -24
  83. package/src/adapters/claude.ts +100 -100
  84. package/src/adapters/codex-agent-message-capture.ts +26 -63
  85. package/src/adapters/codex-app-server.ts +1 -1
  86. package/src/adapters/codex-client.ts +9 -9
  87. package/src/adapters/codex-effective-config.ts +1 -1
  88. package/src/adapters/codex-launch-config.ts +5 -5
  89. package/src/adapters/codex-rollout-context.ts +2 -2
  90. package/src/adapters/codex-skill-discovery.ts +1 -1
  91. package/src/adapters/codex-token-usage-capture.ts +16 -16
  92. package/src/adapters/codex-tool-output.ts +4 -4
  93. package/src/adapters/codex-version.ts +3 -3
  94. package/src/adapters/codex.ts +64 -64
  95. package/src/adapters/opencode-api.ts +1 -1
  96. package/src/adapters/opencode-context.ts +20 -22
  97. package/src/adapters/opencode-model-registry.ts +7 -7
  98. package/src/adapters/opencode-provider.ts +8 -8
  99. package/src/adapters/opencode-provisioning.ts +30 -0
  100. package/src/adapters/opencode-server-retry.ts +1 -1
  101. package/src/adapters/opencode-soft-steer.ts +5 -5
  102. package/src/adapters/opencode-spawn.ts +18 -14
  103. package/src/adapters/opencode-token-usage-capture.ts +12 -12
  104. package/src/adapters/opencode-tool-events.ts +15 -15
  105. package/src/adapters/opencode.ts +232 -232
  106. package/src/adapters/project-shell.ts +11 -11
  107. package/src/adapters/service-access.ts +6 -6
  108. package/src/adapters/tmux-offload.ts +9 -9
  109. package/src/adapters/tool-summary.ts +5 -5
  110. package/src/agent-redact.ts +1 -1
  111. package/src/agent-runner/commands/dispatch.ts +10 -10
  112. package/src/agent-runner/commands/lifecycle.ts +2 -2
  113. package/src/agent-runner/commands/prompt.ts +19 -19
  114. package/src/agent-runner/credentials.ts +5 -5
  115. package/src/agent-runner/inbox.ts +1 -1
  116. package/src/agent-runner/lifecycle-milestones.ts +3 -3
  117. package/src/agent-runner/lifecycle-phase.ts +2 -2
  118. package/src/agent-runner/liveness-exchange.ts +4 -10
  119. package/src/agent-runner/registration.ts +8 -8
  120. package/src/agent-runner/runner-context.ts +9 -14
  121. package/src/agent-runner/session-mirror.ts +12 -12
  122. package/src/agent-runner/task-claims.ts +2 -2
  123. package/src/agent-runner/token-usage-coverage.ts +1 -1
  124. package/src/agent-runner/turn-report.ts +12 -12
  125. package/src/api-budget-accounting.ts +3 -3
  126. package/src/area-memory.ts +6 -6
  127. package/src/artifact-path-materialize.ts +1 -1
  128. package/src/async-guard.ts +5 -5
  129. package/src/attachment-cache.ts +17 -22
  130. package/src/background-script-reconcile.ts +6 -6
  131. package/src/bounded-log.ts +1 -1
  132. package/src/busy-reconciler.ts +22 -22
  133. package/src/claim-tracker.ts +3 -3
  134. package/src/claude-settings.ts +8 -8
  135. package/src/claude-system-prompt.ts +1 -1
  136. package/src/claude-workspace-trust.ts +1 -1
  137. package/src/config.ts +62 -61
  138. package/src/context-boundary.ts +5 -5
  139. package/src/context-command.ts +3 -3
  140. package/src/control-server.ts +32 -32
  141. package/src/delivery-result.ts +2 -2
  142. package/src/effort-command.ts +2 -2
  143. package/src/empty-turn.ts +22 -14
  144. package/src/free-port.ts +1 -1
  145. package/src/gate-run-config.ts +3 -3
  146. package/src/host-asset-plan.ts +24 -17
  147. package/src/host-claude-config-worker.ts +2 -2
  148. package/src/host-sources.ts +2 -2
  149. package/src/index.ts +16 -16
  150. package/src/launch-assembly.ts +94 -96
  151. package/src/liveness.ts +15 -15
  152. package/src/logger.ts +3 -3
  153. package/src/mcp-outbox.ts +3 -3
  154. package/src/mcp-preflight.ts +2 -2
  155. package/src/mcp-source-selection.ts +2 -2
  156. package/src/mcp-tool-deferral.ts +1 -1
  157. package/src/mcp-vault-env.ts +2 -2
  158. package/src/message-body-config/config.ts +4 -4
  159. package/src/mid-turn-delivery.ts +2 -2
  160. package/src/native-memory-lever.ts +8 -8
  161. package/src/outbox.ts +15 -15
  162. package/src/pending-permission-store.ts +1 -1
  163. package/src/profile-home.ts +19 -19
  164. package/src/profile-projection.ts +26 -24
  165. package/src/project-instructions.ts +5 -5
  166. package/src/provider-auth-warning.ts +17 -0
  167. package/src/provider-double.ts +1 -1
  168. package/src/provider-hold.ts +3 -3
  169. package/src/provider-home-marker.ts +1 -1
  170. package/src/provider-home-tombstone.ts +1 -1
  171. package/src/provider-input-queue.ts +2 -2
  172. package/src/provider-integration.ts +15 -13
  173. package/src/provider-setup.ts +15 -3
  174. package/src/providers.ts +1 -1
  175. package/src/provisioning.ts +37 -40
  176. package/src/question-decision.ts +2 -2
  177. package/src/quota.ts +0 -6
  178. package/src/rate-limit.ts +24 -13
  179. package/src/refused-prompt-store.ts +2 -2
  180. package/src/relay-injection-events.ts +2 -2
  181. package/src/relay-instructions.ts +5 -5
  182. package/src/relay-mcp-proxy.long-turn-e2e.ts +13 -13
  183. package/src/relay-mcp-proxy.ts +57 -58
  184. package/src/relay-mcp.ts +55 -69
  185. package/src/reply-obligation-cache.ts +9 -9
  186. package/src/response-capture-report.ts +9 -9
  187. package/src/response-routing.ts +1 -1
  188. package/src/runner-core.ts +209 -222
  189. package/src/runner-exit.ts +1 -1
  190. package/src/runner-helpers.ts +10 -10
  191. package/src/runner-insights.ts +4 -2
  192. package/src/runner-mcp-proxy.ts +3 -3
  193. package/src/runner-spawn-config.ts +3 -3
  194. package/src/runtime-token-file.ts +1 -1
  195. package/src/runtime-token-remint.ts +1 -1
  196. package/src/service-access.ts +2 -2
  197. package/src/session-destroy.ts +1 -1
  198. package/src/session-insights.ts +1 -1
  199. package/src/session-scratch.ts +24 -37
  200. package/src/spawn-input.ts +6 -6
  201. package/src/startup-config-fetch.ts +4 -4
  202. package/src/subreaper.ts +3 -5
  203. package/src/test-run-dedup.ts +13 -13
  204. package/src/transcript-context.ts +2 -2
  205. package/src/transport-errors.ts +1 -1
  206. package/src/turn-opener.ts +1 -1
  207. package/src/turn-reconcile-event.ts +2 -2
  208. package/src/vault-proxy-hop.ts +2 -2
  209. package/src/web-tools.ts +2 -2
  210. package/agent-bin/agent-relay-browser +0 -10
  211. package/src/adapters/provider-quota-harvest.ts +0 -76
@@ -1,28 +1,20 @@
1
1
  type EmitAgentMessage = (body: string) => void;
2
- type ResponseEntry = { itemId?: string; body: string };
2
+ type ResponseEntry = { itemId?: string; body: string; phase?: unknown };
3
3
  type CompleteItemOptions = { includeInResponse?: boolean; phase?: unknown };
4
4
 
5
- const MAX_CAPTURED_RESPONSE_CHARS = 16_000;
6
- // `\s` is defined as exactly the character set String#trim strips, so this tests
7
- // `text.trim() !== ""` without allocating the trimmed copy. It is NOT free — matching forces a
8
- // flatten of the accumulated buffer's rope — so it must stay off the per-delta hot path and is
9
- // only used where the pre-#2504 whitespace guard actually changed the outcome.
10
- const NON_WHITESPACE = /\S/;
11
- const TRUNCATION_MARKER_PREFIX = "[truncated:";
12
-
13
5
  export class CodexAgentMessageCapture {
14
6
  private segments: ResponseEntry[][] = [];
15
7
  private currentSegment: ResponseEntry[] = [];
16
8
  private readonly buffers = new Map<string, string>();
17
9
  private readonly flushedLengths = new Map<string, number>();
18
- private readonly responseRecordedLengths = new Map<string, number>();
10
+ private readonly responseItems = new Map<string, ResponseEntry>();
19
11
 
20
12
  reset(): void {
21
13
  this.segments = [];
22
14
  this.currentSegment = [];
23
15
  this.buffers.clear();
24
16
  this.flushedLengths.clear();
25
- this.responseRecordedLengths.clear();
17
+ this.responseItems.clear();
26
18
  }
27
19
 
28
20
  appendDelta(itemId: string | undefined, delta: string): void {
@@ -36,7 +28,7 @@ export class CodexAgentMessageCapture {
36
28
  if (options.includeInResponse === false || options.phase === "commentary") {
37
29
  this.forgetResponseItem(itemId);
38
30
  } else {
39
- this.recordResponseText(itemId, fullText);
31
+ this.recordResponseText(itemId, fullText, options.phase);
40
32
  }
41
33
  if (!itemId) return;
42
34
  this.buffers.delete(itemId);
@@ -51,20 +43,22 @@ export class CodexAgentMessageCapture {
51
43
  }
52
44
 
53
45
  // Deterministic turn-final response: prefer the trailing structured report span, not a generic
54
- // post-handoff coda. The original #1079/#1086 trailing-run rule correctly kept early narration
46
+ // post-handoff coda. The original trailing-run rule correctly kept early narration
55
47
  // out, but it over-trimmed Codex turns shaped "summary -> workspace ready -> Landed..." and made
56
- // report-up deliver only the coda (#1136). We still drop leading narration; we just let structured
48
+ // report-up deliver only the coda. We still drop leading narration; we just let structured
57
49
  // report markers beat a terse final status line.
58
50
  finish(emit: EmitAgentMessage): string | undefined {
59
51
  this.closeSegment(emit);
60
- const trailingSegment = selectFinalResponseSegments(this.segments);
52
+ // explicit provider phases outrank wording-based selection for older Codex versions.
53
+ const finalItems = this.segments.flat().filter((entry) => entry.phase === "final_answer");
54
+ const selected = finalItems.length ? [finalItems] : selectFinalResponseSegments(this.segments);
61
55
  this.segments = [];
62
- const text = trailingSegment ? trailingSegment.map(segmentBody).join("\n\n").trim() : undefined;
63
- return truncateCapturedResponse(text);
56
+ this.responseItems.clear();
57
+ return selected ? selected.map(segmentBody).join("\n\n").trim() : undefined;
64
58
  }
65
59
 
66
60
  private flushPending(emit: EmitAgentMessage): void {
67
- // #2504 hot path: EVERY reasoning/plan delta calls closeSegment -> flushPending, so this runs
61
+ // hot path: EVERY reasoning/plan delta calls closeSegment -> flushPending, so this runs
68
62
  // thousands of times per turn. Bail before the defensive entry-array copy when there is no
69
63
  // open agentMessage buffer (the common case) — that snapshot exists only so `emit` re-entering
70
64
  // and mutating `buffers` cannot disturb the iteration, and there is nothing to iterate here.
@@ -78,7 +72,7 @@ export class CodexAgentMessageCapture {
78
72
  private flushText(itemId: string | undefined, text: string | undefined, emit: EmitAgentMessage): void {
79
73
  if (!text) return;
80
74
  const offset = itemId ? (this.flushedLengths.get(itemId) ?? 0) : 0;
81
- // #2504: `text` is the WHOLE accumulated item buffer and this is re-entered on every
75
+ // `text` is the WHOLE accumulated item buffer and this is re-entered on every
82
76
  // reasoning/plan delta. `.length` is O(1) on a rope, `.slice()`/`.trim()` are not — they
83
77
  // flatten and copy the entire buffer. Skip both when nothing new has arrived, which is the
84
78
  // overwhelmingly common case. Equivalent to the old code: slicing at/past the end yields ""
@@ -95,52 +89,26 @@ export class CodexAgentMessageCapture {
95
89
  emit(body);
96
90
  }
97
91
 
98
- // #2504 root cause. This used to open with `const body = text.trim()` — a full flatten+copy of
99
- // the entire accumulated agentMessage buffer — before branching, and on the itemId path `body`
100
- // was then DEAD: that branch computes its own incremental `chunk` and pushes that. Because every
101
- // reasoning/plan delta re-enters here via closeSegment -> flushPending with the same growing
102
- // buffer, the copy ran per delta over the whole buffer: O(N^2) transient-string churn that
103
- // fragmented the runner's allocator into multi-GB RSS bun never returned. Work here is now
104
- // bounded by the number of NEW characters, and is O(1) when nothing new arrived.
105
- private recordResponseText(itemId: string | undefined, text: string | undefined): void {
92
+ // segment boundaries may arrive mid-item. Keep one entry holding the whole item,
93
+ // rather than selecting independently trimmed tails. Updating the buffer reference is O(1),
94
+ // including on the per-reasoning-delta hot path that keeps free of full-text copies.
95
+ private recordResponseText(itemId: string | undefined, text: string | undefined, phase?: unknown): void {
106
96
  if (!text) return;
107
- if (itemId) {
108
- const offset = this.responseRecordedLengths.get(itemId) ?? 0;
109
- // THE hot path — every reasoning/plan delta lands here with an unchanged buffer. `.length`
110
- // is O(1); the pre-fix full-buffer trim was not, and its outcome was always "record
111
- // nothing" anyway (slicing at the end yields an empty chunk, and rewriting the offset to
112
- // the value it already holds is a no-op).
113
- if (text.length === offset) return;
114
- if (text.length < offset) {
115
- // The item's text got SHORTER than what we recorded — a buffer re-opened by fresh deltas
116
- // after completeItem, which clears `buffers` but deliberately keeps this offset. Nothing
117
- // can be recorded from it; the pre-fix code only rewound the offset, and only for
118
- // non-blank text.
119
- if (NON_WHITESPACE.test(text)) this.responseRecordedLengths.set(itemId, text.length);
120
- return;
121
- }
122
- // Only the NEW tail is examined. `slice` on the accumulated buffer is rope-aware in JSC and
123
- // stays proportional to the tail; a regex probe over `text` would NOT be — it forces a full
124
- // flatten of the rope on every delta, which is the very cost this fix exists to remove.
125
- const chunk = text.slice(offset).trim();
126
- // The pre-fix guard was `if (!text.trim()) return` — skip the offset advance while the item
127
- // is still ENTIRELY whitespace. A non-blank chunk already proves the text is non-blank (the
128
- // chunk is a substring of it), so the whole-text probe below is only reached for an all-blank
129
- // tail, and then it stops at the buffer's first real character.
130
- if (!chunk && !NON_WHITESPACE.test(text)) return;
131
- this.responseRecordedLengths.set(itemId, text.length);
132
- if (!chunk) return;
133
- this.currentSegment.push({ itemId, body: chunk });
97
+ const entry = itemId ? this.responseItems.get(itemId) : undefined;
98
+ if (entry) {
99
+ entry.body = text;
100
+ if (phase !== undefined) entry.phase = phase;
134
101
  return;
135
102
  }
136
- const body = text.trim();
137
- if (!body) return;
138
- this.currentSegment.push({ body });
103
+ if (!/\S/.test(text)) return;
104
+ const created: ResponseEntry = { itemId, body: text, phase };
105
+ if (itemId) this.responseItems.set(itemId, created);
106
+ this.currentSegment.push(created);
139
107
  }
140
108
 
141
109
  private forgetResponseItem(itemId: string | undefined): void {
142
110
  if (!itemId) return;
143
- this.responseRecordedLengths.delete(itemId);
111
+ this.responseItems.delete(itemId);
144
112
  this.currentSegment = this.currentSegment.filter((entry) => entry.itemId !== itemId);
145
113
  this.segments = this.segments
146
114
  .map((segment) => segment.filter((entry) => entry.itemId !== itemId))
@@ -182,8 +150,3 @@ function isGenericFinalCoda(body: string): boolean {
182
150
  const trimmed = body.trim();
183
151
  return trimmed.length <= 180 && GENERIC_FINAL_CODA.test(trimmed) && !isReportBoundarySegment(trimmed);
184
152
  }
185
-
186
- function truncateCapturedResponse(text: string | undefined): string | undefined {
187
- if (!text || text.length <= MAX_CAPTURED_RESPONSE_CHARS) return text;
188
- return `${text.slice(0, MAX_CAPTURED_RESPONSE_CHARS).trimEnd()}\n\n${TRUNCATION_MARKER_PREFIX} showing first ${MAX_CAPTURED_RESPONSE_CHARS} of ${text.length} chars]`;
189
- }
@@ -5,7 +5,7 @@ import { CodexAppClient } from "./codex-client";
5
5
  const STARTUP_TIMEOUT_MS = 60_000;
6
6
  const STDERR_TAIL_CHARS = 8_192;
7
7
 
8
- /** Connect only after our child announces its bound socket, not after a guessed delay (#3225). */
8
+ /** Connect only after our child announces its bound socket, not after a guessed delay. */
9
9
  export async function spawnCodexAppServer(args: SpawnArgs, signal?: AbortSignal) {
10
10
  signal?.throwIfAborted();
11
11
  const appServerUrl = args.env.CODEX_APP_SERVER_URL!;
@@ -81,7 +81,7 @@ interface AccountRateLimitsResponse {
81
81
 
82
82
  export const CODEX_APP_CLIENT_EVENT_CAP = 5_000;
83
83
 
84
- // #2504: the frame ring is diagnostic-only (getEvents()), but a COUNT cap alone bounds nothing —
84
+ // the frame ring is diagnostic-only (getEvents), but a COUNT cap alone bounds nothing —
85
85
  // a single app-server frame can carry a whole file diff or a large tool payload, so 5,000 frames
86
86
  // is an unbounded number of bytes. Cap the retained frame text too and evict oldest-first. The
87
87
  // budget is charged in raw frame characters; the parsed objects retained alongside them cost a
@@ -90,7 +90,7 @@ export const CODEX_APP_CLIENT_EVENT_CAP = 5_000;
90
90
  export const CODEX_APP_CLIENT_EVENT_BYTE_CAP = 4_000_000;
91
91
 
92
92
  /**
93
- * #2539 Phase A — a JSON-RPC error response from the app-server, kept STRUCTURED. The client
93
+ * Phase A — a JSON-RPC error response from the app-server, kept STRUCTURED. The client
94
94
  * used to flatten every error into `"<message> (<code>)"`, which forced callers to classify
95
95
  * rejections by message substring and made the turn/steer fallbacks (not-steerable vs
96
96
  * stale-turn vs transport) indistinguishable from real faults. The message keeps the old
@@ -107,7 +107,7 @@ export class CodexRpcError extends Error {
107
107
  }
108
108
  }
109
109
 
110
- // #1339: every JSON-RPC method the app-server exposes acknowledges promptly — turn/start
110
+ // every JSON-RPC method the app-server exposes acknowledges promptly — turn/start
111
111
  // returns the created turn immediately (turn progress/completion arrive later as separate
112
112
  // notifications), compact/start acks and reports completion via thread/compacted, etc. So a
113
113
  // request whose response never arrives (a half-open socket, a lost frame, an app-server that
@@ -172,8 +172,8 @@ export class CodexAppClient {
172
172
  async initialize(): Promise<unknown> {
173
173
  return this.request("initialize", {
174
174
  clientInfo: {
175
- name: "agent-relay-runner-codex",
176
- title: "Agent Relay Codex Runner",
175
+ name: "humwick-runner-codex",
176
+ title: "Humwick Codex Runner",
177
177
  version: "0.1.0",
178
178
  },
179
179
  capabilities: {
@@ -240,7 +240,7 @@ export class CodexAppClient {
240
240
  threadId,
241
241
  expectedTurnId: turnId,
242
242
  input: [{ type: "text", text }],
243
- // #2539 Phase A — stable acceptance-correlation id (echoed back as the userMessage
243
+ // Phase A — stable acceptance-correlation id (echoed back as the userMessage
244
244
  // item's clientId). Absent keeps the legacy wire shape.
245
245
  ...(clientUserMessageId ? { clientUserMessageId } : {}),
246
246
  });
@@ -275,7 +275,7 @@ export class CodexAppClient {
275
275
  const id = this.nextId++;
276
276
  const payload: JsonRpcRequest = { id, method, params };
277
277
  const promise = new Promise<T>((resolve, reject) => {
278
- // #1339: cap the wait so a response that never arrives can't hang forever. The timer
278
+ // cap the wait so a response that never arrives can't hang forever. The timer
279
279
  // is armed here and cleared by settle() the moment the matching frame lands (or the
280
280
  // socket closes and onclose rejects every pending). unref so a pending request never
281
281
  // by itself keeps the process alive.
@@ -312,7 +312,7 @@ export class CodexAppClient {
312
312
  if (pending) {
313
313
  this.pending.delete(parsed.id);
314
314
  if (parsed.error) {
315
- // #2539 Phase A — reject with the STRUCTURED error (code + data preserved) so the
315
+ // Phase A — reject with the STRUCTURED error (code + data preserved) so the
316
316
  // adapter can classify turn/steer rejections; the message keeps the legacy shape.
317
317
  pending.reject(new CodexRpcError(parsed.error.code, parsed.error.message, parsed.error.data));
318
318
  } else {
@@ -339,7 +339,7 @@ export class CodexAppClient {
339
339
  this.eventBytes += size;
340
340
  // Decide how many oldest frames both ceilings require dropping, then drop them in ONE splice —
341
341
  // this runs on every received frame, so a second shift of a 5,000-entry array would itself be
342
- // the kind of steady per-frame churn #2504 is about.
342
+ // the kind of steady per-frame churn is about.
343
343
  let drop = Math.max(0, this.events.length - CODEX_APP_CLIENT_EVENT_CAP);
344
344
  let bytes = this.eventBytes;
345
345
  for (let i = 0; i < drop; i++) bytes -= this.events[i]!.size;
@@ -19,7 +19,7 @@ export function readCodexConfig(configArgs: string[], codexHome?: string, cwd?:
19
19
  if (existsSync(join(dir, ".git")) || dirname(dir) === dir) break;
20
20
  }
21
21
  const projects = isRecord(config.projects) ? config.projects : {};
22
- // #3476 — no repository means cwd-only settings; trust in a nested cwd never trusts its ancestors.
22
+ // no repository means cwd-only settings; trust in a nested cwd never trusts its ancestors.
23
23
  if (!existsSync(join(dirs.at(-1)!, ".git"))) dirs.splice(1);
24
24
  let projectTrusted = false;
25
25
  for (const dir of dirs.reverse()) {
@@ -1,6 +1,6 @@
1
1
  // Codex launch/config-arg construction: the `-c` override builders, CLI command
2
2
  // resolution (shim detection), and the tool-output token limit. Extracted from
3
- // codex.ts (file-size ratchet, epic #291); codex.ts re-exports these so existing
3
+ // codex.ts; codex.ts re-exports these so existing
4
4
  // consumers + tests keep their import path.
5
5
  import { accessSync, constants, realpathSync } from "node:fs";
6
6
  import { basename, join } from "node:path";
@@ -15,7 +15,7 @@ import { tomlString } from "../relay-mcp";
15
15
 
16
16
  export const DEFAULT_CODEX_TOOL_OUTPUT_TOKEN_LIMIT = 12_000;
17
17
 
18
- // #1248 — default background-terminal poll window (300000ms/5min) is too short for
18
+ // default background-terminal poll window (300000ms/5min) is too short for
19
19
  // our long-running commands (tests/releases run 5+ min), forcing agents to poll
20
20
  // instead of just waiting. Verified against installed codex-cli's source (rust-v0.144.0):
21
21
  // `background_terminal_max_timeout` is a top-level Config field — it replaced the older
@@ -29,7 +29,7 @@ export const DEFAULT_CODEX_TOOL_OUTPUT_TOKEN_LIMIT = 12_000;
29
29
  // "empty polls wait 5000-300000 ms by default" — that never reflects this override, so a
30
30
  // model sees no signal it may request more and keeps polling at ~300000ms. That's why a
31
31
  // live probe still saw ~300.001s polls after this override landed. We deliberately do NOT
32
- // advertise longer polls to the model (#2776): agents are steered to end the turn and take the wake.
32
+ // advertise longer polls to the model: agents are steered to end the turn and take the wake.
33
33
  const CODEX_BACKGROUND_TERMINAL_MAX_TIMEOUT_MS = 900000;
34
34
 
35
35
  export function codexModelConfigArgs(model?: string, effort?: string, record?: ResolvedModelRecord): string[] {
@@ -55,7 +55,7 @@ export function codexApprovalConfigArgs(approvalMode?: string): string[] {
55
55
  ];
56
56
  }
57
57
 
58
- // #1248 — see CODEX_BACKGROUND_TERMINAL_MAX_TIMEOUT_MS above.
58
+ // see CODEX_BACKGROUND_TERMINAL_MAX_TIMEOUT_MS above.
59
59
  export function codexManagedConfigArgs(): string[] {
60
60
  return [
61
61
  "-c",
@@ -122,7 +122,7 @@ function realPath(path: string): string {
122
122
  }
123
123
 
124
124
  function isHumwickCodexShim(path: string): boolean {
125
- return path.includes("/.agent-relay/codex/bin/codex");
125
+ return path.includes("/.humwick/codex/bin/codex") || path.includes("/.agent-relay/codex/bin/codex");
126
126
  }
127
127
 
128
128
  export function codexAppServerConfigArgs(...argLists: string[][]): string[] {
@@ -11,7 +11,7 @@ import { incrementalFileNeedsReset, readIncrementalFileRangeSync, splitIncrement
11
11
  * codexTokenUsageReport (codex-token-usage-capture.ts) read out of a token-usage
12
12
  * payload — the app-server's `thread/tokenUsage/updated` params or a rollout
13
13
  * `token_count` payload share the same field-name variants (camelCase live,
14
- * snake_case in the rollout). One resolver so the variant list can't drift (#2483). */
14
+ * snake_case in the rollout). One resolver so the variant list can't drift. */
15
15
  export interface CodexUsageSections {
16
16
  usage: Record<string, unknown>;
17
17
  total?: Record<string, unknown>;
@@ -197,7 +197,7 @@ export function findCodexRolloutPath(threadId: string): string | undefined {
197
197
  }
198
198
 
199
199
  /**
200
- * Fallback model resolution (#2483 review F2) for when the spawn config carries no
200
+ * Fallback model resolution for when the spawn config carries no
201
201
  * `model` — a managed-policy/restart Codex spawn resolves with no explicit model
202
202
  * (`skipDefaultWhenEmpty:true`), so the token-usage capture would otherwise skip
203
203
  * every report for that agent forever, silently. The rollout records the resolved
@@ -2,7 +2,7 @@ import { errMessage } from "@humwick/sdk";
2
2
  import type { CodexAppClient } from "./codex-client";
3
3
 
4
4
  /** Register once before any thread starts. Codex owns catalog rendering; updating these
5
- * roots later can re-inject its full catalog (#3694), so Relay supplies the complete set here. */
5
+ * roots later can re-inject its full catalog, so Relay supplies the complete set here. */
6
6
  export async function registerCodexSkillRoots(
7
7
  client: Pick<CodexAppClient, "skillsSetExtraRoots">,
8
8
  roots: string[],
@@ -1,6 +1,6 @@
1
- // #2483 — per-turn Codex token-usage capture: the runner-side half of the token-usage spine's
2
- // Codex capture seam (sibling to claude-token-usage-capture.ts's #2482 seam, proven live via
3
- // #2482/#2491). Unlike Claude's transcript-tail (no flush hook exists for Codex, so the runner
1
+ // per-turn Codex token-usage capture: the runner-side half of the token-usage spine's
2
+ // Codex capture seam (sibling to claude-token-usage-capture.ts's seam, proven live).
3
+ // Unlike Claude's transcript-tail (no flush hook exists for Codex, so the runner
4
4
  // has to read whatever bytes have been appended), Codex's app-server pushes usage LIVE on its
5
5
  // JSON-RPC event stream (`thread/tokenUsage/updated`, already parsed once by handleCodexEvent
6
6
  // for context-state tracking via codexTokenUsageContext) — no flush race, so this reads directly
@@ -12,27 +12,27 @@
12
12
  // completed) token usage, each with input/cached-input/cache-write-input/output/reasoning-output
13
13
  // breakdowns. `last` is what we report — it matches the spine's per-turn semantics.
14
14
  //
15
- // #2483 review round 1, F1 — Codex's buckets are NOT disjoint (cachedInputTokens ⊂ inputTokens,
15
+ // review round 1, F1 — Codex's buckets are NOT disjoint (cachedInputTokens ⊂ inputTokens,
16
16
  // reasoningOutputTokens ⊂ outputTokens; a live fixture confirms input+output alone equals
17
- // totalTokens). The spine's convention (locked by src/db/token-usage.test.ts, set by Claude's
18
- // #2482) is DISJOINT buckets — its aggregate sums input+output+cacheRead+cacheWrite+reasoning as
17
+ // totalTokens). The spine's convention (locked by src/db/token-usage.test.ts, set by Claude's)
18
+ // is DISJOINT buckets — its aggregate sums input+output+cacheRead+cacheWrite+reasoning as
19
19
  // the total. Posting Codex's raw inputTokens/outputTokens double-counts the cache/reasoning
20
20
  // sub-portions (~2x on the live fixture) — so cachedInputTokens/cacheWriteInputTokens are
21
21
  // subtracted out of inputTokens, and reasoningOutputTokens out of outputTokens, before posting.
22
22
  //
23
- // #2483 review round 1, F4 — the dedupe key needs `total.totalTokens` specifically (the
23
+ // review round 1, F4 — the dedupe key needs `total.totalTokens` specifically (the
24
24
  // cumulative counter, which only ever grows within a thread — a compaction just mints a fresh
25
25
  // baseline, changing the key). Falling back to `last.totalTokens` when `total` doesn't carry a
26
26
  // usable totalTokens degrades to a key that can collide across two different exchanges (both
27
27
  // deltas the same size) and silently undercounts via the spine's ON CONFLICT collapse — so a
28
28
  // report with no usable cumulative total is dropped rather than posted under an unsafe key.
29
29
  //
30
- // #2483 review round 2, LOW-1 — a real-corpus check (0.3% of events) found `totalTokens > 0`
30
+ // review round 2, LOW-1 — a real-corpus check (0.3% of events) found `totalTokens > 0`
31
31
  // with an all-zero input/output breakdown: the only shape that breaks the F1 sum invariant on
32
32
  // real data (a nothing-attributable row would post as all-zero, silently undercounting). Reported
33
- // as explicit unknown usage (#3351) rather than guessed at.
33
+ // as explicit unknown usage rather than guessed at.
34
34
  //
35
- // #2483 review round 2, MED-1 — the F2 rollout fallback must cache a MISS too, not just a hit:
35
+ // review round 2, MED-1 — the F2 rollout fallback must cache a MISS too, not just a hit:
36
36
  // an unresolvable model (the managed/restart no-model case) would otherwise re-run
37
37
  // findCodexRolloutPath (a recursive readdirSync of ~/.codex/sessions) + a synchronous ~1 MiB
38
38
  // read on EVERY thread/tokenUsage/updated event, forever, on the app-server event loop. The miss
@@ -41,7 +41,7 @@ import { isRecord, stringValue } from "@humwick/sdk";
41
41
  import type { ProviderTokenUsageRecord, ProviderTokenUsageReporter } from "../adapter";
42
42
  import { codexModelFromRolloutPath, codexUsageSections, findCodexRolloutPath, numberValue } from "./codex-rollout-context";
43
43
 
44
- /** The spine's turn-less report shape (POST /api/agents/:id/token-usage, #2485). */
44
+ /** The spine's turn-less report shape. */
45
45
  export type CodexTokenUsageSpineRecord = ProviderTokenUsageRecord & { provider: "codex" };
46
46
 
47
47
  interface CodexTokenUsageBreakdown {
@@ -91,7 +91,7 @@ export function codexTokenUsageReport(
91
91
  const base = { sessionId: opts.sessionId, provider: "codex" as const, model: opts.model,
92
92
  dedupeKey: `${opts.sessionId}:${totalTokensCumulative}:${lastTotal}`, createdAt: opts.now ?? Date.now() };
93
93
  const lastBreakdown = breakdownFrom(sections.last);
94
- // LOW-1 / #3351: positive usage without a readable input/output breakdown is real spend whose
94
+ // LOW-1: positive usage without a readable input/output breakdown is real spend whose
95
95
  // buckets are unknown; report it as unknown under the key a readable report would use.
96
96
  if (!lastBreakdown || (lastBreakdown.inputTokens === 0 && lastBreakdown.outputTokens === 0)) {
97
97
  return { ...base, inputTokens: 0, outputTokens: 0, cacheReadTokens: 0, cacheWriteTokens: 0, usageStatus: "unknown" };
@@ -119,15 +119,15 @@ interface CodexTokenUsageModelProbe {
119
119
 
120
120
  function readModelProbe(meta: Record<string, unknown>, threadId: string): CodexTokenUsageModelProbe | undefined {
121
121
  const probe = meta.tokenUsageModelProbe;
122
- if (!isRecord(probe) || probe.threadId !== threadId) return undefined; // #2483 review MED-1: keyed to
122
+ if (!isRecord(probe) || probe.threadId !== threadId) return undefined; // review MED-1: keyed to
123
123
  // threadId, so a thread change re-arms the probe immediately instead of inheriting a stale miss.
124
124
  return { threadId, model: stringValue(probe.model), missedAt: numberValue(probe.missedAt) };
125
125
  }
126
126
 
127
127
  /**
128
128
  * codex.ts's handleCodexEvent call site — kept to one line there so the reporter wiring doesn't
129
- * grow that file past its size ratchet (#291). Resolves the model (the spawn config's, falling
130
- * back to the rollout's most recent `turn_context.model` — #2483 review F2: a managed-
129
+ * grow that file past its size ratchet. Resolves the model (the spawn config's, falling
130
+ * back to the rollout's most recent `turn_context.model` — review F2: a managed-
131
131
  * policy/restart spawn resolves with no explicit model, and without this fallback every report
132
132
  * for that agent would silently, permanently skip), builds the record, and reports it
133
133
  * (best-effort; failures go to reportFailure).
@@ -155,7 +155,7 @@ export function reportCodexTokenUsage(
155
155
  }
156
156
  }
157
157
  }
158
- // #3351 — an unresolved model is a label gap, not missing spend: report under "unknown".
158
+ // an unresolved model is a label gap, not missing spend: report under "unknown".
159
159
  if (!model) reporter.debug(`token-usage capture: no known model for Codex thread ${threadId}; reporting as unknown model`);
160
160
  const record = codexTokenUsageReport(params, { sessionId: threadId, model: model ?? "unknown", now });
161
161
  if (!record) return;
@@ -12,7 +12,7 @@ export function codexMcpOutputRedactor(cwd: string): ((response: Response) => Pr
12
12
  return (response) => redactCodexMcpResponse(response, cwd);
13
13
  }
14
14
 
15
- // Codex's blocking PostToolUse replacement throws inside functions.exec (#3248).
15
+ // Codex's blocking PostToolUse replacement throws inside functions.exec.
16
16
  // Replace at the transport boundary instead; the existing hook remains a backstop.
17
17
  export async function redactCodexMcpResponse(response: Response, cwd: string, env: NodeJS.ProcessEnv = process.env): Promise<Response> {
18
18
  let body: Record<string, unknown> = {};
@@ -24,17 +24,17 @@ export async function redactCodexMcpResponse(response: Response, cwd: string, en
24
24
  field = "result" in body ? "result" : "error";
25
25
  if (!(field in body)) return Response.json(body, { status: response.status });
26
26
  const input = JSON.stringify({ cwd, tool_name: "mcp__agent_relay__result", tool_response: body[field] });
27
- const binary = join(agentRedactInstallDir(relayEnv(env, "AGENT_RELAY_HOME") || humwickHome()), AGENT_REDACT_FILES.binary);
27
+ const binary = join(agentRedactInstallDir(relayEnv(env, "HUMWICK_HOME") || humwickHome()), AGENT_REDACT_FILES.binary);
28
28
  // The Claude renderer preserves JSON shape; both adapters use the same collector/masker.
29
29
  const output = await new Promise<string>((resolve, reject) => {
30
30
  const child = execFile(binary, ["hook", "claude"], {
31
- cwd, env: writeChildEnv({}, { ...env, AGENT_REDACT_CONFIG: env.AGENT_REDACT_CONFIG || agentRedactConfigPath() }),
31
+ cwd, env: writeChildEnv({}, { ...env, AGENT_REDACT_CONFIG: env.AGENT_REDACT_CONFIG || agentRedactConfigPath() }, []),
32
32
  timeout: 10_000, killSignal: "SIGKILL", maxBuffer: Math.max(16_384, Buffer.byteLength(input) * 3),
33
33
  }, (error, stdout, stderr) => error || stderr.trim() ? reject(new Error("masker failed")) : resolve(stdout));
34
34
  child.stdin?.on("error", () => reject(new Error("masker input failed")));
35
35
  child.stdin?.end(input);
36
36
  });
37
- // A successful hook writes nothing when no strings needed masking (#3255).
37
+ // A successful hook writes nothing when no strings needed masking.
38
38
  const masked: unknown = JSON.parse(output.trim() || "{}");
39
39
  if (!isRecord(masked)) throw new Error("invalid masker output");
40
40
  if (Object.keys(masked).length) {
@@ -1,11 +1,11 @@
1
- // #1096 review, finding 4 — Codex hooks (CODEX_HOME/hooks.json with Claude-compatible
1
+ // review, finding 4 — Codex hooks (CODEX_HOME/hooks.json with Claude-compatible
2
2
  // event names) are only consumed by Codex >= 0.124. An older Codex silently ignores the
3
3
  // file, so writing it and reporting `provisioning.hooks: applied` would be a lie. This
4
4
  // module probes the installed Codex version once and answers whether hook delivery is
5
5
  // actually honored, so the assembler can emit a truthful projection (a LOUD warning when
6
6
  // the guard cannot fire) instead of a silent absence.
7
7
  //
8
- // TODO(#1096): a post-deploy LIVE check is still owed — assert that a Codex >= 0.124 actually
8
+ // TODO: a post-deploy LIVE check is still owed — assert that a Codex >= 0.124 actually
9
9
  // executes CODEX_HOME/hooks.json for these event names (Stop/PreToolUse/…), not just that the
10
10
  // version string clears the gate. The version probe is necessary but not sufficient.
11
11
 
@@ -44,7 +44,7 @@ function probeCodexVersion(command: string): string | null {
44
44
  }
45
45
 
46
46
  // Whether the installed Codex actually consumes managed hooks. An explicit
47
- // AGENT_RELAY_CODEX_VERSION override wins (deterministic tests + operator escape hatch);
47
+ // HUMWICK_CODEX_VERSION override wins (deterministic tests + operator escape hatch);
48
48
  // otherwise the real `codex --version` is probed once and cached. An UNKNOWN version (probe
49
49
  // failed / Codex not installed) is treated as supported: we neither block launches nor cry
50
50
  // wolf on a machine that can't be inspected — we only claim "not applied" when we can PROVE