@gajae-code/coding-agent 0.15.5 → 0.16.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 (272) hide show
  1. package/CHANGELOG.md +85 -1
  2. package/dist/types/async/job-manager.d.ts +83 -0
  3. package/dist/types/cli/args.d.ts +2 -0
  4. package/dist/types/cli/auth-gateway-cli.d.ts +17 -0
  5. package/dist/types/cli/model-presets-cli.d.ts +9 -0
  6. package/dist/types/cli/root-flags.d.ts +14 -0
  7. package/dist/types/cli/update-cli.d.ts +15 -0
  8. package/dist/types/cli.d.ts +7 -0
  9. package/dist/types/commands/auth-gateway.d.ts +3 -0
  10. package/dist/types/commands/gc.d.ts +13 -0
  11. package/dist/types/commands/launch.d.ts +10 -0
  12. package/dist/types/commands/model-presets.d.ts +21 -0
  13. package/dist/types/config/autorouting-tier-map.d.ts +120 -0
  14. package/dist/types/config/file-lock.d.ts +54 -2
  15. package/dist/types/config/internal/model-preset-registry-test-state.d.ts +4 -0
  16. package/dist/types/config/internal/model-preset-registry-test-support.d.ts +3 -0
  17. package/dist/types/config/keybindings.d.ts +3 -2
  18. package/dist/types/config/model-preset-registry.d.ts +368 -0
  19. package/dist/types/config/model-profile-activation.d.ts +39 -0
  20. package/dist/types/config/model-profiles.d.ts +5 -3
  21. package/dist/types/config/model-registry.d.ts +17 -5
  22. package/dist/types/config/settings-schema.d.ts +19 -1
  23. package/dist/types/config/settings.d.ts +2 -0
  24. package/dist/types/coordinator-mcp/model-preset.d.ts +7 -5
  25. package/dist/types/coordinator-mcp/projection-scan.d.ts +54 -0
  26. package/dist/types/crash/record-loader.d.ts +15 -0
  27. package/dist/types/extensibility/extensions/types.d.ts +2 -5
  28. package/dist/types/gjc-runtime/empty-delete-gc.d.ts +58 -0
  29. package/dist/types/gjc-runtime/gc-runtime.d.ts +2 -0
  30. package/dist/types/gjc-runtime/launch-worktree-reservation.d.ts +21 -0
  31. package/dist/types/gjc-runtime/managed-owner-supervisor.d.ts +2 -0
  32. package/dist/types/gjc-runtime/session-state-lock.d.ts +35 -6
  33. package/dist/types/gjc-runtime/session-state-sidecar.d.ts +72 -1
  34. package/dist/types/gjc-runtime/tmux-owner-isolation.d.ts +1 -0
  35. package/dist/types/gjc-runtime/tmux-sessions.d.ts +27 -0
  36. package/dist/types/index.d.ts +1 -0
  37. package/dist/types/main.d.ts +3 -1
  38. package/dist/types/master-mode/context.d.ts +21 -0
  39. package/dist/types/master-mode/first-request.d.ts +34 -0
  40. package/dist/types/master-mode/snapshot.d.ts +32 -0
  41. package/dist/types/modes/components/frictionless-onboarding-selector.d.ts +10 -0
  42. package/dist/types/modes/components/jobs-overlay-model.d.ts +4 -2
  43. package/dist/types/modes/components/oauth-selector.d.ts +1 -0
  44. package/dist/types/modes/components/status-line/model-name.d.ts +18 -0
  45. package/dist/types/modes/components/status-line/priority-row.d.ts +34 -0
  46. package/dist/types/modes/components/status-line/segments.d.ts +10 -0
  47. package/dist/types/modes/jobs-observer.d.ts +36 -0
  48. package/dist/types/modes/print-mode.d.ts +17 -0
  49. package/dist/types/modes/ui-language.d.ts +7 -1
  50. package/dist/types/sdk/broker/broker.d.ts +80 -1
  51. package/dist/types/sdk/broker/endpoint-authority.d.ts +24 -0
  52. package/dist/types/sdk/broker/index.d.ts +3 -0
  53. package/dist/types/sdk/broker/lifecycle.d.ts +26 -0
  54. package/dist/types/sdk/broker/master-capability.d.ts +10 -0
  55. package/dist/types/sdk/broker/session-index.d.ts +40 -11
  56. package/dist/types/sdk/broker/session-scope.d.ts +72 -0
  57. package/dist/types/sdk/broker/spawn-authority.d.ts +201 -0
  58. package/dist/types/sdk/broker/spawn-substrate.d.ts +30 -0
  59. package/dist/types/sdk/broker/state-version.d.ts +4 -1
  60. package/dist/types/sdk/broker/worktree-occupancy.d.ts +28 -0
  61. package/dist/types/sdk/bus/chat-daemon-control.d.ts +3 -0
  62. package/dist/types/sdk/bus/index.d.ts +6 -0
  63. package/dist/types/sdk/bus/kind-aware-reconciliation.d.ts +3 -0
  64. package/dist/types/sdk/bus/prompt-reconciliation.d.ts +4 -0
  65. package/dist/types/sdk/bus/telegram-daemon-contract.d.ts +1 -1
  66. package/dist/types/sdk/cli/index.d.ts +1 -0
  67. package/dist/types/sdk/cli/master-cli.d.ts +63 -0
  68. package/dist/types/sdk/cli/rows.d.ts +4 -3
  69. package/dist/types/sdk/cli/session-cli.d.ts +18 -2
  70. package/dist/types/sdk/client/client.d.ts +3 -2
  71. package/dist/types/sdk/client/discovery.d.ts +1 -0
  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/host.d.ts +10 -0
  77. package/dist/types/sdk/host/model-pin.d.ts +7 -2
  78. package/dist/types/sdk/host/sdk-run-capability.d.ts +1 -0
  79. package/dist/types/sdk/host/session-runtime.d.ts +67 -1
  80. package/dist/types/sdk/lifecycle/broker-client.d.ts +6 -0
  81. package/dist/types/sdk/lifecycle/service.d.ts +15 -3
  82. package/dist/types/sdk/lifecycle-session.d.ts +2 -0
  83. package/dist/types/sdk/prompt-deadline-manager.d.ts +11 -1
  84. package/dist/types/sdk/protocol/adapter-validation.d.ts +2 -0
  85. package/dist/types/sdk/router/session-router.d.ts +11 -0
  86. package/dist/types/sdk/session.d.ts +16 -0
  87. package/dist/types/sdk/startup-capability.d.ts +2 -1
  88. package/dist/types/session/account-inventory.d.ts +1 -0
  89. package/dist/types/session/agent-session.d.ts +24 -7
  90. package/dist/types/session/credential-store-errors.d.ts +1 -0
  91. package/dist/types/session/fold-coordinator.d.ts +201 -0
  92. package/dist/types/session/sdk-run-capability-internal.d.ts +7 -0
  93. package/dist/types/session/sdk-run-capability.d.ts +2 -0
  94. package/dist/types/session/session-manager.d.ts +65 -0
  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/telemetry/control.d.ts +11 -0
  98. package/dist/types/telemetry/events.d.ts +20 -0
  99. package/dist/types/telemetry/index.d.ts +3 -0
  100. package/dist/types/telemetry/transport.d.ts +22 -0
  101. package/dist/types/tools/bash-interactive.d.ts +23 -0
  102. package/dist/types/tools/bash.d.ts +4 -0
  103. package/dist/types/tools/gh.d.ts +8 -0
  104. package/dist/types/tools/index.d.ts +10 -5
  105. package/dist/types/web/search/providers/base.d.ts +9 -4
  106. package/dist/types/web/search/providers/xai.d.ts +2 -1
  107. package/dist/types/web/search/types.d.ts +14 -0
  108. package/package.json +10 -7
  109. package/scripts/generate-hotkeys-docs.ts +4 -0
  110. package/scripts/generate-sdk-operation-inventory.ts +9 -3
  111. package/scripts/verify-gjc-sdk-canonicalization.ts +35 -1
  112. package/src/async/job-manager.ts +671 -39
  113. package/src/cli/accounts-cli.ts +9 -5
  114. package/src/cli/args.ts +17 -0
  115. package/src/cli/auth-broker-cli.ts +40 -12
  116. package/src/cli/auth-gateway-cli.ts +273 -97
  117. package/src/cli/config-cli.ts +2 -2
  118. package/src/cli/fast-help.ts +4 -0
  119. package/src/cli/model-presets-cli.ts +100 -0
  120. package/src/cli/root-flags.ts +5 -0
  121. package/src/cli/update-cli.ts +117 -16
  122. package/src/cli.ts +6 -2
  123. package/src/commands/auth-gateway.ts +12 -8
  124. package/src/commands/gc.ts +19 -0
  125. package/src/commands/launch.ts +86 -18
  126. package/src/commands/model-presets.ts +24 -0
  127. package/src/commands/sdk.ts +113 -21
  128. package/src/config/autorouting-tier-map.ts +44 -0
  129. package/src/config/file-lock-gc.ts +9 -4
  130. package/src/config/file-lock.ts +890 -99
  131. package/src/config/internal/model-preset-registry-test-state.ts +28 -0
  132. package/src/config/internal/model-preset-registry-test-support.ts +11 -0
  133. package/src/config/keybindings.ts +6 -2
  134. package/src/config/model-preset-registry.ts +2795 -0
  135. package/src/config/model-profile-activation.ts +115 -28
  136. package/src/config/model-profile-contract.ts +4 -1
  137. package/src/config/model-profiles.ts +56 -14
  138. package/src/config/model-registry.ts +1044 -122
  139. package/src/config/model-resolver.ts +5 -0
  140. package/src/config/settings-schema.ts +19 -1
  141. package/src/config/settings.ts +11 -1
  142. package/src/coordinator-mcp/model-preset.ts +32 -17
  143. package/src/coordinator-mcp/projection-scan.ts +592 -0
  144. package/src/coordinator-mcp/question-state.ts +9 -11
  145. package/src/coordinator-mcp/server.ts +111 -42
  146. package/src/coordinator-mcp/session-reaper.ts +9 -3
  147. package/src/crash/index-store.ts +2 -0
  148. package/src/crash/record-loader.ts +10 -0
  149. package/src/extensibility/extensions/loader.ts +0 -1
  150. package/src/extensibility/extensions/types.ts +2 -3
  151. package/src/extensibility/gjc-plugins/runtime-adapters.ts +1 -0
  152. package/src/gjc-runtime/empty-delete-gc.ts +301 -0
  153. package/src/gjc-runtime/gc-render.ts +29 -0
  154. package/src/gjc-runtime/gc-runtime.ts +174 -11
  155. package/src/gjc-runtime/launch-worktree-reservation.ts +242 -0
  156. package/src/gjc-runtime/managed-owner-supervisor.ts +63 -51
  157. package/src/gjc-runtime/session-state-lock.ts +1321 -147
  158. package/src/gjc-runtime/session-state-sidecar.ts +1004 -25
  159. package/src/gjc-runtime/tmux-owner-isolation-cli.ts +4 -7
  160. package/src/gjc-runtime/tmux-owner-isolation.ts +2 -2
  161. package/src/gjc-runtime/tmux-sessions.ts +252 -13
  162. package/src/hooks/skill-state.ts +6 -0
  163. package/src/index.ts +1 -0
  164. package/src/internal-urls/docs-index.generated.ts +14 -13
  165. package/src/internal-urls/issue-pr-protocol.ts +4 -2
  166. package/src/lsp/config.ts +4 -3
  167. package/src/lsp/path-trust.ts +12 -6
  168. package/src/main.ts +66 -4
  169. package/src/master-mode/context.ts +50 -0
  170. package/src/master-mode/first-request.ts +65 -0
  171. package/src/master-mode/snapshot.ts +205 -0
  172. package/src/modes/acp/acp-agent.ts +616 -161
  173. package/src/modes/components/footer.ts +4 -2
  174. package/src/modes/components/frictionless-onboarding-selector.ts +48 -2
  175. package/src/modes/components/jobs-overlay-model.ts +99 -16
  176. package/src/modes/components/model-selector.ts +158 -18
  177. package/src/modes/components/oauth-selector.ts +6 -1
  178. package/src/modes/components/settings-selector.ts +1 -0
  179. package/src/modes/components/status-line/model-name.ts +53 -0
  180. package/src/modes/components/status-line/priority-row.ts +107 -0
  181. package/src/modes/components/status-line/segments.ts +29 -10
  182. package/src/modes/components/tool-status-header.ts +151 -15
  183. package/src/modes/controllers/extension-ui-controller.ts +1 -1
  184. package/src/modes/controllers/input-controller.ts +51 -6
  185. package/src/modes/controllers/selector-controller.ts +191 -96
  186. package/src/modes/interactive-mode.ts +14 -1
  187. package/src/modes/jobs-observer.ts +149 -16
  188. package/src/modes/print-mode.ts +64 -0
  189. package/src/modes/runtime-init.ts +1 -1
  190. package/src/modes/ui-language.ts +49 -2
  191. package/src/modes/utils/injected-user-submission.ts +15 -1
  192. package/src/prompts/system/master-mode.md +28 -0
  193. package/src/prompts/tools/bash.md +2 -2
  194. package/src/prompts/tools/fold-receipt.md +3 -0
  195. package/src/prompts/tools/job.md +1 -1
  196. package/src/sdk/acp/adapter.ts +17 -2
  197. package/src/sdk/broker/broker.ts +1671 -34
  198. package/src/sdk/broker/endpoint-authority.ts +86 -0
  199. package/src/sdk/broker/ensure.ts +5 -0
  200. package/src/sdk/broker/index.ts +3 -0
  201. package/src/sdk/broker/lifecycle.ts +217 -46
  202. package/src/sdk/broker/master-capability.ts +232 -0
  203. package/src/sdk/broker/session-index.ts +121 -17
  204. package/src/sdk/broker/session-scope.ts +272 -0
  205. package/src/sdk/broker/spawn-authority.ts +1031 -0
  206. package/src/sdk/broker/spawn-substrate.ts +548 -0
  207. package/src/sdk/broker/state-version.ts +26 -14
  208. package/src/sdk/broker/transport.ts +1 -0
  209. package/src/sdk/broker/worktree-occupancy.ts +90 -0
  210. package/src/sdk/bus/chat-daemon-control.ts +5 -2
  211. package/src/sdk/bus/chat-daemon-runtime.ts +46 -5
  212. package/src/sdk/bus/index.ts +51 -4
  213. package/src/sdk/bus/kind-aware-reconciliation.ts +36 -6
  214. package/src/sdk/bus/prompt-reconciliation.ts +28 -4
  215. package/src/sdk/bus/slack-conversation.ts +5 -1
  216. package/src/sdk/bus/telegram-daemon-contract.ts +1 -1
  217. package/src/sdk/cli/index.ts +1 -0
  218. package/src/sdk/cli/master-cli.ts +259 -0
  219. package/src/sdk/cli/rows.ts +11 -8
  220. package/src/sdk/cli/session-cli.ts +276 -6
  221. package/src/sdk/client/client.ts +14 -8
  222. package/src/sdk/client/discovery.ts +3 -1
  223. package/src/sdk/host/control/dispatch.ts +101 -37
  224. package/src/sdk/host/control/index.ts +2 -0
  225. package/src/sdk/host/control/operations.ts +2 -0
  226. package/src/sdk/host/control/runtime-gate.ts +203 -11
  227. package/src/sdk/host/host.ts +27 -2
  228. package/src/sdk/host/model-pin.ts +185 -25
  229. package/src/sdk/host/sdk-run-capability.ts +1 -0
  230. package/src/sdk/host/session-runtime.test.ts +581 -43
  231. package/src/sdk/host/session-runtime.ts +655 -101
  232. package/src/sdk/lifecycle/broker-client.ts +28 -0
  233. package/src/sdk/lifecycle/service.ts +279 -20
  234. package/src/sdk/lifecycle-session.ts +10 -2
  235. package/src/sdk/prompt-deadline-manager.ts +16 -1
  236. package/src/sdk/protocol/adapter-validation.ts +27 -0
  237. package/src/sdk/protocol/operation-inventory.generated.json +53 -2
  238. package/src/sdk/protocol/operation-registry.ts +17 -4
  239. package/src/sdk/router/session-router.ts +294 -154
  240. package/src/sdk/session.ts +440 -167
  241. package/src/sdk/startup-capability.ts +1 -0
  242. package/src/session/account-inventory.ts +27 -11
  243. package/src/session/agent-session.ts +1292 -180
  244. package/src/session/blob-store.ts +15 -5
  245. package/src/session/credential-store-errors.ts +2 -0
  246. package/src/session/fold-coordinator.ts +433 -0
  247. package/src/session/internal/managed-session-storage.ts +153 -78
  248. package/src/session/sdk-run-capability-internal.ts +15 -0
  249. package/src/session/sdk-run-capability.ts +2 -0
  250. package/src/session/session-manager.ts +342 -28
  251. package/src/session/yield-queue.ts +24 -10
  252. package/src/setup/frictionless-onboarding.ts +2 -0
  253. package/src/setup/provider-presets.json +1 -1
  254. package/src/slash-commands/helpers/credential-switch.ts +6 -1
  255. package/src/task/executor.ts +19 -3
  256. package/src/telemetry/control.ts +26 -0
  257. package/src/telemetry/events.ts +1268 -0
  258. package/src/telemetry/index.ts +3 -0
  259. package/src/telemetry/transport.ts +87 -0
  260. package/src/tools/bash-interactive.ts +182 -85
  261. package/src/tools/bash.ts +1028 -109
  262. package/src/tools/gh.ts +104 -48
  263. package/src/tools/github-cache.ts +2 -2
  264. package/src/tools/index.ts +11 -5
  265. package/src/tools/tool-catalog.generated.ts +1 -1
  266. package/src/web/search/provider.ts +120 -11
  267. package/src/web/search/providers/anthropic.ts +89 -17
  268. package/src/web/search/providers/base.ts +9 -4
  269. package/src/web/search/providers/gemini.ts +87 -15
  270. package/src/web/search/providers/openai-compatible.ts +23 -8
  271. package/src/web/search/providers/xai.ts +98 -12
  272. package/src/web/search/types.ts +16 -0
@@ -2,24 +2,39 @@ import { randomBytes } from "node:crypto";
2
2
  import * as fsSync from "node:fs";
3
3
  import * as fs from "node:fs/promises";
4
4
  import * as path from "node:path";
5
+ import { replaceTabs, truncateToWidth } from "@gajae-code/tui";
5
6
  import { getAgentDir } from "@gajae-code/utils";
6
7
  import { repo as resolveGitRepository } from "../../utils/git";
7
8
  import { ensureBroker } from "../broker/ensure";
9
+ import { resolveSessionLocator } from "../broker/session-index";
10
+ import {
11
+ resolveScopeRequest,
12
+ type ScopeNameV1,
13
+ type ScopeRequestV1,
14
+ type SdkSearchResultV1,
15
+ type SdkSearchRowV1,
16
+ } from "../broker/session-scope";
8
17
  import { lifecycleRequestTimeoutMs } from "../broker/startup-budget";
9
- import { SdkClientError } from "../client";
18
+ import { readSdkBrokerDiscovery, SdkClient, SdkClientError } from "../client";
10
19
  import { createBrokerSessionLifecycleService } from "../lifecycle/broker-client";
11
20
  import type {
12
21
  SessionLifecycleMutationRequest,
13
22
  SessionLifecycleOperation,
14
23
  SessionLifecycleSavedSession,
15
24
  SessionLifecycleSavedSessionIdentity,
25
+ SessionLifecycleService,
16
26
  SessionReconcileUncertainTarget,
17
27
  } from "../lifecycle/service";
18
28
  import { PROMPT_CLIENT_REF_MAX_LENGTH } from "../prompt-status";
19
- import { validateAdapterControl, validateAdapterSecretFields } from "../protocol/adapter-validation";
29
+ import {
30
+ validateAdapterControl,
31
+ validateAdapterSecretFields,
32
+ validateRequiredPromptText,
33
+ } from "../protocol/adapter-validation";
20
34
  import { adapterDispositionError, findOperation, type OperationKind } from "../protocol/operation-registry";
21
35
  import { type SessionAttachment, SessionRouter, SessionRouterError, type SessionRouterFrame } from "../router";
22
36
  import { SessionListTraversalError, sessionListPageFromResponse, traverseSessionList } from "../session-list";
37
+ import { SESSION_REQUEST_TIMEOUT_MS } from "../session-reconnect";
23
38
  import {
24
39
  type SdkCheckpointRecordV1,
25
40
  type SdkRetentionGapV1,
@@ -35,6 +50,7 @@ import {
35
50
 
36
51
  export type SdkSessionCliAction =
37
52
  | "list"
53
+ | "search"
38
54
  | "inspect"
39
55
  | "send"
40
56
  | "status"
@@ -60,7 +76,6 @@ export interface SdkSessionCliArgs {
60
76
  jsonInputFile?: string;
61
77
  jsonInputStdin?: boolean;
62
78
  idempotencyKey?: string;
63
- scope?: string;
64
79
  confirm?: boolean;
65
80
  cursor?: string;
66
81
  wait?: boolean;
@@ -69,6 +84,10 @@ export interface SdkSessionCliArgs {
69
84
  untilIdle?: boolean;
70
85
  allEvents?: boolean;
71
86
  repo?: string;
87
+ scope?: string;
88
+ limit?: number;
89
+ json?: boolean;
90
+
72
91
  agentDir?: string;
73
92
  }
74
93
 
@@ -91,6 +110,9 @@ const TAIL_OFFLINE_MAX_SCAN_BYTES = 4 * 1024 * 1024;
91
110
  const TAIL_OFFLINE_MAX_SCANNED_LINES = 4_096;
92
111
  const TAIL_OFFLINE_MAX_LINE_BYTES = 256 * 1024;
93
112
  const transcriptDecoder = new TextDecoder("utf-8", { fatal: true });
113
+ const SEARCH_PROBE_TIMEOUT_MS = 2_000;
114
+ const SEARCH_PROBE_MAX_ROWS = 100;
115
+ const SEARCH_TEXT_WIDTH = 80;
94
116
 
95
117
  const TERMINAL_TURN_KINDS = new Set(["turn_end", "agent_end"]);
96
118
  const START_TURN_KINDS = new Set(["turn_start", "agent_start"]);
@@ -210,6 +232,10 @@ function isEndpointOperation(operation: string): boolean {
210
232
  return operation === "session.get_endpoint";
211
233
  }
212
234
 
235
+ function isRawSpawnOperation(kind: OperationKind, operation: string): boolean {
236
+ return kind === "global" && operation === "session.spawn";
237
+ }
238
+
213
239
  function isLifecycleOperation(operation: string): operation is LifecycleMutationOperation {
214
240
  return (
215
241
  operation === "session.create" ||
@@ -253,9 +279,11 @@ async function withRouter<T>(
253
279
  agentDir: string,
254
280
  action: (router: SessionRouter) => Promise<T>,
255
281
  onFrame?: (attachment: SessionAttachment, frame: SessionRouterFrame) => void,
282
+ sessionIds?: readonly string[],
256
283
  ): Promise<T> {
257
284
  const router = new SessionRouter({
258
285
  agentDir,
286
+ ...(sessionIds === undefined ? {} : { sessionIds }),
259
287
  ...(onFrame === undefined ? {} : { deps: { onFrame } }),
260
288
  });
261
289
  let result!: T;
@@ -363,6 +391,145 @@ async function sessionRows(agentDir: string): Promise<SessionRows> {
363
391
  });
364
392
  }
365
393
 
394
+ function searchScopeRequest(scope: ScopeNameV1, locator: { cwd: string; worktreeRoot: string | null }): ScopeRequestV1 {
395
+ return { version: 1, requested: scope, requestAnchor: { cwd: locator.cwd, worktreeRoot: locator.worktreeRoot } };
396
+ }
397
+
398
+ function searchProbe(row: SdkSearchRowV1, router: SessionRouter): Promise<SdkSearchRowV1> {
399
+ if (!row.live) return Promise.resolve({ ...row, probe: "stale" });
400
+ return (async () => {
401
+ try {
402
+ const attachment = router.attachment(row.id);
403
+ if (!attachment) return { ...row, probe: "unreachable" };
404
+ const response = await router.request(
405
+ row.id,
406
+ { type: "query_request", query: "session.checkpoint", input: {} },
407
+ attachment.generation,
408
+ attachment,
409
+ { timeoutMs: SEARCH_PROBE_TIMEOUT_MS },
410
+ );
411
+ return { ...row, probe: response.ok === true ? "reachable" : "unreachable" };
412
+ } catch {
413
+ return { ...row, probe: "unreachable" };
414
+ }
415
+ })();
416
+ }
417
+
418
+ export function mergeProbedSearchRows(
419
+ rows: readonly SdkSearchRowV1[],
420
+ probedRows: readonly SdkSearchRowV1[],
421
+ ): readonly SdkSearchRowV1[] {
422
+ return [...probedRows, ...rows.slice(probedRows.length)];
423
+ }
424
+
425
+ async function probeSearchRows(agentDir: string, result: SdkSearchResultV1): Promise<SdkSearchResultV1> {
426
+ if (result.status === "unavailable" || result.status === "not-in-git-worktree" || result.rows.length === 0)
427
+ return result;
428
+ const rows = result.rows.slice(0, SEARCH_PROBE_MAX_ROWS);
429
+ try {
430
+ const probes = await withRouter(
431
+ agentDir,
432
+ async router => await Promise.all(rows.map(row => searchProbe(row, router))),
433
+ undefined,
434
+ rows.map(row => row.id),
435
+ );
436
+ return { ...result, rows: mergeProbedSearchRows(result.rows, probes) };
437
+ } catch {
438
+ return {
439
+ ...result,
440
+ rows: mergeProbedSearchRows(
441
+ result.rows,
442
+ rows.map(row => ({ ...row, probe: row.live ? "unreachable" : "stale" })),
443
+ ),
444
+ };
445
+ }
446
+ }
447
+
448
+ /** Resolves, lists, and probes only the exact rows selected by the Broker-scoped search. */
449
+ export async function runSdkSearch(
450
+ args: Pick<SdkSessionCliArgs, "agentDir" | "repo" | "scope" | "limit" | "cursor">,
451
+ createService: (agentDir: string) => SessionLifecycleService = createBrokerSessionLifecycleService,
452
+ probe: (agentDir: string, result: SdkSearchResultV1) => Promise<SdkSearchResultV1> = probeSearchRows,
453
+ ): Promise<{ result: SdkSearchResultV1; exitCode: 0 | 1 }> {
454
+ const agentDir = args.agentDir ?? getAgentDir();
455
+ const locator = await resolveSessionLocator(args.repo ?? process.cwd(), agentDir);
456
+ const scope: ScopeNameV1 =
457
+ args.scope === undefined || args.scope === "repo" || args.scope === "pwd" || args.scope === "global"
458
+ ? ((args.scope ?? "repo") as ScopeNameV1)
459
+ : (() => {
460
+ throw new SdkSessionCliError(
461
+ "usage",
462
+ `Invalid search scope "${args.scope}". Expected repo, pwd, or global.`,
463
+ 2,
464
+ );
465
+ })();
466
+ const request = searchScopeRequest(scope, locator);
467
+ const resolved = await resolveScopeRequest(request);
468
+ const outcome = await createService(agentDir).scopedList(request, args.limit, args.cursor);
469
+ if (outcome.ok) {
470
+ if ("rows" in outcome.result) {
471
+ if (outcome.result.status === "not-in-git-worktree") return { result: outcome.result, exitCode: 0 };
472
+ if (outcome.result.status === "unavailable") return { result: outcome.result, exitCode: 1 };
473
+ return { result: await probe(agentDir, outcome.result), exitCode: 0 };
474
+ }
475
+ return {
476
+ result: {
477
+ version: 1,
478
+ scope: resolved,
479
+ status: "unavailable",
480
+ observedAt: new Date().toISOString(),
481
+ rows: [],
482
+ warnings: [],
483
+ error: { code: "malformed_response", message: "broker search returned an unscoped result" },
484
+ },
485
+ exitCode: 1,
486
+ };
487
+ }
488
+ if (!outcome.ok && outcome.result?.status === "unavailable") return { result: outcome.result, exitCode: 1 };
489
+ return {
490
+ result: {
491
+ version: 1,
492
+ scope: resolved,
493
+ status: "unavailable",
494
+ observedAt: new Date().toISOString(),
495
+ rows: [],
496
+ warnings: [],
497
+ error: { code: outcome.error.code, message: outcome.error.message },
498
+ },
499
+ exitCode: 1,
500
+ };
501
+ }
502
+
503
+ function searchScopeLabel(result: SdkSearchResultV1): string {
504
+ const resolved = result.scope.resolved;
505
+ if (resolved === null) return "not-in-git-worktree";
506
+ if (resolved.kind === "repo") return resolved.worktreeRoot;
507
+ if (resolved.kind === "pwd") return resolved.cwd;
508
+ return resolved.visibility;
509
+ }
510
+
511
+ function safeSearchText(value: string): string {
512
+ return truncateToWidth(replaceTabs(value).replaceAll(/[\r\n]/g, " "), SEARCH_TEXT_WIDTH);
513
+ }
514
+
515
+ /** Renders a credential-free scope/status preamble and bounded search table. */
516
+ export function renderSdkSearchTable(result: SdkSearchResultV1): string {
517
+ const lines = [
518
+ `Scope requested: ${result.scope.requested}`,
519
+ `Scope resolved: ${safeSearchText(searchScopeLabel(result))}`,
520
+ `Status: ${result.status}`,
521
+ `Observed at: ${result.observedAt}`,
522
+ ...(result.cursor === undefined ? [] : [`Continuation cursor: ${safeSearchText(result.cursor)}`]),
523
+ ];
524
+ if (result.rows.length === 0) return lines.join("\n");
525
+ lines.push("ID PROBE LIVE CWD");
526
+ for (const row of result.rows)
527
+ lines.push(
528
+ `${safeSearchText(row.id).padEnd(20)} ${(row.probe ?? "-").padEnd(11)} ${String(row.live).padEnd(4)} ${safeSearchText(row.locator.cwd)}`,
529
+ );
530
+ return lines.join("\n");
531
+ }
532
+
366
533
  const SESSION_LIST_SCOPES: readonly SdkSessionListScope[] = ["repo", "cwd", "worktree", "all"];
367
534
  /** Row workspaces whose locator cannot be canonicalized still get one deterministic identity. */
368
535
  const WORKSPACE_IDENTITY_CACHE_LIMIT = 4096;
@@ -483,7 +650,7 @@ export async function filterSessionRowsByScope(
483
650
  const warnings: string[] = [];
484
651
  const sessions: SdkSessionRowV1[] = [];
485
652
  for (const row of rows) {
486
- const identity = await workspaceIdentity(row.locator.repo);
653
+ const identity = await workspaceIdentity(row.locator.cwd);
487
654
  let keep: boolean;
488
655
  if (scope === "cwd") keep = identity.canonicalPath === selection.selection.canonicalPath;
489
656
  else if (scope === "worktree")
@@ -492,7 +659,7 @@ export async function filterSessionRowsByScope(
492
659
  if (keep) sessions.push(row);
493
660
  else if (identity.repoRoot === null && identity.commonDir === null)
494
661
  warnings.push(
495
- `Session ${row.sessionId} workspace "${row.locator.repo}" is outside Git; excluded by scope ${scope}.`,
662
+ `Session ${row.sessionId} workspace "${row.locator.cwd}" is outside Git; excluded by scope ${scope}.`,
496
663
  );
497
664
  }
498
665
  return { sessions, warnings };
@@ -559,7 +726,7 @@ async function requestControl(
559
726
  const attachment = attachmentFor(router, sessionId);
560
727
  const response = await router.request(
561
728
  sessionId,
562
- { type: "control_request", operation, input, confirm: args.confirm === true },
729
+ controlRequestFrame(operation, input, args),
563
730
  attachment.generation,
564
731
  attachment,
565
732
  args.timeoutMs === undefined ? undefined : { timeoutMs: args.timeoutMs },
@@ -568,6 +735,81 @@ async function requestControl(
568
735
  return response;
569
736
  }
570
737
 
738
+ /** Builds the public CLI control envelope without leaking envelope fields into operation input. */
739
+ export function controlRequestFrame(
740
+ operation: string,
741
+ input: JsonRecord,
742
+ args: Pick<SdkSessionCliArgs, "confirm" | "idempotencyKey">,
743
+ ): JsonRecord {
744
+ return {
745
+ type: "control_request",
746
+ operation,
747
+ input,
748
+ confirm: args.confirm === true,
749
+ ...(args.idempotencyKey ? { idempotencyKey: args.idempotencyKey } : {}),
750
+ };
751
+ }
752
+
753
+ const BROKER_OPERATOR_ABORT_FIELDS = new Set(["mode", "scope", "operator"]);
754
+
755
+ export function operatorAbortBrokerRequest(
756
+ sessionId: string,
757
+ operation: string,
758
+ input: JsonRecord,
759
+ args: Pick<SdkSessionCliArgs, "confirm" | "idempotencyKey">,
760
+ ): JsonRecord | undefined {
761
+ if (!Object.hasOwn(input, "operator")) return undefined;
762
+ if (operation !== "turn.abort")
763
+ throw new SdkSessionCliError("invalid_input", "operator is only available for terminal turn.abort.", 2);
764
+ if (input.operator !== true)
765
+ throw new SdkSessionCliError("invalid_input", "turn.abort operator must be true when provided.", 2);
766
+ if (input.mode !== "terminal")
767
+ throw new SdkSessionCliError("invalid_input", 'operator turn.abort requires mode:"terminal".', 2);
768
+ for (const key of Object.keys(input))
769
+ if (!BROKER_OPERATOR_ABORT_FIELDS.has(key))
770
+ throw new SdkSessionCliError(`invalid_input`, `Unknown turn.abort terminal field: ${key}`, 2);
771
+ if (input.scope !== undefined && input.scope !== "turn" && input.scope !== "owned")
772
+ throw new SdkSessionCliError("invalid_input", 'operator turn.abort scope must be "turn" or "owned".', 2);
773
+ if (args.confirm !== true)
774
+ throw new SdkSessionCliError("invalid_input", "operator terminal abort requires --confirm.", 2);
775
+ if (!args.idempotencyKey)
776
+ throw new SdkSessionCliError("invalid_input", "operator terminal abort requires --idempotency-key.", 2);
777
+ return { sessionId, operation: "turn.abort", input, confirm: true };
778
+ }
779
+
780
+ async function requestBrokerOperatorAbort(
781
+ agentDir: string,
782
+ request: JsonRecord,
783
+ args: SdkSessionCliArgs,
784
+ ): Promise<JsonRecord> {
785
+ const idempotencyKey = args.idempotencyKey;
786
+ if (!idempotencyKey)
787
+ throw new SdkSessionCliError("invalid_input", "operator terminal abort requires --idempotency-key.", 2);
788
+ const discovery = await readSdkBrokerDiscovery(agentDir);
789
+ if (!discovery) throw new SdkSessionCliError("session_unavailable", "SDK broker discovery is unavailable.", 1);
790
+ const timeoutMs = args.timeoutMs ?? SESSION_REQUEST_TIMEOUT_MS;
791
+ const client = await SdkClient.connect(discovery.url, discovery.token, {
792
+ timeoutMs,
793
+ reconnectAttempts: 0,
794
+ });
795
+ try {
796
+ const response = await client.global("session.control", request, {
797
+ idempotencyKey,
798
+ timeoutMs,
799
+ });
800
+ const result = object(response);
801
+ if (!result) throw new SdkClientError("protocol_error", "SDK broker returned a malformed control response.");
802
+ throwResponseFailure(result);
803
+ return result;
804
+ } finally {
805
+ await client.close().catch(error => {
806
+ process.stderr.write(
807
+ `SDK broker control cleanup failed: ${error instanceof Error ? error.message : String(error)}\n`,
808
+ );
809
+ });
810
+ }
811
+ }
812
+
571
813
  async function requestQuery(
572
814
  router: SessionRouter,
573
815
  sessionId: string,
@@ -654,6 +896,8 @@ async function runSend(agentDir: string, sessionId: string, args: SdkSessionCliA
654
896
  if (args.text !== undefined && Object.keys(input).length > 0)
655
897
  throw new SdkSessionCliError("usage", "Use either --text or one JSON input source for the prompt, not both.", 2);
656
898
  const promptInput: JsonRecord = args.text === undefined ? { ...input } : { text: args.text };
899
+ const promptError = validateRequiredPromptText("turn.prompt", promptInput);
900
+ if (promptError) throw new SdkSessionCliError(promptError.code, promptError.message, 2);
657
901
  const inputRef = clientRefFromInput(promptInput);
658
902
  const clientRef = inputRef ?? args.opRef?.trim() ?? createOperationRef();
659
903
  if (args.opRef !== undefined && inputRef !== undefined && inputRef !== args.opRef.trim())
@@ -914,6 +1158,11 @@ function publicationSequence(publicationId: string): number | undefined {
914
1158
 
915
1159
  function tailItemFromRouterFrame(frame: SessionRouterFrame): SdkTailItemV1 | undefined {
916
1160
  if (frame.name === undefined) return undefined;
1161
+ // The replay response is an out-of-band control frame, not an event-ring
1162
+ // item. Its generation/lastSeq metadata is a response position, and it
1163
+ // intentionally has no event `seq`; projecting it as a tail item would make
1164
+ // the strict event-position validator reject every otherwise valid tail.
1165
+ if (frame.name === "event_replay_result") return undefined;
917
1166
  const seq = frame.publicationId === undefined ? undefined : publicationSequence(frame.publicationId);
918
1167
  return toTailItemV1(
919
1168
  {
@@ -1110,6 +1359,12 @@ async function offlineTailReplay(
1110
1359
  });
1111
1360
  if (!outcome.ok)
1112
1361
  throw new SdkSessionCliError(outcome.error.code, outcome.error.message, 1, { certainty: outcome.certainty });
1362
+ if ("rows" in outcome.result)
1363
+ throw new SdkSessionCliError(
1364
+ "malformed_response",
1365
+ "broker returned a scoped result for retained transcript replay",
1366
+ 1,
1367
+ );
1113
1368
  const savedSession = outcome.result.savedSession;
1114
1369
  if (!savedSession)
1115
1370
  throw new SdkSessionCliError(
@@ -1402,6 +1657,8 @@ async function runRawControl(
1402
1657
  const invalid = validateAdapterControl(operation, input);
1403
1658
  if (invalid) throw new SdkSessionCliError(invalid.code, invalid.message, 2);
1404
1659
  await ensureBroker({ agentDir });
1660
+ const operatorRequest = operatorAbortBrokerRequest(sessionId, operation, input, args);
1661
+ if (operatorRequest) return await requestBrokerOperatorAbort(agentDir, operatorRequest, args);
1405
1662
  return await withRouter(agentDir, async router => await requestControl(router, sessionId, operation, input, args));
1406
1663
  }
1407
1664
 
@@ -1495,6 +1752,7 @@ export async function runSdkSessionCli(
1495
1752
  const action = args.action;
1496
1753
  if (
1497
1754
  action !== "list" &&
1755
+ action !== "search" &&
1498
1756
  action !== "inspect" &&
1499
1757
  action !== "send" &&
1500
1758
  action !== "status" &&
@@ -1515,6 +1773,12 @@ export async function runSdkSessionCli(
1515
1773
  writeOutput(stripSecretFields(await runList(agentDir, args)));
1516
1774
  return;
1517
1775
  }
1776
+ if (action === "search") {
1777
+ const search = await runSdkSearch(args);
1778
+ writeOutput(args.json === true ? search.result : renderSdkSearchTable(search.result));
1779
+ if (search.exitCode !== 0) setExitCode(search.exitCode);
1780
+ return;
1781
+ }
1518
1782
  if (action === "inspect") {
1519
1783
  writeOutput(stripSecretFields(await runInspect(agentDir, requireValue(args.sessionId, "<sessionId>"))));
1520
1784
  return;
@@ -1566,6 +1830,12 @@ export async function runSdkSessionCli(
1566
1830
  const kind = rawKind(requireValue(action, "<verb>"), args);
1567
1831
  if (!kind) throw new SdkSessionCliError("usage", "raw requires one of: control, query, global.", 2);
1568
1832
  const operation = kind === "query" ? requireValue(args.query, "--query") : requireValue(args.operation, "--op");
1833
+ if (isRawSpawnOperation(kind, operation))
1834
+ throw new SdkSessionCliError(
1835
+ "adapter_operation_prohibited",
1836
+ "session.spawn is unavailable through the SDK session CLI.",
1837
+ 1,
1838
+ );
1569
1839
  const dispositionError = cliOperationError(kind, operation);
1570
1840
  if (dispositionError) throw new SdkSessionCliError(dispositionError.code, dispositionError.message, 1);
1571
1841
  if (isEndpointOperation(operation))
@@ -116,13 +116,13 @@ export interface SdkDispatchContext {
116
116
  readonly generation: number;
117
117
  }
118
118
 
119
- /** Request identity retained after an uncertain send for lifecycle reconciliation. */
120
-
119
+ /** Request identity retained after an uncertain send. */
121
120
  export interface SdkSentRecord {
122
121
  readonly id: string;
123
122
  readonly operation?: string;
124
123
  readonly idempotencyKey?: string;
125
- readonly fingerprint: string;
124
+ /** Present for lifecycle lookups; `session.spawn` replays through its idempotency key. */
125
+ readonly fingerprint?: string;
126
126
  }
127
127
 
128
128
  export type SdkFrameHandler = (frame: SdkFrame) => void;
@@ -473,8 +473,10 @@ export class SdkClient {
473
473
  const sentOperation = typeof serializedFrame.operation === "string" ? serializedFrame.operation : undefined;
474
474
  const sentIdempotencyKey =
475
475
  typeof serializedFrame.idempotencyKey === "string" ? serializedFrame.idempotencyKey : undefined;
476
- const sentFingerprint =
477
- typeof serializedFrame.operation === "string"
476
+ const isSpawn = sentOperation === "session.spawn";
477
+ const sentFingerprint = isSpawn
478
+ ? undefined
479
+ : typeof serializedFrame.operation === "string"
478
480
  ? lifecycleFingerprint(serializedFrame.operation, serializedFrame.input ?? {})
479
481
  : inputFingerprint(serializedFrame.input ?? {});
480
482
  const deferred = Promise.withResolvers<unknown>();
@@ -571,11 +573,15 @@ export class SdkClient {
571
573
  // nothing reached the transport, so the request stays retryable and
572
574
  // non-uncertain with no record retained.
573
575
  pending.sent = true;
576
+ // A spawn claim replays only through its caller-provided idempotency key;
577
+ // it deliberately has no lifecycle fingerprint. Retain the common sent
578
+ // record anyway so an uncertain transport result tells the caller which
579
+ // key can join that durable claim.
574
580
  this.#rememberSentRecord({
575
581
  id,
576
- operation: sentOperation,
577
- idempotencyKey: sentIdempotencyKey,
578
- fingerprint: sentFingerprint,
582
+ ...(sentOperation === undefined ? {} : { operation: sentOperation }),
583
+ ...(sentIdempotencyKey === undefined ? {} : { idempotencyKey: sentIdempotencyKey }),
584
+ ...(sentFingerprint === undefined ? {} : { fingerprint: sentFingerprint }),
579
585
  });
580
586
  try {
581
587
  incarnation.socket.send(serializedRequest);
@@ -35,7 +35,7 @@ export class SdkDiscoveryError extends Error {
35
35
 
36
36
  export type SdkSessionEndpointScope = "default" | "chat";
37
37
 
38
- function endpointDirectory(repo: string, scope: SdkSessionEndpointScope = "default"): string {
38
+ export function endpointDirectory(repo: string, scope: SdkSessionEndpointScope = "default"): string {
39
39
  return scope === "chat" ? path.join(repo, ".gjc", "state", "chat", "sdk") : path.join(repo, ".gjc", "state", "sdk");
40
40
  }
41
41
  function isUsableSessionId(sessionId: string): boolean {
@@ -132,6 +132,8 @@ export async function readSdkSessionEndpoint(
132
132
  if (!isUsableSessionId(sessionId)) return null;
133
133
  const file = path.join(endpointDirectory(repo, scope), `${sessionId}.json`);
134
134
  try {
135
+ const stat = await fs.lstat(file);
136
+ if (!stat.isFile()) return null;
135
137
  return parseEndpoint(sessionId, file, JSON.parse(await fs.readFile(file, "utf8")));
136
138
  } catch (error) {
137
139
  if ((error as NodeJS.ErrnoException).code === "ENOENT") return null;