@astrosheep/keiyaku 2.9.8 → 2.9.10

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 (230) hide show
  1. package/README.md +50 -182
  2. package/build/.tsbuildinfo +1 -1
  3. package/build/agents/harness/control-types.js +18 -1
  4. package/build/agents/harness/index.js +2 -0
  5. package/build/agents/harness/outcome-error.js +81 -0
  6. package/build/agents/harness/projection.js +10 -7
  7. package/build/agents/harness/router.js +5 -1
  8. package/build/agents/providers/claude-agent-sdk/adapter.js +18 -4
  9. package/build/agents/providers/claude-agent-sdk/registration.js +11 -1
  10. package/build/agents/providers/claude-agent-sdk/session.js +19 -5
  11. package/build/agents/providers/codex-app-server/adapter.js +11 -6
  12. package/build/agents/providers/codex-app-server/events.js +67 -6
  13. package/build/agents/providers/codex-app-server/registration.js +11 -1
  14. package/build/agents/providers/opencode-sdk/adapter.js +8 -1
  15. package/build/agents/providers/opencode-sdk/events.js +24 -2
  16. package/build/agents/providers/opencode-sdk/registration.js +11 -1
  17. package/build/agents/providers/pi/checkpoint.js +9 -1
  18. package/build/agents/providers/pi/events.js +2 -0
  19. package/build/agents/providers/pi/registration.js +14 -1
  20. package/build/cli/commands/akuma/akuma/handler.js +1 -1
  21. package/build/cli/commands/akuma/akuma/meta.js +3 -3
  22. package/build/cli/commands/akuma/catalog.js +2 -2
  23. package/build/cli/commands/akuma/{view → show}/handler.js +5 -5
  24. package/build/cli/commands/akuma/{view → show}/meta.js +3 -3
  25. package/build/cli/commands/akuma.js +1 -1
  26. package/build/cli/commands/contract/amend/handler.js +5 -4
  27. package/build/cli/commands/contract/arc/handler.js +5 -4
  28. package/build/cli/commands/contract/audit/handler.js +2 -2
  29. package/build/cli/commands/contract/bind/handler.js +12 -3
  30. package/build/cli/commands/contract/bind/meta.js +45 -15
  31. package/build/cli/commands/contract/forfeit/handler.js +9 -5
  32. package/build/cli/commands/contract/log/handler.js +2 -2
  33. package/build/cli/commands/contract/petition/handler.js +34 -7
  34. package/build/cli/commands/contract/renew/handler.js +7 -7
  35. package/build/cli/commands/contract/renew/meta.js +3 -3
  36. package/build/cli/commands/metadata.js +10 -6
  37. package/build/cli/commands/projection/call/meta.js +1 -1
  38. package/build/cli/commands/projection/catalog.js +2 -0
  39. package/build/cli/commands/projection/history/handler.js +21 -0
  40. package/build/cli/commands/projection/history/meta.js +9 -0
  41. package/build/cli/commands/projection/shared.js +3 -4
  42. package/build/cli/commands/projection/status/handler.js +3 -3
  43. package/build/cli/commands/projection/wait/handler.js +20 -11
  44. package/build/cli/commands/task/add/handler.js +14 -5
  45. package/build/cli/commands/task/add/meta.js +2 -2
  46. package/build/cli/commands/task/catalog.js +4 -0
  47. package/build/cli/commands/task/done/handler.js +1 -4
  48. package/build/cli/commands/task/done/meta.js +1 -1
  49. package/build/cli/commands/task/drop/meta.js +1 -1
  50. package/build/cli/commands/task/hold/handler.js +3 -0
  51. package/build/cli/commands/task/hold/meta.js +8 -0
  52. package/build/cli/commands/task/log/handler.js +1 -1
  53. package/build/cli/commands/task/log/meta.js +2 -2
  54. package/build/cli/commands/task/resume/handler.js +3 -0
  55. package/build/cli/commands/task/resume/meta.js +8 -0
  56. package/build/cli/commands/task/shared.js +39 -14
  57. package/build/cli/commands/task/show/handler.js +1 -1
  58. package/build/cli/commands/task/show/meta.js +1 -1
  59. package/build/cli/commands/task/start/meta.js +1 -1
  60. package/build/cli/commands/task/stop/handler.js +1 -4
  61. package/build/cli/commands/task/stop/meta.js +1 -1
  62. package/build/cli/commands/task/update/handler.js +11 -3
  63. package/build/cli/commands/task/update/meta.js +3 -3
  64. package/build/cli/completion.js +3 -2
  65. package/build/cli/flags.js +51 -14
  66. package/build/cli/help.js +4 -1
  67. package/build/cli/index.js +40 -25
  68. package/build/cli/parse-flags.js +40 -7
  69. package/build/cli/parse-metadata.js +5 -2
  70. package/build/cli/parse-selectors.js +2 -3
  71. package/build/cli/parse.js +40 -6
  72. package/build/cli/projection-address.js +12 -10
  73. package/build/cli/render/amendment-warning.js +8 -0
  74. package/build/cli/render/arc.js +6 -2
  75. package/build/cli/render/audit.js +400 -81
  76. package/build/cli/render/kanshi.js +242 -79
  77. package/build/cli/render/lifecycle.js +56 -0
  78. package/build/cli/render/misc.js +24 -28
  79. package/build/cli/render/petition.js +5 -0
  80. package/build/cli/render/projection-activity.js +65 -29
  81. package/build/cli/render/projection-history.js +64 -0
  82. package/build/cli/render/shared.js +23 -9
  83. package/build/cli/render/status.js +13 -35
  84. package/build/cli/render/task.js +51 -26
  85. package/build/cli/render/terminal-failure.js +3 -1
  86. package/build/cli/render/wait.js +45 -69
  87. package/build/cli/subagent-guard.js +1 -1
  88. package/build/cli/types.js +1 -1
  89. package/build/config/env-keys.js +3 -1
  90. package/build/config/env.js +24 -1
  91. package/build/config/settings/schema.js +3 -1
  92. package/build/core/address-carrier.js +89 -0
  93. package/build/core/addressing.js +255 -196
  94. package/build/core/amend.js +115 -38
  95. package/build/core/amendment-criteria.js +17 -0
  96. package/build/core/amendment-warning.js +10 -0
  97. package/build/core/arc.js +184 -83
  98. package/build/core/audit/candidate.js +110 -9
  99. package/build/core/audit/coordinates.js +120 -65
  100. package/build/core/audit/evidence.js +19 -23
  101. package/build/core/audit/facade.js +34 -11
  102. package/build/core/audit/report.js +5 -5
  103. package/build/core/bind-reconciliation.js +390 -0
  104. package/build/core/bind-workspace.js +12 -20
  105. package/build/core/bind.js +242 -273
  106. package/build/core/call/call.js +1 -0
  107. package/build/core/call/context.js +46 -71
  108. package/build/core/call/prompt.js +6 -3
  109. package/build/core/context.js +49 -35
  110. package/build/core/contract-carrier-runtime.js +408 -0
  111. package/build/core/contract-carrier.js +270 -0
  112. package/build/core/contract-view.js +33 -27
  113. package/build/core/contract.js +5 -1
  114. package/build/core/derived-replay.js +207 -0
  115. package/build/core/draft.js +125 -57
  116. package/build/core/forfeit.js +113 -56
  117. package/build/core/identifier-slug.js +63 -13
  118. package/build/core/ids.js +4 -4
  119. package/build/core/lifecycle-history.js +35 -0
  120. package/build/core/lifecycle-recovery.js +117 -0
  121. package/build/core/lifecycle-runner.js +377 -0
  122. package/build/core/log.js +37 -11
  123. package/build/core/outcome-base.js +181 -0
  124. package/build/core/process-group.js +139 -0
  125. package/build/core/projection/generation/database.js +82 -7
  126. package/build/core/projection/generation/model.js +9 -36
  127. package/build/core/projection/generation/projection-generation-continuation.js +10 -22
  128. package/build/core/projection/generation/projection-generation-execution.js +13 -6
  129. package/build/core/projection/index.js +5 -4
  130. package/build/core/projection/projection-activity.js +29 -8
  131. package/build/core/projection/projection-alias.js +23 -10
  132. package/build/core/projection/projection-core.js +21 -7
  133. package/build/core/projection/projection-execution-observer.js +91 -39
  134. package/build/core/projection/projection-history.js +474 -0
  135. package/build/core/projection/projection-runner-lock.js +12 -0
  136. package/build/core/projection/projection-status-observation.js +365 -0
  137. package/build/core/projection/projection-status.js +35 -337
  138. package/build/core/projection/projection-terminal-failure.js +13 -8
  139. package/build/core/projection/projection-wait.js +7 -2
  140. package/build/core/projection/tell/launch-store.js +244 -0
  141. package/build/core/projection/tell/model.js +3 -3
  142. package/build/core/projection/tell/store.js +70 -118
  143. package/build/core/registry.js +22 -87
  144. package/build/core/renew-build.js +424 -0
  145. package/build/core/renew-plan.js +239 -0
  146. package/build/core/renew-rewrite.js +77 -0
  147. package/build/core/renew.js +295 -520
  148. package/build/core/repository-ledger/accepted-fold-read.js +19 -0
  149. package/build/core/repository-ledger/accepted-tail.js +116 -0
  150. package/build/core/repository-ledger/admission.js +38 -0
  151. package/build/core/repository-ledger/authoritative-transaction.js +1 -0
  152. package/build/core/repository-ledger/claim-evidence.js +147 -0
  153. package/build/core/repository-ledger/codec.js +297 -0
  154. package/build/core/repository-ledger/current-state-store.js +853 -0
  155. package/build/core/repository-ledger/fold-repository.js +375 -0
  156. package/build/core/repository-ledger/fold.js +248 -0
  157. package/build/core/repository-ledger/git-object-store.js +243 -0
  158. package/build/core/repository-ledger/history-read.js +15 -0
  159. package/build/core/repository-ledger/incremental-read.js +153 -0
  160. package/build/core/repository-ledger/index.js +15 -0
  161. package/build/core/repository-ledger/inventory-plan.js +75 -0
  162. package/build/core/repository-ledger/inventory.js +310 -0
  163. package/build/core/repository-ledger/publication-admission.js +125 -0
  164. package/build/core/repository-ledger/publication.js +353 -0
  165. package/build/core/repository-ledger/read-model.js +469 -0
  166. package/build/core/repository-ledger/transaction-membership.js +78 -0
  167. package/build/core/repository-ledger/traversal.js +404 -0
  168. package/build/core/repository-ledger/write-transaction.js +339 -0
  169. package/build/core/seal.js +168 -161
  170. package/build/core/settlement/claim-delivery.js +42 -215
  171. package/build/core/settlement/claim.js +235 -153
  172. package/build/core/settlement/index.js +4 -4
  173. package/build/core/settlement/petition-claim-gates.js +52 -33
  174. package/build/core/settlement/petition-forfeit.js +12 -5
  175. package/build/core/settlement/petition-head-guard.js +8 -5
  176. package/build/core/settlement/petition-preview.js +68 -56
  177. package/build/core/settlement/petition-window.js +50 -0
  178. package/build/core/settlement/petition.js +252 -131
  179. package/build/core/settlement/queue-read-model.js +72 -95
  180. package/build/core/settlement/queue-seat-allocation.js +35 -126
  181. package/build/core/settlement/queue.js +1 -1
  182. package/build/core/settlement/settlement.js +172 -80
  183. package/build/core/settlement/verdict.js +112 -66
  184. package/build/core/settlement/verification-coordination.js +305 -0
  185. package/build/core/settlement/verification-supervisor.js +275 -0
  186. package/build/core/settlement/verification.js +398 -31
  187. package/build/core/status/board.js +125 -127
  188. package/build/core/status/lifecycle.js +7 -80
  189. package/build/core/structured-query.js +23 -0
  190. package/build/core/target-ref.js +1 -1
  191. package/build/core/task/board.js +59 -24
  192. package/build/core/task/commands.js +181 -33
  193. package/build/core/task/coordinate.js +56 -0
  194. package/build/core/task/document.js +25 -9
  195. package/build/core/task/index.js +7 -4
  196. package/build/core/task/query.js +17 -27
  197. package/build/core/task/settlement-git.js +164 -179
  198. package/build/core/task/settlement-policy.js +15 -11
  199. package/build/core/task/source-board.js +31 -13
  200. package/build/core/task/task-bind-preparation.js +193 -0
  201. package/build/core/task/task-contract.js +102 -193
  202. package/build/core/task/task-file-transaction.js +171 -0
  203. package/build/core/task/task-git-runtime.js +76 -14
  204. package/build/core/task/task-git-store.js +68 -169
  205. package/build/core/task/task-store-repository.js +13 -6
  206. package/build/core/task/task-worktree.js +6 -33
  207. package/build/core/task/task.js +1 -1
  208. package/build/core/task/tree.js +3 -3
  209. package/build/core/task/validation.js +4 -3
  210. package/build/core/transcripts.js +93 -1
  211. package/build/core/worktree-bootstrap.js +44 -36
  212. package/build/core/worktree-path.js +210 -77
  213. package/build/flow-error.js +7 -7
  214. package/build/generated/version.js +2 -2
  215. package/build/git/core.js +76 -13
  216. package/build/git/refs.js +168 -46
  217. package/build/git/streaming-batch.js +73 -13
  218. package/package.json +1 -1
  219. package/skills/keiyaku/SKILL.md +1 -1
  220. package/skills/keiyaku-akuma/SKILL.md +3 -3
  221. package/skills/keiyaku-task/SKILL.md +4 -2
  222. package/skills/keiyaku-workflow/SKILL.md +88 -203
  223. package/build/core/entry.js +0 -305
  224. package/build/core/ledger-batch.js +0 -194
  225. package/build/core/ledger.js +0 -84
  226. package/build/core/projection/leash.js +0 -87
  227. package/build/core/ref-log.js +0 -119
  228. package/build/core/renew-session.js +0 -128
  229. package/build/core/status/ledger-batch.js +0 -1
  230. package/build/core/status/reconciliation.js +0 -137
@@ -1,4 +1,185 @@
1
1
  import { ACTOR_IDENTITY } from "../keiyaku.js";
2
+ export function isJsonObject(value) {
3
+ return value !== null && typeof value === "object" && !Array.isArray(value);
4
+ }
5
+ export function isCompleteLifecycleOutcome(outcome) {
6
+ return outcome.family === "committed" && outcome.completion.status === "complete";
7
+ }
8
+ export function isContainedLifecycleOutcome(outcome) {
9
+ return outcome.family === "committed" && outcome.completion.status === "contained";
10
+ }
11
+ function effectCause(error) {
12
+ const source = error;
13
+ const code = typeof source?.code === "string" ? source.code : "LIFECYCLE_EFFECT_FAILED";
14
+ const message = typeof source?.message === "string" ? source.message : String(error);
15
+ const facts = source?.facts && typeof source.facts === "object" && !Array.isArray(source.facts)
16
+ ? source.facts
17
+ : undefined;
18
+ return { code, message, ...(facts ? { facts } : {}) };
19
+ }
20
+ /** Append-only, deduplicating executor used after the first durable effect begins. */
21
+ export class EffectJournal {
22
+ #prefix;
23
+ #sequence = [];
24
+ #byKey = new Map();
25
+ #ledgerCommitted = false;
26
+ constructor(prefix) {
27
+ this.#prefix = prefix;
28
+ }
29
+ rows(phase) {
30
+ return this.#sequence.filter((entry) => entry.phase === phase).map((entry) => entry.row);
31
+ }
32
+ all() {
33
+ return this.#sequence.map((entry) => entry.row);
34
+ }
35
+ async run(attempt) {
36
+ const idempotencyKey = attempt.idempotencyKey ?? `${this.#prefix}:${attempt.effect}`;
37
+ const previous = this.#byKey.get(idempotencyKey);
38
+ if (previous)
39
+ return previous;
40
+ if (attempt.dependency) {
41
+ const dependency = this.#byKey.get(attempt.dependency)
42
+ ?? this.all().find((row) => row.effect === attempt.dependency);
43
+ if (!dependency || dependency.status !== "succeeded") {
44
+ return this.#append({
45
+ effect: attempt.effect,
46
+ idempotencyKey,
47
+ dependency: attempt.dependency,
48
+ status: "not-attempted",
49
+ cause: { code: "DEPENDENCY_FAILED", message: `dependency ${attempt.dependency} did not succeed` },
50
+ });
51
+ }
52
+ }
53
+ try {
54
+ const receipt = await attempt.run();
55
+ return this.#append({
56
+ effect: attempt.effect,
57
+ idempotencyKey,
58
+ ...(attempt.dependency ? { dependency: attempt.dependency } : {}),
59
+ status: "succeeded",
60
+ ...(receipt ? { receipt } : {}),
61
+ });
62
+ }
63
+ catch (error) {
64
+ return this.#append({
65
+ effect: attempt.effect,
66
+ idempotencyKey,
67
+ ...(attempt.dependency ? { dependency: attempt.dependency } : {}),
68
+ status: "failed",
69
+ cause: effectCause(error),
70
+ });
71
+ }
72
+ }
73
+ record(row) {
74
+ const previous = this.#byKey.get(row.idempotencyKey);
75
+ return previous ?? this.#append(row);
76
+ }
77
+ /** Typed ledger acceptance starts derived replay without fabricating a CAS receipt. */
78
+ beginDerivedReplay() {
79
+ this.#ledgerCommitted = true;
80
+ }
81
+ #append(row) {
82
+ const phase = this.#ledgerCommitted ? "downstream" : "prerequisite";
83
+ this.#sequence.push({ phase, row });
84
+ this.#byKey.set(row.idempotencyKey, row);
85
+ if (row.effect === "ledger-cas" && row.status === "succeeded")
86
+ this.#ledgerCommitted = true;
87
+ return row;
88
+ }
89
+ }
90
+ export function carrierLifecycleEffect(carrier) {
91
+ const idempotencyKey = `carrier:${carrier.ledgerHead}`;
92
+ if (carrier.status === "failed") {
93
+ return { effect: "carrier-projection", idempotencyKey, status: "failed", cause: { code: "CARRIER_MATERIALIZATION_FAILED", message: carrier.cause } };
94
+ }
95
+ if (carrier.status === "target-unavailable") {
96
+ return { effect: "carrier-projection", idempotencyKey, status: "failed", cause: { code: "CARRIER_TARGET_UNAVAILABLE", message: carrier.reason } };
97
+ }
98
+ return { effect: "carrier-projection", idempotencyKey, status: "succeeded", receipt: { ledgerHead: carrier.ledgerHead, status: carrier.status } };
99
+ }
100
+ export function committedLifecycleFacts(input) {
101
+ return {
102
+ family: "committed",
103
+ completion: { status: "complete" },
104
+ committed: { head: input.head, entryKind: input.entryKind },
105
+ carrier: input.carrier,
106
+ prerequisites: input.prerequisites ?? [],
107
+ downstream: input.downstream ?? [],
108
+ replay: {
109
+ idempotencyKey: `${input.contractId}:${input.head}`,
110
+ action: (input.downstream ?? []).some((row) => row.status !== "succeeded") ? "reconcile" : "none",
111
+ reason: (input.downstream ?? []).some((row) => row.status !== "succeeded")
112
+ ? "one or more committed-head effects remain"
113
+ : "all minimum effects completed",
114
+ },
115
+ cancellationObserved: input.cancellationObserved ?? false,
116
+ };
117
+ }
118
+ export function containedCommittedLifecycleFacts(input) {
119
+ const complete = committedLifecycleFacts(input);
120
+ return { ...complete, completion: { status: "contained", cause: input.cause } };
121
+ }
122
+ export function cancelledLifecycleFacts(contractId, authoritativeHead) {
123
+ return {
124
+ family: "cancelled-before-effects",
125
+ authoritativeHead,
126
+ cancellation: { code: "CARRIER_GATE_CANCELLED", message: "contract lifecycle operation cancelled before durable effects" },
127
+ prerequisites: [],
128
+ downstream: [],
129
+ replay: { idempotencyKey: `${contractId}:${authoritativeHead ?? "unobserved"}:cancelled`, action: "retry", reason: "zero durable effects were attempted" },
130
+ cancellationObserved: true,
131
+ };
132
+ }
133
+ export function refusedLifecycleFacts(input) {
134
+ return {
135
+ family: "refused-before-effects",
136
+ authoritativeHead: input.authoritativeHead,
137
+ reason: input.message,
138
+ failedInvariant: { code: input.code, message: input.message, ...(input.facts ? { facts: input.facts } : {}), ...(input.pinned ? { pinned: input.pinned } : {}), ...(input.current ? { current: input.current } : {}) },
139
+ prerequisites: [],
140
+ downstream: [],
141
+ replay: { idempotencyKey: `${input.contractId}:${input.authoritativeHead}:refused`, action: "retry", reason: "zero durable effects were attempted" },
142
+ cancellationObserved: false,
143
+ };
144
+ }
145
+ export function precommitLifecycleFacts(input) {
146
+ const durableReceipt = input.journal.all().some((row) => row.status === "succeeded");
147
+ return {
148
+ family: "precommit-effects-only",
149
+ authoritativeHead: input.authoritativeHead,
150
+ prerequisites: input.journal.rows("prerequisite"),
151
+ downstream: input.journal.rows("downstream"),
152
+ replay: {
153
+ idempotencyKey: `${input.contractId}:${input.authoritativeHead}:precommit`,
154
+ action: durableReceipt ? "reconcile" : "retry",
155
+ reason: durableReceipt
156
+ ? "durable prerequisite receipts require idempotent reconciliation"
157
+ : "no successful durable prerequisite receipt was recorded",
158
+ },
159
+ cancellationObserved: input.cancellationObserved ?? false,
160
+ };
161
+ }
162
+ export function derivedCleanupFacts(input) {
163
+ const cleanup = {
164
+ effect: input.effect,
165
+ idempotencyKey: `${input.contractId}:${input.authoritativeHead}:${input.effect}`,
166
+ status: "succeeded",
167
+ receipt: input.receipt,
168
+ };
169
+ return {
170
+ family: "precommit-effects-only",
171
+ authoritativeHead: input.authoritativeHead,
172
+ cleanup,
173
+ prerequisites: [cleanup],
174
+ downstream: [],
175
+ replay: {
176
+ idempotencyKey: `${input.contractId}:${input.authoritativeHead}:${input.effect}`,
177
+ action: "none",
178
+ reason: "derived cleanup completed without changing lifecycle authority",
179
+ },
180
+ cancellationObserved: false,
181
+ };
182
+ }
2
183
  export function defaultActorClock(nowMs = Date.now()) {
3
184
  return { actor: ACTOR_IDENTITY, nowMs };
4
185
  }
@@ -0,0 +1,139 @@
1
+ import { execFile } from "node:child_process";
2
+ export const PROCESS_GROUP_KILL_GRACE_MS = 10_000;
3
+ const PROCESS_DISAPPEARANCE_POLL_MS = 25;
4
+ export class ProcessTreeTerminationError extends Error {
5
+ code = "TREE_TERMINATION_FAILED";
6
+ constructor(message, options) {
7
+ super(message, options);
8
+ this.name = "ProcessTreeTerminationError";
9
+ }
10
+ }
11
+ function defaultWait(ms) {
12
+ return new Promise((resolve) => setTimeout(resolve, ms));
13
+ }
14
+ function isMissingProcessError(error) {
15
+ return Boolean(error && typeof error === "object" && "code" in error && error.code === "ESRCH");
16
+ }
17
+ export function ownedProcessIdentity(child, platform = process.platform) {
18
+ if (!child.pid)
19
+ return undefined;
20
+ return { pid: child.pid, platform: platform === "win32" ? "win32" : "posix" };
21
+ }
22
+ export function ownedProcessIsLive(identity) {
23
+ try {
24
+ process.kill(identity.platform === "posix" ? -identity.pid : identity.pid, 0);
25
+ return true;
26
+ }
27
+ catch (error) {
28
+ return !isMissingProcessError(error);
29
+ }
30
+ }
31
+ function signalOwnedProcess(identity, signal) {
32
+ try {
33
+ process.kill(identity.platform === "posix" ? -identity.pid : identity.pid, signal);
34
+ }
35
+ catch (error) {
36
+ if (!isMissingProcessError(error))
37
+ throw error;
38
+ }
39
+ }
40
+ async function waitForDisappearance(identity, timeoutMs, wait) {
41
+ const deadline = Date.now() + timeoutMs;
42
+ while (ownedProcessIsLive(identity)) {
43
+ const remaining = deadline - Date.now();
44
+ if (remaining <= 0)
45
+ return false;
46
+ await wait(Math.min(PROCESS_DISAPPEARANCE_POLL_MS, remaining));
47
+ }
48
+ return true;
49
+ }
50
+ function waitForChildClose(child) {
51
+ return new Promise((resolve) => {
52
+ child.once("close", resolve);
53
+ child.once("error", resolve);
54
+ });
55
+ }
56
+ function defaultTaskkill(pid) {
57
+ return new Promise((resolve, reject) => {
58
+ execFile("taskkill", ["/PID", String(pid), "/T", "/F"], { windowsHide: true }, (error) => {
59
+ if (error)
60
+ reject(error);
61
+ else
62
+ resolve();
63
+ });
64
+ });
65
+ }
66
+ async function terminateIdentity(identity, options) {
67
+ if (!ownedProcessIsLive(identity))
68
+ return;
69
+ if (identity.platform === "win32") {
70
+ try {
71
+ await options.taskkill(identity.pid);
72
+ }
73
+ catch (error) {
74
+ if (!ownedProcessIsLive(identity))
75
+ return;
76
+ throw error;
77
+ }
78
+ if (!await waitForDisappearance(identity, options.graceMs, options.wait)) {
79
+ throw new Error(`owned process tree ${identity.pid} remained after taskkill`);
80
+ }
81
+ return;
82
+ }
83
+ signalOwnedProcess(identity, "SIGTERM");
84
+ if (await waitForDisappearance(identity, options.graceMs, options.wait))
85
+ return;
86
+ signalOwnedProcess(identity, "SIGKILL");
87
+ if (!await waitForDisappearance(identity, options.graceMs, options.wait)) {
88
+ throw new Error(`owned process group ${identity.pid} remained after SIGKILL`);
89
+ }
90
+ }
91
+ /** Terminate an orphaned runner identity retained by repository coordination. */
92
+ export async function terminateOwnedProcess(identity, options = {}) {
93
+ try {
94
+ await terminateIdentity(identity, {
95
+ graceMs: options.graceMs ?? PROCESS_GROUP_KILL_GRACE_MS,
96
+ wait: options.wait ?? defaultWait,
97
+ taskkill: options.taskkill ?? defaultTaskkill,
98
+ });
99
+ }
100
+ catch (error) {
101
+ throw new ProcessTreeTerminationError(error instanceof Error ? error.message : String(error), { cause: error });
102
+ }
103
+ }
104
+ /** Terminate a detached child tree, then await group disappearance and pipe-draining close. */
105
+ export async function terminateProcessGroup(child, options = {}) {
106
+ const graceMs = options.graceMs ?? PROCESS_GROUP_KILL_GRACE_MS;
107
+ const wait = options.wait ?? defaultWait;
108
+ const platform = options.platform ?? process.platform;
109
+ const identity = ownedProcessIdentity(child, platform);
110
+ const closed = waitForChildClose(child);
111
+ try {
112
+ if (platform === "win32" && identity) {
113
+ await Promise.all([
114
+ (options.taskkill ?? defaultTaskkill)(identity.pid),
115
+ closed,
116
+ ]);
117
+ return;
118
+ }
119
+ if (identity) {
120
+ await Promise.all([
121
+ terminateIdentity(identity, {
122
+ graceMs,
123
+ wait,
124
+ taskkill: options.taskkill ?? defaultTaskkill,
125
+ }),
126
+ closed,
127
+ ]);
128
+ return;
129
+ }
130
+ child.kill("SIGTERM");
131
+ await wait(graceMs);
132
+ if (child.exitCode === null && child.signalCode === null)
133
+ child.kill("SIGKILL");
134
+ await closed;
135
+ }
136
+ catch (error) {
137
+ throw new ProcessTreeTerminationError(error instanceof Error ? error.message : String(error), { cause: error });
138
+ }
139
+ }
@@ -4,7 +4,7 @@ import { DatabaseSync } from "node:sqlite";
4
4
  import { configureHeartBusyTimeout } from "../heart.js";
5
5
  import { ProjectionGenerationStoreError, parseStoredRow, validateHistory, } from "./model.js";
6
6
  export const PROJECTION_GENERATION_STORE_FILE = "heart";
7
- export const PROJECTION_GENERATION_SCHEMA_VERSION = 3;
7
+ export const PROJECTION_GENERATION_SCHEMA_VERSION = 4;
8
8
  const SCHEMA_TABLE_SQL = `
9
9
  CREATE TABLE generation_schema (
10
10
  singleton INTEGER PRIMARY KEY CHECK (singleton = 1),
@@ -42,7 +42,7 @@ const TELL_TABLE_SQL = `
42
42
  created_at TEXT NOT NULL CHECK (length(trim(created_at)) > 0),
43
43
  effort TEXT CHECK (effort IS NULL OR length(trim(effort)) > 0),
44
44
  state TEXT NOT NULL CHECK (
45
- state IN ('inbox', 'inflight', 'submitted', 'consumed', 'delivered')
45
+ state IN ('inbox', 'inflight', 'submitted', 'seen', 'consumed')
46
46
  ),
47
47
  execution_id TEXT CHECK (
48
48
  execution_id IS NULL OR length(trim(execution_id)) > 0
@@ -50,12 +50,47 @@ const TELL_TABLE_SQL = `
50
50
  fence_json TEXT CHECK (fence_json IS NULL OR json_valid(fence_json)),
51
51
  replayed_from_json TEXT CHECK (
52
52
  replayed_from_json IS NULL OR json_valid(replayed_from_json)
53
- ),
54
- delivered_at TEXT CHECK (
55
- delivered_at IS NULL OR length(trim(delivered_at)) > 0
56
53
  )
57
54
  ) STRICT
58
55
  `;
56
+ const TELL_RECEIPT_TABLE_SQL = `
57
+ CREATE TABLE projection_tell_receipts (
58
+ tell_id TEXT NOT NULL REFERENCES projection_tells(tell_id),
59
+ execution_id TEXT NOT NULL CHECK (length(trim(execution_id)) > 0),
60
+ stage TEXT NOT NULL CHECK (stage IN ('seen', 'consumed')),
61
+ correlation_kind TEXT NOT NULL CHECK (correlation_kind IN ('exact', 'fence', 'inherited')),
62
+ fence_json TEXT CHECK (fence_json IS NULL OR json_valid(fence_json)),
63
+ received_at TEXT NOT NULL CHECK (length(trim(received_at)) > 0),
64
+ PRIMARY KEY (tell_id, execution_id, stage)
65
+ ) STRICT
66
+ `;
67
+ const TELL_RECEIPT_CAPABILITY_TABLE_SQL = `
68
+ CREATE TABLE projection_tell_receipt_capabilities (
69
+ execution_id TEXT PRIMARY KEY CHECK (length(trim(execution_id)) > 0),
70
+ seen TEXT NOT NULL CHECK (seen IN ('exact', 'fence', 'unavailable')),
71
+ consumed TEXT NOT NULL CHECK (consumed IN ('exact', 'fence', 'unavailable'))
72
+ ) STRICT
73
+ `;
74
+ const TELL_LAUNCH_BATCH_TABLE_SQL = `
75
+ CREATE TABLE projection_tell_launch_batches (
76
+ execution_id TEXT NOT NULL CHECK (length(trim(execution_id)) > 0),
77
+ tell_id TEXT NOT NULL REFERENCES projection_tells(tell_id),
78
+ PRIMARY KEY (execution_id, tell_id)
79
+ ) STRICT
80
+ `;
81
+ /** One row records that an execution's launch-input membership is fixed, even when empty. */
82
+ const TELL_LAUNCH_ADMISSION_TABLE_SQL = `
83
+ CREATE TABLE projection_tell_launch_admissions (
84
+ execution_id TEXT PRIMARY KEY CHECK (length(trim(execution_id)) > 0)
85
+ ) STRICT
86
+ `;
87
+ const TELL_LAUNCH_COORDINATE_TABLE_SQL = `
88
+ CREATE TABLE projection_tell_launch_coordinates (
89
+ execution_id TEXT NOT NULL CHECK (length(trim(execution_id)) > 0),
90
+ fence_json TEXT NOT NULL CHECK (json_valid(fence_json)),
91
+ PRIMARY KEY (execution_id, fence_json)
92
+ ) STRICT
93
+ `;
59
94
  const closedDatabases = new WeakSet();
60
95
  /** Classify only proved missing schema evidence as historical incompatibility. */
61
96
  export function projectionGenerationStoreInspectionError(databasePath, error) {
@@ -114,7 +149,7 @@ function validateSchemaShape(database) {
114
149
  ORDER BY name
115
150
  `).all();
116
151
  const signature = objects.map((object) => `${object.type}:${object.name}`).join(",");
117
- if (signature !== "table:generation_records,table:generation_schema,table:projection_identity,table:projection_tells") {
152
+ if (signature !== "table:generation_records,table:generation_schema,table:projection_identity,table:projection_tell_launch_admissions,table:projection_tell_launch_batches,table:projection_tell_launch_coordinates,table:projection_tell_receipt_capabilities,table:projection_tell_receipts,table:projection_tells") {
118
153
  throw new ProjectionGenerationStoreError(`projection generation schema contains unexpected objects: ${signature || "none"}`, { observation: "incompatible" });
119
154
  }
120
155
  const metadata = database.prepare("SELECT singleton, version FROM generation_schema").all();
@@ -139,9 +174,44 @@ function validateSchemaShape(database) {
139
174
  const tellSignature = tellColumns
140
175
  .map((column) => `${column.name}:${column.type}:${Number(column.notnull)}:${Number(column.pk)}`)
141
176
  .join(",");
142
- if (tellSignature !== "tell_id:TEXT:1:1,text:TEXT:1:0,created_at:TEXT:1:0,effort:TEXT:0:0,state:TEXT:1:0,execution_id:TEXT:0:0,fence_json:TEXT:0:0,replayed_from_json:TEXT:0:0,delivered_at:TEXT:0:0") {
177
+ if (tellSignature !== "tell_id:TEXT:1:1,text:TEXT:1:0,created_at:TEXT:1:0,effort:TEXT:0:0,state:TEXT:1:0,execution_id:TEXT:0:0,fence_json:TEXT:0:0,replayed_from_json:TEXT:0:0") {
143
178
  throw new ProjectionGenerationStoreError("projection tell schema is malformed", { observation: "incompatible" });
144
179
  }
180
+ const receiptColumns = database.prepare("PRAGMA table_info(projection_tell_receipts)").all();
181
+ const receiptSignature = receiptColumns
182
+ .map((column) => `${column.name}:${column.type}:${Number(column.notnull)}:${Number(column.pk)}`)
183
+ .join(",");
184
+ if (receiptSignature !== "tell_id:TEXT:1:1,execution_id:TEXT:1:2,stage:TEXT:1:3,correlation_kind:TEXT:1:0,fence_json:TEXT:0:0,received_at:TEXT:1:0") {
185
+ throw new ProjectionGenerationStoreError("projection tell receipt schema is malformed", { observation: "incompatible" });
186
+ }
187
+ const capabilityColumns = database.prepare("PRAGMA table_info(projection_tell_receipt_capabilities)").all();
188
+ const capabilitySignature = capabilityColumns
189
+ .map((column) => `${column.name}:${column.type}:${Number(column.notnull)}:${Number(column.pk)}`)
190
+ .join(",");
191
+ if (capabilitySignature !== "execution_id:TEXT:1:1,seen:TEXT:1:0,consumed:TEXT:1:0") {
192
+ throw new ProjectionGenerationStoreError("projection tell receipt capability schema is malformed", { observation: "incompatible" });
193
+ }
194
+ const launchBatchColumns = database.prepare("PRAGMA table_info(projection_tell_launch_batches)").all();
195
+ const launchBatchSignature = launchBatchColumns
196
+ .map((column) => `${column.name}:${column.type}:${Number(column.notnull)}:${Number(column.pk)}`)
197
+ .join(",");
198
+ if (launchBatchSignature !== "execution_id:TEXT:1:1,tell_id:TEXT:1:2") {
199
+ throw new ProjectionGenerationStoreError("projection tell launch batch schema is malformed", { observation: "incompatible" });
200
+ }
201
+ const launchAdmissionColumns = database.prepare("PRAGMA table_info(projection_tell_launch_admissions)").all();
202
+ const launchAdmissionSignature = launchAdmissionColumns
203
+ .map((column) => `${column.name}:${column.type}:${Number(column.notnull)}:${Number(column.pk)}`)
204
+ .join(",");
205
+ if (launchAdmissionSignature !== "execution_id:TEXT:1:1") {
206
+ throw new ProjectionGenerationStoreError("projection tell launch admission schema is malformed", { observation: "incompatible" });
207
+ }
208
+ const launchCoordinateColumns = database.prepare("PRAGMA table_info(projection_tell_launch_coordinates)").all();
209
+ const launchCoordinateSignature = launchCoordinateColumns
210
+ .map((column) => `${column.name}:${column.type}:${Number(column.notnull)}:${Number(column.pk)}`)
211
+ .join(",");
212
+ if (launchCoordinateSignature !== "execution_id:TEXT:1:1,fence_json:TEXT:1:2") {
213
+ throw new ProjectionGenerationStoreError("projection tell launch coordinate schema is malformed", { observation: "incompatible" });
214
+ }
145
215
  const quickCheck = database.prepare("PRAGMA quick_check").get();
146
216
  if (!quickCheck || Object.values(quickCheck)[0] !== "ok") {
147
217
  throw new ProjectionGenerationStoreError("projection generation database failed quick_check", { observation: "incompatible" });
@@ -162,6 +232,11 @@ function initializeOrValidateSchema(database) {
162
232
  database.exec(RECORD_TABLE_SQL);
163
233
  database.exec(IDENTITY_TABLE_SQL);
164
234
  database.exec(TELL_TABLE_SQL);
235
+ database.exec(TELL_RECEIPT_TABLE_SQL);
236
+ database.exec(TELL_RECEIPT_CAPABILITY_TABLE_SQL);
237
+ database.exec(TELL_LAUNCH_BATCH_TABLE_SQL);
238
+ database.exec(TELL_LAUNCH_ADMISSION_TABLE_SQL);
239
+ database.exec(TELL_LAUNCH_COORDINATE_TABLE_SQL);
165
240
  database.prepare("INSERT INTO generation_schema (singleton, version) VALUES (1, ?)")
166
241
  .run(PROJECTION_GENERATION_SCHEMA_VERSION);
167
242
  database.exec(`PRAGMA user_version = ${PROJECTION_GENERATION_SCHEMA_VERSION}`);
@@ -1,16 +1,7 @@
1
1
  import { GENERATION_VERDICT_STATES, } from "../projection-life-protocol.js";
2
- import { OUTCOME_ERROR_VERSION, SUBAGENT_FAILURE_CODES } from "../../../agents/types.js";
2
+ import { OutcomeErrorValidationError, parseCurrentOutcomeError, } from "../../../agents/harness/index.js";
3
3
  export { GENERATION_VERDICT_STATES };
4
4
  export const VERDICTS = new Set(GENERATION_VERDICT_STATES);
5
- const OUTCOME_ERROR_CODES = new Set(SUBAGENT_FAILURE_CODES);
6
- const OUTCOME_ERROR_KEYS = new Set([
7
- "version",
8
- "code",
9
- "message",
10
- "timeoutMs",
11
- "exitCode",
12
- "publicCause",
13
- ]);
14
5
  export class ProjectionGenerationStoreError extends Error {
15
6
  code = "PROJECTION_GENERATION_STORE_ERROR";
16
7
  /** Historical schemas/outcomes are not a current lifecycle observation. */
@@ -152,34 +143,16 @@ export function validateVerdictFacts(value, location) {
152
143
  facts.terminalAt = assertIsoTimestamp(facts.terminalAt, `${location}.terminalAt`);
153
144
  }
154
145
  if (facts.state === "failed") {
155
- const error = isPlainObject(facts.error) ? facts.error : undefined;
156
- const version = error?.version;
157
- if (!error || version !== OUTCOME_ERROR_VERSION) {
158
- throw new ProjectionGenerationStoreError(`${location}.error has incompatible version ${version === undefined ? "missing" : String(version)}; expected ${OUTCOME_ERROR_VERSION}`, { observation: "incompatible" });
146
+ try {
147
+ facts.error = parseCurrentOutcomeError(facts.error, `${location}.error`);
159
148
  }
160
- if (Object.keys(error).some((key) => !OUTCOME_ERROR_KEYS.has(key))) {
161
- throw new ProjectionGenerationStoreError(`${location}.error is not the current closed OutcomeError shape`);
162
- }
163
- if (typeof error.code !== "string" || !OUTCOME_ERROR_CODES.has(error.code)) {
164
- throw new ProjectionGenerationStoreError(`${location}.error.code is not a recognized subagent failure code`);
165
- }
166
- if (typeof error.message !== "string") {
167
- throw new ProjectionGenerationStoreError(`${location}.error.message must be a string`);
168
- }
169
- if (error.timeoutMs !== null && (typeof error.timeoutMs !== "number"
170
- || !Number.isSafeInteger(error.timeoutMs)
171
- || error.timeoutMs < 0)) {
172
- throw new ProjectionGenerationStoreError(`${location}.error.timeoutMs must be null or a non-negative safe integer`);
173
- }
174
- if (error.exitCode !== null && (typeof error.exitCode !== "number"
175
- || !Number.isSafeInteger(error.exitCode))) {
176
- throw new ProjectionGenerationStoreError(`${location}.error.exitCode must be null or a safe integer`);
177
- }
178
- if (error.publicCause !== undefined) {
179
- const cause = isPlainObject(error.publicCause) ? error.publicCause : undefined;
180
- if (!cause || cause.kind !== "rate_limit_exhausted" || Object.keys(cause).length !== 1) {
181
- throw new ProjectionGenerationStoreError(`${location}.error.publicCause is not a current closed cause`);
149
+ catch (error) {
150
+ if (error instanceof OutcomeErrorValidationError) {
151
+ throw new ProjectionGenerationStoreError(error.message, {
152
+ observation: error.incompatible ? "incompatible" : "unknown",
153
+ });
182
154
  }
155
+ throw error;
183
156
  }
184
157
  }
185
158
  return facts;
@@ -3,14 +3,6 @@ import { applySnapshotEffort, parseResolvedAkumaLaunchSnapshot, } from "../../..
3
3
  import { listTellMetadataFromDatabase, readPendingTellWindowFromDatabase, } from "../tell/store.js";
4
4
  import { PENDING_PROJECTION_TELL_STATES } from "../tell/model.js";
5
5
  import { readValidatedHistory, withGenerationReadSnapshot } from "./database.js";
6
- function retainedFenceFor(current) {
7
- return current?.verdict?.facts.state === "launch-failed"
8
- ? current.launch.facts.tellFence ?? []
9
- : [];
10
- }
11
- function interruptedForReplay(current) {
12
- return current?.verdict?.facts.state === "interrupted";
13
- }
14
6
  function snapshotTells(database, layers, admit) {
15
7
  const pending = listTellMetadataFromDatabase(database, layers)
16
8
  .filter((item) => admit?.({
@@ -26,18 +18,19 @@ function snapshotTells(database, layers, admit) {
26
18
  ...(effort ? { effort } : {}),
27
19
  };
28
20
  }
29
- /** Ordinary completed-generation handoff retains only tells not yet delivered. */
21
+ /** All non-consumed tells remain eligible for an ordinary successor. */
30
22
  export function snapshotPendingTells(projectionDirectory) {
31
23
  return withGenerationReadSnapshot(projectionDirectory, (database) => snapshotTells(database, PENDING_PROJECTION_TELL_STATES));
32
24
  }
33
- /** Interrupted generations replay pending mail and their own delivered tells. */
25
+ /** Interrupt successors replay every non-consumed tell. */
34
26
  export function snapshotInterruptTells(projectionDirectory, executionId) {
35
- return withGenerationReadSnapshot(projectionDirectory, (database) => snapshotTells(database, [...PENDING_PROJECTION_TELL_STATES, "delivered"], (item) => item.layer !== "delivered" || item.executionId === executionId));
27
+ void executionId;
28
+ return withGenerationReadSnapshot(projectionDirectory, (database) => snapshotTells(database, PENDING_PROJECTION_TELL_STATES));
36
29
  }
37
- /** Retry ordinary pending mail plus exactly the durable fence of a failed launch. */
30
+ /** Failed launch recovery retains every non-consumed tell. */
38
31
  export function snapshotRetainedSuccessorTells(projectionDirectory, retainedFence) {
39
- const retained = new Set(retainedFence);
40
- return withGenerationReadSnapshot(projectionDirectory, (database) => snapshotTells(database, [...PENDING_PROJECTION_TELL_STATES, "delivered"], ({ layer, tellId }) => layer !== "delivered" || retained.has(tellId)));
32
+ void retainedFence;
33
+ return withGenerationReadSnapshot(projectionDirectory, (database) => snapshotTells(database, PENDING_PROJECTION_TELL_STATES));
41
34
  }
42
35
  /** Build the durable successor fence only after continuation is admitted. */
43
36
  export function snapshotProjectionContinuationTells(projectionDirectory, current) {
@@ -47,14 +40,9 @@ export function snapshotProjectionContinuationTells(projectionDirectory, current
47
40
  }
48
41
  return snapshot.successor;
49
42
  }
50
- function readContinuationFromDatabase(database, current, limit = 10) {
43
+ function readContinuationFromDatabase(database, limit = 10) {
51
44
  const tells = readPendingTellWindowFromDatabase(database, limit);
52
- const retainedFence = retainedFenceFor(current);
53
- const successor = interruptedForReplay(current)
54
- ? snapshotTells(database, [...PENDING_PROJECTION_TELL_STATES, "delivered"], (item) => item.layer !== "delivered" || item.executionId === current.launch.executionId)
55
- : retainedFence.length > 0
56
- ? snapshotTells(database, [...PENDING_PROJECTION_TELL_STATES, "delivered"], ({ layer, tellId }) => layer !== "delivered" || retainedFence.includes(tellId))
57
- : snapshotTells(database, PENDING_PROJECTION_TELL_STATES);
45
+ const successor = snapshotTells(database, PENDING_PROJECTION_TELL_STATES);
58
46
  return {
59
47
  tells,
60
48
  successor,
@@ -65,7 +53,7 @@ function readContinuationFromDatabase(database, current, limit = 10) {
65
53
  export function readProjectionGenerationContinuationSnapshot(projectionDirectory, limit = 10) {
66
54
  return withGenerationReadSnapshot(projectionDirectory, (database) => {
67
55
  const { records, current } = readValidatedHistory(database);
68
- const continuation = readContinuationFromDatabase(database, current, limit);
56
+ const continuation = readContinuationFromDatabase(database, limit);
69
57
  return {
70
58
  records,
71
59
  current,
@@ -2,7 +2,7 @@ import { initializeConfig } from "../../../config/env.js";
2
2
  import { buildCallTerms } from "../../../agents/call-terms.js";
3
3
  import { parseResolvedAkumaLaunchSnapshot } from "../../../agents/launch-snapshot/model.js";
4
4
  import { revive, call, } from "../../../agents/harness/index.js";
5
- import { claimFencedTells, readTellOriginal, readProjectionEvents, } from "../projection-core.js";
5
+ import { claimAndReserveFencedTells, readTellOriginal, readProjectionEvents, } from "../projection-core.js";
6
6
  import { createProjectionExecutionObserver, renderProjectionTellFrame, } from "../projection-execution-observer.js";
7
7
  import { readProjectionIdentity } from "../projection-identity.js";
8
8
  import { selectValidStoredAgentEvents } from "../../stored-agent-event.js";
@@ -26,6 +26,12 @@ function requirePositiveSafeInteger(value, field) {
26
26
  }
27
27
  return value;
28
28
  }
29
+ function requireNonNegativeSafeInteger(value, field) {
30
+ if (typeof value !== "number" || !Number.isSafeInteger(value) || value < 0) {
31
+ throw new Error(`generation launch ${field} must be a non-negative safe integer`);
32
+ }
33
+ return value;
34
+ }
29
35
  function parseLaunchInputs(launch) {
30
36
  const raw = launch.facts.inputs;
31
37
  if (!isObject(raw))
@@ -58,7 +64,7 @@ function parseLaunchInputs(launch) {
58
64
  ...(context ? { context } : {}),
59
65
  launchSnapshot,
60
66
  idleTimeoutMs: requirePositiveSafeInteger(raw.idleTimeoutMs, "idleTimeoutMs"),
61
- executionTimeoutMs: requirePositiveSafeInteger(raw.executionTimeoutMs, "executionTimeoutMs"),
67
+ executionTimeoutMs: requireNonNegativeSafeInteger(raw.executionTimeoutMs, "executionTimeoutMs"),
62
68
  persistProjectionResponse: raw.persistProjectionResponse,
63
69
  ...(raw.session ? { session: raw.session } : {}),
64
70
  ...(resumePath ? { resumePath } : {}),
@@ -104,6 +110,7 @@ async function completionFacts(projectionDirectory, executionId, inputs, outcome
104
110
  },
105
111
  task: inputs.task,
106
112
  response: outcome.finalMessage,
113
+ ...(outcome.error ? { terminalFailure: outcome.error } : {}),
107
114
  session: outcome.session,
108
115
  launchSnapshot: inputs.launchSnapshot,
109
116
  effectivePolicy: inputs.launchSnapshot.akuma.policy,
@@ -140,7 +147,7 @@ export function prepareProjectionGenerationExecution(projectionDirectory, launch
140
147
  const inputs = parseLaunchInputs(launch);
141
148
  initializeConfig(process.env, inputs.cwd);
142
149
  const executionId = launch.executionId;
143
- const initialTellIds = claimFencedTells(projectionDirectory, launch.facts.tellFence ?? [], executionId);
150
+ const initialTellIds = claimAndReserveFencedTells(projectionDirectory, launch.facts.tellFence ?? [], executionId);
144
151
  const tellPrompt = initialTellIds.map((tellId) => {
145
152
  const tell = readTellOriginal(projectionDirectory, "inflight", tellId);
146
153
  return renderProjectionTellFrame(tellId, tell.text);
@@ -158,7 +165,7 @@ export function prepareProjectionGenerationExecution(projectionDirectory, launch
158
165
  const observerController = createProjectionExecutionObserver({
159
166
  projectionDirectory,
160
167
  executionId,
161
- initialTellIds,
168
+ reservedLaunchTellIds: initialTellIds,
162
169
  });
163
170
  return {
164
171
  start() {
@@ -169,8 +176,8 @@ export function prepareProjectionGenerationExecution(projectionDirectory, launch
169
176
  ...terms,
170
177
  session: inputs.session,
171
178
  resumePath: inputs.resumePath ?? projectionDirectory,
172
- }, { observer: observerController.observer })
173
- : call(terms, { observer: observerController.observer });
179
+ }, { observer: observerController.observer, launchTellIds: initialTellIds })
180
+ : call(terms, { observer: observerController.observer, launchTellIds: initialTellIds });
174
181
  }
175
182
  catch (error) {
176
183
  void observerController.close();