@gajae-code/coding-agent 0.16.3 → 0.16.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 (277) hide show
  1. package/CHANGELOG.md +113 -0
  2. package/dist/types/capability/types.d.ts +4 -0
  3. package/dist/types/cli/args.d.ts +2 -2
  4. package/dist/types/cli/customize-doctor.d.ts +22 -0
  5. package/dist/types/cli/root-flags.d.ts +4 -2
  6. package/dist/types/cli.d.ts +1 -1
  7. package/dist/types/commands/launch.d.ts +1 -1
  8. package/dist/types/config/autorouting-tier-map.d.ts +3 -0
  9. package/dist/types/config/file-lock.d.ts +31 -3
  10. package/dist/types/config/model-registry.d.ts +4 -0
  11. package/dist/types/config/model-resolver.d.ts +9 -0
  12. package/dist/types/config/settings-schema.d.ts +29 -1
  13. package/dist/types/coordinator-mcp/question-state.d.ts +7 -0
  14. package/dist/types/coordinator-mcp/server.d.ts +7 -0
  15. package/dist/types/discovery/claude-plugins.d.ts +3 -1
  16. package/dist/types/discovery/helpers.d.ts +35 -0
  17. package/dist/types/exec/bash-executor.d.ts +17 -1
  18. package/dist/types/exec/bash-shell-guardian.d.ts +15 -0
  19. package/dist/types/exec/bash-shell-runtime-entry.d.ts +2 -0
  20. package/dist/types/exec/bash-shell-signals.d.ts +2 -0
  21. package/dist/types/exec/bash-shell-supervisor-entry.d.ts +1 -0
  22. package/dist/types/exec/bash-shell-supervisor.d.ts +10 -0
  23. package/dist/types/exec/bash-shell-worker-entry.d.ts +2 -0
  24. package/dist/types/exec/bash-shell-worker-protocol.d.ts +63 -0
  25. package/dist/types/exec/bash-shell-worker.d.ts +1 -0
  26. package/dist/types/exec/isolated-shell.d.ts +19 -0
  27. package/dist/types/extensibility/extensions/function-hooks-internal.d.ts +9 -0
  28. package/dist/types/extensibility/extensions/function-hooks.d.ts +169 -0
  29. package/dist/types/extensibility/extensions/index.d.ts +1 -0
  30. package/dist/types/extensibility/extensions/runner.d.ts +46 -6
  31. package/dist/types/extensibility/extensions/types.d.ts +16 -0
  32. package/dist/types/extensibility/extensions/wrapper.d.ts +3 -1
  33. package/dist/types/extensibility/gjc-plugins/constrained-hooks.d.ts +35 -2
  34. package/dist/types/extensibility/gjc-plugins/mcp-policy.d.ts +16 -0
  35. package/dist/types/extensibility/gjc-plugins/paths.d.ts +2 -0
  36. package/dist/types/extensibility/gjc-plugins/types.d.ts +25 -0
  37. package/dist/types/extensibility/hooks/types.d.ts +4 -0
  38. package/dist/types/extensibility/runtime-skill-discovery.d.ts +5 -2
  39. package/dist/types/extensibility/shared-events.d.ts +1 -1
  40. package/dist/types/extensibility/skill-management.d.ts +9 -4
  41. package/dist/types/extensibility/skills.d.ts +2 -0
  42. package/dist/types/gjc-runtime/empty-delete-gc.d.ts +4 -1
  43. package/dist/types/gjc-runtime/linux-proc.d.ts +2 -0
  44. package/dist/types/gjc-runtime/session-state-lock.d.ts +14 -0
  45. package/dist/types/gjc-runtime/ultragoal-runtime.d.ts +41 -3
  46. package/dist/types/gjc-runtime/ultragoal-succession.d.ts +251 -0
  47. package/dist/types/modes/components/assistant-message.d.ts +1 -2
  48. package/dist/types/modes/components/bash-execution.d.ts +1 -0
  49. package/dist/types/modes/components/execution-shared.d.ts +1 -0
  50. package/dist/types/modes/components/settings-selector.d.ts +6 -1
  51. package/dist/types/modes/components/status-line/command.d.ts +35 -0
  52. package/dist/types/modes/components/status-line/index.d.ts +1 -0
  53. package/dist/types/modes/components/status-line/types.d.ts +5 -0
  54. package/dist/types/modes/components/tool-status-header.d.ts +14 -0
  55. package/dist/types/modes/controllers/event-controller.d.ts +6 -0
  56. package/dist/types/modes/interactive-mode.d.ts +3 -0
  57. package/dist/types/modes/types.d.ts +7 -0
  58. package/dist/types/runtime-mcp/client.d.ts +9 -1
  59. package/dist/types/runtime-mcp/manager.d.ts +7 -1
  60. package/dist/types/runtime-mcp/pool.d.ts +22 -3
  61. package/dist/types/runtime-mcp/transports/stdio.d.ts +0 -4
  62. package/dist/types/runtime-mcp/types.d.ts +22 -0
  63. package/dist/types/sdk/broker/ensure.d.ts +32 -0
  64. package/dist/types/sdk/broker/lifecycle-ledger.d.ts +0 -1
  65. package/dist/types/sdk/broker/lifecycle.d.ts +6 -0
  66. package/dist/types/sdk/broker/startup-failure.d.ts +4 -1
  67. package/dist/types/sdk/bus/chat-daemon-control.d.ts +2 -0
  68. package/dist/types/sdk/bus/chat-daemon-runtime.d.ts +12 -0
  69. package/dist/types/sdk/bus/chat-effect-journal.d.ts +17 -2
  70. package/dist/types/sdk/bus/conversation-store.d.ts +11 -0
  71. package/dist/types/sdk/bus/lifecycle-commands.d.ts +8 -8
  72. package/dist/types/sdk/bus/slack-daemon.d.ts +1 -1
  73. package/dist/types/sdk/bus/slack-live-provider.d.ts +6 -0
  74. package/dist/types/sdk/bus/slack-provider.d.ts +12 -0
  75. package/dist/types/sdk/bus/topic-registry.d.ts +8 -0
  76. package/dist/types/sdk/client/client.d.ts +6 -0
  77. package/dist/types/sdk/host/host.d.ts +3 -1
  78. package/dist/types/sdk/host/session-runtime.d.ts +26 -1
  79. package/dist/types/sdk/lifecycle/service.d.ts +1 -0
  80. package/dist/types/sdk/router/session-router.d.ts +10 -2
  81. package/dist/types/sdk/session-api-key.d.ts +26 -0
  82. package/dist/types/sdk/session.d.ts +5 -1
  83. package/dist/types/sdk/transport/serve-cli.d.ts +1 -1
  84. package/dist/types/session/agent-session.d.ts +11 -0
  85. package/dist/types/session/fallback-chain-controller.d.ts +15 -0
  86. package/dist/types/session/internal/managed-session-storage.d.ts +7 -0
  87. package/dist/types/session/messages.d.ts +1 -0
  88. package/dist/types/session/session-manager.d.ts +3 -1
  89. package/dist/types/setup/paseo/announce.d.ts +112 -0
  90. package/dist/types/setup/paseo/setup-deps.d.ts +2 -1
  91. package/dist/types/skill-state/canonical-skills.d.ts +4 -0
  92. package/dist/types/system-prompt.d.ts +10 -0
  93. package/dist/types/task/executor.d.ts +5 -1
  94. package/dist/types/tools/bash.d.ts +1 -0
  95. package/dist/types/tools/fetch.d.ts +7 -0
  96. package/dist/types/tools/github-cache.d.ts +2 -1
  97. package/dist/types/tools/index.d.ts +10 -0
  98. package/dist/types/tools/path-utils.d.ts +2 -0
  99. package/dist/types/tools/search.d.ts +6 -0
  100. package/package.json +9 -7
  101. package/scripts/capture-status-line-custom-editor-showcase.ts +151 -0
  102. package/scripts/generate-sdk-operation-inventory.ts +11 -0
  103. package/scripts/verify-status-line-custom-editor-showcase.ts +309 -0
  104. package/src/capability/index.ts +14 -2
  105. package/src/capability/types.ts +4 -0
  106. package/src/cli/args.ts +20 -9
  107. package/src/cli/customize-doctor.ts +155 -12
  108. package/src/cli/root-flags.ts +5 -3
  109. package/src/cli.ts +22 -0
  110. package/src/commands/sdk.ts +74 -31
  111. package/src/config/autorouting-tier-map.ts +1 -0
  112. package/src/config/file-lock-gc.ts +40 -2
  113. package/src/config/file-lock.ts +379 -31
  114. package/src/config/model-preset-registry.ts +120 -19
  115. package/src/config/model-profiles.ts +40 -0
  116. package/src/config/model-registry.ts +73 -23
  117. package/src/config/model-resolver.ts +48 -0
  118. package/src/config/settings-schema.ts +33 -1
  119. package/src/config/settings.ts +1 -0
  120. package/src/coordinator-mcp/question-state.ts +33 -2
  121. package/src/coordinator-mcp/server.ts +477 -101
  122. package/src/crash/sanitize.ts +1 -1
  123. package/src/defaults/gjc/extensions/grok-cli-vendor/src/models/catalog.ts +11 -11
  124. package/src/defaults/gjc/extensions/grok-cli-vendor/src/provider/register.ts +13 -2
  125. package/src/defaults/gjc/skills/ultragoal/SKILL.md +22 -0
  126. package/src/discovery/builtin.ts +52 -55
  127. package/src/discovery/claude-plugins.ts +12 -4
  128. package/src/discovery/helpers.ts +307 -34
  129. package/src/exec/bash-executor.ts +330 -50
  130. package/src/exec/bash-shell-guardian.ts +388 -0
  131. package/src/exec/bash-shell-runtime-entry.ts +5 -0
  132. package/src/exec/bash-shell-signals.ts +29 -0
  133. package/src/exec/bash-shell-supervisor-entry.ts +3 -0
  134. package/src/exec/bash-shell-supervisor.ts +359 -0
  135. package/src/exec/bash-shell-worker-entry.ts +5 -0
  136. package/src/exec/bash-shell-worker-protocol.ts +36 -0
  137. package/src/exec/bash-shell-worker.ts +116 -0
  138. package/src/exec/isolated-shell.ts +393 -0
  139. package/src/extensibility/extensions/function-hooks-internal.ts +73 -0
  140. package/src/extensibility/extensions/function-hooks.ts +959 -0
  141. package/src/extensibility/extensions/index.ts +22 -0
  142. package/src/extensibility/extensions/loader.ts +53 -2
  143. package/src/extensibility/extensions/runner.ts +895 -264
  144. package/src/extensibility/extensions/types.ts +42 -0
  145. package/src/extensibility/extensions/wrapper.ts +56 -15
  146. package/src/extensibility/gjc-plugins/compiler.ts +228 -60
  147. package/src/extensibility/gjc-plugins/constrained-hooks.ts +321 -23
  148. package/src/extensibility/gjc-plugins/installer.ts +13 -12
  149. package/src/extensibility/gjc-plugins/mcp-policy.ts +81 -52
  150. package/src/extensibility/gjc-plugins/paths.ts +13 -0
  151. package/src/extensibility/gjc-plugins/registry.ts +113 -1
  152. package/src/extensibility/gjc-plugins/runtime-adapters.ts +758 -16
  153. package/src/extensibility/gjc-plugins/runtime-quarantine.ts +20 -2
  154. package/src/extensibility/gjc-plugins/schema.ts +121 -4
  155. package/src/extensibility/gjc-plugins/types.ts +25 -0
  156. package/src/extensibility/hooks/loader.ts +133 -16
  157. package/src/extensibility/hooks/types.ts +28 -0
  158. package/src/extensibility/runtime-skill-discovery.ts +149 -190
  159. package/src/extensibility/shared-events.ts +1 -1
  160. package/src/extensibility/skill-management.ts +200 -52
  161. package/src/extensibility/skills.ts +73 -25
  162. package/src/gjc-runtime/empty-delete-gc.ts +94 -4
  163. package/src/gjc-runtime/gc-runtime.ts +29 -0
  164. package/src/gjc-runtime/linux-proc.ts +14 -0
  165. package/src/gjc-runtime/repository-binding.ts +1 -1
  166. package/src/gjc-runtime/session-state-lock.ts +143 -10
  167. package/src/gjc-runtime/session-state-sidecar.ts +27 -11
  168. package/src/gjc-runtime/state-writer.ts +30 -29
  169. package/src/gjc-runtime/ultragoal-runtime.ts +189 -47
  170. package/src/gjc-runtime/ultragoal-succession.ts +1667 -0
  171. package/src/gjc-runtime/workflow-manifest.generated.json +52 -1
  172. package/src/gjc-runtime/workflow-manifest.ts +16 -0
  173. package/src/harness-control-plane/session-lease.ts +2 -2
  174. package/src/internal-urls/docs-index.generated.ts +12 -12
  175. package/src/lsp/config.ts +10 -3
  176. package/src/lsp/index.ts +1 -1
  177. package/src/lsp/path-trust.ts +69 -14
  178. package/src/main.ts +78 -35
  179. package/src/modes/DESIGN.md +54 -0
  180. package/src/modes/acp/acp-agent.ts +16 -3
  181. package/src/modes/components/assistant-message.ts +22 -9
  182. package/src/modes/components/bash-execution.ts +4 -1
  183. package/src/modes/components/execution-shared.ts +4 -1
  184. package/src/modes/components/model-selector.ts +166 -33
  185. package/src/modes/components/session-observer-overlay.ts +1 -1
  186. package/src/modes/components/settings-selector.ts +1075 -235
  187. package/src/modes/components/status-line/command.ts +190 -0
  188. package/src/modes/components/status-line/index.ts +1 -0
  189. package/src/modes/components/status-line/segments.ts +32 -5
  190. package/src/modes/components/status-line/types.ts +5 -0
  191. package/src/modes/components/tool-status-header.ts +396 -22
  192. package/src/modes/controllers/command-controller.ts +3 -0
  193. package/src/modes/controllers/event-controller.ts +151 -7
  194. package/src/modes/controllers/extension-ui-controller.ts +1 -0
  195. package/src/modes/controllers/input-controller.ts +6 -3
  196. package/src/modes/controllers/selector-controller.ts +21 -0
  197. package/src/modes/interactive-mode.ts +53 -6
  198. package/src/modes/print-mode.ts +5 -1
  199. package/src/modes/prompt-action-autocomplete.ts +54 -27
  200. package/src/modes/runtime-init.ts +2 -0
  201. package/src/modes/types.ts +17 -3
  202. package/src/modes/utils/ui-helpers.ts +2 -0
  203. package/src/prompts/system/system-prompt.md +8 -0
  204. package/src/prompts/tools/search.md +2 -0
  205. package/src/prompts/tools/subagent.md +1 -0
  206. package/src/runtime-mcp/client.ts +60 -17
  207. package/src/runtime-mcp/manager.ts +189 -14
  208. package/src/runtime-mcp/pool.test.ts +218 -0
  209. package/src/runtime-mcp/pool.ts +224 -23
  210. package/src/runtime-mcp/transports/stdio.ts +158 -24
  211. package/src/runtime-mcp/types.ts +24 -0
  212. package/src/sdk/broker/broker.ts +124 -32
  213. package/src/sdk/broker/ensure.ts +248 -41
  214. package/src/sdk/broker/lifecycle-ledger.ts +7 -17
  215. package/src/sdk/broker/lifecycle.ts +56 -6
  216. package/src/sdk/broker/startup-failure.ts +15 -6
  217. package/src/sdk/bus/chat-daemon-control.ts +3 -1
  218. package/src/sdk/bus/chat-daemon-runtime.ts +72 -6
  219. package/src/sdk/bus/chat-effect-journal.ts +132 -7
  220. package/src/sdk/bus/conversation-store.ts +29 -0
  221. package/src/sdk/bus/lifecycle-commands.ts +17 -11
  222. package/src/sdk/bus/slack-conversation.ts +1 -0
  223. package/src/sdk/bus/slack-daemon.ts +478 -49
  224. package/src/sdk/bus/slack-live-provider.ts +12 -0
  225. package/src/sdk/bus/slack-provider.ts +5 -0
  226. package/src/sdk/bus/telegram-daemon.ts +16 -2
  227. package/src/sdk/bus/topic-registry.ts +19 -0
  228. package/src/sdk/cli/master-cli.ts +1 -1
  229. package/src/sdk/cli/session-cli.ts +11 -8
  230. package/src/sdk/client/client.ts +66 -17
  231. package/src/sdk/host/host.ts +29 -9
  232. package/src/sdk/host/query/revision-store.ts +19 -13
  233. package/src/sdk/host/reverse-leases.ts +7 -4
  234. package/src/sdk/host/session-runtime.test.ts +52 -10
  235. package/src/sdk/host/session-runtime.ts +383 -44
  236. package/src/sdk/lifecycle/service.ts +4 -0
  237. package/src/sdk/protocol/operation-inventory.generated.json +77 -0
  238. package/src/sdk/router/session-router.ts +89 -38
  239. package/src/sdk/session-api-key.ts +94 -0
  240. package/src/sdk/session.ts +228 -55
  241. package/src/sdk/transport/relay.ts +35 -13
  242. package/src/sdk/transport/serve-cli.ts +3 -3
  243. package/src/session/agent-session.ts +240 -90
  244. package/src/session/contribution-prep.ts +214 -21
  245. package/src/session/fallback-chain-controller.ts +38 -0
  246. package/src/session/internal/managed-session-storage.ts +19 -5
  247. package/src/session/messages.ts +5 -0
  248. package/src/session/session-manager.ts +29 -5
  249. package/src/session-import/redact.ts +5 -1
  250. package/src/setup/paseo/announce.ts +456 -0
  251. package/src/setup/paseo/setup-deps.ts +15 -3
  252. package/src/skill-state/canonical-skills.ts +12 -0
  253. package/src/skills/skills.test.ts +41 -21
  254. package/src/slash-commands/builtin-registry.ts +1 -0
  255. package/src/system-prompt.ts +29 -3
  256. package/src/task/executor.ts +7 -1
  257. package/src/task/index.ts +21 -4
  258. package/src/tools/bash.ts +63 -19
  259. package/src/tools/cron.ts +1 -1
  260. package/src/tools/fetch.ts +29 -1
  261. package/src/tools/gh.ts +26 -3
  262. package/src/tools/github-cache.ts +68 -16
  263. package/src/tools/index.ts +10 -0
  264. package/src/tools/job.ts +21 -4
  265. package/src/tools/monitor.ts +8 -2
  266. package/src/tools/path-utils.ts +7 -0
  267. package/src/tools/read.ts +14 -2
  268. package/src/tools/render-utils.ts +1 -3
  269. package/src/tools/resource-gc.ts +28 -8
  270. package/src/tools/search.ts +116 -58
  271. package/src/tools/skill-discovery.ts +5 -0
  272. package/src/tools/skill.ts +37 -2
  273. package/src/tools/subagent.ts +6 -3
  274. package/src/tools/tool-catalog.generated.ts +10 -3
  275. package/src/tools/ultragoal-ask-guard.ts +2 -2
  276. package/src/web/insane/bridge.ts +113 -23
  277. package/src/web/search/providers/perplexity.ts +102 -41
@@ -2841,6 +2841,50 @@
2841
2841
  "testIds": "not_applicable"
2842
2842
  }
2843
2843
  },
2844
+ {
2845
+ "sourceId": "agent_session:awaitDisposeCompletion",
2846
+ "sourceFile": "packages/coding-agent/src/session/agent-session.ts",
2847
+ "sourceKind": "agent_session",
2848
+ "decision": "exclude",
2849
+ "rationale": "test-only disposal completion seam, not a user-facing SDK control seam",
2850
+ "exclusionMetadata": {
2851
+ "adapterMappings": "not_applicable",
2852
+ "testIds": "not_applicable"
2853
+ }
2854
+ },
2855
+ {
2856
+ "sourceId": "agent_session:setDisposeTimeoutForTests",
2857
+ "sourceFile": "packages/coding-agent/src/session/agent-session.ts",
2858
+ "sourceKind": "agent_session",
2859
+ "decision": "exclude",
2860
+ "rationale": "test-only disposal timeout seam, not a user-facing SDK control seam",
2861
+ "exclusionMetadata": {
2862
+ "adapterMappings": "not_applicable",
2863
+ "testIds": "not_applicable"
2864
+ }
2865
+ },
2866
+ {
2867
+ "sourceId": "agent_session:trackPostPromptTaskForTests",
2868
+ "sourceFile": "packages/coding-agent/src/session/agent-session.ts",
2869
+ "sourceKind": "agent_session",
2870
+ "decision": "exclude",
2871
+ "rationale": "test-only post-prompt task seam, not a user-facing SDK control seam",
2872
+ "exclusionMetadata": {
2873
+ "adapterMappings": "not_applicable",
2874
+ "testIds": "not_applicable"
2875
+ }
2876
+ },
2877
+ {
2878
+ "sourceId": "agent_session:requestWorkerIntegrationForTests",
2879
+ "sourceFile": "packages/coding-agent/src/session/agent-session.ts",
2880
+ "sourceKind": "agent_session",
2881
+ "decision": "exclude",
2882
+ "rationale": "test-only worker integration seam, not a user-facing SDK control seam",
2883
+ "exclusionMetadata": {
2884
+ "adapterMappings": "not_applicable",
2885
+ "testIds": "not_applicable"
2886
+ }
2887
+ },
2844
2888
  {
2845
2889
  "sourceId": "agent_session:closeWriterStrict",
2846
2890
  "sourceFile": "packages/coding-agent/src/session/agent-session.ts",
@@ -2885,6 +2929,28 @@
2885
2929
  "testIds": "not_applicable"
2886
2930
  }
2887
2931
  },
2932
+ {
2933
+ "sourceId": "agent_session:runWithPromptAdmissionForTests",
2934
+ "sourceFile": "packages/coding-agent/src/session/agent-session.ts",
2935
+ "sourceKind": "agent_session",
2936
+ "decision": "exclude",
2937
+ "rationale": "test-only prompt admission seam, not a user-facing SDK control seam",
2938
+ "exclusionMetadata": {
2939
+ "adapterMappings": "not_applicable",
2940
+ "testIds": "not_applicable"
2941
+ }
2942
+ },
2943
+ {
2944
+ "sourceId": "agent_session:awaitCoordinatorRuntimeStatePersistenceForTests",
2945
+ "sourceFile": "packages/coding-agent/src/session/agent-session.ts",
2946
+ "sourceKind": "agent_session",
2947
+ "decision": "exclude",
2948
+ "rationale": "test-only coordinator persistence drain barrier, not a user-facing SDK control seam",
2949
+ "exclusionMetadata": {
2950
+ "adapterMappings": "not_applicable",
2951
+ "testIds": "not_applicable"
2952
+ }
2953
+ },
2888
2954
  {
2889
2955
  "sourceId": "agent_session:queueCoordinatorRuntimeStatePersistForTests",
2890
2956
  "sourceFile": "packages/coding-agent/src/session/agent-session.ts",
@@ -3912,6 +3978,17 @@
3912
3978
  "testIds": "not_applicable"
3913
3979
  }
3914
3980
  },
3981
+ {
3982
+ "sourceId": "agent_session:reloadSkills",
3983
+ "sourceFile": "packages/coding-agent/src/session/agent-session.ts",
3984
+ "sourceKind": "agent_session",
3985
+ "decision": "exclude",
3986
+ "rationale": "internal marketplace lifecycle refresh for the session skill catalog; not an independent SDK ingress operation",
3987
+ "exclusionMetadata": {
3988
+ "adapterMappings": "not_applicable",
3989
+ "testIds": "not_applicable"
3990
+ }
3991
+ },
3915
3992
  {
3916
3993
  "sourceId": "agent_session:retireWorkspaceTreeForRescope",
3917
3994
  "sourceFile": "packages/coding-agent/src/session/agent-session.ts",
@@ -2,6 +2,7 @@ import * as crypto from "node:crypto";
2
2
  import * as fs from "node:fs/promises";
3
3
  import * as path from "node:path";
4
4
  import { logger, resolveEquivalentPath } from "@gajae-code/utils";
5
+ import { matchesIndexedEndpointFile } from "../broker/endpoint-authority";
5
6
  import {
6
7
  canonicalSessionCwd,
7
8
  SessionIndex as DefaultSessionIndex,
@@ -11,7 +12,13 @@ import {
11
12
  type SessionIndex,
12
13
  } from "../broker/session-index";
13
14
  import { cancellableSleep, lifecycleRequestTimeoutMs } from "../broker/startup-budget";
14
- import { SdkClient, SdkClientError, type SdkDispatchContext, type SdkDispatchHandler } from "../client/client";
15
+ import {
16
+ SdkClient,
17
+ SdkClientError,
18
+ type SdkDispatchContext,
19
+ type SdkDispatchHandler,
20
+ SdkPreparedDispatchError,
21
+ } from "../client/client";
15
22
  import {
16
23
  endpointDirectory,
17
24
  readSdkBrokerDiscovery,
@@ -27,6 +34,7 @@ import {
27
34
  import { ACP_SESSION_RECONNECT, SESSION_REQUEST_TIMEOUT_MS } from "../session-reconnect";
28
35
 
29
36
  export interface SessionEndpointIdentity {
37
+ readonly dev: bigint;
30
38
  readonly mtimeMs: number;
31
39
  readonly mtimeNs: bigint;
32
40
  readonly ctimeNs: bigint;
@@ -56,6 +64,7 @@ export function sessionAttachmentAuthorityId(input: {
56
64
  .digest("hex");
57
65
  const endpointIdentity = input.endpointIdentity
58
66
  ? {
67
+ dev: input.endpointIdentity.dev.toString(),
59
68
  mtimeMs: input.endpointIdentity.mtimeMs,
60
69
  mtimeNs: input.endpointIdentity.mtimeNs.toString(),
61
70
  ctimeNs: input.endpointIdentity.ctimeNs.toString(),
@@ -86,7 +95,13 @@ export interface SessionAttachment {
86
95
  readonly connectionId?: string;
87
96
  readonly generation: number;
88
97
  isCurrent(): boolean;
89
- send(frame: Record<string, unknown>): unknown;
98
+ send(
99
+ frame: Record<string, unknown>,
100
+ options?: {
101
+ beforeDispatch?: () => void;
102
+ dispatchFence?: (dispatch: () => Promise<void>) => Promise<void>;
103
+ },
104
+ ): unknown;
90
105
  /**
91
106
  * Idempotent provider-lease heartbeat send that skips the pre-send authority
92
107
  * reconcile (#4689).
@@ -179,6 +194,7 @@ export interface SessionRouterClient {
179
194
  frame: Record<string, unknown>,
180
195
  options?: {
181
196
  timeoutMs?: number;
197
+ connectedOnly?: boolean;
182
198
  /** Synchronous pre-send observer; a throw aborts the dispatch before the wire. */
183
199
  beforeDispatch?: (context: SdkDispatchContext) => void;
184
200
  /** Synchronous post-send boundary observer for transport-close-aware consumers. */
@@ -456,6 +472,7 @@ async function lstatEndpoint(file: string): Promise<SessionEndpointIdentity | un
456
472
  )
457
473
  return undefined;
458
474
  return {
475
+ dev: identity.dev,
459
476
  mtimeMs: stat.mtimeMs,
460
477
  mtimeNs: identity.mtimeNs,
461
478
  ctimeNs: identity.ctimeNs,
@@ -470,6 +487,7 @@ function warningAffectsSession(warning: string, sessionId: string): boolean {
470
487
 
471
488
  function sameEndpointIdentity(expected: SessionEndpointIdentity, current: SessionEndpointIdentity): boolean {
472
489
  return (
490
+ expected.dev === current.dev &&
473
491
  expected.mtimeMs === current.mtimeMs &&
474
492
  expected.mtimeNs === current.mtimeNs &&
475
493
  expected.ctimeNs === current.ctimeNs &&
@@ -909,6 +927,7 @@ export class SessionRouter {
909
927
  timeoutMs?: number;
910
928
  beforeDispatch?: (context: SdkDispatchContext) => void;
911
929
  onDispatch?: SdkDispatchHandler;
930
+ dispatchFence?: (dispatch: () => Promise<Record<string, unknown>>) => Promise<Record<string, unknown>>;
912
931
  },
913
932
  ): Promise<Record<string, unknown>> {
914
933
  const matchesExpectedAuthority = (attachment: SessionAttachment): boolean =>
@@ -946,31 +965,34 @@ export class SessionRouter {
946
965
  // token: the wire frame alone carries credentials, and the observer
947
966
  // context is a deep-frozen, token-redacted copy (#4640 review).
948
967
  const wireFrame = this.#prepareFrame(attached, frame);
949
- const { beforeDispatch, onDispatch, ...requestOptions } = options ?? {};
950
- const response = await attached.client.request(wireFrame, {
951
- ...requestOptions,
952
- timeoutMs: requestOptions.timeoutMs ?? SESSION_REQUEST_TIMEOUT_MS,
953
- ...(beforeDispatch
954
- ? {
955
- beforeDispatch: (context: SdkDispatchContext) => {
956
- return beforeDispatch({
957
- ...context,
958
- frame: redactDispatchFrame(context.frame),
959
- });
960
- },
961
- }
962
- : {}),
963
- ...(onDispatch
964
- ? {
965
- onDispatch: (context: SdkDispatchContext) => {
966
- return onDispatch({
967
- ...context,
968
- frame: redactDispatchFrame(context.frame),
969
- });
970
- },
971
- }
972
- : {}),
973
- });
968
+ const { beforeDispatch, onDispatch, dispatchFence, ...requestOptions } = options ?? {};
969
+ try {
970
+ await attached.client.connect?.();
971
+ } catch (error) {
972
+ if (error instanceof SdkClientError) throw new SdkPreparedDispatchError(error);
973
+ throw error;
974
+ }
975
+ if (!this.#attachmentPublished(attached))
976
+ throw new SessionRouterError("pre_send", "SDK session attachment changed during transport preparation.");
977
+ const dispatch = () =>
978
+ attached.client.request(wireFrame, {
979
+ ...requestOptions,
980
+ connectedOnly: true,
981
+ timeoutMs: requestOptions.timeoutMs ?? SESSION_REQUEST_TIMEOUT_MS,
982
+ ...(beforeDispatch
983
+ ? {
984
+ beforeDispatch: (context: SdkDispatchContext) =>
985
+ beforeDispatch({ ...context, frame: redactDispatchFrame(context.frame) }),
986
+ }
987
+ : {}),
988
+ ...(onDispatch
989
+ ? {
990
+ onDispatch: (context: SdkDispatchContext) =>
991
+ onDispatch({ ...context, frame: redactDispatchFrame(context.frame) }),
992
+ }
993
+ : {}),
994
+ });
995
+ const response = await (dispatchFence ? dispatchFence(dispatch) : dispatch());
974
996
  const settled = this.#sessions.get(sessionId);
975
997
  if (
976
998
  !settled ||
@@ -1141,7 +1163,12 @@ export class SessionRouter {
1141
1163
  }
1142
1164
 
1143
1165
  /** Lists saved sessions through Router-owned Broker discovery without exposing credentials or mutation authority. */
1144
- async listBrokerSessions(input: Record<string, unknown>, idempotencyKey: string): Promise<Record<string, unknown>> {
1166
+ async listBrokerSessions(
1167
+ input: Record<string, unknown>,
1168
+ idempotencyKey: string,
1169
+ options?: { beforeDispatch?: (context: SdkDispatchContext) => void },
1170
+ dispatchFence?: (dispatch: () => Promise<Record<string, unknown>>) => Promise<Record<string, unknown>>,
1171
+ ): Promise<Record<string, unknown>> {
1145
1172
  const operation = "session.list";
1146
1173
  const discovery = await readSdkBrokerDiscovery(this.#agentDir);
1147
1174
  if (!discovery) throw new SessionRouterError("pre_send", "SDK broker discovery is unavailable.");
@@ -1155,10 +1182,18 @@ export class SessionRouter {
1155
1182
  }
1156
1183
  try {
1157
1184
  const timeoutMs = lifecycleRequestTimeoutMs(operation, input);
1158
- return await client.request(
1159
- { type: "broker_request", operation, input, idempotencyKey },
1160
- timeoutMs === undefined ? undefined : { timeoutMs },
1161
- );
1185
+ try {
1186
+ await client.connect?.();
1187
+ } catch (error) {
1188
+ if (error instanceof SdkClientError) throw new SdkPreparedDispatchError(error);
1189
+ throw error;
1190
+ }
1191
+ const dispatch = () =>
1192
+ client.request(
1193
+ { type: "broker_request", operation, input, idempotencyKey },
1194
+ { ...(timeoutMs === undefined ? {} : { timeoutMs }), ...options, connectedOnly: true },
1195
+ );
1196
+ return await (dispatchFence ? dispatchFence(dispatch) : dispatch());
1162
1197
  } finally {
1163
1198
  await client.close().catch(error => {
1164
1199
  logger.warn(`SDK Broker session.list transport cleanup failed (${String(error)}).`);
@@ -1360,7 +1395,7 @@ export class SessionRouter {
1360
1395
  // cannot keep the old attachment authorized.
1361
1396
  const identityBefore = await lstatEndpoint(attached.endpoint.path);
1362
1397
  if (!identityBefore || !sameEndpointIdentity(attached.endpointIdentity, identityBefore)) return false;
1363
- if (identityBefore.mtimeMs !== indexed.endpointMtimeMs) return false;
1398
+ if (!matchesIndexedEndpointFile(identityBefore, indexed)) return false;
1364
1399
  let raw: Record<string, unknown>;
1365
1400
  try {
1366
1401
  const parsed = JSON.parse(await Bun.file(attached.endpoint.path).text());
@@ -1384,7 +1419,7 @@ export class SessionRouter {
1384
1419
  const identityAfter = await lstatEndpoint(attached.endpoint.path);
1385
1420
  return (
1386
1421
  identityAfter !== undefined &&
1387
- identityAfter.mtimeMs === indexed.endpointMtimeMs &&
1422
+ matchesIndexedEndpointFile(identityAfter, indexed) &&
1388
1423
  sameEndpointIdentity(attached.endpointIdentity, identityAfter)
1389
1424
  );
1390
1425
  }
@@ -1416,7 +1451,7 @@ export class SessionRouter {
1416
1451
  const endpointIdentity = await lstatEndpoint(endpointPath);
1417
1452
  const endpoint = await readSdkSessionEndpoint(cwd, indexed.sessionId, scope);
1418
1453
  if (!endpoint || endpoint.stale || endpoint.pid !== indexed.pid) return null;
1419
- if (!endpointIdentity || endpointIdentity.mtimeMs !== indexed.endpointMtimeMs) return null;
1454
+ if (!endpointIdentity || !matchesIndexedEndpointFile(endpointIdentity, indexed)) return null;
1420
1455
  // Identity is proven INSIDE this authority read (#4730 review): sampling it
1421
1456
  // afterwards would let an identical rename between the read and the sample
1422
1457
  // install the replacement's inode as the trusted baseline.
@@ -1441,7 +1476,7 @@ export class SessionRouter {
1441
1476
  const endpointIdentityAfterRead = await lstatEndpoint(endpoint.path);
1442
1477
  if (
1443
1478
  !endpointIdentityAfterRead ||
1444
- endpointIdentityAfterRead.mtimeMs !== indexed.endpointMtimeMs ||
1479
+ !matchesIndexedEndpointFile(endpointIdentityAfterRead, indexed) ||
1445
1480
  !sameEndpointIdentity(endpointIdentity, endpointIdentityAfterRead)
1446
1481
  )
1447
1482
  return null;
@@ -1641,7 +1676,13 @@ export class SessionRouter {
1641
1676
  return attached?.client.connectionId;
1642
1677
  },
1643
1678
  isCurrent: () => attached !== undefined && this.#attachmentPublished(attached),
1644
- send: async (frame: Record<string, unknown>) => {
1679
+ send: async (
1680
+ frame: Record<string, unknown>,
1681
+ options?: {
1682
+ beforeDispatch?: () => void;
1683
+ dispatchFence?: (dispatch: () => Promise<void>) => Promise<void>;
1684
+ },
1685
+ ) => {
1645
1686
  if (!attached || !this.#attachmentPublished(attached))
1646
1687
  throw new SessionRouterError("pre_send", "SDK session attachment is stale.");
1647
1688
  if (attached.initializingPublication) {
@@ -1659,7 +1700,17 @@ export class SessionRouter {
1659
1700
  } else await this.#serialReconcile(runEpoch, true, true);
1660
1701
  if (!attached || !this.#attachmentPublished(attached))
1661
1702
  throw new SessionRouterError("pre_send", "SDK session attachment is stale.");
1662
- attached.client.send(this.#prepareFrame(attached, frame));
1703
+ const dispatchAttachment = attached;
1704
+ const dispatch = async () => {
1705
+ options?.beforeDispatch?.();
1706
+ try {
1707
+ dispatchAttachment.client.send(this.#prepareFrame(dispatchAttachment, frame));
1708
+ } catch (error) {
1709
+ if (error instanceof SdkClientError) throw new SdkPreparedDispatchError(error);
1710
+ throw error;
1711
+ }
1712
+ };
1713
+ await (options?.dispatchFence ? options.dispatchFence(dispatch) : dispatch());
1663
1714
  },
1664
1715
  /**
1665
1716
  * Provider-lease heartbeats skip the pre-send authority reconcile
@@ -0,0 +1,94 @@
1
+ /**
2
+ * AgentLoop calls `getApiKey(provider)` before `streamFn`.
3
+ * Resolve/stream already use `getApiKey(model)`. If those two lookups diverge,
4
+ * the child dies locally (~8ms, 0 tokens, sanitized "Agent run failed.")
5
+ * and never reaches the working model-scoped path (#5081).
6
+ */
7
+ export type SessionApiKeyModel = {
8
+ provider: string;
9
+ baseUrl?: string;
10
+ };
11
+
12
+ export type SessionApiKeyRegistry = {
13
+ getApiKey(
14
+ model: SessionApiKeyModel,
15
+ sessionId?: string,
16
+ options?: { signal?: AbortSignal },
17
+ ): Promise<string | undefined>;
18
+ getApiKeyForProvider(
19
+ provider: string,
20
+ sessionId?: string,
21
+ baseUrl?: string,
22
+ options?: { signal?: AbortSignal },
23
+ ): Promise<string | undefined>;
24
+ };
25
+
26
+ export type SessionApiKeyResolution = {
27
+ apiKey: string;
28
+ credentialSessionId: string | undefined;
29
+ };
30
+
31
+ export type SessionApiKeyCredentialType = "api_key" | "oauth" | undefined;
32
+
33
+ export function isStableSessionApiKeyCredentialType(
34
+ previous: SessionApiKeyCredentialType,
35
+ replacement: SessionApiKeyCredentialType,
36
+ ): boolean {
37
+ return previous === replacement;
38
+ }
39
+
40
+ export function resolveLiveSessionApiKeyModel(
41
+ live: SessionApiKeyModel | undefined,
42
+ captured: SessionApiKeyModel | undefined,
43
+ provider: string,
44
+ ): SessionApiKeyModel | undefined {
45
+ if (live?.provider === provider) return live;
46
+ if (captured?.provider === provider) return captured;
47
+ return undefined;
48
+ }
49
+
50
+ async function lookupOnce(
51
+ registry: SessionApiKeyRegistry,
52
+ provider: string,
53
+ sessionId: string | undefined,
54
+ model: SessionApiKeyModel | undefined,
55
+ signal: AbortSignal | undefined,
56
+ ): Promise<string | undefined> {
57
+ signal?.throwIfAborted();
58
+ const matchingModel = model?.provider === provider ? model : undefined;
59
+ const options = signal ? { signal } : undefined;
60
+ let key: string | undefined;
61
+ if (matchingModel) {
62
+ key = await registry.getApiKey(matchingModel, sessionId, options);
63
+ signal?.throwIfAborted();
64
+ }
65
+ if (!key) {
66
+ key = await registry.getApiKeyForProvider(provider, sessionId, matchingModel?.baseUrl, options);
67
+ signal?.throwIfAborted();
68
+ }
69
+ return key;
70
+ }
71
+
72
+ export async function lookupSessionApiKey(
73
+ registry: SessionApiKeyRegistry,
74
+ provider: string,
75
+ sessionId: string | undefined,
76
+ model: SessionApiKeyModel | undefined,
77
+ allowUnscopedRetry = true,
78
+ signal?: AbortSignal,
79
+ ): Promise<SessionApiKeyResolution> {
80
+ let key = await lookupOnce(registry, provider, sessionId, model, signal);
81
+ // Architect children inherit the parent SID. Direct `-p --no-session` does
82
+ // not. Scoped miss then global/broker hit is the remaining 0-token death
83
+ // after #5105 (jsonl: Agent run failed / output 0).
84
+ if (!key && sessionId && allowUnscopedRetry) {
85
+ key = await lookupOnce(registry, provider, undefined, model, signal);
86
+ if (key) return { apiKey: key, credentialSessionId: undefined };
87
+ }
88
+ if (!key) {
89
+ throw Object.assign(new Error(`No API key found for provider "${provider}"`), {
90
+ code: "provider_unavailable",
91
+ });
92
+ }
93
+ return { apiKey: key, credentialSessionId: sessionId };
94
+ }