@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
package/src/relay-mcp.ts CHANGED
@@ -1,4 +1,4 @@
1
- import { mcpServerInfo, parseToolGeneration, isRelayMcpServerName, type ToolGeneration } from "@humwick/sdk";
1
+ import { MCP_SERVER_INFO, isRelayMcpServerName } from "@humwick/sdk";
2
2
  import { relayEnv, writeChildEnv } from "@humwick/sdk/env";
3
3
  import { codexVaultMcpEnv } from "./mcp-vault-env";
4
4
  // Single home for the relay HTTP MCP endpoint descriptor + per-provider injection
@@ -6,35 +6,35 @@ import { codexVaultMcpEnv } from "./mcp-vault-env";
6
6
  // server name, and token-handling rules live in exactly one place.
7
7
  //
8
8
  // Token handling: the bearer token is NEVER placed in argv (it would leak via `ps`
9
- // and the agent's own process inspection). Claude expands `${AGENT_RELAY_SESSION_TOKEN}`
9
+ // and the agent's own process inspection). Claude expands `${HUMWICK_SESSION_TOKEN}`
10
10
  // from the env at MCP-config parse time; Codex reads it from the named env var.
11
11
  //
12
- // Why a dedicated var and NOT `AGENT_RELAY_TOKEN`: a managed session inherits the
12
+ // Why a dedicated var and NOT `HUMWICK_TOKEN`: a managed session inherits the
13
13
  // runner's scoped, identity-bearing session token, but Claude Code applies a rig/user
14
14
  // `settings.json` `env` block OVER the inherited env. A rig that sets
15
- // `env.AGENT_RELAY_TOKEN` (e.g. to the admin token, for interactive CLI use) would
15
+ // `env.HUMWICK_TOKEN` (e.g. to the admin token, for interactive CLI use) would
16
16
  // clobber the scoped token at parse time, so the relay MCP connection authenticates as
17
- // the `server` actor with no agent identity — `relay_whoami` returns null and
18
- // `relay_send_message` demands a `from` it can't supply (#233). The runner exports the
17
+ // the `server` actor with no agent identity — `humwick_whoami` returns null and
18
+ // `humwick_send_message` demands a `from` it can't supply. The runner exports the
19
19
  // scoped token under this dedicated name that rigs don't set, so nothing on the host can
20
20
  // hijack the managed agent's identity.
21
- export const RELAY_MCP_SERVER_NAME = mcpServerInfo("relay").name;
21
+ export const RELAY_MCP_SERVER_NAME = MCP_SERVER_INFO.name;
22
22
  export { isRelayMcpServerName };
23
23
  export const RELAY_MCP_PATH = "/api/mcp";
24
- export const RELAY_MCP_TOKEN_ENV = "AGENT_RELAY_SESSION_TOKEN";
24
+ export const RELAY_MCP_TOKEN_ENV = "HUMWICK_SESSION_TOKEN";
25
25
 
26
- // #672 — shared host-listener MCP. The agent connects to ONE orchestrator-owned shared
26
+ // shared host-listener MCP. The agent connects to ONE orchestrator-owned shared
27
27
  // `callmux` listener via a per-agent `callmux bridge`, fronting the host's NON-IDENTITY MCP
28
28
  // (tokenlean, github). The bridge is a stdio child (auto-reconnect + retry-once on a
29
29
  // listener/downstream hiccup; `--cwd` injects `x-callmux-cwd` for per-worktree session-cwd
30
30
  // isolation), so it rides the SAME provider injection as any provisioned stdio server. The
31
- // identity-bearing relay endpoint is NEVER routed here — it stays per-agent on #215.
31
+ // identity-bearing relay endpoint is NEVER routed here — it stays per-agent.
32
32
  export { SHARED_MCP_SERVER_NAME };
33
33
  // callmux's documented manual-listener endpoint (`callmux --listen 4860`, Streamable HTTP at
34
34
  // /mcp). In managed launches the orchestrator owns the real listener URL and passes it through
35
- // AGENT_RELAY_SHARED_MCP_URL; this fallback is for direct runner/manual-listener use.
35
+ // HUMWICK_SHARED_MCP_URL; this fallback is for direct runner/manual-listener use.
36
36
  export const DEFAULT_SHARED_MCP_URL = "http://localhost:4860/mcp";
37
- export const SHARED_MCP_URL_ENV = "AGENT_RELAY_SHARED_MCP_URL";
37
+ export const SHARED_MCP_URL_ENV = "HUMWICK_SHARED_MCP_URL";
38
38
  export { runnerManagedToolDeferralRoute } from "./mcp-tool-deferral";
39
39
 
40
40
  /** Resolve the shared-listener URL: explicit runner option → env → documented default. */
@@ -42,22 +42,11 @@ export function resolveSharedMcpUrl(explicit?: string): string {
42
42
  return explicit ?? relayEnv(process.env, SHARED_MCP_URL_ENV) ?? DEFAULT_SHARED_MCP_URL;
43
43
  }
44
44
 
45
- export function runnerMcpGeneration(env: Record<string, string | undefined>): ToolGeneration {
46
- return parseToolGeneration(relayEnv(env, "AGENT_RELAY_MCP_TOOLS"));
45
+ export function relayMcpEndpoint(relayUrl: string): string {
46
+ return `${relayUrl.replace(/\/+$/, "")}${RELAY_MCP_PATH}`;
47
47
  }
48
48
 
49
- function generationEndpoint(endpoint: string, generation: ToolGeneration): string {
50
- const url = new URL(endpoint);
51
- if (generation === "humwick") url.searchParams.set("tools", generation);
52
- else url.searchParams.delete("tools");
53
- return url.toString();
54
- }
55
-
56
- export function relayMcpEndpoint(relayUrl: string, generation: ToolGeneration = "relay"): string {
57
- return generationEndpoint(`${relayUrl.replace(/\/+$/, "")}${RELAY_MCP_PATH}`, generation);
58
- }
59
-
60
- // #1248 — per-server Codex MCP overrides. Default `tool_timeout_sec` (60s) and
49
+ // per-server Codex MCP overrides. Default `tool_timeout_sec` (60s) and
61
50
  // `startup_timeout_ms` are too short for our long-running tools (tests/releases run
62
51
  // 5+ min), forcing agents to poll/sleep instead of just waiting on the call. Verified
63
52
  // against the installed codex-cli binary (0.144.0): both fields live on the
@@ -77,12 +66,12 @@ function codexTimeoutConfigArgs(key: string): string[] {
77
66
 
78
67
  // Codex: `-c mcp_servers.<name>.*` overrides. `bearer_token_env_var` tells Codex to
79
68
  // read the token from the env var itself → transport resolves to streamable_http.
80
- // `endpoint` overrides the target URL (runner-local proxy, Stage 2 #215) — see above.
81
- export function relayMcpCodexConfigArgs(relayUrl: string, endpoint?: string, generation: ToolGeneration = "relay"): string[] {
82
- const key = `mcp_servers.${mcpServerInfo(generation).name}`;
69
+ // `endpoint` overrides the target URL (runner-local proxy, Stage 2) — see above.
70
+ export function relayMcpCodexConfigArgs(relayUrl: string, endpoint?: string): string[] {
71
+ const key = `mcp_servers.${MCP_SERVER_INFO.name}`;
83
72
  return [
84
73
  "-c",
85
- `${key}.url=${tomlString(generationEndpoint(endpoint ?? relayMcpEndpoint(relayUrl), generation))}`,
74
+ `${key}.url=${tomlString(endpoint ?? relayMcpEndpoint(relayUrl))}`,
86
75
  "-c",
87
76
  `${key}.bearer_token_env_var=${tomlString(RELAY_MCP_TOKEN_ENV)}`,
88
77
  ...codexTimeoutConfigArgs(key),
@@ -154,8 +143,8 @@ function codexNativeMcpToml(server: ProvisioningMcpServer): { root: string[]; ne
154
143
  return { root, nested };
155
144
  }
156
145
 
157
- // #555 — compose the launch MCP set from (the relay endpoint, if relay.mcp) PLUS the
158
- // profile's relay-provisioned servers, PLUS (#662) the host USER-scope servers for an
146
+ // compose the launch MCP set from (the relay endpoint, if relay.mcp) PLUS the
147
+ // profile's relay-provisioned servers, PLUS the host USER-scope servers for an
159
148
  // `mcp.mode:"host"` profile. One `--mcp-config` JSON carries them all. `strict` adds
160
149
  // `--strict-mcp-config` so ONLY these load — Claude reads no on-disk config, so the
161
150
  // project-cwd `.mcp.json` is never discovered (its enable modal can't wedge a headless
@@ -168,11 +157,11 @@ import { SHARED_MCP_SERVER_NAME, sharedCallmuxToolCallTimeoutMsFromEnv, type Pro
168
157
 
169
158
  const __dirname = dirname(fileURLToPath(import.meta.url));
170
159
 
171
- // #1301 — a bare `command: "callmux"` resolves off PATH, which can drift from the version
160
+ // a bare `command: "callmux"` resolves off PATH, which can drift from the version
172
161
  // this package pins (root/runner/orchestrator all pin the same `callmux` dependency). npm
173
162
  // (and bun/pnpm workspaces) always link a dependency's declared bin to `node_modules/.bin/<name>`
174
163
  // at whichever ancestor directory it resolves/hoists to — the same layout-agnostic-bin
175
- // invariant #1305/#1222 rely on for the humwick CLI itself — so walk up from this
164
+ // invariant rely on for the humwick CLI itself — so walk up from this
176
165
  // module's own location to find the PINNED bridge binary instead of trusting PATH. Falls
177
166
  // back to the bare name (old behavior) only if no bundled bin is found anywhere above.
178
167
  export function resolveCallmuxBin(startDir: string = __dirname): string {
@@ -188,7 +177,7 @@ export function resolveCallmuxBin(startDir: string = __dirname): string {
188
177
 
189
178
  // The shared-listener bridge as a provider-neutral stdio server descriptor: a `callmux bridge`
190
179
  // child fronting the host's shared listener, with per-agent `--cwd "$WORKTREE"` for session-cwd
191
- // isolation (#672). It flows through the existing claude/codex stdio injection unchanged.
180
+ // isolation. It flows through the existing claude/codex stdio injection unchanged.
192
181
  export function sharedMcpBridgeServer(url: string, cwd: string): ProvisioningMcpServer {
193
182
  return {
194
183
  command: resolveCallmuxBin(),
@@ -196,7 +185,7 @@ export function sharedMcpBridgeServer(url: string, cwd: string): ProvisioningMcp
196
185
  };
197
186
  }
198
187
 
199
- // #1575 — the per-agent metaOnly callmux for Codex workers. The header callmux reads to scope
188
+ // the per-agent metaOnly callmux for Codex workers. The header callmux reads to scope
200
189
  // session-cwd stdio downstreams (tokenlean) per worktree; the shared bridge injects it dynamically,
201
190
  // and here we pin it statically because a stdio callmux has no incoming request headers to forward.
202
191
  const CALLMUX_CWD_HEADER = "x-callmux-cwd";
@@ -205,20 +194,20 @@ const CALLMUX_CWD_HEADER = "x-callmux-cwd";
205
194
  // materializer (writes it) so disk == launch.
206
195
  export const SHARED_MCP_METAONLY_CONFIG_FILENAME = "callmux-metaonly.json";
207
196
 
208
- // #1450 — the request header the per-agent metaOnly callmux sets on its relay downstream. The runner
197
+ // the request header the per-agent metaOnly callmux sets on its relay downstream. The runner
209
198
  // proxy reads it to decide which relay tools/list view to serve: WITHOUT it (the agent's DIRECT relay
210
199
  // client) the proxy hides the on-demand set so only the eager hot-path set boots; WITH it (this
211
200
  // callmux, fronting the on-demand set) the proxy serves the FULL set the relay already returned so
212
201
  // callmux can route callmux_call to any of them. It can only ever widen the LIST back to that
213
- // relay-returned set — never authorization: tools/call stays scope-gated regardless (#215 / capability
202
+ // relay-returned set — never authorization: tools/call stays scope-gated regardless (/ capability
214
203
  // neutrality). Kept out of `forward` so the relay itself never sees it.
215
- const RELAY_MCP_ONDEMAND_SURFACE_HEADER = "x-agent-relay-ondemand-front";
204
+ const RELAY_MCP_ONDEMAND_SURFACE_HEADER = "x-humwick-ondemand-front";
216
205
  export const RELAY_MCP_ONDEMAND_SURFACE_VALUE = "1";
217
206
 
218
- // #1450 — a Codex worker's relay downstream inside the per-agent metaOnly callmux. It points at the
207
+ // a Codex worker's relay downstream inside the per-agent metaOnly callmux. It points at the
219
208
  // runner-LOCAL proxy (never the relay directly): the proxy injects the live relay token, so identity
220
- // rides through and the agent/callmux never hold the real token on disk (#215). `authSecret` is the
221
- // per-session PROXY secret (which the agent already holds in AGENT_RELAY_SESSION_TOKEN — writing it to
209
+ // rides through and the agent/callmux never hold the real token on disk. `authSecret` is the
210
+ // per-session PROXY secret (which the agent already holds in HUMWICK_SESSION_TOKEN — writing it to
222
211
  // this 0600 config adds no exposure, and it is NOT the relay token). cachePolicy denies every tool:
223
212
  // relay ops are identity-bearing/mutating and must never be served from cache.
224
213
  export interface MetaOnlyRelayDownstream {
@@ -226,18 +215,17 @@ export interface MetaOnlyRelayDownstream {
226
215
  endpoint: string;
227
216
  /** The per-session proxy secret the agent authenticates to the local proxy with. */
228
217
  authSecret: string;
229
- generation?: ToolGeneration;
230
218
  }
231
219
 
232
220
  function metaOnlyRelayServerConfig(relay: MetaOnlyRelayDownstream): Record<string, unknown> {
233
221
  return {
234
- url: generationEndpoint(relay.endpoint, relay.generation ?? "relay"),
222
+ url: relay.endpoint,
235
223
  transport: "streamable-http",
236
224
  headers: {
237
225
  Authorization: `Bearer ${relay.authSecret}`,
238
226
  [RELAY_MCP_ONDEMAND_SURFACE_HEADER]: RELAY_MCP_ONDEMAND_SURFACE_VALUE,
239
227
  },
240
- // #1450 — never cache relay ops (identity-bearing/mutating). Wildcard-deny is explicit belt-and-
228
+ // never cache relay ops (identity-bearing/mutating). Wildcard-deny is explicit belt-and-
241
229
  // suspenders on top of the config's disabled global cache TTL.
242
230
  cachePolicy: { denyTools: ["*"] },
243
231
  callTimeoutMs: sharedCallmuxToolCallTimeoutMsFromEnv(),
@@ -246,13 +234,13 @@ function metaOnlyRelayServerConfig(relay: MetaOnlyRelayDownstream): Record<strin
246
234
 
247
235
  // The per-agent metaOnly callmux config: streamable-http downstreams exposing ONLY the callmux
248
236
  // meta-tools (callmux_search_tools/callmux_call/…). A Codex worker boots with ~11 meta-tool schemas
249
- // instead of the flat downstream schemas (#1570 dominant lever) and reaches the real tools on demand
237
+ // instead of the flat downstream schemas and reaches the real tools on demand
250
238
  // through callmux_call.
251
239
  // • The shared listener (SHARED_MCP_SERVER_NAME): the worktree cwd is a STATIC downstream header —
252
240
  // the per-agent callmux runs over stdio (no incoming headers to forwardHeaders) and the worktree
253
241
  // is fixed per agent, so a fixed x-callmux-cwd routes the shared listener's session-cwd tools to
254
242
  // this worktree exactly as the dynamic bridge header did.
255
- // • #1450 — when `relay` is given, the identity-bearing relay endpoint is added as a SECOND
243
+ // • — when `relay` is given, the identity-bearing relay endpoint is added as a SECOND
256
244
  // downstream (via the runner-local proxy) so the on-demand relay set loads through callmux_call.
257
245
  // Timeouts mirror the bridge's SHARED_CALLMUX_TOOL_CALL_TIMEOUT_MS floor so nested calls keep the same
258
246
  // call-timeout budget.
@@ -272,7 +260,7 @@ export function sharedMcpMetaOnlyConfig(url: string, cwd: string, relay?: MetaOn
272
260
  cachePolicy: { denyTools: ["*"] },
273
261
  callTimeoutMs,
274
262
  },
275
- ...(relay ? { [mcpServerInfo(relay.generation ?? "relay").name]: metaOnlyRelayServerConfig(relay) } : {}),
263
+ ...(relay ? { [MCP_SERVER_INFO.name]: metaOnlyRelayServerConfig(relay) } : {}),
276
264
  },
277
265
  };
278
266
  }
@@ -288,22 +276,22 @@ export function sharedMcpMetaOnlyServer(configPath: string): ProvisioningMcpServ
288
276
  }
289
277
 
290
278
  // Materialize the per-agent metaOnly config `sharedMcpMetaOnlyServer(configPath)` points at. The
291
- // shared-listener URL is non-secret. #1450 — when a `relay` downstream is given, the config carries
292
- // the per-session PROXY secret (NOT the relay token — the proxy injects that; #215). The agent
293
- // already holds the proxy secret in AGENT_RELAY_SESSION_TOKEN, so the 0600 file adds no exposure.
279
+ // shared-listener URL is non-secret. When a `relay` downstream is given, the config carries
280
+ // the per-session PROXY secret (NOT the relay token — the proxy injects that). The agent
281
+ // already holds the proxy secret in HUMWICK_SESSION_TOKEN, so the 0600 file adds no exposure.
294
282
  export function writeSharedMcpMetaOnlyConfig(configPath: string, url: string, cwd: string, relay?: MetaOnlyRelayDownstream): void {
295
283
  mkdirSync(dirname(configPath), { recursive: true });
296
284
  writeFileSync(configPath, JSON.stringify(sharedMcpMetaOnlyConfig(url, cwd, relay), null, 2) + "\n", { mode: 0o600 });
297
285
  }
298
286
 
299
- // #1292 — shared by the Claude JSON payload builder and the Codex TOML materializer, so
287
+ // shared by the Claude JSON payload builder and the Codex TOML materializer, so
300
288
  // both projections merge `configEnv` (non-secret, allowlisted literals) into `env` the
301
289
  // same way instead of re-deriving the merge twice.
302
290
  export function mergedConfigEnv(server: ProvisioningMcpServer): ProvisioningMcpServer {
303
291
  if (!server.configEnv || Object.keys(server.configEnv).length === 0) return server;
304
292
  return {
305
293
  ...server,
306
- env: writeChildEnv({}, { ...server.configEnv, ...(server.env ?? {}) }),
294
+ env: writeChildEnv({}, { ...server.configEnv, ...(server.env ?? {}) }, []),
307
295
  };
308
296
  }
309
297
 
@@ -323,16 +311,15 @@ export interface ClaudeMcpConfigOpts {
323
311
  relayUrl: string;
324
312
  endpoint?: string;
325
313
  includeRelay: boolean;
326
- generation?: ToolGeneration;
327
314
  servers?: Record<string, ProvisioningMcpServer>;
328
- /** #662 — host USER-scope MCP servers (`~/.claude.json` `mcpServers`), re-injected for
315
+ /** host USER-scope MCP servers (`~/.claude.json` `mcpServers`), re-injected for
329
316
  * `mcp.mode:"host"` so `--strict-mcp-config` PRESERVES them instead of dropping the
330
317
  * ambient host MCP a host-base agent loaded before strict. Lowest precedence: the relay
331
318
  * endpoint and relay-provisioned servers win on a name collision (relay decides). */
332
319
  hostServers?: Record<string, ProvisioningMcpServer>;
333
320
  }
334
321
 
335
- // #1292 — the `{ mcpServers: {...} }` object Claude parses, extracted so both the launch
322
+ // the `{ mcpServers: {...} }` object Claude parses, extracted so both the launch
336
323
  // assembler (decides whether it's written to argv or a file) and the materializer (writes
337
324
  // the file) build the IDENTICAL payload from the same inputs.
338
325
  export function claudeMcpConfigPayload(opts: ClaudeMcpConfigOpts): Record<string, unknown> {
@@ -345,9 +332,9 @@ export function claudeMcpConfigPayload(opts: ClaudeMcpConfigOpts): Record<string
345
332
  if (Object.keys(entry).length) mcpServers[name] = entry;
346
333
  }
347
334
  if (opts.includeRelay) {
348
- mcpServers[mcpServerInfo(opts.generation ?? "relay").name] = {
335
+ mcpServers[MCP_SERVER_INFO.name] = {
349
336
  type: "http",
350
- url: generationEndpoint(opts.endpoint ?? relayMcpEndpoint(opts.relayUrl), opts.generation ?? "relay"),
337
+ url: opts.endpoint ?? relayMcpEndpoint(opts.relayUrl),
351
338
  headers: { Authorization: `Bearer \${${RELAY_MCP_TOKEN_ENV}}` },
352
339
  };
353
340
  }
@@ -400,7 +387,6 @@ export interface OpenCodeMcpConfigOpts {
400
387
  relayUrl: string;
401
388
  endpoint?: string;
402
389
  includeRelay: boolean;
403
- generation?: ToolGeneration;
404
390
  servers?: Record<string, ProvisioningMcpServer>;
405
391
  }
406
392
 
@@ -408,9 +394,9 @@ export interface OpenCodeMcpConfigOpts {
408
394
  export function openCodeMcpConfigPayload(opts: OpenCodeMcpConfigOpts): Record<string, unknown> {
409
395
  const mcp: Record<string, unknown> = {};
410
396
  if (opts.includeRelay) {
411
- mcp[mcpServerInfo(opts.generation ?? "relay").name] = {
397
+ mcp[MCP_SERVER_INFO.name] = {
412
398
  type: "remote",
413
- url: generationEndpoint(opts.endpoint ?? relayMcpEndpoint(opts.relayUrl), opts.generation ?? "relay"),
399
+ url: opts.endpoint ?? relayMcpEndpoint(opts.relayUrl),
414
400
  headers: { Authorization: `Bearer {env:${RELAY_MCP_TOKEN_ENV}}` },
415
401
  oauth: false,
416
402
  timeout: OPENCODE_MCP_TIMEOUT_MS,
@@ -426,7 +412,7 @@ export function openCodeMcpConfigPayload(opts: OpenCodeMcpConfigOpts): Record<st
426
412
 
427
413
  export function claudeMcpLaunchArgs(opts: ClaudeMcpConfigOpts & {
428
414
  strict?: boolean;
429
- /** #1292 — when set, `--mcp-config` points at this path instead of carrying the JSON
415
+ /** when set, `--mcp-config` points at this path instead of carrying the JSON
430
416
  * inline. Inline JSON lands verbatim in argv, which is world-readable via `ps`/
431
417
  * `/proc/<pid>/cmdline`; a provisioned server's `env`/`headers` can carry a literal
432
418
  * secret, so any launch with a relay-owned scratch dir to write into (see
@@ -442,8 +428,8 @@ export function claudeMcpLaunchArgs(opts: ClaudeMcpConfigOpts & {
442
428
  return ["--mcp-config", configArg, ...(opts.strict ? ["--strict-mcp-config"] : [])];
443
429
  }
444
430
 
445
- // #555 (Codex) — `-c mcp_servers.<name>.*` overrides for each relay-provisioned server, the
446
- // same shape relayMcpCodexConfigArgs uses for the relay endpoint. #1292 — this puts a
431
+ // (Codex) — `-c mcp_servers.<name>.*` overrides for each relay-provisioned server, the
432
+ // same shape relayMcpCodexConfigArgs uses for the relay endpoint. This puts a
447
433
  // provisioned server's literal `env`/`headers` secret material in argv (world-readable via
448
434
  // `ps`/`/proc/<pid>/cmdline`), so it's ONLY safe to use when there is no isolated CODEX_HOME
449
435
  // to materialize `codexProvisionedMcpToml` into instead (host-base Codex reuses the real
@@ -464,12 +450,12 @@ export function codexProvisionedMcpArgs(servers?: Record<string, ProvisioningMcp
464
450
  args.push("-c", `${key}.env.${envKey}=${tomlString(envVal)}`);
465
451
  }
466
452
  if (server.disabled_tools?.length) args.push("-c", `${key}.disabled_tools=${tomlValue(server.disabled_tools)}`);
467
- args.push(...codexTimeoutConfigArgs(key)); // #1248
453
+ args.push(...codexTimeoutConfigArgs(key));
468
454
  }
469
455
  return args;
470
456
  }
471
457
 
472
- // #1292 — the non-secret half of the provisioned-server `-c` overrides (per-server
458
+ // the non-secret half of the provisioned-server `-c` overrides (per-server
473
459
  // tool/startup timeouts only). Pairs with `codexProvisionedMcpToml`, which carries
474
460
  // command/args/url/env into CODEX_HOME/config.toml instead of argv.
475
461
  export function codexProvisionedMcpTimeoutArgs(servers?: Record<string, ProvisioningMcpServer>): string[] {
@@ -481,7 +467,7 @@ export function codexProvisionedMcpTimeoutArgs(servers?: Record<string, Provisio
481
467
  return args;
482
468
  }
483
469
 
484
- // #1292 — renders provisioned servers as `[mcp_servers.<name>]` / `[mcp_servers.<name>.env]`
470
+ // renders provisioned servers as `[mcp_servers.<name>]` / `[mcp_servers.<name>.env]`
485
471
  // TOML tables for CODEX_HOME/config.toml, mirroring the `-c mcp_servers.<name>.*` dotted-key
486
472
  // shape `codexProvisionedMcpArgs` uses, so command/args/url/env never touch argv. Table/key
487
473
  // names are always TOML-quoted (server + env-var names aren't guaranteed bare-key safe).
@@ -544,7 +530,7 @@ export function upsertTomlTable(config: string, header: string, bodyLines: strin
544
530
  return lines.join("\n");
545
531
  }
546
532
 
547
- // #1292 — idempotently upsert provisioned MCP servers' command/args/url/env into
533
+ // idempotently upsert provisioned MCP servers' command/args/url/env into
548
534
  // CODEX_HOME/config.toml, the isolated per-instance home Codex actually reads. Pairs with
549
535
  // `codexProvisionedMcpTimeoutArgs` (the non-secret `-c` overrides that stay in argv). Only
550
536
  // safe to call for an ISOLATED CODEX_HOME (host-base reuses the real `~/.codex`, which relay
@@ -553,7 +539,7 @@ export function writeCodexMcpServersToml(codexHome: string, servers: Record<stri
553
539
  const relevant = Object.entries(servers ?? {}).filter(([name]) => !isRelayMcpServerName(name));
554
540
  const configPath = join(codexHome, "config.toml");
555
541
  const existing = existsSync(configPath) ? readFileSync(configPath, "utf8") : "";
556
- // #3387 — every mcp_servers table in a managed CODEX_HOME is Relay-written: drop the previous
542
+ // every mcp_servers table in a managed CODEX_HOME is Relay-written: drop the previous
557
543
  // launch's set first, so a host MCP class switched off on reuse leaves no stale server behind.
558
544
  let config = withoutTomlTables(existing, "mcp_servers.");
559
545
  if (!relevant.length) {
@@ -4,9 +4,9 @@ import { latestReplyableMessage, type ProviderSessionEvent } from "./adapter";
4
4
  import { logger } from "./logger";
5
5
  import { receivesTurnResponseAutomatically } from "./response-routing";
6
6
 
7
- // Phase 2 (#196) — the crux. The Claude Stop hook used to ask the server, synchronously
7
+ // Phase 2 — the crux. The Claude Stop hook used to ask the server, synchronously
8
8
  // and in the hot path, "does this agent owe a reply?" before clearing the turn. A slow
9
- // server answer (the unindexed reply_to scan, #199) blew the hook's timeout and wedged the
9
+ // server answer (the unindexed reply_to scan) blew the hook's timeout and wedged the
10
10
  // agent in `busy` forever. The fix: the hook asks the Runner, the Runner answers instantly
11
11
  // from this local snapshot, and the snapshot is refreshed from the server only in the
12
12
  // background — never on the path that ends a turn.
@@ -23,13 +23,13 @@ export function obligationRequiresExplicitReply(obligation: ReplyObligation): bo
23
23
  return !receivesTurnResponseAutomatically(obligation.from);
24
24
  }
25
25
 
26
- // #2831 — automatic nags (Stop-hook block, idle re-injection) skip an obligation the server marked
27
- // deferred: its owner is waiting on its own relay_run. The obligation itself stays open.
26
+ // automatic nags (Stop-hook block, idle re-injection) skip an obligation the server marked
27
+ // deferred: its owner is waiting on its own humwick_run. The obligation itself stays open.
28
28
  export function obligationNagEligible(obligation: ReplyObligation): boolean {
29
29
  return obligationRequiresExplicitReply(obligation) && obligation.nagDeferral === undefined;
30
30
  }
31
31
 
32
- // #2335 (P1 privacy) — does this obligation's originator sit behind a CHANNEL-EGRESS bridge
32
+ // (P1 privacy) — does this obligation's originator sit behind a CHANNEL-EGRESS bridge
33
33
  // (`telegram:<account>`, …)? A bridge forwards what it receives to a real human's DM, so the runner's
34
34
  // auto-captured turn narration must never be ADDRESSED at one; the obligation itself still stands and
35
35
  // still requires an EXPLICIT reply (obligationRequiresExplicitReply is deliberately untouched).
@@ -72,14 +72,14 @@ export function responseCaptureReplyRoute(
72
72
 
73
73
  type ReplyObligationFetch = () => Promise<ReplyObligation[]>;
74
74
 
75
- /** The route a turn opened by this delivered batch answers (#2840): its latest replyable message that could
75
+ /** The route a turn opened by this delivered batch answers: its latest replyable message that could
76
76
  * carry an obligation — never the agent's own run/timer wake (sent to itself) nor a system/control notice. */
77
77
  export function deliveredBatchReplyRoute(messages: Message[], selfId?: string): NonNullable<ProviderSessionEvent["replyRoute"]> | null {
78
78
  const replyable = latestReplyableMessage(messages.filter((m) => m.from !== m.to && m.from !== selfId && !isMechanicalMessageKind(m.kind)));
79
79
  return replyable ? { to: replyable.from, replyToMessageId: replyable.id, channelTarget: obligationOriginatesFromChannel(replyable), replyExpected: replyable.replyExpected === true } : null;
80
80
  }
81
81
 
82
- /** #2840 — the server's "answered" rule (annotateAnsweredMessages): the agent replied to it or later in
82
+ /** the server's "answered" rule (annotateAnsweredMessages): the agent replied to it or later in
83
83
  * its thread with a live (non-suppressed) message, or acknowledged it with an ack reaction. */
84
84
  export function threadAnswers(thread: Message[], askedId: number, agentId: string): boolean {
85
85
  const asked = thread.find((m) => m.id === askedId);
@@ -93,7 +93,7 @@ function repliesTo(thread: Message[], askedId: number, agentId: string): boolean
93
93
  return thread.some((m) => m.from === agentId && m.kind !== "session" && m.replyTo === askedId && (isHumanAgentId(m.to) || m.payload?.reportSuppression === undefined));
94
94
  }
95
95
 
96
- // Peer chat has no obligation row and can be acknowledged without a reply, so the thread decides: relay_reply
96
+ // Peer chat has no obligation row and can be acknowledged without a reply, so the thread decides: humwick_reply
97
97
  // (or 👍) followed by a native final stays single. A route backed by an obligation row (a reply-expected
98
98
  // message, or `fromObligationRow`) follows the server's settle rule and is already proven open when unreadable.
99
99
  export async function submittedResponseReplyRoute(
@@ -175,7 +175,7 @@ export class ReplyObligationCache {
175
175
  this.dirtyTimer.unref?.();
176
176
  }
177
177
 
178
- // #2831 — a nag (Stop-hook block, idle re-injection) decided on a stale snapshot can miss a relay_run
178
+ // a nag (Stop-hook block, idle re-injection) decided on a stale snapshot can miss a humwick_run
179
179
  // started this turn, or one that just settled. Re-read once, bounded well under the Stop hook's 2s
180
180
  // curl budget, but only when an explicit-reply obligation is in play; otherwise serve the snapshot.
181
181
  async freshForNag(timeoutMs = NAG_REFRESH_TIMEOUT_MS): Promise<ReplyObligation[]> {
@@ -14,7 +14,7 @@ type SessionPublisher = (input: {
14
14
  replyTo?: number;
15
15
  }) => void | Promise<void>;
16
16
 
17
- /** The turn-final capture's addressing, after the #2335 channel guard has had its say. */
17
+ /** The turn-final capture's addressing, after the channel guard has had its say. */
18
18
  interface CaptureRoute {
19
19
  to: string;
20
20
  replyTo?: number;
@@ -29,13 +29,13 @@ interface CapturedResponseInput {
29
29
  session: MessageSessionMeta;
30
30
  replyTo?: number;
31
31
  /**
32
- * #2335 — the reply route resolved to a CHANNEL-EGRESS bridge (see obligationOriginatesFromChannel,
32
+ * the reply route resolved to a CHANNEL-EGRESS bridge (see obligationOriginatesFromChannel,
33
33
  * runner/src/reply-obligation-cache.ts). Set by the caller; absent means "not a channel".
34
34
  */
35
35
  channelTarget?: boolean;
36
36
  }
37
37
 
38
- // #1086: dual-stamp the Part A (#1082) turn-finality field alongside the legacy
38
+ // dual-stamp the Part A turn-finality field alongside the legacy
39
39
  // `final` alias so old consumers keep working while new ones can key off the
40
40
  // typed `turnFinal` flag directly instead of every writer's own `final: true`.
41
41
  function finalSession(session: MessageSessionMeta): MessageSessionMeta {
@@ -43,7 +43,7 @@ function finalSession(session: MessageSessionMeta): MessageSessionMeta {
43
43
  }
44
44
 
45
45
  /**
46
- * #2335 (P1 privacy) — the runner-side half of "auto-captured narration NEVER reaches a channel".
46
+ * (P1 privacy) — the runner-side half of "auto-captured narration NEVER reaches a channel".
47
47
  *
48
48
  * What this function captures is the agent's raw terminal narration at turn end: its own running
49
49
  * commentary, written for its operator's scrollback. Whenever a reply obligation was open, that text
@@ -59,10 +59,10 @@ function finalSession(session: MessageSessionMeta): MessageSessionMeta {
59
59
  * stamp and the channel-recipient poll (isDisallowedChannelContent, src/db/channel-egress-allowlist.ts),
60
60
  * which is what still holds when the sender is a stale runner, an automation, or a raw POST.
61
61
  *
62
- * The capture is not discarded — #1083 requires a turn-final capture to ALWAYS be mirrored. It is
62
+ * The capture is not discarded — requires a turn-final capture to ALWAYS be mirrored. It is
63
63
  * re-addressed to the `user` dashboard-mirror sink with the reply link dropped, exactly the shape an
64
64
  * autonomous turn with no eligible obligation already produces. Dropping `replyTo` is the point, not a
65
- * side effect: the channel obligation stays OPEN and still demands an explicit `relay_reply` /
65
+ * side effect: the channel obligation stays OPEN and still demands an explicit `humwick_reply` /
66
66
  * `relay_egress_to_channel`, so a channel gets a deliberate, addressed answer or none at all.
67
67
  */
68
68
  function captureRoute(input: CapturedResponseInput): CaptureRoute {
@@ -74,10 +74,10 @@ export async function publishCapturedResponse(input: CapturedResponseInput & {
74
74
  publishSessionEvent: SessionPublisher;
75
75
  }): Promise<void> {
76
76
  const route = captureRoute(input);
77
- // #2844 — the enqueued chat report IS the parent delivery, so the mirror stays
77
+ // the enqueued chat report IS the parent delivery, so the mirror stays
78
78
  // transcript-only (`user` sink): at most one parent-addressed row per turn-final. (Before,
79
79
  // the mirror ALSO went to the parent, doubling opener-answered turns and splitting the
80
- // #2831/#2840 suppression decision.) The mirror keeps replyTo + turn stamp for lineage.
80
+ // suppression decision.) The mirror keeps replyTo + turn stamp for lineage.
81
81
  const reported = input.replyTo !== undefined && !receivesTurnResponseAutomatically(input.to) && input.channelTarget !== true;
82
82
  await input.publishSessionEvent({
83
83
  from: input.from,
@@ -90,7 +90,7 @@ export async function publishCapturedResponse(input: CapturedResponseInput & {
90
90
  }
91
91
 
92
92
  export function enqueueCapturedResponseReport(input: CapturedResponseInput): void {
93
- // #2335 — `to === "user"` is kept (the dashboard mirror already carries the turn; a report would
93
+ // `to === "user"` is kept (the dashboard mirror already carries the turn; a report would
94
94
  // double it) but is NO LONGER the only skip: a channel-egress target is dropped outright, whether
95
95
  // it arrived here pre-rewritten by captureRoute or straight from a caller that never consulted it.
96
96
  if (!input.replyTo || receivesTurnResponseAutomatically(input.to) || input.channelTarget === true) return;
@@ -1,6 +1,6 @@
1
1
  import { isHumanAgentId } from "@humwick/sdk/types/users";
2
2
  // The runner mirrors every completed turn to the dashboard's user sink, regardless of role.
3
- // Keep header/nag policy and capture-report routing on the same recipient rule (#3346).
3
+ // Keep header/nag policy and capture-report routing on the same recipient rule.
4
4
  export function receivesTurnResponseAutomatically(recipient: string): boolean {
5
5
  return isHumanAgentId(recipient);
6
6
  }