@gajae-code/coding-agent 0.18.12 → 0.19.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 (305) hide show
  1. package/CHANGELOG.md +467 -0
  2. package/dist/types/capability/rule.d.ts +7 -2
  3. package/dist/types/cli/append-system-prompt.d.ts +6 -0
  4. package/dist/types/cli/args.d.ts +7 -2
  5. package/dist/types/cli/customize-doctor-precedence.d.ts +16 -0
  6. package/dist/types/cli/customize-doctor.d.ts +4 -3
  7. package/dist/types/cli/public-command-errors.d.ts +1 -0
  8. package/dist/types/cli/root-flags.d.ts +0 -12
  9. package/dist/types/cli/update-cli.d.ts +7 -2
  10. package/dist/types/cli-main.d.ts +0 -6
  11. package/dist/types/commands/launch.d.ts +0 -6
  12. package/dist/types/commands/migrate.d.ts +1 -1
  13. package/dist/types/commands/sdk.d.ts +8 -0
  14. package/dist/types/commit/map-reduce/index.d.ts +4 -4
  15. package/dist/types/config/autorouting-tier-map.d.ts +23 -1
  16. package/dist/types/config/config-hot-reload.d.ts +5 -0
  17. package/dist/types/config/file-lock.d.ts +44 -2
  18. package/dist/types/config/model-profile-activation.d.ts +19 -5
  19. package/dist/types/config/model-registry.d.ts +16 -3
  20. package/dist/types/config/model-resolver.d.ts +5 -4
  21. package/dist/types/config/settings-schema.d.ts +11 -1
  22. package/dist/types/coordinator-mcp/server.d.ts +4 -0
  23. package/dist/types/customization/import.d.ts +2 -0
  24. package/dist/types/customization/types.d.ts +1 -1
  25. package/dist/types/dap/session.d.ts +11 -2
  26. package/dist/types/discovery/helpers.d.ts +7 -2
  27. package/dist/types/edit/index.d.ts +5 -0
  28. package/dist/types/edit/modes/patch.d.ts +9 -0
  29. package/dist/types/eval/py/runtime.d.ts +9 -0
  30. package/dist/types/export/ttsr.d.ts +19 -0
  31. package/dist/types/extensibility/extensions/types.d.ts +5 -1
  32. package/dist/types/extensibility/slash-commands.d.ts +1 -0
  33. package/dist/types/gjc-runtime/ralplan-current-final.d.ts +54 -0
  34. package/dist/types/gjc-runtime/ralplan-runtime.d.ts +5 -0
  35. package/dist/types/gjc-runtime/tmux-sessions.d.ts +24 -0
  36. package/dist/types/gjc-runtime/ultragoal-change-set.d.ts +6 -0
  37. package/dist/types/gjc-runtime/ultragoal-runtime.d.ts +1 -0
  38. package/dist/types/harness-control-plane/receipt-spool.d.ts +4 -0
  39. package/dist/types/hindsight/content.d.ts +5 -6
  40. package/dist/types/internal-urls/types.d.ts +2 -0
  41. package/dist/types/lsp/clients/swiftlint-client.d.ts +0 -4
  42. package/dist/types/main.d.ts +23 -18
  43. package/dist/types/migrate/action-planner.d.ts +10 -1
  44. package/dist/types/migrate/adapters/codex.d.ts +1 -1
  45. package/dist/types/migrate/adapters/index.d.ts +35 -3
  46. package/dist/types/migrate/hook-module.d.ts +23 -0
  47. package/dist/types/migrate/report.d.ts +2 -1
  48. package/dist/types/migrate/types.d.ts +69 -2
  49. package/dist/types/modes/components/agent-dashboard.d.ts +1 -1
  50. package/dist/types/modes/components/model-selector.d.ts +43 -0
  51. package/dist/types/modes/prompt-suggestion-controller.d.ts +1 -1
  52. package/dist/types/modes/utils/host-status.d.ts +15 -11
  53. package/dist/types/runtime-mcp/project-host-values.d.ts +11 -5
  54. package/dist/types/sdk/broker/broker-exit.d.ts +1 -1
  55. package/dist/types/sdk/broker/broker.d.ts +22 -1
  56. package/dist/types/sdk/broker/ensure.d.ts +5 -2
  57. package/dist/types/sdk/broker/hop.d.ts +9 -0
  58. package/dist/types/sdk/broker/lifecycle.d.ts +22 -0
  59. package/dist/types/sdk/broker/session-index.d.ts +2 -0
  60. package/dist/types/sdk/broker/transport.d.ts +2 -0
  61. package/dist/types/sdk/bus/index.d.ts +21 -0
  62. package/dist/types/sdk/bus/telegram-daemon-contract.d.ts +7 -1
  63. package/dist/types/sdk/bus/telegram-daemon.d.ts +2 -0
  64. package/dist/types/sdk/bus/threaded-render.d.ts +2 -1
  65. package/dist/types/sdk/host/control/operations.d.ts +5 -1
  66. package/dist/types/sdk/host/events.d.ts +4 -0
  67. package/dist/types/sdk/host/host.d.ts +2 -1
  68. package/dist/types/sdk/host/prompt-image-upload.d.ts +43 -0
  69. package/dist/types/sdk/host/session-runtime.d.ts +8 -8
  70. package/dist/types/sdk/host/surface-policy.d.ts +2 -1
  71. package/dist/types/sdk/lifecycle/service.d.ts +12 -0
  72. package/dist/types/sdk/prompt-status.d.ts +11 -4
  73. package/dist/types/session/agent-session.d.ts +50 -8
  74. package/dist/types/session/session-manager.d.ts +16 -0
  75. package/dist/types/skill-state/workflow-mutation-guard.d.ts +13 -1
  76. package/dist/types/slash-commands/helpers/mcp.d.ts +5 -0
  77. package/dist/types/ssh/ssh-executor.d.ts +8 -1
  78. package/dist/types/system-prompt.d.ts +3 -2
  79. package/dist/types/task/role-body.d.ts +11 -0
  80. package/dist/types/tools/awaiting-input.d.ts +28 -0
  81. package/dist/types/tools/bash-allowed-prefixes.d.ts +6 -0
  82. package/dist/types/tools/bash-skill-urls.d.ts +4 -0
  83. package/dist/types/tools/bash.d.ts +7 -0
  84. package/dist/types/tools/descriptors.d.ts +13 -2
  85. package/dist/types/tools/fetch.d.ts +6 -0
  86. package/dist/types/tools/irc.d.ts +5 -3
  87. package/dist/types/tools/tmux-self-injection-guard.d.ts +2 -0
  88. package/dist/types/tools/ultragoal-ask-guard.d.ts +1 -1
  89. package/dist/types/tools/write.d.ts +2 -0
  90. package/dist/types/utils/file-mentions.d.ts +5 -0
  91. package/dist/types/utils/image-limits.d.ts +7 -0
  92. package/dist/types/utils/image-loading.d.ts +0 -1
  93. package/dist/types/utils/open.d.ts +0 -1
  94. package/dist/types/utils/pasted-image-loading.d.ts +1 -5
  95. package/dist/types/utils/prompt-suggestion.d.ts +2 -2
  96. package/dist/types/utils/shell-snapshot.d.ts +2 -0
  97. package/dist/types/utils/title-generator.d.ts +13 -3
  98. package/dist/types/web/search/providers/codex.d.ts +6 -7
  99. package/package.json +9 -8
  100. package/scripts/acp-conformance-agent.ts +12 -2
  101. package/scripts/generate-sdk-operation-inventory.ts +16 -0
  102. package/src/capability/fs.ts +31 -3
  103. package/src/capability/rule.ts +11 -2
  104. package/src/cli/append-system-prompt.ts +12 -0
  105. package/src/cli/args.ts +11 -7
  106. package/src/cli/customize-doctor-precedence.ts +31 -0
  107. package/src/cli/customize-doctor.ts +49 -39
  108. package/src/cli/fast-help.ts +0 -2
  109. package/src/cli/migrate-cli.ts +14 -3
  110. package/src/cli/public-command-errors.ts +2 -0
  111. package/src/cli/root-flags.ts +0 -2
  112. package/src/cli/update-cli.ts +247 -6
  113. package/src/commands/harness.ts +25 -15
  114. package/src/commands/migrate.ts +3 -3
  115. package/src/commands/sdk.ts +81 -11
  116. package/src/commit/agentic/agent.ts +6 -6
  117. package/src/commit/analysis/conventional.ts +14 -5
  118. package/src/commit/map-reduce/index.ts +7 -7
  119. package/src/commit/pipeline.ts +12 -12
  120. package/src/config/autorouting-tier-map.ts +20 -1
  121. package/src/config/config-file.ts +106 -3
  122. package/src/config/config-hot-reload.ts +8 -0
  123. package/src/config/file-lock.ts +1431 -304
  124. package/src/config/keybindings.ts +154 -7
  125. package/src/config/model-profile-activation.ts +177 -39
  126. package/src/config/model-registry.ts +380 -55
  127. package/src/config/model-resolver.ts +49 -21
  128. package/src/config/settings-schema.ts +13 -1
  129. package/src/config/settings.ts +2 -0
  130. package/src/coordinator-mcp/server.ts +37 -2
  131. package/src/crash/sanitize.ts +3 -1
  132. package/src/customization/import.ts +276 -7
  133. package/src/customization/inventory.ts +24 -8
  134. package/src/customization/types.ts +1 -0
  135. package/src/dap/client.ts +3 -2
  136. package/src/dap/session.ts +161 -36
  137. package/src/debug/crash-diagnostics.ts +87 -5
  138. package/src/debug/report-bundle.ts +71 -2
  139. package/src/discovery/builtin.ts +4 -3
  140. package/src/discovery/helpers.ts +24 -14
  141. package/src/edit/index.ts +191 -5
  142. package/src/edit/modes/patch.ts +27 -0
  143. package/src/eval/py/runtime.ts +44 -4
  144. package/src/exec/non-interactive-env.ts +0 -1
  145. package/src/export/html/index.ts +60 -20
  146. package/src/export/ttsr.ts +176 -1
  147. package/src/extensibility/extensions/loader.ts +2 -0
  148. package/src/extensibility/extensions/ouroboros-ooo-bridge.ts +63 -1
  149. package/src/extensibility/extensions/types.ts +5 -1
  150. package/src/extensibility/hooks/loader.ts +10 -2
  151. package/src/extensibility/plugins/marketplace/manager.ts +36 -10
  152. package/src/extensibility/slash-commands.ts +5 -0
  153. package/src/gjc-runtime/gc-runtime.ts +4 -1
  154. package/src/gjc-runtime/launch-tmux.ts +23 -13
  155. package/src/gjc-runtime/ralplan-current-final.ts +294 -0
  156. package/src/gjc-runtime/ralplan-runtime.ts +95 -1
  157. package/src/gjc-runtime/state-writer.ts +109 -5
  158. package/src/gjc-runtime/tmux-sessions.ts +93 -13
  159. package/src/gjc-runtime/ultragoal-change-set.ts +90 -11
  160. package/src/gjc-runtime/ultragoal-guard.ts +9 -1
  161. package/src/gjc-runtime/ultragoal-runtime.ts +15 -5
  162. package/src/harness-control-plane/finalize.ts +11 -0
  163. package/src/harness-control-plane/receipt-spool.ts +105 -28
  164. package/src/harness-control-plane/storage.ts +124 -6
  165. package/src/hindsight/content.ts +116 -16
  166. package/src/hindsight/mental-models.ts +22 -10
  167. package/src/hindsight/state.ts +7 -4
  168. package/src/hooks/native-skill-hook.ts +3 -1
  169. package/src/internal-urls/artifact-protocol.ts +3 -1
  170. package/src/internal-urls/docs-index.generated.ts +14 -14
  171. package/src/internal-urls/local-protocol.ts +28 -3
  172. package/src/internal-urls/types.ts +2 -0
  173. package/src/lsp/client.ts +2 -1
  174. package/src/lsp/clients/biome-client.ts +2 -0
  175. package/src/lsp/clients/swiftlint-client.ts +2 -0
  176. package/src/main.ts +330 -92
  177. package/src/memories/index.ts +25 -2
  178. package/src/migrate/action-planner.ts +517 -2
  179. package/src/migrate/adapters/claude-code.ts +141 -3
  180. package/src/migrate/adapters/codex.ts +57 -5
  181. package/src/migrate/adapters/index.ts +240 -5
  182. package/src/migrate/adapters/opencode.ts +4 -0
  183. package/src/migrate/executor.ts +141 -3
  184. package/src/migrate/hook-module.ts +193 -0
  185. package/src/migrate/report.ts +45 -1
  186. package/src/migrate/types.ts +73 -3
  187. package/src/modes/acp/acp-agent.ts +10 -1
  188. package/src/modes/components/agent-dashboard.ts +20 -4
  189. package/src/modes/components/customization/customization-dashboard.ts +7 -0
  190. package/src/modes/components/customization/import-wizard.ts +29 -3
  191. package/src/modes/components/model-selector.ts +157 -103
  192. package/src/modes/components/welcome.ts +3 -1
  193. package/src/modes/controllers/command-controller.ts +271 -16
  194. package/src/modes/controllers/event-controller.ts +1 -1
  195. package/src/modes/controllers/extension-ui-controller.ts +31 -1
  196. package/src/modes/controllers/goal-mode-controller.ts +79 -10
  197. package/src/modes/controllers/input-controller.ts +2 -0
  198. package/src/modes/controllers/plan-mode-controller.ts +49 -4
  199. package/src/modes/controllers/runtime-mcp-command-controller.ts +7 -6
  200. package/src/modes/controllers/selector-controller.ts +14 -1
  201. package/src/modes/interactive-mode.ts +55 -4
  202. package/src/modes/prompt-suggestion-controller.ts +1 -1
  203. package/src/modes/runtime-init.ts +5 -1
  204. package/src/modes/utils/host-status.ts +21 -10
  205. package/src/prompts/agent-fragments/browser-aside.md +2 -2
  206. package/src/prompts/system/irc-incoming.md +3 -1
  207. package/src/prompts/tools/irc.md +1 -1
  208. package/src/runtime-mcp/manager.ts +8 -3
  209. package/src/runtime-mcp/project-host-values.ts +12 -8
  210. package/src/runtime-mcp/tool-bridge.ts +68 -4
  211. package/src/runtime-mcp/transports/stdio.ts +2 -1
  212. package/src/sdk/broker/broker-exit.ts +2 -0
  213. package/src/sdk/broker/broker.ts +143 -4
  214. package/src/sdk/broker/ensure.ts +74 -6
  215. package/src/sdk/broker/hop.ts +13 -1
  216. package/src/sdk/broker/lifecycle-ledger.ts +3 -0
  217. package/src/sdk/broker/lifecycle.ts +102 -6
  218. package/src/sdk/broker/session-index.ts +29 -4
  219. package/src/sdk/broker/transport.ts +17 -1
  220. package/src/sdk/bus/chat-daemon-control.ts +2 -2
  221. package/src/sdk/bus/index.ts +408 -14
  222. package/src/sdk/bus/session-control.ts +2 -0
  223. package/src/sdk/bus/slack-daemon.ts +105 -9
  224. package/src/sdk/bus/telegram-daemon-contract.ts +7 -1
  225. package/src/sdk/bus/telegram-daemon.ts +10 -2
  226. package/src/sdk/bus/threaded-render.ts +20 -1
  227. package/src/sdk/cli/session-cli.ts +2 -0
  228. package/src/sdk/host/control/dispatch.ts +13 -1
  229. package/src/sdk/host/control/operations.ts +10 -1
  230. package/src/sdk/host/events.ts +67 -4
  231. package/src/sdk/host/host.ts +8 -2
  232. package/src/sdk/host/prompt-image-upload.ts +438 -0
  233. package/src/sdk/host/sdk-surface-parity.test.ts +15 -0
  234. package/src/sdk/host/session-runtime.test.ts +1537 -24
  235. package/src/sdk/host/session-runtime.ts +332 -36
  236. package/src/sdk/host/surface-policy.ts +4 -0
  237. package/src/sdk/host/websocket-transport.lifecycle.test.ts +90 -2
  238. package/src/sdk/host/websocket-transport.ts +19 -7
  239. package/src/sdk/lifecycle/service.ts +14 -1
  240. package/src/sdk/prompt-status.ts +6 -4
  241. package/src/sdk/protocol/adapter-validation.ts +28 -1
  242. package/src/sdk/protocol/operation-inventory.generated.json +160 -0
  243. package/src/sdk/protocol/operation-registry.ts +33 -3
  244. package/src/sdk/session.ts +123 -36
  245. package/src/session/agent-session.ts +1614 -329
  246. package/src/session/history-storage.ts +182 -12
  247. package/src/session/internal/managed-session-scope.ts +47 -18
  248. package/src/session/internal/managed-session-storage.ts +71 -1
  249. package/src/session/session-manager.ts +90 -5
  250. package/src/session-import/redact.ts +53 -2
  251. package/src/skill-state/workflow-mutation-guard.ts +1498 -18
  252. package/src/slash-commands/builtin-registry.ts +6 -3
  253. package/src/slash-commands/helpers/mcp.ts +11 -3
  254. package/src/ssh/ssh-executor.ts +9 -2
  255. package/src/stt/recorder.ts +26 -9
  256. package/src/system-prompt.ts +61 -16
  257. package/src/task/executor.ts +12 -8
  258. package/src/task/role-body.ts +84 -0
  259. package/src/task/token-log-mode.test.ts +162 -0
  260. package/src/task/token-log.ts +91 -2
  261. package/src/tools/archive-reader.ts +650 -37
  262. package/src/tools/ask.ts +1 -0
  263. package/src/tools/ast-grep.ts +7 -2
  264. package/src/tools/auto-generated-guard.ts +12 -3
  265. package/src/tools/awaiting-input.ts +114 -0
  266. package/src/tools/bash-allowed-prefixes.ts +61 -1
  267. package/src/tools/bash-skill-urls.ts +58 -5
  268. package/src/tools/bash.ts +99 -26
  269. package/src/tools/browser/tab-worker.ts +39 -13
  270. package/src/tools/browser.ts +17 -7
  271. package/src/tools/cron.ts +10 -1
  272. package/src/tools/debug.ts +9 -2
  273. package/src/tools/descriptors.test.ts +4 -4
  274. package/src/tools/descriptors.ts +16 -4
  275. package/src/tools/eval.ts +15 -0
  276. package/src/tools/fetch.ts +90 -2
  277. package/src/tools/gh.ts +27 -12
  278. package/src/tools/image-gen.ts +20 -6
  279. package/src/tools/irc.ts +5 -3
  280. package/src/tools/monitor.ts +9 -1
  281. package/src/tools/output-meta.ts +51 -3
  282. package/src/tools/read.ts +9 -2
  283. package/src/tools/recipe/runner.ts +23 -12
  284. package/src/tools/render-utils.ts +2 -2
  285. package/src/tools/search.ts +3 -2
  286. package/src/tools/ssh.ts +274 -8
  287. package/src/tools/subagent.ts +10 -4
  288. package/src/tools/tmux-self-injection-guard.ts +3301 -122
  289. package/src/tools/tool-catalog.generated.ts +1 -1
  290. package/src/tools/ultragoal-ask-guard.ts +13 -1
  291. package/src/tools/write.ts +7 -0
  292. package/src/tui/tree-list.ts +15 -1
  293. package/src/utils/commit-message-generator.ts +3 -2
  294. package/src/utils/external-editor.ts +21 -7
  295. package/src/utils/file-mentions.ts +46 -6
  296. package/src/utils/git.ts +38 -7
  297. package/src/utils/image-limits.ts +8 -0
  298. package/src/utils/image-loading.ts +1 -1
  299. package/src/utils/open.ts +73 -4
  300. package/src/utils/pasted-image-loading.ts +9 -8
  301. package/src/utils/prompt-suggestion.ts +5 -4
  302. package/src/utils/shell-snapshot.ts +9 -1
  303. package/src/utils/title-generator.ts +65 -20
  304. package/src/web/search/index.ts +2 -1
  305. package/src/web/search/providers/codex.ts +29 -32
@@ -1,3 +1,4 @@
1
+ import { setSessionOutboundRedact } from "../host/events";
1
2
  import {
2
3
  type GenericNotificationSessionSource,
3
4
  getCurrentTelegramActivationMarker,
@@ -480,6 +481,7 @@ export class NotificationSessionController {
480
481
  policy: Omit<NotificationRuntimePolicy, "mode">,
481
482
  mode: NotificationRuntimePolicy["mode"],
482
483
  ): void {
484
+ setSessionOutboundRedact(binding.sessionId, policy.redact);
483
485
  runtime?.refreshPolicy?.(binding, { ...policy, mode });
484
486
  }
485
487
 
@@ -58,6 +58,7 @@ function slackPublicationClientMsgId(publicationId: string): string {
58
58
 
59
59
  // Durable filesystem publication leases must outlast one event-loop and persistence turn.
60
60
  const MIN_PUBLICATION_LEASE_MS = 100;
61
+ const ROOT_PUBLICATION_POLL_MS = 50;
61
62
  const INBOUND_REACTION_DEADLINE_MS = 5_000;
62
63
  const RETIREMENT_DRAIN_MS = 5_000;
63
64
  const INBOUND_REACTION = "eyes";
@@ -800,6 +801,7 @@ export class SlackNotificationDaemon {
800
801
  endpointGeneration?: number,
801
802
  requestedClientMsgId?: string,
802
803
  ): Promise<SlackRootPublication> {
804
+ const lifecycleGeneration = this.#lifecycleGeneration;
803
805
  const endpoint = await this.#resolveAttachment(sessionId);
804
806
  if (!endpoint || (endpointGeneration !== undefined && endpoint.generation !== endpointGeneration))
805
807
  throw new SlackEndpointBindingError("Slack root publication requires the current session endpoint.");
@@ -816,7 +818,14 @@ export class SlackNotificationDaemon {
816
818
  )
817
819
  return current;
818
820
  if (current?.state === "posting_root") {
819
- if (current.rootPublicationOwner === this.#publicationOwnerId) return current;
821
+ // A live lease of our own belongs to an in-flight post; an expired one is
822
+ // reclaimed under a new fence exactly like a crashed peer's, otherwise the
823
+ // root waiter would re-enter postRoot forever.
824
+ if (
825
+ current.rootPublicationOwner === this.#publicationOwnerId &&
826
+ !this.#leaseExpired(current.rootPublicationLeaseExpiresAt, now)
827
+ )
828
+ return current;
820
829
  claimed = true;
821
830
  return nextRecord(current, {
822
831
  rootPublicationOwner: this.#publicationOwnerId,
@@ -859,7 +868,17 @@ export class SlackNotificationDaemon {
859
868
  }
860
869
  if (!pending?.clientMsgId) throw new Error("Unable to persist Slack root post intent");
861
870
  if (!claimed)
862
- return { conversation: await this.#waitForRoot(pendingKey, sessionId, body, generation), created: false };
871
+ return {
872
+ conversation: await this.#waitForRoot(
873
+ pendingKey,
874
+ sessionId,
875
+ body,
876
+ generation,
877
+ lifecycleGeneration,
878
+ endpoint.authorityId,
879
+ ),
880
+ created: false,
881
+ };
863
882
  if (pending.endpointGeneration !== generation) throw new SlackEndpointBindingError();
864
883
  const clientMsgId = pending.clientMsgId;
865
884
  const fence = pending.rootPublicationFence;
@@ -907,7 +926,7 @@ export class SlackNotificationDaemon {
907
926
  // dispatch; resume performs any required replacement after reconciliation.
908
927
  const active = await this.store.transact(pendingKey, current => {
909
928
  if (
910
- !current ||
929
+ current?.state !== "posting_root" ||
911
930
  current.clientMsgId !== clientMsgId ||
912
931
  current.rootPublicationOwner !== this.#publicationOwnerId ||
913
932
  current.rootPublicationFence !== fence
@@ -926,7 +945,17 @@ export class SlackNotificationDaemon {
926
945
  });
927
946
  if (!active) throw new Error("Slack root mapping disappeared");
928
947
  if (active.state !== "active")
929
- return { conversation: await this.#waitForRoot(pendingKey, sessionId, body, generation), created: false };
948
+ return {
949
+ conversation: await this.#waitForRoot(
950
+ pendingKey,
951
+ sessionId,
952
+ body,
953
+ generation,
954
+ lifecycleGeneration,
955
+ endpoint.authorityId,
956
+ ),
957
+ created: false,
958
+ };
930
959
  return { conversation: active, created: createdRootEffect };
931
960
  }
932
961
 
@@ -1267,8 +1296,12 @@ export class SlackNotificationDaemon {
1267
1296
  )
1268
1297
  return record;
1269
1298
  inboundDispatches = [...(record.inboundDispatches ?? [])];
1299
+ // Fence the publisher before an abort-ignoring provider can renew or reactivate this mapping.
1270
1300
  return nextRecord(record, {
1271
1301
  state: "closed_marker",
1302
+ rootPublicationOwner: undefined,
1303
+ rootPublicationLeaseExpiresAt: undefined,
1304
+ rootPublicationFence: (record.rootPublicationFence ?? 0) + 1,
1272
1305
  pendingActionId: undefined,
1273
1306
  cleanupEffectId: undefined,
1274
1307
  inboundDispatches: [],
@@ -3105,7 +3138,7 @@ export class SlackNotificationDaemon {
3105
3138
  let renewed = false;
3106
3139
  await this.store.transact(key, current => {
3107
3140
  if (
3108
- !current ||
3141
+ current?.state !== "posting_root" ||
3109
3142
  current.clientMsgId !== clientMsgId ||
3110
3143
  current.rootPublicationOwner !== this.#publicationOwnerId ||
3111
3144
  current.rootPublicationFence !== fence
@@ -3269,18 +3302,82 @@ export class SlackNotificationDaemon {
3269
3302
  sessionId: string,
3270
3303
  body: string,
3271
3304
  endpointGeneration: number,
3305
+ lifecycleGeneration: number,
3306
+ attachmentAuthorityId?: string,
3272
3307
  ): Promise<SlackConversation> {
3273
- for (let attempt = 0; attempt < 100; attempt++) {
3274
- await Bun.sleep(10);
3308
+ // Another owner may renew this lease while provider work is active, so wait for the durable transition.
3309
+ while (true) {
3310
+ if (lifecycleGeneration !== this.#lifecycleGeneration)
3311
+ throw new Error("Slack daemon stopped while waiting for root publication");
3312
+ await Bun.sleep(ROOT_PUBLICATION_POLL_MS);
3313
+ if (lifecycleGeneration !== this.#lifecycleGeneration)
3314
+ throw new Error("Slack daemon stopped while waiting for root publication");
3275
3315
  const current = await this.store.read(key);
3316
+ if (lifecycleGeneration !== this.#lifecycleGeneration)
3317
+ throw new Error("Slack daemon stopped while waiting for root publication");
3276
3318
  if (current?.state === "active") {
3277
3319
  const currentGeneration = this.#requireEndpointGeneration(current);
3278
- if (currentGeneration === endpointGeneration) return current;
3320
+ if (currentGeneration === endpointGeneration) {
3321
+ const endpoint = await this.#resolveAttachment(sessionId);
3322
+ if (lifecycleGeneration !== this.#lifecycleGeneration)
3323
+ throw new Error("Slack daemon stopped while waiting for root publication");
3324
+ if (
3325
+ !endpoint?.isCurrent() ||
3326
+ endpoint.sessionId !== sessionId ||
3327
+ endpoint.generation !== endpointGeneration ||
3328
+ endpoint.authorityId !== attachmentAuthorityId ||
3329
+ current.attachmentAuthorityId !== attachmentAuthorityId
3330
+ )
3331
+ throw new SlackEndpointBindingError("Slack root publication was retired before completion.");
3332
+ return current;
3333
+ }
3279
3334
  if (currentGeneration > endpointGeneration)
3280
3335
  throw new SlackEndpointBindingError("Slack root belongs to a newer endpoint generation.");
3281
3336
  return await this.resume(sessionId, body, endpointGeneration);
3282
3337
  }
3338
+ if (current?.state === "closed_marker") {
3339
+ const currentGeneration = this.#requireEndpointGeneration(current);
3340
+ if (currentGeneration > endpointGeneration)
3341
+ throw new SlackEndpointBindingError("Slack root belongs to a newer endpoint generation.");
3342
+ const endpoint = await this.#resolveAttachment(sessionId);
3343
+ if (lifecycleGeneration !== this.#lifecycleGeneration)
3344
+ throw new Error("Slack daemon stopped while waiting for root publication");
3345
+ if (
3346
+ !endpoint?.isCurrent() ||
3347
+ endpoint.sessionId !== sessionId ||
3348
+ endpoint.generation !== endpointGeneration ||
3349
+ endpoint.authorityId !== attachmentAuthorityId
3350
+ )
3351
+ throw new SlackEndpointBindingError("Slack root publication was retired before completion.");
3352
+ if (currentGeneration === endpointGeneration) {
3353
+ if (!current.rootTs)
3354
+ throw new SlackEndpointBindingError("Slack root publication was retired before completion.");
3355
+ if (
3356
+ current.rootPublicationOwner !== undefined &&
3357
+ !this.#leaseExpired(current.rootPublicationLeaseExpiresAt, this.#now())
3358
+ )
3359
+ continue;
3360
+ return await this.postRoot(sessionId, body, endpointGeneration);
3361
+ }
3362
+ if (
3363
+ current.rootPublicationOwner !== undefined &&
3364
+ !this.#leaseExpired(current.rootPublicationLeaseExpiresAt, this.#now())
3365
+ )
3366
+ continue;
3367
+ return await this.resume(sessionId, body, endpointGeneration);
3368
+ }
3369
+ if (current?.state === "resumed_root") {
3370
+ const currentGeneration = this.#requireEndpointGeneration(current);
3371
+ if (currentGeneration > endpointGeneration)
3372
+ throw new SlackEndpointBindingError("Slack root belongs to a newer endpoint generation.");
3373
+ if (
3374
+ current.rootPublicationOwner === undefined ||
3375
+ this.#leaseExpired(current.rootPublicationLeaseExpiresAt, this.#now())
3376
+ )
3377
+ return await this.resume(sessionId, body, endpointGeneration);
3378
+ }
3283
3379
  if (
3380
+ current === undefined ||
3284
3381
  current?.state === "error" ||
3285
3382
  (current?.state === "posting_root" &&
3286
3383
  this.#leaseExpired(current.rootPublicationLeaseExpiresAt, this.#now())) ||
@@ -3291,7 +3388,6 @@ export class SlackNotificationDaemon {
3291
3388
  return await this.postRoot(sessionId, body, endpointGeneration);
3292
3389
  }
3293
3390
  }
3294
- throw new Error("Slack root post is still pending");
3295
3391
  }
3296
3392
 
3297
3393
  async #waitForAction(key: string, sessionId: string, body: string, actionId: string): Promise<SlackConversation> {
@@ -303,8 +303,14 @@ export const SDK_LIFECYCLE_ROUTER_PROTOCOL_VERSION = 1;
303
303
  * before its terminal record is durable.
304
304
  * Generation 203 replaces owners that still treat a project `.gjc` file as an
305
305
  * explicit deadline-autosave opt-in for a primary checkout.
306
+ * Generation 205 replaces owners whose process descendant walk can adopt
307
+ * children of a recycled PID. Generation 206 verifies macOS descendant
308
+ * identities and parentage before retaining PTY termination authority, and
309
+ * reaps adopted Linux PTY descendants when GJC is a child reaper.
310
+ * Generation 209 installs connection-owned private SDK image staging with
311
+ * execution-owned reservations, replacing hosts without that lifecycle contract.
306
312
  */
307
- export const DAEMON_GENERATION = 204;
313
+ export const DAEMON_GENERATION = 209;
308
314
 
309
315
  /**
310
316
  * Serving-compatibility boundary for daemon lifecycle requests. Epoch 7
@@ -302,6 +302,8 @@ export const ASK_SELECTED_ACK_CAPABILITY = "ask_selected_ack_v1";
302
302
  export const ASK_CONTROLS_CAPABILITY = "ask_controls_v1";
303
303
  /** Capability for the closed tool phase set: started, completed, failed, and cancelled. */
304
304
  export const TOOL_ACTIVITY_CAPABILITY = "tool_activity_v2";
305
+ /** Receive content-free notifications when a running command needs host-side authentication. */
306
+ export const AWAITING_INPUT_CAPABILITY = "awaiting_input_v1";
305
307
  /** Receive-only compatibility capability for pre-v2 hosts. */
306
308
  export const LEGACY_TOOL_ACTIVITY_CAPABILITY = "tool_activity_v1";
307
309
  /** Public `sdk/bus/telegram-daemon` path for the canonical `sdk/host` capability token. */
@@ -5428,6 +5430,7 @@ export class TelegramNotificationDaemon {
5428
5430
  ASK_CONTROLS_CAPABILITY,
5429
5431
  ASK_SELECTED_ACK_CAPABILITY,
5430
5432
  TOOL_ACTIVITY_CAPABILITY,
5433
+ AWAITING_INPUT_CAPABILITY,
5431
5434
  LEGACY_TOOL_ACTIVITY_CAPABILITY,
5432
5435
  POSITIONED_NOTIFICATION_EFFECTS_CAPABILITY,
5433
5436
  "ephemeral_turn_v1",
@@ -7255,6 +7258,7 @@ export class TelegramNotificationDaemon {
7255
7258
  "config_update",
7256
7259
  "control_command_result",
7257
7260
  "tool_activity",
7261
+ "awaiting_input",
7258
7262
  "reasoning_summary",
7259
7263
  ]);
7260
7264
 
@@ -10874,8 +10878,12 @@ export class TelegramNotificationDaemon {
10874
10878
  this.failLegacyToolStart(toolActivity);
10875
10879
  await this.#failPublicationPreSend(publicationId, "topic authority is inactive or malformed");
10876
10880
  }
10881
+ const flatSend =
10882
+ threadedFrame.type === "awaiting_input"
10883
+ ? { ...send, text: `${send.text} · ${sessionTag(logicalSessionId)}` }
10884
+ : send;
10877
10885
  if (!send.identity && !existingTopic && !this.flatIdentitySent.has(logicalSessionId)) {
10878
- await this.deliverFlatFallback(logicalSessionId, send, toolActivity, socketLease, publicationId);
10886
+ await this.deliverFlatFallback(logicalSessionId, flatSend, toolActivity, socketLease, publicationId);
10879
10887
  return;
10880
10888
  }
10881
10889
  const topicId =
@@ -10896,7 +10904,7 @@ export class TelegramNotificationDaemon {
10896
10904
  abandonStaleToolStart();
10897
10905
  return;
10898
10906
  }
10899
- await this.deliverFlatFallback(logicalSessionId, send, toolActivity, socketLease, publicationId);
10907
+ await this.deliverFlatFallback(logicalSessionId, flatSend, toolActivity, socketLease, publicationId);
10900
10908
  return;
10901
10909
  }
10902
10910
  if (send.identity) {
@@ -2,7 +2,7 @@
2
2
  * Pure rendering of threaded-session frames into Telegram send specs.
3
3
  *
4
4
  * The daemon receives the additive `ServerMessage` frames (identity_header,
5
- * context_update, turn_stream, image_attachment, config_update) over the session
5
+ * context_update, turn_stream, image_attachment, config_update, awaiting_input) over the session
6
6
  * WS and must turn each into a Bot API call scoped to the session's forum topic
7
7
  * (`message_thread_id`), throttled through the shared rate-limit pool. This
8
8
  * module is the pure frame→send mapping (including the priority lane and live-
@@ -80,6 +80,7 @@ interface ThreadedFrame {
80
80
  // tool_activity
81
81
  toolCallId?: unknown;
82
82
  toolName?: unknown;
83
+ kind?: unknown;
83
84
  argsSummary?: unknown;
84
85
  resultSummary?: unknown;
85
86
  isError?: unknown;
@@ -261,6 +262,24 @@ export function renderThreadedFrame(frame: ThreadedFrame): ThreadedSend | undefi
261
262
  terminal: phase !== "started",
262
263
  };
263
264
  }
265
+ case "awaiting_input": {
266
+ const toolCallId = str(frame.toolCallId);
267
+ if (!toolCallId) return undefined;
268
+ let text: string;
269
+ switch (frame.kind) {
270
+ case "browser_auth":
271
+ text =
272
+ "⏳ A running command is waiting for browser sign-in. Finish it on the host; the agent continues when the command does.";
273
+ break;
274
+ case "device_code":
275
+ text =
276
+ "⏳ A running command is waiting for a device-code sign-in. Finish it on the host; the agent continues when the command does.";
277
+ break;
278
+ default:
279
+ return undefined;
280
+ }
281
+ return { method: "sendMessage", lane: "ask", text, coalesceKey: `awaiting:${toolCallId}` };
282
+ }
264
283
  case "reasoning_summary": {
265
284
  const text = str(frame.text);
266
285
  if (!text) return undefined;
@@ -279,6 +279,8 @@ export function lifecyclePublicFailure(outcome: Extract<SessionLifecycleResult,
279
279
  let diagnostic: PublicCommandDiagnosticCode | undefined;
280
280
  if (outcome.error.code === "idempotency_conflict") {
281
281
  diagnostic = "lifecycle_idempotency_conflict";
282
+ } else if (outcome.error.code === "legacy_lifecycle_ambiguous") {
283
+ diagnostic = "lifecycle_legacy_history_ambiguous";
282
284
  } else if (LIFECYCLE_INPUT_REFUSAL_CODES.has(outcome.error.code)) {
283
285
  diagnostic = "lifecycle_input_refused";
284
286
  }
@@ -256,7 +256,19 @@ function invoke(
256
256
  ): Promise<ControlValue> | ControlValue {
257
257
  switch (operation) {
258
258
  case "turn.prompt":
259
- return surface.prompt(text(input), input.images, input.clientRef as string | undefined);
259
+ return surface.prompt(text(input), input.images, input.clientRef as string | undefined, input.stagedImages);
260
+ case "turn.image.begin":
261
+ if (!surface.imageBegin) invalidInput("Image uploads are not installed for this session.");
262
+ return surface.imageBegin(input);
263
+ case "turn.image.append":
264
+ if (!surface.imageAppend) invalidInput("Image uploads are not installed for this session.");
265
+ return surface.imageAppend(input);
266
+ case "turn.image.finish":
267
+ if (!surface.imageFinish) invalidInput("Image uploads are not installed for this session.");
268
+ return surface.imageFinish(input);
269
+ case "turn.image.discard":
270
+ if (!surface.imageDiscard) invalidInput("Image uploads are not installed for this session.");
271
+ return surface.imageDiscard(input);
260
272
  case "turn.steer":
261
273
  return surface.steer(
262
274
  text(input),
@@ -21,7 +21,16 @@ export type ControlInput = Record<string, unknown>;
21
21
  * AgentSession and its controllers without exposing those concrete types here.
22
22
  */
23
23
  export interface ControlSurface {
24
- prompt(text: string, images?: ControlValue, clientRef?: string): Promise<ControlValue> | ControlValue;
24
+ prompt(
25
+ text: string,
26
+ images?: ControlValue,
27
+ clientRef?: string,
28
+ stagedImages?: ControlValue,
29
+ ): Promise<ControlValue> | ControlValue;
30
+ imageBegin?(input: ControlValue): Promise<ControlValue> | ControlValue;
31
+ imageAppend?(input: ControlValue): Promise<ControlValue> | ControlValue;
32
+ imageFinish?(input: ControlValue): Promise<ControlValue> | ControlValue;
33
+ imageDiscard?(input: ControlValue): Promise<ControlValue> | ControlValue;
25
34
  steer(text: string, clientRef?: string, expectedSdkRunToken?: string): Promise<ControlValue> | ControlValue;
26
35
  followUp(text: string): Promise<ControlValue> | ControlValue;
27
36
  abort(): Promise<ControlValue> | ControlValue;
@@ -26,10 +26,37 @@ export interface EventReplay {
26
26
  gap?: EventReplayGap;
27
27
  }
28
28
 
29
+ /** Kinds emit sites suppress once notification redaction is on. */
30
+ const REDACTED_REPLAY_KINDS: ReadonlySet<string> = new Set([
31
+ "turn_stream",
32
+ "context_update",
33
+ "image_attachment",
34
+ "file_attachment",
35
+ "reasoning_summary",
36
+ "tool_activity",
37
+ ]);
38
+
39
+ const outboundRedactBySession = new Map<string, boolean>();
40
+
41
+ /** Notification policy refresh records the live redaction bit for one session ring. */
42
+ export function setSessionOutboundRedact(sessionId: string, enabled: boolean): void {
43
+ outboundRedactBySession.set(sessionId, enabled);
44
+ }
45
+
46
+ function frameSessionId(frame: SdkFrame): string | undefined {
47
+ if (typeof frame.sessionId === "string") return frame.sessionId;
48
+ const payload = frame.payload;
49
+ if (!payload || typeof payload !== "object") return undefined;
50
+ const sessionId = (payload as { sessionId?: unknown }).sessionId;
51
+ return typeof sessionId === "string" ? sessionId : undefined;
52
+ }
53
+
29
54
  export class SessionEventStream {
30
55
  #generation = 0;
31
56
  #seq = 0;
32
57
  #frames: EventFrame[] = [];
58
+ #outboundRedact = false;
59
+ #sessionId: string | undefined;
33
60
  readonly #ringSize: number;
34
61
  readonly #resyncQueries: string[];
35
62
  readonly #revisionProvider: (() => number | undefined) | undefined;
@@ -55,6 +82,12 @@ export class SessionEventStream {
55
82
  return this.#seq;
56
83
  }
57
84
 
85
+ /** Follow the session redaction bit. Replay then drops kinds emit sites already suppress. */
86
+ setOutboundRedact(enabled: boolean): void {
87
+ this.#outboundRedact = enabled;
88
+ if (this.#sessionId !== undefined) outboundRedactBySession.set(this.#sessionId, enabled);
89
+ }
90
+
58
91
  restart(): number {
59
92
  this.#generation += 1;
60
93
  this.#seq = 0;
@@ -63,6 +96,8 @@ export class SessionEventStream {
63
96
  }
64
97
 
65
98
  emit(frame: SdkFrame): EventFrame {
99
+ this.#noteSession(frame);
100
+ this.#noteConfigRedact(frame);
66
101
  const suppliedRevision = frame.revision;
67
102
  const providedRevision = this.#revisionProvider?.();
68
103
  const revision =
@@ -85,10 +120,9 @@ export class SessionEventStream {
85
120
 
86
121
  /** Replays retained current-generation events and reports any required resynchronization. */
87
122
  replay(lastSeq: number, generation = this.#generation): EventReplay {
88
- const events = this.#frames.filter(frame => frame.seq > lastSeq);
89
123
  if (generation !== this.#generation) {
90
124
  return {
91
- events: [...this.#frames],
125
+ events: this.#visible(this.#frames),
92
126
  gap: {
93
127
  kind: "generation_reset",
94
128
  fromGeneration: generation,
@@ -101,7 +135,7 @@ export class SessionEventStream {
101
135
  const replayFrom = lastSeq + 1;
102
136
  if (replayFrom < oldest) {
103
137
  return {
104
- events: this.#frames.filter(frame => frame.seq >= oldest),
138
+ events: this.#visible(this.#frames.filter(frame => frame.seq >= oldest)),
105
139
  gap: {
106
140
  kind: "sequence_gap",
107
141
  fromSeq: replayFrom,
@@ -110,6 +144,35 @@ export class SessionEventStream {
110
144
  },
111
145
  };
112
146
  }
113
- return { events };
147
+ return { events: this.#visible(this.#frames.filter(frame => frame.seq > lastSeq)) };
148
+ }
149
+
150
+ #noteSession(frame: SdkFrame): void {
151
+ if (this.#sessionId !== undefined) return;
152
+ this.#sessionId = frameSessionId(frame);
153
+ }
154
+
155
+ #noteConfigRedact(frame: SdkFrame): void {
156
+ if (frame.kind !== "config_update") return;
157
+ const payload = frame.payload;
158
+ if (!payload || typeof payload !== "object") return;
159
+ const redact = (payload as { redact?: unknown }).redact;
160
+ if (typeof redact !== "boolean") return;
161
+ this.setOutboundRedact(redact);
162
+ }
163
+
164
+ #replayRedact(): boolean {
165
+ if (this.#sessionId !== undefined) {
166
+ const noted = outboundRedactBySession.get(this.#sessionId);
167
+ if (noted !== undefined) return noted;
168
+ }
169
+ return this.#outboundRedact;
170
+ }
171
+
172
+ #visible(frames: readonly EventFrame[]): EventFrame[] {
173
+ const retained = this.#replayRedact()
174
+ ? frames.filter(frame => !REDACTED_REPLAY_KINDS.has(String(frame.kind)))
175
+ : frames;
176
+ return [...retained];
114
177
  }
115
178
  }
@@ -103,6 +103,7 @@ export interface SessionSdkHostOptions extends HostEndpointAdapters {
103
103
  * must see the same capability-gated event kinds on both legs, or live and
104
104
  * replay delivery diverge for the same subscriber. */
105
105
  export const TOOL_ACTIVITY_CAPABILITY = "tool_activity_v2";
106
+ export const AWAITING_INPUT_CAPABILITY = "awaiting_input_v1";
106
107
  /** Capability for non-replayable streamed turn content. */
107
108
  export const TURN_STREAM_CAPABILITY = "turn_stream";
108
109
  /** Capability used by notification adapters that observe a host without owning its work. */
@@ -113,12 +114,17 @@ export const SESSION_HOST_OBSERVER_CAPABILITY = "session_host_observer_v1";
113
114
  * of every frame its positioned envelope already reached.
114
115
  */
115
116
  export const POSITIONED_NOTIFICATION_EFFECTS_CAPABILITY = "positioned_notification_effects_v1";
116
- export const CAP_GATED_FRAME_KINDS: ReadonlySet<string> = new Set(["tool_activity", "reasoning_summary"]);
117
+ export const CAP_GATED_FRAME_KINDS: ReadonlyMap<string, string> = new Map([
118
+ ["tool_activity", TOOL_ACTIVITY_CAPABILITY],
119
+ ["reasoning_summary", TOOL_ACTIVITY_CAPABILITY],
120
+ ["awaiting_input", AWAITING_INPUT_CAPABILITY],
121
+ ]);
117
122
  const EMPTY_CAPABILITIES: ReadonlySet<string> = new Set();
118
123
 
119
124
  /** Keep live delivery and replay on one capability gate. */
120
125
  export function canDeliverSdkEvent(kind: string, capabilities: ReadonlySet<string> | undefined): boolean {
121
- return !CAP_GATED_FRAME_KINDS.has(kind) || capabilities?.has(TOOL_ACTIVITY_CAPABILITY) === true;
126
+ const requiredCapability = CAP_GATED_FRAME_KINDS.get(kind);
127
+ return requiredCapability === undefined || capabilities?.has(requiredCapability) === true;
122
128
  }
123
129
 
124
130
  /** Safe, identifier-free explanations for every refused activation status. */