@astrosheep/keiyaku 1.0.0 → 1.0.2

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 (123) hide show
  1. package/README.md +73 -56
  2. package/build/.tsbuildinfo +1 -1
  3. package/build/agents/call-terms_v2.js +103 -0
  4. package/build/agents/harness/activity-values.js +54 -0
  5. package/build/agents/harness/event-channel.js +55 -20
  6. package/build/agents/harness/events.js +47 -1
  7. package/build/agents/harness/execution-handle.js +292 -61
  8. package/build/agents/harness/index.js +103 -121
  9. package/build/agents/harness/pump.js +36 -47
  10. package/build/agents/harness/runtime.js +3 -0
  11. package/build/agents/launch-snapshot_v2.js +348 -0
  12. package/build/agents/opencode-sdk.js +42 -169
  13. package/build/agents/providers/claude-agent-sdk.js +240 -79
  14. package/build/agents/providers/codex-app-server.js +280 -72
  15. package/build/agents/providers/codex-sdk.js +159 -41
  16. package/build/agents/providers/event-type-tail.js +16 -0
  17. package/build/agents/providers/opencode-sdk.js +165 -59
  18. package/build/agents/providers/pi.js +254 -42
  19. package/build/agents/selector_v2.js +122 -0
  20. package/build/cli/commands/akuma.js +120 -42
  21. package/build/cli/completion.js +6 -7
  22. package/build/cli/flags.js +36 -10
  23. package/build/cli/help.js +42 -26
  24. package/build/cli/index.js +225 -93
  25. package/build/cli/parse.js +194 -75
  26. package/build/cli/projection-address.js +11 -0
  27. package/build/cli/render/address.js +88 -0
  28. package/build/cli/render/arc.js +10 -10
  29. package/build/cli/render/call.js +59 -0
  30. package/build/cli/render/compact-text.js +3 -0
  31. package/build/cli/render/errors.js +4 -2
  32. package/build/cli/render/format.js +6 -0
  33. package/build/cli/render/line-width.js +70 -0
  34. package/build/cli/render/misc.js +7 -4
  35. package/build/cli/render/petition.js +4 -3
  36. package/build/cli/render/projection-activity.js +201 -0
  37. package/build/cli/render/shared.js +46 -12
  38. package/build/cli/render/status.js +59 -10
  39. package/build/cli/render/tool-ledger-rollup.js +74 -0
  40. package/build/cli/render/tool-presentation.js +97 -0
  41. package/build/cli/render/wait.js +229 -0
  42. package/build/cli/skills-install.js +42 -24
  43. package/build/cli/subagent-guard.js +1 -1
  44. package/build/config/akuma-loader_v2.js +222 -0
  45. package/build/config/env-keys.js +0 -4
  46. package/build/config/env.js +0 -6
  47. package/build/config/settings.js +325 -12
  48. package/build/core/addressing.js +224 -0
  49. package/build/core/amend.js +30 -47
  50. package/build/core/arc.js +68 -60
  51. package/build/core/bind.js +105 -25
  52. package/build/core/call-persist.js +69 -0
  53. package/build/core/call.js +658 -0
  54. package/build/core/claim.js +72 -44
  55. package/build/core/command-io.js +19 -19
  56. package/build/core/context.js +11 -36
  57. package/build/core/draft.js +2 -3
  58. package/build/core/entry.js +101 -6
  59. package/build/core/execution-coordinate.js +62 -0
  60. package/build/core/execution-pact.js +141 -0
  61. package/build/core/forfeit.js +2 -3
  62. package/build/core/hints.js +10 -10
  63. package/build/core/ids.js +6 -0
  64. package/build/core/ledger.js +16 -1
  65. package/build/core/petition-claim-gates.js +14 -12
  66. package/build/core/petition-claim.js +1 -0
  67. package/build/core/petition-run.js +6 -2
  68. package/build/core/petition.js +63 -21
  69. package/build/core/places.js +106 -13
  70. package/build/core/projection/generation.js +412 -0
  71. package/build/core/projection/heart.js +316 -0
  72. package/build/core/projection/identity.js +81 -0
  73. package/build/core/projection/leash.js +87 -0
  74. package/build/core/projection/mint.js +154 -0
  75. package/build/core/projection-activity.js +219 -0
  76. package/build/core/projection-coordinate.js +35 -0
  77. package/build/core/projection-core.js +69 -463
  78. package/build/core/projection-generation-continuation.js +31 -0
  79. package/build/core/projection-generation-execution.js +148 -0
  80. package/build/core/projection-generation-identity.js +17 -0
  81. package/build/core/projection-generation-launcher.js +90 -0
  82. package/build/core/projection-generation-process.js +43 -0
  83. package/build/core/projection-generation-runner.js +144 -0
  84. package/build/core/projection-generation-runtime.js +15 -0
  85. package/build/core/projection-generation-store.js +707 -0
  86. package/build/core/projection-identity.js +11 -0
  87. package/build/core/projection-life-observer.js +87 -0
  88. package/build/core/projection-life-protocol.js +95 -0
  89. package/build/core/projection-mint.js +96 -24
  90. package/build/core/projection-runner-lock.js +230 -0
  91. package/build/core/projection-status.js +207 -107
  92. package/build/core/projection-tells.js +401 -0
  93. package/build/core/projection-wait.js +155 -0
  94. package/build/core/projection-wake.js +109 -231
  95. package/build/core/queue_v2.js +342 -0
  96. package/build/core/registry.js +6 -3
  97. package/build/core/render.js +63 -1
  98. package/build/core/renew.js +80 -53
  99. package/build/core/scope.js +186 -137
  100. package/build/core/seal.js +16 -19
  101. package/build/core/status.js +88 -25
  102. package/build/core/stored-agent-event.js +101 -0
  103. package/build/core/transcripts.js +214 -228
  104. package/build/core/verdict.js +27 -15
  105. package/build/core/worktree-bootstrap.js +187 -0
  106. package/build/core/worktree-path.js +72 -18
  107. package/build/flow-error.js +4 -6
  108. package/build/generated/version.js +1 -1
  109. package/build/git/branches.js +6 -1
  110. package/build/index.js +8 -5
  111. package/build/keiyaku.js +1 -3
  112. package/package.json +6 -5
  113. package/skills/keiyaku/SKILL.md +6 -47
  114. package/skills/keiyaku-akuma/SKILL.md +75 -0
  115. package/build/agents/effective-policy.js +0 -32
  116. package/build/agents/index.js +0 -114
  117. package/build/agents/selector.js +0 -28
  118. package/build/cli/render/summon.js +0 -17
  119. package/build/cli/render/tell.js +0 -11
  120. package/build/config/akuma-loader.js +0 -268
  121. package/build/core/queue.js +0 -73
  122. package/build/core/summon-persist.js +0 -73
  123. package/build/core/summon.js +0 -377
@@ -9,37 +9,38 @@ import { startOpencode, opencodeSessionSchema, } from "../providers/opencode-sdk
9
9
  import { startPi, piSessionSchema, } from "../providers/pi.js";
10
10
  import { SubagentFailure, } from "../types.js";
11
11
  import { createEventChannel } from "./event-channel.js";
12
+ import { overflowMarkerFromDropped } from "./execution-handle.js";
12
13
  import { buildSyncCheckpointCallback, createReplayRunStartGate, markReplayCompleted, pumpSupervisor, snapshotReplayPrompt } from "./pump.js";
13
14
  import { createAbortError, renderCapturedText } from "./runtime.js";
14
- import { acquireAdmissionLock, createProjectionExecutionId, demoteSubmittedTellsForReplay, generationResultPath, listTellIds, markTellSubmitted, publishGenerationResult, publishVerdict, readAdmissionLock, readGenerationLaunch, releaseAdmissionLockOwned, touchHeartbeat } from "../../core/projection-core.js";
15
- import { wakeProjection } from "../../core/projection-wake.js";
16
- export function summon(terms) {
15
+ import { demoteSubmittedTellsForReplay, listTellIds, markTellSubmitted } from "../../core/projection-core.js";
16
+ export { activityEventBodySchema, AGENT_EVENT_TEXT_MAX_CHARS, AGENT_UNKNOWN_PAYLOAD_MAX_CHARS, } from "./events.js";
17
+ export function call(terms) {
17
18
  return createProjection(terms);
18
19
  }
19
- export function resume(terms) {
20
+ export function revive(terms) {
20
21
  return createProjection(terms, terms);
21
22
  }
22
23
  function mintOutcome(draft) {
23
24
  return draft;
24
25
  }
25
- function effectivePolicyFor(shared) {
26
- return shared.terms.provider === "codex-sdk" ? shared.terms.effectivePolicy : undefined;
26
+ export function restoreOutcome(record) {
27
+ return { ...record };
27
28
  }
28
- function createProjection(terms, resumeTerms) {
29
+ function createProjection(terms, reviveTerms) {
29
30
  switch (terms.provider) {
30
31
  case "pi":
31
32
  case "claude-agent-sdk":
32
33
  case "codex-app-server":
33
- return createSteerableProjection(terms, resumeTerms);
34
+ return createSteerableProjection(terms, reviveTerms);
34
35
  case "codex-sdk":
35
36
  case "opencode-sdk":
36
- return createReplayProjection(terms, resumeTerms);
37
+ return createReplayProjection(terms, reviveTerms);
37
38
  default:
38
39
  return assertNever(terms);
39
40
  }
40
41
  }
41
- function createSteerableProjection(terms, resumeTerms) {
42
- const shared = createProjectionState(terms, resumeTerms);
42
+ function createSteerableProjection(terms, reviveTerms) {
43
+ const shared = createProjectionState(terms, reviveTerms);
43
44
  const pendingTells = [];
44
45
  let flushActive = false;
45
46
  const flushPendingTells = async () => {
@@ -69,7 +70,7 @@ function createSteerableProjection(terms, resumeTerms) {
69
70
  shared.live = live;
70
71
  void flushPendingTells();
71
72
  };
72
- const outcome = settleProjection(shared).then((value) => finalizeProjectionLedger(shared, value));
73
+ const outcome = settleProjection(shared);
73
74
  return {
74
75
  mode: "steerable",
75
76
  get id() {
@@ -95,12 +96,12 @@ function createSteerableProjection(terms, resumeTerms) {
95
96
  },
96
97
  };
97
98
  }
98
- function createReplayProjection(terms, resumeTerms) {
99
- const shared = createProjectionState(terms, resumeTerms);
99
+ function createReplayProjection(terms, reviveTerms) {
100
+ const shared = createProjectionState(terms, reviveTerms);
100
101
  shared.onLive = (live) => {
101
102
  shared.live = live;
102
103
  };
103
- const outcome = settleProjection(shared).then((value) => finalizeProjectionLedger(shared, value));
104
+ const outcome = settleProjection(shared);
104
105
  return {
105
106
  mode: "replay",
106
107
  get id() {
@@ -117,12 +118,14 @@ function createReplayProjection(terms, resumeTerms) {
117
118
  },
118
119
  };
119
120
  }
120
- function createProjectionState(terms, resumeTerms) {
121
+ function createProjectionState(terms, reviveTerms) {
121
122
  const shared = {
122
123
  id: randomUUID(),
123
124
  terms,
124
- resumeTerms,
125
- publicEvents: createEventChannel(),
125
+ reviveTerms,
126
+ publicEvents: createEventChannel({
127
+ createOverflowMarker: overflowMarkerFromDropped,
128
+ }),
126
129
  lifecycleAbort: new AbortController(),
127
130
  dismissed: false,
128
131
  externallyAborted: false,
@@ -143,37 +146,6 @@ function createProjectionState(terms, resumeTerms) {
143
146
  }
144
147
  return shared;
145
148
  }
146
- function finalizeProjectionLedger(shared, outcome) {
147
- const directory = shared.terms.projectionDir;
148
- const executionId = shared.terms.projectionExecutionId;
149
- if (!directory || !executionId)
150
- return outcome;
151
- const now = Date.now();
152
- publishGenerationResult(directory, {
153
- version: 1,
154
- executionId,
155
- status: outcome.status,
156
- createdAt: new Date(now).toISOString(),
157
- session: outcome.session,
158
- responsePath: generationResultPath(directory, executionId),
159
- detail: outcome.status === "failed" ? outcome.error?.message : undefined,
160
- });
161
- // A stale/duplicate child must never replace a later generation's verdict.
162
- if (readAdmissionLock(directory)?.executionId !== executionId)
163
- return outcome;
164
- const pending = ["inbox", "inflight", "submitted"].some((layer) => listTellIds(directory, layer).length > 0);
165
- if (pending && outcome.status === "completed") {
166
- // The resume lock is acquired by the one wake orchestrator before it releases
167
- // this generation's admission; a crash leaves result-without-done for self-heal.
168
- wakeProjection(directory, "sweep-wake", { handoffExecutionId: executionId });
169
- return outcome;
170
- }
171
- publishVerdict(directory, outcome.status === "completed" ? "done" : outcome.status === "dismissed" ? "dismissed" : "failed", now, outcome.status === "failed" ? outcome.error?.message : undefined);
172
- releaseAdmissionLockOwned(directory, executionId);
173
- if (listTellIds(directory, "inbox").length > 0)
174
- wakeProjection(directory, "sweep-wake");
175
- return outcome;
176
- }
177
149
  function dismissProjection(shared) {
178
150
  if (shared.dismissed || shared.terminal)
179
151
  return;
@@ -196,14 +168,8 @@ async function settleProjection(shared) {
196
168
  diagnosticsBuffer: new StringTailBuffer(shared.terms.maxCaptureChars),
197
169
  warnings: [],
198
170
  };
199
- let timedOut = false;
200
171
  let idleTimedOut = false;
201
172
  let abortRequested = false;
202
- const timeout = setTimeout(() => {
203
- timedOut = true;
204
- shared.lifecycleAbort.abort(createAbortError(`${shared.terms.provider} exec timed out`));
205
- }, shared.terms.timeoutMs);
206
- timeout.unref?.();
207
173
  let idleTimeout;
208
174
  const refreshIdleTimeout = () => {
209
175
  if (idleTimeout)
@@ -224,16 +190,12 @@ async function settleProjection(shared) {
224
190
  shared.lifecycleAbort.signal.addEventListener("abort", requestLiveAbort, { once: true });
225
191
  try {
226
192
  if (shared.terms.projectionDir) {
227
- const executionId = shared.terms.projectionExecutionId ?? createProjectionExecutionId();
228
- const acquired = acquireAdmissionLock(shared.terms.projectionDir, process.pid, Date.now(), executionId);
229
- if (acquired === "taken")
230
- throw new FlowError("INTERNAL_STATE", "projection already has an active generation");
231
- touchHeartbeat(shared.terms.projectionDir, Date.now());
193
+ const executionId = shared.terms.projectionExecutionId;
194
+ if (!executionId)
195
+ throw new FlowError("INTERNAL_STATE", "projection execution id is required in supervisor mode");
232
196
  demoteSubmittedTellsForReplay(shared.terms.projectionDir, executionId);
233
197
  }
234
- const launchTellFence = shared.terms.projectionDir && shared.terms.projectionExecutionId
235
- ? readGenerationLaunch(shared.terms.projectionDir, shared.terms.projectionExecutionId).inputs.tellFence
236
- : undefined;
198
+ const launchTellFence = shared.terms.projectionTellFence;
237
199
  const tellFence = launchTellFence === undefined
238
200
  ? []
239
201
  : Array.isArray(launchTellFence) && launchTellFence.every((tellId) => typeof tellId === "string")
@@ -244,35 +206,41 @@ async function settleProjection(shared) {
244
206
  : undefined;
245
207
  const launchTerms = launchSnapshot
246
208
  ? shared.terms.provider === "codex-sdk" && launchSnapshot.effort
247
- ? { ...shared.terms, prompt: launchSnapshot.prompt, threadOptions: { ...shared.terms.threadOptions, modelReasoningEffort: launchSnapshot.effort === "max" ? "xhigh" : launchSnapshot.effort } }
209
+ ? { ...shared.terms, prompt: launchSnapshot.prompt, threadOptions: { ...shared.terms.threadOptions, modelReasoningEffort: launchSnapshot.effort } }
248
210
  : { ...shared.terms, prompt: launchSnapshot.prompt }
249
211
  : shared.terms;
250
- const live = await startLiveProjection(launchTerms, shared.lifecycleAbort.signal, shared.resumeTerms, launchSnapshot?.tellIds);
251
212
  // A constructed driver is not an SDK run acknowledgement. Tier-2 has no
252
213
  // live steer surface, so the first provider event is the earliest harness
253
214
  // observable start; a successful no-event completion is the only fallback.
254
215
  const replayStartGate = launchSnapshot && shared.terms.provider !== "codex-app-server" && shared.terms.projectionDir && shared.terms.projectionExecutionId
255
216
  ? createReplayRunStartGate(shared.terms.projectionDir, shared.terms.projectionExecutionId, launchSnapshot.tellIds)
256
217
  : undefined;
218
+ const live = await startLiveProjection(launchTerms, shared.lifecycleAbort.signal, shared.reviveTerms, launchSnapshot?.tellIds, refreshIdleTimeout);
257
219
  shared.onLive(live);
258
220
  if (shared.lifecycleAbort.signal.aborted) {
259
221
  requestLiveAbort();
260
222
  }
261
223
  const projectionDir = shared.terms.projectionDir;
262
224
  if (projectionDir) {
263
- // Supervisor mode: pump with heartbeat, checkpoint, claim inbox, deliver tells.
264
- await pumpSupervisor(live, projectionDir, shared.publicEvents, (event) => {
265
- replayStartGate?.observeProviderEvent();
266
- refreshIdleTimeout();
267
- processEvent(state, event);
225
+ const executionId = shared.terms.projectionExecutionId;
226
+ if (!executionId) {
227
+ throw new Error("projection execution id is required in supervisor mode");
228
+ }
229
+ // Supervisor mode: persist events, checkpoint, and claim heart-backed tells.
230
+ await pumpSupervisor(live, projectionDir, executionId, shared.publicEvents, {
231
+ onEvent: (event) => {
232
+ replayStartGate?.observeProviderEvent();
233
+ refreshIdleTimeout();
234
+ processEvent(state, event);
235
+ },
268
236
  });
269
237
  }
270
238
  else {
271
239
  // Legacy mode: direct event loop without supervisor pump.
272
240
  for await (const event of live.events) {
273
241
  replayStartGate?.observeProviderEvent();
274
- shared.publicEvents.push(event);
275
242
  refreshIdleTimeout();
243
+ shared.publicEvents.push(event);
276
244
  processEvent(state, event);
277
245
  }
278
246
  }
@@ -282,16 +250,16 @@ async function settleProjection(shared) {
282
250
  return failFromFailEvent(shared, state, state.failEvent);
283
251
  }
284
252
  if (!completionResult.ok) {
285
- return classifyFailure(shared, state, completionResult.error, timedOut, idleTimedOut);
253
+ return classifyFailure(shared, state, completionResult.error, idleTimedOut);
286
254
  }
287
255
  if (shared.lifecycleAbort.signal.aborted) {
288
- return classifyFailure(shared, state, shared.lifecycleAbort.signal.reason, timedOut, idleTimedOut);
256
+ return classifyFailure(shared, state, shared.lifecycleAbort.signal.reason, idleTimedOut);
289
257
  }
290
258
  if (!state.doneEvent && !completionOutcome) {
291
259
  return failWithError(shared, state, {
292
260
  code: "SUBAGENT_EXEC_ERROR",
293
261
  message: `${shared.terms.provider} exec ended before completion`,
294
- timeoutMs: shared.terms.timeoutMs,
262
+ timeoutMs: null,
295
263
  exitCode: null,
296
264
  }, state.finalMessageCandidate);
297
265
  }
@@ -304,7 +272,7 @@ async function settleProjection(shared) {
304
272
  return failWithError(shared, state, {
305
273
  code: "SUBAGENT_EXEC_ERROR",
306
274
  message: `${shared.terms.provider} exec ended without a session handle`,
307
- timeoutMs: shared.terms.timeoutMs,
275
+ timeoutMs: null,
308
276
  exitCode: null,
309
277
  }, finalMessage);
310
278
  }
@@ -321,17 +289,15 @@ async function settleProjection(shared) {
321
289
  diagnostics: renderCapturedText(state.diagnosticsBuffer),
322
290
  session,
323
291
  warnings: [...state.warnings],
324
- ...(effectivePolicyFor(shared) ? { effectivePolicy: effectivePolicyFor(shared) } : {}),
325
292
  });
326
293
  }
327
294
  catch (error) {
328
295
  if (error instanceof FlowError) {
329
296
  throw error;
330
297
  }
331
- return classifyFailure(shared, state, error, timedOut, idleTimedOut);
298
+ return classifyFailure(shared, state, error, idleTimedOut);
332
299
  }
333
300
  finally {
334
- clearTimeout(timeout);
335
301
  if (idleTimeout)
336
302
  clearTimeout(idleTimeout);
337
303
  shared.lifecycleAbort.signal.removeEventListener("abort", requestLiveAbort);
@@ -341,9 +307,6 @@ async function settleProjection(shared) {
341
307
  }
342
308
  function processEvent(state, event) {
343
309
  switch (event.type) {
344
- case "output":
345
- state.outputBuffer.append(`${JSON.stringify(event.value)}\n`);
346
- return;
347
310
  case "diagnostic":
348
311
  state.diagnosticsBuffer.append(event.text);
349
312
  return;
@@ -366,6 +329,14 @@ function processEvent(state, event) {
366
329
  state.diagnosticsBuffer.append(event.diagnostics);
367
330
  }
368
331
  return;
332
+ case "activity":
333
+ case "unknown":
334
+ case "turn":
335
+ case "usage":
336
+ case "thought":
337
+ case "plan":
338
+ case "truncation":
339
+ return;
369
340
  }
370
341
  }
371
342
  function failFromFailEvent(shared, state, failEvent) {
@@ -378,11 +349,11 @@ function failFromFailEvent(shared, state, failEvent) {
378
349
  return failWithError(shared, state, {
379
350
  code,
380
351
  message: failEvent.message,
381
- timeoutMs: shared.terms.timeoutMs,
352
+ timeoutMs: null,
382
353
  exitCode: failEvent.exitCode ?? null,
383
354
  }, state.finalMessageCandidate, failEvent.diagnostics ?? diagnostics);
384
355
  }
385
- function classifyFailure(shared, state, error, timedOut, idleTimedOut) {
356
+ function classifyFailure(shared, state, error, idleTimedOut) {
386
357
  if (shared.dismissed || shared.externallyAborted) {
387
358
  return mintOutcome({
388
359
  status: "dismissed",
@@ -391,17 +362,8 @@ function classifyFailure(shared, state, error, timedOut, idleTimedOut) {
391
362
  diagnostics: renderCapturedText(state.diagnosticsBuffer),
392
363
  session: state.session ?? fallbackSession(shared),
393
364
  warnings: [...state.warnings],
394
- ...(effectivePolicyFor(shared) ? { effectivePolicy: effectivePolicyFor(shared) } : {}),
395
365
  });
396
366
  }
397
- if (timedOut) {
398
- return failWithError(shared, state, {
399
- code: "SUBAGENT_TIMEOUT",
400
- message: `${shared.terms.provider} exec timed out`,
401
- timeoutMs: shared.terms.timeoutMs,
402
- exitCode: null,
403
- }, state.finalMessageCandidate);
404
- }
405
367
  if (idleTimedOut) {
406
368
  return failWithError(shared, state, {
407
369
  code: "SUBAGENT_TIMEOUT",
@@ -436,11 +398,10 @@ function failWithError(shared, state, error, finalMessage, diagnostics = renderC
436
398
  diagnostics,
437
399
  session: state.session ?? fallbackSession(shared),
438
400
  warnings: [...state.warnings],
439
- ...(effectivePolicyFor(shared) ? { effectivePolicy: effectivePolicyFor(shared) } : {}),
440
401
  });
441
402
  }
442
403
  function fallbackSession(shared) {
443
- return shared.resumeTerms?.session ?? {
404
+ return shared.reviveTerms?.session ?? {
444
405
  provider: shared.terms.provider,
445
406
  version: SUBAGENT_SESSION_VERSION,
446
407
  handle: null,
@@ -472,8 +433,10 @@ function admitResumeShape(provider, schema, session, resumePath) {
472
433
  }
473
434
  return shapeResult.data;
474
435
  }
475
- async function startLiveProjection(terms, signal, resumeTerms, initialTellIds = []) {
476
- const projectionCheckpoint = terms.projectionDir ? buildSyncCheckpointCallback(terms.projectionDir) : undefined;
436
+ async function startLiveProjection(terms, signal, reviveTerms, initialTellIds = [], onIngress) {
437
+ const projectionCheckpoint = terms.projectionDir && terms.projectionExecutionId
438
+ ? buildSyncCheckpointCallback(terms.projectionDir, terms.projectionExecutionId)
439
+ : undefined;
477
440
  let appServerInitialFencePending = terms.provider === "codex-app-server" && initialTellIds.length > 0;
478
441
  const onSyncCheckpoint = terms.projectionDir
479
442
  ? (control, checkpoint) => {
@@ -493,8 +456,8 @@ async function startLiveProjection(terms, signal, resumeTerms, initialTellIds =
493
456
  : undefined;
494
457
  switch (terms.provider) {
495
458
  case "pi": {
496
- if (resumeTerms) {
497
- const session = admitResumeShape("pi", piSessionSchema, resumeTerms.session, resumeTerms.resumePath);
459
+ if (reviveTerms) {
460
+ const session = admitResumeShape("pi", piSessionSchema, reviveTerms.session, reviveTerms.resumePath);
498
461
  return startPi(terms.prompt, terms.workdir, {
499
462
  model: terms.model,
500
463
  thinkingLevel: terms.thinkingLevel,
@@ -503,6 +466,8 @@ async function startLiveProjection(terms, signal, resumeTerms, initialTellIds =
503
466
  appendSystemPromptOverride: terms.appendSystemPromptOverride,
504
467
  onSyncCheckpoint,
505
468
  onEvidence: terms.onEvidence,
469
+ onIngress,
470
+ initialTurn: terms.initialTurn,
506
471
  });
507
472
  }
508
473
  return startPi(terms.prompt, terms.workdir, {
@@ -512,44 +477,45 @@ async function startLiveProjection(terms, signal, resumeTerms, initialTellIds =
512
477
  appendSystemPromptOverride: terms.appendSystemPromptOverride,
513
478
  onSyncCheckpoint,
514
479
  onEvidence: terms.onEvidence,
480
+ onIngress,
481
+ initialTurn: terms.initialTurn,
515
482
  });
516
483
  }
517
484
  case "claude-agent-sdk": {
518
- if (resumeTerms) {
519
- const session = admitResumeShape("claude-agent-sdk", claudeSessionSchema, resumeTerms.session, resumeTerms.resumePath);
520
- return startClaudeAgentSdk(terms.prompt, terms.workdir, {
521
- model: terms.model,
522
- executable: terms.executable,
523
- effort: terms.effort,
524
- signal,
525
- permissionMode: terms.permissionMode,
526
- settingSources: terms.settingSources,
527
- systemPrompt: terms.systemPrompt,
528
- sessionId: session.sessionId,
529
- onSyncCheckpoint,
530
- onEvidence: terms.onEvidence,
531
- });
532
- }
533
- return startClaudeAgentSdk(terms.prompt, terms.workdir, {
485
+ const claudeOptions = {
534
486
  model: terms.model,
535
487
  executable: terms.executable,
536
488
  effort: terms.effort,
537
489
  signal,
538
490
  permissionMode: terms.permissionMode,
539
491
  settingSources: terms.settingSources,
492
+ allowDangerouslySkipPermissions: terms.allowDangerouslySkipPermissions,
493
+ disallowedTools: terms.disallowedTools,
540
494
  systemPrompt: terms.systemPrompt,
541
495
  onSyncCheckpoint,
542
496
  onEvidence: terms.onEvidence,
543
- });
497
+ onIngress,
498
+ initialTurn: terms.initialTurn,
499
+ };
500
+ if (reviveTerms) {
501
+ const session = admitResumeShape("claude-agent-sdk", claudeSessionSchema, reviveTerms.session, reviveTerms.resumePath);
502
+ return startClaudeAgentSdk(terms.prompt, terms.workdir, {
503
+ ...claudeOptions,
504
+ sessionId: session.sessionId,
505
+ });
506
+ }
507
+ return startClaudeAgentSdk(terms.prompt, terms.workdir, claudeOptions);
544
508
  }
545
509
  case "codex-app-server": {
546
- if (resumeTerms) {
547
- const session = admitResumeShape("codex-app-server", codexAppServerSessionSchema, resumeTerms.session, resumeTerms.resumePath);
510
+ if (reviveTerms) {
511
+ const session = admitResumeShape("codex-app-server", codexAppServerSessionSchema, reviveTerms.session, reviveTerms.resumePath);
548
512
  return startCodexAppServer(terms.prompt, terms.workdir, {
549
513
  model: terms.model,
550
514
  effort: terms.effort,
551
515
  profile: terms.profile,
552
516
  config: terms.config,
517
+ sandboxPolicy: terms.sandboxPolicy,
518
+ approvalPolicy: terms.approvalPolicy,
553
519
  executable: terms.executable,
554
520
  signal,
555
521
  threadId: session.threadId,
@@ -557,6 +523,8 @@ async function startLiveProjection(terms, signal, resumeTerms, initialTellIds =
557
523
  developerInstructions: terms.developerInstructions,
558
524
  onSyncCheckpoint,
559
525
  onEvidence: terms.onEvidence,
526
+ onIngress,
527
+ initialTurn: terms.initialTurn,
560
528
  });
561
529
  }
562
530
  return startCodexAppServer(terms.prompt, terms.workdir, {
@@ -564,16 +532,20 @@ async function startLiveProjection(terms, signal, resumeTerms, initialTellIds =
564
532
  effort: terms.effort,
565
533
  profile: terms.profile,
566
534
  config: terms.config,
535
+ sandboxPolicy: terms.sandboxPolicy,
536
+ approvalPolicy: terms.approvalPolicy,
567
537
  executable: terms.executable,
568
538
  signal,
569
539
  developerInstructions: terms.developerInstructions,
570
540
  onSyncCheckpoint,
571
541
  onEvidence: terms.onEvidence,
542
+ onIngress,
543
+ initialTurn: terms.initialTurn,
572
544
  });
573
545
  }
574
546
  case "codex-sdk": {
575
- if (resumeTerms) {
576
- const session = admitResumeShape("codex-sdk", codexThreadSchema, resumeTerms.session, resumeTerms.resumePath);
547
+ if (reviveTerms) {
548
+ const session = admitResumeShape("codex-sdk", codexThreadSchema, reviveTerms.session, reviveTerms.resumePath);
577
549
  return startCodexSdk(terms.prompt, terms.workdir, {
578
550
  model: terms.model,
579
551
  profile: terms.profile,
@@ -583,6 +555,8 @@ async function startLiveProjection(terms, signal, resumeTerms, initialTellIds =
583
555
  signal,
584
556
  threadId: session.threadId,
585
557
  onEvidence: terms.onEvidence,
558
+ onIngress,
559
+ initialTurn: terms.initialTurn,
586
560
  });
587
561
  }
588
562
  return startCodexSdk(terms.prompt, terms.workdir, {
@@ -593,24 +567,32 @@ async function startLiveProjection(terms, signal, resumeTerms, initialTellIds =
593
567
  executable: terms.executable,
594
568
  signal,
595
569
  onEvidence: terms.onEvidence,
570
+ onIngress,
571
+ initialTurn: terms.initialTurn,
596
572
  });
597
573
  }
598
574
  case "opencode-sdk": {
599
- if (resumeTerms) {
600
- const session = admitResumeShape("opencode-sdk", opencodeSessionSchema, resumeTerms.session, resumeTerms.resumePath);
575
+ if (reviveTerms) {
576
+ const session = admitResumeShape("opencode-sdk", opencodeSessionSchema, reviveTerms.session, reviveTerms.resumePath);
601
577
  return startOpencode(terms.prompt, terms.workdir, {
602
578
  model: terms.model,
579
+ effort: terms.effort,
603
580
  executable: terms.executable,
604
581
  signal,
605
582
  sessionId: session.sessionId,
606
583
  onEvidence: terms.onEvidence,
584
+ onIngress,
585
+ initialTurn: terms.initialTurn,
607
586
  });
608
587
  }
609
588
  return startOpencode(terms.prompt, terms.workdir, {
610
589
  model: terms.model,
590
+ effort: terms.effort,
611
591
  executable: terms.executable,
612
592
  signal,
613
593
  onEvidence: terms.onEvidence,
594
+ onIngress,
595
+ initialTurn: terms.initialTurn,
614
596
  });
615
597
  }
616
598
  default:
@@ -1,6 +1,5 @@
1
- import * as fs from "node:fs";
2
- import * as path from "node:path";
3
- import { claimAllInbox, claimFencedTells, listTellIds, markTellSubmitted, markTellConsumed, readTellSubmitted, readTellOriginal, readAdmissionLock, isTellCausallyConsumed, touchHeartbeat as touchHeartbeatFile, } from "../../core/projection-core.js";
1
+ import { claimAllInbox, claimFencedTells, listTellIds, markTellSubmitted, markTellConsumed, readTellSubmitted, readTellOriginal, isTellCausallyConsumed, appendGenerationEvent, } from "../../core/projection-core.js";
2
+ import { prepareAgentEventForPersistence, } from "./execution-handle.js";
4
3
  /** Tier-2 boundary snapshot. No tells leaves prompt byte-identical. */
5
4
  export function snapshotReplayPrompt(projectionDirectory, prompt, tellFence, executionId) {
6
5
  const tellIds = claimFencedTells(projectionDirectory, tellFence, executionId);
@@ -43,79 +42,73 @@ export function createReplayRunStartGate(projectionDirectory, executionId, tellI
43
42
  };
44
43
  return { observeProviderEvent: start, observeSuccessfulCompletion: start };
45
44
  }
46
- /** Heartbeat throttle: no more than one utimes call per 5000ms. */
47
- export const HEARTBEAT_MIN_INTERVAL_MS = 5_000;
48
45
  /**
49
46
  * Supervisor pump: iterates handle.events, forwards to publicChannel,
50
- * touches heartbeat (throttled), checks inbox at checkpoints,
51
- * and on final sweep publishes verdict.
47
+ * persists public events and checks the tell ledger at checkpoints.
52
48
  */
53
- export async function pumpSupervisor(handle, projectionDirectory, publicChannel, onEvent) {
54
- let lastHeartbeatMs = 0;
55
- let heartbeatDegraded = false;
56
- const heartbeat = () => {
57
- const now = Date.now();
58
- if (now - lastHeartbeatMs < HEARTBEAT_MIN_INTERVAL_MS)
59
- return;
60
- lastHeartbeatMs = now;
61
- try {
62
- touchHeartbeatFile(projectionDirectory, now);
63
- heartbeatDegraded = false;
64
- }
65
- catch (error) {
66
- if (!heartbeatDegraded) {
67
- publicChannel.push({
68
- type: "diagnostic",
69
- text: `heartbeat degraded: ${String(error)}`,
70
- });
49
+ export async function pumpSupervisor(handle, projectionDirectory, executionId, publicChannel, callbacks = {}, dependencies = {}) {
50
+ let persistenceEnabled = true;
51
+ let persistenceDiagnosticQueued = false;
52
+ const pendingDiagnostics = [];
53
+ const appendEvent = dependencies.appendGenerationEvent ?? appendGenerationEvent;
54
+ const dispatch = (event, persist = true) => {
55
+ if (persist && persistenceEnabled) {
56
+ try {
57
+ appendEvent(projectionDirectory, executionId, prepareAgentEventForPersistence(event));
58
+ }
59
+ catch (error) {
60
+ persistenceEnabled = false;
61
+ if (!persistenceDiagnosticQueued) {
62
+ persistenceDiagnosticQueued = true;
63
+ pendingDiagnostics.push({
64
+ text: `event persistence degraded: ${String(error)}`,
65
+ persist: false,
66
+ });
67
+ }
71
68
  }
72
- heartbeatDegraded = true;
73
69
  }
70
+ publicChannel.push(event);
71
+ callbacks.onEvent?.(event);
74
72
  };
75
- const heartbeatTimer = setInterval(heartbeat, HEARTBEAT_MIN_INTERVAL_MS);
76
- const stopHeartbeatTimer = () => clearInterval(heartbeatTimer);
77
73
  try {
78
74
  for await (const event of handle.events) {
79
- publicChannel.push(event);
80
- onEvent?.(event);
81
- heartbeat();
82
- if (event.type === "done" || event.type === "fail")
83
- stopHeartbeatTimer();
75
+ dispatch(event);
84
76
  }
85
77
  }
86
78
  catch (error) {
87
79
  if (error instanceof Error && error.code === "PROJECTION_STATE_ERROR") {
88
- publicChannel.push({
80
+ const event = handle.mintEvent({
89
81
  type: "fail",
90
82
  code: "PROJECTION_STATE_ERROR",
91
83
  message: error.message,
92
84
  });
85
+ if (event)
86
+ dispatch(event);
93
87
  }
94
88
  else {
95
- publicChannel.push({
89
+ const event = handle.mintEvent({
96
90
  type: "diagnostic",
97
91
  text: `pump error: ${String(error)}`,
98
92
  });
93
+ if (event)
94
+ dispatch(event);
99
95
  }
100
96
  }
101
- finally {
102
- stopHeartbeatTimer();
97
+ for (const pending of pendingDiagnostics) {
98
+ const event = handle.mintEvent({ type: "diagnostic", text: pending.text });
99
+ if (event)
100
+ dispatch(event, pending.persist);
103
101
  }
104
102
  }
105
- function readTellOriginalText(projectionDirPath, tellId, layer = "inflight") {
106
- const content = JSON.parse(fs.readFileSync(path.join(projectionDirPath, layer, tellId), "utf8"));
107
- return content.text;
108
- }
109
103
  /**
110
104
  * Build an onSyncCheckpoint callback that claims all inbox tells
111
105
  * and delivers them via the given SteerControl.
112
106
  * Intended for use inside adapter start() calls as the onSyncCheckpoint callback.
113
107
  */
114
- export function buildSyncCheckpointCallback(projectionDirectory) {
108
+ export function buildSyncCheckpointCallback(projectionDirectory, executionId) {
115
109
  const submitting = new Set();
116
110
  return (control, checkpoint) => {
117
111
  claimAllInbox(projectionDirectory);
118
- const executionId = readAdmissionLock(projectionDirectory)?.executionId;
119
112
  for (const tellId of listTellIds(projectionDirectory, "inflight")) {
120
113
  if (submitting.has(tellId))
121
114
  continue;
@@ -129,8 +122,6 @@ export function buildSyncCheckpointCallback(projectionDirectory) {
129
122
  };
130
123
  void control.enqueue(tell)
131
124
  .then((ack) => {
132
- if (!executionId)
133
- throw new Error("projection checkpoint has no execution admission");
134
125
  if (!ack)
135
126
  throw new Error("provider enqueue resolved without TellSubmitAck");
136
127
  markTellSubmitted(projectionDirectory, tellId, { executionId, fence: ack.fence });
@@ -140,8 +131,6 @@ export function buildSyncCheckpointCallback(projectionDirectory) {
140
131
  })
141
132
  .finally(() => submitting.delete(tellId));
142
133
  }
143
- if (!executionId)
144
- return;
145
134
  for (const tellId of listTellIds(projectionDirectory, "submitted")) {
146
135
  const submitted = readTellSubmitted(projectionDirectory, tellId);
147
136
  if (submitted.executionId === executionId && submitted.fence && isTellCausallyConsumed(submitted.fence, checkpoint.fence)) {
@@ -38,6 +38,9 @@ export function createAbortError(message) {
38
38
  error.name = "AbortError";
39
39
  return error;
40
40
  }
41
+ export function coerceString(value) {
42
+ return typeof value === "string" && value.trim().length > 0 ? value : undefined;
43
+ }
41
44
  export function renderTruncationSuffix(truncatedChars) {
42
45
  if (truncatedChars <= 0)
43
46
  return "";