@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
@@ -0,0 +1,86 @@
1
+ import * as fsSync from "node:fs";
2
+ import * as fs from "node:fs/promises";
3
+
4
+ const MAX_ENDPOINT_BYTES = 4_096n;
5
+
6
+ export type EndpointFileRead = {
7
+ source: string;
8
+ dev: bigint;
9
+ ino: bigint;
10
+ nlink: bigint;
11
+ size: bigint;
12
+ mtimeNs: bigint;
13
+ mtimeMs: number;
14
+ };
15
+
16
+ export type EndpointAuthorityFilesystem = Pick<typeof fs, "open">;
17
+
18
+ export type IndexedEndpointAuthority = {
19
+ endpointMtimeMs?: number;
20
+ endpointFileId?: string;
21
+ };
22
+
23
+ function readFlags(): number {
24
+ return fsSync.constants.O_RDONLY | (fsSync.constants.O_NOFOLLOW ?? 0) | (fsSync.constants.O_NONBLOCK ?? 0);
25
+ }
26
+
27
+ /**
28
+ * Reads one endpoint from the descriptor opened for its pathname. The opened
29
+ * object must remain a small, singly-linked regular file with stable identity
30
+ * and metadata for the whole read; a pathname replacement cannot affect the
31
+ * bytes returned by this function.
32
+ */
33
+ export async function readEndpointFile(
34
+ filePath: string,
35
+ filesystem: EndpointAuthorityFilesystem = fs,
36
+ ): Promise<EndpointFileRead | undefined> {
37
+ let handle: fs.FileHandle | undefined;
38
+ try {
39
+ handle = await filesystem.open(filePath, readFlags());
40
+ const before = await handle.stat({ bigint: true });
41
+ if (!before.isFile() || before.nlink !== 1n || before.size > MAX_ENDPOINT_BYTES) return undefined;
42
+ if (before.size > BigInt(Number.MAX_SAFE_INTEGER)) return undefined;
43
+ const bytes = Buffer.alloc(Number(before.size));
44
+ let offset = 0;
45
+ while (offset < bytes.length) {
46
+ const { bytesRead } = await handle.read(bytes, offset, bytes.length - offset, offset);
47
+ if (bytesRead <= 0) return undefined;
48
+ offset += bytesRead;
49
+ }
50
+ const after = await handle.stat({ bigint: true });
51
+ if (
52
+ before.dev !== after.dev ||
53
+ before.ino !== after.ino ||
54
+ before.nlink !== after.nlink ||
55
+ before.size !== after.size ||
56
+ before.mtimeNs !== after.mtimeNs
57
+ )
58
+ return undefined;
59
+ return {
60
+ source: bytes.toString("utf8"),
61
+ dev: before.dev,
62
+ ino: before.ino,
63
+ nlink: before.nlink,
64
+ size: before.size,
65
+ mtimeNs: before.mtimeNs,
66
+ mtimeMs: Number(before.mtimeNs) / 1_000_000,
67
+ };
68
+ } catch {
69
+ return undefined;
70
+ } finally {
71
+ await handle?.close().catch(() => undefined);
72
+ }
73
+ }
74
+
75
+ /** Match a descriptor-bound endpoint read to the index representation. */
76
+ export function matchesIndexedEndpointFile(
77
+ file: Pick<EndpointFileRead, "dev" | "ino" | "mtimeMs">,
78
+ authority: IndexedEndpointAuthority,
79
+ ): boolean {
80
+ return (
81
+ authority.endpointMtimeMs !== undefined &&
82
+ Number.isFinite(authority.endpointMtimeMs) &&
83
+ Math.abs(file.mtimeMs - authority.endpointMtimeMs) <= 0.001 &&
84
+ (authority.endpointFileId === undefined || authority.endpointFileId === `${file.dev}:${file.ino}`)
85
+ );
86
+ }
@@ -3,9 +3,21 @@ import { randomUUID } from "node:crypto";
3
3
  import type { FileHandle } from "node:fs/promises";
4
4
  import * as fs from "node:fs/promises";
5
5
  import path from "node:path";
6
+ import packageJson from "../../../package.json" with { type: "json" };
7
+ import { SdkClient } from "../client/client";
6
8
  import { type BrokerDiscovery, brokerProcessIncarnation, readBrokerDiscovery } from "./discovery";
7
9
  import { resolveSdkInternalSpawnCommand, type SdkInternalSpawnCommand } from "./runtime";
8
10
  import { BrokerStartupError, clearBrokerStartupFailureMarker, readBrokerStartupFailureMarker } from "./startup-failure";
11
+
12
+ function resolveExpectedBrokerGeneration(): string {
13
+ const v = (packageJson as { version?: unknown }).version;
14
+ return typeof v === "string" && v.length > 0 ? v : "unknown";
15
+ }
16
+
17
+ function isBrokerGenerationCompatible(discovery: BrokerDiscovery | null): boolean {
18
+ if (!discovery) return false;
19
+ return discovery.packageGeneration === resolveExpectedBrokerGeneration();
20
+ }
9
21
  export interface EnsureBrokerSettings {
10
22
  agentDir: string;
11
23
  heartbeatTtlMs?: number;
@@ -232,6 +244,11 @@ function registerBrokerOwner(
232
244
  function brokerSpawnEnvironment(command: SdkInternalSpawnCommand, override?: NodeJS.ProcessEnv): NodeJS.ProcessEnv {
233
245
  const environment = { ...(override ?? command.env) };
234
246
  delete environment.BUN_OPTIONS;
247
+ // The master capability is a transient in-memory dispatch input. A broker
248
+ // cold-started from the master's own Bash environment would otherwise inherit
249
+ // it and pass it on to every substrate child it later launches, so it is
250
+ // stripped at the lifecycle boundary exactly as lifecycle children strip it.
251
+ delete environment.GJC_MASTER_CAPABILITY;
235
252
  if (command.kind === "bun-source") {
236
253
  delete environment.PI_COMPILED;
237
254
  delete environment.GJC_COMPILED;
@@ -243,6 +260,47 @@ function fixtureLeaseUnavailable(): Error {
243
260
  return new Error("fixture_broker_lease_unavailable");
244
261
  }
245
262
 
263
+ const STALE_BROKER_SHUTDOWN_TIMEOUT_MS = 5_000;
264
+ const STALE_BROKER_POLL_MS = 50;
265
+
266
+ type BrokerOwnerIdentity = Pick<BrokerDiscovery, "ownerId" | "pid" | "incarnation">;
267
+
268
+ function sameBrokerOwner(left: BrokerOwnerIdentity | null, right: BrokerOwnerIdentity): boolean {
269
+ return Boolean(
270
+ left && left.ownerId === right.ownerId && left.pid === right.pid && left.incarnation === right.incarnation,
271
+ );
272
+ }
273
+
274
+ async function retireStaleBrokerGeneration(stale: BrokerDiscovery, settings: EnsureBrokerSettings): Promise<void> {
275
+ let shutdownSucceeded = false;
276
+ try {
277
+ const current = await readBrokerDiscovery(settings.agentDir, settings.heartbeatTtlMs);
278
+ if (!current || !sameBrokerOwner(current, stale)) return;
279
+ const client = await SdkClient.connect(current.url, current.token, { timeoutMs: 2_000 });
280
+ try {
281
+ await client.global("broker.shutdown", {});
282
+ shutdownSucceeded = true;
283
+ } finally {
284
+ await client.close().catch(() => {});
285
+ }
286
+ } catch {}
287
+ if (!shutdownSucceeded) {
288
+ try {
289
+ if (brokerProcessIncarnation(stale.pid) === stale.incarnation) {
290
+ try {
291
+ process.kill(stale.pid, "SIGTERM");
292
+ } catch {}
293
+ }
294
+ } catch {}
295
+ }
296
+ // Wait for the stale identity to disappear (owner-fenced: pid+incarnation).
297
+ const deadline = Date.now() + STALE_BROKER_SHUTDOWN_TIMEOUT_MS;
298
+ while (Date.now() < deadline) {
299
+ const current = await readBrokerDiscovery(settings.agentDir, settings.heartbeatTtlMs);
300
+ if (!sameBrokerOwner(current, stale)) break;
301
+ await sleep(STALE_BROKER_POLL_MS);
302
+ }
303
+ }
246
304
  function createFixtureLeaseFromChild(child: ChildProcess, terminate: () => Promise<void>): ExactFixtureBrokerLease {
247
305
  let termination: Promise<void> | undefined;
248
306
  const hasExited = (): boolean => child.exitCode !== null || child.signalCode !== null || child.pid === undefined;
@@ -295,11 +353,24 @@ async function ensureBrokerOnce(settings: EnsureBrokerSettings, initiator: Ensur
295
353
  if (priorOwner.canReuse(existing)) return { kind: "prior-local-owner", discovery: existing!, owner: priorOwner };
296
354
  await priorOwner.stop();
297
355
  const discoveredAfterCleanup = await readBrokerDiscovery(settings.agentDir, settings.heartbeatTtlMs);
298
- if (discoveredAfterCleanup) return { kind: "external-discovery", discovery: discoveredAfterCleanup };
356
+ if (discoveredAfterCleanup && isBrokerGenerationCompatible(discoveredAfterCleanup))
357
+ return { kind: "external-discovery", discovery: discoveredAfterCleanup };
358
+ // A stale-generation discovery after cleanup must not be reused — fall through to replacement.
359
+ if (!discoveredAfterCleanup) {
360
+ // no discovery left after cleanup — fall through to spawn
361
+ } else {
362
+ await retireStaleBrokerGeneration(discoveredAfterCleanup, settings);
363
+ const afterRetire = await readBrokerDiscovery(settings.agentDir, settings.heartbeatTtlMs);
364
+ if (afterRetire && isBrokerGenerationCompatible(afterRetire))
365
+ return { kind: "external-discovery", discovery: afterRetire };
366
+ }
299
367
  } else if (existing) {
300
- return { kind: "external-discovery", discovery: existing };
368
+ if (isBrokerGenerationCompatible(existing)) return { kind: "external-discovery", discovery: existing };
369
+ await retireStaleBrokerGeneration(existing, settings);
370
+ const afterRetire = await readBrokerDiscovery(settings.agentDir, settings.heartbeatTtlMs);
371
+ if (afterRetire && isBrokerGenerationCompatible(afterRetire))
372
+ return { kind: "external-discovery", discovery: afterRetire };
301
373
  }
302
-
303
374
  const command = resolveSdkInternalSpawnCommand("broker-internal");
304
375
  const spawnLog = await openBrokerSpawnLog(settings.agentDir);
305
376
  // A stale marker must never be misattributed to this spawn; clear it first.
@@ -472,6 +543,13 @@ export function startFixtureBrokerCommandWithLeaseForTest(command: FixtureBroker
472
543
  export function brokerOwnerForTest(agentDir: string): BrokerOwner | undefined {
473
544
  return owners.get(agentDir);
474
545
  }
546
+ /** Test hook: verifies the complete broker owner identity used during stale retirement. */
547
+ export function brokerOwnerIdentityMatchesForTest(
548
+ left: BrokerOwnerIdentity | null,
549
+ right: BrokerOwnerIdentity,
550
+ ): boolean {
551
+ return sameBrokerOwner(left, right);
552
+ }
475
553
  /** Test hook: drives the detached-broker reap on a controllable child surface. */
476
554
  export function reapSpawnedBrokerForTest(child: ChildProcess, timing: ReapTiming = DEFAULT_REAP_TIMING): Promise<void> {
477
555
  return reapSpawnedBroker(child, timing);
@@ -4,6 +4,9 @@ export * from "./ensure";
4
4
  export * from "./identity";
5
5
  export * from "./lifecycle";
6
6
  export * from "./lifecycle-ledger";
7
+ export * from "./master-capability";
7
8
  export * from "./session-index";
9
+ export * from "./spawn-authority";
10
+ export * from "./spawn-substrate";
8
11
  export * from "./startup-budget";
9
12
  export * from "./transport";
@@ -55,6 +55,7 @@ export interface LifecycleDurableEffectsReceipt {
55
55
  cwdDigest: string;
56
56
  created: boolean;
57
57
  reused: boolean;
58
+ createdBranch: boolean;
58
59
  branchDigest?: string;
59
60
  };
60
61
  transcript?: {
@@ -62,6 +63,11 @@ export interface LifecycleDurableEffectsReceipt {
62
63
  contentDigest: string;
63
64
  };
64
65
  startup?: LifecycleStartupFailureReceipt;
66
+ timings?: {
67
+ worktreePreparationMs?: number;
68
+ dependencyPreparationMs?: number;
69
+ spawnAuthorizedAtOffsetMs?: number;
70
+ };
65
71
  digest?: string;
66
72
  }
67
73