@astrosheep/keiyaku 1.0.0 → 1.0.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +73 -56
- package/build/.tsbuildinfo +1 -1
- package/build/agents/call-terms_v2.js +103 -0
- package/build/agents/harness/activity-values.js +54 -0
- package/build/agents/harness/event-channel.js +55 -20
- package/build/agents/harness/events.js +47 -1
- package/build/agents/harness/execution-handle.js +292 -61
- package/build/agents/harness/index.js +103 -121
- package/build/agents/harness/pump.js +36 -47
- package/build/agents/harness/runtime.js +3 -0
- package/build/agents/launch-snapshot_v2.js +348 -0
- package/build/agents/opencode-sdk.js +42 -169
- package/build/agents/providers/claude-agent-sdk.js +240 -79
- package/build/agents/providers/codex-app-server.js +280 -72
- package/build/agents/providers/codex-sdk.js +159 -41
- package/build/agents/providers/event-type-tail.js +16 -0
- package/build/agents/providers/opencode-sdk.js +165 -59
- package/build/agents/providers/pi.js +254 -42
- package/build/agents/selector_v2.js +122 -0
- package/build/cli/commands/akuma.js +120 -42
- package/build/cli/completion.js +6 -7
- package/build/cli/flags.js +36 -10
- package/build/cli/help.js +42 -26
- package/build/cli/index.js +225 -93
- package/build/cli/parse.js +194 -75
- package/build/cli/projection-address.js +11 -0
- package/build/cli/render/address.js +88 -0
- package/build/cli/render/arc.js +10 -10
- package/build/cli/render/call.js +59 -0
- package/build/cli/render/compact-text.js +3 -0
- package/build/cli/render/errors.js +4 -2
- package/build/cli/render/format.js +6 -0
- package/build/cli/render/line-width.js +70 -0
- package/build/cli/render/misc.js +7 -4
- package/build/cli/render/petition.js +4 -3
- package/build/cli/render/projection-activity.js +201 -0
- package/build/cli/render/shared.js +46 -12
- package/build/cli/render/status.js +59 -10
- package/build/cli/render/tool-ledger-rollup.js +74 -0
- package/build/cli/render/tool-presentation.js +97 -0
- package/build/cli/render/wait.js +229 -0
- package/build/cli/skills-install.js +42 -24
- package/build/cli/subagent-guard.js +1 -1
- package/build/config/akuma-loader_v2.js +222 -0
- package/build/config/env-keys.js +0 -4
- package/build/config/env.js +0 -6
- package/build/config/settings.js +325 -12
- package/build/core/addressing.js +224 -0
- package/build/core/amend.js +30 -47
- package/build/core/arc.js +68 -60
- package/build/core/bind.js +105 -25
- package/build/core/call-persist.js +69 -0
- package/build/core/call.js +658 -0
- package/build/core/claim.js +72 -44
- package/build/core/command-io.js +19 -19
- package/build/core/context.js +11 -36
- package/build/core/draft.js +2 -3
- package/build/core/entry.js +101 -6
- package/build/core/execution-coordinate.js +62 -0
- package/build/core/execution-pact.js +141 -0
- package/build/core/forfeit.js +2 -3
- package/build/core/hints.js +10 -10
- package/build/core/ids.js +6 -0
- package/build/core/ledger.js +16 -1
- package/build/core/petition-claim-gates.js +14 -12
- package/build/core/petition-claim.js +1 -0
- package/build/core/petition-run.js +6 -2
- package/build/core/petition.js +63 -21
- package/build/core/places.js +106 -13
- package/build/core/projection/generation.js +412 -0
- package/build/core/projection/heart.js +316 -0
- package/build/core/projection/identity.js +81 -0
- package/build/core/projection/leash.js +87 -0
- package/build/core/projection/mint.js +154 -0
- package/build/core/projection-activity.js +219 -0
- package/build/core/projection-coordinate.js +35 -0
- package/build/core/projection-core.js +69 -463
- package/build/core/projection-generation-continuation.js +31 -0
- package/build/core/projection-generation-execution.js +148 -0
- package/build/core/projection-generation-identity.js +17 -0
- package/build/core/projection-generation-launcher.js +90 -0
- package/build/core/projection-generation-process.js +43 -0
- package/build/core/projection-generation-runner.js +144 -0
- package/build/core/projection-generation-runtime.js +15 -0
- package/build/core/projection-generation-store.js +707 -0
- package/build/core/projection-identity.js +11 -0
- package/build/core/projection-life-observer.js +87 -0
- package/build/core/projection-life-protocol.js +95 -0
- package/build/core/projection-mint.js +96 -24
- package/build/core/projection-runner-lock.js +230 -0
- package/build/core/projection-status.js +207 -107
- package/build/core/projection-tells.js +401 -0
- package/build/core/projection-wait.js +155 -0
- package/build/core/projection-wake.js +109 -231
- package/build/core/queue_v2.js +342 -0
- package/build/core/registry.js +6 -3
- package/build/core/render.js +63 -1
- package/build/core/renew.js +80 -53
- package/build/core/scope.js +186 -137
- package/build/core/seal.js +16 -19
- package/build/core/status.js +88 -25
- package/build/core/stored-agent-event.js +101 -0
- package/build/core/transcripts.js +214 -228
- package/build/core/verdict.js +27 -15
- package/build/core/worktree-bootstrap.js +187 -0
- package/build/core/worktree-path.js +72 -18
- package/build/flow-error.js +4 -6
- package/build/generated/version.js +1 -1
- package/build/git/branches.js +6 -1
- package/build/index.js +8 -5
- package/build/keiyaku.js +1 -3
- package/package.json +6 -5
- package/skills/keiyaku/SKILL.md +6 -47
- package/skills/keiyaku-akuma/SKILL.md +75 -0
- package/build/agents/effective-policy.js +0 -32
- package/build/agents/index.js +0 -114
- package/build/agents/selector.js +0 -28
- package/build/cli/render/summon.js +0 -17
- package/build/cli/render/tell.js +0 -11
- package/build/config/akuma-loader.js +0 -268
- package/build/core/queue.js +0 -73
- package/build/core/summon-persist.js +0 -73
- package/build/core/summon.js +0 -377
|
@@ -0,0 +1,658 @@
|
|
|
1
|
+
import * as fs from "fs/promises";
|
|
2
|
+
import * as path from "path";
|
|
3
|
+
import { ACTOR_IDENTITY, BASE_RULES_FILE, KEIYAKU_FILE } from "../keiyaku.js";
|
|
4
|
+
import { isErrnoException } from "../errno.js";
|
|
5
|
+
import { FlowError, requireText } from "../flow-error.js";
|
|
6
|
+
import { isGitRepo } from "../git/branches.js";
|
|
7
|
+
import { createGit, wrapGitError } from "../git/core.js";
|
|
8
|
+
import { createAbortError } from "../agents/harness/runtime.js";
|
|
9
|
+
import { selectSubagentV2 } from "../agents/selector_v2.js";
|
|
10
|
+
import { buildCallTermsV2 } from "../agents/call-terms_v2.js";
|
|
11
|
+
import { applySnapshotEffort, resolveAkumaLaunchSnapshot, } from "../agents/launch-snapshot_v2.js";
|
|
12
|
+
import { restoreOutcome } from "../agents/harness/index.js";
|
|
13
|
+
import { mintProjection, mintRepositoryProjection } from "./projection-mint.js";
|
|
14
|
+
import { waitForProjection } from "./projection-wait.js";
|
|
15
|
+
import { requireResumableArtifact } from "./transcripts.js";
|
|
16
|
+
import { appendDebugBlock } from "../telemetry/debug-log.js";
|
|
17
|
+
import { logInfo } from "../telemetry/logger.js";
|
|
18
|
+
import { SubagentFailure } from "../agents/types.js";
|
|
19
|
+
import { AddressResolutionError, buildResolutionAttempt, buildUncommissionedAddress, resolveContractAddress, resolveUncommissionedRepositoryAddress, withResolvedAddress, } from "./addressing.js";
|
|
20
|
+
import { readLedger } from "./ledger.js";
|
|
21
|
+
import { findOpenArcView } from "./arc.js";
|
|
22
|
+
import { deriveContractState, isTerminalState } from "./status.js";
|
|
23
|
+
import { contractPresentedByWorktree, contractWorktreePathFromBind, stableRepoRoot } from "./worktree-path.js";
|
|
24
|
+
import { createProjectionExecutionId } from "./projection-generation-identity.js";
|
|
25
|
+
import { openProjectionGenerationStore, openProjectionGenerationStoreReadOnly, } from "./projection-generation-store.js";
|
|
26
|
+
import { PROJECTION_ADOPTION_TIMEOUT_MS, startProjectionGeneration } from "./projection-generation-launcher.js";
|
|
27
|
+
import { observeProjectionLife } from "./projection-life-observer.js";
|
|
28
|
+
export function buildCallPrompt(input) {
|
|
29
|
+
const references = [...new Set((input.referenceFiles ?? []).map((file) => file.trim()).filter(Boolean))];
|
|
30
|
+
if (!input.arc) {
|
|
31
|
+
return [input.task, input.context?.trim() ?? "", references.length > 0 ? `Reference files:\n${references.map((file) => `- ${file}`).join("\n")}` : ""]
|
|
32
|
+
.filter(Boolean)
|
|
33
|
+
.join("\n\n");
|
|
34
|
+
}
|
|
35
|
+
const arc = input.arc;
|
|
36
|
+
return [
|
|
37
|
+
`You are a called ${ACTOR_IDENTITY} working inside open arc a${arc.arc}.`,
|
|
38
|
+
"Stay within the arc scope and do the requested work directly.",
|
|
39
|
+
`Title: ${arc.title}`,
|
|
40
|
+
`Objective: ${arc.objective}`,
|
|
41
|
+
`Brief:\n${arc.brief}`,
|
|
42
|
+
"",
|
|
43
|
+
`Task:\n${input.task}`,
|
|
44
|
+
`Context:\n${input.context?.trim() || "(none provided)"}`,
|
|
45
|
+
references.length > 0 ? `Reference files:\n${references.map((file) => `- ${file}`).join("\n")}` : "",
|
|
46
|
+
"Provide the final result for the caller/main agent.",
|
|
47
|
+
].filter(Boolean).join("\n\n");
|
|
48
|
+
}
|
|
49
|
+
function renderSubagentWarning(warning) {
|
|
50
|
+
const modelTransition = warning.originalModel && warning.fallbackModel
|
|
51
|
+
? `${warning.originalModel} -> ${warning.fallbackModel}`
|
|
52
|
+
: warning.originalModel ?? warning.fallbackModel;
|
|
53
|
+
const details = [
|
|
54
|
+
modelTransition,
|
|
55
|
+
warning.direction ? `direction=${warning.direction}` : undefined,
|
|
56
|
+
warning.requestId !== undefined ? `requestId=${warning.requestId ?? "null"}` : undefined,
|
|
57
|
+
warning.signerModel ? `signerModel=${warning.signerModel}` : undefined,
|
|
58
|
+
].filter((value) => Boolean(value));
|
|
59
|
+
const summary = warning.reason === "refusal" ? "Provider warning: model fallback." : "Provider warning: overload fallback.";
|
|
60
|
+
return details.length > 0 ? `${summary} ${details.join(", ")}` : summary;
|
|
61
|
+
}
|
|
62
|
+
async function fileExists(cwd, relativePath) {
|
|
63
|
+
try {
|
|
64
|
+
await fs.access(path.join(cwd, relativePath));
|
|
65
|
+
return true;
|
|
66
|
+
}
|
|
67
|
+
catch (error) {
|
|
68
|
+
if (isErrnoException(error) && error.code === "ENOENT") {
|
|
69
|
+
return false;
|
|
70
|
+
}
|
|
71
|
+
throw error;
|
|
72
|
+
}
|
|
73
|
+
}
|
|
74
|
+
async function collectCallReferenceFiles(cwd, input) {
|
|
75
|
+
const candidates = [
|
|
76
|
+
...(input.activeKeiyaku ? [KEIYAKU_FILE] : []),
|
|
77
|
+
BASE_RULES_FILE,
|
|
78
|
+
];
|
|
79
|
+
const existing = await Promise.all(candidates.map(async (file) => ({ file, exists: await fileExists(cwd, file) })));
|
|
80
|
+
return existing.filter((entry) => entry.exists).map((entry) => entry.file);
|
|
81
|
+
}
|
|
82
|
+
async function findWorktreePathForContract(cwd, contractId) {
|
|
83
|
+
const git = createGit(cwd);
|
|
84
|
+
let output;
|
|
85
|
+
try {
|
|
86
|
+
output = await git.raw(["worktree", "list", "--porcelain"]);
|
|
87
|
+
}
|
|
88
|
+
catch (error) {
|
|
89
|
+
throw wrapGitError("worktree list --porcelain", error, cwd);
|
|
90
|
+
}
|
|
91
|
+
let currentPath;
|
|
92
|
+
for (const line of output.split(/\r?\n/)) {
|
|
93
|
+
if (line.startsWith("worktree ")) {
|
|
94
|
+
currentPath = line.slice("worktree ".length);
|
|
95
|
+
continue;
|
|
96
|
+
}
|
|
97
|
+
if (line === `branch refs/heads/keiyaku/${contractId}`) {
|
|
98
|
+
return currentPath;
|
|
99
|
+
}
|
|
100
|
+
}
|
|
101
|
+
return undefined;
|
|
102
|
+
}
|
|
103
|
+
async function resolveWorkspaceCwd(cwd, contractId, bind) {
|
|
104
|
+
const repoRoot = cwd;
|
|
105
|
+
if (bind.data.workspace === "here")
|
|
106
|
+
return repoRoot;
|
|
107
|
+
const listedWorktreePath = await findWorktreePathForContract(cwd, contractId);
|
|
108
|
+
const worktreePath = listedWorktreePath ?? await contractWorktreePathFromBind(repoRoot, contractId, bind);
|
|
109
|
+
let stat;
|
|
110
|
+
try {
|
|
111
|
+
stat = await fs.stat(worktreePath);
|
|
112
|
+
}
|
|
113
|
+
catch (error) {
|
|
114
|
+
throw new FlowError("INTERNAL_STATE", `contract ${contractId} worktree not found at ${worktreePath}`, { cause: error });
|
|
115
|
+
}
|
|
116
|
+
if (!stat.isDirectory()) {
|
|
117
|
+
throw new FlowError("INTERNAL_STATE", `contract ${contractId} worktree path is not a directory: ${worktreePath}`);
|
|
118
|
+
}
|
|
119
|
+
return worktreePath;
|
|
120
|
+
}
|
|
121
|
+
async function readCallKeiyakuContext(executionCwd, input) {
|
|
122
|
+
const ledgerCandidate = input.repoCwd ?? executionCwd;
|
|
123
|
+
const isRepo = await isGitRepo(ledgerCandidate);
|
|
124
|
+
if (!isRepo) {
|
|
125
|
+
if (input.bare && input.contractId) {
|
|
126
|
+
const asTyped = input.contractAddressSource === "at-address"
|
|
127
|
+
? `@${input.contractId}`
|
|
128
|
+
: input.contractId;
|
|
129
|
+
throw new AddressResolutionError("EMPTY_PARAM", `commission address ${asTyped} cannot be combined with --bare`, buildResolutionAttempt({
|
|
130
|
+
repo: { kind: "user" },
|
|
131
|
+
workdir: executionCwd,
|
|
132
|
+
spelling: {
|
|
133
|
+
asTyped,
|
|
134
|
+
source: input.contractAddressSource ?? "explicit-flag",
|
|
135
|
+
},
|
|
136
|
+
}));
|
|
137
|
+
}
|
|
138
|
+
if (input.contractId) {
|
|
139
|
+
const asTyped = input.contractAddressSource === "at-address"
|
|
140
|
+
? `@${input.contractId}`
|
|
141
|
+
: input.contractId;
|
|
142
|
+
throw new AddressResolutionError("EMPTY_PARAM", `${asTyped} is a commission address, but ${ledgerCandidate} has no repository ledger; contract addressing requires a ledger`, buildResolutionAttempt({
|
|
143
|
+
repo: { kind: "user" },
|
|
144
|
+
workdir: executionCwd,
|
|
145
|
+
spelling: {
|
|
146
|
+
asTyped,
|
|
147
|
+
source: input.contractAddressSource ?? "explicit-flag",
|
|
148
|
+
},
|
|
149
|
+
}));
|
|
150
|
+
}
|
|
151
|
+
return {
|
|
152
|
+
activeKeiyaku: false,
|
|
153
|
+
effectiveCwd: executionCwd,
|
|
154
|
+
historyCwd: executionCwd,
|
|
155
|
+
profileCwd: executionCwd,
|
|
156
|
+
projectionCwd: executionCwd,
|
|
157
|
+
address: buildUncommissionedAddress({
|
|
158
|
+
repo: { kind: "user" },
|
|
159
|
+
workdir: executionCwd,
|
|
160
|
+
binding: input.bare ? "bare" : "unbound",
|
|
161
|
+
}),
|
|
162
|
+
};
|
|
163
|
+
}
|
|
164
|
+
const ledgerCwd = await stableRepoRoot(ledgerCandidate);
|
|
165
|
+
if (input.reviveBinding) {
|
|
166
|
+
if (input.reviveBinding.kind === "commission") {
|
|
167
|
+
input.contractId = input.reviveBinding.commissionId;
|
|
168
|
+
input.contractAddressSource = "artifact-provenance";
|
|
169
|
+
}
|
|
170
|
+
else {
|
|
171
|
+
return {
|
|
172
|
+
activeKeiyaku: false,
|
|
173
|
+
effectiveCwd: executionCwd,
|
|
174
|
+
historyCwd: ledgerCwd,
|
|
175
|
+
profileCwd: ledgerCwd,
|
|
176
|
+
projectionCwd: ledgerCwd,
|
|
177
|
+
address: await resolveUncommissionedRepositoryAddress({
|
|
178
|
+
repoCwd: ledgerCwd,
|
|
179
|
+
workdir: executionCwd,
|
|
180
|
+
binding: input.reviveBinding.kind,
|
|
181
|
+
}),
|
|
182
|
+
};
|
|
183
|
+
}
|
|
184
|
+
}
|
|
185
|
+
if (input.bare && input.contractId) {
|
|
186
|
+
const repositoryAddress = await resolveUncommissionedRepositoryAddress({
|
|
187
|
+
repoCwd: ledgerCwd,
|
|
188
|
+
workdir: executionCwd,
|
|
189
|
+
binding: "bare",
|
|
190
|
+
});
|
|
191
|
+
const asTyped = input.contractAddressSource === "at-address"
|
|
192
|
+
? `@${input.contractId}`
|
|
193
|
+
: input.contractId;
|
|
194
|
+
throw new AddressResolutionError("EMPTY_PARAM", `commission address ${asTyped} cannot be combined with --bare`, buildResolutionAttempt({
|
|
195
|
+
repo: repositoryAddress.repo,
|
|
196
|
+
workdir: repositoryAddress.workdir,
|
|
197
|
+
spelling: {
|
|
198
|
+
asTyped,
|
|
199
|
+
source: input.contractAddressSource ?? "explicit-flag",
|
|
200
|
+
},
|
|
201
|
+
}));
|
|
202
|
+
}
|
|
203
|
+
if (input.bare) {
|
|
204
|
+
return {
|
|
205
|
+
activeKeiyaku: false,
|
|
206
|
+
effectiveCwd: executionCwd,
|
|
207
|
+
historyCwd: ledgerCwd,
|
|
208
|
+
profileCwd: ledgerCwd,
|
|
209
|
+
projectionCwd: ledgerCwd,
|
|
210
|
+
address: await resolveUncommissionedRepositoryAddress({
|
|
211
|
+
repoCwd: ledgerCwd,
|
|
212
|
+
workdir: executionCwd,
|
|
213
|
+
binding: "bare",
|
|
214
|
+
}),
|
|
215
|
+
};
|
|
216
|
+
}
|
|
217
|
+
let address;
|
|
218
|
+
if (input.contractId) {
|
|
219
|
+
address = await resolveContractAddress(ledgerCwd, input.contractId, input.contractAddressSource);
|
|
220
|
+
}
|
|
221
|
+
else if (await isGitRepo(executionCwd)) {
|
|
222
|
+
const executionRepo = await stableRepoRoot(executionCwd);
|
|
223
|
+
if (input.repoCwd && executionRepo !== ledgerCwd) {
|
|
224
|
+
const presented = await contractPresentedByWorktree(executionCwd);
|
|
225
|
+
if (presented) {
|
|
226
|
+
throw new FlowError("EMPTY_PARAM", `cross-repository contract-worktree addressing is pending: -C presents @${presented} from ${executionRepo}, but --repo selects ${ledgerCwd}`);
|
|
227
|
+
}
|
|
228
|
+
}
|
|
229
|
+
else {
|
|
230
|
+
const presented = await contractPresentedByWorktree(executionCwd);
|
|
231
|
+
if (presented) {
|
|
232
|
+
address = await resolveContractAddress(ledgerCwd, presented, "worktree-presented");
|
|
233
|
+
}
|
|
234
|
+
}
|
|
235
|
+
}
|
|
236
|
+
if (!address) {
|
|
237
|
+
try {
|
|
238
|
+
address = await resolveContractAddress(ledgerCwd);
|
|
239
|
+
}
|
|
240
|
+
catch (error) {
|
|
241
|
+
if (error instanceof AddressResolutionError
|
|
242
|
+
&& error.code === "EMPTY_PARAM"
|
|
243
|
+
&& error.attempt.candidates.length === 0) {
|
|
244
|
+
return {
|
|
245
|
+
activeKeiyaku: false,
|
|
246
|
+
effectiveCwd: executionCwd,
|
|
247
|
+
historyCwd: ledgerCwd,
|
|
248
|
+
profileCwd: ledgerCwd,
|
|
249
|
+
projectionCwd: ledgerCwd,
|
|
250
|
+
address: await resolveUncommissionedRepositoryAddress({
|
|
251
|
+
repoCwd: ledgerCwd,
|
|
252
|
+
workdir: executionCwd,
|
|
253
|
+
binding: "unbound",
|
|
254
|
+
}),
|
|
255
|
+
};
|
|
256
|
+
}
|
|
257
|
+
throw error;
|
|
258
|
+
}
|
|
259
|
+
}
|
|
260
|
+
return await withResolvedAddress(address, async () => {
|
|
261
|
+
const contractId = address.binding.commissionId;
|
|
262
|
+
const ledger = await readLedger(ledgerCwd, contractId);
|
|
263
|
+
if (!ledger) {
|
|
264
|
+
throw new FlowError("INTERNAL_STATE", `contract ${contractId} not found or not active`);
|
|
265
|
+
}
|
|
266
|
+
const state = deriveContractState(ledger.entries);
|
|
267
|
+
if (state.state === "corrupt") {
|
|
268
|
+
throw new FlowError("INTERNAL_STATE", `contract ${contractId} is corrupt at entry ${state.at}: ${state.reason}`);
|
|
269
|
+
}
|
|
270
|
+
if (isTerminalState(state)) {
|
|
271
|
+
throw new FlowError("EMPTY_PARAM", `commission ${contractId} is terminal and cannot launch an Akuma`);
|
|
272
|
+
}
|
|
273
|
+
const bind = ledger.entries.find((entry) => entry.kind === "bind");
|
|
274
|
+
if (!bind) {
|
|
275
|
+
throw new FlowError("INTERNAL_STATE", `contract ${contractId} active ledger is missing bind entry`);
|
|
276
|
+
}
|
|
277
|
+
const effectiveCwd = await resolveWorkspaceCwd(ledgerCwd, contractId, bind);
|
|
278
|
+
const executionAddress = { ...address, workdir: effectiveCwd };
|
|
279
|
+
const openArc = findOpenArcView(ledger.entries);
|
|
280
|
+
return {
|
|
281
|
+
activeKeiyaku: true,
|
|
282
|
+
effectiveCwd,
|
|
283
|
+
historyCwd: ledgerCwd,
|
|
284
|
+
profileCwd: effectiveCwd,
|
|
285
|
+
projectionCwd: ledgerCwd,
|
|
286
|
+
openArc: openArc ?? undefined,
|
|
287
|
+
address: executionAddress,
|
|
288
|
+
};
|
|
289
|
+
});
|
|
290
|
+
}
|
|
291
|
+
export async function execSubagent(agentName, prompt, cwd, options) {
|
|
292
|
+
const { outcome } = await executeSubagent(agentName, prompt, cwd, options);
|
|
293
|
+
return outcome;
|
|
294
|
+
}
|
|
295
|
+
function readCurrentGenerationOrThrow(projectionDirectory) {
|
|
296
|
+
const store = openProjectionGenerationStoreReadOnly(projectionDirectory);
|
|
297
|
+
try {
|
|
298
|
+
const current = store.readCurrentGeneration();
|
|
299
|
+
if (!current)
|
|
300
|
+
throw new FlowError("INTERNAL_STATE", "projection has no current generation");
|
|
301
|
+
return current;
|
|
302
|
+
}
|
|
303
|
+
finally {
|
|
304
|
+
store.close();
|
|
305
|
+
}
|
|
306
|
+
}
|
|
307
|
+
function outcomeFromGeneration(current) {
|
|
308
|
+
const verdict = current.verdict;
|
|
309
|
+
if (!verdict)
|
|
310
|
+
throw new FlowError("INTERNAL_STATE", "projection generation has no terminal verdict");
|
|
311
|
+
const facts = verdict.facts;
|
|
312
|
+
const finalMessage = typeof facts.finalMessage === "string"
|
|
313
|
+
? facts.finalMessage
|
|
314
|
+
: typeof facts.detail === "string"
|
|
315
|
+
? facts.detail
|
|
316
|
+
: "";
|
|
317
|
+
if (facts.state === "failed" && typeof facts.detail === "string") {
|
|
318
|
+
throw new FlowError("SUBAGENT_EXEC_ERROR", facts.detail);
|
|
319
|
+
}
|
|
320
|
+
const session = facts.session;
|
|
321
|
+
if (session === null || typeof session !== "object" || Array.isArray(session)) {
|
|
322
|
+
throw new FlowError("INTERNAL_STATE", "projection verdict is missing its session envelope");
|
|
323
|
+
}
|
|
324
|
+
const record = {
|
|
325
|
+
status: facts.state === "completed"
|
|
326
|
+
? "completed"
|
|
327
|
+
: facts.state === "dismissed"
|
|
328
|
+
? "dismissed"
|
|
329
|
+
: "failed",
|
|
330
|
+
finalMessage,
|
|
331
|
+
output: typeof facts.output === "string" ? facts.output : "",
|
|
332
|
+
diagnostics: typeof facts.diagnostics === "string" ? facts.diagnostics : "",
|
|
333
|
+
session: session,
|
|
334
|
+
warnings: Array.isArray(facts.warnings)
|
|
335
|
+
? facts.warnings
|
|
336
|
+
: [],
|
|
337
|
+
...(facts.error && typeof facts.error === "object" && !Array.isArray(facts.error)
|
|
338
|
+
? { error: facts.error }
|
|
339
|
+
: {}),
|
|
340
|
+
};
|
|
341
|
+
return restoreOutcome(record);
|
|
342
|
+
}
|
|
343
|
+
async function executeSubagent(agentName, prompt, cwd, options) {
|
|
344
|
+
const { projection: projectionCoordinate, launchSnapshot } = await buildCallConfig(agentName, prompt, cwd, options);
|
|
345
|
+
if (!projectionCoordinate) {
|
|
346
|
+
throw new FlowError("INTERNAL_STATE", "durable execution requires a projection coordinate");
|
|
347
|
+
}
|
|
348
|
+
const launched = await startProjectionGeneration(projectionCoordinate.dir, projectionCoordinate.executionId);
|
|
349
|
+
if (launched.status === "failed") {
|
|
350
|
+
throw new FlowError("SUBAGENT_EXEC_ERROR", launched.detail);
|
|
351
|
+
}
|
|
352
|
+
const joined = await waitForProjection({
|
|
353
|
+
projectionDirectory: projectionCoordinate.dir,
|
|
354
|
+
signal: options.signal,
|
|
355
|
+
});
|
|
356
|
+
if (joined.state !== "terminal") {
|
|
357
|
+
throw new FlowError("INTERNAL_STATE", "durable execution ended without a terminal generation");
|
|
358
|
+
}
|
|
359
|
+
const current = readCurrentGenerationOrThrow(projectionCoordinate.dir);
|
|
360
|
+
const providerOutcome = outcomeFromGeneration(current);
|
|
361
|
+
const outcome = {
|
|
362
|
+
status: providerOutcome.status,
|
|
363
|
+
finalMessage: providerOutcome.finalMessage,
|
|
364
|
+
output: providerOutcome.output,
|
|
365
|
+
diagnostics: providerOutcome.diagnostics,
|
|
366
|
+
session: providerOutcome.session,
|
|
367
|
+
warnings: providerOutcome.warnings,
|
|
368
|
+
...(providerOutcome.error ? { error: providerOutcome.error } : {}),
|
|
369
|
+
launchSnapshot,
|
|
370
|
+
};
|
|
371
|
+
const completionFacts = current.verdict?.facts;
|
|
372
|
+
switch (outcome.status) {
|
|
373
|
+
case "completed":
|
|
374
|
+
return { outcome, projection: projectionCoordinate, ...(completionFacts ? { completionFacts } : {}) };
|
|
375
|
+
case "dismissed":
|
|
376
|
+
throw createAbortError("subagent exec cancelled by client");
|
|
377
|
+
case "failed": {
|
|
378
|
+
const error = outcome.error;
|
|
379
|
+
if (!error) {
|
|
380
|
+
throw new FlowError("SUBAGENT_EXEC_ERROR", "Subagent exec failed without an error payload.");
|
|
381
|
+
}
|
|
382
|
+
throw new FlowError(error.code, error.message, {
|
|
383
|
+
cause: new SubagentFailure(error.code, error.message, {
|
|
384
|
+
timeoutMs: error.timeoutMs,
|
|
385
|
+
exitCode: error.exitCode,
|
|
386
|
+
output: outcome.output,
|
|
387
|
+
diagnostics: outcome.diagnostics,
|
|
388
|
+
}),
|
|
389
|
+
});
|
|
390
|
+
}
|
|
391
|
+
}
|
|
392
|
+
throw new FlowError("INTERNAL_STATE", "Unhandled subagent outcome state.");
|
|
393
|
+
}
|
|
394
|
+
async function launchSubagentGeneration(agentName, prompt, cwd, options) {
|
|
395
|
+
const { projection, launchSnapshot } = await buildCallConfig(agentName, prompt, cwd, options);
|
|
396
|
+
if (!projection) {
|
|
397
|
+
throw new FlowError("INTERNAL_STATE", "durable launch requires a projection coordinate");
|
|
398
|
+
}
|
|
399
|
+
const launched = await startProjectionGeneration(projection.dir, projection.executionId);
|
|
400
|
+
if (launched.status === "failed") {
|
|
401
|
+
throw new FlowError("SUBAGENT_EXEC_ERROR", launched.detail);
|
|
402
|
+
}
|
|
403
|
+
return { projection, launchSnapshot };
|
|
404
|
+
}
|
|
405
|
+
async function buildCallConfig(agentName, prompt, cwd, options) {
|
|
406
|
+
const launchSnapshot = options.launchSnapshot ?? resolveAkumaLaunchSnapshot(await selectSubagentV2(agentName, cwd), {
|
|
407
|
+
executionCwd: cwd,
|
|
408
|
+
projectRoot: cwd,
|
|
409
|
+
...(options.model !== undefined ? { model: options.model } : {}),
|
|
410
|
+
...(options.effort !== undefined ? { effort: options.effort } : {}),
|
|
411
|
+
});
|
|
412
|
+
const resolvedTerms = buildCallTermsV2(launchSnapshot, prompt, cwd, {
|
|
413
|
+
signal: options.signal,
|
|
414
|
+
idleTimeoutMs: options.idleTimeoutMs,
|
|
415
|
+
});
|
|
416
|
+
// Mint projection directory for supervisor pump.
|
|
417
|
+
const slug = launchSnapshot.akuma.name.replace(/[^a-zA-Z0-9_-]/g, "-").toLowerCase();
|
|
418
|
+
const publishProjection = async () => {
|
|
419
|
+
const mintInput = {
|
|
420
|
+
cwd: options.projectionCwd ?? cwd,
|
|
421
|
+
workspaceRoot: cwd,
|
|
422
|
+
slug,
|
|
423
|
+
pid: process.pid,
|
|
424
|
+
akuma: launchSnapshot.akuma.name,
|
|
425
|
+
provider: launchSnapshot.provider.kind,
|
|
426
|
+
binding: options.binding,
|
|
427
|
+
effectivePolicy: launchSnapshot.akuma.policy,
|
|
428
|
+
nowMs: Date.now(),
|
|
429
|
+
};
|
|
430
|
+
const minted = options.authority.kind === "repository"
|
|
431
|
+
? await mintRepositoryProjection({ ...mintInput, authority: { kind: "repository" } })
|
|
432
|
+
: mintProjection({ ...mintInput, authority: { kind: "user" } });
|
|
433
|
+
const executionId = createProjectionExecutionId();
|
|
434
|
+
const launchFacts = JSON.parse(JSON.stringify({
|
|
435
|
+
executionId,
|
|
436
|
+
createdAt: new Date().toISOString(),
|
|
437
|
+
launchedBy: "call",
|
|
438
|
+
inputs: {
|
|
439
|
+
prompt,
|
|
440
|
+
task: options.task ?? prompt,
|
|
441
|
+
launchSnapshot,
|
|
442
|
+
idleTimeoutMs: resolvedTerms.idleTimeoutMs,
|
|
443
|
+
cwd,
|
|
444
|
+
historyCwd: options.historyStorageCwd ?? cwd,
|
|
445
|
+
...(options.evidenceCwd !== undefined ? { evidenceCwd: options.evidenceCwd } : {}),
|
|
446
|
+
...(options.title !== undefined ? { title: options.title } : {}),
|
|
447
|
+
...(options.context !== undefined ? { context: options.context } : {}),
|
|
448
|
+
persistProjectionResponse: options.persistProjectionResponse ?? false,
|
|
449
|
+
...(options.continuation ? {
|
|
450
|
+
session: options.continuation.session,
|
|
451
|
+
resumePath: options.continuation.resumePath,
|
|
452
|
+
} : {}),
|
|
453
|
+
},
|
|
454
|
+
}));
|
|
455
|
+
const store = openProjectionGenerationStore(minted.dir);
|
|
456
|
+
try {
|
|
457
|
+
const launch = store.launchIfSettled({ executionId, facts: launchFacts });
|
|
458
|
+
if (launch.status !== "committed") {
|
|
459
|
+
throw new FlowError("INTERNAL_STATE", `initial generation launch rejected: ${launch.reason}`);
|
|
460
|
+
}
|
|
461
|
+
}
|
|
462
|
+
finally {
|
|
463
|
+
store.close();
|
|
464
|
+
}
|
|
465
|
+
return { id: minted.id, dir: minted.dir, executionId };
|
|
466
|
+
};
|
|
467
|
+
// Every public launch has one durable projection object. Repository-free
|
|
468
|
+
// authority changes the storage owner, not whether the object exists; mint
|
|
469
|
+
// failure is therefore a launch failure rather than an in-process fallback.
|
|
470
|
+
const projectionCoordinate = await publishProjection();
|
|
471
|
+
const terms = buildCallTermsV2(launchSnapshot, prompt, cwd, {
|
|
472
|
+
signal: options.signal,
|
|
473
|
+
idleTimeoutMs: resolvedTerms.idleTimeoutMs,
|
|
474
|
+
projectionDir: projectionCoordinate.dir,
|
|
475
|
+
projectionExecutionId: projectionCoordinate.executionId,
|
|
476
|
+
});
|
|
477
|
+
return {
|
|
478
|
+
terms,
|
|
479
|
+
projection: projectionCoordinate,
|
|
480
|
+
launchSnapshot,
|
|
481
|
+
};
|
|
482
|
+
}
|
|
483
|
+
export async function runCall(input) {
|
|
484
|
+
const { signal, agent } = input;
|
|
485
|
+
const reviveArtifact = input.reviveArtifact ? requireResumableArtifact(input.reviveArtifact) : undefined;
|
|
486
|
+
let rawCwd = input.cwd;
|
|
487
|
+
let rawRepo = input.repo;
|
|
488
|
+
if (reviveArtifact?.provenance.authority.kind === "user") {
|
|
489
|
+
if (!("workdir" in reviveArtifact.provenance.evidence)) {
|
|
490
|
+
throw new FlowError("INVALID_RESPONSE_PATH", `Invalid response artifact '${reviveArtifact.artifactId}': field 'provenance.evidence.workdir' is missing.`);
|
|
491
|
+
}
|
|
492
|
+
rawCwd = reviveArtifact.provenance.evidence.workdir;
|
|
493
|
+
rawRepo = undefined;
|
|
494
|
+
}
|
|
495
|
+
else if (reviveArtifact?.provenance.authority.kind === "repository") {
|
|
496
|
+
const artifactHome = await fs.realpath(reviveArtifact.storageRoot);
|
|
497
|
+
const addressed = input.repo ?? input.cwd;
|
|
498
|
+
if (await isGitRepo(addressed)) {
|
|
499
|
+
const addressedRoot = await stableRepoRoot(addressed);
|
|
500
|
+
if (addressedRoot !== artifactHome) {
|
|
501
|
+
throw new FlowError("INVALID_RESPONSE_PATH", `Response artifact '${reviveArtifact.artifactId}' belongs to ${artifactHome}, but the addressed repository is ${addressedRoot}.`);
|
|
502
|
+
}
|
|
503
|
+
}
|
|
504
|
+
rawCwd = artifactHome;
|
|
505
|
+
rawRepo = artifactHome;
|
|
506
|
+
}
|
|
507
|
+
const cwd = await fs.realpath(rawCwd);
|
|
508
|
+
const repoCwd = rawRepo ? await fs.realpath(rawRepo) : undefined;
|
|
509
|
+
const title = requireText("title", input.title);
|
|
510
|
+
const task = requireText("task", input.task);
|
|
511
|
+
if (input.incognito && reviveArtifact) {
|
|
512
|
+
throw new FlowError("EMPTY_PARAM", "Parameter 'incognito' cannot be used with 'reviveArtifact' — incognito calls cannot be revived or persist conversation history.");
|
|
513
|
+
}
|
|
514
|
+
const keiyakuContext = await readCallKeiyakuContext(cwd, {
|
|
515
|
+
bare: input.bare,
|
|
516
|
+
contractId: input.contractId,
|
|
517
|
+
contractAddressSource: input.contractAddressSource,
|
|
518
|
+
mode: input.mode ?? "call",
|
|
519
|
+
repoCwd,
|
|
520
|
+
reviveBinding: reviveArtifact?.provenance.binding,
|
|
521
|
+
});
|
|
522
|
+
return await withResolvedAddress(keiyakuContext.address, async () => {
|
|
523
|
+
const executionCwd = keiyakuContext.effectiveCwd;
|
|
524
|
+
const referenceFiles = await collectCallReferenceFiles(executionCwd, keiyakuContext);
|
|
525
|
+
const prompt = buildCallPrompt({
|
|
526
|
+
task,
|
|
527
|
+
context: input.context,
|
|
528
|
+
referenceFiles,
|
|
529
|
+
arc: keiyakuContext.openArc,
|
|
530
|
+
});
|
|
531
|
+
const launchSnapshot = reviveArtifact
|
|
532
|
+
? input.effort !== undefined
|
|
533
|
+
? applySnapshotEffort(reviveArtifact.launchSnapshot, input.effort)
|
|
534
|
+
: reviveArtifact.launchSnapshot
|
|
535
|
+
: resolveAkumaLaunchSnapshot(await selectSubagentV2(agent, keiyakuContext.profileCwd), {
|
|
536
|
+
executionCwd,
|
|
537
|
+
projectRoot: keiyakuContext.profileCwd,
|
|
538
|
+
...(input.model !== undefined ? { model: input.model } : {}),
|
|
539
|
+
...(input.effort !== undefined ? { effort: input.effort } : {}),
|
|
540
|
+
});
|
|
541
|
+
const continuation = reviveArtifact
|
|
542
|
+
? { session: reviveArtifact.session, resumePath: reviveArtifact.absolutePath }
|
|
543
|
+
: undefined;
|
|
544
|
+
logInfo(`[${ACTOR_IDENTITY}] Running execution for '${launchSnapshot.akuma.name}' in ${executionCwd}`, {
|
|
545
|
+
cwd: executionCwd,
|
|
546
|
+
section: "script",
|
|
547
|
+
progressOnly: true,
|
|
548
|
+
});
|
|
549
|
+
// Storage coordinate: addressed repository stable root / caller cwd.
|
|
550
|
+
// Evidence coordinate: commission worktree for bound runs; execution cwd otherwise.
|
|
551
|
+
const historyStorageCwd = keiyakuContext.historyCwd;
|
|
552
|
+
const evidenceCwd = executionCwd;
|
|
553
|
+
const executionOptions = {
|
|
554
|
+
signal,
|
|
555
|
+
continuation,
|
|
556
|
+
task,
|
|
557
|
+
launchSnapshot,
|
|
558
|
+
persistProjectionResponse: !input.incognito,
|
|
559
|
+
historyStorageCwd,
|
|
560
|
+
evidenceCwd,
|
|
561
|
+
projectionCwd: keiyakuContext.projectionCwd,
|
|
562
|
+
authority: keiyakuContext.address.repo.kind === "repository"
|
|
563
|
+
? { kind: "repository" }
|
|
564
|
+
: { kind: "user" },
|
|
565
|
+
binding: keiyakuContext.address.binding,
|
|
566
|
+
title,
|
|
567
|
+
context: input.context,
|
|
568
|
+
};
|
|
569
|
+
if ((input.mode ?? "call") === "call") {
|
|
570
|
+
const launched = await launchSubagentGeneration(launchSnapshot.akuma.name, prompt, executionCwd, executionOptions);
|
|
571
|
+
const adoptedProjection = {
|
|
572
|
+
id: launched.projection.id,
|
|
573
|
+
phase: observeProjectionLife(launched.projection.dir, {
|
|
574
|
+
nowMs: Date.now(),
|
|
575
|
+
startupTimeoutMs: PROJECTION_ADOPTION_TIMEOUT_MS,
|
|
576
|
+
}).phase,
|
|
577
|
+
};
|
|
578
|
+
input.onLaunch?.({ address: keiyakuContext.address, projection: adoptedProjection });
|
|
579
|
+
if (input.detach) {
|
|
580
|
+
return {
|
|
581
|
+
state: "launched",
|
|
582
|
+
summary: "",
|
|
583
|
+
agent: launchSnapshot.akuma.name,
|
|
584
|
+
launchSnapshot,
|
|
585
|
+
projectionOwnsResponsePersistence: true,
|
|
586
|
+
projection: adoptedProjection,
|
|
587
|
+
address: keiyakuContext.address,
|
|
588
|
+
};
|
|
589
|
+
}
|
|
590
|
+
const joined = await waitForProjection({
|
|
591
|
+
projectionDirectory: launched.projection.dir,
|
|
592
|
+
signal,
|
|
593
|
+
});
|
|
594
|
+
if (joined.state !== "terminal") {
|
|
595
|
+
throw new FlowError("INTERNAL_STATE", "foreground call wait ended without a terminal projection verdict");
|
|
596
|
+
}
|
|
597
|
+
return {
|
|
598
|
+
state: "completed",
|
|
599
|
+
summary: joined.outcomeText || `${ACTOR_IDENTITY} completed successfully.`,
|
|
600
|
+
agent: launchSnapshot.akuma.name,
|
|
601
|
+
launchSnapshot,
|
|
602
|
+
...(joined.snapshot.responsePath ? { responsePath: joined.snapshot.responsePath } : {}),
|
|
603
|
+
...(joined.snapshot.artifactId ? { artifactId: joined.snapshot.artifactId } : {}),
|
|
604
|
+
projectionOwnsResponsePersistence: true,
|
|
605
|
+
meta: { effectivePolicy: launchSnapshot.akuma.policy },
|
|
606
|
+
projection: { id: launched.projection.id, phase: joined.snapshot.phase },
|
|
607
|
+
foregroundWait: joined,
|
|
608
|
+
address: keiyakuContext.address,
|
|
609
|
+
};
|
|
610
|
+
}
|
|
611
|
+
const execution = await executeSubagent(launchSnapshot.akuma.name, prompt, executionCwd, executionOptions);
|
|
612
|
+
const subagentOutcome = execution.outcome;
|
|
613
|
+
if (subagentOutcome.diagnostics.trim()) {
|
|
614
|
+
appendDebugBlock("subagent diagnostics", subagentOutcome.diagnostics, { cwd: executionCwd, section: "subagent-stderr" });
|
|
615
|
+
}
|
|
616
|
+
let projection;
|
|
617
|
+
let responsePath;
|
|
618
|
+
let artifactId;
|
|
619
|
+
if (execution.projection) {
|
|
620
|
+
responsePath = typeof execution.completionFacts?.responsePath === "string"
|
|
621
|
+
? execution.completionFacts.responsePath
|
|
622
|
+
: undefined;
|
|
623
|
+
artifactId = typeof execution.completionFacts?.artifactId === "string"
|
|
624
|
+
? execution.completionFacts.artifactId
|
|
625
|
+
: undefined;
|
|
626
|
+
try {
|
|
627
|
+
projection = {
|
|
628
|
+
id: execution.projection.id,
|
|
629
|
+
phase: observeProjectionLife(execution.projection.dir, {
|
|
630
|
+
nowMs: Date.now(),
|
|
631
|
+
startupTimeoutMs: PROJECTION_ADOPTION_TIMEOUT_MS,
|
|
632
|
+
}).phase,
|
|
633
|
+
};
|
|
634
|
+
}
|
|
635
|
+
catch {
|
|
636
|
+
// Projection observations are best-effort and must not reverse success.
|
|
637
|
+
}
|
|
638
|
+
}
|
|
639
|
+
return {
|
|
640
|
+
state: "completed",
|
|
641
|
+
summary: subagentOutcome.finalMessage || `${ACTOR_IDENTITY} completed successfully.`,
|
|
642
|
+
agent: launchSnapshot.akuma.name,
|
|
643
|
+
session: subagentOutcome.session,
|
|
644
|
+
launchSnapshot: subagentOutcome.launchSnapshot,
|
|
645
|
+
...(responsePath ? { responsePath } : {}),
|
|
646
|
+
...(artifactId ? { artifactId } : {}),
|
|
647
|
+
...(reviveArtifact ? { revivedArtifactId: reviveArtifact.artifactId } : {}),
|
|
648
|
+
projectionOwnsResponsePersistence: execution.projection !== undefined,
|
|
649
|
+
meta: {
|
|
650
|
+
...(subagentOutcome.warnings.length > 0 ? { warnings: subagentOutcome.warnings.map(renderSubagentWarning) } : {}),
|
|
651
|
+
effectivePolicy: subagentOutcome.launchSnapshot.akuma.policy,
|
|
652
|
+
},
|
|
653
|
+
...(projection ? { projection } : {}),
|
|
654
|
+
address: keiyakuContext.address,
|
|
655
|
+
...(input.incognito ? {} : { evidenceCwd }),
|
|
656
|
+
};
|
|
657
|
+
});
|
|
658
|
+
}
|