@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
|
@@ -0,0 +1,474 @@
|
|
|
1
|
+
import * as fs from "node:fs";
|
|
2
|
+
import * as path from "node:path";
|
|
3
|
+
import { isErrnoException } from "../../errno.js";
|
|
4
|
+
import { FlowError } from "../../flow-error.js";
|
|
5
|
+
import { readContractHistoryConsequences, unavailableContractHistoryConsequences, } from "../repository-ledger/history-read.js";
|
|
6
|
+
import { indexResponseArtifacts, isResponseArtifactId, locateResponseArtifact, } from "../transcripts.js";
|
|
7
|
+
import { parseStructuredQuery, STRUCTURED_QUERY_COMPLETION, STRUCTURED_QUERY_GRAMMAR } from "../structured-query.js";
|
|
8
|
+
import { selectValidStoredAgentEvents } from "../stored-agent-event.js";
|
|
9
|
+
import { extractTerminalTimestamp } from "./generation/model.js";
|
|
10
|
+
import { readProjectionAlias } from "./projection-alias.js";
|
|
11
|
+
import { foldPublicProjectionActivity } from "./projection-activity.js";
|
|
12
|
+
import { isProjectionId, projectionDir, projectionRoot, readGenerationEventsWithRetention, readProjectionEvents } from "./projection-core.js";
|
|
13
|
+
import { projectionCoordinateRoot } from "./projection-coordinate.js";
|
|
14
|
+
import { readProjectionIdentity } from "./projection-identity.js";
|
|
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";
|
|
18
|
+
export const PROJECTION_HISTORY_FILTER_FIELDS = ["contract", "akuma", "role", "outcome", "created", "updated"];
|
|
19
|
+
export const PROJECTION_HISTORY_FILTER_GRAMMAR = STRUCTURED_QUERY_GRAMMAR;
|
|
20
|
+
export const PROJECTION_HISTORY_FILTER_COMPLETION = STRUCTURED_QUERY_COMPLETION;
|
|
21
|
+
const HISTORY_FIELDS = new Set(PROJECTION_HISTORY_FILTER_FIELDS);
|
|
22
|
+
const HISTORY_ROLES = new Set(["call", "revive", "unknown"]);
|
|
23
|
+
const HISTORY_OUTCOMES = new Set(["minting", "startup-timeout", "unknown", "out", "lost", "done", "killed", "dismissed", "failed", "dead", "incompatible"]);
|
|
24
|
+
const RELATIVE_TIME = /^now-([1-9][0-9]*)([smhdw])$/;
|
|
25
|
+
const UNIT_MS = { s: 1_000, m: 60_000, h: 3_600_000, d: 86_400_000, w: 604_800_000 };
|
|
26
|
+
function invalidHistoryFilter(detail, recovered = []) {
|
|
27
|
+
const suffix = recovered.length > 0
|
|
28
|
+
? `; parsed terms: ${recovered.map((term) => `${term.field}${term.operator}${term.values.join(",")}`).join("|")}`
|
|
29
|
+
: "";
|
|
30
|
+
throw new FlowError("INVALID_TARGET", `invalid history filter: ${detail}${suffix}`);
|
|
31
|
+
}
|
|
32
|
+
function parseTimestamp(value, nowMs, recovered) {
|
|
33
|
+
const relative = RELATIVE_TIME.exec(value);
|
|
34
|
+
if (relative)
|
|
35
|
+
return nowMs - Number(relative[1]) * UNIT_MS[relative[2]];
|
|
36
|
+
const parsed = Date.parse(value);
|
|
37
|
+
if (!Number.isFinite(parsed) || new Date(parsed).toISOString() !== value) {
|
|
38
|
+
invalidHistoryFilter(`timestamp '${value}' must be canonical ISO-8601 or now-<duration>`, recovered);
|
|
39
|
+
}
|
|
40
|
+
return parsed;
|
|
41
|
+
}
|
|
42
|
+
export function parseProjectionHistoryFilter(expression) {
|
|
43
|
+
return parseStructuredQuery({
|
|
44
|
+
expression,
|
|
45
|
+
fields: HISTORY_FIELDS,
|
|
46
|
+
invalid: invalidHistoryFilter,
|
|
47
|
+
validate: (field, operator, values, recovered) => {
|
|
48
|
+
const ordered = field === "created" || field === "updated";
|
|
49
|
+
const text = field === "contract" || field === "akuma" || field === "role" || field === "outcome";
|
|
50
|
+
if ((ordered && operator !== "=" && operator !== "!=" && operator !== "<" && operator !== "<=" && operator !== ">" && operator !== ">=")
|
|
51
|
+
|| (text && operator !== "=" && operator !== "!=" && operator !== "~")) {
|
|
52
|
+
invalidHistoryFilter(`field '${field}' does not support operator '${operator}'`, recovered);
|
|
53
|
+
}
|
|
54
|
+
if (field === "role" && values.some((value) => !HISTORY_ROLES.has(value))) {
|
|
55
|
+
invalidHistoryFilter(`unknown role '${values.find((value) => !HISTORY_ROLES.has(value))}'`, recovered);
|
|
56
|
+
}
|
|
57
|
+
if (field === "outcome" && values.some((value) => !HISTORY_OUTCOMES.has(value))) {
|
|
58
|
+
invalidHistoryFilter(`unknown outcome '${values.find((value) => !HISTORY_OUTCOMES.has(value))}'`, recovered);
|
|
59
|
+
}
|
|
60
|
+
if (ordered)
|
|
61
|
+
values.forEach((value) => parseTimestamp(value, 0, recovered));
|
|
62
|
+
},
|
|
63
|
+
});
|
|
64
|
+
}
|
|
65
|
+
export function parseProjectionHistorySort(value) {
|
|
66
|
+
if (value === undefined)
|
|
67
|
+
return undefined;
|
|
68
|
+
const keys = value.split(",");
|
|
69
|
+
if (keys.some((key) => !["created", "updated", "outcome", "akuma"].includes(key))) {
|
|
70
|
+
throw new FlowError("INVALID_TARGET", `invalid history sort key '${keys.find((key) => !["created", "updated", "outcome", "akuma"].includes(key))}'; expected created, updated, outcome, or akuma`);
|
|
71
|
+
}
|
|
72
|
+
if (new Set(keys).size !== keys.length)
|
|
73
|
+
throw new FlowError("INVALID_TARGET", "history sort keys must not repeat");
|
|
74
|
+
return keys;
|
|
75
|
+
}
|
|
76
|
+
function compare(left, right, operator) {
|
|
77
|
+
if (operator === "=")
|
|
78
|
+
return left === right;
|
|
79
|
+
if (operator === "!=")
|
|
80
|
+
return left !== right;
|
|
81
|
+
if (operator === "<")
|
|
82
|
+
return left < right;
|
|
83
|
+
if (operator === "<=")
|
|
84
|
+
return left <= right;
|
|
85
|
+
if (operator === ">")
|
|
86
|
+
return left > right;
|
|
87
|
+
if (operator === ">=")
|
|
88
|
+
return left >= right;
|
|
89
|
+
return false;
|
|
90
|
+
}
|
|
91
|
+
function matches(run, filter, nowMs) {
|
|
92
|
+
return filter.values.some((value) => {
|
|
93
|
+
const text = filter.field === "contract" ? run.contractId ?? "none"
|
|
94
|
+
: filter.field === "akuma" ? run.akuma ?? "none"
|
|
95
|
+
: filter.field === "role" ? run.role
|
|
96
|
+
: filter.field === "outcome" ? run.outcome
|
|
97
|
+
: undefined;
|
|
98
|
+
if (text !== undefined)
|
|
99
|
+
return filter.operator === "~" ? text.toLowerCase().includes(value.toLowerCase()) : compare(text, value, filter.operator);
|
|
100
|
+
const stamp = filter.field === "created" ? run.mintedAt : run.activityAt ?? run.terminalAt ?? run.mintedAt;
|
|
101
|
+
return stamp === null ? filter.operator === "!=" : compare(Date.parse(stamp), parseTimestamp(value, nowMs, [filter]), filter.operator);
|
|
102
|
+
});
|
|
103
|
+
}
|
|
104
|
+
function listDirectories(directory) {
|
|
105
|
+
try {
|
|
106
|
+
if (!fs.lstatSync(directory).isDirectory())
|
|
107
|
+
return null;
|
|
108
|
+
return fs.readdirSync(directory).filter((name) => !name.startsWith(".")).sort();
|
|
109
|
+
}
|
|
110
|
+
catch (error) {
|
|
111
|
+
if (isErrnoException(error) && error.code === "ENOENT")
|
|
112
|
+
return null;
|
|
113
|
+
throw error;
|
|
114
|
+
}
|
|
115
|
+
}
|
|
116
|
+
function outcomeFromLife(phase) {
|
|
117
|
+
return phase === "active" || phase === "quiescent" ? "out" : phase;
|
|
118
|
+
}
|
|
119
|
+
function roleFromRecords(records) {
|
|
120
|
+
const inputs = records?.find((record) => record.kind === "launch")?.facts.inputs;
|
|
121
|
+
return inputs && typeof inputs === "object" && !Array.isArray(inputs) && typeof inputs.sourceArtifactId === "string"
|
|
122
|
+
? "revive"
|
|
123
|
+
: records?.some((record) => record.kind === "launch") ? "call" : "unknown";
|
|
124
|
+
}
|
|
125
|
+
async function artifactFromVerdict(cwd, artifactId, executionId, index) {
|
|
126
|
+
if (artifactId === undefined)
|
|
127
|
+
return null;
|
|
128
|
+
if (typeof artifactId !== "string" || !isResponseArtifactId(artifactId)) {
|
|
129
|
+
return { id: null, path: null, retention: "incompatible", executionId };
|
|
130
|
+
}
|
|
131
|
+
const indexed = index?.lookup(artifactId);
|
|
132
|
+
if (indexed) {
|
|
133
|
+
return indexed.state === "available"
|
|
134
|
+
? { id: artifactId, path: indexed.artifact.absolutePath, retention: "available", executionId }
|
|
135
|
+
: { id: artifactId, path: null, retention: indexed.state, executionId };
|
|
136
|
+
}
|
|
137
|
+
if (index?.coverage === "complete")
|
|
138
|
+
return { id: artifactId, path: null, retention: "missing", executionId };
|
|
139
|
+
if (index)
|
|
140
|
+
return { id: artifactId, path: null, retention: "unavailable", executionId };
|
|
141
|
+
try {
|
|
142
|
+
const artifact = await locateResponseArtifact({
|
|
143
|
+
cwd,
|
|
144
|
+
artifactId,
|
|
145
|
+
attempt: { repo: { kind: "user" }, workdir: path.resolve(cwd), expected: "artifact", supplied: artifactId, candidates: [], inference: ["projection history artifact observation"] },
|
|
146
|
+
});
|
|
147
|
+
return { id: artifactId, path: artifact.absolutePath, retention: "available", executionId };
|
|
148
|
+
}
|
|
149
|
+
catch (error) {
|
|
150
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
151
|
+
return { id: artifactId, path: null, retention: /invalid|incompatible|malformed/i.test(message) ? "incompatible" : "missing", executionId };
|
|
152
|
+
}
|
|
153
|
+
}
|
|
154
|
+
function generationExecutionIds(records) {
|
|
155
|
+
return records?.filter((record) => record.kind === "launch").map((record) => record.executionId) ?? [];
|
|
156
|
+
}
|
|
157
|
+
function namespaceGenerationEvents(events, executionId, startOrder) {
|
|
158
|
+
return events.map((event, index) => {
|
|
159
|
+
const identity = "id" in event && typeof event.id === "string"
|
|
160
|
+
? { id: `${executionId}:${event.id}` }
|
|
161
|
+
: {};
|
|
162
|
+
return { ...event, ...identity, seq: startOrder + index, historyTurnKey: `${executionId}:${event.turn}`, historyExecutionId: executionId };
|
|
163
|
+
});
|
|
164
|
+
}
|
|
165
|
+
function readHistoryEvents(directory, records) {
|
|
166
|
+
const executionIds = generationExecutionIds(records);
|
|
167
|
+
if (executionIds.length === 0) {
|
|
168
|
+
const selected = selectValidStoredAgentEvents(readProjectionEvents(directory));
|
|
169
|
+
return {
|
|
170
|
+
events: selected.events.map((event, index) => ({ ...event, seq: index })),
|
|
171
|
+
malformed: selected.malformed,
|
|
172
|
+
retention: selected.malformed > 0 ? "incompatible" : undefined,
|
|
173
|
+
};
|
|
174
|
+
}
|
|
175
|
+
const events = [];
|
|
176
|
+
let malformed = 0;
|
|
177
|
+
let unavailable = false;
|
|
178
|
+
for (const executionId of executionIds) {
|
|
179
|
+
const ledger = readGenerationEventsWithRetention(directory, executionId);
|
|
180
|
+
unavailable ||= ledger.retention === "unavailable";
|
|
181
|
+
const selected = selectValidStoredAgentEvents(ledger.events);
|
|
182
|
+
malformed += selected.malformed;
|
|
183
|
+
events.push(...namespaceGenerationEvents(selected.events, executionId, events.length));
|
|
184
|
+
}
|
|
185
|
+
return {
|
|
186
|
+
events,
|
|
187
|
+
malformed,
|
|
188
|
+
retention: malformed > 0 ? "incompatible" : unavailable ? "unavailable" : undefined,
|
|
189
|
+
};
|
|
190
|
+
}
|
|
191
|
+
function telemetryFromActivity(activity) {
|
|
192
|
+
if (!activity)
|
|
193
|
+
return null;
|
|
194
|
+
let inputTokens = 0;
|
|
195
|
+
let outputTokens = 0;
|
|
196
|
+
let cachedInputTokens = 0;
|
|
197
|
+
let costUsd = 0;
|
|
198
|
+
let hasUsage = false;
|
|
199
|
+
let hasCachedInputTokens = false;
|
|
200
|
+
let hasCostUsd = false;
|
|
201
|
+
for (const turn of activity.ledgerTurns) {
|
|
202
|
+
if (turn.inputTokens !== undefined) {
|
|
203
|
+
inputTokens += turn.inputTokens;
|
|
204
|
+
hasUsage = true;
|
|
205
|
+
}
|
|
206
|
+
if (turn.outputTokens !== undefined) {
|
|
207
|
+
outputTokens += turn.outputTokens;
|
|
208
|
+
hasUsage = true;
|
|
209
|
+
}
|
|
210
|
+
if (turn.cachedInputTokens !== undefined) {
|
|
211
|
+
cachedInputTokens += turn.cachedInputTokens;
|
|
212
|
+
hasCachedInputTokens = true;
|
|
213
|
+
}
|
|
214
|
+
if (turn.costUsd !== undefined) {
|
|
215
|
+
costUsd += turn.costUsd;
|
|
216
|
+
hasCostUsd = true;
|
|
217
|
+
}
|
|
218
|
+
}
|
|
219
|
+
return hasUsage || hasCachedInputTokens || hasCostUsd
|
|
220
|
+
? { inputTokens, outputTokens, ...(hasCachedInputTokens ? { cachedInputTokens } : {}), ...(hasCostUsd ? { costUsd } : {}) }
|
|
221
|
+
: null;
|
|
222
|
+
}
|
|
223
|
+
async function artifactsFromGenerations(cwd, records, artifactIndex) {
|
|
224
|
+
const artifacts = [];
|
|
225
|
+
for (const record of records ?? []) {
|
|
226
|
+
if (record.kind !== "verdict" || record.facts.artifactId === undefined)
|
|
227
|
+
continue;
|
|
228
|
+
const artifact = await artifactFromVerdict(cwd, record.facts.artifactId, record.executionId, artifactIndex);
|
|
229
|
+
if (artifact)
|
|
230
|
+
artifacts.push(artifact);
|
|
231
|
+
}
|
|
232
|
+
return artifacts;
|
|
233
|
+
}
|
|
234
|
+
async function readRun(artifactRoot, directory, id, alias, artifactIndex, addressedArtifactId) {
|
|
235
|
+
const diagnostics = [];
|
|
236
|
+
const snapshot = observeProjectionLifeSnapshot(directory, {
|
|
237
|
+
nowMs: Date.now(),
|
|
238
|
+
startupTimeoutMs: PROJECTION_ADOPTION_TIMEOUT_MS,
|
|
239
|
+
});
|
|
240
|
+
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 };
|
|
242
|
+
}
|
|
243
|
+
let identity;
|
|
244
|
+
try {
|
|
245
|
+
identity = readProjectionIdentity(directory);
|
|
246
|
+
}
|
|
247
|
+
catch (error) {
|
|
248
|
+
diagnostics.push(`identity unavailable: ${error instanceof Error ? error.message : String(error)}`);
|
|
249
|
+
}
|
|
250
|
+
const current = snapshot.records ? selectCurrentGeneration(snapshot.records) : undefined;
|
|
251
|
+
let activity = null;
|
|
252
|
+
let activityRetention = "complete";
|
|
253
|
+
try {
|
|
254
|
+
const historyEvents = readHistoryEvents(directory, snapshot.records);
|
|
255
|
+
if (historyEvents.malformed > 0)
|
|
256
|
+
diagnostics.push(`${historyEvents.malformed} malformed activity event${historyEvents.malformed === 1 ? "" : "s"} omitted`);
|
|
257
|
+
activity = historyEvents.events.length > 0
|
|
258
|
+
? foldPublicProjectionActivity(historyEvents.events, historyEvents.retention === "incompatible" ? "incompatible" : historyEvents.retention === "unavailable" ? "unavailable" : undefined)
|
|
259
|
+
: null;
|
|
260
|
+
activityRetention = activity?.retention.state ?? historyEvents.retention ?? "complete";
|
|
261
|
+
}
|
|
262
|
+
catch (error) {
|
|
263
|
+
activityRetention = "unavailable";
|
|
264
|
+
diagnostics.push(`activity unavailable: ${error instanceof Error ? error.message : String(error)}`);
|
|
265
|
+
}
|
|
266
|
+
const terminalAt = extractTerminalTimestamp(current?.verdict?.facts) ?? null;
|
|
267
|
+
const activityAt = activity?.retainedTimeline.at(-1)?.at ?? null;
|
|
268
|
+
const artifacts = await artifactsFromGenerations(artifactRoot, snapshot.records, artifactIndex);
|
|
269
|
+
const artifact = addressedArtifactId === undefined
|
|
270
|
+
? current ? artifacts.find((candidate) => candidate.executionId === current.launch.executionId) ?? null : null
|
|
271
|
+
: artifacts.find((candidate) => candidate.id === addressedArtifactId) ?? null;
|
|
272
|
+
return {
|
|
273
|
+
id,
|
|
274
|
+
...(alias ? { alias } : {}),
|
|
275
|
+
akuma: identity?.akuma ?? null,
|
|
276
|
+
provider: identity?.provider ?? null,
|
|
277
|
+
pid: identity?.pid ?? null,
|
|
278
|
+
role: roleFromRecords(snapshot.records),
|
|
279
|
+
contractId: identity?.binding.kind === "commission" ? identity.binding.commissionId : null,
|
|
280
|
+
outcome: outcomeFromLife(snapshot.life.phase),
|
|
281
|
+
mintedAt: identity?.mintedAt ?? null,
|
|
282
|
+
terminalAt,
|
|
283
|
+
generation: current && typeof current.launch.facts.createdAt === "string"
|
|
284
|
+
? {
|
|
285
|
+
executionId: current.launch.executionId,
|
|
286
|
+
createdAt: current.launch.facts.createdAt,
|
|
287
|
+
adoptedAt: typeof current.adoption?.facts.adoptedAt === "string" ? current.adoption.facts.adoptedAt : null,
|
|
288
|
+
}
|
|
289
|
+
: null,
|
|
290
|
+
activityAt,
|
|
291
|
+
artifact,
|
|
292
|
+
artifacts,
|
|
293
|
+
activityRetention,
|
|
294
|
+
activityCount: activity?.retention.retainedCount ?? 0,
|
|
295
|
+
activityRange: activity?.retention.range ?? null,
|
|
296
|
+
telemetry: telemetryFromActivity(activity),
|
|
297
|
+
consequences: null,
|
|
298
|
+
diagnostics,
|
|
299
|
+
activity,
|
|
300
|
+
};
|
|
301
|
+
}
|
|
302
|
+
async function consequencesForContracts(cwd, contractIds) {
|
|
303
|
+
return readContractHistoryConsequences(cwd, contractIds);
|
|
304
|
+
}
|
|
305
|
+
function withLifecycleConsequences(run, consequences) {
|
|
306
|
+
return run.contractId === null ? run : { ...run, consequences: consequences.get(run.contractId) ?? unavailableContractHistoryConsequences("repository-unavailable") };
|
|
307
|
+
}
|
|
308
|
+
async function aliasesFor(cwd, root) {
|
|
309
|
+
const result = new Map();
|
|
310
|
+
const aliasRoot = path.join(path.dirname(path.dirname(root)), ".keiyaku", "projection-alias");
|
|
311
|
+
for (const alias of listDirectories(aliasRoot) ?? []) {
|
|
312
|
+
try {
|
|
313
|
+
const id = await readProjectionAlias(cwd, alias);
|
|
314
|
+
if (id)
|
|
315
|
+
result.set(id, alias);
|
|
316
|
+
}
|
|
317
|
+
catch { /* malformed alias has no trustworthy history coordinate */ }
|
|
318
|
+
}
|
|
319
|
+
return result;
|
|
320
|
+
}
|
|
321
|
+
export async function readProjectionHistory(cwd, options = {}) {
|
|
322
|
+
const root = await projectionRoot(cwd);
|
|
323
|
+
const akumas = listDirectories(root);
|
|
324
|
+
if (!akumas)
|
|
325
|
+
return [];
|
|
326
|
+
const aliases = await aliasesFor(cwd, root);
|
|
327
|
+
const artifactRoot = await projectionCoordinateRoot(cwd);
|
|
328
|
+
const artifactIndex = await indexResponseArtifacts({
|
|
329
|
+
cwd: artifactRoot,
|
|
330
|
+
...(options.readArtifactDirectory ? { readDirectory: options.readArtifactDirectory } : {}),
|
|
331
|
+
});
|
|
332
|
+
const discovered = [];
|
|
333
|
+
for (const akuma of akumas) {
|
|
334
|
+
for (const hex of listDirectories(path.join(root, akuma)) ?? []) {
|
|
335
|
+
const id = `${akuma}/${hex}`;
|
|
336
|
+
if (!isProjectionId(id))
|
|
337
|
+
continue;
|
|
338
|
+
discovered.push(await readRun(artifactRoot, path.join(root, akuma, hex), id, aliases.get(id), artifactIndex));
|
|
339
|
+
}
|
|
340
|
+
}
|
|
341
|
+
const consequences = await consequencesForContracts(cwd, discovered.flatMap((run) => run.contractId === null ? [] : [run.contractId]));
|
|
342
|
+
return discovered.map(({ activity: _activity, ...run }) => withLifecycleConsequences(run, consequences));
|
|
343
|
+
}
|
|
344
|
+
export async function readExactProjectionHistory(cwd, id) {
|
|
345
|
+
if (!isProjectionId(id))
|
|
346
|
+
return null;
|
|
347
|
+
const directory = await projectionDir(cwd, id);
|
|
348
|
+
const root = await projectionRoot(cwd);
|
|
349
|
+
if (!listDirectories(directory))
|
|
350
|
+
return null;
|
|
351
|
+
const artifactRoot = await projectionCoordinateRoot(cwd);
|
|
352
|
+
const artifactIndex = await indexResponseArtifacts({ cwd: artifactRoot });
|
|
353
|
+
const run = await readRun(artifactRoot, directory, id, (await aliasesFor(cwd, root)).get(id), artifactIndex);
|
|
354
|
+
return withLifecycleConsequences(run, await consequencesForContracts(cwd, run.contractId === null ? [] : [run.contractId]));
|
|
355
|
+
}
|
|
356
|
+
function directoryForId(root, id) {
|
|
357
|
+
const [akuma, hex] = id.split("/");
|
|
358
|
+
return path.join(root, akuma, hex);
|
|
359
|
+
}
|
|
360
|
+
function projectionHistoryDirectories(root, preferredAkuma) {
|
|
361
|
+
const directories = (listDirectories(root) ?? []).flatMap((akuma) => (listDirectories(path.join(root, akuma)) ?? []).map((hex) => path.join(root, akuma, hex)));
|
|
362
|
+
if (preferredAkuma === undefined)
|
|
363
|
+
return directories;
|
|
364
|
+
return directories.sort((left, right) => {
|
|
365
|
+
const leftPreferred = path.basename(path.dirname(left)) === preferredAkuma;
|
|
366
|
+
const rightPreferred = path.basename(path.dirname(right)) === preferredAkuma;
|
|
367
|
+
return leftPreferred === rightPreferred ? left.localeCompare(right) : leftPreferred ? -1 : 1;
|
|
368
|
+
});
|
|
369
|
+
}
|
|
370
|
+
/**
|
|
371
|
+
* One query-local relationship index reads every published heart through its
|
|
372
|
+
* public snapshot. Response metadata may prioritize a likely Akuma, but it
|
|
373
|
+
* never excludes another heart from ambiguity or uniqueness truth.
|
|
374
|
+
*/
|
|
375
|
+
async function retainedArtifactRelationships(root, artifactId, preferredAkuma) {
|
|
376
|
+
const directories = projectionHistoryDirectories(root, preferredAkuma);
|
|
377
|
+
const matches = [];
|
|
378
|
+
const batchSize = 32;
|
|
379
|
+
for (let start = 0; start < directories.length; start += batchSize) {
|
|
380
|
+
const batch = directories.slice(start, start + batchSize);
|
|
381
|
+
const rows = await Promise.all(batch.map(async (directory) => {
|
|
382
|
+
const id = `${path.basename(path.dirname(directory))}/${path.basename(directory)}`;
|
|
383
|
+
if (!isProjectionId(id))
|
|
384
|
+
return [];
|
|
385
|
+
const snapshot = observeProjectionLifeSnapshot(directory, {
|
|
386
|
+
nowMs: Date.now(),
|
|
387
|
+
startupTimeoutMs: PROJECTION_ADOPTION_TIMEOUT_MS,
|
|
388
|
+
});
|
|
389
|
+
if (snapshot.compatibility === "incompatible" || !snapshot.records)
|
|
390
|
+
return [];
|
|
391
|
+
return snapshot.records
|
|
392
|
+
.filter((record) => record.kind === "verdict" && record.facts.artifactId === artifactId)
|
|
393
|
+
.map((record) => ({ id, executionId: record.executionId }));
|
|
394
|
+
}));
|
|
395
|
+
matches.push(...rows.flat());
|
|
396
|
+
}
|
|
397
|
+
return matches;
|
|
398
|
+
}
|
|
399
|
+
async function resolveProjectionHistoryAddressAtRoot(cwd, root, address, artifactAkuma) {
|
|
400
|
+
if (isProjectionId(address))
|
|
401
|
+
return listDirectories(directoryForId(root, address)) ? { id: address } : null;
|
|
402
|
+
if (isResponseArtifactId(address)) {
|
|
403
|
+
const matches = await retainedArtifactRelationships(root, address, artifactAkuma);
|
|
404
|
+
if (matches.length > 1) {
|
|
405
|
+
throw new FlowError("INVALID_TARGET", `response artifact '${address}' has ambiguous retained projection relationships: ${matches.map((match) => `${match.id}@${match.executionId}`).join(", ")}`);
|
|
406
|
+
}
|
|
407
|
+
return matches[0] ? { id: matches[0].id, addressedArtifactId: address } : null;
|
|
408
|
+
}
|
|
409
|
+
const id = await readProjectionAlias(cwd, address);
|
|
410
|
+
if (!id)
|
|
411
|
+
return null;
|
|
412
|
+
if (!listDirectories(directoryForId(root, id))) {
|
|
413
|
+
throw new FlowError("PROJECTION_HISTORY_NOT_FOUND", `projection alias ${address} points to missing retained projection ${id}`);
|
|
414
|
+
}
|
|
415
|
+
return { id };
|
|
416
|
+
}
|
|
417
|
+
/** Resolve and inspect one coordinate with one root/alias read and one query-local artifact index. */
|
|
418
|
+
export async function readProjectionHistoryAddress(cwd, address, options = {}) {
|
|
419
|
+
const root = await projectionRoot(cwd);
|
|
420
|
+
const artifactRoot = await projectionCoordinateRoot(cwd);
|
|
421
|
+
const artifactIndex = isResponseArtifactId(address)
|
|
422
|
+
? await indexResponseArtifacts({
|
|
423
|
+
cwd: artifactRoot,
|
|
424
|
+
...(options.readArtifactDirectory ? { readDirectory: options.readArtifactDirectory } : {}),
|
|
425
|
+
})
|
|
426
|
+
: undefined;
|
|
427
|
+
const indexedArtifact = artifactIndex?.lookup(address);
|
|
428
|
+
const artifactAkuma = indexedArtifact?.state === "available"
|
|
429
|
+
? indexedArtifact.artifact.subagentName ?? indexedArtifact.artifact.launchSnapshot?.akuma.name
|
|
430
|
+
: undefined;
|
|
431
|
+
const resolved = await resolveProjectionHistoryAddressAtRoot(cwd, root, address, artifactAkuma);
|
|
432
|
+
if (!resolved)
|
|
433
|
+
return null;
|
|
434
|
+
const directory = directoryForId(root, resolved.id);
|
|
435
|
+
if (!listDirectories(directory))
|
|
436
|
+
return null;
|
|
437
|
+
const resolvedArtifactIndex = artifactIndex ?? await indexResponseArtifacts({
|
|
438
|
+
cwd: artifactRoot,
|
|
439
|
+
...(options.readArtifactDirectory ? { readDirectory: options.readArtifactDirectory } : {}),
|
|
440
|
+
});
|
|
441
|
+
const run = await readRun(artifactRoot, directory, resolved.id, (await aliasesFor(cwd, root)).get(resolved.id), resolvedArtifactIndex, resolved.addressedArtifactId);
|
|
442
|
+
return withLifecycleConsequences(run, await consequencesForContracts(cwd, run.contractId === null ? [] : [run.contractId]));
|
|
443
|
+
}
|
|
444
|
+
export async function resolveProjectionHistoryAddress(cwd, address) {
|
|
445
|
+
const root = await projectionRoot(cwd);
|
|
446
|
+
return (await resolveProjectionHistoryAddressAtRoot(cwd, root, address))?.id ?? null;
|
|
447
|
+
}
|
|
448
|
+
export function selectProjectionHistory(runs, options = {}) {
|
|
449
|
+
const filters = (options.filters ?? []).map(parseProjectionHistoryFilter);
|
|
450
|
+
const keys = parseProjectionHistorySort(options.sort);
|
|
451
|
+
const nowMs = options.nowMs ?? Date.now();
|
|
452
|
+
const stamp = (run, key) => key === "created" ? run.mintedAt ?? "" : run.activityAt ?? run.terminalAt ?? run.mintedAt ?? "";
|
|
453
|
+
return [...runs]
|
|
454
|
+
.filter((run) => filters.every((expression) => expression.some((filter) => matches(run, filter, nowMs))))
|
|
455
|
+
.sort((left, right) => {
|
|
456
|
+
const compareKey = (key) => {
|
|
457
|
+
const a = key === "akuma" ? left.akuma ?? "" : key === "outcome" ? left.outcome : stamp(left, key);
|
|
458
|
+
const b = key === "akuma" ? right.akuma ?? "" : key === "outcome" ? right.outcome : stamp(right, key);
|
|
459
|
+
return a < b ? -1 : a > b ? 1 : 0;
|
|
460
|
+
};
|
|
461
|
+
if (keys)
|
|
462
|
+
for (const key of keys) {
|
|
463
|
+
const result = compareKey(key);
|
|
464
|
+
if (result)
|
|
465
|
+
return result;
|
|
466
|
+
}
|
|
467
|
+
else {
|
|
468
|
+
const recent = stamp(right, "updated").localeCompare(stamp(left, "updated"));
|
|
469
|
+
if (recent)
|
|
470
|
+
return recent;
|
|
471
|
+
}
|
|
472
|
+
return left.id.localeCompare(right.id);
|
|
473
|
+
});
|
|
474
|
+
}
|
|
@@ -323,6 +323,18 @@ export function observeProjectionRunnerLock(projectionDirectory, platform = proc
|
|
|
323
323
|
};
|
|
324
324
|
}
|
|
325
325
|
const lockPath = projectionRunnerLockPath(projectionDirectory);
|
|
326
|
+
try {
|
|
327
|
+
if (!fs.lstatSync(lockPath).isFile()) {
|
|
328
|
+
return {
|
|
329
|
+
state: "unknown",
|
|
330
|
+
reason: "open-failed",
|
|
331
|
+
detail: "projection runner lock database is not a regular file",
|
|
332
|
+
};
|
|
333
|
+
}
|
|
334
|
+
}
|
|
335
|
+
catch (error) {
|
|
336
|
+
return { state: "unknown", reason: "open-failed", detail: errorDetail(error) };
|
|
337
|
+
}
|
|
326
338
|
let database;
|
|
327
339
|
try {
|
|
328
340
|
database = new DatabaseSync(lockPath);
|