@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
@@ -2,17 +2,27 @@ 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";
@@ -24,6 +34,7 @@ import {
24
34
  import { adapterDispositionError, findOperation, type OperationKind } from "../protocol/operation-registry";
25
35
  import { type SessionAttachment, SessionRouter, SessionRouterError, type SessionRouterFrame } from "../router";
26
36
  import { SessionListTraversalError, sessionListPageFromResponse, traverseSessionList } from "../session-list";
37
+ import { SESSION_REQUEST_TIMEOUT_MS } from "../session-reconnect";
27
38
  import {
28
39
  type SdkCheckpointRecordV1,
29
40
  type SdkRetentionGapV1,
@@ -39,6 +50,7 @@ import {
39
50
 
40
51
  export type SdkSessionCliAction =
41
52
  | "list"
53
+ | "search"
42
54
  | "inspect"
43
55
  | "send"
44
56
  | "status"
@@ -64,7 +76,6 @@ export interface SdkSessionCliArgs {
64
76
  jsonInputFile?: string;
65
77
  jsonInputStdin?: boolean;
66
78
  idempotencyKey?: string;
67
- scope?: string;
68
79
  confirm?: boolean;
69
80
  cursor?: string;
70
81
  wait?: boolean;
@@ -73,6 +84,10 @@ export interface SdkSessionCliArgs {
73
84
  untilIdle?: boolean;
74
85
  allEvents?: boolean;
75
86
  repo?: string;
87
+ scope?: string;
88
+ limit?: number;
89
+ json?: boolean;
90
+
76
91
  agentDir?: string;
77
92
  }
78
93
 
@@ -95,6 +110,9 @@ const TAIL_OFFLINE_MAX_SCAN_BYTES = 4 * 1024 * 1024;
95
110
  const TAIL_OFFLINE_MAX_SCANNED_LINES = 4_096;
96
111
  const TAIL_OFFLINE_MAX_LINE_BYTES = 256 * 1024;
97
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;
98
116
 
99
117
  const TERMINAL_TURN_KINDS = new Set(["turn_end", "agent_end"]);
100
118
  const START_TURN_KINDS = new Set(["turn_start", "agent_start"]);
@@ -214,6 +232,10 @@ function isEndpointOperation(operation: string): boolean {
214
232
  return operation === "session.get_endpoint";
215
233
  }
216
234
 
235
+ function isRawSpawnOperation(kind: OperationKind, operation: string): boolean {
236
+ return kind === "global" && operation === "session.spawn";
237
+ }
238
+
217
239
  function isLifecycleOperation(operation: string): operation is LifecycleMutationOperation {
218
240
  return (
219
241
  operation === "session.create" ||
@@ -257,9 +279,11 @@ async function withRouter<T>(
257
279
  agentDir: string,
258
280
  action: (router: SessionRouter) => Promise<T>,
259
281
  onFrame?: (attachment: SessionAttachment, frame: SessionRouterFrame) => void,
282
+ sessionIds?: readonly string[],
260
283
  ): Promise<T> {
261
284
  const router = new SessionRouter({
262
285
  agentDir,
286
+ ...(sessionIds === undefined ? {} : { sessionIds }),
263
287
  ...(onFrame === undefined ? {} : { deps: { onFrame } }),
264
288
  });
265
289
  let result!: T;
@@ -367,6 +391,145 @@ async function sessionRows(agentDir: string): Promise<SessionRows> {
367
391
  });
368
392
  }
369
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
+
370
533
  const SESSION_LIST_SCOPES: readonly SdkSessionListScope[] = ["repo", "cwd", "worktree", "all"];
371
534
  /** Row workspaces whose locator cannot be canonicalized still get one deterministic identity. */
372
535
  const WORKSPACE_IDENTITY_CACHE_LIMIT = 4096;
@@ -450,14 +613,15 @@ async function workspaceIdentity(target: string): Promise<WorkspaceIdentity> {
450
613
  /**
451
614
  * Resolves the list selection from `--repo` (default: process cwd) under the
452
615
  * effective scope. Outside Git, `repo`/`worktree` fail typed and actionable —
453
- * they never broaden to `all`; `cwd` remains an exact canonical match.
616
+ * they never broaden to `all`; `cwd` remains an exact canonical match, and
617
+ * `all` ignores the selection entirely so it works outside any checkout.
454
618
  */
455
619
  export async function resolveSessionListSelection(
456
620
  scope: SdkSessionListScope,
457
621
  repoArg: string | undefined,
458
622
  ): Promise<SdkSessionListSelection> {
459
623
  const selection = await workspaceIdentity(repoArg ?? process.cwd());
460
- if (scope !== "cwd" && !selection.repoRoot)
624
+ if (scope !== "cwd" && scope !== "all" && !selection.repoRoot)
461
625
  throw new SdkSessionCliError(
462
626
  "not_a_repository",
463
627
  `--scope ${scope} requires a Git repository, but "${selection.canonicalPath}" is outside any Git checkout. ` +
@@ -487,7 +651,7 @@ export async function filterSessionRowsByScope(
487
651
  const warnings: string[] = [];
488
652
  const sessions: SdkSessionRowV1[] = [];
489
653
  for (const row of rows) {
490
- const identity = await workspaceIdentity(row.locator.repo);
654
+ const identity = await workspaceIdentity(row.locator.cwd);
491
655
  let keep: boolean;
492
656
  if (scope === "cwd") keep = identity.canonicalPath === selection.selection.canonicalPath;
493
657
  else if (scope === "worktree")
@@ -496,7 +660,7 @@ export async function filterSessionRowsByScope(
496
660
  if (keep) sessions.push(row);
497
661
  else if (identity.repoRoot === null && identity.commonDir === null)
498
662
  warnings.push(
499
- `Session ${row.sessionId} workspace "${row.locator.repo}" is outside Git; excluded by scope ${scope}.`,
663
+ `Session ${row.sessionId} workspace "${row.locator.cwd}" is outside Git; excluded by scope ${scope}.`,
500
664
  );
501
665
  }
502
666
  return { sessions, warnings };
@@ -563,7 +727,7 @@ async function requestControl(
563
727
  const attachment = attachmentFor(router, sessionId);
564
728
  const response = await router.request(
565
729
  sessionId,
566
- { type: "control_request", operation, input, confirm: args.confirm === true },
730
+ controlRequestFrame(operation, input, args),
567
731
  attachment.generation,
568
732
  attachment,
569
733
  args.timeoutMs === undefined ? undefined : { timeoutMs: args.timeoutMs },
@@ -572,6 +736,81 @@ async function requestControl(
572
736
  return response;
573
737
  }
574
738
 
739
+ /** Builds the public CLI control envelope without leaking envelope fields into operation input. */
740
+ export function controlRequestFrame(
741
+ operation: string,
742
+ input: JsonRecord,
743
+ args: Pick<SdkSessionCliArgs, "confirm" | "idempotencyKey">,
744
+ ): JsonRecord {
745
+ return {
746
+ type: "control_request",
747
+ operation,
748
+ input,
749
+ confirm: args.confirm === true,
750
+ ...(args.idempotencyKey ? { idempotencyKey: args.idempotencyKey } : {}),
751
+ };
752
+ }
753
+
754
+ const BROKER_OPERATOR_ABORT_FIELDS = new Set(["mode", "scope", "operator"]);
755
+
756
+ export function operatorAbortBrokerRequest(
757
+ sessionId: string,
758
+ operation: string,
759
+ input: JsonRecord,
760
+ args: Pick<SdkSessionCliArgs, "confirm" | "idempotencyKey">,
761
+ ): JsonRecord | undefined {
762
+ if (!Object.hasOwn(input, "operator")) return undefined;
763
+ if (operation !== "turn.abort")
764
+ throw new SdkSessionCliError("invalid_input", "operator is only available for terminal turn.abort.", 2);
765
+ if (input.operator !== true)
766
+ throw new SdkSessionCliError("invalid_input", "turn.abort operator must be true when provided.", 2);
767
+ if (input.mode !== "terminal")
768
+ throw new SdkSessionCliError("invalid_input", 'operator turn.abort requires mode:"terminal".', 2);
769
+ for (const key of Object.keys(input))
770
+ if (!BROKER_OPERATOR_ABORT_FIELDS.has(key))
771
+ throw new SdkSessionCliError(`invalid_input`, `Unknown turn.abort terminal field: ${key}`, 2);
772
+ if (input.scope !== undefined && input.scope !== "turn" && input.scope !== "owned")
773
+ throw new SdkSessionCliError("invalid_input", 'operator turn.abort scope must be "turn" or "owned".', 2);
774
+ if (args.confirm !== true)
775
+ throw new SdkSessionCliError("invalid_input", "operator terminal abort requires --confirm.", 2);
776
+ if (!args.idempotencyKey)
777
+ throw new SdkSessionCliError("invalid_input", "operator terminal abort requires --idempotency-key.", 2);
778
+ return { sessionId, operation: "turn.abort", input, confirm: true };
779
+ }
780
+
781
+ async function requestBrokerOperatorAbort(
782
+ agentDir: string,
783
+ request: JsonRecord,
784
+ args: SdkSessionCliArgs,
785
+ ): Promise<JsonRecord> {
786
+ const idempotencyKey = args.idempotencyKey;
787
+ if (!idempotencyKey)
788
+ throw new SdkSessionCliError("invalid_input", "operator terminal abort requires --idempotency-key.", 2);
789
+ const discovery = await readSdkBrokerDiscovery(agentDir);
790
+ if (!discovery) throw new SdkSessionCliError("session_unavailable", "SDK broker discovery is unavailable.", 1);
791
+ const timeoutMs = args.timeoutMs ?? SESSION_REQUEST_TIMEOUT_MS;
792
+ const client = await SdkClient.connect(discovery.url, discovery.token, {
793
+ timeoutMs,
794
+ reconnectAttempts: 0,
795
+ });
796
+ try {
797
+ const response = await client.global("session.control", request, {
798
+ idempotencyKey,
799
+ timeoutMs,
800
+ });
801
+ const result = object(response);
802
+ if (!result) throw new SdkClientError("protocol_error", "SDK broker returned a malformed control response.");
803
+ throwResponseFailure(result);
804
+ return result;
805
+ } finally {
806
+ await client.close().catch(error => {
807
+ process.stderr.write(
808
+ `SDK broker control cleanup failed: ${error instanceof Error ? error.message : String(error)}\n`,
809
+ );
810
+ });
811
+ }
812
+ }
813
+
575
814
  async function requestQuery(
576
815
  router: SessionRouter,
577
816
  sessionId: string,
@@ -731,11 +970,7 @@ function extractCheckpoint(response: unknown): CheckpointExtraction {
731
970
  if (gap !== undefined) return { gap };
732
971
  const record = toCheckpointRecordV1(result.checkpoint ?? result);
733
972
  const cursor =
734
- typeof result.cursor === "string" && result.cursor
735
- ? result.cursor
736
- : typeof result.checkpointToken === "string" && result.checkpointToken
737
- ? result.checkpointToken
738
- : undefined;
973
+ typeof result.checkpointToken === "string" && result.checkpointToken ? result.checkpointToken : undefined;
739
974
  return { record, cursor };
740
975
  }
741
976
 
@@ -912,12 +1147,6 @@ function eventGapToRetentionGap(
912
1147
  return undefined;
913
1148
  }
914
1149
 
915
- function publicationSequence(publicationId: string): number | undefined {
916
- const parts = publicationId.split(":");
917
- const value = Number(parts.at(-1));
918
- return Number.isSafeInteger(value) && value >= 0 ? value : undefined;
919
- }
920
-
921
1150
  function tailItemFromRouterFrame(frame: SessionRouterFrame): SdkTailItemV1 | undefined {
922
1151
  if (frame.name === undefined) return undefined;
923
1152
  // The replay response is an out-of-band control frame, not an event-ring
@@ -925,18 +1154,16 @@ function tailItemFromRouterFrame(frame: SessionRouterFrame): SdkTailItemV1 | und
925
1154
  // intentionally has no event `seq`; projecting it as a tail item would make
926
1155
  // the strict event-position validator reject every otherwise valid tail.
927
1156
  if (frame.name === "event_replay_result") return undefined;
928
- const seq = frame.publicationId === undefined ? undefined : publicationSequence(frame.publicationId);
1157
+ const seq = frame.seq;
929
1158
  return toTailItemV1(
930
1159
  {
931
1160
  kind: frame.name,
932
- ...(frame.generation === undefined ? {} : { generation: frame.generation }),
933
- ...(seq === undefined ? {} : { seq }),
1161
+ ...(seq === undefined ? {} : { generation: frame.generation, seq }),
934
1162
  payload: frame.body,
935
1163
  },
936
1164
  {
937
1165
  kind: frame.name,
938
- ...(frame.generation === undefined ? {} : { generation: frame.generation }),
939
- ...(seq === undefined ? {} : { seq }),
1166
+ ...(seq === undefined ? {} : { generation: frame.generation, seq }),
940
1167
  },
941
1168
  );
942
1169
  }
@@ -1121,6 +1348,12 @@ async function offlineTailReplay(
1121
1348
  });
1122
1349
  if (!outcome.ok)
1123
1350
  throw new SdkSessionCliError(outcome.error.code, outcome.error.message, 1, { certainty: outcome.certainty });
1351
+ if ("rows" in outcome.result)
1352
+ throw new SdkSessionCliError(
1353
+ "malformed_response",
1354
+ "broker returned a scoped result for retained transcript replay",
1355
+ 1,
1356
+ );
1124
1357
  const savedSession = outcome.result.savedSession;
1125
1358
  if (!savedSession)
1126
1359
  throw new SdkSessionCliError(
@@ -1144,7 +1377,9 @@ async function offlineTailReplay(
1144
1377
  version: SESSION_ROWS_VERSION,
1145
1378
  source: "offline",
1146
1379
  session: row,
1147
- items: entries.map((entry, index) => toTailItemV1(entry, { kind: "transcript", seq: index })),
1380
+ items: entries.map((entry, index) =>
1381
+ toTailItemV1(entry, { kind: "transcript", revision: index + 1, seq: index }),
1382
+ ),
1148
1383
  terminal: true,
1149
1384
  },
1150
1385
  };
@@ -1252,6 +1487,8 @@ async function runLiveTail(
1252
1487
  if (attachment.sessionId !== sessionId) return;
1253
1488
  const item = tailItemFromRouterFrame(frame);
1254
1489
  if (!item) return;
1490
+ if (item.revision === undefined && checkpoint?.revision !== undefined) item.revision = checkpoint.revision;
1491
+ // If checkpoint was undefined, queue for later stamping? For now leave without rev
1255
1492
  applyLifecycle(mergeEventTailItems(eventItems, seenEvents, [item], include));
1256
1493
  };
1257
1494
 
@@ -1293,10 +1530,18 @@ async function runLiveTail(
1293
1530
  );
1294
1531
  throwResponseFailure(response);
1295
1532
  const page = extractTranscriptPage(response);
1533
+ const rev = checkpoint?.revision;
1534
+ let nextSeq = transcriptItems.length;
1296
1535
  mergeTailItems(
1297
1536
  transcriptItems,
1298
1537
  seenTranscript,
1299
- page.items.map(item => toTailItemV1(item, { kind: "transcript" })),
1538
+ page.items.map(item => {
1539
+ const it = toTailItemV1(item, { kind: "transcript" });
1540
+ if (it.revision === undefined && rev !== undefined) it.revision = rev;
1541
+ if (it.generation === undefined) it.generation = checkpoint?.generation ?? 0;
1542
+ if (it.seq === undefined) it.seq = nextSeq++;
1543
+ return it;
1544
+ }),
1300
1545
  include,
1301
1546
  );
1302
1547
  if (page.complete || page.cursor === undefined) break;
@@ -1342,9 +1587,20 @@ async function runLiveTail(
1342
1587
  raw.seq,
1343
1588
  );
1344
1589
  }
1345
- const replayItems = rawEvents.map(event => toTailItemV1(event, { kind: "event" }));
1590
+ const replayItems = rawEvents.map(event => {
1591
+ const it = toTailItemV1(event, { kind: "event" });
1592
+ if (it.revision === undefined && checkpoint?.revision !== undefined) it.revision = checkpoint.revision;
1593
+ return it;
1594
+ });
1346
1595
  applyLifecycle(mergeEventTailItems(eventItems, seenEvents, replayItems, include));
1347
1596
  if (malformed !== undefined) throw malformed;
1597
+ if (checkpoint !== undefined) {
1598
+ const checkpointKey = { generation: checkpoint.generation, seq: checkpoint.seq };
1599
+ if (turnStateKey === undefined || compareTailSeqKeys(checkpointKey, turnStateKey) >= 0) {
1600
+ turnStateKey = checkpointKey;
1601
+ turnIdle = checkpoint.idle;
1602
+ }
1603
+ }
1348
1604
  if (liveReason === undefined && args.untilIdle === true && turnIdle) liveReason = "idle";
1349
1605
  if (liveReason === undefined) {
1350
1606
  const completion = Promise.withResolvers<TailExitReason>();
@@ -1413,6 +1669,8 @@ async function runRawControl(
1413
1669
  const invalid = validateAdapterControl(operation, input);
1414
1670
  if (invalid) throw new SdkSessionCliError(invalid.code, invalid.message, 2);
1415
1671
  await ensureBroker({ agentDir });
1672
+ const operatorRequest = operatorAbortBrokerRequest(sessionId, operation, input, args);
1673
+ if (operatorRequest) return await requestBrokerOperatorAbort(agentDir, operatorRequest, args);
1416
1674
  return await withRouter(agentDir, async router => await requestControl(router, sessionId, operation, input, args));
1417
1675
  }
1418
1676
 
@@ -1506,6 +1764,7 @@ export async function runSdkSessionCli(
1506
1764
  const action = args.action;
1507
1765
  if (
1508
1766
  action !== "list" &&
1767
+ action !== "search" &&
1509
1768
  action !== "inspect" &&
1510
1769
  action !== "send" &&
1511
1770
  action !== "status" &&
@@ -1526,6 +1785,12 @@ export async function runSdkSessionCli(
1526
1785
  writeOutput(stripSecretFields(await runList(agentDir, args)));
1527
1786
  return;
1528
1787
  }
1788
+ if (action === "search") {
1789
+ const search = await runSdkSearch(args);
1790
+ writeOutput(args.json === true ? search.result : renderSdkSearchTable(search.result));
1791
+ if (search.exitCode !== 0) setExitCode(search.exitCode);
1792
+ return;
1793
+ }
1529
1794
  if (action === "inspect") {
1530
1795
  writeOutput(stripSecretFields(await runInspect(agentDir, requireValue(args.sessionId, "<sessionId>"))));
1531
1796
  return;
@@ -1577,6 +1842,12 @@ export async function runSdkSessionCli(
1577
1842
  const kind = rawKind(requireValue(action, "<verb>"), args);
1578
1843
  if (!kind) throw new SdkSessionCliError("usage", "raw requires one of: control, query, global.", 2);
1579
1844
  const operation = kind === "query" ? requireValue(args.query, "--query") : requireValue(args.operation, "--op");
1845
+ if (isRawSpawnOperation(kind, operation))
1846
+ throw new SdkSessionCliError(
1847
+ "adapter_operation_prohibited",
1848
+ "session.spawn is unavailable through the SDK session CLI.",
1849
+ 1,
1850
+ );
1580
1851
  const dispositionError = cliOperationError(kind, operation);
1581
1852
  if (dispositionError) throw new SdkSessionCliError(dispositionError.code, dispositionError.message, 1);
1582
1853
  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);