@astrosheep/keiyaku 0.1.87 → 1.0.0
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 +12 -22
- package/build/.tsbuildinfo +1 -1
- package/build/agents/effective-policy.js +32 -0
- package/build/agents/harness/event-channel.js +178 -0
- package/build/agents/harness/execution-handle.js +117 -0
- package/build/agents/harness/index.js +622 -0
- package/build/agents/harness/pump.js +152 -0
- package/build/agents/harness/runtime.js +68 -0
- package/build/agents/index.js +104 -213
- package/build/agents/opencode-sdk.js +12 -18
- package/build/agents/providers/claude-agent-sdk.js +529 -0
- package/build/agents/providers/codex-app-server.js +656 -0
- package/build/agents/providers/codex-sdk.js +193 -0
- package/build/agents/providers/opencode-sdk.js +257 -0
- package/build/agents/providers/pi.js +345 -0
- package/build/agents/selector.js +15 -6
- package/build/agents/session.js +29 -0
- package/build/agents/types.js +5 -11
- package/build/cli/commands/akuma.js +79 -0
- package/build/cli/completion.js +71 -0
- package/build/cli/draft-artifact.js +50 -0
- package/build/cli/flags.js +96 -0
- package/build/cli/help.js +108 -0
- package/build/cli/index.js +204 -912
- package/build/cli/parse.js +280 -0
- package/build/cli/render/arc.js +63 -0
- package/build/{responses/tool-errors.js → cli/render/errors.js} +15 -16
- package/build/cli/render/format.js +199 -0
- package/build/cli/render/misc.js +40 -0
- package/build/cli/render/petition.js +32 -0
- package/build/{responses → cli/render}/response-style.js +4 -4
- package/build/cli/render/shared.js +122 -0
- package/build/cli/render/status.js +69 -0
- package/build/cli/render/summon.js +17 -0
- package/build/cli/render/tell.js +11 -0
- package/build/cli/skills-install.js +237 -0
- package/build/cli/subagent-guard.js +6 -3
- package/build/config/akuma-loader.js +268 -0
- package/build/config/env-keys.js +0 -10
- package/build/config/{schema.js → env.js} +117 -23
- package/build/config/settings.js +137 -260
- package/build/core/amend.js +120 -0
- package/build/core/arc.js +225 -0
- package/build/core/atomic-publish.js +64 -0
- package/build/core/bind.js +281 -0
- package/build/core/claim.js +360 -0
- package/build/core/command-io.js +174 -0
- package/build/core/context.js +108 -0
- package/build/core/contract.js +14 -0
- package/build/core/draft.js +82 -0
- package/build/core/entry.js +203 -0
- package/build/core/forfeit.js +60 -0
- package/build/core/hints.js +108 -0
- package/build/core/ids.js +70 -0
- package/build/core/ledger.js +117 -0
- package/build/core/log.js +24 -0
- package/build/{protocol → core}/markdown/parser.js +68 -0
- package/build/{protocol → core}/markdown/sections.js +2 -28
- package/build/core/markdown/titles.js +9 -0
- package/build/{tools/petition/claim-gates.js → core/petition-claim-gates.js} +43 -29
- package/build/core/petition-claim.js +116 -0
- package/build/core/petition-forfeit.js +39 -0
- package/build/{tools/round/head-guard.js → core/petition-head-guard.js} +2 -2
- package/build/core/petition-run.js +81 -0
- package/build/core/petition.js +96 -0
- package/build/core/places.js +82 -0
- package/build/core/projection-core.js +541 -0
- package/build/core/projection-mint.js +79 -0
- package/build/core/projection-status.js +179 -0
- package/build/core/projection-wake.js +252 -0
- package/build/core/queue.js +73 -0
- package/build/core/ref-log.js +94 -0
- package/build/core/registry.js +96 -0
- package/build/core/render.js +326 -0
- package/build/core/renew.js +195 -0
- package/build/core/scope.js +163 -0
- package/build/core/seal.js +195 -0
- package/build/core/status.js +476 -0
- package/build/{tools/summon/persist.js → core/summon-persist.js} +10 -9
- package/build/core/summon.js +377 -0
- package/build/{protocol/response-history.js → core/transcripts.js} +83 -39
- package/build/core/verdict.js +96 -0
- package/build/core/worktree-path.js +134 -0
- package/build/flow-error.js +80 -4
- package/build/generated/version.js +1 -1
- package/build/git/branches.js +8 -9
- package/build/git/core.js +1 -1
- package/build/git/diff/pathspec.js +2 -3
- package/build/git/diff/preview.js +7 -2
- package/build/git/diff/read.js +4 -3
- package/build/git/staging.js +1 -1
- package/build/index.js +12 -6
- package/build/keiyaku.js +17 -13
- package/build/telemetry/debug-log.js +1 -1
- package/package.json +16 -9
- package/skills/keiyaku/SKILL.md +16 -13
- package/build/agents/claude-agent-sdk-exec.js +0 -352
- package/build/agents/codex-cli-exec.js +0 -237
- package/build/agents/codex-sdk-exec.js +0 -291
- package/build/agents/gemini-cli-core-exec.js +0 -499
- package/build/agents/opencode-exec.js +0 -297
- package/build/agents/process-runner.js +0 -239
- package/build/agents/progress-reporter.js +0 -137
- package/build/agents/round-runner.js +0 -53
- package/build/config/apply-argument-descriptions.js +0 -16
- package/build/config/architect-hints.js +0 -374
- package/build/config/base-rules.js +0 -23
- package/build/config/config.js +0 -50
- package/build/config/dotenv.js +0 -61
- package/build/config/keiyaku-home.js +0 -9
- package/build/config/path-utils.js +0 -11
- package/build/config/render-template.js +0 -8
- package/build/git/diff/filter.js +0 -67
- package/build/git/keiyaku-state.js +0 -58
- package/build/git/snapshot.js +0 -41
- package/build/git/stash.js +0 -62
- package/build/protocol/bind-draft.js +0 -195
- package/build/protocol/draft-artifacts.js +0 -99
- package/build/protocol/file-guards.js +0 -24
- package/build/protocol/keiyaku-draft.js +0 -168
- package/build/protocol/ledger.js +0 -374
- package/build/protocol/markdown/index.js +0 -14
- package/build/protocol/markdown/normalization.js +0 -71
- package/build/protocol/markdown/render.js +0 -68
- package/build/protocol/oath.js +0 -36
- package/build/protocol/render-keiyaku.js +0 -37
- package/build/protocol/review.js +0 -1
- package/build/protocol/status-previews.js +0 -68
- package/build/responses/responses.js +0 -515
- package/build/tools/amend/index.js +0 -18
- package/build/tools/amend/run.js +0 -44
- package/build/tools/bind/index.js +0 -44
- package/build/tools/bind/run.js +0 -237
- package/build/tools/contracts.js +0 -1
- package/build/tools/create-handler.js +0 -31
- package/build/tools/help.js +0 -9
- package/build/tools/petition/claim.js +0 -174
- package/build/tools/petition/forfeit.js +0 -100
- package/build/tools/petition/index.js +0 -83
- package/build/tools/petition/run.js +0 -66
- package/build/tools/round/guidance.js +0 -7
- package/build/tools/round/prompt.js +0 -70
- package/build/tools/round/report.js +0 -153
- package/build/tools/round/worktree.js +0 -55
- package/build/tools/round-close/index.js +0 -19
- package/build/tools/round-close/run.js +0 -98
- package/build/tools/round-open/index.js +0 -20
- package/build/tools/round-open/run.js +0 -77
- package/build/tools/schema.js +0 -69
- package/build/tools/status/index.js +0 -16
- package/build/tools/status/read.js +0 -155
- package/build/tools/summon/index.js +0 -27
- package/build/tools/summon/run.js +0 -80
- /package/build/{config/toml.js → agents/harness/events.js} +0 -0
- /package/build/{git/diff/contracts.js → cli/render/types.js} +0 -0
- /package/build/{protocol/bind-input.js → cli/types.js} +0 -0
- /package/build/{protocol → core}/markdown/lex.js +0 -0
- /package/build/{protocol → core}/markdown/types.js +0 -0
|
@@ -0,0 +1,622 @@
|
|
|
1
|
+
import { randomUUID } from "node:crypto";
|
|
2
|
+
import { FlowError } from "../../flow-error.js";
|
|
3
|
+
import { StringTailBuffer } from "../string-tail-buffer.js";
|
|
4
|
+
import { SUBAGENT_SESSION_VERSION, subagentSessionEnvelopeSchema, } from "../session.js";
|
|
5
|
+
import { startClaudeAgentSdk, claudeSessionSchema, } from "../providers/claude-agent-sdk.js";
|
|
6
|
+
import { startCodexAppServer, codexAppServerSessionSchema, } from "../providers/codex-app-server.js";
|
|
7
|
+
import { startCodexSdk, codexThreadSchema, } from "../providers/codex-sdk.js";
|
|
8
|
+
import { startOpencode, opencodeSessionSchema, } from "../providers/opencode-sdk.js";
|
|
9
|
+
import { startPi, piSessionSchema, } from "../providers/pi.js";
|
|
10
|
+
import { SubagentFailure, } from "../types.js";
|
|
11
|
+
import { createEventChannel } from "./event-channel.js";
|
|
12
|
+
import { buildSyncCheckpointCallback, createReplayRunStartGate, markReplayCompleted, pumpSupervisor, snapshotReplayPrompt } from "./pump.js";
|
|
13
|
+
import { createAbortError, renderCapturedText } from "./runtime.js";
|
|
14
|
+
import { acquireAdmissionLock, createProjectionExecutionId, demoteSubmittedTellsForReplay, generationResultPath, listTellIds, markTellSubmitted, publishGenerationResult, publishVerdict, readAdmissionLock, readGenerationLaunch, releaseAdmissionLockOwned, touchHeartbeat } from "../../core/projection-core.js";
|
|
15
|
+
import { wakeProjection } from "../../core/projection-wake.js";
|
|
16
|
+
export function summon(terms) {
|
|
17
|
+
return createProjection(terms);
|
|
18
|
+
}
|
|
19
|
+
export function resume(terms) {
|
|
20
|
+
return createProjection(terms, terms);
|
|
21
|
+
}
|
|
22
|
+
function mintOutcome(draft) {
|
|
23
|
+
return draft;
|
|
24
|
+
}
|
|
25
|
+
function effectivePolicyFor(shared) {
|
|
26
|
+
return shared.terms.provider === "codex-sdk" ? shared.terms.effectivePolicy : undefined;
|
|
27
|
+
}
|
|
28
|
+
function createProjection(terms, resumeTerms) {
|
|
29
|
+
switch (terms.provider) {
|
|
30
|
+
case "pi":
|
|
31
|
+
case "claude-agent-sdk":
|
|
32
|
+
case "codex-app-server":
|
|
33
|
+
return createSteerableProjection(terms, resumeTerms);
|
|
34
|
+
case "codex-sdk":
|
|
35
|
+
case "opencode-sdk":
|
|
36
|
+
return createReplayProjection(terms, resumeTerms);
|
|
37
|
+
default:
|
|
38
|
+
return assertNever(terms);
|
|
39
|
+
}
|
|
40
|
+
}
|
|
41
|
+
function createSteerableProjection(terms, resumeTerms) {
|
|
42
|
+
const shared = createProjectionState(terms, resumeTerms);
|
|
43
|
+
const pendingTells = [];
|
|
44
|
+
let flushActive = false;
|
|
45
|
+
const flushPendingTells = async () => {
|
|
46
|
+
const initialEnqueue = shared.live?.enqueue;
|
|
47
|
+
if (flushActive || shared.terminal || !initialEnqueue)
|
|
48
|
+
return;
|
|
49
|
+
flushActive = true;
|
|
50
|
+
try {
|
|
51
|
+
while (!shared.terminal && pendingTells.length > 0) {
|
|
52
|
+
const enqueue = shared.live?.enqueue;
|
|
53
|
+
if (!enqueue)
|
|
54
|
+
break;
|
|
55
|
+
const nextTell = pendingTells.shift();
|
|
56
|
+
if (!nextTell)
|
|
57
|
+
break;
|
|
58
|
+
await enqueue(nextTell);
|
|
59
|
+
}
|
|
60
|
+
}
|
|
61
|
+
finally {
|
|
62
|
+
flushActive = false;
|
|
63
|
+
if (!shared.terminal && pendingTells.length > 0 && shared.live?.enqueue) {
|
|
64
|
+
void flushPendingTells();
|
|
65
|
+
}
|
|
66
|
+
}
|
|
67
|
+
};
|
|
68
|
+
shared.onLive = (live) => {
|
|
69
|
+
shared.live = live;
|
|
70
|
+
void flushPendingTells();
|
|
71
|
+
};
|
|
72
|
+
const outcome = settleProjection(shared).then((value) => finalizeProjectionLedger(shared, value));
|
|
73
|
+
return {
|
|
74
|
+
mode: "steerable",
|
|
75
|
+
get id() {
|
|
76
|
+
return shared.id;
|
|
77
|
+
},
|
|
78
|
+
get events() {
|
|
79
|
+
return shared.publicEvents.iterable;
|
|
80
|
+
},
|
|
81
|
+
get outcome() {
|
|
82
|
+
return outcome;
|
|
83
|
+
},
|
|
84
|
+
tell(tell) {
|
|
85
|
+
if (shared.terminal)
|
|
86
|
+
return;
|
|
87
|
+
pendingTells.push(tell);
|
|
88
|
+
void flushPendingTells();
|
|
89
|
+
},
|
|
90
|
+
recallTells() {
|
|
91
|
+
return pendingTells.splice(0, pendingTells.length);
|
|
92
|
+
},
|
|
93
|
+
dismiss() {
|
|
94
|
+
dismissProjection(shared);
|
|
95
|
+
},
|
|
96
|
+
};
|
|
97
|
+
}
|
|
98
|
+
function createReplayProjection(terms, resumeTerms) {
|
|
99
|
+
const shared = createProjectionState(terms, resumeTerms);
|
|
100
|
+
shared.onLive = (live) => {
|
|
101
|
+
shared.live = live;
|
|
102
|
+
};
|
|
103
|
+
const outcome = settleProjection(shared).then((value) => finalizeProjectionLedger(shared, value));
|
|
104
|
+
return {
|
|
105
|
+
mode: "replay",
|
|
106
|
+
get id() {
|
|
107
|
+
return shared.id;
|
|
108
|
+
},
|
|
109
|
+
get events() {
|
|
110
|
+
return shared.publicEvents.iterable;
|
|
111
|
+
},
|
|
112
|
+
get outcome() {
|
|
113
|
+
return outcome;
|
|
114
|
+
},
|
|
115
|
+
dismiss() {
|
|
116
|
+
dismissProjection(shared);
|
|
117
|
+
},
|
|
118
|
+
};
|
|
119
|
+
}
|
|
120
|
+
function createProjectionState(terms, resumeTerms) {
|
|
121
|
+
const shared = {
|
|
122
|
+
id: randomUUID(),
|
|
123
|
+
terms,
|
|
124
|
+
resumeTerms,
|
|
125
|
+
publicEvents: createEventChannel(),
|
|
126
|
+
lifecycleAbort: new AbortController(),
|
|
127
|
+
dismissed: false,
|
|
128
|
+
externallyAborted: false,
|
|
129
|
+
terminal: false,
|
|
130
|
+
onLive() { },
|
|
131
|
+
};
|
|
132
|
+
if (terms.signal) {
|
|
133
|
+
const listener = () => {
|
|
134
|
+
shared.externallyAborted = true;
|
|
135
|
+
if (!shared.lifecycleAbort.signal.aborted) {
|
|
136
|
+
shared.lifecycleAbort.abort(terms.signal?.reason);
|
|
137
|
+
}
|
|
138
|
+
};
|
|
139
|
+
terms.signal.addEventListener("abort", listener, { once: true });
|
|
140
|
+
if (terms.signal.aborted) {
|
|
141
|
+
listener();
|
|
142
|
+
}
|
|
143
|
+
}
|
|
144
|
+
return shared;
|
|
145
|
+
}
|
|
146
|
+
function finalizeProjectionLedger(shared, outcome) {
|
|
147
|
+
const directory = shared.terms.projectionDir;
|
|
148
|
+
const executionId = shared.terms.projectionExecutionId;
|
|
149
|
+
if (!directory || !executionId)
|
|
150
|
+
return outcome;
|
|
151
|
+
const now = Date.now();
|
|
152
|
+
publishGenerationResult(directory, {
|
|
153
|
+
version: 1,
|
|
154
|
+
executionId,
|
|
155
|
+
status: outcome.status,
|
|
156
|
+
createdAt: new Date(now).toISOString(),
|
|
157
|
+
session: outcome.session,
|
|
158
|
+
responsePath: generationResultPath(directory, executionId),
|
|
159
|
+
detail: outcome.status === "failed" ? outcome.error?.message : undefined,
|
|
160
|
+
});
|
|
161
|
+
// A stale/duplicate child must never replace a later generation's verdict.
|
|
162
|
+
if (readAdmissionLock(directory)?.executionId !== executionId)
|
|
163
|
+
return outcome;
|
|
164
|
+
const pending = ["inbox", "inflight", "submitted"].some((layer) => listTellIds(directory, layer).length > 0);
|
|
165
|
+
if (pending && outcome.status === "completed") {
|
|
166
|
+
// The resume lock is acquired by the one wake orchestrator before it releases
|
|
167
|
+
// this generation's admission; a crash leaves result-without-done for self-heal.
|
|
168
|
+
wakeProjection(directory, "sweep-wake", { handoffExecutionId: executionId });
|
|
169
|
+
return outcome;
|
|
170
|
+
}
|
|
171
|
+
publishVerdict(directory, outcome.status === "completed" ? "done" : outcome.status === "dismissed" ? "dismissed" : "failed", now, outcome.status === "failed" ? outcome.error?.message : undefined);
|
|
172
|
+
releaseAdmissionLockOwned(directory, executionId);
|
|
173
|
+
if (listTellIds(directory, "inbox").length > 0)
|
|
174
|
+
wakeProjection(directory, "sweep-wake");
|
|
175
|
+
return outcome;
|
|
176
|
+
}
|
|
177
|
+
function dismissProjection(shared) {
|
|
178
|
+
if (shared.dismissed || shared.terminal)
|
|
179
|
+
return;
|
|
180
|
+
shared.dismissed = true;
|
|
181
|
+
if (!shared.lifecycleAbort.signal.aborted) {
|
|
182
|
+
shared.lifecycleAbort.abort(createAbortError(`${shared.terms.provider} exec dismissed`));
|
|
183
|
+
}
|
|
184
|
+
requestAbort(shared.live);
|
|
185
|
+
}
|
|
186
|
+
function requestAbort(live) {
|
|
187
|
+
if (!live)
|
|
188
|
+
return;
|
|
189
|
+
void Promise.resolve()
|
|
190
|
+
.then(() => live.abort())
|
|
191
|
+
.catch(() => undefined);
|
|
192
|
+
}
|
|
193
|
+
async function settleProjection(shared) {
|
|
194
|
+
const state = {
|
|
195
|
+
outputBuffer: new StringTailBuffer(shared.terms.maxCaptureChars),
|
|
196
|
+
diagnosticsBuffer: new StringTailBuffer(shared.terms.maxCaptureChars),
|
|
197
|
+
warnings: [],
|
|
198
|
+
};
|
|
199
|
+
let timedOut = false;
|
|
200
|
+
let idleTimedOut = false;
|
|
201
|
+
let abortRequested = false;
|
|
202
|
+
const timeout = setTimeout(() => {
|
|
203
|
+
timedOut = true;
|
|
204
|
+
shared.lifecycleAbort.abort(createAbortError(`${shared.terms.provider} exec timed out`));
|
|
205
|
+
}, shared.terms.timeoutMs);
|
|
206
|
+
timeout.unref?.();
|
|
207
|
+
let idleTimeout;
|
|
208
|
+
const refreshIdleTimeout = () => {
|
|
209
|
+
if (idleTimeout)
|
|
210
|
+
clearTimeout(idleTimeout);
|
|
211
|
+
idleTimeout = setTimeout(() => {
|
|
212
|
+
idleTimedOut = true;
|
|
213
|
+
shared.lifecycleAbort.abort(createAbortError(`${shared.terms.provider} exec idle timed out`));
|
|
214
|
+
}, shared.terms.idleTimeoutMs);
|
|
215
|
+
idleTimeout.unref?.();
|
|
216
|
+
};
|
|
217
|
+
refreshIdleTimeout();
|
|
218
|
+
const requestLiveAbort = () => {
|
|
219
|
+
if (abortRequested)
|
|
220
|
+
return;
|
|
221
|
+
abortRequested = true;
|
|
222
|
+
requestAbort(shared.live);
|
|
223
|
+
};
|
|
224
|
+
shared.lifecycleAbort.signal.addEventListener("abort", requestLiveAbort, { once: true });
|
|
225
|
+
try {
|
|
226
|
+
if (shared.terms.projectionDir) {
|
|
227
|
+
const executionId = shared.terms.projectionExecutionId ?? createProjectionExecutionId();
|
|
228
|
+
const acquired = acquireAdmissionLock(shared.terms.projectionDir, process.pid, Date.now(), executionId);
|
|
229
|
+
if (acquired === "taken")
|
|
230
|
+
throw new FlowError("INTERNAL_STATE", "projection already has an active generation");
|
|
231
|
+
touchHeartbeat(shared.terms.projectionDir, Date.now());
|
|
232
|
+
demoteSubmittedTellsForReplay(shared.terms.projectionDir, executionId);
|
|
233
|
+
}
|
|
234
|
+
const launchTellFence = shared.terms.projectionDir && shared.terms.projectionExecutionId
|
|
235
|
+
? readGenerationLaunch(shared.terms.projectionDir, shared.terms.projectionExecutionId).inputs.tellFence
|
|
236
|
+
: undefined;
|
|
237
|
+
const tellFence = launchTellFence === undefined
|
|
238
|
+
? []
|
|
239
|
+
: Array.isArray(launchTellFence) && launchTellFence.every((tellId) => typeof tellId === "string")
|
|
240
|
+
? launchTellFence
|
|
241
|
+
: (() => { throw new FlowError("INTERNAL_STATE", "generation launch has an invalid tellFence"); })();
|
|
242
|
+
const launchSnapshot = shared.terms.projectionDir && shared.terms.projectionExecutionId
|
|
243
|
+
? snapshotReplayPrompt(shared.terms.projectionDir, shared.terms.prompt, tellFence, shared.terms.projectionExecutionId)
|
|
244
|
+
: undefined;
|
|
245
|
+
const launchTerms = launchSnapshot
|
|
246
|
+
? shared.terms.provider === "codex-sdk" && launchSnapshot.effort
|
|
247
|
+
? { ...shared.terms, prompt: launchSnapshot.prompt, threadOptions: { ...shared.terms.threadOptions, modelReasoningEffort: launchSnapshot.effort === "max" ? "xhigh" : launchSnapshot.effort } }
|
|
248
|
+
: { ...shared.terms, prompt: launchSnapshot.prompt }
|
|
249
|
+
: shared.terms;
|
|
250
|
+
const live = await startLiveProjection(launchTerms, shared.lifecycleAbort.signal, shared.resumeTerms, launchSnapshot?.tellIds);
|
|
251
|
+
// A constructed driver is not an SDK run acknowledgement. Tier-2 has no
|
|
252
|
+
// live steer surface, so the first provider event is the earliest harness
|
|
253
|
+
// observable start; a successful no-event completion is the only fallback.
|
|
254
|
+
const replayStartGate = launchSnapshot && shared.terms.provider !== "codex-app-server" && shared.terms.projectionDir && shared.terms.projectionExecutionId
|
|
255
|
+
? createReplayRunStartGate(shared.terms.projectionDir, shared.terms.projectionExecutionId, launchSnapshot.tellIds)
|
|
256
|
+
: undefined;
|
|
257
|
+
shared.onLive(live);
|
|
258
|
+
if (shared.lifecycleAbort.signal.aborted) {
|
|
259
|
+
requestLiveAbort();
|
|
260
|
+
}
|
|
261
|
+
const projectionDir = shared.terms.projectionDir;
|
|
262
|
+
if (projectionDir) {
|
|
263
|
+
// Supervisor mode: pump with heartbeat, checkpoint, claim inbox, deliver tells.
|
|
264
|
+
await pumpSupervisor(live, projectionDir, shared.publicEvents, (event) => {
|
|
265
|
+
replayStartGate?.observeProviderEvent();
|
|
266
|
+
refreshIdleTimeout();
|
|
267
|
+
processEvent(state, event);
|
|
268
|
+
});
|
|
269
|
+
}
|
|
270
|
+
else {
|
|
271
|
+
// Legacy mode: direct event loop without supervisor pump.
|
|
272
|
+
for await (const event of live.events) {
|
|
273
|
+
replayStartGate?.observeProviderEvent();
|
|
274
|
+
shared.publicEvents.push(event);
|
|
275
|
+
refreshIdleTimeout();
|
|
276
|
+
processEvent(state, event);
|
|
277
|
+
}
|
|
278
|
+
}
|
|
279
|
+
const completionResult = await live.completion.then((value) => ({ ok: true, value }), (error) => ({ ok: false, error }));
|
|
280
|
+
const completionOutcome = completionResult.ok ? completionResult.value : undefined;
|
|
281
|
+
if (state.failEvent) {
|
|
282
|
+
return failFromFailEvent(shared, state, state.failEvent);
|
|
283
|
+
}
|
|
284
|
+
if (!completionResult.ok) {
|
|
285
|
+
return classifyFailure(shared, state, completionResult.error, timedOut, idleTimedOut);
|
|
286
|
+
}
|
|
287
|
+
if (shared.lifecycleAbort.signal.aborted) {
|
|
288
|
+
return classifyFailure(shared, state, shared.lifecycleAbort.signal.reason, timedOut, idleTimedOut);
|
|
289
|
+
}
|
|
290
|
+
if (!state.doneEvent && !completionOutcome) {
|
|
291
|
+
return failWithError(shared, state, {
|
|
292
|
+
code: "SUBAGENT_EXEC_ERROR",
|
|
293
|
+
message: `${shared.terms.provider} exec ended before completion`,
|
|
294
|
+
timeoutMs: shared.terms.timeoutMs,
|
|
295
|
+
exitCode: null,
|
|
296
|
+
}, state.finalMessageCandidate);
|
|
297
|
+
}
|
|
298
|
+
const finalMessage = state.doneEvent?.finalMessage ??
|
|
299
|
+
completionOutcome?.finalMessage ??
|
|
300
|
+
state.finalMessageCandidate ??
|
|
301
|
+
renderCapturedText(state.outputBuffer).trim();
|
|
302
|
+
const session = state.doneEvent?.session ?? completionOutcome?.session;
|
|
303
|
+
if (!session) {
|
|
304
|
+
return failWithError(shared, state, {
|
|
305
|
+
code: "SUBAGENT_EXEC_ERROR",
|
|
306
|
+
message: `${shared.terms.provider} exec ended without a session handle`,
|
|
307
|
+
timeoutMs: shared.terms.timeoutMs,
|
|
308
|
+
exitCode: null,
|
|
309
|
+
}, finalMessage);
|
|
310
|
+
}
|
|
311
|
+
// A provider may complete successfully without emitting any observable
|
|
312
|
+
// events. Its successful completion is still a real run start.
|
|
313
|
+
replayStartGate?.observeSuccessfulCompletion();
|
|
314
|
+
if (launchSnapshot && shared.terms.projectionDir && shared.terms.projectionExecutionId) {
|
|
315
|
+
markReplayCompleted(shared.terms.projectionDir, shared.terms.projectionExecutionId);
|
|
316
|
+
}
|
|
317
|
+
return mintOutcome({
|
|
318
|
+
status: "completed",
|
|
319
|
+
finalMessage,
|
|
320
|
+
output: renderCapturedText(state.outputBuffer),
|
|
321
|
+
diagnostics: renderCapturedText(state.diagnosticsBuffer),
|
|
322
|
+
session,
|
|
323
|
+
warnings: [...state.warnings],
|
|
324
|
+
...(effectivePolicyFor(shared) ? { effectivePolicy: effectivePolicyFor(shared) } : {}),
|
|
325
|
+
});
|
|
326
|
+
}
|
|
327
|
+
catch (error) {
|
|
328
|
+
if (error instanceof FlowError) {
|
|
329
|
+
throw error;
|
|
330
|
+
}
|
|
331
|
+
return classifyFailure(shared, state, error, timedOut, idleTimedOut);
|
|
332
|
+
}
|
|
333
|
+
finally {
|
|
334
|
+
clearTimeout(timeout);
|
|
335
|
+
if (idleTimeout)
|
|
336
|
+
clearTimeout(idleTimeout);
|
|
337
|
+
shared.lifecycleAbort.signal.removeEventListener("abort", requestLiveAbort);
|
|
338
|
+
shared.terminal = true;
|
|
339
|
+
shared.publicEvents.close();
|
|
340
|
+
}
|
|
341
|
+
}
|
|
342
|
+
function processEvent(state, event) {
|
|
343
|
+
switch (event.type) {
|
|
344
|
+
case "output":
|
|
345
|
+
state.outputBuffer.append(`${JSON.stringify(event.value)}\n`);
|
|
346
|
+
return;
|
|
347
|
+
case "diagnostic":
|
|
348
|
+
state.diagnosticsBuffer.append(event.text);
|
|
349
|
+
return;
|
|
350
|
+
case "message":
|
|
351
|
+
state.finalMessageCandidate = event.text;
|
|
352
|
+
return;
|
|
353
|
+
case "warning":
|
|
354
|
+
state.warnings.push(event.warning);
|
|
355
|
+
return;
|
|
356
|
+
case "done":
|
|
357
|
+
state.doneEvent = event;
|
|
358
|
+
state.session = event.session;
|
|
359
|
+
if (event.finalMessage !== undefined) {
|
|
360
|
+
state.finalMessageCandidate = event.finalMessage;
|
|
361
|
+
}
|
|
362
|
+
return;
|
|
363
|
+
case "fail":
|
|
364
|
+
state.failEvent = event;
|
|
365
|
+
if (event.diagnostics) {
|
|
366
|
+
state.diagnosticsBuffer.append(event.diagnostics);
|
|
367
|
+
}
|
|
368
|
+
return;
|
|
369
|
+
}
|
|
370
|
+
}
|
|
371
|
+
function failFromFailEvent(shared, state, failEvent) {
|
|
372
|
+
const code = failEvent.code === "PROJECTION_STATE_ERROR" || failEvent.code === undefined
|
|
373
|
+
? "SUBAGENT_EXEC_ERROR"
|
|
374
|
+
: failEvent.code;
|
|
375
|
+
const diagnostics = failEvent.code === "PROJECTION_STATE_ERROR"
|
|
376
|
+
? [renderCapturedText(state.diagnosticsBuffer), "PROJECTION_STATE_ERROR"].filter(Boolean).join("\n")
|
|
377
|
+
: renderCapturedText(state.diagnosticsBuffer);
|
|
378
|
+
return failWithError(shared, state, {
|
|
379
|
+
code,
|
|
380
|
+
message: failEvent.message,
|
|
381
|
+
timeoutMs: shared.terms.timeoutMs,
|
|
382
|
+
exitCode: failEvent.exitCode ?? null,
|
|
383
|
+
}, state.finalMessageCandidate, failEvent.diagnostics ?? diagnostics);
|
|
384
|
+
}
|
|
385
|
+
function classifyFailure(shared, state, error, timedOut, idleTimedOut) {
|
|
386
|
+
if (shared.dismissed || shared.externallyAborted) {
|
|
387
|
+
return mintOutcome({
|
|
388
|
+
status: "dismissed",
|
|
389
|
+
finalMessage: state.finalMessageCandidate ?? "",
|
|
390
|
+
output: renderCapturedText(state.outputBuffer),
|
|
391
|
+
diagnostics: renderCapturedText(state.diagnosticsBuffer),
|
|
392
|
+
session: state.session ?? fallbackSession(shared),
|
|
393
|
+
warnings: [...state.warnings],
|
|
394
|
+
...(effectivePolicyFor(shared) ? { effectivePolicy: effectivePolicyFor(shared) } : {}),
|
|
395
|
+
});
|
|
396
|
+
}
|
|
397
|
+
if (timedOut) {
|
|
398
|
+
return failWithError(shared, state, {
|
|
399
|
+
code: "SUBAGENT_TIMEOUT",
|
|
400
|
+
message: `${shared.terms.provider} exec timed out`,
|
|
401
|
+
timeoutMs: shared.terms.timeoutMs,
|
|
402
|
+
exitCode: null,
|
|
403
|
+
}, state.finalMessageCandidate);
|
|
404
|
+
}
|
|
405
|
+
if (idleTimedOut) {
|
|
406
|
+
return failWithError(shared, state, {
|
|
407
|
+
code: "SUBAGENT_TIMEOUT",
|
|
408
|
+
message: `${shared.terms.provider} exec idle timed out`,
|
|
409
|
+
timeoutMs: shared.terms.idleTimeoutMs,
|
|
410
|
+
exitCode: null,
|
|
411
|
+
}, state.finalMessageCandidate);
|
|
412
|
+
}
|
|
413
|
+
if (error instanceof SubagentFailure) {
|
|
414
|
+
return failWithError(shared, state, {
|
|
415
|
+
code: error.code,
|
|
416
|
+
message: error.message,
|
|
417
|
+
timeoutMs: error.timeoutMs,
|
|
418
|
+
exitCode: error.exitCode,
|
|
419
|
+
}, state.finalMessageCandidate ?? error.message, error.diagnostics || renderCapturedText(state.diagnosticsBuffer), error.output || renderCapturedText(state.outputBuffer));
|
|
420
|
+
}
|
|
421
|
+
const message = error instanceof Error ? error.message : `Unknown ${shared.terms.provider} exec error`;
|
|
422
|
+
appendThrownMessage(state.diagnosticsBuffer, message);
|
|
423
|
+
return failWithError(shared, state, {
|
|
424
|
+
code: "SUBAGENT_EXEC_ERROR",
|
|
425
|
+
message,
|
|
426
|
+
timeoutMs: null,
|
|
427
|
+
exitCode: null,
|
|
428
|
+
}, state.finalMessageCandidate ?? message);
|
|
429
|
+
}
|
|
430
|
+
function failWithError(shared, state, error, finalMessage, diagnostics = renderCapturedText(state.diagnosticsBuffer), output = renderCapturedText(state.outputBuffer)) {
|
|
431
|
+
return mintOutcome({
|
|
432
|
+
status: "failed",
|
|
433
|
+
error,
|
|
434
|
+
finalMessage: finalMessage ?? error.message,
|
|
435
|
+
output,
|
|
436
|
+
diagnostics,
|
|
437
|
+
session: state.session ?? fallbackSession(shared),
|
|
438
|
+
warnings: [...state.warnings],
|
|
439
|
+
...(effectivePolicyFor(shared) ? { effectivePolicy: effectivePolicyFor(shared) } : {}),
|
|
440
|
+
});
|
|
441
|
+
}
|
|
442
|
+
function fallbackSession(shared) {
|
|
443
|
+
return shared.resumeTerms?.session ?? {
|
|
444
|
+
provider: shared.terms.provider,
|
|
445
|
+
version: SUBAGENT_SESSION_VERSION,
|
|
446
|
+
handle: null,
|
|
447
|
+
};
|
|
448
|
+
}
|
|
449
|
+
function appendThrownMessage(buffer, message) {
|
|
450
|
+
if (message.length === 0)
|
|
451
|
+
return;
|
|
452
|
+
const snapshot = buffer.snapshot().text;
|
|
453
|
+
if (snapshot.length > 0 && !snapshot.endsWith("\n")) {
|
|
454
|
+
buffer.append("\n");
|
|
455
|
+
}
|
|
456
|
+
buffer.append(message);
|
|
457
|
+
}
|
|
458
|
+
function admitResumeShape(provider, schema, session, resumePath) {
|
|
459
|
+
const envelopeResult = subagentSessionEnvelopeSchema.safeParse(session);
|
|
460
|
+
if (!envelopeResult.success) {
|
|
461
|
+
throw new FlowError("INVALID_RESPONSE_PATH", `Resume path file '${resumePath}' includes an invalid session envelope.`);
|
|
462
|
+
}
|
|
463
|
+
if (session.provider !== provider) {
|
|
464
|
+
throw new FlowError("INVALID_RESPONSE_PATH", `Resume path file '${resumePath}' was created with provider '${session.provider}', but the selected subagent resolves to '${provider}'. Cross-provider resume is not supported.`);
|
|
465
|
+
}
|
|
466
|
+
if (session.version !== SUBAGENT_SESSION_VERSION) {
|
|
467
|
+
throw new FlowError("INVALID_RESPONSE_PATH", `Resume path file '${resumePath}' uses unsupported session version '${session.version}' for provider '${session.provider}'.`);
|
|
468
|
+
}
|
|
469
|
+
const shapeResult = schema.safeParse(session.handle);
|
|
470
|
+
if (!shapeResult.success) {
|
|
471
|
+
throw new FlowError("INVALID_RESPONSE_PATH", `Resume path file '${resumePath}' includes an invalid session handle for provider '${session.provider}'.`);
|
|
472
|
+
}
|
|
473
|
+
return shapeResult.data;
|
|
474
|
+
}
|
|
475
|
+
async function startLiveProjection(terms, signal, resumeTerms, initialTellIds = []) {
|
|
476
|
+
const projectionCheckpoint = terms.projectionDir ? buildSyncCheckpointCallback(terms.projectionDir) : undefined;
|
|
477
|
+
let appServerInitialFencePending = terms.provider === "codex-app-server" && initialTellIds.length > 0;
|
|
478
|
+
const onSyncCheckpoint = terms.projectionDir
|
|
479
|
+
? (control, checkpoint) => {
|
|
480
|
+
if (appServerInitialFencePending && terms.projectionExecutionId) {
|
|
481
|
+
for (const tellId of initialTellIds) {
|
|
482
|
+
if (listTellIds(terms.projectionDir, "inflight").includes(tellId)) {
|
|
483
|
+
markTellSubmitted(terms.projectionDir, tellId, { executionId: terms.projectionExecutionId, fence: checkpoint.fence });
|
|
484
|
+
}
|
|
485
|
+
}
|
|
486
|
+
appServerInitialFencePending = false;
|
|
487
|
+
return;
|
|
488
|
+
}
|
|
489
|
+
projectionCheckpoint?.(control, checkpoint);
|
|
490
|
+
}
|
|
491
|
+
: "onCheckpoint" in terms
|
|
492
|
+
? terms.onCheckpoint
|
|
493
|
+
: undefined;
|
|
494
|
+
switch (terms.provider) {
|
|
495
|
+
case "pi": {
|
|
496
|
+
if (resumeTerms) {
|
|
497
|
+
const session = admitResumeShape("pi", piSessionSchema, resumeTerms.session, resumeTerms.resumePath);
|
|
498
|
+
return startPi(terms.prompt, terms.workdir, {
|
|
499
|
+
model: terms.model,
|
|
500
|
+
thinkingLevel: terms.thinkingLevel,
|
|
501
|
+
signal,
|
|
502
|
+
sessionFile: session.sessionFile,
|
|
503
|
+
appendSystemPromptOverride: terms.appendSystemPromptOverride,
|
|
504
|
+
onSyncCheckpoint,
|
|
505
|
+
onEvidence: terms.onEvidence,
|
|
506
|
+
});
|
|
507
|
+
}
|
|
508
|
+
return startPi(terms.prompt, terms.workdir, {
|
|
509
|
+
model: terms.model,
|
|
510
|
+
thinkingLevel: terms.thinkingLevel,
|
|
511
|
+
signal,
|
|
512
|
+
appendSystemPromptOverride: terms.appendSystemPromptOverride,
|
|
513
|
+
onSyncCheckpoint,
|
|
514
|
+
onEvidence: terms.onEvidence,
|
|
515
|
+
});
|
|
516
|
+
}
|
|
517
|
+
case "claude-agent-sdk": {
|
|
518
|
+
if (resumeTerms) {
|
|
519
|
+
const session = admitResumeShape("claude-agent-sdk", claudeSessionSchema, resumeTerms.session, resumeTerms.resumePath);
|
|
520
|
+
return startClaudeAgentSdk(terms.prompt, terms.workdir, {
|
|
521
|
+
model: terms.model,
|
|
522
|
+
executable: terms.executable,
|
|
523
|
+
effort: terms.effort,
|
|
524
|
+
signal,
|
|
525
|
+
permissionMode: terms.permissionMode,
|
|
526
|
+
settingSources: terms.settingSources,
|
|
527
|
+
systemPrompt: terms.systemPrompt,
|
|
528
|
+
sessionId: session.sessionId,
|
|
529
|
+
onSyncCheckpoint,
|
|
530
|
+
onEvidence: terms.onEvidence,
|
|
531
|
+
});
|
|
532
|
+
}
|
|
533
|
+
return startClaudeAgentSdk(terms.prompt, terms.workdir, {
|
|
534
|
+
model: terms.model,
|
|
535
|
+
executable: terms.executable,
|
|
536
|
+
effort: terms.effort,
|
|
537
|
+
signal,
|
|
538
|
+
permissionMode: terms.permissionMode,
|
|
539
|
+
settingSources: terms.settingSources,
|
|
540
|
+
systemPrompt: terms.systemPrompt,
|
|
541
|
+
onSyncCheckpoint,
|
|
542
|
+
onEvidence: terms.onEvidence,
|
|
543
|
+
});
|
|
544
|
+
}
|
|
545
|
+
case "codex-app-server": {
|
|
546
|
+
if (resumeTerms) {
|
|
547
|
+
const session = admitResumeShape("codex-app-server", codexAppServerSessionSchema, resumeTerms.session, resumeTerms.resumePath);
|
|
548
|
+
return startCodexAppServer(terms.prompt, terms.workdir, {
|
|
549
|
+
model: terms.model,
|
|
550
|
+
effort: terms.effort,
|
|
551
|
+
profile: terms.profile,
|
|
552
|
+
config: terms.config,
|
|
553
|
+
executable: terms.executable,
|
|
554
|
+
signal,
|
|
555
|
+
threadId: session.threadId,
|
|
556
|
+
codexHome: session.codexHome,
|
|
557
|
+
developerInstructions: terms.developerInstructions,
|
|
558
|
+
onSyncCheckpoint,
|
|
559
|
+
onEvidence: terms.onEvidence,
|
|
560
|
+
});
|
|
561
|
+
}
|
|
562
|
+
return startCodexAppServer(terms.prompt, terms.workdir, {
|
|
563
|
+
model: terms.model,
|
|
564
|
+
effort: terms.effort,
|
|
565
|
+
profile: terms.profile,
|
|
566
|
+
config: terms.config,
|
|
567
|
+
executable: terms.executable,
|
|
568
|
+
signal,
|
|
569
|
+
developerInstructions: terms.developerInstructions,
|
|
570
|
+
onSyncCheckpoint,
|
|
571
|
+
onEvidence: terms.onEvidence,
|
|
572
|
+
});
|
|
573
|
+
}
|
|
574
|
+
case "codex-sdk": {
|
|
575
|
+
if (resumeTerms) {
|
|
576
|
+
const session = admitResumeShape("codex-sdk", codexThreadSchema, resumeTerms.session, resumeTerms.resumePath);
|
|
577
|
+
return startCodexSdk(terms.prompt, terms.workdir, {
|
|
578
|
+
model: terms.model,
|
|
579
|
+
profile: terms.profile,
|
|
580
|
+
config: terms.config,
|
|
581
|
+
threadOptions: terms.threadOptions,
|
|
582
|
+
executable: terms.executable,
|
|
583
|
+
signal,
|
|
584
|
+
threadId: session.threadId,
|
|
585
|
+
onEvidence: terms.onEvidence,
|
|
586
|
+
});
|
|
587
|
+
}
|
|
588
|
+
return startCodexSdk(terms.prompt, terms.workdir, {
|
|
589
|
+
model: terms.model,
|
|
590
|
+
profile: terms.profile,
|
|
591
|
+
config: terms.config,
|
|
592
|
+
threadOptions: terms.threadOptions,
|
|
593
|
+
executable: terms.executable,
|
|
594
|
+
signal,
|
|
595
|
+
onEvidence: terms.onEvidence,
|
|
596
|
+
});
|
|
597
|
+
}
|
|
598
|
+
case "opencode-sdk": {
|
|
599
|
+
if (resumeTerms) {
|
|
600
|
+
const session = admitResumeShape("opencode-sdk", opencodeSessionSchema, resumeTerms.session, resumeTerms.resumePath);
|
|
601
|
+
return startOpencode(terms.prompt, terms.workdir, {
|
|
602
|
+
model: terms.model,
|
|
603
|
+
executable: terms.executable,
|
|
604
|
+
signal,
|
|
605
|
+
sessionId: session.sessionId,
|
|
606
|
+
onEvidence: terms.onEvidence,
|
|
607
|
+
});
|
|
608
|
+
}
|
|
609
|
+
return startOpencode(terms.prompt, terms.workdir, {
|
|
610
|
+
model: terms.model,
|
|
611
|
+
executable: terms.executable,
|
|
612
|
+
signal,
|
|
613
|
+
onEvidence: terms.onEvidence,
|
|
614
|
+
});
|
|
615
|
+
}
|
|
616
|
+
default:
|
|
617
|
+
return assertNever(terms);
|
|
618
|
+
}
|
|
619
|
+
}
|
|
620
|
+
function assertNever(_value) {
|
|
621
|
+
throw new Error("Unhandled provider");
|
|
622
|
+
}
|