@astrosheep/keiyaku 2.9.12 → 2.9.13

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 (212) hide show
  1. package/README.md +22 -18
  2. package/build/.tsbuildinfo +1 -1
  3. package/build/agents/call-terms.js +1 -0
  4. package/build/agents/providers/claude-agent-sdk/adapter.js +2 -2
  5. package/build/cli/commands/akuma/akuma/handler.js +2 -1
  6. package/build/cli/commands/akuma/akuma/meta.js +4 -3
  7. package/build/cli/commands/akuma/catalog.js +2 -0
  8. package/build/cli/commands/akuma/list/handler.js +1 -1
  9. package/build/cli/commands/akuma/list/meta-list.js +12 -0
  10. package/build/cli/commands/akuma/list/meta-ls.js +2 -2
  11. package/build/cli/commands/akuma.js +5 -0
  12. package/build/cli/commands/contract/amend/meta.js +4 -3
  13. package/build/cli/commands/contract/arc/meta.js +2 -0
  14. package/build/cli/commands/contract/audit/handler.js +28 -2
  15. package/build/cli/commands/contract/audit/meta.js +4 -3
  16. package/build/cli/commands/contract/petition/handler.js +2 -1
  17. package/build/cli/commands/contract/petition/meta.js +2 -2
  18. package/build/cli/commands/contract/renew/handler.js +44 -3
  19. package/build/cli/commands/contract/renew/meta.js +5 -4
  20. package/build/cli/commands/metadata.js +12 -9
  21. package/build/cli/commands/projection/call/handler.js +1 -1
  22. package/build/cli/commands/projection/call/meta.js +1 -1
  23. package/build/cli/commands/projection/catalog.js +2 -2
  24. package/build/cli/commands/projection/{revive → fork}/handler.js +10 -5
  25. package/build/cli/commands/projection/fork/meta.js +12 -0
  26. package/build/cli/commands/projection/history/handler.js +46 -2
  27. package/build/cli/commands/projection/history/meta.js +3 -3
  28. package/build/cli/commands/projection/kill/meta.js +2 -2
  29. package/build/cli/commands/projection/status/handler.js +29 -13
  30. package/build/cli/commands/projection/status/meta.js +14 -3
  31. package/build/cli/commands/projection/tell/handler.js +8 -4
  32. package/build/cli/commands/projection/tell/meta.js +1 -1
  33. package/build/cli/commands/projection/wait/handler.js +45 -20
  34. package/build/cli/commands/projection/wait/meta.js +1 -1
  35. package/build/cli/commands/shared.js +1 -1
  36. package/build/cli/commands/system/catalog.js +2 -0
  37. package/build/cli/commands/task/add/handler.js +28 -12
  38. package/build/cli/commands/task/add/meta.js +18 -6
  39. package/build/cli/commands/task/catalog.js +2 -0
  40. package/build/cli/commands/task/compose/handler.js +2 -2
  41. package/build/cli/commands/task/compose/meta.js +2 -2
  42. package/build/cli/commands/task/doctor/handler.js +2 -3
  43. package/build/cli/commands/task/doctor/meta.js +2 -2
  44. package/build/cli/commands/task/done/meta.js +2 -2
  45. package/build/cli/commands/task/drop/handler.js +7 -2
  46. package/build/cli/commands/task/drop/meta.js +6 -3
  47. package/build/cli/commands/task/hold/meta.js +2 -2
  48. package/build/cli/commands/task/log/meta.js +2 -2
  49. package/build/cli/commands/task/ls/meta.js +2 -2
  50. package/build/cli/commands/task/note/handler.js +6 -0
  51. package/build/cli/commands/task/note/meta.js +11 -0
  52. package/build/cli/commands/task/resume/meta.js +2 -2
  53. package/build/cli/commands/task/shared.js +29 -15
  54. package/build/cli/commands/task/show/meta.js +2 -2
  55. package/build/cli/commands/task/start/meta.js +2 -2
  56. package/build/cli/commands/task/stop/meta.js +2 -2
  57. package/build/cli/commands/task/update/meta.js +3 -2
  58. package/build/cli/commands/verification/handler.js +43 -0
  59. package/build/cli/commands/verification/meta.js +13 -0
  60. package/build/cli/completion.js +16 -13
  61. package/build/cli/flags.js +43 -11
  62. package/build/cli/help.js +29 -30
  63. package/build/cli/index.js +6 -5
  64. package/build/cli/parse-flags.js +39 -0
  65. package/build/cli/parse-metadata.js +5 -2
  66. package/build/cli/parse-selectors.js +18 -0
  67. package/build/cli/parse.js +21 -8
  68. package/build/cli/render/arc.js +12 -3
  69. package/build/cli/render/audit.js +2 -2
  70. package/build/cli/render/call.js +4 -4
  71. package/build/cli/render/kanshi.js +122 -85
  72. package/build/cli/render/misc.js +2 -2
  73. package/build/cli/render/petition.js +16 -1
  74. package/build/cli/render/projection-history.js +17 -4
  75. package/build/cli/render/shared.js +5 -5
  76. package/build/cli/render/status-indicator.js +47 -0
  77. package/build/cli/render/status.js +106 -76
  78. package/build/cli/render/success-response.js +20 -5
  79. package/build/cli/render/task.js +6 -5
  80. package/build/cli/render/tell.js +10 -4
  81. package/build/cli/render/tool-command-normalization.js +137 -0
  82. package/build/cli/render/tool-presentation.js +2 -1
  83. package/build/cli/render/verification.js +23 -0
  84. package/build/cli/render/wait.js +101 -33
  85. package/build/cli/types.js +2 -1
  86. package/build/config/akuma-loader.js +21 -1
  87. package/build/config/settings/knobs.js +7 -1
  88. package/build/config/settings/schema.js +10 -5
  89. package/build/core/amend.js +102 -17
  90. package/build/core/arc.js +4 -4
  91. package/build/core/audit/candidate.js +10 -18
  92. package/build/core/audit/coordinates.js +12 -17
  93. package/build/core/audit/evidence.js +6 -4
  94. package/build/core/audit/facade.js +3 -3
  95. package/build/core/audit/report.js +1 -1
  96. package/build/core/bind-reconciliation.js +42 -23
  97. package/build/core/bind.js +182 -81
  98. package/build/core/call/call.js +6 -7
  99. package/build/core/call/context.js +43 -30
  100. package/build/core/call/execution.js +21 -6
  101. package/build/core/call/prompt.js +11 -42
  102. package/build/core/contract-carrier-runtime.js +209 -105
  103. package/build/core/contract-carrier.js +156 -73
  104. package/build/core/contract-view.js +42 -5
  105. package/build/core/contract.js +2 -1
  106. package/build/core/derived-replay.js +168 -35
  107. package/build/core/draft.js +9 -5
  108. package/build/core/forfeit.js +1 -1
  109. package/build/core/lifecycle-history.js +13 -0
  110. package/build/core/lifecycle-recovery.js +32 -27
  111. package/build/core/lifecycle-runner.js +14 -14
  112. package/build/core/log.js +1 -1
  113. package/build/core/outcome-base.js +2 -2
  114. package/build/core/projection/akuma-name.js +43 -0
  115. package/build/core/projection/generation/database.js +141 -79
  116. package/build/core/projection/generation/ledger.js +91 -0
  117. package/build/core/projection/generation/model.js +18 -13
  118. package/build/core/projection/generation/projection-generation-continuation.js +1 -1
  119. package/build/core/projection/generation/projection-generation-doorbell.js +88 -0
  120. package/build/core/projection/generation/projection-generation-execution.js +24 -0
  121. package/build/core/projection/generation/projection-generation-identity.js +4 -0
  122. package/build/core/projection/generation/projection-generation-launcher.js +72 -138
  123. package/build/core/projection/generation/projection-generation-process.js +16 -15
  124. package/build/core/projection/generation/projection-generation-runner.js +125 -79
  125. package/build/core/projection/generation/projection-generation-runtime.js +10 -61
  126. package/build/core/projection/generation/protocol.js +68 -0
  127. package/build/core/projection/generation/store.js +17 -7
  128. package/build/core/projection/generation/transitions.js +14 -14
  129. package/build/core/projection/index.js +5 -5
  130. package/build/core/projection/projection-core.js +6 -2
  131. package/build/core/projection/projection-execution-observer.js +1 -1
  132. package/build/core/projection/projection-history.js +7 -7
  133. package/build/core/projection/projection-kill.js +15 -6
  134. package/build/core/projection/projection-life-observer.js +8 -8
  135. package/build/core/projection/projection-mint.js +6 -8
  136. package/build/core/projection/projection-status-observation.js +77 -8
  137. package/build/core/projection/projection-status.js +151 -4
  138. package/build/core/projection/projection-wait.js +46 -27
  139. package/build/core/projection/projection-wake.js +27 -89
  140. package/build/core/projection/tell/launch-store.js +1 -1
  141. package/build/core/projection/tell/store.js +1 -1
  142. package/build/core/renew-build.js +204 -74
  143. package/build/core/renew-plan.js +34 -9
  144. package/build/core/renew-rewrite.js +56 -15
  145. package/build/core/renew.js +39 -35
  146. package/build/core/repository-ledger/accepted-fold-read.js +6 -3
  147. package/build/core/repository-ledger/codec.js +11 -5
  148. package/build/core/repository-ledger/current-state-store.js +13 -16
  149. package/build/core/repository-ledger/fold-repository.js +5 -6
  150. package/build/core/repository-ledger/identity.js +60 -0
  151. package/build/core/repository-ledger/inventory.js +3 -3
  152. package/build/core/repository-ledger/read-model.js +54 -6
  153. package/build/core/repository-ledger/write-transaction.js +9 -4
  154. package/build/core/run-control/connection-bound-close.js +41 -0
  155. package/build/core/run-control/detached-lease-bootstrap.js +208 -0
  156. package/build/core/run-control/detached-process.js +14 -0
  157. package/build/core/run-control/index.js +6 -0
  158. package/build/core/{process-group.js → run-control/process-tree.js} +1 -1
  159. package/build/core/run-control/runner-lease.js +172 -0
  160. package/build/core/{sqlite-process-lifetime-lock.js → run-control/sqlite-process-lifetime-lock.js} +4 -24
  161. package/build/core/seal.js +44 -32
  162. package/build/core/settlement/claim-delivery.js +22 -9
  163. package/build/core/settlement/claim.js +8 -6
  164. package/build/core/settlement/petition-claim-gates.js +9 -8
  165. package/build/core/settlement/petition-head-guard.js +5 -6
  166. package/build/core/settlement/petition-preview.js +21 -5
  167. package/build/core/settlement/petition.js +51 -17
  168. package/build/core/settlement/settlement.js +20 -15
  169. package/build/core/settlement/verdict.js +2 -2
  170. package/build/core/settlement/verification.js +492 -364
  171. package/build/core/status/board.js +184 -75
  172. package/build/core/status/drift.js +8 -10
  173. package/build/core/task/board.js +1 -0
  174. package/build/core/task/commands.js +25 -0
  175. package/build/core/task/compose.js +3 -2
  176. package/build/core/task/document.js +102 -39
  177. package/build/core/task/index.js +3 -3
  178. package/build/core/task/settlement-git.js +1 -4
  179. package/build/core/task/settlement-policy.js +27 -14
  180. package/build/core/task/source-board.js +1 -0
  181. package/build/core/task/task-bind-preparation.js +59 -9
  182. package/build/core/task/task-contract.js +2 -2
  183. package/build/core/task/task-store-repository.js +1 -0
  184. package/build/core/task/task.js +2 -2
  185. package/build/core/transcripts.js +4 -4
  186. package/build/core/verification-declaration.js +95 -0
  187. package/build/core/worktree-bootstrap.js +1 -1
  188. package/build/core/worktree-path.js +7 -11
  189. package/build/flow-error.js +9 -1
  190. package/build/generated/version.js +2 -2
  191. package/build/git/branches.js +41 -70
  192. package/build/git/commits.js +92 -21
  193. package/build/git/core.js +9 -161
  194. package/build/git/diff/preview.js +4 -4
  195. package/build/git/diff/read.js +4 -4
  196. package/build/git/diff/structured.js +5 -5
  197. package/build/git/process.js +229 -0
  198. package/build/git/refs.js +30 -5
  199. package/build/git/staging.js +39 -115
  200. package/build/git/streaming-batch.js +9 -16
  201. package/build/git/worktree.js +104 -91
  202. package/build/index.js +7 -0
  203. package/package.json +1 -1
  204. package/skills/keiyaku-akuma/SKILL.md +21 -20
  205. package/skills/keiyaku-task/SKILL.md +38 -4
  206. package/skills/keiyaku-workflow/SKILL.md +51 -11
  207. package/build/cli/commands/projection/revive/meta.js +0 -12
  208. package/build/core/projection/projection-life-protocol.js +0 -115
  209. package/build/core/projection/projection-runner-lock.js +0 -211
  210. package/build/core/projection/tell/database.js +0 -127
  211. package/build/core/settlement/verification-coordination.js +0 -305
  212. package/build/core/settlement/verification-supervisor.js +0 -275
@@ -10,7 +10,7 @@ export function launchIfSettled(database, readOnly, input) {
10
10
  const executionId = assertExecutionId(input.executionId);
11
11
  const facts = validateLaunchFacts(input.facts, "launch facts");
12
12
  return writeTransition(database, readOnly, () => {
13
- const history = readValidatedHistory(database);
13
+ const history = readValidatedHistory(database, readOnly);
14
14
  if (history.records.some((record) => record.kind === "launch" && record.executionId === executionId)) {
15
15
  return rejected("execution-id-already-used", history.current);
16
16
  }
@@ -27,7 +27,7 @@ export function failInterruptWakeIfSettled(database, readOnly, input) {
27
27
  const successorFacts = validateLaunchFacts(input.successorFacts, "interrupt wake successor launch facts");
28
28
  const terminalAt = assertIsoTimestamp(input.terminalAt, "terminalAt");
29
29
  return writeTransition(database, readOnly, () => {
30
- const history = readValidatedHistory(database);
30
+ const history = readValidatedHistory(database, readOnly);
31
31
  const current = history.current;
32
32
  if (!current)
33
33
  return rejected("no-current-launch", null);
@@ -56,7 +56,7 @@ export function adoptionIfCurrent(database, readOnly, input) {
56
56
  const executionId = assertExecutionId(input.executionId);
57
57
  const facts = validateAdoptionFacts(input.facts, "adoption facts");
58
58
  return writeTransition(database, readOnly, () => {
59
- const history = readValidatedHistory(database);
59
+ const history = readValidatedHistory(database, readOnly);
60
60
  const current = history.current;
61
61
  if (!current)
62
62
  return rejected("no-current-launch", null);
@@ -73,7 +73,7 @@ export function requestKillIfOpen(database, readOnly, input) {
73
73
  const executionId = assertExecutionId(input.executionId);
74
74
  const killedAt = assertIsoTimestamp(input.killedAt, "killedAt");
75
75
  return writeTransition(database, readOnly, () => {
76
- const history = readValidatedHistory(database);
76
+ const history = readValidatedHistory(database, readOnly);
77
77
  const current = history.current;
78
78
  if (!current)
79
79
  return rejected("no-current-launch", null);
@@ -93,7 +93,7 @@ export function requestInterruptIfOpen(database, readOnly, input) {
93
93
  const executionId = assertExecutionId(input.executionId);
94
94
  const interruptedAt = assertIsoTimestamp(input.interruptedAt, "interruptedAt");
95
95
  return writeTransition(database, readOnly, () => {
96
- const history = readValidatedHistory(database);
96
+ const history = readValidatedHistory(database, readOnly);
97
97
  const current = history.current;
98
98
  if (!current)
99
99
  return rejected("no-current-launch", null);
@@ -126,7 +126,7 @@ export function runnerKilledIfRequested(database, readOnly, input) {
126
126
  throw new ProjectionGenerationStoreError("killed verdict facts.terminalAt is reserved");
127
127
  }
128
128
  return writeTransition(database, readOnly, () => {
129
- const history = readValidatedHistory(database);
129
+ const history = readValidatedHistory(database, readOnly);
130
130
  const current = history.current;
131
131
  if (!current)
132
132
  return rejected("no-current-launch", null);
@@ -158,7 +158,7 @@ export function runnerInterruptedIfRequested(database, readOnly, input) {
158
158
  ? undefined
159
159
  : assertIsoTimestamp(input.terminalAt, "terminalAt");
160
160
  return writeTransition(database, readOnly, () => {
161
- const history = readValidatedHistory(database);
161
+ const history = readValidatedHistory(database, readOnly);
162
162
  const current = history.current;
163
163
  if (!current)
164
164
  return rejected("no-current-launch", null);
@@ -189,7 +189,7 @@ export function runnerVerdictIfOpen(database, readOnly, input) {
189
189
  const callerFacts = cloneFacts(input.facts ?? {}, "runner verdict facts");
190
190
  const terminalAt = assertIsoTimestamp(input.terminalAt, "terminalAt");
191
191
  return writeTransition(database, readOnly, () => {
192
- const history = readValidatedHistory(database);
192
+ const history = readValidatedHistory(database, readOnly);
193
193
  const current = history.current;
194
194
  if (!current)
195
195
  return rejected("no-current-launch", null);
@@ -245,7 +245,7 @@ export function settleKillIfRunnerDead(database, readOnly, input) {
245
245
  throw new ProjectionGenerationStoreError("killed verdict facts.terminalAt is reserved");
246
246
  }
247
247
  return writeTransition(database, readOnly, () => {
248
- const history = readValidatedHistory(database);
248
+ const history = readValidatedHistory(database, readOnly);
249
249
  const current = history.current;
250
250
  if (!current)
251
251
  return rejected("no-current-launch", null);
@@ -274,7 +274,7 @@ export function verdictIfOpen(database, readOnly, input) {
274
274
  throw new ProjectionGenerationStoreError(`verdict '${input.verdict}' is reserved to its runner-owned transition`);
275
275
  }
276
276
  return writeTransition(database, readOnly, () => {
277
- const history = readValidatedHistory(database);
277
+ const history = readValidatedHistory(database, readOnly);
278
278
  const current = history.current;
279
279
  if (!current)
280
280
  return rejected("no-current-launch", null);
@@ -308,7 +308,7 @@ export function supersedeStartupTimeout(database, readOnly, input) {
308
308
  ? undefined
309
309
  : assertIsoTimestamp(input.terminalAt, "terminalAt");
310
310
  return writeTransition(database, readOnly, () => {
311
- const history = readValidatedHistory(database);
311
+ const history = readValidatedHistory(database, readOnly);
312
312
  const current = history.current;
313
313
  if (!current)
314
314
  return rejected("no-current-launch", null);
@@ -356,7 +356,7 @@ export function settleStartupFailure(database, readOnly, input) {
356
356
  ? undefined
357
357
  : assertIsoTimestamp(input.terminalAt, "terminalAt");
358
358
  return writeTransition(database, readOnly, () => {
359
- const history = readValidatedHistory(database);
359
+ const history = readValidatedHistory(database, readOnly);
360
360
  const current = history.current;
361
361
  if (!current)
362
362
  return rejected("no-current-launch", null);
@@ -395,7 +395,7 @@ export function replaceDeadRunner(database, readOnly, input) {
395
395
  ? undefined
396
396
  : assertIsoTimestamp(input.terminalAt, "terminalAt");
397
397
  return writeTransition(database, readOnly, () => {
398
- const history = readValidatedHistory(database);
398
+ const history = readValidatedHistory(database, readOnly);
399
399
  const current = history.current;
400
400
  if (!current)
401
401
  return rejected("no-current-launch", null);
@@ -436,7 +436,7 @@ export function handoff(database, readOnly, input) {
436
436
  const tellFence = validateTellFence([...input.tellFence], "handoff tellFence");
437
437
  const successorFacts = validateLaunchFacts({ ...successorFactsBase, tellFence }, "handoff successor launch facts");
438
438
  return writeTransition(database, readOnly, () => {
439
- const history = readValidatedHistory(database);
439
+ const history = readValidatedHistory(database, readOnly);
440
440
  const current = history.current;
441
441
  if (!current)
442
442
  return rejected("no-current-launch", null);
@@ -10,18 +10,18 @@ export { resolveProjectionCoordinate } from "./projection-coordinate.js";
10
10
  export { appendGenerationEvent, executionEventsPath, isProjectionId, isPublishedProjection, isoFromNowMs, projectionDir, projectionDirAtCoordinate, projectionRoot, readProjectionEvents, readGenerationEventsWithRetention, } from "./projection-core.js";
11
11
  export { readProjectionIdentity } from "./projection-identity.js";
12
12
  export { interruptProjectionGeneration, killProjectionGeneration } from "./projection-kill.js";
13
+ export { openProjectionGenerationDoorbell, projectionGenerationDoorbellAddress, ringProjectionGenerationDoorbell, } from "./generation/projection-generation-doorbell.js";
13
14
  export { observeProjectionLife, observeProjectionLifeSnapshot, } from "./projection-life-observer.js";
14
15
  export { mintProjection, mintRepositoryProjection, ProjectionMintCollisionError, } from "./projection-mint.js";
15
- export { acquireProjectionRunnerLock, acquireProjectionRunnerSettlementLock, acquireProjectionRunnerTransferTarget, initializeProjectionRunnerLockDatabase, isProjectionRunnerLockPlatformSupported, observeProjectionRunnerLock, projectionRunnerLockPath, } from "./projection-runner-lock.js";
16
- export { hasStrandableProjectionGeneration, prioritizeProjectionStatusRows, readAkumaNonterminalProjectionStatus, readExactProjectionStatus, readOperationalProjectionStatusBoard, } from "./projection-status.js";
16
+ export { hasStrandableProjectionGeneration, prioritizeProjectionStatusRows, paginateProjectionStatusRoster, triageProjectionStatusRows, readAkumaNonterminalProjectionStatus, readExactProjectionStatus, readOperationalProjectionStatusBoard, readProjectionRosterStatusBoard, selectProjectionStatus, } from "./projection-status.js";
17
17
  export { PROJECTION_HISTORY_FILTER_COMPLETION, PROJECTION_HISTORY_FILTER_FIELDS, PROJECTION_HISTORY_FILTER_GRAMMAR, parseProjectionHistoryFilter, parseProjectionHistorySort, readProjectionHistoryAddress, readExactProjectionHistory, readProjectionHistory, resolveProjectionHistoryAddress, selectProjectionHistory, } from "./projection-history.js";
18
18
  export { projectTerminalFailure, } from "./projection-terminal-failure.js";
19
19
  export { snapshotCommissionProjectionIds, snapshotCommissionProjectionIdsAtCoordinate, waitForProjection, waitForProjectionSet, } from "./projection-wait.js";
20
20
  export { assertTellEffortSupported, assertProjectionTellable, tellProjection, } from "./projection-wake.js";
21
21
  export { openProjectionGenerationStore, openProjectionGenerationStoreReadOnly, ProjectionGenerationStore, } from "./generation/store.js";
22
- export { createProjectionExecutionId } from "./generation/projection-generation-identity.js";
23
- export { PROJECTION_ADOPTION_TIMEOUT_MS, startProjectionGeneration, } from "./generation/projection-generation-launcher.js";
24
- export { projectionGenerationRunnerArgv, spawnProjectionGenerationRunner, } from "./generation/projection-generation-process.js";
22
+ export { createProjectionExecutionId, projectionRunnerLeasePath, } from "./generation/projection-generation-identity.js";
23
+ export { startProjectionGeneration } from "./generation/projection-generation-launcher.js";
24
+ export { projectionGenerationRunnerArgv, prepareProjectionGenerationRunner, } from "./generation/projection-generation-process.js";
25
25
  export { runProjectionGeneration, } from "./generation/projection-generation-runner.js";
26
26
  export { executeProjectionGeneration, prepareProjectionGenerationExecution, } from "./generation/projection-generation-execution.js";
27
27
  export { runProjectionGenerationRuntime } from "./generation/projection-generation-runtime.js";
@@ -3,14 +3,18 @@ import * as path from "node:path";
3
3
  import { isErrnoException } from "../../errno.js";
4
4
  import { resolveProjectionCoordinate } from "./projection-coordinate.js";
5
5
  import { ProjectionStateError, } from "../atomic-publish.js";
6
+ import { isAkumaName } from "./akuma-name.js";
6
7
  export { atomicPublishFile, ProjectionStateError, randomHex8 } from "../atomic-publish.js";
7
8
  export { isTellCausallyConsumed, } from "./tell/model.js";
8
9
  export { bindTellLaunchReservation, claimAllInbox, claimAndReserveFencedTells, claimFencedTells, listTellLaunchBatch, listTellLaunchAdmissions, isTellLaunchAdmissionMember, claimTellToInflight, listTellIds, markTellSubmitted, recordTellReceiptCapabilities, recordTellReceipt, readTellReceiptCapabilities, readTellOriginal, readTellSubmitted, snapshotPendingTells, writeInboxTell, } from "./tell/store.js";
9
10
  /** Relative segments for `.keiyaku/projection`. */
10
11
  export const PROJECTION_ROOT_SEGMENTS = [".keiyaku", "projection"];
11
- export const PROJECTION_ID_PATTERN = /^[a-zA-Z0-9]+(?:[.-][a-zA-Z0-9]+)*\/[0-9a-f]{8}$/;
12
+ const PROJECTION_HEX_PATTERN = /^[0-9a-f]{8}$/;
12
13
  export function isProjectionId(value) {
13
- return PROJECTION_ID_PATTERN.test(value);
14
+ const slash = value.indexOf("/");
15
+ if (slash <= 0 || slash !== value.lastIndexOf("/"))
16
+ return false;
17
+ return isAkumaName(value.slice(0, slash)) && PROJECTION_HEX_PATTERN.test(value.slice(slash + 1));
14
18
  }
15
19
  /** Derive one projection storage root from already-resolved repository authority. */
16
20
  export function projectionRootAtCoordinate(coordinate) {
@@ -1,5 +1,5 @@
1
1
  import { claimAllInbox, isTellLaunchAdmissionMember, listTellLaunchAdmissions, listTellIds, markTellSubmitted, bindTellLaunchReservation, recordTellReceiptCapabilities, recordTellReceipt, readTellSubmitted, readTellOriginal, isTellCausallyConsumed, appendGenerationEvent, } from "./projection-core.js";
2
- import { openProjectionTellObserver, } from "./tell/database.js";
2
+ import { openProjectionTellObserver } from "./generation/database.js";
3
3
  const LIVE_TELL_DELIVERY_THROTTLE_MS = 200;
4
4
  export function renderProjectionTellFrame(tellId, text) {
5
5
  void tellId;
@@ -13,13 +13,12 @@ import { isProjectionId, projectionDir, projectionRoot, readGenerationEventsWith
13
13
  import { projectionCoordinateRoot } from "./projection-coordinate.js";
14
14
  import { readProjectionIdentity } from "./projection-identity.js";
15
15
  import { observeProjectionLifeSnapshot } from "./projection-life-observer.js";
16
- import { selectCurrentGeneration } from "./projection-life-protocol.js";
17
- import { PROJECTION_ADOPTION_TIMEOUT_MS } from "./generation/projection-generation-launcher.js";
16
+ import { selectCurrentGeneration } from "./generation/protocol.js";
18
17
  export const PROJECTION_HISTORY_FILTER_FIELDS = ["contract", "akuma", "role", "outcome", "created", "updated"];
19
18
  export const PROJECTION_HISTORY_FILTER_GRAMMAR = STRUCTURED_QUERY_GRAMMAR;
20
19
  export const PROJECTION_HISTORY_FILTER_COMPLETION = STRUCTURED_QUERY_COMPLETION;
21
20
  const HISTORY_FIELDS = new Set(PROJECTION_HISTORY_FILTER_FIELDS);
22
- const HISTORY_ROLES = new Set(["call", "revive", "unknown"]);
21
+ const HISTORY_ROLES = new Set(["call", "fork", "unknown"]);
23
22
  const HISTORY_OUTCOMES = new Set(["minting", "startup-timeout", "unknown", "out", "lost", "done", "killed", "dismissed", "failed", "dead", "incompatible"]);
24
23
  const RELATIVE_TIME = /^now-([1-9][0-9]*)([smhdw])$/;
25
24
  const UNIT_MS = { s: 1_000, m: 60_000, h: 3_600_000, d: 86_400_000, w: 604_800_000 };
@@ -119,7 +118,7 @@ function outcomeFromLife(phase) {
119
118
  function roleFromRecords(records) {
120
119
  const inputs = records?.find((record) => record.kind === "launch")?.facts.inputs;
121
120
  return inputs && typeof inputs === "object" && !Array.isArray(inputs) && typeof inputs.sourceArtifactId === "string"
122
- ? "revive"
121
+ ? "fork"
123
122
  : records?.some((record) => record.kind === "launch") ? "call" : "unknown";
124
123
  }
125
124
  async function artifactFromVerdict(cwd, artifactId, executionId, index) {
@@ -235,10 +234,9 @@ async function readRun(artifactRoot, directory, id, alias, artifactIndex, addres
235
234
  const diagnostics = [];
236
235
  const snapshot = observeProjectionLifeSnapshot(directory, {
237
236
  nowMs: Date.now(),
238
- startupTimeoutMs: PROJECTION_ADOPTION_TIMEOUT_MS,
239
237
  });
240
238
  if (snapshot.compatibility === "incompatible") {
241
- return { id, ...(alias ? { alias } : {}), akuma: null, provider: null, pid: null, role: "unknown", contractId: null, outcome: "incompatible", mintedAt: null, terminalAt: null, generation: null, activityAt: null, artifact: null, artifacts: [], activityRetention: "incompatible", activityCount: 0, activityRange: null, telemetry: null, consequences: null, diagnostics: [snapshot.diagnostic], activity: null };
239
+ return { id, ...(alias ? { alias } : {}), akuma: null, provider: null, pid: null, role: "unknown", contractId: null, outcome: "incompatible", mintedAt: null, terminalAt: null, generation: null, activityAt: null, artifact: null, artifacts: [], activityRetention: "incompatible", activityCount: 0, activityRange: null, telemetry: null, consequences: null, diagnostics: [snapshot.diagnostic], terminalAnswer: null, activity: null };
242
240
  }
243
241
  let identity;
244
242
  try {
@@ -296,6 +294,9 @@ async function readRun(artifactRoot, directory, id, alias, artifactIndex, addres
296
294
  telemetry: telemetryFromActivity(activity),
297
295
  consequences: null,
298
296
  diagnostics,
297
+ terminalAnswer: typeof current?.verdict?.facts.finalMessage === "string"
298
+ ? { kind: "text", text: current.verdict.facts.finalMessage }
299
+ : null,
299
300
  activity,
300
301
  };
301
302
  }
@@ -384,7 +385,6 @@ async function retainedArtifactRelationships(root, artifactId, preferredAkuma) {
384
385
  return [];
385
386
  const snapshot = observeProjectionLifeSnapshot(directory, {
386
387
  nowMs: Date.now(),
387
- startupTimeoutMs: PROJECTION_ADOPTION_TIMEOUT_MS,
388
388
  });
389
389
  if (snapshot.compatibility === "incompatible" || !snapshot.records)
390
390
  return [];
@@ -1,5 +1,7 @@
1
1
  import { openProjectionGenerationStore } from "./generation/store.js";
2
- import { acquireProjectionRunnerSettlementLock, observeProjectionRunnerLock, ProjectionRunnerLockUnavailableError, } from "./projection-runner-lock.js";
2
+ import { acquireRunnerLostProof, observeRunnerLease, RunnerLeaseUnavailableError, } from "../run-control/runner-lease.js";
3
+ import { projectionRunnerLeasePath } from "./generation/projection-generation-identity.js";
4
+ import { ringProjectionGenerationDoorbell } from "./generation/projection-generation-doorbell.js";
3
5
  /** The runner observes this durable intent and owns native termination. */
4
6
  export function interruptProjectionGeneration(projectionDirectory, nowMs = Date.now()) {
5
7
  const store = openProjectionGenerationStore(projectionDirectory);
@@ -11,19 +13,23 @@ export function interruptProjectionGeneration(projectionDirectory, nowMs = Date.
11
13
  if (current.verdict)
12
14
  return { status: "already-closed", executionId: current.launch.executionId };
13
15
  const executionId = current.launch.executionId;
14
- if (!current.adoption || observeProjectionRunnerLock(projectionDirectory).state !== "held") {
16
+ if (!current.adoption || observeRunnerLease(projectionRunnerLeasePath(projectionDirectory)).state !== "held") {
15
17
  return { status: "already-closed", executionId };
16
18
  }
17
19
  const request = store.requestInterruptIfOpen({
18
20
  executionId,
19
21
  interruptedAt: new Date(nowMs).toISOString(),
20
22
  });
21
- if (request.status === "committed")
23
+ if (request.status === "committed") {
24
+ ringProjectionGenerationDoorbell(store.readIdentity(), executionId);
22
25
  return { status: "accepted", executionId };
26
+ }
23
27
  if (request.reason === "not-current")
24
28
  continue;
25
- if (request.reason === "interrupt-requested")
29
+ if (request.reason === "interrupt-requested") {
30
+ ringProjectionGenerationDoorbell(store.readIdentity(), executionId);
26
31
  return { status: "accepted", executionId };
32
+ }
27
33
  return { status: "already-closed", executionId };
28
34
  }
29
35
  }
@@ -39,10 +45,10 @@ function settleReleasedLeash(store, projectionDirectory, executionId, terminalAt
39
45
  }
40
46
  let deathEvidence;
41
47
  try {
42
- deathEvidence = acquireProjectionRunnerSettlementLock(projectionDirectory);
48
+ deathEvidence = acquireRunnerLostProof(projectionRunnerLeasePath(projectionDirectory));
43
49
  }
44
50
  catch (error) {
45
- if (error instanceof ProjectionRunnerLockUnavailableError) {
51
+ if (error instanceof RunnerLeaseUnavailableError) {
46
52
  return { status: "accepted", executionId };
47
53
  }
48
54
  return { status: "accepted", executionId };
@@ -79,10 +85,13 @@ export function killProjectionGeneration(projectionDirectory, nowMs = Date.now()
79
85
  const executionId = current.launch.executionId;
80
86
  const request = store.requestKillIfOpen({ executionId, killedAt });
81
87
  if (request.status === "committed") {
88
+ // A missing, stale, or rejected endpoint has no lifecycle meaning.
89
+ ringProjectionGenerationDoorbell(store.readIdentity(), executionId);
82
90
  return settleReleasedLeash(store, projectionDirectory, executionId, new Date(nowMs).toISOString());
83
91
  }
84
92
  if (request.reason === "kill-requested") {
85
93
  // Idempotent first intent: still accepted, then one leash observation.
94
+ ringProjectionGenerationDoorbell(store.readIdentity(), executionId);
86
95
  return settleReleasedLeash(store, projectionDirectory, executionId, new Date(nowMs).toISOString());
87
96
  }
88
97
  if (request.reason === "already-terminal") {
@@ -1,13 +1,15 @@
1
1
  import { isIncompatibleProjectionGenerationStoreError, openProjectionGenerationStoreReadOnly, } from "./generation/store.js";
2
- import { deriveProjectionLife, selectCurrentGeneration, } from "./projection-life-protocol.js";
3
- import { observeProjectionRunnerLock } from "./projection-runner-lock.js";
2
+ import { deriveProjectionLife, selectCurrentGeneration, } from "./generation/protocol.js";
3
+ import { observeRunnerLease } from "../run-control/runner-lease.js";
4
+ import { projectionRunnerLeasePath } from "./generation/projection-generation-identity.js";
4
5
  function errorDetail(error) {
5
6
  return error instanceof Error ? error.message : String(error);
6
7
  }
7
8
  /**
8
9
  * Observe the current generation without creating, repairing, or mutating
9
10
  * projection storage. Generation history is the ordering authority; the
10
- * runner lock is consulted only for an adopted, non-terminal generation.
11
+ * runner lock is consulted for every non-terminal generation. Launch age is
12
+ * not lifecycle authority.
11
13
  */
12
14
  export function observeProjectionLifeSnapshot(projectionDirectory, options) {
13
15
  const readRecords = () => {
@@ -37,18 +39,17 @@ export function observeProjectionLifeSnapshot(projectionDirectory, options) {
37
39
  };
38
40
  }
39
41
  const current = selectCurrentGeneration(records);
40
- if (!current || current.verdict || !current.adoption) {
42
+ if (!current || current.verdict) {
41
43
  return {
42
44
  compatibility: "current",
43
45
  life: deriveProjectionLife({
44
46
  records,
45
47
  nowMs: options.nowMs,
46
- startupTimeoutMs: options.startupTimeoutMs,
47
48
  }),
48
49
  records,
49
50
  };
50
51
  }
51
- const lock = observeProjectionRunnerLock(projectionDirectory, options.platform);
52
+ const lock = observeRunnerLease(projectionRunnerLeasePath(projectionDirectory));
52
53
  let after;
53
54
  try {
54
55
  after = readRecords();
@@ -74,13 +75,12 @@ export function observeProjectionLifeSnapshot(projectionDirectory, options) {
74
75
  compatibility: "current",
75
76
  life: deriveProjectionLife({
76
77
  records,
77
- runnerLock: lock.state === "held"
78
+ runnerLease: lock.state === "held"
78
79
  ? "held"
79
80
  : lock.state === "released"
80
81
  ? "released"
81
82
  : "unknown",
82
83
  nowMs: options.nowMs,
83
- startupTimeoutMs: options.startupTimeoutMs,
84
84
  }),
85
85
  records,
86
86
  };
@@ -5,7 +5,9 @@ import { ProjectionStateError, randomHex8 } from "../atomic-publish.js";
5
5
  import { isoFromNowMs, PROJECTION_ROOT_SEGMENTS, projectionDir, projectionRoot, } from "./projection-core.js";
6
6
  import { parseExecutionAuthoritySelection, parseExecutionPact, parseExecutionPactFields, } from "../execution-pact.js";
7
7
  import { openProjectionGenerationStore } from "./generation/store.js";
8
- import { initializeProjectionRunnerLockDatabase } from "./projection-runner-lock.js";
8
+ import { initializeRunnerLeaseDatabase } from "../run-control/runner-lease.js";
9
+ import { projectionRunnerLeasePath } from "./generation/projection-generation-identity.js";
10
+ import { isAkumaName } from "./akuma-name.js";
9
11
  export class ProjectionMintCollisionError extends Error {
10
12
  code = "PROJECTION_MINT_COLLISION";
11
13
  constructor(message, options) {
@@ -15,14 +17,10 @@ export class ProjectionMintCollisionError extends Error {
15
17
  }
16
18
  const DEFAULT_MINT_ATTEMPTS = 16;
17
19
  function assertMintSlug(slug) {
18
- const normalized = slug.trim();
19
- if (!normalized) {
20
- throw new ProjectionStateError("projection slug cannot be empty");
21
- }
22
- if (!/^[a-zA-Z0-9]+(?:[.-][a-zA-Z0-9]+)*$/.test(normalized)) {
20
+ if (!isAkumaName(slug)) {
23
21
  throw new ProjectionStateError(`invalid projection slug: ${slug}`);
24
22
  }
25
- return normalized;
23
+ return slug;
26
24
  }
27
25
  function removeEmptyAkumaRoot(dir) {
28
26
  try {
@@ -115,7 +113,7 @@ function publishReservedProjection(pact, reserved) {
115
113
  finally {
116
114
  heart.close();
117
115
  }
118
- initializeProjectionRunnerLockDatabase(reserved.stagingDir);
116
+ initializeRunnerLeaseDatabase(projectionRunnerLeasePath(reserved.stagingDir));
119
117
  fs.renameSync(reserved.stagingDir, reserved.dir);
120
118
  published = true;
121
119
  return { id: reserved.id, dir: reserved.dir, pact };
@@ -7,12 +7,51 @@ import { foldPublicProjectionActivity } from "./projection-activity.js";
7
7
  import { readProjectionIdentity } from "./projection-identity.js";
8
8
  import { readPendingTellWindow, readProjectionTellCounts } from "./tell/store.js";
9
9
  import { ProjectionGenerationStoreError } from "./generation/store.js";
10
- import { PROJECTION_ADOPTION_TIMEOUT_MS } from "./generation/projection-generation-launcher.js";
11
10
  import { observeProjectionLifeSnapshot } from "./projection-life-observer.js";
12
- import { selectCurrentGeneration } from "./projection-life-protocol.js";
11
+ import { selectCurrentGeneration } from "./generation/protocol.js";
13
12
  import { invalidActivityEventDiagnostic, selectValidStoredAgentEvents } from "../stored-agent-event.js";
14
13
  import { projectTerminalFailure } from "./projection-terminal-failure.js";
15
14
  import { extractTerminalTimestamp } from "./generation/model.js";
15
+ function latestActivityRow(activity, kind, predicate) {
16
+ const rows = activity?.retainedTimeline.filter((row) => row.kind === kind) ?? [];
17
+ return [...rows].reverse().find((row) => predicate?.(row) ?? true);
18
+ }
19
+ export function deriveProjectionStatusReadFacts(input) {
20
+ const warning = latestActivityRow(input.activity, "warning");
21
+ const runningTool = latestActivityRow(input.activity, "ran", (row) => row.state === "running");
22
+ const said = latestActivityRow(input.activity, "said");
23
+ const answer = typeof input.verdictFacts?.finalMessage === "string"
24
+ ? { kind: "text", text: input.verdictFacts.finalMessage }
25
+ : undefined;
26
+ const responsePath = typeof input.verdictFacts?.responsePath === "string" ? input.verdictFacts.responsePath : undefined;
27
+ const artifactId = typeof input.verdictFacts?.artifactId === "string" ? input.verdictFacts.artifactId : undefined;
28
+ const launchInputs = input.launchFacts?.inputs;
29
+ const incognito = launchInputs !== null && typeof launchInputs === "object" && !Array.isArray(launchInputs)
30
+ && launchInputs.persistProjectionResponse === false;
31
+ const persistenceFailed = input.verdictFacts?.responsePersistError !== undefined;
32
+ const snapshot = warning
33
+ ? { kind: "warning", row: warning }
34
+ : runningTool
35
+ ? { kind: "running-tool", row: runningTool }
36
+ : said
37
+ ? { kind: "said", row: said }
38
+ : input.activity?.plan
39
+ ? { kind: "plan", plan: input.activity.plan }
40
+ : undefined;
41
+ const responseArtifact = input.verdictFacts === undefined
42
+ ? undefined
43
+ : responsePath
44
+ ? { state: "retained", path: responsePath, ...(artifactId ? { artifactId } : {}) }
45
+ : incognito
46
+ ? { state: "none", reason: "incognito" }
47
+ : { state: "unavailable", reason: persistenceFailed ? "persistence-failed" : "not-observed" };
48
+ return {
49
+ ...(answer ? { terminalAnswer: answer } : {}),
50
+ ...(responseArtifact ? { responseArtifact } : {}),
51
+ attention: { ...(warning ? { latestWarning: warning } : {}) },
52
+ ...(snapshot ? { snapshot } : {}),
53
+ };
54
+ }
16
55
  const MAX_STATUS_FILE_BYTES = 256 * 1024;
17
56
  class ProjectionStatusFileError extends Error {
18
57
  diagnostic;
@@ -233,11 +272,19 @@ function readActivityFacts(projectionDir, executionId, nowMs, diagnostics, fallb
233
272
  function terminalTimestamp(current) {
234
273
  return extractTerminalTimestamp(current?.verdict?.facts);
235
274
  }
275
+ function terminalAgeAt(terminalAt, observedAtMs) {
276
+ const terminalAtMs = Date.parse(terminalAt ?? "");
277
+ return Number.isFinite(terminalAtMs) ? Math.max(0, observedAtMs - terminalAtMs) : undefined;
278
+ }
279
+ function projectionStatusState(life) {
280
+ if (life.verdict === "lost-runner")
281
+ return "dead";
282
+ return life.phase === "active" || life.phase === "quiescent" ? "out" : life.phase;
283
+ }
236
284
  export function readProjectionSummary(projectionDir, id, alias, nowMs, options) {
237
285
  const diagnostics = [];
238
286
  const snapshot = observeProjectionLifeSnapshot(projectionDir, {
239
287
  nowMs,
240
- startupTimeoutMs: PROJECTION_ADOPTION_TIMEOUT_MS,
241
288
  });
242
289
  if (snapshot.compatibility === "incompatible") {
243
290
  return {
@@ -251,8 +298,7 @@ export function readProjectionSummary(projectionDir, id, alias, nowMs, options)
251
298
  };
252
299
  }
253
300
  const life = snapshot.life;
254
- const phase = life.phase;
255
- const state = phase === "active" || phase === "quiescent" ? "out" : phase;
301
+ const state = projectionStatusState(life);
256
302
  const identityFacts = options?.requiresIdentity?.(state) === false
257
303
  ? null
258
304
  : readIdentityFacts(projectionDir, diagnostics);
@@ -268,27 +314,38 @@ export function readProjectionSummary(projectionDir, id, alias, nowMs, options)
268
314
  }
269
315
  export function readProjectionRow(summary, nowMs, readDetails) {
270
316
  const { id, alias, projectionDir, snapshot } = summary;
317
+ const identity = { canonicalId: id };
271
318
  const diagnostics = [...summary.diagnostics];
272
319
  if (snapshot.compatibility === "incompatible") {
273
320
  return {
274
321
  id,
322
+ identity,
275
323
  observedAtMs: nowMs,
276
324
  ...(alias ? { alias } : {}),
277
325
  state: "incompatible",
278
326
  observation: snapshot,
279
327
  tellCounts: emptyTellCounts(),
328
+ attention: {},
280
329
  diagnostics,
281
330
  };
282
331
  }
283
332
  const identityFacts = summary.identityFacts;
284
333
  if (identityFacts === null && !readDetails) {
334
+ const current = snapshot.records ? selectCurrentGeneration(snapshot.records) : null;
335
+ const terminalAt = terminalTimestamp(current);
336
+ const terminalAgeMs = current?.verdict ? terminalAgeAt(terminalAt, nowMs) : undefined;
337
+ const readFacts = deriveProjectionStatusReadFacts({ state: summary.state });
285
338
  return {
286
339
  id,
340
+ identity,
287
341
  observedAtMs: nowMs,
288
342
  ...(alias ? { alias } : {}),
289
343
  state: summary.state,
290
344
  observation: { compatibility: "current", lifecycle: snapshot.life },
345
+ ...(terminalAt ? { terminalAt } : {}),
346
+ ...(terminalAgeMs !== undefined ? { terminalAgeMs } : {}),
291
347
  tellCounts: emptyTellCounts(),
348
+ ...readFacts,
292
349
  diagnostics,
293
350
  };
294
351
  }
@@ -297,6 +354,7 @@ export function readProjectionRow(summary, nowMs, readDetails) {
297
354
  ?? "projection identity could not be observed";
298
355
  return {
299
356
  id,
357
+ identity,
300
358
  observedAtMs: nowMs,
301
359
  ...(alias ? { alias } : {}),
302
360
  state: "unknown",
@@ -305,6 +363,7 @@ export function readProjectionRow(summary, nowMs, readDetails) {
305
363
  lifecycle: { phase: "unknown", diagnostic },
306
364
  },
307
365
  tellCounts: emptyTellCounts(),
366
+ attention: {},
308
367
  diagnostics,
309
368
  };
310
369
  }
@@ -325,8 +384,10 @@ export function readProjectionRow(summary, nowMs, readDetails) {
325
384
  : {};
326
385
  const mintedAtMs = Date.parse(identityFacts.mintedAt ?? "");
327
386
  const terminalAt = terminalTimestamp(current);
387
+ const terminalAtMs = Date.parse(terminalAt ?? "");
388
+ const terminalAgeMs = current?.verdict ? terminalAgeAt(terminalAt, nowMs) : undefined;
328
389
  const durationAnchorMs = current?.verdict
329
- ? (terminalAt === undefined ? Number.NaN : Date.parse(terminalAt))
390
+ ? terminalAtMs
330
391
  : nowMs;
331
392
  const durationMs = Number.isFinite(mintedAtMs) && Number.isFinite(durationAnchorMs)
332
393
  ? Math.max(0, durationAnchorMs - mintedAtMs)
@@ -336,13 +397,19 @@ export function readProjectionRow(summary, nowMs, readDetails) {
336
397
  if (life.phase === "unknown" && life.diagnostic) {
337
398
  diagnostics.push({ kind: "unknown-lifecycle", detail: life.diagnostic });
338
399
  }
339
- const phase = life.phase;
340
- const state = phase === "active" || phase === "quiescent" ? "out" : phase;
400
+ const state = projectionStatusState(life);
341
401
  const terminalFailure = current?.verdict
342
402
  ? projectTerminalFailure(current.verdict.facts.state, current.verdict.facts)
343
403
  : undefined;
404
+ const readFacts = deriveProjectionStatusReadFacts({
405
+ state,
406
+ activity: activityFacts.activity,
407
+ verdictFacts: current?.verdict?.facts,
408
+ launchFacts: current?.launch.facts,
409
+ });
344
410
  return {
345
411
  id,
412
+ identity,
346
413
  observedAtMs: nowMs,
347
414
  ...(alias ? { alias } : {}),
348
415
  state,
@@ -353,11 +420,13 @@ export function readProjectionRow(summary, nowMs, readDetails) {
353
420
  pid: identityFacts.pid,
354
421
  mintedAt: identityFacts.mintedAt,
355
422
  ...(terminalAt ? { terminalAt } : {}),
423
+ ...(terminalAgeMs !== undefined ? { terminalAgeMs } : {}),
356
424
  ...(durationMs !== undefined ? { durationMs } : {}),
357
425
  ...(identityFacts.workspaceRoot ? { workspaceRoot: identityFacts.workspaceRoot } : {}),
358
426
  ...activityFacts,
359
427
  ...(displayAgeMs !== undefined ? { activityAgeMs: displayAgeMs } : {}),
360
428
  ...(terminalFailure ? { terminalFailure } : {}),
429
+ ...readFacts,
361
430
  tellCounts,
362
431
  ...(pendingTells.length > 0 ? { pendingTells } : {}),
363
432
  diagnostics,