@gajae-code/coding-agent 0.15.6 → 0.16.1

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 (270) hide show
  1. package/CHANGELOG.md +99 -0
  2. package/dist/types/async/job-manager.d.ts +32 -1
  3. package/dist/types/browser-backend.d.ts +12 -0
  4. package/dist/types/cli/args.d.ts +2 -0
  5. package/dist/types/cli/root-flags.d.ts +14 -0
  6. package/dist/types/cli/update-cli.d.ts +14 -0
  7. package/dist/types/cli.d.ts +7 -0
  8. package/dist/types/commands/launch.d.ts +10 -0
  9. package/dist/types/commands/sdk.d.ts +2 -0
  10. package/dist/types/config/autorouting-tier-map.d.ts +24 -0
  11. package/dist/types/config/billing-path.d.ts +25 -0
  12. package/dist/types/config/config-schema-version.d.ts +13 -1
  13. package/dist/types/config/file-lock.d.ts +5 -0
  14. package/dist/types/config/model-registry.d.ts +22 -0
  15. package/dist/types/config/settings-schema.d.ts +27 -8
  16. package/dist/types/config/settings.d.ts +2 -0
  17. package/dist/types/coordinator-mcp/projection-scan.d.ts +8 -0
  18. package/dist/types/crash/record-loader.d.ts +15 -0
  19. package/dist/types/extensibility/extensions/types.d.ts +8 -3
  20. package/dist/types/gjc-runtime/launch-worktree-reservation.d.ts +21 -0
  21. package/dist/types/gjc-runtime/launch-worktree.d.ts +25 -1
  22. package/dist/types/gjc-runtime/managed-owner-supervisor.d.ts +2 -0
  23. package/dist/types/gjc-runtime/session-state-lock.d.ts +22 -0
  24. package/dist/types/gjc-runtime/session-state-sidecar.d.ts +66 -1
  25. package/dist/types/gjc-runtime/tmux-owner-isolation.d.ts +1 -0
  26. package/dist/types/gjc-runtime/tmux-sessions.d.ts +27 -0
  27. package/dist/types/main.d.ts +1 -0
  28. package/dist/types/master-mode/context.d.ts +21 -0
  29. package/dist/types/master-mode/first-request.d.ts +34 -0
  30. package/dist/types/master-mode/snapshot.d.ts +32 -0
  31. package/dist/types/modes/acp/acp-agent.d.ts +21 -21
  32. package/dist/types/modes/components/frictionless-onboarding-selector.d.ts +10 -0
  33. package/dist/types/modes/components/model-selector.d.ts +4 -0
  34. package/dist/types/modes/components/oauth-selector.d.ts +1 -0
  35. package/dist/types/modes/components/session-selector.d.ts +1 -1
  36. package/dist/types/modes/components/sessions-dashboard.d.ts +2 -1
  37. package/dist/types/modes/components/status-line/model-name.d.ts +18 -0
  38. package/dist/types/modes/components/status-line/priority-row.d.ts +34 -0
  39. package/dist/types/modes/components/status-line/segments.d.ts +10 -0
  40. package/dist/types/modes/interactive-mode.d.ts +16 -2
  41. package/dist/types/modes/print-mode.d.ts +17 -0
  42. package/dist/types/modes/theme/theme.d.ts +2 -1
  43. package/dist/types/modes/types.d.ts +2 -0
  44. package/dist/types/modes/ui-language.d.ts +7 -1
  45. package/dist/types/sdk/broker/broker.d.ts +79 -2
  46. package/dist/types/sdk/broker/endpoint-authority.d.ts +24 -0
  47. package/dist/types/sdk/broker/ensure.d.ts +3 -0
  48. package/dist/types/sdk/broker/index.d.ts +3 -0
  49. package/dist/types/sdk/broker/lifecycle-ledger.d.ts +6 -0
  50. package/dist/types/sdk/broker/lifecycle.d.ts +40 -0
  51. package/dist/types/sdk/broker/master-capability.d.ts +10 -0
  52. package/dist/types/sdk/broker/session-index.d.ts +40 -11
  53. package/dist/types/sdk/broker/session-scope.d.ts +72 -0
  54. package/dist/types/sdk/broker/spawn-authority.d.ts +201 -0
  55. package/dist/types/sdk/broker/spawn-substrate.d.ts +30 -0
  56. package/dist/types/sdk/broker/startup-budget.d.ts +52 -0
  57. package/dist/types/sdk/broker/state-version.d.ts +21 -1
  58. package/dist/types/sdk/broker/worktree-occupancy.d.ts +28 -0
  59. package/dist/types/sdk/bus/chat-daemon-control.d.ts +3 -0
  60. package/dist/types/sdk/bus/discord-daemon.d.ts +1 -1
  61. package/dist/types/sdk/bus/index.d.ts +9 -1
  62. package/dist/types/sdk/bus/kind-aware-reconciliation.d.ts +1 -1
  63. package/dist/types/sdk/bus/prompt-reconciliation.d.ts +1 -1
  64. package/dist/types/sdk/bus/slack-conversation.d.ts +1 -1
  65. package/dist/types/sdk/bus/slack-daemon.d.ts +1 -1
  66. package/dist/types/sdk/bus/telegram-daemon-contract.d.ts +1 -1
  67. package/dist/types/sdk/cli/index.d.ts +1 -0
  68. package/dist/types/sdk/cli/master-cli.d.ts +63 -0
  69. package/dist/types/sdk/cli/rows.d.ts +12 -5
  70. package/dist/types/sdk/cli/session-cli.d.ts +20 -3
  71. package/dist/types/sdk/client/client.d.ts +3 -2
  72. package/dist/types/sdk/host/control/dispatch.d.ts +10 -0
  73. package/dist/types/sdk/host/control/index.d.ts +1 -1
  74. package/dist/types/sdk/host/control/operations.d.ts +2 -0
  75. package/dist/types/sdk/host/control/runtime-gate.d.ts +21 -7
  76. package/dist/types/sdk/host/events.d.ts +1 -0
  77. package/dist/types/sdk/host/host.d.ts +10 -0
  78. package/dist/types/sdk/host/query/cursor.d.ts +4 -2
  79. package/dist/types/sdk/host/query/handlers.d.ts +1 -0
  80. package/dist/types/sdk/host/query/revision-store.d.ts +1 -0
  81. package/dist/types/sdk/host/session-runtime.d.ts +70 -17
  82. package/dist/types/sdk/lifecycle/broker-client.d.ts +6 -0
  83. package/dist/types/sdk/lifecycle/service.d.ts +15 -3
  84. package/dist/types/sdk/lifecycle-session.d.ts +3 -1
  85. package/dist/types/sdk/prompt-deadline-manager.d.ts +2 -5
  86. package/dist/types/sdk/receipt-state.d.ts +2 -0
  87. package/dist/types/sdk/router/session-router.d.ts +6 -0
  88. package/dist/types/sdk/session.d.ts +16 -0
  89. package/dist/types/sdk/startup-capability.d.ts +2 -1
  90. package/dist/types/sdk/turn-result.d.ts +6 -0
  91. package/dist/types/session/agent-session.d.ts +52 -7
  92. package/dist/types/session/credential-store-errors.d.ts +1 -0
  93. package/dist/types/session/fold-coordinator.d.ts +29 -2
  94. package/dist/types/session/session-manager.d.ts +79 -2
  95. package/dist/types/session/yield-queue.d.ts +5 -4
  96. package/dist/types/setup/frictionless-onboarding.d.ts +1 -0
  97. package/dist/types/tools/ask.d.ts +1 -0
  98. package/dist/types/tools/bash.d.ts +16 -0
  99. package/dist/types/tools/browser/launch.d.ts +10 -0
  100. package/dist/types/tools/browser/registry.d.ts +8 -0
  101. package/dist/types/tools/gh.d.ts +8 -0
  102. package/dist/types/tools/index.d.ts +10 -10
  103. package/dist/types/tools/job.d.ts +3 -1
  104. package/dist/types/utils/external-editor.d.ts +7 -0
  105. package/examples/extensions/README.md +1 -1
  106. package/examples/extensions/pirate.ts +7 -4
  107. package/package.json +7 -7
  108. package/scripts/generate-sdk-operation-inventory.ts +13 -1
  109. package/scripts/verify-gjc-sdk-canonicalization.ts +35 -1
  110. package/src/async/job-manager.ts +107 -9
  111. package/src/browser-backend.ts +29 -0
  112. package/src/cli/accounts-cli.ts +9 -5
  113. package/src/cli/args.ts +17 -0
  114. package/src/cli/fast-help.ts +1 -0
  115. package/src/cli/plugin-cli.ts +29 -0
  116. package/src/cli/root-flags.ts +5 -0
  117. package/src/cli/update-cli.ts +140 -23
  118. package/src/commands/launch.ts +86 -18
  119. package/src/commands/sdk.ts +135 -28
  120. package/src/config/autorouting-tier-map.ts +12 -0
  121. package/src/config/billing-path.ts +42 -0
  122. package/src/config/config-schema-version.ts +18 -1
  123. package/src/config/file-lock.ts +158 -83
  124. package/src/config/model-preset-registry.ts +178 -23
  125. package/src/config/model-profiles.ts +5 -5
  126. package/src/config/model-registry.ts +291 -42
  127. package/src/config/model-resolver.ts +5 -0
  128. package/src/config/settings-schema.ts +27 -8
  129. package/src/config/settings.ts +51 -15
  130. package/src/coordinator-mcp/projection-scan.ts +27 -6
  131. package/src/coordinator-mcp/question-state.ts +9 -11
  132. package/src/coordinator-mcp/server.ts +115 -21
  133. package/src/crash/index-store.ts +2 -0
  134. package/src/crash/record-loader.ts +10 -0
  135. package/src/defaults/gjc/skills/deep-interview/SKILL.md +1 -1
  136. package/src/extensibility/extensions/runner.ts +27 -0
  137. package/src/extensibility/extensions/types.ts +8 -3
  138. package/src/extensibility/gjc-plugins/installer.ts +51 -11
  139. package/src/extensibility/runtime-skill-discovery.ts +110 -3
  140. package/src/extensibility/skills.ts +22 -12
  141. package/src/gjc-runtime/launch-worktree-reservation.ts +242 -0
  142. package/src/gjc-runtime/launch-worktree.ts +285 -4
  143. package/src/gjc-runtime/managed-owner-supervisor.ts +63 -51
  144. package/src/gjc-runtime/session-state-lock.ts +213 -14
  145. package/src/gjc-runtime/session-state-sidecar.ts +938 -39
  146. package/src/gjc-runtime/tmux-owner-isolation-cli.ts +4 -7
  147. package/src/gjc-runtime/tmux-owner-isolation.ts +18 -3
  148. package/src/gjc-runtime/tmux-sessions.ts +252 -13
  149. package/src/hooks/skill-state.ts +6 -0
  150. package/src/internal-urls/docs-index.generated.ts +15 -15
  151. package/src/internal-urls/issue-pr-protocol.ts +4 -2
  152. package/src/main.ts +61 -3
  153. package/src/master-mode/context.ts +50 -0
  154. package/src/master-mode/first-request.ts +65 -0
  155. package/src/master-mode/snapshot.ts +205 -0
  156. package/src/modes/acp/acp-agent.ts +610 -161
  157. package/src/modes/components/footer.ts +4 -2
  158. package/src/modes/components/frictionless-onboarding-selector.ts +48 -2
  159. package/src/modes/components/model-selector.ts +76 -8
  160. package/src/modes/components/oauth-selector.ts +6 -1
  161. package/src/modes/components/session-selector.ts +23 -15
  162. package/src/modes/components/sessions-dashboard.ts +8 -5
  163. package/src/modes/components/settings-selector.ts +1 -0
  164. package/src/modes/components/status-line/model-name.ts +53 -0
  165. package/src/modes/components/status-line/priority-row.ts +107 -0
  166. package/src/modes/components/status-line/segments.ts +23 -9
  167. package/src/modes/components/tool-status-header.ts +151 -15
  168. package/src/modes/controllers/command-controller.ts +1 -1
  169. package/src/modes/controllers/event-controller.ts +30 -4
  170. package/src/modes/controllers/extension-ui-controller.ts +13 -13
  171. package/src/modes/controllers/input-controller.ts +30 -2
  172. package/src/modes/controllers/selector-controller.ts +200 -98
  173. package/src/modes/interactive-mode.ts +36 -12
  174. package/src/modes/print-mode.ts +64 -0
  175. package/src/modes/prompt-action-autocomplete.ts +30 -4
  176. package/src/modes/runtime-init.ts +12 -9
  177. package/src/modes/theme/theme.ts +5 -0
  178. package/src/modes/types.ts +2 -0
  179. package/src/modes/ui-language.ts +49 -2
  180. package/src/modes/utils/ui-helpers.ts +22 -3
  181. package/src/prompts/agent-fragments/browser-aside.md +22 -0
  182. package/src/prompts/system/master-mode.md +28 -0
  183. package/src/prompts/tools/ask.md +1 -1
  184. package/src/prompts/tools/bash.md +1 -0
  185. package/src/prompts/tools/job.md +1 -1
  186. package/src/sdk/acp/adapter.ts +5 -0
  187. package/src/sdk/broker/broker.ts +1646 -27
  188. package/src/sdk/broker/endpoint-authority.ts +86 -0
  189. package/src/sdk/broker/ensure.ts +81 -3
  190. package/src/sdk/broker/index.ts +3 -0
  191. package/src/sdk/broker/lifecycle-ledger.ts +6 -0
  192. package/src/sdk/broker/lifecycle.ts +440 -75
  193. package/src/sdk/broker/master-capability.ts +232 -0
  194. package/src/sdk/broker/session-index.ts +121 -17
  195. package/src/sdk/broker/session-scope.ts +272 -0
  196. package/src/sdk/broker/spawn-authority.ts +1031 -0
  197. package/src/sdk/broker/spawn-substrate.ts +591 -0
  198. package/src/sdk/broker/startup-budget.ts +144 -2
  199. package/src/sdk/broker/state-version.ts +43 -14
  200. package/src/sdk/broker/transport.ts +1 -0
  201. package/src/sdk/broker/worktree-occupancy.ts +90 -0
  202. package/src/sdk/bus/chat-daemon-control.ts +5 -2
  203. package/src/sdk/bus/chat-daemon-runtime.ts +136 -18
  204. package/src/sdk/bus/discord-daemon.ts +50 -10
  205. package/src/sdk/bus/index.ts +84 -7
  206. package/src/sdk/bus/kind-aware-reconciliation.ts +296 -48
  207. package/src/sdk/bus/lifecycle-commands.ts +4 -0
  208. package/src/sdk/bus/notification-service.ts +22 -5
  209. package/src/sdk/bus/prompt-reconciliation.ts +7 -2
  210. package/src/sdk/bus/reconciliation-store.ts +55 -31
  211. package/src/sdk/bus/slack-conversation.ts +1 -1
  212. package/src/sdk/bus/slack-daemon.ts +72 -38
  213. package/src/sdk/bus/telegram-daemon-contract.ts +1 -1
  214. package/src/sdk/cli/index.ts +1 -0
  215. package/src/sdk/cli/master-cli.ts +259 -0
  216. package/src/sdk/cli/rows.ts +41 -15
  217. package/src/sdk/cli/session-cli.ts +297 -26
  218. package/src/sdk/client/client.ts +14 -8
  219. package/src/sdk/host/control/dispatch.ts +101 -40
  220. package/src/sdk/host/control/index.ts +2 -0
  221. package/src/sdk/host/control/operations.ts +2 -0
  222. package/src/sdk/host/control/runtime-gate.ts +203 -11
  223. package/src/sdk/host/events.ts +1 -0
  224. package/src/sdk/host/host.ts +29 -2
  225. package/src/sdk/host/query/cursor.ts +52 -7
  226. package/src/sdk/host/query/handlers.ts +33 -12
  227. package/src/sdk/host/query/revision-store.ts +12 -0
  228. package/src/sdk/host/session-runtime.test.ts +673 -14
  229. package/src/sdk/host/session-runtime.ts +674 -103
  230. package/src/sdk/host/surface-policy.ts +3 -3
  231. package/src/sdk/lifecycle/broker-client.ts +28 -0
  232. package/src/sdk/lifecycle/service.ts +279 -20
  233. package/src/sdk/lifecycle-session.ts +17 -4
  234. package/src/sdk/prompt-deadline-manager.ts +2 -1
  235. package/src/sdk/protocol/operation-inventory.generated.json +105 -3
  236. package/src/sdk/protocol/operation-registry.ts +14 -4
  237. package/src/sdk/receipt-state.ts +10 -0
  238. package/src/sdk/router/session-router.ts +219 -118
  239. package/src/sdk/session.ts +416 -195
  240. package/src/sdk/startup-capability.ts +1 -0
  241. package/src/sdk/turn-result.ts +22 -0
  242. package/src/session/agent-session.ts +1628 -318
  243. package/src/session/blob-store.ts +15 -5
  244. package/src/session/credential-store-errors.ts +2 -0
  245. package/src/session/fold-coordinator.ts +40 -3
  246. package/src/session/internal/managed-session-storage.ts +153 -78
  247. package/src/session/session-manager.ts +850 -97
  248. package/src/session/yield-queue.ts +24 -10
  249. package/src/setup/frictionless-onboarding.ts +2 -0
  250. package/src/slash-commands/builtin-registry.ts +41 -0
  251. package/src/task/executor.ts +9 -4
  252. package/src/telemetry/events.ts +1138 -12
  253. package/src/tools/ask-contract.test.ts +64 -0
  254. package/src/tools/ask-contract.ts +36 -1
  255. package/src/tools/ask.ts +10 -8
  256. package/src/tools/bash-interactive.ts +11 -2
  257. package/src/tools/bash.ts +412 -75
  258. package/src/tools/browser/launch.ts +37 -0
  259. package/src/tools/browser/registry.ts +99 -9
  260. package/src/tools/cron.ts +6 -4
  261. package/src/tools/descriptors.test.ts +15 -0
  262. package/src/tools/descriptors.ts +5 -1
  263. package/src/tools/gh.ts +104 -48
  264. package/src/tools/github-cache.ts +2 -2
  265. package/src/tools/index.ts +10 -6
  266. package/src/tools/job.ts +4 -1
  267. package/src/tools/monitor.ts +12 -9
  268. package/src/tools/resolve.ts +30 -10
  269. package/src/tools/tool-catalog.generated.ts +2 -2
  270. package/src/utils/external-editor.ts +72 -1
@@ -52,6 +52,10 @@ const LOCKED_EXCLUSIONS: Readonly<Record<string, string>> = {
52
52
  "slash_command:tutorial": "visual/local-only onboarding selector, not a user-facing SDK control seam",
53
53
  "slash_command:transcript": "visual/local-only transcript viewer, not a user-facing SDK control seam",
54
54
  "slash_command:sessions": "visual/local-only sessions dashboard, not a user-facing SDK control seam",
55
+ "slash_command:star":
56
+ "local transcript-discovery metadata command; no SDK operation counterpart or runtime/retention authority",
57
+ "slash_command:unstar":
58
+ "local transcript-discovery metadata command; no SDK operation counterpart or runtime/retention authority",
55
59
  "agent_session:constructor": "internal accessor/plumbing, not a user-facing control seam",
56
60
  "agent_session:getSessionAgentDir":
57
61
  "internal session-scoped agent-directory accessor, not a user-facing SDK control seam",
@@ -107,6 +111,12 @@ const LOCKED_EXCLUSIONS: Readonly<Record<string, string>> = {
107
111
  "agent_session:activeMidRunBarrierCountForTests": "read-only test seam, not a user-facing SDK control seam",
108
112
  "agent_session:activeMidRunMaintenanceCountForTests": "read-only test seam, not a user-facing SDK control seam",
109
113
  "agent_session:getPendingNextTurnMessagesForTests": "read-only test seam, not a user-facing SDK control seam",
114
+ "agent_session:queueCoordinatorRuntimeStatePersistForTests":
115
+ "test-only coordinator persistence ordering seam, not a user-facing SDK control seam",
116
+ "agent_session:parkAgentEndForCoordinatorPersistForTests":
117
+ "test-only parked terminal persistence seam, not a user-facing SDK control seam",
118
+ "agent_session:flushParkedAgentEndForCoordinatorPersistForTests":
119
+ "test-only parked terminal flush seam, not a user-facing SDK control seam",
110
120
  "agent_session:setCancelAndSubmitAbortOutcomeProviderForTests":
111
121
  "test-only cancellation seam, not a user-facing SDK control seam",
112
122
  "agent_session:getAgentId": "internal accessor/plumbing, not a user-facing control seam",
@@ -131,6 +141,7 @@ const LOCKED_EXCLUSIONS: Readonly<Record<string, string>> = {
131
141
  "agent_session:hasForegroundBashBackgroundRequestHandler":
132
142
  "internal accessor/plumbing, not a user-facing control seam",
133
143
  "agent_session:requestForegroundBashBackground": "internal accessor/plumbing, not a user-facing SDK control seam",
144
+ "agent_session:onJobFold": "internal fold-event plumbing feeding the bash_folded event frame, not a control seam",
134
145
 
135
146
  "agent_session:getSelectedMCPToolNames": "internal accessor/plumbing, not a user-facing control seam",
136
147
  "agent_session:isToolDiscoveryEnabled": "internal accessor/plumbing, not a user-facing control seam",
@@ -284,7 +295,7 @@ const SEAM_TO_SDK: Readonly<Record<string, string>> = {
284
295
  "agent_session:cycleThinkingLevel": "thinking.cycle",
285
296
  "agent_session:setSteeringMode": "queue.steering_mode.set",
286
297
  "agent_session:setFollowUpMode": "queue.follow_up_mode.set",
287
- "agent_session:setInterruptMode": "queue.interrupt_mode.set",
298
+ "agent_session:setToolInterruptPolicy": "queue.tool_interrupt_policy.set",
288
299
  "agent_session:compact": "compaction.run",
289
300
  "agent_session:setAutoCompactionEnabled": "compaction.auto.set",
290
301
  "agent_session:setAutoRetryEnabled": "retry.auto.set",
@@ -296,6 +307,7 @@ const SEAM_TO_SDK: Readonly<Record<string, string>> = {
296
307
  "agent_session:moveQueuedMessageForEditing": "queue.message.move",
297
308
  "agent_session:executeBash": "bash.execute",
298
309
  "agent_session:abortBash": "bash.abort",
310
+ "agent_session:requestForegroundBashBackgroundOutcome": "bash.background",
299
311
  "agent_session:switchSession": "session.switch",
300
312
  "agent_session:branch": "session.branch",
301
313
  "agent_session:handoff": "session.handoff",
@@ -1567,6 +1567,24 @@ function isTypeOnlyTmuxPrimitiveOccurrence(contents: string, occurrence: TmuxPri
1567
1567
  return /^\s*(?:export\s+)?type\b/.test(contents.slice(lineStart, lineEnd === -1 ? contents.length : lineEnd));
1568
1568
  }
1569
1569
 
1570
+ function isExactTmuxSelfInjectionGuardVerb(
1571
+ file: string,
1572
+ contents: string,
1573
+ occurrence: TmuxPrimitiveOccurrence,
1574
+ ): boolean {
1575
+ if (file !== "packages/coding-agent/src/tools/tmux-self-injection-guard.ts") return false;
1576
+ const declaration = 'const INPUT_VERBS = new Set(["send-keys", "paste-buffer", "send-prefix"]);';
1577
+ const declarationStart = contents.indexOf(declaration);
1578
+ if (declarationStart === -1 || contents.indexOf(declaration, declarationStart + 1) !== -1) return false;
1579
+ const references = [...contents.matchAll(/\bINPUT_VERBS\b/g)];
1580
+ if (references.length !== 2) return false;
1581
+ const guardUse = "INPUT_VERBS.has(invocation.verb)";
1582
+ const guardUseStart = contents.indexOf(guardUse);
1583
+ if (guardUseStart === -1 || contents.indexOf(guardUse, guardUseStart + 1) !== -1) return false;
1584
+ if (!references.some(reference => reference.index === guardUseStart)) return false;
1585
+ return occurrence.start >= declarationStart && occurrence.end <= declarationStart + declaration.length;
1586
+ }
1587
+
1570
1588
  function tmuxMachineBusViolations(file: string, contents: string): string[] {
1571
1589
  if (isGeneratedDocumentationIndex(file)) return [];
1572
1590
  const allowedTeamFallbackRanges =
@@ -1581,7 +1599,8 @@ function tmuxMachineBusViolations(file: string, contents: string): string[] {
1581
1599
  if (
1582
1600
  isExactTeamFallback ||
1583
1601
  isExactTmuxScrollCopyModeSendKeys(file, contents, occurrence) ||
1584
- isTypeOnlyTmuxPrimitiveOccurrence(contents, occurrence)
1602
+ isTypeOnlyTmuxPrimitiveOccurrence(contents, occurrence) ||
1603
+ isExactTmuxSelfInjectionGuardVerb(file, contents, occurrence)
1585
1604
  )
1586
1605
  continue;
1587
1606
  const detail =
@@ -3147,6 +3166,21 @@ fi
3147
3166
  1,
3148
3167
  "tmux send-keys content injection is outside sanctioned process lifecycle",
3149
3168
  );
3169
+ await runSelfTestFixture(
3170
+ {
3171
+ "packages/coding-agent/src/tools/tmux-self-injection-guard.ts":
3172
+ 'const INPUT_VERBS = new Set(["send-keys", "paste-buffer", "send-prefix"]);\nfunction guarded(invocation: { verb: string }): void { if (!INPUT_VERBS.has(invocation.verb)) return; }\n',
3173
+ },
3174
+ 0,
3175
+ );
3176
+ await runSelfTestFixture(
3177
+ {
3178
+ "packages/coding-agent/src/tools/tmux-self-injection-guard.ts":
3179
+ 'const INPUT_VERBS = new Set(["send-keys", "paste-buffer", "send-prefix"]);\nfunction guarded(invocation: { verb: string }): void { if (!INPUT_VERBS.has(invocation.verb)) return; }\nfor (const verb of INPUT_VERBS) Bun.spawnSync(["tmux", verb, "-t", pane, prompt]);\n',
3180
+ },
3181
+ 1,
3182
+ "tmux send-keys content injection is outside sanctioned process lifecycle",
3183
+ );
3150
3184
  await runSelfTestFixture(
3151
3185
  {
3152
3186
  "packages/coding-agent/src/gjc-runtime/team-runtime.ts":
@@ -68,9 +68,25 @@ export function jobElapsedMs(job: Pick<AsyncJob, "startTime" | "endTime">, now:
68
68
  return Math.max(0, (job.endTime ?? now) - job.startTime);
69
69
  }
70
70
 
71
+ /**
72
+ * Why a foreground wait left the foreground. Every fold path names its
73
+ * trigger so job snapshots and SDK clients can tell a user steer from the
74
+ * chord, the auto-background timer, or the explicit `bash.background` control.
75
+ */
76
+ export type FoldReason = "steer" | "chord" | "timer" | "sdk_control";
77
+
78
+ /** A fold observed by the manager; published to SDK clients as `bash_folded`. */
79
+ export interface JobFoldEvent {
80
+ jobId: string;
81
+ generation: string;
82
+ reason: FoldReason;
83
+ }
84
+
71
85
  export interface AsyncJobMetadata {
72
86
  /** Set once a foreground wait has been folded, so surfacing can show folded work. */
73
87
  backgrounded?: boolean;
88
+ /** Why a foreground wait was folded; absent for jobs started directly in the background (`async: true`). */
89
+ foldReason?: FoldReason;
74
90
  subagent?: {
75
91
  id: string;
76
92
  agent: string;
@@ -271,6 +287,7 @@ interface AsyncJobDelivery {
271
287
  label: string;
272
288
  status: AsyncJob["status"];
273
289
  backgrounded: boolean;
290
+ foldReason?: FoldReason;
274
291
  text: string;
275
292
  originalBytes?: number;
276
293
  truncated?: boolean;
@@ -290,6 +307,7 @@ interface AsyncJobReceiptClaim {
290
307
  label: string;
291
308
  status: AsyncJob["status"];
292
309
  backgrounded: boolean;
310
+ foldReason?: FoldReason;
293
311
  ownerId?: string;
294
312
  }
295
313
 
@@ -331,6 +349,8 @@ export interface AsyncJobSnapshotEntry {
331
349
  status: AsyncJob["status"];
332
350
  generation: string;
333
351
  backgrounded: boolean;
352
+ /** Present when `backgrounded` was set by a fold; absent for direct background starts. */
353
+ foldReason?: FoldReason;
334
354
  deliveryState: JobDeliveryState;
335
355
  }
336
356
 
@@ -630,6 +650,7 @@ export class AsyncJobManager {
630
650
  }
631
651
 
632
652
  readonly #jobs = new Map<string, AsyncJob>();
653
+ #retainedDisposalCompletion: Promise<void> = Promise.resolve();
633
654
  readonly #deliveries: AsyncJobDelivery[] = [];
634
655
  readonly #inFlightDeliveries: AsyncJobDelivery[] = [];
635
656
  readonly #suppressedDeliveries = new Set<string>();
@@ -653,7 +674,6 @@ export class AsyncJobManager {
653
674
  readonly #maxDeadLetterOverflowOwners: number;
654
675
  #deliveryLoop: Promise<void> | undefined;
655
676
  #disposed = false;
656
- #disposing = false;
657
677
  #runningOwnerCleanups = false;
658
678
  readonly #subagentRecords = new Map<string, SubagentRecord>();
659
679
  readonly #terminalEvents = new Map<string, TerminalEvent>();
@@ -996,6 +1016,8 @@ export class AsyncJobManager {
996
1016
  };
997
1017
  }
998
1018
 
1019
+ readonly #foldListeners = new Set<(event: JobFoldEvent) => void>();
1020
+
999
1021
  #notifyChange(): void {
1000
1022
  for (const cb of this.#changeListeners) {
1001
1023
  try {
@@ -2307,6 +2329,7 @@ export class AsyncJobManager {
2307
2329
  status: job.status,
2308
2330
  generation: job.generation,
2309
2331
  backgrounded: job.metadata?.backgrounded === true,
2332
+ foldReason: job.metadata?.foldReason,
2310
2333
  deliveryState,
2311
2334
  };
2312
2335
  });
@@ -2329,6 +2352,7 @@ export class AsyncJobManager {
2329
2352
  status: delivery.status,
2330
2353
  generation: delivery.generation,
2331
2354
  backgrounded: delivery.backgrounded,
2355
+ foldReason: delivery.foldReason,
2332
2356
  deliveryState: "pending",
2333
2357
  });
2334
2358
  }
@@ -2344,6 +2368,7 @@ export class AsyncJobManager {
2344
2368
  status: delivery.status,
2345
2369
  generation: delivery.generation,
2346
2370
  backgrounded: delivery.backgrounded,
2371
+ foldReason: delivery.foldReason,
2347
2372
  deliveryState: "pending",
2348
2373
  });
2349
2374
  }
@@ -2359,6 +2384,7 @@ export class AsyncJobManager {
2359
2384
  status: claim.status,
2360
2385
  generation: claim.generation,
2361
2386
  backgrounded: claim.backgrounded,
2387
+ foldReason: claim.foldReason,
2362
2388
  deliveryState: "pending",
2363
2389
  });
2364
2390
  }
@@ -2408,6 +2434,7 @@ export class AsyncJobManager {
2408
2434
  label: job.label,
2409
2435
  status: job.status,
2410
2436
  backgrounded: job.metadata?.backgrounded === true,
2437
+ foldReason: job.metadata?.foldReason,
2411
2438
  text,
2412
2439
  attempt: 0,
2413
2440
  nextAttemptAt: Date.now(),
@@ -2426,6 +2453,7 @@ export class AsyncJobManager {
2426
2453
  label: job.label,
2427
2454
  status: job.status,
2428
2455
  backgrounded: job.metadata?.backgrounded === true,
2456
+ foldReason: job.metadata?.foldReason,
2429
2457
  ownerId: job.ownerId,
2430
2458
  });
2431
2459
  this.#notifyChange();
@@ -2632,12 +2660,26 @@ export class AsyncJobManager {
2632
2660
  }
2633
2661
 
2634
2662
  /**
2635
- * Mark a running job as backgrounded (folded out of its foreground call).
2636
- * Read by getJobsSnapshot so folded work stays visible; safe to call twice.
2663
+ * Subscribe to fold events. Fires once per successful `markBackgrounded`
2664
+ * transition (never on the idempotent repeat), so a subscriber can publish
2665
+ * exactly one `bash_folded` frame per fold. Listener errors are isolated.
2637
2666
  */
2638
- markBackgrounded(jobId: string, generation: string): boolean {
2667
+ onFold(cb: (event: JobFoldEvent) => void): () => void {
2668
+ this.#foldListeners.add(cb);
2669
+ return () => {
2670
+ this.#foldListeners.delete(cb);
2671
+ };
2672
+ }
2673
+
2674
+ /**
2675
+ * Mark a job that was launched directly in the background (`bash` with
2676
+ * `async: true`). It never had a foreground wait, so this is not a fold: no
2677
+ * `foldReason` is recorded and no fold listener fires.
2678
+ */
2679
+ markStartedInBackground(jobId: string, generation: string): boolean {
2639
2680
  const job = this.#jobs.get(jobId);
2640
2681
  if (!job || job.generation !== generation) return false;
2682
+ if (job.metadata?.backgrounded === true) return true;
2641
2683
  job.metadata = { ...job.metadata, backgrounded: true };
2642
2684
  for (const delivery of [...this.#deliveries, ...this.#inFlightDeliveries]) {
2643
2685
  if (delivery.jobId === jobId && delivery.generation === generation) delivery.backgrounded = true;
@@ -2646,6 +2688,37 @@ export class AsyncJobManager {
2646
2688
  return true;
2647
2689
  }
2648
2690
 
2691
+ /**
2692
+ * Mark a running job as backgrounded (folded out of its foreground call).
2693
+ * Read by getJobsSnapshot so folded work stays visible; safe to call twice.
2694
+ * The first transition records `reason` and notifies fold listeners; a
2695
+ * repeat keeps the original reason and emits nothing.
2696
+ */
2697
+ markBackgrounded(jobId: string, generation: string, reason: FoldReason): boolean {
2698
+ const job = this.#jobs.get(jobId);
2699
+ if (!job || job.generation !== generation) return false;
2700
+ if (job.metadata?.backgrounded === true) return true;
2701
+ job.metadata = { ...job.metadata, backgrounded: true, foldReason: reason };
2702
+ for (const delivery of [...this.#deliveries, ...this.#inFlightDeliveries]) {
2703
+ if (delivery.jobId === jobId && delivery.generation === generation) {
2704
+ delivery.backgrounded = true;
2705
+ delivery.foldReason = reason;
2706
+ }
2707
+ }
2708
+ this.#notifyChange();
2709
+ const event: JobFoldEvent = { jobId, generation, reason };
2710
+ for (const cb of this.#foldListeners) {
2711
+ try {
2712
+ cb(event);
2713
+ } catch (error) {
2714
+ logger.warn("Async job fold listener failed", {
2715
+ error: error instanceof Error ? error.message : String(error),
2716
+ });
2717
+ }
2718
+ }
2719
+ return true;
2720
+ }
2721
+
2649
2722
  async waitForAll(): Promise<void> {
2650
2723
  await Promise.all(Array.from(this.#jobs.values()).map(job => job.promise));
2651
2724
  }
@@ -2707,6 +2780,17 @@ export class AsyncJobManager {
2707
2780
  // registration keeps the original protection (cleanups cannot register fresh
2708
2781
  // work) without disabling delivery. Errors in cleanups are logged, never
2709
2782
  // escalated.
2783
+ const unsettledJobPromises = new Set<Promise<void>>();
2784
+ const jobIdsByPromise = new Map<Promise<void>, string>();
2785
+ for (const job of this.#jobs.values()) {
2786
+ const promise = job.promise;
2787
+ unsettledJobPromises.add(promise);
2788
+ jobIdsByPromise.set(promise, job.id);
2789
+ void promise.then(
2790
+ () => unsettledJobPromises.delete(promise),
2791
+ () => unsettledJobPromises.delete(promise),
2792
+ );
2793
+ }
2710
2794
  this.#registrationClosed = true;
2711
2795
  this.#clearEvictionTimers();
2712
2796
  this.#runningOwnerCleanups = true;
@@ -2715,7 +2799,6 @@ export class AsyncJobManager {
2715
2799
  } finally {
2716
2800
  this.#runningOwnerCleanups = false;
2717
2801
  }
2718
- this.#disposing = true;
2719
2802
  this.cancelAll();
2720
2803
  for (const tombstone of this.#monitorTombstones.values()) {
2721
2804
  try {
@@ -2734,13 +2817,22 @@ export class AsyncJobManager {
2734
2817
  const remainingDeliveryMs = Math.max(0, disposalDeadline - Date.now());
2735
2818
  const drained = await this.drainDeliveries({ timeoutMs: remainingDeliveryMs });
2736
2819
  if (!drained) this.#projectUndeliveredDisposalFailures();
2737
- const disposalCompleted = waitResult.completed && drained;
2820
+ const retainedAuthority = new Set<Promise<void>>(unsettledJobPromises);
2821
+ for (const delivery of this.#inFlightDeliveries) {
2822
+ if (delivery.promise) retainedAuthority.add(delivery.promise);
2823
+ }
2824
+ this.#retainedDisposalCompletion = Promise.allSettled(retainedAuthority).then(() => {});
2825
+ const physicalStuckJobIds = [...unsettledJobPromises]
2826
+ .map(promise => jobIdsByPromise.get(promise))
2827
+ .filter((id): id is string => id !== undefined);
2828
+ const stuckJobIds = [...new Set([...waitResult.stuckJobIds, ...physicalStuckJobIds])];
2829
+ const disposalCompleted = waitResult.completed && drained && retainedAuthority.size === 0;
2738
2830
  this.#lastDisposeDiagnostics = {
2739
- stuckJobIds: waitResult.stuckJobIds,
2831
+ stuckJobIds,
2740
2832
  deliveriesDrained: disposalCompleted,
2741
2833
  };
2742
- if (waitResult.stuckJobIds.length > 0) {
2743
- logger.warn("Async job manager dispose timed out waiting for jobs", { stuckJobIds: waitResult.stuckJobIds });
2834
+ if (stuckJobIds.length > 0) {
2835
+ logger.warn("Async job manager dispose timed out waiting for jobs", { stuckJobIds });
2744
2836
  }
2745
2837
  this.#clearEvictionTimers();
2746
2838
  this.#disposed = true;
@@ -2772,9 +2864,14 @@ export class AsyncJobManager {
2772
2864
  this.#ownerSubagentShutdownLeases.clear();
2773
2865
  this.#notifyChange();
2774
2866
  this.#changeListeners.clear();
2867
+ this.#foldListeners.clear();
2775
2868
  return disposalCompleted;
2776
2869
  }
2777
2870
 
2871
+ awaitRetainedDisposalCompletion(): Promise<void> {
2872
+ return this.#retainedDisposalCompletion;
2873
+ }
2874
+
2778
2875
  #projectUndeliveredDisposalFailures(): void {
2779
2876
  const retained = [...this.#deliveries, ...this.#inFlightDeliveries];
2780
2877
  for (const delivery of retained) {
@@ -3145,6 +3242,7 @@ export class AsyncJobManager {
3145
3242
  label: job.label,
3146
3243
  status: job.status,
3147
3244
  backgrounded: job.metadata?.backgrounded === true,
3245
+ foldReason: job.metadata?.foldReason,
3148
3246
  text: deliveryText.text,
3149
3247
  originalBytes: deliveryText.originalBytes,
3150
3248
  truncated: deliveryText.truncated,
@@ -0,0 +1,29 @@
1
+ import browserAsidePrompt from "./prompts/agent-fragments/browser-aside.md" with { type: "text" };
2
+
3
+ export type BrowserBackendId = "native" | "aside";
4
+
5
+ export interface BrowserBackend {
6
+ readonly id: BrowserBackendId;
7
+ readonly exposesBuiltinTool: boolean;
8
+ readonly developerInstructions?: string;
9
+ }
10
+
11
+ interface BrowserBackendSettingsSource {
12
+ get(key: string): unknown;
13
+ }
14
+
15
+ const nativeBrowserBackend: BrowserBackend = {
16
+ id: "native",
17
+ exposesBuiltinTool: true,
18
+ };
19
+
20
+ const asideBrowserBackend: BrowserBackend = {
21
+ id: "aside",
22
+ exposesBuiltinTool: false,
23
+ developerInstructions: browserAsidePrompt.trim(),
24
+ };
25
+
26
+ /** Resolve the configured browser backend. Aside is strictly opt-in. */
27
+ export function resolveBrowserBackend(settings: BrowserBackendSettingsSource): BrowserBackend {
28
+ return settings.get("browser.backend") === "aside" ? asideBrowserBackend : nativeBrowserBackend;
29
+ }
@@ -10,6 +10,7 @@ import {
10
10
  type AuthStorage,
11
11
  type CredentialInventoryRecord,
12
12
  type CredentialRemovalTarget,
13
+ isSqliteCorruptionError,
13
14
  OAuthCredentialSelectorError,
14
15
  type OAuthPinTarget,
15
16
  resolveOAuthStorageProvider,
@@ -23,6 +24,7 @@ import {
23
24
  checkAccountInventory,
24
25
  clearPersistentPinForRemovedRows,
25
26
  } from "../session/account-inventory";
27
+ import { CREDENTIAL_STORE_UNREADABLE_MESSAGE } from "../session/credential-store-errors";
26
28
  import { resolveStartupAuthConfig, type StartupAuthConfigSnapshot } from "../session/startup-auth-config";
27
29
 
28
30
  export const ACCOUNTS_ACTIONS = ["list", "check", "pin", "logout"] as const;
@@ -48,6 +50,7 @@ class AccountsCommandError extends Error {
48
50
  }
49
51
  }
50
52
  export function toAccountsCommandError(error: unknown): AccountsCommandError | undefined {
53
+ if (isSqliteCorruptionError(error)) return new AccountsCommandError(CREDENTIAL_STORE_UNREADABLE_MESSAGE);
51
54
  if (error instanceof OAuthCredentialSelectorError) return new AccountsCommandError(error.message);
52
55
  return undefined;
53
56
  }
@@ -391,16 +394,17 @@ export async function runAccountsCommand(cmd: AccountsCommandArgs): Promise<void
391
394
  }
392
395
  }
393
396
  } catch (error) {
397
+ const normalizedError = toAccountsCommandError(error) ?? error;
394
398
  if (cmd.flags.json) {
395
- if (!(error instanceof AccountsCommandError)) {
396
- process.stderr.write(`accounts command failed: ${cleanReason(error) ?? "Command failed."}\n`);
399
+ if (!(normalizedError instanceof AccountsCommandError)) {
400
+ process.stderr.write(`accounts command failed: ${cleanReason(normalizedError) ?? "Command failed."}\n`);
397
401
  }
398
402
  process.exitCode = 1;
399
- writeJsonFailure(error);
403
+ writeJsonFailure(normalizedError);
400
404
  return;
401
405
  }
402
- if (error instanceof AccountsCommandError) {
403
- process.stderr.write(`${error.message}\n`);
406
+ if (normalizedError instanceof AccountsCommandError) {
407
+ process.stderr.write(`${normalizedError.message}\n`);
404
408
  process.exitCode = 1;
405
409
  return;
406
410
  }
package/src/cli/args.ts CHANGED
@@ -84,6 +84,8 @@ export interface Args {
84
84
  noRules?: boolean;
85
85
  listModels?: string | true;
86
86
  noTitle?: boolean;
87
+ master?: true;
88
+ masterScope?: "repo" | "pwd" | "global";
87
89
  messages: string[];
88
90
  fileArgs: string[];
89
91
  /** Flags unknown to every launch parser; their consumer supplies the diagnostic. */
@@ -178,6 +180,17 @@ export function parseArgs(args: string[], authority: ParseArgsAuthority = "local
178
180
  result.help = true;
179
181
  } else if (arg === "--version" || arg === "-v") {
180
182
  result.version = true;
183
+ } else if (arg === "--master") {
184
+ result.master = true;
185
+ } else if (arg === "--scope") {
186
+ const scope = takeFlagValue(args, i++, "--scope");
187
+ if (scope !== "repo" && scope !== "pwd" && scope !== "global") {
188
+ throw new CliParseError("invalid --scope value (expected repo, pwd, or global)");
189
+ }
190
+ if (result.masterScope !== undefined && result.masterScope !== scope) {
191
+ throw new CliParseError("--scope cannot be specified with conflicting values");
192
+ }
193
+ result.masterScope = scope;
181
194
  } else if (arg === "--allow-home") {
182
195
  result.allowHome = true;
183
196
  } else if (arg === "--mode") {
@@ -371,6 +384,10 @@ export function parseArgs(args: string[], authority: ParseArgsAuthority = "local
371
384
  }
372
385
  }
373
386
 
387
+ if (result.masterScope !== undefined && result.master !== true) {
388
+ throw new CliParseError("--scope requires --master");
389
+ }
390
+ if (result.master === true && result.masterScope === undefined) result.masterScope = "repo";
374
391
  if (result.default && !result.mpreset) {
375
392
  throw new CliParseError("--default requires --mpreset <name>");
376
393
  }
@@ -79,6 +79,7 @@ Environment Variables:
79
79
 
80
80
  # Configuration
81
81
  GJC_CODING_AGENT_DIR - Session storage directory (default: ~/${CONFIG_DIR_NAME}/agent)
82
+ GJC_UI_LANGUAGE - Explicit first-use onboarding language (en, ko, zh, or ja); invalid values use English without prompting
82
83
  GJC_PACKAGE_DIR - Override package directory (for Nix/Guix store paths)
83
84
  GJC_SMOL_MODEL - Override smol/fast model (see --smol)
84
85
  GJC_SLOW_MODEL - Override slow/reasoning model (see --slow)
@@ -886,11 +886,40 @@ async function handleLink(manager: PluginManager, paths: string[], flags: { json
886
886
  }
887
887
  }
888
888
 
889
+ async function collectMarketplaceDoctorChecks(): Promise<
890
+ Array<{ name: string; status: "ok" | "warning" | "error"; message: string }>
891
+ > {
892
+ const extra: Array<{ name: string; status: "ok" | "warning" | "error"; message: string }> = [];
893
+ try {
894
+ const mktMgr = await makeMarketplaceManager();
895
+ const installed = await mktMgr.listInstalledPlugins();
896
+ for (const summary of installed) {
897
+ for (const entry of summary.entries) {
898
+ let exists = false;
899
+ try {
900
+ const { statSync } = await import("node:fs");
901
+ statSync(entry.installPath);
902
+ exists = true;
903
+ } catch {}
904
+ if (!exists) {
905
+ extra.push({
906
+ name: `marketplace:${summary.id}:${entry.scope}`,
907
+ status: "error",
908
+ message: `Marketplace plugin "${summary.id}" (${entry.scope}) references missing cache dir "${entry.installPath}" (v${entry.version}); reinstall or upgrade the plugin`,
909
+ });
910
+ }
911
+ }
912
+ }
913
+ } catch {}
914
+ return extra;
915
+ }
916
+
889
917
  async function handleDoctor(
890
918
  manager: PluginManager,
891
919
  flags: { json?: boolean; fix?: boolean; migratePlugins?: boolean },
892
920
  ): Promise<void> {
893
921
  const checks = await manager.doctor({ fix: flags.fix });
922
+ checks.push(...(await collectMarketplaceDoctorChecks()));
894
923
  try {
895
924
  const statuses = flags.migratePlugins
896
925
  ? await runGjcPluginMigrationPreflight(getProjectDir())
@@ -73,6 +73,11 @@ export const ROOT_LAUNCH_FLAGS = {
73
73
  optionalValue: true,
74
74
  }),
75
75
  "no-title": Flags.boolean({ description: "Disable title auto-generation" }),
76
+ master: Flags.boolean({ description: "Launch an interactive master session" }),
77
+ scope: Flags.string({
78
+ description: "Master scope: repo (default), pwd, or global",
79
+ options: ["repo", "pwd", "global"],
80
+ }),
76
81
  };
77
82
 
78
83
  export type ConsumerLaunchFlagName = "hook" | "extension" | "no-extensions" | "no-skills" | "skills";