@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.
- package/README.md +50 -182
- package/build/.tsbuildinfo +1 -1
- package/build/agents/harness/control-types.js +18 -1
- package/build/agents/harness/index.js +2 -0
- package/build/agents/harness/outcome-error.js +81 -0
- package/build/agents/harness/projection.js +10 -7
- package/build/agents/harness/router.js +5 -1
- package/build/agents/providers/claude-agent-sdk/adapter.js +18 -4
- package/build/agents/providers/claude-agent-sdk/registration.js +11 -1
- package/build/agents/providers/claude-agent-sdk/session.js +19 -5
- package/build/agents/providers/codex-app-server/adapter.js +11 -6
- package/build/agents/providers/codex-app-server/events.js +67 -6
- package/build/agents/providers/codex-app-server/registration.js +11 -1
- package/build/agents/providers/opencode-sdk/adapter.js +8 -1
- package/build/agents/providers/opencode-sdk/events.js +24 -2
- package/build/agents/providers/opencode-sdk/registration.js +11 -1
- package/build/agents/providers/pi/checkpoint.js +9 -1
- package/build/agents/providers/pi/events.js +2 -0
- package/build/agents/providers/pi/registration.js +14 -1
- package/build/cli/commands/akuma/akuma/handler.js +1 -1
- package/build/cli/commands/akuma/akuma/meta.js +3 -3
- package/build/cli/commands/akuma/catalog.js +2 -2
- package/build/cli/commands/akuma/{view → show}/handler.js +5 -5
- package/build/cli/commands/akuma/{view → show}/meta.js +3 -3
- package/build/cli/commands/akuma.js +1 -1
- package/build/cli/commands/contract/amend/handler.js +5 -4
- package/build/cli/commands/contract/arc/handler.js +5 -4
- package/build/cli/commands/contract/audit/handler.js +2 -2
- package/build/cli/commands/contract/bind/handler.js +12 -3
- package/build/cli/commands/contract/bind/meta.js +45 -15
- package/build/cli/commands/contract/forfeit/handler.js +9 -5
- package/build/cli/commands/contract/log/handler.js +2 -2
- package/build/cli/commands/contract/petition/handler.js +34 -7
- package/build/cli/commands/contract/renew/handler.js +7 -7
- package/build/cli/commands/contract/renew/meta.js +3 -3
- package/build/cli/commands/metadata.js +10 -6
- package/build/cli/commands/projection/call/meta.js +1 -1
- package/build/cli/commands/projection/catalog.js +2 -0
- package/build/cli/commands/projection/history/handler.js +21 -0
- package/build/cli/commands/projection/history/meta.js +9 -0
- package/build/cli/commands/projection/shared.js +3 -4
- package/build/cli/commands/projection/status/handler.js +3 -3
- package/build/cli/commands/projection/wait/handler.js +20 -11
- package/build/cli/commands/task/add/handler.js +14 -5
- package/build/cli/commands/task/add/meta.js +2 -2
- package/build/cli/commands/task/catalog.js +4 -0
- package/build/cli/commands/task/done/handler.js +1 -4
- package/build/cli/commands/task/done/meta.js +1 -1
- package/build/cli/commands/task/drop/meta.js +1 -1
- package/build/cli/commands/task/hold/handler.js +3 -0
- package/build/cli/commands/task/hold/meta.js +8 -0
- package/build/cli/commands/task/log/handler.js +1 -1
- package/build/cli/commands/task/log/meta.js +2 -2
- package/build/cli/commands/task/resume/handler.js +3 -0
- package/build/cli/commands/task/resume/meta.js +8 -0
- package/build/cli/commands/task/shared.js +39 -14
- package/build/cli/commands/task/show/handler.js +1 -1
- package/build/cli/commands/task/show/meta.js +1 -1
- package/build/cli/commands/task/start/meta.js +1 -1
- package/build/cli/commands/task/stop/handler.js +1 -4
- package/build/cli/commands/task/stop/meta.js +1 -1
- package/build/cli/commands/task/update/handler.js +11 -3
- package/build/cli/commands/task/update/meta.js +3 -3
- package/build/cli/completion.js +3 -2
- package/build/cli/flags.js +51 -14
- package/build/cli/help.js +4 -1
- package/build/cli/index.js +40 -25
- package/build/cli/parse-flags.js +40 -7
- package/build/cli/parse-metadata.js +5 -2
- package/build/cli/parse-selectors.js +2 -3
- package/build/cli/parse.js +40 -6
- package/build/cli/projection-address.js +12 -10
- package/build/cli/render/amendment-warning.js +8 -0
- package/build/cli/render/arc.js +6 -2
- package/build/cli/render/audit.js +400 -81
- package/build/cli/render/kanshi.js +242 -79
- package/build/cli/render/lifecycle.js +56 -0
- package/build/cli/render/misc.js +24 -28
- package/build/cli/render/petition.js +5 -0
- package/build/cli/render/projection-activity.js +65 -29
- package/build/cli/render/projection-history.js +64 -0
- package/build/cli/render/shared.js +23 -9
- package/build/cli/render/status.js +13 -35
- package/build/cli/render/task.js +51 -26
- package/build/cli/render/terminal-failure.js +3 -1
- package/build/cli/render/wait.js +45 -69
- package/build/cli/subagent-guard.js +1 -1
- package/build/cli/types.js +1 -1
- package/build/config/env-keys.js +3 -1
- package/build/config/env.js +24 -1
- package/build/config/settings/schema.js +3 -1
- package/build/core/address-carrier.js +89 -0
- package/build/core/addressing.js +255 -196
- package/build/core/amend.js +115 -38
- package/build/core/amendment-criteria.js +17 -0
- package/build/core/amendment-warning.js +10 -0
- package/build/core/arc.js +184 -83
- package/build/core/audit/candidate.js +110 -9
- package/build/core/audit/coordinates.js +120 -65
- package/build/core/audit/evidence.js +19 -23
- package/build/core/audit/facade.js +34 -11
- package/build/core/audit/report.js +5 -5
- package/build/core/bind-reconciliation.js +390 -0
- package/build/core/bind-workspace.js +12 -20
- package/build/core/bind.js +242 -273
- package/build/core/call/call.js +1 -0
- package/build/core/call/context.js +46 -71
- package/build/core/call/prompt.js +6 -3
- package/build/core/context.js +49 -35
- package/build/core/contract-carrier-runtime.js +408 -0
- package/build/core/contract-carrier.js +270 -0
- package/build/core/contract-view.js +33 -27
- package/build/core/contract.js +5 -1
- package/build/core/derived-replay.js +207 -0
- package/build/core/draft.js +125 -57
- package/build/core/forfeit.js +113 -56
- package/build/core/identifier-slug.js +63 -13
- package/build/core/ids.js +4 -4
- package/build/core/lifecycle-history.js +35 -0
- package/build/core/lifecycle-recovery.js +117 -0
- package/build/core/lifecycle-runner.js +377 -0
- package/build/core/log.js +37 -11
- package/build/core/outcome-base.js +181 -0
- package/build/core/process-group.js +139 -0
- package/build/core/projection/generation/database.js +82 -7
- package/build/core/projection/generation/model.js +9 -36
- package/build/core/projection/generation/projection-generation-continuation.js +10 -22
- package/build/core/projection/generation/projection-generation-execution.js +13 -6
- package/build/core/projection/index.js +5 -4
- package/build/core/projection/projection-activity.js +29 -8
- package/build/core/projection/projection-alias.js +23 -10
- package/build/core/projection/projection-core.js +21 -7
- package/build/core/projection/projection-execution-observer.js +91 -39
- package/build/core/projection/projection-history.js +474 -0
- package/build/core/projection/projection-runner-lock.js +12 -0
- package/build/core/projection/projection-status-observation.js +365 -0
- package/build/core/projection/projection-status.js +35 -337
- package/build/core/projection/projection-terminal-failure.js +13 -8
- package/build/core/projection/projection-wait.js +7 -2
- package/build/core/projection/tell/launch-store.js +244 -0
- package/build/core/projection/tell/model.js +3 -3
- package/build/core/projection/tell/store.js +70 -118
- package/build/core/registry.js +22 -87
- package/build/core/renew-build.js +424 -0
- package/build/core/renew-plan.js +239 -0
- package/build/core/renew-rewrite.js +77 -0
- package/build/core/renew.js +295 -520
- package/build/core/repository-ledger/accepted-fold-read.js +19 -0
- package/build/core/repository-ledger/accepted-tail.js +116 -0
- package/build/core/repository-ledger/admission.js +38 -0
- package/build/core/repository-ledger/authoritative-transaction.js +1 -0
- package/build/core/repository-ledger/claim-evidence.js +147 -0
- package/build/core/repository-ledger/codec.js +297 -0
- package/build/core/repository-ledger/current-state-store.js +853 -0
- package/build/core/repository-ledger/fold-repository.js +375 -0
- package/build/core/repository-ledger/fold.js +248 -0
- package/build/core/repository-ledger/git-object-store.js +243 -0
- package/build/core/repository-ledger/history-read.js +15 -0
- package/build/core/repository-ledger/incremental-read.js +153 -0
- package/build/core/repository-ledger/index.js +15 -0
- package/build/core/repository-ledger/inventory-plan.js +75 -0
- package/build/core/repository-ledger/inventory.js +310 -0
- package/build/core/repository-ledger/publication-admission.js +125 -0
- package/build/core/repository-ledger/publication.js +353 -0
- package/build/core/repository-ledger/read-model.js +469 -0
- package/build/core/repository-ledger/transaction-membership.js +78 -0
- package/build/core/repository-ledger/traversal.js +404 -0
- package/build/core/repository-ledger/write-transaction.js +339 -0
- package/build/core/seal.js +168 -161
- package/build/core/settlement/claim-delivery.js +42 -215
- package/build/core/settlement/claim.js +235 -153
- package/build/core/settlement/index.js +4 -4
- package/build/core/settlement/petition-claim-gates.js +52 -33
- package/build/core/settlement/petition-forfeit.js +12 -5
- package/build/core/settlement/petition-head-guard.js +8 -5
- package/build/core/settlement/petition-preview.js +68 -56
- package/build/core/settlement/petition-window.js +50 -0
- package/build/core/settlement/petition.js +252 -131
- package/build/core/settlement/queue-read-model.js +72 -95
- package/build/core/settlement/queue-seat-allocation.js +35 -126
- package/build/core/settlement/queue.js +1 -1
- package/build/core/settlement/settlement.js +172 -80
- package/build/core/settlement/verdict.js +112 -66
- package/build/core/settlement/verification-coordination.js +305 -0
- package/build/core/settlement/verification-supervisor.js +275 -0
- package/build/core/settlement/verification.js +398 -31
- package/build/core/status/board.js +125 -127
- package/build/core/status/lifecycle.js +7 -80
- package/build/core/structured-query.js +23 -0
- package/build/core/target-ref.js +1 -1
- package/build/core/task/board.js +59 -24
- package/build/core/task/commands.js +181 -33
- package/build/core/task/coordinate.js +56 -0
- package/build/core/task/document.js +25 -9
- package/build/core/task/index.js +7 -4
- package/build/core/task/query.js +17 -27
- package/build/core/task/settlement-git.js +164 -179
- package/build/core/task/settlement-policy.js +15 -11
- package/build/core/task/source-board.js +31 -13
- package/build/core/task/task-bind-preparation.js +193 -0
- package/build/core/task/task-contract.js +102 -193
- package/build/core/task/task-file-transaction.js +171 -0
- package/build/core/task/task-git-runtime.js +76 -14
- package/build/core/task/task-git-store.js +68 -169
- package/build/core/task/task-store-repository.js +13 -6
- package/build/core/task/task-worktree.js +6 -33
- package/build/core/task/task.js +1 -1
- package/build/core/task/tree.js +3 -3
- package/build/core/task/validation.js +4 -3
- package/build/core/transcripts.js +93 -1
- package/build/core/worktree-bootstrap.js +44 -36
- package/build/core/worktree-path.js +210 -77
- package/build/flow-error.js +7 -7
- package/build/generated/version.js +2 -2
- package/build/git/core.js +76 -13
- package/build/git/refs.js +168 -46
- package/build/git/streaming-batch.js +73 -13
- package/package.json +1 -1
- package/skills/keiyaku/SKILL.md +1 -1
- package/skills/keiyaku-akuma/SKILL.md +3 -3
- package/skills/keiyaku-task/SKILL.md +4 -2
- package/skills/keiyaku-workflow/SKILL.md +88 -203
- package/build/core/entry.js +0 -305
- package/build/core/ledger-batch.js +0 -194
- package/build/core/ledger.js +0 -84
- package/build/core/projection/leash.js +0 -87
- package/build/core/ref-log.js +0 -119
- package/build/core/renew-session.js +0 -128
- package/build/core/status/ledger-batch.js +0 -1
- 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 =
|
|
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', '
|
|
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
|
|
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 {
|
|
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
|
-
|
|
156
|
-
|
|
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
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
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
|
-
/**
|
|
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
|
-
/**
|
|
25
|
+
/** Interrupt successors replay every non-consumed tell. */
|
|
34
26
|
export function snapshotInterruptTells(projectionDirectory, executionId) {
|
|
35
|
-
|
|
27
|
+
void executionId;
|
|
28
|
+
return withGenerationReadSnapshot(projectionDirectory, (database) => snapshotTells(database, PENDING_PROJECTION_TELL_STATES));
|
|
36
29
|
}
|
|
37
|
-
/**
|
|
30
|
+
/** Failed launch recovery retains every non-consumed tell. */
|
|
38
31
|
export function snapshotRetainedSuccessorTells(projectionDirectory, retainedFence) {
|
|
39
|
-
|
|
40
|
-
return withGenerationReadSnapshot(projectionDirectory, (database) => snapshotTells(database,
|
|
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,
|
|
43
|
+
function readContinuationFromDatabase(database, limit = 10) {
|
|
51
44
|
const tells = readPendingTellWindowFromDatabase(database, limit);
|
|
52
|
-
const
|
|
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,
|
|
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 {
|
|
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:
|
|
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 =
|
|
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();
|