@gajae-code/coding-agent 0.16.0 → 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 (163) hide show
  1. package/CHANGELOG.md +41 -1
  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/update-cli.d.ts +1 -0
  5. package/dist/types/commands/sdk.d.ts +2 -0
  6. package/dist/types/config/billing-path.d.ts +25 -0
  7. package/dist/types/config/config-schema-version.d.ts +13 -1
  8. package/dist/types/config/model-registry.d.ts +16 -0
  9. package/dist/types/config/settings-schema.d.ts +17 -7
  10. package/dist/types/extensibility/extensions/types.d.ts +8 -3
  11. package/dist/types/gjc-runtime/launch-worktree.d.ts +25 -1
  12. package/dist/types/gjc-runtime/session-state-lock.d.ts +22 -0
  13. package/dist/types/main.d.ts +1 -0
  14. package/dist/types/modes/acp/acp-agent.d.ts +21 -21
  15. package/dist/types/modes/components/model-selector.d.ts +4 -0
  16. package/dist/types/modes/components/session-selector.d.ts +1 -1
  17. package/dist/types/modes/components/sessions-dashboard.d.ts +2 -1
  18. package/dist/types/modes/interactive-mode.d.ts +16 -2
  19. package/dist/types/modes/theme/theme.d.ts +2 -1
  20. package/dist/types/modes/types.d.ts +2 -0
  21. package/dist/types/sdk/broker/broker.d.ts +2 -1
  22. package/dist/types/sdk/broker/ensure.d.ts +3 -0
  23. package/dist/types/sdk/broker/lifecycle-ledger.d.ts +6 -0
  24. package/dist/types/sdk/broker/lifecycle.d.ts +14 -0
  25. package/dist/types/sdk/broker/startup-budget.d.ts +52 -0
  26. package/dist/types/sdk/broker/state-version.d.ts +17 -0
  27. package/dist/types/sdk/bus/discord-daemon.d.ts +1 -1
  28. package/dist/types/sdk/bus/index.d.ts +3 -1
  29. package/dist/types/sdk/bus/kind-aware-reconciliation.d.ts +1 -1
  30. package/dist/types/sdk/bus/prompt-reconciliation.d.ts +1 -1
  31. package/dist/types/sdk/bus/slack-conversation.d.ts +1 -1
  32. package/dist/types/sdk/bus/slack-daemon.d.ts +1 -1
  33. package/dist/types/sdk/bus/telegram-daemon-contract.d.ts +1 -1
  34. package/dist/types/sdk/cli/rows.d.ts +8 -2
  35. package/dist/types/sdk/cli/session-cli.d.ts +2 -1
  36. package/dist/types/sdk/host/events.d.ts +1 -0
  37. package/dist/types/sdk/host/query/cursor.d.ts +4 -2
  38. package/dist/types/sdk/host/query/handlers.d.ts +1 -0
  39. package/dist/types/sdk/host/query/revision-store.d.ts +1 -0
  40. package/dist/types/sdk/host/session-runtime.d.ts +18 -17
  41. package/dist/types/sdk/lifecycle-session.d.ts +1 -1
  42. package/dist/types/sdk/prompt-deadline-manager.d.ts +2 -5
  43. package/dist/types/sdk/receipt-state.d.ts +2 -0
  44. package/dist/types/sdk/router/session-router.d.ts +4 -0
  45. package/dist/types/sdk/turn-result.d.ts +6 -0
  46. package/dist/types/session/agent-session.d.ts +47 -7
  47. package/dist/types/session/fold-coordinator.d.ts +29 -2
  48. package/dist/types/session/session-manager.d.ts +14 -2
  49. package/dist/types/tools/ask.d.ts +1 -0
  50. package/dist/types/tools/bash.d.ts +12 -0
  51. package/dist/types/tools/browser/launch.d.ts +10 -0
  52. package/dist/types/tools/browser/registry.d.ts +8 -0
  53. package/dist/types/tools/index.d.ts +6 -10
  54. package/dist/types/tools/job.d.ts +3 -1
  55. package/dist/types/utils/external-editor.d.ts +7 -0
  56. package/examples/extensions/README.md +1 -1
  57. package/examples/extensions/pirate.ts +7 -4
  58. package/package.json +7 -7
  59. package/scripts/generate-sdk-operation-inventory.ts +7 -1
  60. package/src/async/job-manager.ts +107 -9
  61. package/src/browser-backend.ts +29 -0
  62. package/src/cli/plugin-cli.ts +29 -0
  63. package/src/cli/update-cli.ts +63 -12
  64. package/src/commands/sdk.ts +22 -7
  65. package/src/config/billing-path.ts +42 -0
  66. package/src/config/config-schema-version.ts +18 -1
  67. package/src/config/model-preset-registry.ts +177 -18
  68. package/src/config/model-registry.ts +268 -42
  69. package/src/config/settings-schema.ts +19 -7
  70. package/src/config/settings.ts +46 -15
  71. package/src/coordinator-mcp/projection-scan.ts +6 -6
  72. package/src/coordinator-mcp/server.ts +21 -1
  73. package/src/defaults/gjc/skills/deep-interview/SKILL.md +1 -1
  74. package/src/extensibility/extensions/runner.ts +27 -0
  75. package/src/extensibility/extensions/types.ts +8 -3
  76. package/src/extensibility/gjc-plugins/installer.ts +51 -11
  77. package/src/extensibility/runtime-skill-discovery.ts +110 -3
  78. package/src/extensibility/skills.ts +22 -12
  79. package/src/gjc-runtime/launch-worktree.ts +285 -4
  80. package/src/gjc-runtime/session-state-lock.ts +154 -28
  81. package/src/gjc-runtime/session-state-sidecar.ts +10 -17
  82. package/src/gjc-runtime/tmux-owner-isolation.ts +16 -1
  83. package/src/internal-urls/docs-index.generated.ts +10 -10
  84. package/src/main.ts +4 -1
  85. package/src/modes/acp/acp-agent.ts +5 -5
  86. package/src/modes/components/model-selector.ts +76 -8
  87. package/src/modes/components/session-selector.ts +23 -15
  88. package/src/modes/components/sessions-dashboard.ts +8 -5
  89. package/src/modes/controllers/command-controller.ts +1 -1
  90. package/src/modes/controllers/event-controller.ts +30 -4
  91. package/src/modes/controllers/extension-ui-controller.ts +13 -13
  92. package/src/modes/controllers/input-controller.ts +2 -2
  93. package/src/modes/controllers/selector-controller.ts +10 -3
  94. package/src/modes/interactive-mode.ts +36 -12
  95. package/src/modes/prompt-action-autocomplete.ts +30 -4
  96. package/src/modes/runtime-init.ts +12 -9
  97. package/src/modes/theme/theme.ts +5 -0
  98. package/src/modes/types.ts +2 -0
  99. package/src/modes/utils/ui-helpers.ts +22 -3
  100. package/src/prompts/agent-fragments/browser-aside.md +22 -0
  101. package/src/prompts/tools/ask.md +1 -1
  102. package/src/prompts/tools/bash.md +1 -0
  103. package/src/prompts/tools/job.md +1 -1
  104. package/src/sdk/acp/adapter.ts +5 -0
  105. package/src/sdk/broker/broker.ts +10 -1
  106. package/src/sdk/broker/ensure.ts +76 -3
  107. package/src/sdk/broker/lifecycle-ledger.ts +6 -0
  108. package/src/sdk/broker/lifecycle.ts +254 -34
  109. package/src/sdk/broker/spawn-substrate.ts +45 -2
  110. package/src/sdk/broker/startup-budget.ts +144 -2
  111. package/src/sdk/broker/state-version.ts +17 -0
  112. package/src/sdk/bus/chat-daemon-control.ts +2 -2
  113. package/src/sdk/bus/chat-daemon-runtime.ts +111 -24
  114. package/src/sdk/bus/discord-daemon.ts +50 -10
  115. package/src/sdk/bus/index.ts +33 -3
  116. package/src/sdk/bus/kind-aware-reconciliation.ts +296 -48
  117. package/src/sdk/bus/lifecycle-commands.ts +4 -0
  118. package/src/sdk/bus/notification-service.ts +22 -5
  119. package/src/sdk/bus/prompt-reconciliation.ts +7 -2
  120. package/src/sdk/bus/reconciliation-store.ts +55 -31
  121. package/src/sdk/bus/slack-conversation.ts +1 -1
  122. package/src/sdk/bus/slack-daemon.ts +72 -38
  123. package/src/sdk/bus/telegram-daemon-contract.ts +1 -1
  124. package/src/sdk/cli/rows.ts +30 -7
  125. package/src/sdk/cli/session-cli.ts +33 -21
  126. package/src/sdk/host/control/dispatch.ts +2 -2
  127. package/src/sdk/host/events.ts +1 -0
  128. package/src/sdk/host/host.ts +2 -0
  129. package/src/sdk/host/query/cursor.ts +52 -7
  130. package/src/sdk/host/query/handlers.ts +33 -12
  131. package/src/sdk/host/query/revision-store.ts +12 -0
  132. package/src/sdk/host/session-runtime.test.ts +463 -8
  133. package/src/sdk/host/session-runtime.ts +383 -54
  134. package/src/sdk/host/surface-policy.ts +3 -3
  135. package/src/sdk/lifecycle-session.ts +7 -2
  136. package/src/sdk/prompt-deadline-manager.ts +2 -1
  137. package/src/sdk/protocol/operation-inventory.generated.json +54 -3
  138. package/src/sdk/protocol/operation-registry.ts +1 -1
  139. package/src/sdk/receipt-state.ts +10 -0
  140. package/src/sdk/router/session-router.ts +152 -59
  141. package/src/sdk/session.ts +60 -41
  142. package/src/sdk/turn-result.ts +22 -0
  143. package/src/session/agent-session.ts +1033 -260
  144. package/src/session/fold-coordinator.ts +40 -3
  145. package/src/session/session-manager.ts +517 -77
  146. package/src/slash-commands/builtin-registry.ts +41 -0
  147. package/src/task/executor.ts +9 -4
  148. package/src/tools/ask-contract.test.ts +64 -0
  149. package/src/tools/ask-contract.ts +36 -1
  150. package/src/tools/ask.ts +10 -8
  151. package/src/tools/bash-interactive.ts +11 -2
  152. package/src/tools/bash.ts +156 -33
  153. package/src/tools/browser/launch.ts +37 -0
  154. package/src/tools/browser/registry.ts +99 -9
  155. package/src/tools/cron.ts +6 -4
  156. package/src/tools/descriptors.test.ts +15 -0
  157. package/src/tools/descriptors.ts +5 -1
  158. package/src/tools/index.ts +6 -6
  159. package/src/tools/job.ts +4 -1
  160. package/src/tools/monitor.ts +12 -9
  161. package/src/tools/resolve.ts +30 -10
  162. package/src/tools/tool-catalog.generated.ts +2 -2
  163. package/src/utils/external-editor.ts +72 -1
@@ -26,10 +26,13 @@ import {
26
26
  import { mergeModelProfiles } from "../../config/model-profiles";
27
27
  import { ModelsConfigFile } from "../../config/model-registry";
28
28
  import {
29
- ensureLaunchWorktree,
30
- ensureReusableNodeModules,
29
+ DependencyPreparationTimeoutError,
30
+ ensureLaunchWorktreeCancellable,
31
+ ensureReusableNodeModulesCancellable,
31
32
  type GjcLaunchWorktreePlan,
32
33
  planLaunchWorktree,
34
+ removeOwnedLaunchWorktree,
35
+ WorktreePreparationTimeoutError,
33
36
  } from "../../gjc-runtime/launch-worktree";
34
37
  import { probeLinuxProcPidSync } from "../../gjc-runtime/linux-proc";
35
38
  import {
@@ -91,8 +94,11 @@ import {
91
94
  import {
92
95
  cancellableSleep,
93
96
  DEFAULT_READINESS_TIMEOUT_MS,
97
+ deriveLifecycleOuterDeadlines,
94
98
  isValidReadinessTimeoutMs,
99
+ PREPARATION_TIMEOUT_INVALID_MESSAGE,
95
100
  READINESS_TIMEOUT_INVALID_MESSAGE,
101
+ readPreparationTimeouts,
96
102
  startupQueueWaitMs,
97
103
  } from "./startup-budget";
98
104
  import { worktreeOccupant } from "./worktree-occupancy";
@@ -201,6 +207,17 @@ const lifecycleCommandResolversForTest = new WeakMap<Broker, LifecycleCommandRes
201
207
  const lifecycleCleanupHooksForTest = new WeakMap<Broker, () => void>();
202
208
  const startupAdmittedInputs = new WeakSet<Input>();
203
209
  const startupLaunchInputs = new WeakMap<Input, SessionLaunch>();
210
+ type EnsureLaunchWorktreeForTest = (
211
+ plan: GjcLaunchWorktreePlan,
212
+ opts: { signal: AbortSignal; deadlineAt: number },
213
+ ) => Promise<SessionLifecycleWorktreeReceipt & { createdBranch: boolean }>;
214
+ type EnsureReusableNodeModulesForTest = (
215
+ sourceRoot: string,
216
+ worktreePath: string,
217
+ opts: { signal: AbortSignal; deadlineAt: number },
218
+ ) => Promise<"symlink" | "present" | "missing">;
219
+ let ensureLaunchWorktreeForTest: EnsureLaunchWorktreeForTest | undefined;
220
+ let ensureReusableNodeModulesForTest: EnsureReusableNodeModulesForTest | undefined;
204
221
 
205
222
  /** Test-only hook for simulating a crash immediately after one exact lifecycle detach. */
206
223
  export function setLifecycleCleanupHookForTest(broker: Broker, hook: (() => void) | undefined): void {
@@ -220,6 +237,20 @@ export function setLifecycleTimingForTest(broker: Broker, timing: LifecycleTimin
220
237
  if (timing) lifecycleTimingsForTest.set(broker, timing);
221
238
  else lifecycleTimingsForTest.delete(broker);
222
239
  }
240
+ export function setEnsureLaunchWorktreeForTest(
241
+ _broker: Broker | undefined,
242
+ fn?: EnsureLaunchWorktreeForTest | undefined,
243
+ ): void {
244
+ ensureLaunchWorktreeForTest = typeof _broker === "function" ? (_broker as EnsureLaunchWorktreeForTest) : fn;
245
+ }
246
+
247
+ export function setEnsureReusableNodeModulesForTest(
248
+ _broker: Broker | undefined,
249
+ fn?: EnsureReusableNodeModulesForTest | undefined,
250
+ ): void {
251
+ ensureReusableNodeModulesForTest =
252
+ typeof _broker === "function" ? (_broker as EnsureReusableNodeModulesForTest) : fn;
253
+ }
223
254
 
224
255
  let lifecycleHostPlatformForTest: NodeJS.Platform | undefined;
225
256
 
@@ -300,6 +331,7 @@ export interface SessionLifecycleWorktreeReceipt {
300
331
  cwd: string;
301
332
  created: boolean;
302
333
  reused: boolean;
334
+ createdBranch: boolean;
303
335
  branch?: string;
304
336
  }
305
337
 
@@ -734,7 +766,7 @@ function isLifecycleWorktreeTarget(value: unknown): value is SessionLifecycleWor
734
766
 
735
767
  function lifecycleWorktreeTarget(input: Input): SessionLifecycleWorktreeTarget | null | undefined {
736
768
  const target = input.target as Record<string, unknown> | undefined;
737
- const worktree = target?.worktree;
769
+ const worktree = target?.worktree ?? input.worktree;
738
770
  if (worktree === undefined) return undefined;
739
771
  return isLifecycleWorktreeTarget(worktree) ? worktree : null;
740
772
  }
@@ -4150,8 +4182,22 @@ export function worktreeOccupantForTest(
4150
4182
  return worktreeOccupant(sessions, worktreePath, observe);
4151
4183
  }
4152
4184
 
4153
- function preparePlannedWorktree(plan: GjcLaunchWorktreePlan): SessionLifecycleWorktreeReceipt {
4154
- const prepared = ensureLaunchWorktree(plan);
4185
+ async function preparePlannedWorktree(
4186
+ plan: GjcLaunchWorktreePlan,
4187
+ opts: { signal: AbortSignal; deadlineAt: number; now: () => number },
4188
+ ): Promise<SessionLifecycleWorktreeReceipt> {
4189
+ const hooked = ensureLaunchWorktreeForTest;
4190
+ if (hooked) {
4191
+ const receipt = await hooked(plan, { signal: opts.signal, deadlineAt: opts.deadlineAt });
4192
+ if (!receipt.enabled || path.resolve(receipt.cwd) !== path.resolve(plan.worktreePath))
4193
+ throw new Error("Lifecycle worktree preparation did not preserve the durable worktree identity.");
4194
+ return receipt;
4195
+ }
4196
+ const prepared = await ensureLaunchWorktreeCancellable(plan, {
4197
+ signal: opts.signal,
4198
+ deadlineAt: opts.deadlineAt,
4199
+ now: opts.now,
4200
+ });
4155
4201
  if (!prepared.enabled || path.resolve(prepared.worktreePath) !== path.resolve(plan.worktreePath))
4156
4202
  throw new Error("Lifecycle worktree preparation did not preserve the durable worktree identity.");
4157
4203
  return {
@@ -4159,9 +4205,67 @@ function preparePlannedWorktree(plan: GjcLaunchWorktreePlan): SessionLifecycleWo
4159
4205
  cwd: path.resolve(prepared.worktreePath),
4160
4206
  created: prepared.created,
4161
4207
  reused: prepared.reused,
4208
+ createdBranch: prepared.createdBranch,
4162
4209
  ...(prepared.branchName ? { branch: prepared.branchName } : {}),
4163
4210
  };
4164
4211
  }
4212
+
4213
+ function mapPreparationFailure(error: unknown): BrokerResponse {
4214
+ const message = sanitizeSdkStartupMessage(error);
4215
+ if (
4216
+ error instanceof WorktreePreparationTimeoutError ||
4217
+ (error as { code?: string }).code === "worktree_preparation_timeout"
4218
+ ) {
4219
+ return fail("worktree_preparation_timeout", message);
4220
+ }
4221
+ if (
4222
+ error instanceof DependencyPreparationTimeoutError ||
4223
+ (error as { code?: string }).code === "dependency_preparation_timeout"
4224
+ ) {
4225
+ return fail("dependency_preparation_timeout", message);
4226
+ }
4227
+ return fail("spawn_failed", `Unable to prepare lifecycle worktree: ${message}`);
4228
+ }
4229
+
4230
+ function abortWhenDue(
4231
+ controller: AbortController,
4232
+ deadlineAt: number,
4233
+ now: () => number,
4234
+ ): ReturnType<typeof setInterval> {
4235
+ const tick = (): void => {
4236
+ if (now() >= deadlineAt && !controller.signal.aborted) controller.abort();
4237
+ };
4238
+ tick();
4239
+ return setInterval(tick, 10);
4240
+ }
4241
+
4242
+ function durableWorktreeEffects(
4243
+ receipt: SessionLifecycleWorktreeReceipt,
4244
+ timings: { worktreePreparationMs?: number; dependencyPreparationMs?: number; spawnAuthorizedAtOffsetMs?: number },
4245
+ ): LifecycleDurableEffectsReceipt {
4246
+ const worktree = {
4247
+ cwdDigest: createHash("sha256").update(receipt.cwd, "utf8").digest("hex"),
4248
+ created: receipt.created,
4249
+ reused: receipt.reused,
4250
+ createdBranch: receipt.createdBranch,
4251
+ ...(receipt.branch ? { branchDigest: createHash("sha256").update(receipt.branch, "utf8").digest("hex") } : {}),
4252
+ };
4253
+ return {
4254
+ worktree,
4255
+ timings,
4256
+ digest: createHash("sha256").update(canonicalJson({ worktree, timings })).digest("hex"),
4257
+ };
4258
+ }
4259
+
4260
+ function deadlineFieldsPresent(input: Input): boolean {
4261
+ return [
4262
+ input.receivedAt,
4263
+ input.requestedReadinessTimeoutMs,
4264
+ input.semanticReadyDeadlineAt,
4265
+ input.terminationStartDeadlineAt,
4266
+ input.lifecycleCleanupDeadlineAt,
4267
+ ].some(value => value !== undefined);
4268
+ }
4165
4269
  async function launchInput(
4166
4270
  broker: Broker,
4167
4271
  operation: "session.create" | "session.fork" | "session.resume",
@@ -4923,8 +5027,16 @@ async function executeLifecycleResponse(
4923
5027
  const queueWaitMs = startupQueueWaitMs(requestedReadinessTimeoutMs);
4924
5028
  const launch = await launchInput(broker, operation, input);
4925
5029
  if ("ok" in launch) return launch;
5030
+ if (launch.worktreePlan && deadlineFieldsPresent(input)) {
5031
+ return fail(
5032
+ "invalid_input",
5033
+ "Lifecycle worktree launches cannot carry a caller-supplied child deadline tuple.",
5034
+ );
5035
+ }
4926
5036
  const admitted = await broker.runStartup(queueWaitMs, timing, async admittedAt => {
4927
- const admittedInput = { ...input, ...deriveLifecycleDeadlines(admittedAt, requestedReadinessTimeoutMs) };
5037
+ const admittedInput = launch.worktreePlan
5038
+ ? { ...input, admittedAt, requestedReadinessTimeoutMs }
5039
+ : { ...input, ...deriveLifecycleDeadlines(admittedAt, requestedReadinessTimeoutMs) };
4928
5040
  startupAdmittedInputs.add(admittedInput);
4929
5041
  startupLaunchInputs.set(admittedInput, launch);
4930
5042
  try {
@@ -4944,15 +5056,52 @@ async function executeLifecycleResponse(
4944
5056
  return fail("startup_admission_timeout", failure.message);
4945
5057
  }
4946
5058
 
4947
- const deadlines = lifecycleDeadlines(input, input.receivedAt as number);
4948
-
4949
- if ("ok" in deadlines) return deadlines;
4950
- const lifecycleDeadline = deadlines.lifecycleCleanupDeadlineAt;
4951
- const readinessDeadline = deadlines.semanticReadyDeadlineAt;
4952
- const terminationStartDeadline = deadlines.terminationStartDeadlineAt;
4953
-
4954
5059
  const launch = startupLaunchInputs.get(input) ?? (await launchInput(broker, operation, input));
4955
5060
  if ("ok" in launch) return launch;
5061
+ if (launch.worktreePlan && deadlineFieldsPresent(input) && input.admittedAt === undefined) {
5062
+ return fail(
5063
+ "invalid_input",
5064
+ "Lifecycle worktree launches cannot carry a caller-supplied child deadline tuple.",
5065
+ );
5066
+ }
5067
+
5068
+ let childDeadlines: LifecycleDeadlines | undefined;
5069
+ let lifecycleDeadline: number;
5070
+ let readinessDeadline: number;
5071
+ let terminationStartDeadline: number;
5072
+ let outerCleanupDeadlineAt: number | undefined;
5073
+ if (launch.worktreePlan) {
5074
+ const prepTimeouts = readPreparationTimeouts(input);
5075
+ if (!prepTimeouts.ok) return fail("invalid_input", PREPARATION_TIMEOUT_INVALID_MESSAGE);
5076
+ const admittedAt =
5077
+ typeof input.admittedAt === "number" && Number.isSafeInteger(input.admittedAt)
5078
+ ? input.admittedAt
5079
+ : timing.now();
5080
+ const requestedReadinessTimeoutMs =
5081
+ typeof input.requestedReadinessTimeoutMs === "number" &&
5082
+ isValidReadinessTimeoutMs(input.requestedReadinessTimeoutMs)
5083
+ ? input.requestedReadinessTimeoutMs
5084
+ : readinessTimeout(input);
5085
+ if (typeof requestedReadinessTimeoutMs !== "number") return requestedReadinessTimeoutMs;
5086
+ const outer = deriveLifecycleOuterDeadlines({
5087
+ admittedAt,
5088
+ worktreePrepTimeoutMs: prepTimeouts.worktreePrepTimeoutMs,
5089
+ dependencyPrepTimeoutMs: prepTimeouts.dependencyPrepTimeoutMs,
5090
+ requestedReadinessTimeoutMs,
5091
+ });
5092
+ outerCleanupDeadlineAt = outer.lifecycleCleanupDeadlineAt;
5093
+ lifecycleDeadline = outer.lifecycleCleanupDeadlineAt;
5094
+ readinessDeadline = outer.lifecycleCleanupDeadlineAt;
5095
+ terminationStartDeadline = outer.lifecycleCleanupDeadlineAt;
5096
+ } else {
5097
+ const deadlines = lifecycleDeadlines(input, input.receivedAt as number);
5098
+ if ("ok" in deadlines) return deadlines;
5099
+ childDeadlines = deadlines;
5100
+ lifecycleDeadline = deadlines.lifecycleCleanupDeadlineAt;
5101
+ readinessDeadline = deadlines.semanticReadyDeadlineAt;
5102
+ terminationStartDeadline = deadlines.terminationStartDeadlineAt;
5103
+ }
5104
+
4956
5105
  if (!hasProcessIncarnationAuthority())
4957
5106
  return fail(
4958
5107
  "incarnation_unavailable",
@@ -4972,7 +5121,8 @@ async function executeLifecycleResponse(
4972
5121
  sessionId: launch.id,
4973
5122
  stateRoot: launch.root,
4974
5123
  childOwnershipEstablished: false,
4975
- lifecycleCleanupDeadlineAt: deadlines.lifecycleCleanupDeadlineAt,
5124
+ lifecycleCleanupDeadlineAt:
5125
+ outerCleanupDeadlineAt ?? (childDeadlines as LifecycleDeadlines).lifecycleCleanupDeadlineAt,
4976
5126
  ...(plannedWorktreeIntent ? { worktree: plannedWorktreeIntent } : {}),
4977
5127
  };
4978
5128
 
@@ -4989,39 +5139,109 @@ async function executeLifecycleResponse(
4989
5139
  let worktreeReceipt: SessionLifecycleWorktreeReceipt | undefined;
4990
5140
  try {
4991
5141
  if (launch.worktreePlan) {
4992
- worktreeReceipt = preparePlannedWorktree(launch.worktreePlan);
4993
- const worktree = {
4994
- cwdDigest: createHash("sha256").update(worktreeReceipt.cwd, "utf8").digest("hex"),
4995
- created: worktreeReceipt.created,
4996
- reused: worktreeReceipt.reused,
4997
- ...(worktreeReceipt.branch
4998
- ? { branchDigest: createHash("sha256").update(worktreeReceipt.branch, "utf8").digest("hex") }
4999
- : {}),
5000
- };
5001
- const durableEffects: LifecycleDurableEffectsReceipt = {
5002
- worktree,
5003
- digest: createHash("sha256").update(canonicalJson({ worktree })).digest("hex"),
5004
- };
5005
- await broker.ledger.transition(identity, "effect_started", { durableEffects });
5006
- ensureReusableNodeModules(launch.worktreePlan.repoRoot, launch.worktreePlan.worktreePath);
5142
+ const prepTimeouts = readPreparationTimeouts(input);
5143
+ if (!prepTimeouts.ok) return fail("invalid_input", PREPARATION_TIMEOUT_INVALID_MESSAGE);
5144
+ const admittedAt =
5145
+ typeof input.admittedAt === "number" && Number.isSafeInteger(input.admittedAt)
5146
+ ? input.admittedAt
5147
+ : timing.now();
5148
+ const requestedReadinessTimeoutMs =
5149
+ typeof input.requestedReadinessTimeoutMs === "number" &&
5150
+ isValidReadinessTimeoutMs(input.requestedReadinessTimeoutMs)
5151
+ ? input.requestedReadinessTimeoutMs
5152
+ : readinessTimeout(input);
5153
+ if (typeof requestedReadinessTimeoutMs !== "number") return requestedReadinessTimeoutMs;
5154
+ const outer = deriveLifecycleOuterDeadlines({
5155
+ admittedAt,
5156
+ worktreePrepTimeoutMs: prepTimeouts.worktreePrepTimeoutMs,
5157
+ dependencyPrepTimeoutMs: prepTimeouts.dependencyPrepTimeoutMs,
5158
+ requestedReadinessTimeoutMs,
5159
+ });
5160
+ const controller = new AbortController();
5161
+ const worktreeStartedAt = timing.now();
5162
+ const worktreeWatch = abortWhenDue(controller, outer.worktreePreparationDeadlineAt, () => timing.now());
5163
+ try {
5164
+ worktreeReceipt = await preparePlannedWorktree(launch.worktreePlan, {
5165
+ signal: controller.signal,
5166
+ deadlineAt: outer.worktreePreparationDeadlineAt,
5167
+ now: () => timing.now(),
5168
+ });
5169
+ } finally {
5170
+ clearInterval(worktreeWatch);
5171
+ }
5172
+ const worktreeDoneAt = timing.now();
5173
+ const worktreePreparationMs = Math.max(0, worktreeDoneAt - worktreeStartedAt);
5174
+ await broker.ledger.transition(identity, "effect_started", {
5175
+ durableEffects: durableWorktreeEffects(worktreeReceipt, { worktreePreparationMs }),
5176
+ });
5177
+ const remainingDepBudget = Math.min(
5178
+ prepTimeouts.dependencyPrepTimeoutMs,
5179
+ Math.max(0, outer.lifecycleCleanupDeadlineAt - worktreeDoneAt - requestedReadinessTimeoutMs),
5180
+ );
5181
+ const dependencyDeadlineAt = worktreeDoneAt + remainingDepBudget;
5182
+ if (timing.now() >= dependencyDeadlineAt) throw new DependencyPreparationTimeoutError();
5183
+ const depWatch = abortWhenDue(controller, dependencyDeadlineAt, () => timing.now());
5184
+ const depHook = ensureReusableNodeModulesForTest;
5185
+ const dependencyStartedAt = timing.now();
5186
+ try {
5187
+ if (depHook) {
5188
+ await depHook(launch.worktreePlan.repoRoot, launch.worktreePlan.worktreePath, {
5189
+ signal: controller.signal,
5190
+ deadlineAt: dependencyDeadlineAt,
5191
+ });
5192
+ } else {
5193
+ await ensureReusableNodeModulesCancellable(
5194
+ launch.worktreePlan.repoRoot,
5195
+ launch.worktreePlan.worktreePath,
5196
+ {
5197
+ signal: controller.signal,
5198
+ deadlineAt: dependencyDeadlineAt,
5199
+ now: () => timing.now(),
5200
+ },
5201
+ );
5202
+ }
5203
+ } finally {
5204
+ clearInterval(depWatch);
5205
+ }
5206
+ const dependencyPreparationMs = Math.max(0, timing.now() - dependencyStartedAt);
5207
+ await broker.ledger.transition(identity, "effect_started", {
5208
+ durableEffects: durableWorktreeEffects(worktreeReceipt, {
5209
+ worktreePreparationMs,
5210
+ dependencyPreparationMs,
5211
+ spawnAuthorizedAtOffsetMs: Math.max(0, timing.now() - admittedAt),
5212
+ }),
5213
+ });
5214
+ const prepSucceededAt = timing.now();
5215
+ childDeadlines = deriveLifecycleDeadlines(prepSucceededAt, requestedReadinessTimeoutMs);
5216
+ readinessDeadline = childDeadlines.semanticReadyDeadlineAt;
5217
+ terminationStartDeadline = childDeadlines.terminationStartDeadlineAt;
5218
+ lifecycleDeadline = childDeadlines.lifecycleCleanupDeadlineAt;
5007
5219
  }
5008
5220
  await reapDeadLifecycleMarkers(launch.root);
5009
5221
  } catch (error) {
5010
- return fail(
5011
- "spawn_failed",
5012
- `Unable to prepare lifecycle worktree: ${error instanceof Error ? error.message : String(error)}`,
5013
- );
5222
+ if (launch.worktreePlan && worktreeReceipt?.created && !worktreeReceipt.reused) {
5223
+ removeOwnedLaunchWorktree(launch.worktreePlan, worktreeReceipt);
5224
+ }
5225
+ return mapPreparationFailure(error);
5014
5226
  }
5015
- if (timing.now() >= readinessDeadline)
5227
+ if (!launch.worktreePlan && timing.now() >= readinessDeadline)
5016
5228
  return fail(
5017
5229
  "readiness_timeout",
5018
5230
  "Lifecycle preparation exhausted the semantic readiness deadline before spawning.",
5019
5231
  );
5232
+ if (launch.worktreePlan && !childDeadlines)
5233
+ return fail(
5234
+ worktreeReceipt ? "dependency_preparation_timeout" : "worktree_preparation_timeout",
5235
+ worktreeReceipt
5236
+ ? "Dependency preparation exceeded its deadline before the session host was spawned."
5237
+ : "Worktree preparation exceeded its deadline before the session host was spawned.",
5238
+ );
5020
5239
  if (!hasProcessIncarnationAuthority())
5021
5240
  return fail(
5022
5241
  "incarnation_unavailable",
5023
5242
  "OS process incarnation authority is unavailable; refusing to spawn a lifecycle session.",
5024
5243
  );
5244
+ const deadlines = childDeadlines as LifecycleDeadlines;
5025
5245
 
5026
5246
  const coordinatorSidecarTarget =
5027
5247
  typeof launch.coordinatorStateDir === "string" && typeof launch.coordinatorSidecarKeyId === "string"
@@ -90,6 +90,30 @@ function launchFailureMessage(selected: "tmux" | "psmux" | "headless", error: un
90
90
  return `${selected} substrate launch failed${bounded ? `: ${bounded}` : ""}`;
91
91
  }
92
92
 
93
+ function cleanupFailureMessage(selected: "tmux" | "psmux", error: unknown): string {
94
+ const detail = error instanceof Error ? error.message : String(error);
95
+ const bounded = detail
96
+ .replace(/[\u0000-\u001f\u007f]/g, " ")
97
+ .trim()
98
+ .slice(0, MAX_SUBSTRATE_DIAGNOSTIC_LENGTH);
99
+ return `${selected} substrate cleanup failed${bounded ? `: ${bounded}` : ""}`;
100
+ }
101
+
102
+ function cleanupUncertaintyMessage(selected: "tmux" | "psmux"): string {
103
+ return `${selected} substrate cleanup could not be verified`;
104
+ }
105
+
106
+ function cleanupWasVerifiedAfterManagedLaunchFailure(error: unknown): boolean {
107
+ if (error instanceof AggregateError) return false;
108
+ const detail = error instanceof Error ? error.message : String(error);
109
+ // The owner-isolation layer reports a failed tmux allocation before a
110
+ // managed session exists. That is the only managed launch failure that is
111
+ // safe to replace with the exact headless substrate. Every other thrown
112
+ // error may follow a partial create/proof path and therefore needs explicit
113
+ // cleanup evidence instead of optimistic fallback.
114
+ return detail.includes("planned_spawn_failed") && !detail.includes("cleanup_uncertain");
115
+ }
116
+
93
117
  function hasOnlyKeys(value: Record<string, unknown>, keys: Set<string>): boolean {
94
118
  return Object.keys(value).every(key => keys.has(key));
95
119
  }
@@ -440,6 +464,16 @@ export function createSpawnSubstrateProvider(
440
464
  managed = launchManaged(spec, launchEnv, platform);
441
465
  } catch (error) {
442
466
  const failure = launchFailureMessage(selected, error);
467
+ if (!cleanupWasVerifiedAfterManagedLaunchFailure(error))
468
+ return {
469
+ ok: false,
470
+ code: "substrate_proof_failed",
471
+ message: `${failure}; ${
472
+ error instanceof AggregateError
473
+ ? cleanupFailureMessage(selected, error)
474
+ : cleanupUncertaintyMessage(selected)
475
+ }`,
476
+ };
443
477
  try {
444
478
  const fallback = await launchHeadlessSubstrate(spec, launchEnv);
445
479
  if (fallback.ok) return fallback;
@@ -458,12 +492,21 @@ export function createSpawnSubstrateProvider(
458
492
  }
459
493
  const incarnation = readIncarnation(managed.pid);
460
494
  if (!incarnation || verifyManaged(managed, launchEnv) !== "verified") {
495
+ let cleanupVerified = false;
496
+ let cleanupError: unknown;
461
497
  try {
462
498
  await closeManaged(managed, launchEnv);
463
- } catch {
464
- // The managed close primitive is already exact-proof fenced. Retain its result only in authority state.
499
+ cleanupVerified = true;
500
+ } catch (error) {
501
+ cleanupError = error;
465
502
  }
466
503
  const failure = launchFailureMessage(selected, new Error("exact substrate proof failed"));
504
+ if (!cleanupVerified)
505
+ return {
506
+ ok: false,
507
+ code: "substrate_proof_failed",
508
+ message: `${failure}; ${cleanupFailureMessage(selected, cleanupError)}`,
509
+ };
467
510
  const fallback = await launchHeadlessSubstrate(spec, launchEnv);
468
511
  if (fallback.ok) return fallback;
469
512
  return {
@@ -3,6 +3,14 @@ export const DEFAULT_READINESS_TIMEOUT_MS = 10_000;
3
3
  export const MIN_READINESS_TIMEOUT_MS = 4_000;
4
4
  export const MAX_READINESS_TIMEOUT_MS = 60_000;
5
5
 
6
+ /** Bounded git worktree add/reuse window; independent of child semantic readiness. */
7
+ export const DEFAULT_WORKTREE_PREPARATION_TIMEOUT_MS = 30_000;
8
+ export const MIN_PREPARATION_TIMEOUT_MS = 1_000;
9
+ export const MAX_PREPARATION_TIMEOUT_MS = 120_000;
10
+
11
+ /** Bounded workspace install window; independent of child semantic readiness. */
12
+ export const DEFAULT_DEPENDENCY_PREPARATION_TIMEOUT_MS = 30_000;
13
+
6
14
  export function isValidReadinessTimeoutMs(value: unknown): value is number {
7
15
  return (
8
16
  typeof value === "number" &&
@@ -12,7 +20,17 @@ export function isValidReadinessTimeoutMs(value: unknown): value is number {
12
20
  );
13
21
  }
14
22
 
23
+ export function isValidPreparationTimeoutMs(value: unknown): value is number {
24
+ return (
25
+ typeof value === "number" &&
26
+ Number.isSafeInteger(value) &&
27
+ value >= MIN_PREPARATION_TIMEOUT_MS &&
28
+ value <= MAX_PREPARATION_TIMEOUT_MS
29
+ );
30
+ }
31
+
15
32
  export const READINESS_TIMEOUT_INVALID_MESSAGE = `readinessTimeoutMs must be an integer between ${MIN_READINESS_TIMEOUT_MS} and ${MAX_READINESS_TIMEOUT_MS}.`;
33
+ export const PREPARATION_TIMEOUT_INVALID_MESSAGE = `worktreePreparationTimeoutMs and dependencyPreparationTimeoutMs must be integers between ${MIN_PREPARATION_TIMEOUT_MS} and ${MAX_PREPARATION_TIMEOUT_MS}.`;
16
34
  /**
17
35
  * Sleep until the duration elapses or the caller cancels the wait. Queue admission
18
36
  * uses this so a granted or refused waiter does not retain its cutoff timer for the
@@ -77,6 +95,10 @@ export function lifecycleStartupBudgetMs(requestedReadinessTimeoutMs: number): n
77
95
  * rather than left unextended: the broker queues it for exactly as long as one that
78
96
  * asked, so the common path would otherwise fail client-side while the broker runs
79
97
  * the startup to a durably persisted terminal result.
98
+ *
99
+ * Worktree launches add independent preparation budgets so git add / install cannot
100
+ * cut the caller while the broker still owns the request. One-arg
101
+ * {@link lifecycleStartupBudgetMs} stays queue+readiness for no-worktree callers.
80
102
  */
81
103
  export function lifecycleRequestTimeoutMs(operation: string, input: Record<string, unknown>): number | undefined {
82
104
  const deadlineFields = [
@@ -97,7 +119,127 @@ export function lifecycleRequestTimeoutMs(operation: string, input: Record<strin
97
119
  if (requested !== undefined && !isValidReadinessTimeoutMs(requested)) return undefined;
98
120
  supplied = requested as number | undefined;
99
121
  }
100
- if (isStartupLifecycleOperation(operation))
101
- return lifecycleStartupBudgetMs(supplied ?? DEFAULT_READINESS_TIMEOUT_MS) + CALLER_DEADLINE_SLACK_MS;
122
+ if (isStartupLifecycleOperation(operation)) {
123
+ const readiness = supplied ?? DEFAULT_READINESS_TIMEOUT_MS;
124
+ const preparation = preparationBudgetMs(input);
125
+ if (preparation === undefined) return undefined;
126
+ return lifecycleStartupBudgetMs(readiness) + preparation + CALLER_DEADLINE_SLACK_MS;
127
+ }
102
128
  return supplied === undefined ? undefined : supplied + CALLER_DEADLINE_SLACK_MS;
103
129
  }
130
+
131
+ function recordEnabled(value: unknown): boolean {
132
+ return (
133
+ typeof value === "object" &&
134
+ value !== null &&
135
+ !Array.isArray(value) &&
136
+ (value as Record<string, unknown>).enabled === true
137
+ );
138
+ }
139
+
140
+ function worktreeNamePresent(value: unknown): boolean {
141
+ if (typeof value !== "object" || value === null || Array.isArray(value)) return false;
142
+ const name = (value as Record<string, unknown>).name;
143
+ return typeof name === "string" && name.length > 0;
144
+ }
145
+
146
+ /**
147
+ * True when this request will run named or detached worktree preparation.
148
+ * Coordinator nests the selector at `target.worktree`; Direct/lifecycle service
149
+ * put `worktree` on the mutation target itself; broker serialization also
150
+ * accepts `{ worktree: { name } }` without `enabled`.
151
+ */
152
+ export function lifecycleRequestHasWorktree(input: Record<string, unknown>): boolean {
153
+ const nested = input.target;
154
+ if (typeof nested === "object" && nested !== null && !Array.isArray(nested)) {
155
+ const nestedWorktree = (nested as Record<string, unknown>).worktree;
156
+ if (recordEnabled(nestedWorktree) || worktreeNamePresent(nestedWorktree)) return true;
157
+ }
158
+ const worktree = input.worktree;
159
+ if (worktree === true) return true;
160
+ return recordEnabled(worktree) || worktreeNamePresent(worktree);
161
+ }
162
+
163
+ function readPreparationTimeout(
164
+ input: Record<string, unknown>,
165
+ key: "worktreePreparationTimeoutMs" | "dependencyPreparationTimeoutMs",
166
+ ): number | undefined | "invalid" {
167
+ const value = input[key];
168
+ if (value === undefined) return undefined;
169
+ return isValidPreparationTimeoutMs(value) ? value : "invalid";
170
+ }
171
+
172
+ /**
173
+ * Sum of worktree and dependency preparation budgets, or 0 when the request
174
+ * has no worktree. Returns `undefined` when an explicit prep field is present
175
+ * but invalid so callers fail closed instead of silently dropping prep time.
176
+ */
177
+ export function preparationBudgetMs(input: Record<string, unknown>): number | undefined {
178
+ const worktreeTimeout = readPreparationTimeout(input, "worktreePreparationTimeoutMs");
179
+ const dependencyTimeout = readPreparationTimeout(input, "dependencyPreparationTimeoutMs");
180
+ if (worktreeTimeout === "invalid" || dependencyTimeout === "invalid") return undefined;
181
+ if (!lifecycleRequestHasWorktree(input)) return 0;
182
+ return (
183
+ (worktreeTimeout ?? DEFAULT_WORKTREE_PREPARATION_TIMEOUT_MS) +
184
+ (dependencyTimeout ?? DEFAULT_DEPENDENCY_PREPARATION_TIMEOUT_MS)
185
+ );
186
+ }
187
+
188
+ export interface LifecycleOuterDeadlines {
189
+ admittedAt: number;
190
+ worktreePrepTimeoutMs: number;
191
+ dependencyPrepTimeoutMs: number;
192
+ requestedReadinessTimeoutMs: number;
193
+ worktreePreparationDeadlineAt: number;
194
+ lifecycleCleanupDeadlineAt: number;
195
+ }
196
+
197
+ /**
198
+ * Whole-request outer deadlines for a worktree launch. Child semantic readiness
199
+ * is still derived later at prepSucceededAt. Dependency start is sequential:
200
+ * callers clip remaining dep budget against `lifecycleCleanupDeadlineAt`
201
+ * after worktreeDoneAt.
202
+ */
203
+ export function deriveLifecycleOuterDeadlines(input: {
204
+ admittedAt: number;
205
+ worktreePrepTimeoutMs: number;
206
+ dependencyPrepTimeoutMs: number;
207
+ requestedReadinessTimeoutMs: number;
208
+ }): LifecycleOuterDeadlines {
209
+ const { admittedAt, worktreePrepTimeoutMs, dependencyPrepTimeoutMs, requestedReadinessTimeoutMs } = input;
210
+ if (
211
+ !Number.isSafeInteger(admittedAt) ||
212
+ !isValidPreparationTimeoutMs(worktreePrepTimeoutMs) ||
213
+ !isValidPreparationTimeoutMs(dependencyPrepTimeoutMs) ||
214
+ !isValidReadinessTimeoutMs(requestedReadinessTimeoutMs)
215
+ ) {
216
+ throw new Error("Lifecycle outer timing values must be safe integers in the approved ranges.");
217
+ }
218
+ const worktreePreparationDeadlineAt = admittedAt + worktreePrepTimeoutMs;
219
+ const lifecycleCleanupDeadlineAt =
220
+ admittedAt + worktreePrepTimeoutMs + dependencyPrepTimeoutMs + requestedReadinessTimeoutMs;
221
+ if (!Number.isSafeInteger(worktreePreparationDeadlineAt) || !Number.isSafeInteger(lifecycleCleanupDeadlineAt)) {
222
+ throw new Error("Lifecycle outer timing values overflow the safe integer range.");
223
+ }
224
+ return {
225
+ admittedAt,
226
+ worktreePrepTimeoutMs,
227
+ dependencyPrepTimeoutMs,
228
+ requestedReadinessTimeoutMs,
229
+ worktreePreparationDeadlineAt,
230
+ lifecycleCleanupDeadlineAt,
231
+ };
232
+ }
233
+
234
+ export function readPreparationTimeouts(
235
+ input: Record<string, unknown>,
236
+ ): { ok: true; worktreePrepTimeoutMs: number; dependencyPrepTimeoutMs: number } | { ok: false } {
237
+ const worktreeTimeout = readPreparationTimeout(input, "worktreePreparationTimeoutMs");
238
+ const dependencyTimeout = readPreparationTimeout(input, "dependencyPreparationTimeoutMs");
239
+ if (worktreeTimeout === "invalid" || dependencyTimeout === "invalid") return { ok: false };
240
+ return {
241
+ ok: true,
242
+ worktreePrepTimeoutMs: worktreeTimeout ?? DEFAULT_WORKTREE_PREPARATION_TIMEOUT_MS,
243
+ dependencyPrepTimeoutMs: dependencyTimeout ?? DEFAULT_DEPENDENCY_PREPARATION_TIMEOUT_MS,
244
+ };
245
+ }
@@ -8,6 +8,16 @@ export const SDK_STATE_VERSION = 1;
8
8
  export const SESSION_INDEX_SNAPSHOT_VERSION = 4;
9
9
  export const SESSION_INDEX_EVENT_VERSION = SESSION_INDEX_SNAPSHOT_VERSION;
10
10
 
11
+ /**
12
+ * Thrown when a persisted SDK state file carries a version this build does not
13
+ * support. `assertSupportedStateVersion` fences only generic SDK state (broker
14
+ * discovery, lifecycle-ledger rows, guide cache meta) whose current format is
15
+ * exactly `SDK_STATE_VERSION`. Session-index files carry their own versions
16
+ * (see `assertSupportedSessionIndexEventVersion` and
17
+ * `assertSupportedSnapshotVersion`) and MUST NOT be validated with the generic
18
+ * guard — a supported v4 index row would crash with "maximum supported version
19
+ * is 1" (#5181).
20
+ */
11
21
  export class UnsupportedStateVersionError extends Error {
12
22
  readonly code = "unsupported_state_version";
13
23
 
@@ -23,6 +33,13 @@ export class UnsupportedStateVersionError extends Error {
23
33
  }
24
34
  }
25
35
 
36
+ /**
37
+ * Generic SDK-state fence: rejects any version above `SDK_STATE_VERSION`.
38
+ * ONLY for generic state whose current format is exactly `SDK_STATE_VERSION`
39
+ * (broker discovery `broker.json`, lifecycle-ledger rows, guide cache meta).
40
+ * Never validate session-index rows or snapshots with this guard — they have
41
+ * dedicated version fences and currently accept v4 (#5181).
42
+ */
26
43
  export function assertSupportedStateVersion(file: string, value: unknown): void {
27
44
  if (!value || typeof value !== "object") return;
28
45
  const record = value as { version?: unknown; stateVersion?: unknown };
@@ -135,8 +135,8 @@ export type ChatDaemonAction = "stop" | "reload";
135
135
  * pre-upgrade owner cannot retain the queue race fixed by #5120.
136
136
  */
137
137
  export const CHAT_DAEMON_GENERATIONS: Readonly<Record<ChatDaemonKind, number>> = {
138
- discord: 72,
139
- slack: 75,
138
+ discord: 73,
139
+ slack: 77,
140
140
  };
141
141
 
142
142
  export function chatDaemonGeneration(kind: ChatDaemonKind): number {