@gajae-code/coding-agent 0.10.1 → 0.11.0

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 (474) hide show
  1. package/CHANGELOG.md +70 -2
  2. package/dist/types/append-only-mode.d.ts +1 -1
  3. package/dist/types/cli/args.d.ts +4 -5
  4. package/dist/types/cli/daemon-cli.d.ts +12 -1
  5. package/dist/types/cli/notify-cli.d.ts +21 -1
  6. package/dist/types/cli.d.ts +3 -0
  7. package/dist/types/commands/daemon.d.ts +39 -0
  8. package/dist/types/commands/launch.d.ts +3 -0
  9. package/dist/types/commands/notify.d.ts +28 -1
  10. package/dist/types/commands/sdk.d.ts +33 -0
  11. package/dist/types/config/atomic-yaml-patch.d.ts +85 -0
  12. package/dist/types/config/model-profile-activation.d.ts +35 -15
  13. package/dist/types/config/model-profiles.d.ts +11 -5
  14. package/dist/types/config/model-registry.d.ts +3 -3
  15. package/dist/types/config/model-resolver.d.ts +39 -6
  16. package/dist/types/config/model-selector-value.d.ts +17 -0
  17. package/dist/types/config/models-config-schema.d.ts +10 -6
  18. package/dist/types/config/settings-schema.d.ts +133 -6
  19. package/dist/types/config/settings.d.ts +63 -35
  20. package/dist/types/coordinator-mcp/model-preset.d.ts +7 -6
  21. package/dist/types/coordinator-mcp/server.d.ts +6 -49
  22. package/dist/types/daemon/builtin.d.ts +6 -5
  23. package/dist/types/daemon/control-types.d.ts +1 -1
  24. package/dist/types/discovery/mcp-json.d.ts +12 -1
  25. package/dist/types/extensibility/custom-tools/types.d.ts +1 -1
  26. package/dist/types/extensibility/extensions/compact-handler.d.ts +4 -2
  27. package/dist/types/extensibility/extensions/get-commands-handler.d.ts +2 -2
  28. package/dist/types/extensibility/extensions/loader.d.ts +10 -1
  29. package/dist/types/extensibility/extensions/runner.d.ts +3 -3
  30. package/dist/types/extensibility/extensions/types.d.ts +141 -16
  31. package/dist/types/extensibility/hooks/types.d.ts +1 -1
  32. package/dist/types/gjc-runtime/launch-tmux.d.ts +3 -2
  33. package/dist/types/gjc-runtime/psmux-detect.d.ts +3 -0
  34. package/dist/types/gjc-runtime/session-state-sidecar.d.ts +3 -0
  35. package/dist/types/gjc-runtime/team-runtime.d.ts +3 -4
  36. package/dist/types/gjc-runtime/tmux-owner-isolation-cli.d.ts +13 -0
  37. package/dist/types/gjc-runtime/tmux-sessions.d.ts +4 -1
  38. package/dist/types/gjc-runtime/windows-powershell-command.d.ts +9 -0
  39. package/dist/types/harness-control-plane/operate.d.ts +5 -5
  40. package/dist/types/harness-control-plane/owner.d.ts +18 -4
  41. package/dist/types/harness-control-plane/receipts.d.ts +0 -1
  42. package/dist/types/harness-control-plane/sdk-transport.d.ts +43 -0
  43. package/dist/types/harness-control-plane/session-transport.d.ts +48 -0
  44. package/dist/types/harness-control-plane/types.d.ts +3 -4
  45. package/dist/types/lsp/index.d.ts +1 -1
  46. package/dist/types/main.d.ts +32 -38
  47. package/dist/types/modes/acp/acp-agent.d.ts +224 -53
  48. package/dist/types/modes/acp/acp-client-bridge.d.ts +1 -0
  49. package/dist/types/modes/acp/acp-mode.d.ts +6 -15
  50. package/dist/types/modes/acp/permission-mode.d.ts +4 -0
  51. package/dist/types/modes/acp/startup-options.d.ts +6 -0
  52. package/dist/types/modes/components/agent-dashboard.d.ts +3 -0
  53. package/dist/types/modes/components/gajae-pet-widget.d.ts +1 -0
  54. package/dist/types/modes/components/model-selector.d.ts +1 -0
  55. package/dist/types/modes/components/notifications-settings-editor.d.ts +135 -0
  56. package/dist/types/modes/components/pet-capability.d.ts +37 -0
  57. package/dist/types/modes/components/pet-selector.d.ts +1 -1
  58. package/dist/types/modes/components/settings-selector.d.ts +15 -4
  59. package/dist/types/modes/components/welcome.d.ts +1 -1
  60. package/dist/types/modes/controllers/selector-controller.d.ts +35 -1
  61. package/dist/types/modes/index.d.ts +0 -4
  62. package/dist/types/modes/interactive-mode.d.ts +5 -1
  63. package/dist/types/modes/runtime-init.d.ts +2 -2
  64. package/dist/types/modes/shared/agent-wire/approval-gate.d.ts +6 -9
  65. package/dist/types/modes/shared/agent-wire/deep-interview-gate.d.ts +4 -6
  66. package/dist/types/modes/shared/agent-wire/event-contract.d.ts +6 -6
  67. package/dist/types/modes/shared/agent-wire/event-envelope.d.ts +4 -9
  68. package/dist/types/modes/shared/agent-wire/event-observation.d.ts +1 -1
  69. package/dist/types/modes/shared/agent-wire/workflow-gate-broker.d.ts +169 -30
  70. package/dist/types/modes/shared/agent-wire/workflow-gate-schema.d.ts +7 -7
  71. package/dist/types/modes/shared/agent-wire/workflow-gate-types.d.ts +105 -0
  72. package/dist/types/modes/types.d.ts +16 -1
  73. package/dist/types/runtime-mcp/client.d.ts +2 -0
  74. package/dist/types/runtime-mcp/config.d.ts +5 -1
  75. package/dist/types/runtime-mcp/manager.d.ts +8 -1
  76. package/dist/types/runtime-mcp/types.d.ts +4 -0
  77. package/dist/types/sdk/acp/adapter.d.ts +67 -0
  78. package/dist/types/sdk/acp/index.d.ts +2 -0
  79. package/dist/types/sdk/broker/broker.d.ts +53 -0
  80. package/dist/types/sdk/broker/discovery.d.ts +30 -0
  81. package/dist/types/sdk/broker/ensure.d.ts +33 -0
  82. package/dist/types/sdk/broker/identity.d.ts +3 -0
  83. package/dist/types/sdk/broker/index.d.ts +8 -0
  84. package/dist/types/sdk/broker/lifecycle-ledger.d.ts +95 -0
  85. package/dist/types/sdk/broker/lifecycle.d.ts +85 -0
  86. package/dist/types/sdk/broker/process-incarnation.d.ts +14 -0
  87. package/dist/types/sdk/broker/runtime.d.ts +33 -0
  88. package/dist/types/sdk/broker/session-index.d.ts +57 -0
  89. package/dist/types/sdk/broker/state-version.d.ts +8 -0
  90. package/dist/types/sdk/broker/transport.d.ts +9 -0
  91. package/dist/types/sdk/bus/chat-command-policy.d.ts +49 -0
  92. package/dist/types/sdk/bus/chat-daemon-cli.d.ts +21 -0
  93. package/dist/types/sdk/bus/chat-daemon-control.d.ts +111 -0
  94. package/dist/types/sdk/bus/chat-daemon-runtime.d.ts +77 -0
  95. package/dist/types/sdk/bus/chat-effect-journal.d.ts +84 -0
  96. package/dist/types/{notifications → sdk/bus}/config-commands.d.ts +7 -2
  97. package/dist/types/sdk/bus/config.d.ts +274 -0
  98. package/dist/types/sdk/bus/conversation-store.d.ts +72 -0
  99. package/dist/types/sdk/bus/discord-conversation.d.ts +58 -0
  100. package/dist/types/sdk/bus/discord-daemon.d.ts +45 -0
  101. package/dist/types/sdk/bus/discord-live-provider.d.ts +70 -0
  102. package/dist/types/sdk/bus/discord-provider.d.ts +85 -0
  103. package/dist/types/{notifications → sdk/bus}/html-format.d.ts +2 -2
  104. package/dist/types/{notifications → sdk/bus}/index.d.ts +113 -13
  105. package/dist/types/{notifications → sdk/bus}/lifecycle-control-runtime.d.ts +4 -2
  106. package/dist/types/{notifications → sdk/bus}/lifecycle-orchestrator.d.ts +18 -21
  107. package/dist/types/{notifications → sdk/bus}/managed-daemon.d.ts +1 -1
  108. package/dist/types/sdk/bus/native-runtime-compatibility.d.ts +13 -0
  109. package/dist/types/sdk/bus/notification-orchestration.d.ts +170 -0
  110. package/dist/types/{notifications → sdk/bus}/notification-service.d.ts +7 -1
  111. package/dist/types/sdk/bus/session-control.d.ts +83 -0
  112. package/dist/types/sdk/bus/slack-conversation.d.ts +56 -0
  113. package/dist/types/sdk/bus/slack-daemon.d.ts +60 -0
  114. package/dist/types/sdk/bus/slack-live-provider.d.ts +59 -0
  115. package/dist/types/sdk/bus/slack-provider.d.ts +55 -0
  116. package/dist/types/{notifications → sdk/bus}/telegram-cli.d.ts +4 -4
  117. package/dist/types/{notifications → sdk/bus}/telegram-daemon-cli.d.ts +14 -5
  118. package/dist/types/sdk/bus/telegram-daemon-contract.d.ts +12 -0
  119. package/dist/types/{notifications → sdk/bus}/telegram-daemon-control.d.ts +2 -2
  120. package/dist/types/{notifications → sdk/bus}/telegram-daemon.d.ts +55 -17
  121. package/dist/types/{notifications → sdk/bus}/telegram-reference.d.ts +2 -2
  122. package/dist/types/sdk/bus/telegram-setup.d.ts +128 -0
  123. package/dist/types/{notifications → sdk/bus}/topic-registry.d.ts +11 -0
  124. package/dist/types/sdk/cli/index.d.ts +1 -0
  125. package/dist/types/sdk/cli/session-cli.d.ts +19 -0
  126. package/dist/types/sdk/client/client.d.ts +1 -0
  127. package/dist/types/sdk/client/discovery.d.ts +27 -0
  128. package/dist/types/sdk/client/index.d.ts +2 -0
  129. package/dist/types/sdk/host/control/dispatch.d.ts +34 -0
  130. package/dist/types/sdk/host/control/index.d.ts +2 -0
  131. package/dist/types/sdk/host/control/operations.d.ts +65 -0
  132. package/dist/types/sdk/host/events.d.ts +35 -0
  133. package/dist/types/sdk/host/host.d.ts +35 -0
  134. package/dist/types/sdk/host/index.d.ts +4 -0
  135. package/dist/types/sdk/host/query/cursor.d.ts +62 -0
  136. package/dist/types/sdk/host/query/handlers.d.ts +70 -0
  137. package/dist/types/sdk/host/query/index.d.ts +3 -0
  138. package/dist/types/sdk/host/query/revision-store.d.ts +60 -0
  139. package/dist/types/sdk/host/reverse-leases.d.ts +45 -0
  140. package/dist/types/sdk/host/types.d.ts +32 -0
  141. package/dist/types/sdk/index.d.ts +6 -0
  142. package/dist/types/sdk/lifecycle-session.d.ts +14 -0
  143. package/dist/types/sdk/mcp/index.d.ts +1 -0
  144. package/dist/types/sdk/mcp/server.d.ts +37 -0
  145. package/dist/types/sdk/models.d.ts +44 -0
  146. package/dist/types/sdk/protocol/adapter-validation.d.ts +14 -0
  147. package/dist/types/sdk/protocol/operation-registry.d.ts +24 -0
  148. package/dist/types/{sdk.d.ts → sdk/session.d.ts} +44 -38
  149. package/dist/types/sdk/startup-capability.d.ts +50 -0
  150. package/dist/types/session/agent-session.d.ts +114 -21
  151. package/dist/types/session/default-model-selection.d.ts +25 -0
  152. package/dist/types/session/fallback-chain-controller.d.ts +52 -0
  153. package/dist/types/session/session-manager.d.ts +58 -1
  154. package/dist/types/setup/credential-auto-import.d.ts +36 -6
  155. package/dist/types/tools/ask-answer-registry.d.ts +1 -1
  156. package/dist/types/tools/browser/benchmark/detector-report.d.ts +63 -0
  157. package/dist/types/tools/browser/benchmark/run-suite.d.ts +18 -0
  158. package/dist/types/tools/browser/launch.d.ts +22 -0
  159. package/dist/types/tools/browser/profile-discovery.d.ts +31 -0
  160. package/dist/types/tools/browser/profile-posture.d.ts +37 -0
  161. package/dist/types/tools/browser/profile-reuse.d.ts +39 -0
  162. package/dist/types/tools/browser/profile-warmup.d.ts +39 -0
  163. package/dist/types/tools/browser/registry.d.ts +19 -0
  164. package/dist/types/tools/browser/tab-protocol.d.ts +4 -0
  165. package/dist/types/tools/index.d.ts +8 -8
  166. package/package.json +34 -13
  167. package/scripts/build-sdk-package-smoke.ts +94 -0
  168. package/scripts/capture-notifications-settings-showcase.ts +435 -0
  169. package/scripts/compile-args.ts +4 -2
  170. package/scripts/g005-daemon-smoke.ts +4 -5
  171. package/scripts/g011-daemon-path-smoke.ts +5 -5
  172. package/scripts/generate-sdk-adapter-parity-manifest.ts +163 -0
  173. package/scripts/generate-sdk-operation-inventory.ts +867 -0
  174. package/scripts/generate-telegram-baseline-manifest.ts +128 -0
  175. package/scripts/manifest-schema.ts +124 -0
  176. package/scripts/run-test-manifest.ts +153 -0
  177. package/scripts/run-threat-model-check.ts +157 -0
  178. package/scripts/verify-gjc-sdk-canonicalization.ts +3467 -0
  179. package/scripts/verify-gjc-sdk-rename.ts +90 -0
  180. package/src/append-only-mode.ts +1 -1
  181. package/src/cli/args.ts +43 -12
  182. package/src/cli/auth-gateway-cli.ts +2 -2
  183. package/src/cli/config-cli.ts +2 -1
  184. package/src/cli/daemon-cli.ts +63 -14
  185. package/src/cli/list-models.ts +1 -1
  186. package/src/cli/notify-cli.ts +288 -286
  187. package/src/cli.ts +21 -2
  188. package/src/commands/daemon.ts +50 -6
  189. package/src/commands/harness.ts +93 -36
  190. package/src/commands/launch.ts +5 -2
  191. package/src/commands/mcp-serve.ts +19 -12
  192. package/src/commands/notify.ts +36 -3
  193. package/src/commands/sdk.ts +426 -0
  194. package/src/commit/agentic/agent.ts +1 -0
  195. package/src/config/atomic-yaml-patch.ts +423 -0
  196. package/src/config/model-profile-activation.ts +240 -89
  197. package/src/config/model-profiles.ts +37 -34
  198. package/src/config/model-registry.ts +51 -26
  199. package/src/config/model-resolver.ts +138 -51
  200. package/src/config/model-selector-value.ts +50 -0
  201. package/src/config/models-config-schema.ts +14 -21
  202. package/src/config/settings-schema.ts +128 -10
  203. package/src/config/settings.ts +1120 -158
  204. package/src/coordinator-mcp/model-preset.ts +74 -20
  205. package/src/coordinator-mcp/server.ts +1597 -3052
  206. package/src/daemon/builtin.ts +8 -5
  207. package/src/daemon/control-types.ts +1 -1
  208. package/src/defaults/gjc/skills/team/SKILL.md +4 -4
  209. package/src/discovery/mcp-json.ts +327 -20
  210. package/src/extensibility/custom-tools/types.ts +1 -1
  211. package/src/extensibility/extensions/compact-handler.ts +3 -3
  212. package/src/extensibility/extensions/get-commands-handler.ts +2 -2
  213. package/src/extensibility/extensions/loader.ts +69 -5
  214. package/src/extensibility/extensions/runner.ts +112 -4
  215. package/src/extensibility/extensions/types.ts +176 -17
  216. package/src/extensibility/hooks/types.ts +1 -1
  217. package/src/extensibility/slash-commands.ts +3 -3
  218. package/src/gjc-runtime/launch-tmux.ts +170 -86
  219. package/src/gjc-runtime/psmux-detect.ts +82 -4
  220. package/src/gjc-runtime/session-state-sidecar.ts +41 -31
  221. package/src/gjc-runtime/team-runtime.ts +54 -35
  222. package/src/gjc-runtime/tmux-owner-isolation-cli.ts +53 -17
  223. package/src/gjc-runtime/tmux-owner-isolation.ts +5 -2
  224. package/src/gjc-runtime/tmux-sessions.ts +19 -13
  225. package/src/gjc-runtime/ultragoal-guard.ts +53 -6
  226. package/src/gjc-runtime/ultragoal-runtime.ts +155 -3
  227. package/src/gjc-runtime/windows-powershell-command.ts +58 -0
  228. package/src/harness-control-plane/operate.ts +9 -9
  229. package/src/harness-control-plane/owner.ts +283 -39
  230. package/src/harness-control-plane/receipts.ts +0 -1
  231. package/src/harness-control-plane/sdk-transport.ts +395 -0
  232. package/src/harness-control-plane/session-transport.ts +94 -0
  233. package/src/harness-control-plane/types.ts +1 -1
  234. package/src/internal-urls/docs-index.generated.ts +36 -37
  235. package/src/lsp/client.ts +42 -6
  236. package/src/lsp/index.ts +2 -2
  237. package/src/main.ts +299 -204
  238. package/src/modes/DESIGN.md +258 -0
  239. package/src/modes/acp/acp-agent.ts +1228 -2612
  240. package/src/modes/acp/acp-client-bridge.ts +2 -20
  241. package/src/modes/acp/acp-event-mapper.ts +18 -2
  242. package/src/modes/acp/acp-mode.ts +8 -36
  243. package/src/modes/acp/permission-mode.ts +25 -0
  244. package/src/modes/acp/startup-options.ts +6 -0
  245. package/src/modes/components/agent-dashboard.ts +37 -17
  246. package/src/modes/components/custom-editor.ts +1 -0
  247. package/src/modes/components/gajae-pet-widget.ts +169 -20
  248. package/src/modes/components/model-selector.ts +93 -35
  249. package/src/modes/components/notifications-settings-editor.ts +1379 -0
  250. package/src/modes/components/pet-capability.ts +115 -0
  251. package/src/modes/components/pet-selector.ts +4 -6
  252. package/src/modes/components/settings-selector.ts +86 -11
  253. package/src/modes/components/welcome.ts +4 -4
  254. package/src/modes/controllers/command-controller.ts +1 -2
  255. package/src/modes/controllers/event-controller.ts +11 -16
  256. package/src/modes/controllers/extension-ui-controller.ts +357 -12
  257. package/src/modes/controllers/input-controller.ts +43 -13
  258. package/src/modes/controllers/selector-controller.ts +671 -44
  259. package/src/modes/index.ts +0 -22
  260. package/src/modes/interactive-mode.ts +108 -24
  261. package/src/modes/runtime-init.ts +298 -6
  262. package/src/modes/shared/agent-wire/approval-gate.ts +9 -11
  263. package/src/modes/shared/agent-wire/deep-interview-gate.ts +10 -11
  264. package/src/modes/shared/agent-wire/event-contract.ts +7 -8
  265. package/src/modes/shared/agent-wire/event-envelope.ts +9 -17
  266. package/src/modes/shared/agent-wire/event-observation.ts +16 -7
  267. package/src/modes/shared/agent-wire/workflow-gate-broker.ts +1254 -87
  268. package/src/modes/shared/agent-wire/workflow-gate-schema.ts +15 -15
  269. package/src/modes/shared/agent-wire/workflow-gate-types.ts +113 -0
  270. package/src/modes/types.ts +16 -1
  271. package/src/runtime-mcp/client.ts +94 -19
  272. package/src/runtime-mcp/config.ts +27 -15
  273. package/src/runtime-mcp/manager.ts +343 -105
  274. package/src/runtime-mcp/oauth-flow.ts +42 -12
  275. package/src/runtime-mcp/transports/http.ts +49 -19
  276. package/src/runtime-mcp/transports/stdio.ts +47 -28
  277. package/src/runtime-mcp/types.ts +10 -0
  278. package/src/sdk/acp/adapter.ts +418 -0
  279. package/src/sdk/acp/index.ts +2 -0
  280. package/src/sdk/broker/broker.ts +642 -0
  281. package/src/sdk/broker/discovery.ts +94 -0
  282. package/src/sdk/broker/ensure.ts +249 -0
  283. package/src/sdk/broker/identity.ts +42 -0
  284. package/src/sdk/broker/index.ts +8 -0
  285. package/src/sdk/broker/internal-source-marker-2178.txt +1 -0
  286. package/src/sdk/broker/internal-source.bunfig.toml +1 -0
  287. package/src/sdk/broker/lifecycle-ledger.ts +286 -0
  288. package/src/sdk/broker/lifecycle.ts +2157 -0
  289. package/src/sdk/broker/process-incarnation.ts +159 -0
  290. package/src/sdk/broker/runtime.ts +164 -0
  291. package/src/sdk/broker/session-index.ts +318 -0
  292. package/src/sdk/broker/state-version.ts +23 -0
  293. package/src/sdk/broker/transport.ts +125 -0
  294. package/src/sdk/bus/chat-command-policy.ts +107 -0
  295. package/src/sdk/bus/chat-daemon-cli.ts +214 -0
  296. package/src/sdk/bus/chat-daemon-control.ts +713 -0
  297. package/src/sdk/bus/chat-daemon-runtime.ts +526 -0
  298. package/src/sdk/bus/chat-effect-journal.ts +353 -0
  299. package/src/{notifications → sdk/bus}/config-commands.ts +39 -10
  300. package/src/sdk/bus/config.ts +433 -0
  301. package/src/sdk/bus/conversation-store.ts +391 -0
  302. package/src/sdk/bus/discord-conversation.ts +85 -0
  303. package/src/sdk/bus/discord-daemon.ts +2275 -0
  304. package/src/sdk/bus/discord-live-provider.ts +521 -0
  305. package/src/sdk/bus/discord-provider.ts +56 -0
  306. package/src/{notifications → sdk/bus}/html-format.ts +2 -2
  307. package/src/sdk/bus/index.ts +3943 -0
  308. package/src/{notifications → sdk/bus}/lifecycle-commands.ts +2 -0
  309. package/src/{notifications → sdk/bus}/lifecycle-control-runtime.ts +225 -196
  310. package/src/{notifications → sdk/bus}/lifecycle-orchestrator.ts +161 -74
  311. package/src/{notifications → sdk/bus}/managed-daemon.ts +2 -2
  312. package/src/sdk/bus/native-runtime-compatibility.ts +42 -0
  313. package/src/sdk/bus/natives-augment.d.ts +38 -0
  314. package/src/sdk/bus/notification-orchestration.ts +535 -0
  315. package/src/{notifications → sdk/bus}/notification-service.ts +106 -12
  316. package/src/sdk/bus/session-control.ts +333 -0
  317. package/src/sdk/bus/slack-conversation.ts +74 -0
  318. package/src/sdk/bus/slack-daemon.ts +2144 -0
  319. package/src/sdk/bus/slack-live-provider.ts +380 -0
  320. package/src/sdk/bus/slack-provider.ts +76 -0
  321. package/src/{notifications → sdk/bus}/telegram-cli.ts +8 -8
  322. package/src/sdk/bus/telegram-daemon-cli.ts +330 -0
  323. package/src/sdk/bus/telegram-daemon-contract.ts +14 -0
  324. package/src/{notifications → sdk/bus}/telegram-daemon-control.ts +4 -4
  325. package/src/{notifications → sdk/bus}/telegram-daemon.ts +502 -54
  326. package/src/{notifications → sdk/bus}/telegram-reference.ts +2 -2
  327. package/src/sdk/bus/telegram-setup.ts +663 -0
  328. package/src/{notifications → sdk/bus}/topic-registry.ts +32 -0
  329. package/src/sdk/cli/index.ts +1 -0
  330. package/src/sdk/cli/session-cli.ts +275 -0
  331. package/src/sdk/client/client.ts +1 -0
  332. package/src/sdk/client/discovery.ts +108 -0
  333. package/src/sdk/client/index.ts +2 -0
  334. package/src/sdk/host/control/dispatch.ts +367 -0
  335. package/src/sdk/host/control/index.ts +10 -0
  336. package/src/sdk/host/control/operations.ts +63 -0
  337. package/src/sdk/host/events.ts +91 -0
  338. package/src/sdk/host/host.ts +296 -0
  339. package/src/sdk/host/index.ts +4 -0
  340. package/src/sdk/host/query/cursor.ts +189 -0
  341. package/src/sdk/host/query/handlers.ts +533 -0
  342. package/src/sdk/host/query/index.ts +3 -0
  343. package/src/sdk/host/query/revision-store.ts +1126 -0
  344. package/src/sdk/host/reverse-leases.ts +325 -0
  345. package/src/sdk/host/types.ts +29 -0
  346. package/src/sdk/index.ts +13 -0
  347. package/src/sdk/lifecycle-session.ts +39 -0
  348. package/src/sdk/mcp/index.ts +1 -0
  349. package/src/sdk/mcp/server.ts +332 -0
  350. package/src/sdk/models.ts +168 -0
  351. package/src/sdk/protocol/adapter-validation.ts +48 -0
  352. package/src/sdk/protocol/operation-inventory.generated.json +5654 -0
  353. package/src/sdk/protocol/operation-registry.ts +312 -0
  354. package/src/{sdk.ts → sdk/session.ts} +339 -212
  355. package/src/sdk/startup-capability.ts +189 -0
  356. package/src/session/agent-session.ts +2470 -866
  357. package/src/session/default-model-selection.ts +93 -0
  358. package/src/session/fallback-chain-controller.ts +136 -0
  359. package/src/session/session-manager.ts +447 -13
  360. package/src/session/session-storage.ts +8 -1
  361. package/src/setup/credential-auto-import.ts +290 -35
  362. package/src/setup/hermes/templates/operator-instructions.v1.md +3 -5
  363. package/src/slash-commands/builtin-registry.ts +33 -30
  364. package/src/task/executor.ts +62 -13
  365. package/src/tools/ask-answer-registry.ts +1 -1
  366. package/src/tools/ask.ts +13 -8
  367. package/src/tools/browser/STEALTH-THIRD-PARTY-NOTICES.md +26 -0
  368. package/src/tools/browser/benchmark/detector-report.ts +157 -0
  369. package/src/tools/browser/benchmark/run-suite.ts +39 -0
  370. package/src/tools/browser/launch.ts +124 -19
  371. package/src/tools/browser/profile-discovery.ts +63 -0
  372. package/src/tools/browser/profile-posture.ts +59 -0
  373. package/src/tools/browser/profile-reuse.ts +102 -0
  374. package/src/tools/browser/profile-warmup.ts +109 -0
  375. package/src/tools/browser/registry.ts +87 -11
  376. package/src/tools/browser/tab-protocol.ts +1 -0
  377. package/src/tools/browser/tab-supervisor.ts +1 -0
  378. package/src/tools/browser/tab-worker.ts +1 -1
  379. package/src/tools/browser.ts +6 -0
  380. package/src/tools/index.ts +8 -8
  381. package/src/tools/puppeteer/14_stealth_permissions.txt +57 -0
  382. package/src/tools/puppeteer/15_stealth_webrtc.txt +60 -0
  383. package/src/tools/telegram-send.ts +2 -2
  384. package/vendor/insane-search/MANIFEST.json +2 -1
  385. package/vendor/insane-search/engine/bias_check.py +4 -1
  386. package/vendor/insane-search/engine/phase0.py +26 -1
  387. package/dist/types/harness-control-plane/rpc-adapter.d.ts +0 -69
  388. package/dist/types/modes/bridge/auth.d.ts +0 -12
  389. package/dist/types/modes/bridge/bridge-client-bridge.d.ts +0 -9
  390. package/dist/types/modes/bridge/bridge-mode.d.ts +0 -47
  391. package/dist/types/modes/bridge/bridge-ui-context.d.ts +0 -88
  392. package/dist/types/modes/bridge/event-stream.d.ts +0 -8
  393. package/dist/types/modes/rpc/host-tools.d.ts +0 -1
  394. package/dist/types/modes/rpc/host-uris.d.ts +0 -1
  395. package/dist/types/modes/rpc/rpc-client.d.ts +0 -299
  396. package/dist/types/modes/rpc/rpc-mode.d.ts +0 -86
  397. package/dist/types/modes/rpc/rpc-socket-security.d.ts +0 -10
  398. package/dist/types/modes/rpc/rpc-types.d.ts +0 -810
  399. package/dist/types/modes/shared/agent-wire/command-contract.d.ts +0 -18
  400. package/dist/types/modes/shared/agent-wire/command-dispatch.d.ts +0 -37
  401. package/dist/types/modes/shared/agent-wire/command-validation.d.ts +0 -2
  402. package/dist/types/modes/shared/agent-wire/handshake.d.ts +0 -56
  403. package/dist/types/modes/shared/agent-wire/host-tool-bridge.d.ts +0 -16
  404. package/dist/types/modes/shared/agent-wire/host-uri-bridge.d.ts +0 -17
  405. package/dist/types/modes/shared/agent-wire/protocol.d.ts +0 -25
  406. package/dist/types/modes/shared/agent-wire/responses.d.ts +0 -4
  407. package/dist/types/modes/shared/agent-wire/scopes.d.ts +0 -18
  408. package/dist/types/modes/shared/agent-wire/session-registry.d.ts +0 -25
  409. package/dist/types/modes/shared/agent-wire/ui-request-broker.d.ts +0 -42
  410. package/dist/types/modes/shared/agent-wire/ui-result.d.ts +0 -27
  411. package/dist/types/modes/shared/agent-wire/unattended-action-policy.d.ts +0 -29
  412. package/dist/types/modes/shared/agent-wire/unattended-audit.d.ts +0 -68
  413. package/dist/types/modes/shared/agent-wire/unattended-run-controller.d.ts +0 -166
  414. package/dist/types/modes/shared/agent-wire/unattended-session.d.ts +0 -127
  415. package/dist/types/notifications/config.d.ts +0 -132
  416. package/src/harness-control-plane/rpc-adapter.ts +0 -290
  417. package/src/modes/bridge/auth.ts +0 -41
  418. package/src/modes/bridge/bridge-client-bridge.ts +0 -47
  419. package/src/modes/bridge/bridge-mode.ts +0 -675
  420. package/src/modes/bridge/bridge-ui-context.ts +0 -200
  421. package/src/modes/bridge/event-stream.ts +0 -70
  422. package/src/modes/rpc/host-tools.ts +0 -1
  423. package/src/modes/rpc/host-uris.ts +0 -1
  424. package/src/modes/rpc/rpc-client.ts +0 -1166
  425. package/src/modes/rpc/rpc-mode.ts +0 -1029
  426. package/src/modes/rpc/rpc-socket-security.ts +0 -115
  427. package/src/modes/rpc/rpc-types.ts +0 -637
  428. package/src/modes/shared/agent-wire/command-contract.ts +0 -18
  429. package/src/modes/shared/agent-wire/command-dispatch.ts +0 -498
  430. package/src/modes/shared/agent-wire/command-validation.ts +0 -181
  431. package/src/modes/shared/agent-wire/handshake.ts +0 -157
  432. package/src/modes/shared/agent-wire/host-tool-bridge.ts +0 -197
  433. package/src/modes/shared/agent-wire/host-uri-bridge.ts +0 -236
  434. package/src/modes/shared/agent-wire/protocol.ts +0 -46
  435. package/src/modes/shared/agent-wire/responses.ts +0 -17
  436. package/src/modes/shared/agent-wire/scopes.ts +0 -94
  437. package/src/modes/shared/agent-wire/session-registry.ts +0 -109
  438. package/src/modes/shared/agent-wire/ui-request-broker.ts +0 -150
  439. package/src/modes/shared/agent-wire/ui-result.ts +0 -48
  440. package/src/modes/shared/agent-wire/unattended-action-policy.ts +0 -365
  441. package/src/modes/shared/agent-wire/unattended-audit.ts +0 -176
  442. package/src/modes/shared/agent-wire/unattended-run-controller.ts +0 -426
  443. package/src/modes/shared/agent-wire/unattended-session.ts +0 -528
  444. package/src/notifications/config.ts +0 -219
  445. package/src/notifications/index.ts +0 -1835
  446. package/src/notifications/telegram-daemon-cli.ts +0 -243
  447. package/src/tools/puppeteer/09_stealth_locale.txt +0 -46
  448. /package/dist/types/{notifications → sdk/bus}/attachment-registry.d.ts +0 -0
  449. /package/dist/types/{notifications → sdk/bus}/chat-adapters.d.ts +0 -0
  450. /package/dist/types/{notifications → sdk/bus}/daemon-paths.d.ts +0 -0
  451. /package/dist/types/{notifications → sdk/bus}/engine.d.ts +0 -0
  452. /package/dist/types/{notifications → sdk/bus}/helpers.d.ts +0 -0
  453. /package/dist/types/{notifications → sdk/bus}/lifecycle-commands.d.ts +0 -0
  454. /package/dist/types/{notifications → sdk/bus}/operator-runtime.d.ts +0 -0
  455. /package/dist/types/{notifications → sdk/bus}/rate-limit-pool.d.ts +0 -0
  456. /package/dist/types/{notifications → sdk/bus}/recent-activity.d.ts +0 -0
  457. /package/dist/types/{notifications → sdk/bus}/reply-sent-store.d.ts +0 -0
  458. /package/dist/types/{notifications → sdk/bus}/rich-draft.d.ts +0 -0
  459. /package/dist/types/{notifications → sdk/bus}/rich-render.d.ts +0 -0
  460. /package/dist/types/{notifications → sdk/bus}/threaded-inbound.d.ts +0 -0
  461. /package/dist/types/{notifications → sdk/bus}/threaded-render.d.ts +0 -0
  462. /package/src/{notifications → sdk/bus}/attachment-registry.ts +0 -0
  463. /package/src/{notifications → sdk/bus}/chat-adapters.ts +0 -0
  464. /package/src/{notifications → sdk/bus}/daemon-paths.ts +0 -0
  465. /package/src/{notifications → sdk/bus}/engine.ts +0 -0
  466. /package/src/{notifications → sdk/bus}/helpers.ts +0 -0
  467. /package/src/{notifications → sdk/bus}/operator-runtime.ts +0 -0
  468. /package/src/{notifications → sdk/bus}/rate-limit-pool.ts +0 -0
  469. /package/src/{notifications → sdk/bus}/recent-activity.ts +0 -0
  470. /package/src/{notifications → sdk/bus}/reply-sent-store.ts +0 -0
  471. /package/src/{notifications → sdk/bus}/rich-draft.ts +0 -0
  472. /package/src/{notifications → sdk/bus}/rich-render.ts +0 -0
  473. /package/src/{notifications → sdk/bus}/threaded-inbound.ts +0 -0
  474. /package/src/{notifications → sdk/bus}/threaded-render.ts +0 -0
@@ -1,1029 +0,0 @@
1
- /**
2
- * RPC mode: Headless operation with JSON stdin/stdout protocol.
3
- *
4
- * Used for embedding the agent in other applications.
5
- * Receives commands as JSON on stdin, outputs events and responses as JSON on stdout.
6
- *
7
- * Protocol:
8
- * - Commands: JSON objects with `type` field, optional `id` for correlation
9
- * - Responses: JSON objects with `type: "response"`, `command`, `success`, and optional `data`/`error`
10
- * - Events: AgentSessionEvent objects streamed as they occur
11
- * - Extension UI: Extension UI requests are emitted, client responds with extension_ui_response
12
- */
13
-
14
- import { $pickenv, isKnownSinkPeerClosedError, logger, readLines, Snowflake } from "@gajae-code/utils";
15
- import type {
16
- ExtensionUIContext,
17
- ExtensionUIDialogOptions,
18
- ExtensionWidgetOptions,
19
- } from "../../extensibility/extensions";
20
- import { workflowGatePath } from "../../gjc-runtime/session-layout";
21
- import { type Theme, theme } from "../../modes/theme/theme";
22
- import type { AgentSession } from "../../session/agent-session";
23
- import { initializeExtensions } from "../runtime-init";
24
- import { dispatchRpcCommand } from "../shared/agent-wire/command-dispatch";
25
- import {
26
- AgentWireCompactEventEncoder,
27
- AgentWireFrameSequencer,
28
- toAgentWireCompactEventFrame,
29
- toAgentWireEventFrame,
30
- } from "../shared/agent-wire/event-envelope";
31
- import { rpcError as error, rpcSuccess } from "../shared/agent-wire/responses";
32
- import { registerRpcSession, unregisterRpcSession } from "../shared/agent-wire/session-registry";
33
- import { defaultAuditPath, UnattendedAuditLog } from "../shared/agent-wire/unattended-audit";
34
- import { modelSupportsTokenCostMetrics, UnattendedSessionControlPlane } from "../shared/agent-wire/unattended-session";
35
- import { FileGateStore } from "../shared/agent-wire/workflow-gate-broker";
36
- import { isRpcHostToolResult, isRpcHostToolUpdate, RpcHostToolBridge } from "./host-tools";
37
- import { isRpcHostUriResult, RpcHostUriBridge } from "./host-uris";
38
- import { prepareRpcSocketPath, verifyRpcSocketAfterListen } from "./rpc-socket-security";
39
- import type {
40
- RpcCommand,
41
- RpcExtensionUIRequest,
42
- RpcExtensionUIResponse,
43
- RpcHostToolCallRequest,
44
- RpcHostToolCancelRequest,
45
- RpcHostUriCancelRequest,
46
- RpcHostUriRequest,
47
- RpcResponse,
48
- } from "./rpc-types";
49
-
50
- // Re-export types for consumers
51
- export type * from "./rpc-types";
52
-
53
- export type PendingExtensionRequest = {
54
- resolve: (response: RpcExtensionUIResponse) => void;
55
- reject: (error: Error) => void;
56
- };
57
-
58
- type RpcOutput = (
59
- obj:
60
- | RpcResponse
61
- | RpcExtensionUIRequest
62
- | RpcHostToolCallRequest
63
- | RpcHostToolCancelRequest
64
- | RpcHostUriRequest
65
- | RpcHostUriCancelRequest
66
- | object,
67
- ) => Promise<void>;
68
- type StdioTransportState = "open" | "terminalizing" | "closed";
69
-
70
- function sinkErrorCode(error: unknown): string | undefined {
71
- if (error === null || (typeof error !== "object" && typeof error !== "function")) return undefined;
72
- try {
73
- const code = Reflect.get(error, "code");
74
- return typeof code === "string" ? code : undefined;
75
- } catch {
76
- return undefined;
77
- }
78
- }
79
-
80
- function parseValueDialogResponse(
81
- response: RpcExtensionUIResponse,
82
- dialogOptions: ExtensionUIDialogOptions | undefined,
83
- ): string | undefined {
84
- if ("cancelled" in response && response.cancelled) {
85
- if (response.timedOut) dialogOptions?.onTimeout?.();
86
- return undefined;
87
- }
88
- if ("value" in response) return response.value;
89
- return undefined;
90
- }
91
-
92
- export function shouldEmitRpcTitlesForTest(): boolean {
93
- const raw = $pickenv("GJC_RPC_EMIT_TITLE", "PI_RPC_EMIT_TITLE");
94
- if (!raw) return false;
95
- const normalized = raw.trim().toLowerCase();
96
- return normalized === "1" || normalized === "true" || normalized === "yes" || normalized === "on";
97
- }
98
-
99
- const shouldEmitRpcTitles = shouldEmitRpcTitlesForTest;
100
-
101
- /**
102
- * Cancellation commands bypass the ordered serial chain because they must
103
- * interrupt in-flight work — they cannot wait behind the very command they are
104
- * meant to abort.
105
- */
106
- export const RPC_CANCELLATION_COMMANDS: ReadonlySet<RpcCommand["type"]> = new Set<RpcCommand["type"]>([
107
- "abort",
108
- "abort_bash",
109
- "abort_retry",
110
- ]);
111
-
112
- /**
113
- * Safe read-only commands that bypass the ordered serial chain so they never
114
- * head-of-line-block behind a long-running ordered command like
115
- * `bash`/`compact`/`handoff`/`login` (#606, issue 13 — the partial fix only
116
- * fast-laned cancellation).
117
- *
118
- * Every command listed here has a dispatch handler that is **fully synchronous
119
- * and side-effect-free**: on the single-threaded event loop it runs to
120
- * completion between the await points of any in-flight ordered command, reading
121
- * live state without mutating it. Because such a read performs no causal write,
122
- * jumping ahead of an earlier *queued* ordered command is observably harmless —
123
- * there is no state change to reorder. Read payloads are additionally
124
- * snapshotted inside the handler (e.g. `get_messages` returns a shallow copy of
125
- * `session.messages`) so a fast-lane read can never serialize a half-mutated
126
- * array that an ordered turn/compaction is rewriting in place.
127
- *
128
- * Deliberately excluded (kept ordered): every async/long command and every
129
- * mutating command. In particular the control-flag setters (`set_thinking_level`,
130
- * `cycle_thinking_level`, `set_steering_mode`, `set_follow_up_mode`,
131
- * `set_interrupt_mode`, `set_auto_compaction`, `set_auto_retry`) stay ordered.
132
- * Their handlers are synchronous, so fast-laning one ahead of an already-queued
133
- * `prompt`/`bash` would apply the new mode *before* that earlier command runs —
134
- * the earlier command would then observe the later setter's value, a
135
- * causal-order (arrival-order) regression. Mutations therefore stay on the
136
- * chain, and new command types default to ordered (fail-safe).
137
- */
138
- export const RPC_SAFE_READ_CONTROL_COMMANDS: ReadonlySet<RpcCommand["type"]> = new Set<RpcCommand["type"]>([
139
- // Pure synchronous reads — snapshot live state at processing time, never mutate.
140
- "get_state",
141
- "get_session_stats",
142
- "get_available_models",
143
- "get_branch_messages",
144
- "get_last_assistant_text",
145
- "get_messages",
146
- "get_login_providers",
147
- "get_pending_workflow_gates",
148
- ]);
149
-
150
- /** True when a command may bypass the ordered serial chain and run immediately. */
151
- export function isFastLaneRpcCommand(type: RpcCommand["type"]): boolean {
152
- return RPC_CANCELLATION_COMMANDS.has(type) || RPC_SAFE_READ_CONTROL_COMMANDS.has(type);
153
- }
154
-
155
- /**
156
- * Schedules inbound RPC commands: fast-lane commands run immediately while
157
- * everything else runs through a serial chain so causal order is preserved. The
158
- * read loop never blocks, which is what lets a fast-lane command reach a
159
- * long-running ordered command instead of being head-of-line-blocked behind it.
160
- */
161
- export function createRpcCommandScheduler(
162
- run: (command: RpcCommand) => Promise<void>,
163
- track: (task: Promise<void>) => void,
164
- ): { dispatch: (command: RpcCommand) => void; close: () => void } {
165
- let orderedChain: Promise<void> = Promise.resolve();
166
- let accepting = true;
167
- return {
168
- dispatch(command: RpcCommand): void {
169
- if (!accepting) return;
170
- if (isFastLaneRpcCommand(command.type)) {
171
- track(run(command));
172
- return;
173
- }
174
- orderedChain = orderedChain.then(async () => {
175
- if (accepting) await run(command);
176
- });
177
- track(orderedChain);
178
- },
179
- close(): void {
180
- accepting = false;
181
- },
182
- };
183
- }
184
-
185
- function auditOutcomeFor(event: string): "accepted" | "rejected" | "denied" | "exceeded" | "aborted" | "info" {
186
- if (event.includes("denied")) return "denied";
187
- if (event.includes("exceeded")) return "exceeded";
188
- if (event.includes("abort")) return "aborted";
189
- if (event.includes("rejected") || event.includes("conflict")) return "rejected";
190
- if (event.includes("accepted") || event.includes("negotiated") || event.includes("emitted")) return "accepted";
191
- return "info";
192
- }
193
-
194
- // Thrown by prepareRpcSocketPath when the --listen path has a live owner;
195
- // re-exported here because main.ts catches it at the RPC launch boundary.
196
- export { RpcListenRefusedError } from "./rpc-socket-security";
197
-
198
- /**
199
- * Probe whether a unix-domain socket path has a live server accepting
200
- * connections. Returns `true` when a connection succeeds (a previous owner is
201
- * still alive), and returns `false` only for known missing/stale endpoints
202
- * (ENOENT / ECONNREFUSED). Unexpected probe failures fail closed as "alive" so
203
- * `--listen` startup refuses to unlink a path it could not safely classify.
204
- */
205
- export async function isUnixSocketAlive(socketPath: string): Promise<boolean> {
206
- try {
207
- const socket = await Bun.connect({
208
- unix: socketPath,
209
- socket: { data() {}, open() {}, error() {}, close() {} },
210
- });
211
- socket.end();
212
- return true;
213
- } catch (err) {
214
- const code = err && typeof err === "object" ? (err as { code?: unknown }).code : undefined;
215
- if (code === "ENOENT" || code === "ECONNREFUSED") return false;
216
- logger.warn("RPC --listen socket probe failed closed", {
217
- socketPath,
218
- code: typeof code === "string" ? code : undefined,
219
- error: err instanceof Error ? err.message : String(err),
220
- });
221
- return true;
222
- }
223
- }
224
-
225
- export function requestRpcEditor(
226
- pendingRequests: Map<string, PendingExtensionRequest>,
227
- output: RpcOutput,
228
- title: string,
229
- prefill?: string,
230
- dialogOptions?: ExtensionUIDialogOptions,
231
- editorOptions?: { promptStyle?: boolean },
232
- ): Promise<string | undefined> {
233
- if (dialogOptions?.signal?.aborted) return Promise.resolve(undefined);
234
-
235
- const id = Snowflake.next() as string;
236
- const { promise, resolve, reject } = Promise.withResolvers<string | undefined>();
237
- let settled = false;
238
-
239
- const cleanup = () => {
240
- dialogOptions?.signal?.removeEventListener("abort", onAbort);
241
- pendingRequests.delete(id);
242
- };
243
- const finish = (value: string | undefined) => {
244
- if (settled) return;
245
- settled = true;
246
- cleanup();
247
- resolve(value);
248
- };
249
- const fail = (error: Error) => {
250
- if (settled) return;
251
- settled = true;
252
- cleanup();
253
- reject(error);
254
- };
255
- const onAbort = () => {
256
- output({
257
- type: "extension_ui_request",
258
- id: Snowflake.next() as string,
259
- method: "cancel",
260
- targetId: id,
261
- } as RpcExtensionUIRequest);
262
- finish(undefined);
263
- };
264
-
265
- dialogOptions?.signal?.addEventListener("abort", onAbort, { once: true });
266
- pendingRequests.set(id, {
267
- resolve: response => {
268
- if ("cancelled" in response && response.cancelled) {
269
- finish(undefined);
270
- } else if ("value" in response) {
271
- finish(response.value);
272
- } else {
273
- finish(undefined);
274
- }
275
- },
276
- reject: fail,
277
- });
278
- output({
279
- type: "extension_ui_request",
280
- id,
281
- method: "editor",
282
- title,
283
- prefill,
284
- promptStyle: editorOptions?.promptStyle,
285
- } as RpcExtensionUIRequest);
286
- return promise;
287
- }
288
- /**
289
- * Run in RPC mode.
290
- * Listens for JSON commands on stdin, outputs events and responses on stdout.
291
- */
292
- export async function runRpcMode(
293
- session: AgentSession,
294
- setToolUIContext?: (uiContext: ExtensionUIContext, hasUI: boolean) => void,
295
- options?: { listen?: string },
296
- ): Promise<never> {
297
- // Signal to RPC clients that the server is ready to accept commands
298
- // Suppress terminal notifications: they write \x07 (BEL) or OSC sequences directly to
299
- // process.stdout with no newline, which the reader merges with the next JSON line and
300
- // breaks JSON.parse. In RPC mode stdout is the JSON protocol channel — nothing else
301
- // may write there.
302
- process.env.PI_NOTIFICATIONS = "off";
303
-
304
- // Frames route to stdout for stdio and to the active client for --listen. A
305
- // socket server begins with no peer, so it must never leak frames to stdout.
306
- const noopSink = (_line: string): void => {};
307
- let stdioTransportState: StdioTransportState = options?.listen ? "closed" : "open";
308
- let beginStdioTerminalization: (() => void) | undefined;
309
- const stdioFatalFailure = Promise.withResolvers<never>();
310
- void stdioFatalFailure.promise.catch(() => {});
311
- let hasStdioWriteFailure = false;
312
- let stdioWriteFailure: unknown;
313
- let stdioPeerClosed = false;
314
- const pendingStdioWrites = new Set<{
315
- completion: Promise<void>;
316
- settle: (failed: boolean, failure?: unknown) => void;
317
- }>();
318
- const settlePendingStdioWrites = (failed: boolean, failure?: unknown): void => {
319
- for (const pending of [...pendingStdioWrites]) pending.settle(failed, failure);
320
- };
321
- const waitForStdioWrites = async (): Promise<void> => {
322
- while (pendingStdioWrites.size > 0) {
323
- await Promise.allSettled([...pendingStdioWrites].map(pending => pending.completion));
324
- }
325
- };
326
- const handleStdioWriteFailure = (failure: unknown): boolean => {
327
- const code = sinkErrorCode(failure);
328
- if (
329
- isKnownSinkPeerClosedError(failure) &&
330
- (code === "EPIPE" || (stdioPeerClosed && code === "ERR_STREAM_DESTROYED"))
331
- ) {
332
- stdioPeerClosed = true;
333
- if (stdioTransportState === "open") stdioTransportState = "terminalizing";
334
- settlePendingStdioWrites(false);
335
- beginStdioTerminalization?.();
336
- return false;
337
- }
338
- if (!hasStdioWriteFailure) {
339
- hasStdioWriteFailure = true;
340
- stdioWriteFailure = failure;
341
- stdioTransportState = "terminalizing";
342
- stdioFatalFailure.reject(failure);
343
- settlePendingStdioWrites(true, failure);
344
- beginStdioTerminalization?.();
345
- }
346
- return true;
347
- };
348
- const onStdioError = (failure: Error): void => {
349
- handleStdioWriteFailure(failure);
350
- };
351
- if (!options?.listen) process.stdout.on("error", onStdioError);
352
- const writeStdioFrame = (line: string): Promise<void> => {
353
- if (stdioTransportState !== "open") return Promise.resolve();
354
- const completion = Promise.withResolvers<void>();
355
- let settled = false;
356
- let pending: { completion: Promise<void>; settle: (failed: boolean, failure?: unknown) => void };
357
- pending = {
358
- completion: completion.promise,
359
- settle: (failed, failure) => {
360
- if (settled) return;
361
- settled = true;
362
- pendingStdioWrites.delete(pending);
363
- if (failed) completion.reject(failure);
364
- else completion.resolve();
365
- },
366
- };
367
- pendingStdioWrites.add(pending);
368
- try {
369
- process.stdout.write(line, writeError => {
370
- if (settled) return;
371
- if (writeError) pending.settle(handleStdioWriteFailure(writeError), writeError);
372
- else pending.settle(false);
373
- });
374
- } catch (failure) {
375
- pending.settle(handleStdioWriteFailure(failure), failure);
376
- }
377
- return completion.promise;
378
- };
379
- let frameSink: (line: string) => void | Promise<void> = options?.listen ? noopSink : writeStdioFrame;
380
- const output: RpcOutput = obj => {
381
- if (!options?.listen && stdioTransportState !== "open") return Promise.resolve();
382
- let emission: Promise<void>;
383
- try {
384
- emission = Promise.resolve(frameSink(`${JSON.stringify(obj)}\n`));
385
- } catch (failure) {
386
- emission = Promise.reject(failure);
387
- if (!options?.listen) handleStdioWriteFailure(failure);
388
- }
389
- void emission.catch(() => {});
390
- return emission;
391
- };
392
- const emitRpcTitles = shouldEmitRpcTitles();
393
- const rpcCapabilities = { compactMessageUpdate: false };
394
- const decodeError = (err: unknown): string => (err instanceof Error ? err.message : String(err));
395
-
396
- const pendingExtensionRequests = new Map<string, PendingExtensionRequest>();
397
- const hostToolBridge = new RpcHostToolBridge(output);
398
- const hostUriBridge = new RpcHostUriBridge(output);
399
- const auditLog = new UnattendedAuditLog(defaultAuditPath(session.sessionId, session.sessionManager.getCwd()), {
400
- redactAnswers: true,
401
- });
402
- const recordAudit = (event: { event: string; [key: string]: unknown }) => {
403
- const payload =
404
- typeof event.payload === "object" && event.payload !== null
405
- ? (event.payload as Record<string, unknown>)
406
- : undefined;
407
- const gateId =
408
- typeof event.gate_id === "string"
409
- ? event.gate_id
410
- : typeof payload?.gate_id === "string"
411
- ? payload.gate_id
412
- : undefined;
413
- auditLog.record({
414
- run_id: session.sessionId,
415
- session_id: session.sessionId,
416
- actor: typeof event.actor === "string" ? event.actor : undefined,
417
- event: event.event,
418
- outcome: auditOutcomeFor(event.event),
419
- dedupe_key: `${event.event}:${gateId ?? "run"}:${JSON.stringify(payload ?? event)}`,
420
- gate_id: gateId,
421
- stage: typeof event.stage === "string" ? (event.stage as never) : undefined,
422
- kind: typeof event.kind === "string" ? (event.kind as never) : undefined,
423
- scope: typeof payload?.scope === "string" ? payload.scope : undefined,
424
- action: typeof payload?.action === "string" ? payload.action : undefined,
425
- budget: event.event === "budget_exceeded" ? (payload as never) : undefined,
426
- answer_hash: typeof event.answer_hash === "string" ? event.answer_hash : undefined,
427
- error: payload && event.event.endsWith("denied") ? payload : undefined,
428
- });
429
- };
430
- // Unattended control plane (#318/#319/#323/G011): routes negotiate_unattended +
431
- // workflow_gate_response and lets skill runtimes emit gates over RPC.
432
- const gateStore = new FileGateStore(
433
- workflowGatePath(session.sessionManager.getCwd(), session.sessionId, session.sessionId),
434
- );
435
- const unattendedControlPlane = new UnattendedSessionControlPlane({
436
- runId: session.sessionId,
437
- sessionId: session.sessionId,
438
- emitFrame: gate => {
439
- void output(gate);
440
- },
441
- store: gateStore,
442
- audit: recordAudit,
443
- providerSupportsTokenCostMetrics: modelSupportsTokenCostMetrics(session.model),
444
- getUsageSnapshot: () => {
445
- const stats = session.getSessionStats();
446
- return { tokens: stats.tokens.total, costUsd: stats.cost };
447
- },
448
- });
449
- unattendedControlPlane.recover().catch(err => {
450
- void output(error(undefined, "workflow_gate_recover", err instanceof Error ? err.message : String(err)));
451
- });
452
- session.setWorkflowGateEmitter(unattendedControlPlane);
453
-
454
- // Shutdown request flag (wrapped in object to allow mutation with const)
455
- const shutdownState = { requested: false };
456
- let detachStdioInput: (() => void) | undefined;
457
- let closeCommandScheduler: (() => void) | undefined;
458
- let shutdownPromise: Promise<never> | undefined;
459
- let shutdownExitCode = 0;
460
- let suppressShutdownProtocolOutput = false;
461
- let shutdownDispatchStopped = false;
462
- // Tracks in-flight non-blocking command handlers so shutdown can drain them.
463
- const inFlightCommands = new Set<Promise<void>>();
464
- function shutdown(
465
- exitCode: number,
466
- reason: string,
467
- suppressProtocolOutput = false,
468
- stopDispatch = true,
469
- ): Promise<never> {
470
- shutdownExitCode = Math.max(shutdownExitCode, exitCode);
471
- suppressShutdownProtocolOutput ||= suppressProtocolOutput;
472
- if (stopDispatch && !shutdownDispatchStopped) {
473
- shutdownDispatchStopped = true;
474
- closeCommandScheduler?.();
475
- detachStdioInput?.();
476
- }
477
- if (shutdownPromise) return shutdownPromise;
478
- shutdownPromise = (async () => {
479
- // Let in-flight non-blocking commands (bash/compact/handoff) finish and emit
480
- // their responses before teardown, bounded so a never-resolving login cannot
481
- // wedge shutdown (issue 13).
482
- if (inFlightCommands.size > 0) {
483
- await Promise.race([Promise.allSettled([...inFlightCommands]), Bun.sleep(5000)]);
484
- }
485
- if (!options?.listen) await waitForStdioWrites();
486
- await unregisterRpcSession(session.sessionId).catch(() => {});
487
- hostToolBridge.rejectAllPending(`${reason} before host tool execution completed`);
488
- hostUriBridge.clear(`${reason} before host URI request completed`);
489
- let cleanupFailure: unknown;
490
- try {
491
- await session.sessionManager.ensureOnDisk();
492
- } catch (failure) {
493
- cleanupFailure = failure;
494
- }
495
- try {
496
- await session.dispose();
497
- } catch (failure) {
498
- if (cleanupFailure === undefined) cleanupFailure = failure;
499
- }
500
- if (cleanupFailure !== undefined && !suppressShutdownProtocolOutput) {
501
- await output(error(undefined, "shutdown", decodeError(cleanupFailure))).catch(() => {});
502
- }
503
- if (!options?.listen) {
504
- await waitForStdioWrites();
505
- process.stdout.removeListener("error", onStdioError);
506
- stdioTransportState = "closed";
507
- }
508
- const terminalFailure = hasStdioWriteFailure ? stdioWriteFailure : cleanupFailure;
509
- const finalExitCode = terminalFailure !== undefined || hasStdioWriteFailure ? 1 : shutdownExitCode;
510
- process.exit(finalExitCode);
511
- return await new Promise<never>(() => {});
512
- })();
513
- return shutdownPromise;
514
- }
515
-
516
- /**
517
- * Extension UI context that uses the RPC protocol.
518
- */
519
- class RpcExtensionUIContext implements ExtensionUIContext {
520
- constructor(
521
- private pendingRequests: Map<string, PendingExtensionRequest>,
522
- private output: RpcOutput,
523
- ) {}
524
-
525
- /** Helper for dialog methods with signal/timeout support */
526
- #createDialogPromise<T>(
527
- opts: ExtensionUIDialogOptions | undefined,
528
- defaultValue: T,
529
- request: Record<string, unknown>,
530
- parseResponse: (response: RpcExtensionUIResponse) => T,
531
- ): Promise<T> {
532
- if (opts?.signal?.aborted) return Promise.resolve(defaultValue);
533
-
534
- const id = Snowflake.next() as string;
535
- const { promise, resolve, reject } = Promise.withResolvers<T>();
536
- let timeoutId: NodeJS.Timeout | undefined;
537
-
538
- const cleanup = () => {
539
- if (timeoutId) clearTimeout(timeoutId);
540
- opts?.signal?.removeEventListener("abort", onAbort);
541
- this.pendingRequests.delete(id);
542
- };
543
-
544
- const onAbort = () => {
545
- cleanup();
546
- resolve(defaultValue);
547
- };
548
- opts?.signal?.addEventListener("abort", onAbort, { once: true });
549
-
550
- if (opts?.timeout !== undefined) {
551
- timeoutId = setTimeout(() => {
552
- opts.onTimeout?.();
553
- cleanup();
554
- resolve(defaultValue);
555
- }, opts.timeout);
556
- }
557
-
558
- this.pendingRequests.set(id, {
559
- resolve: (response: RpcExtensionUIResponse) => {
560
- cleanup();
561
- resolve(parseResponse(response));
562
- },
563
- reject,
564
- });
565
- this.output({ type: "extension_ui_request", id, ...request } as RpcExtensionUIRequest);
566
- return promise;
567
- }
568
-
569
- select(title: string, options: string[], dialogOptions?: ExtensionUIDialogOptions): Promise<string | undefined> {
570
- return this.#createDialogPromise(
571
- dialogOptions,
572
- undefined,
573
- { method: "select", title, options, timeout: dialogOptions?.timeout },
574
- response => parseValueDialogResponse(response, dialogOptions),
575
- );
576
- }
577
-
578
- confirm(title: string, message: string, dialogOptions?: ExtensionUIDialogOptions): Promise<boolean> {
579
- return this.#createDialogPromise(
580
- dialogOptions,
581
- false,
582
- { method: "confirm", title, message, timeout: dialogOptions?.timeout },
583
- response => {
584
- if ("cancelled" in response && response.cancelled) {
585
- if (response.timedOut) dialogOptions?.onTimeout?.();
586
- return false;
587
- }
588
- if ("confirmed" in response) return response.confirmed;
589
- return false;
590
- },
591
- );
592
- }
593
-
594
- input(
595
- title: string,
596
- placeholder?: string,
597
- dialogOptions?: ExtensionUIDialogOptions,
598
- ): Promise<string | undefined> {
599
- return this.#createDialogPromise(
600
- dialogOptions,
601
- undefined,
602
- { method: "input", title, placeholder, timeout: dialogOptions?.timeout },
603
- response => parseValueDialogResponse(response, dialogOptions),
604
- );
605
- }
606
-
607
- onTerminalInput(): () => void {
608
- // Raw terminal input not supported in RPC mode
609
- return () => {};
610
- }
611
-
612
- notify(message: string, type?: "info" | "warning" | "error"): void {
613
- // Fire and forget - no response needed
614
- this.output({
615
- type: "extension_ui_request",
616
- id: Snowflake.next() as string,
617
- method: "notify",
618
- message,
619
- notifyType: type,
620
- } as RpcExtensionUIRequest);
621
- }
622
-
623
- setStatus(key: string, text: string | undefined): void {
624
- // Fire and forget - no response needed
625
- this.output({
626
- type: "extension_ui_request",
627
- id: Snowflake.next() as string,
628
- method: "setStatus",
629
- statusKey: key,
630
- statusText: text,
631
- } as RpcExtensionUIRequest);
632
- }
633
-
634
- setWorkingMessage(_message?: string): void {
635
- // Not supported in RPC mode
636
- }
637
-
638
- setWidget(key: string, content: unknown, options?: ExtensionWidgetOptions): void {
639
- // Only support string arrays in RPC mode - factory functions are ignored
640
- if (content === undefined || Array.isArray(content)) {
641
- this.output({
642
- type: "extension_ui_request",
643
- id: Snowflake.next() as string,
644
- method: "setWidget",
645
- widgetKey: key,
646
- widgetLines: content as string[] | undefined,
647
- widgetPlacement: options?.placement,
648
- } as RpcExtensionUIRequest);
649
- }
650
- // Component factories are not supported in RPC mode - would need TUI access
651
- }
652
-
653
- setFooter(_factory: unknown): void {
654
- // Custom footer not supported in RPC mode - requires TUI access
655
- }
656
-
657
- setHeader(_factory: unknown): void {
658
- // Custom header not supported in RPC mode - requires TUI access
659
- }
660
-
661
- setTitle(title: string): void {
662
- // Title updates are low-value noise for most RPC hosts; opt in via GJC_RPC_EMIT_TITLE=1.
663
- if (!emitRpcTitles) return;
664
- this.output({
665
- type: "extension_ui_request",
666
- id: Snowflake.next() as string,
667
- method: "setTitle",
668
- title,
669
- } as RpcExtensionUIRequest);
670
- }
671
-
672
- async custom(): Promise<never> {
673
- // Custom UI not supported in RPC mode
674
- return undefined as never;
675
- }
676
-
677
- pasteToEditor(text: string): void {
678
- // Paste handling not supported in RPC mode - falls back to setEditorText
679
- this.setEditorText(text);
680
- }
681
-
682
- setEditorText(text: string): void {
683
- // Fire and forget - host can implement editor control
684
- this.output({
685
- type: "extension_ui_request",
686
- id: Snowflake.next() as string,
687
- method: "set_editor_text",
688
- text,
689
- } as RpcExtensionUIRequest);
690
- }
691
-
692
- getEditorText(): string {
693
- // Synchronous method can't wait for RPC response
694
- // Host should track editor state locally if needed
695
- return "";
696
- }
697
-
698
- async editor(
699
- title: string,
700
- prefill?: string,
701
- dialogOptions?: ExtensionUIDialogOptions,
702
- editorOptions?: { promptStyle?: boolean },
703
- ): Promise<string | undefined> {
704
- return requestRpcEditor(this.pendingRequests, this.output, title, prefill, dialogOptions, editorOptions);
705
- }
706
-
707
- get theme(): Theme {
708
- return theme;
709
- }
710
-
711
- getAllThemes(): Promise<{ name: string; path: string | undefined }[]> {
712
- return Promise.resolve([]);
713
- }
714
-
715
- getTheme(_name: string): Promise<Theme | undefined> {
716
- return Promise.resolve(undefined);
717
- }
718
-
719
- setTheme(_theme: string | Theme): Promise<{ success: boolean; error?: string }> {
720
- // Theme switching not supported in RPC mode
721
- return Promise.resolve({ success: false, error: "Theme switching not supported in RPC mode" });
722
- }
723
-
724
- getToolsExpanded() {
725
- // Tool expansion not supported in RPC mode - no TUI
726
- return false;
727
- }
728
-
729
- setToolsExpanded(_expanded: boolean) {
730
- // Tool expansion not supported in RPC mode - no TUI
731
- }
732
-
733
- setEditorComponent(): void {
734
- // Custom editor components not supported in RPC mode
735
- }
736
- }
737
-
738
- // Wire up UI context for tool execution (ask tool, etc.) and extensions.
739
- // A single shared instance routes all responses received on stdin to the
740
- // correct waiting promise regardless of which code path created the request.
741
- const rpcUiContext = new RpcExtensionUIContext(pendingExtensionRequests, output);
742
- setToolUIContext?.(rpcUiContext, true);
743
-
744
- // Set up extensions with RPC-based UI context
745
- await initializeExtensions(session, {
746
- reportSendError: (action, err) => {
747
- output(error(undefined, action, err.message));
748
- },
749
- reportRuntimeError: err => {
750
- output({ type: "extension_error", extensionPath: err.extensionPath, event: err.event, error: err.error });
751
- },
752
- onShutdown: () => {
753
- shutdownState.requested = true;
754
- },
755
- uiContext: rpcUiContext,
756
- });
757
-
758
- // Output all agent events as canonical agent-wire `event` frames (docs/rpc.md):
759
- // { type:"event", protocol_version, session_id, seq, frame_id, payload:{ event_type, event } }.
760
- const eventSequencer = new AgentWireFrameSequencer(session.sessionId);
761
- const compactEventEncoder = new AgentWireCompactEventEncoder(eventSequencer);
762
- session.subscribe(event => {
763
- output(
764
- rpcCapabilities.compactMessageUpdate
765
- ? toAgentWireCompactEventFrame(event, compactEventEncoder)
766
- : toAgentWireEventFrame(event, eventSequencer),
767
- );
768
- });
769
-
770
- // Handle a single command through the shared agent-wire dispatcher so RPC
771
- // and bridge mode use one command surface.
772
- const handleCommand = (command: RpcCommand): Promise<RpcResponse> =>
773
- dispatchRpcCommand(command, {
774
- session,
775
- output,
776
- hostToolRegistry: hostToolBridge,
777
- hostUriRegistry: hostUriBridge,
778
- createUiContext: () => new RpcExtensionUIContext(pendingExtensionRequests, output),
779
- unattendedControlPlane,
780
- });
781
-
782
- // Fast-lane commands (cancellation + safe read/control, see
783
- // isFastLaneRpcCommand) bypass the ordered serial chain and run immediately;
784
- // everything else runs through a serial chain so causal order is preserved
785
- // (e.g. an ordered `set_model` after `bash` still applies after the bash
786
- // result) while the read loop itself never blocks — that is what lets a
787
- // fast-lane command reach a long-running `bash`/`compact`/`handoff`/`login`
788
- // instead of being head-of-line-blocked behind it (issue 13).
789
- const runCommand = async (command: RpcCommand): Promise<void> => {
790
- let response: RpcResponse;
791
- try {
792
- response = await handleCommand(command);
793
- } catch (err) {
794
- response = error(command.id, command.type, decodeError(err));
795
- }
796
- await output(response);
797
- };
798
- const trackCommand = (task: Promise<void>): void => {
799
- inFlightCommands.add(task);
800
- void task.then(
801
- () => inFlightCommands.delete(task),
802
- () => inFlightCommands.delete(task),
803
- );
804
- };
805
- const scheduler = createRpcCommandScheduler(runCommand, trackCommand);
806
- const dispatchCommand = scheduler.dispatch;
807
- closeCommandScheduler = scheduler.close;
808
- let stdioTerminalizationStarted = false;
809
- beginStdioTerminalization = () => {
810
- if (options?.listen || stdioTerminalizationStarted) return;
811
- stdioTerminalizationStarted = true;
812
- closeCommandScheduler?.();
813
- detachStdioInput?.();
814
- void session.abort({ timeoutMs: 5000, cause: "internal", silent: true }).catch(() => {});
815
- void shutdown(
816
- hasStdioWriteFailure ? 1 : 0,
817
- hasStdioWriteFailure ? "RPC stdout write failed" : "RPC stdout peer disconnected",
818
- true,
819
- ).catch(() => {});
820
- };
821
- if (stdioTransportState !== "open") beginStdioTerminalization();
822
-
823
- /**
824
- * Check if shutdown was requested and perform shutdown if so.
825
- * Called after handling each command when waiting for the next command.
826
- */
827
- async function checkShutdownRequested(): Promise<void> {
828
- if (!shutdownState.requested) return;
829
- await shutdown(0, "RPC shutdown requested");
830
- }
831
-
832
- // Parse + route a single inbound JSONL frame. Shared by the stdio reader and the
833
- // persistent UDS server so both transports use the same command surface.
834
- const inputDecoder = new TextDecoder("utf-8", { fatal: false });
835
- async function handleInboundLine(text: string): Promise<void> {
836
- if (!options?.listen && stdioTransportState !== "open") return;
837
- let parsed: unknown;
838
- let parseFailure: RpcResponse | undefined;
839
- try {
840
- parsed = JSON.parse(text);
841
- } catch (err) {
842
- parseFailure = error(undefined, "parse", `Failed to parse command: ${decodeError(err)}`);
843
- }
844
- if (parseFailure) {
845
- output(parseFailure);
846
- return;
847
- }
848
-
849
- let response: RpcResponse | undefined;
850
- try {
851
- if ((parsed as RpcExtensionUIResponse).type === "extension_ui_response") {
852
- const extensionResponse = parsed as RpcExtensionUIResponse;
853
- pendingExtensionRequests.get(extensionResponse.id)?.resolve(extensionResponse);
854
- return;
855
- }
856
- if (isRpcHostToolResult(parsed)) {
857
- hostToolBridge.handleResult(parsed);
858
- return;
859
- }
860
- if (isRpcHostToolUpdate(parsed)) {
861
- hostToolBridge.handleUpdate(parsed);
862
- return;
863
- }
864
- if (isRpcHostUriResult(parsed)) {
865
- hostUriBridge.handleResult(parsed);
866
- return;
867
- }
868
- if ((parsed as RpcCommand).type === "set_capabilities") {
869
- // RPC has no startup handshake; clients negotiate optional event-shape capabilities
870
- // with this explicit command. Defaults remain legacy/full-frame until accepted here.
871
- const command = parsed as Extract<RpcCommand, { type: "set_capabilities" }>;
872
- const requested = Array.isArray(command.capabilities) ? command.capabilities : [];
873
- const acceptedCapabilities = requested.filter(capability => capability === "compact_message_update");
874
- rpcCapabilities.compactMessageUpdate = acceptedCapabilities.includes("compact_message_update");
875
- response = rpcSuccess(command.id, "set_capabilities", {
876
- acceptedCapabilities,
877
- unsupported: requested.filter(capability => capability !== "compact_message_update"),
878
- });
879
- } else {
880
- // Ordered commands run through a serial chain to preserve causal order; the
881
- // reader never blocks, so cancellation commands stay responsive even while a
882
- // long command is in flight (issue 13).
883
- dispatchCommand(parsed as RpcCommand);
884
- await checkShutdownRequested();
885
- }
886
- } catch (err) {
887
- response = error(undefined, "parse", `Failed to parse command: ${decodeError(err)}`);
888
- }
889
- if (response) output(response);
890
- }
891
-
892
- // Persistent UDS server (issue 09): keep the AgentSession alive across client
893
- // reconnects instead of exiting on stdin EOF. Frames route to the active client
894
- // socket; while no client is connected they are dropped (clients resync via
895
- // get_state/get_messages on reconnect).
896
- if (options?.listen) {
897
- const socketPath = options.listen;
898
- await prepareRpcSocketPath(socketPath);
899
- await registerRpcSession({
900
- sessionId: session.sessionId,
901
- pid: process.pid,
902
- transport: "socket",
903
- cwd: session.sessionManager.getCwd(),
904
- model: session.model?.id,
905
- startedAt: new Date().toISOString(),
906
- endpoint: socketPath,
907
- }).catch(() => {});
908
-
909
- let currentSocket: Bun.Socket | undefined;
910
- let buf = "";
911
- const detachCurrentSocket = (socket: Bun.Socket): void => {
912
- if (socket !== currentSocket) return;
913
- currentSocket = undefined;
914
- frameSink = noopSink;
915
- };
916
- const socketIsLocallyTerminal = (socket: Bun.Socket): boolean => socket.readyState <= 0;
917
- const handleSocketFailure = (socket: Bun.Socket, failure: unknown): void => {
918
- if (socket !== currentSocket) return;
919
- const code = sinkErrorCode(failure);
920
- const peerClosed =
921
- isKnownSinkPeerClosedError(failure) &&
922
- (code === "EPIPE" || (code === "ERR_STREAM_DESTROYED" && socketIsLocallyTerminal(socket)));
923
- detachCurrentSocket(socket);
924
- if (peerClosed) return;
925
- void shutdown(1, "RPC socket output failed", true).catch(() => {});
926
- };
927
- const server = Bun.listen({
928
- unix: socketPath,
929
- socket: {
930
- open(socket) {
931
- currentSocket = socket;
932
- buf = "";
933
- frameSink = (line: string) => {
934
- if (socket !== currentSocket) return;
935
- try {
936
- const written = socket.write(line);
937
- if (written >= 0) return;
938
- if (socketIsLocallyTerminal(socket)) {
939
- detachCurrentSocket(socket);
940
- return;
941
- }
942
- handleSocketFailure(
943
- socket,
944
- Object.assign(new Error("RPC socket write failed"), { code: "ERR_STREAM_DESTROYED" }),
945
- );
946
- } catch (failure) {
947
- handleSocketFailure(socket, failure);
948
- }
949
- };
950
- void output({ type: "ready" });
951
- },
952
- data(socket, data) {
953
- if (socket !== currentSocket) return;
954
- buf += inputDecoder.decode(data);
955
- while (true) {
956
- const nl = buf.indexOf("\n");
957
- if (nl < 0) break;
958
- const text = buf.slice(0, nl).trim();
959
- buf = buf.slice(nl + 1);
960
- if (text) void handleInboundLine(text);
961
- }
962
- },
963
- close(socket) {
964
- detachCurrentSocket(socket);
965
- },
966
- error(socket, failure) {
967
- handleSocketFailure(socket, failure);
968
- },
969
- },
970
- });
971
- await verifyRpcSocketAfterListen(socketPath);
972
- void server;
973
-
974
- const onSignal = (): void => {
975
- void shutdown(0, "RPC socket server signal");
976
- };
977
- process.on("SIGINT", onSignal);
978
- process.on("SIGTERM", onSignal);
979
- // Block until an explicit shutdown (signal/extension) calls process.exit.
980
- await new Promise<never>(() => {});
981
- throw new Error("RPC socket server returned unexpectedly");
982
- }
983
-
984
- // Register this stdio RPC session so other processes can discover it (issue 10).
985
- await registerRpcSession({
986
- sessionId: session.sessionId,
987
- pid: process.pid,
988
- transport: "stdio",
989
- cwd: session.sessionManager.getCwd(),
990
- model: session.model?.id,
991
- startedAt: new Date().toISOString(),
992
- }).catch(() => {});
993
-
994
- // Listen for JSONL input using Bun's stdin. Parse frame-by-frame so a malformed
995
- // command reports a parse error without poisoning the whole long-lived RPC session.
996
- const inputLines = readLines(Bun.stdin.stream());
997
- const inputIterator = inputLines[Symbol.asyncIterator]();
998
- detachStdioInput = () => {
999
- const detached = inputIterator.return?.(undefined);
1000
- if (detached) void detached.catch(() => {});
1001
- };
1002
- let inputFailure: unknown;
1003
- try {
1004
- await output({ type: "ready" });
1005
- while (stdioTransportState === "open") {
1006
- const next = await Promise.race([inputIterator.next(), stdioFatalFailure.promise]);
1007
- if (next.done) break;
1008
- const text = inputDecoder.decode(next.value).trim();
1009
- if (text) await handleInboundLine(text);
1010
- }
1011
- } catch (failure) {
1012
- inputFailure = failure;
1013
- } finally {
1014
- detachStdioInput = undefined;
1015
- }
1016
- if (inputFailure !== undefined) {
1017
- return await shutdown(
1018
- 1,
1019
- hasStdioWriteFailure ? "RPC stdout write failed" : "RPC stdin failed",
1020
- hasStdioWriteFailure,
1021
- );
1022
- }
1023
- if (stdioTransportState === "open") return await shutdown(0, "RPC client disconnected", false, false);
1024
- return await shutdown(
1025
- hasStdioWriteFailure ? 1 : 0,
1026
- hasStdioWriteFailure ? "RPC stdout write failed" : "RPC stdout peer disconnected",
1027
- true,
1028
- );
1029
- }