@astrosheep/keiyaku 0.1.86 → 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 +15 -23
- 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 -212
- 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 +206 -874
- 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 +2 -10
- package/build/config/{schema.js → env.js} +120 -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 -2
- 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 +15 -8
- package/skills/keiyaku/SKILL.md +17 -13
- package/build/agents/claude-agent-sdk-exec.js +0 -248
- package/build/agents/codex-cli-exec.js +0 -235
- package/build/agents/codex-sdk-exec.js +0 -216
- 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/round-runner.js +0 -52
- package/build/config/apply-argument-descriptions.js +0 -16
- package/build/config/architect-hints.js +0 -371
- 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 -530
- 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 -68
- package/build/tools/status/index.js +0 -16
- package/build/tools/status/read.js +0 -152
- package/build/tools/summon/index.js +0 -27
- package/build/tools/summon/run.js +0 -79
- /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,152 @@
|
|
|
1
|
+
import * as fs from "node:fs";
|
|
2
|
+
import * as path from "node:path";
|
|
3
|
+
import { claimAllInbox, claimFencedTells, listTellIds, markTellSubmitted, markTellConsumed, readTellSubmitted, readTellOriginal, readAdmissionLock, isTellCausallyConsumed, touchHeartbeat as touchHeartbeatFile, } from "../../core/projection-core.js";
|
|
4
|
+
/** Tier-2 boundary snapshot. No tells leaves prompt byte-identical. */
|
|
5
|
+
export function snapshotReplayPrompt(projectionDirectory, prompt, tellFence, executionId) {
|
|
6
|
+
const tellIds = claimFencedTells(projectionDirectory, tellFence, executionId);
|
|
7
|
+
if (tellIds.length === 0)
|
|
8
|
+
return { prompt, tellIds };
|
|
9
|
+
const originals = tellIds.map((tellId) => readTellOriginal(projectionDirectory, "inflight", tellId));
|
|
10
|
+
const frames = originals.map((original, index) => `--- tell ${tellIds[index]} ---\n${original.text}`);
|
|
11
|
+
const effort = originals.reduce((value, original) => original.effort ?? value, undefined);
|
|
12
|
+
return { prompt: `${prompt}\n\n${frames.join("\n\n")}`, tellIds, ...(effort ? { effort } : {}) };
|
|
13
|
+
}
|
|
14
|
+
/** Tier-2 has no steer acknowledgement: actual driver start is its ordinal-zero submission point. */
|
|
15
|
+
export function markReplayStarted(projectionDirectory, executionId, tellIds) {
|
|
16
|
+
for (const tellId of tellIds) {
|
|
17
|
+
if (listTellIds(projectionDirectory, "inflight").includes(tellId)) {
|
|
18
|
+
markTellSubmitted(projectionDirectory, tellId, { executionId, fence: { kind: "ordinal", ordinal: 0 } });
|
|
19
|
+
}
|
|
20
|
+
}
|
|
21
|
+
}
|
|
22
|
+
/** A successful replay terminal is checkpoint ordinal one; failures intentionally do not consume. */
|
|
23
|
+
export function markReplayCompleted(projectionDirectory, executionId) {
|
|
24
|
+
for (const tellId of listTellIds(projectionDirectory, "submitted")) {
|
|
25
|
+
const submitted = readTellSubmitted(projectionDirectory, tellId);
|
|
26
|
+
if (submitted.executionId === executionId && submitted.fence && isTellCausallyConsumed(submitted.fence, { kind: "ordinal", ordinal: 1 })) {
|
|
27
|
+
markTellConsumed(projectionDirectory, tellId);
|
|
28
|
+
}
|
|
29
|
+
}
|
|
30
|
+
}
|
|
31
|
+
/**
|
|
32
|
+
* Tier-2's only submission acknowledgement. A created driver is deliberately
|
|
33
|
+
* insufficient: observe the first provider event, or a successful completion
|
|
34
|
+
* from a provider that emitted no events.
|
|
35
|
+
*/
|
|
36
|
+
export function createReplayRunStartGate(projectionDirectory, executionId, tellIds) {
|
|
37
|
+
let started = false;
|
|
38
|
+
const start = () => {
|
|
39
|
+
if (started)
|
|
40
|
+
return;
|
|
41
|
+
markReplayStarted(projectionDirectory, executionId, tellIds);
|
|
42
|
+
started = true;
|
|
43
|
+
};
|
|
44
|
+
return { observeProviderEvent: start, observeSuccessfulCompletion: start };
|
|
45
|
+
}
|
|
46
|
+
/** Heartbeat throttle: no more than one utimes call per 5000ms. */
|
|
47
|
+
export const HEARTBEAT_MIN_INTERVAL_MS = 5_000;
|
|
48
|
+
/**
|
|
49
|
+
* Supervisor pump: iterates handle.events, forwards to publicChannel,
|
|
50
|
+
* touches heartbeat (throttled), checks inbox at checkpoints,
|
|
51
|
+
* and on final sweep publishes verdict.
|
|
52
|
+
*/
|
|
53
|
+
export async function pumpSupervisor(handle, projectionDirectory, publicChannel, onEvent) {
|
|
54
|
+
let lastHeartbeatMs = 0;
|
|
55
|
+
let heartbeatDegraded = false;
|
|
56
|
+
const heartbeat = () => {
|
|
57
|
+
const now = Date.now();
|
|
58
|
+
if (now - lastHeartbeatMs < HEARTBEAT_MIN_INTERVAL_MS)
|
|
59
|
+
return;
|
|
60
|
+
lastHeartbeatMs = now;
|
|
61
|
+
try {
|
|
62
|
+
touchHeartbeatFile(projectionDirectory, now);
|
|
63
|
+
heartbeatDegraded = false;
|
|
64
|
+
}
|
|
65
|
+
catch (error) {
|
|
66
|
+
if (!heartbeatDegraded) {
|
|
67
|
+
publicChannel.push({
|
|
68
|
+
type: "diagnostic",
|
|
69
|
+
text: `heartbeat degraded: ${String(error)}`,
|
|
70
|
+
});
|
|
71
|
+
}
|
|
72
|
+
heartbeatDegraded = true;
|
|
73
|
+
}
|
|
74
|
+
};
|
|
75
|
+
const heartbeatTimer = setInterval(heartbeat, HEARTBEAT_MIN_INTERVAL_MS);
|
|
76
|
+
const stopHeartbeatTimer = () => clearInterval(heartbeatTimer);
|
|
77
|
+
try {
|
|
78
|
+
for await (const event of handle.events) {
|
|
79
|
+
publicChannel.push(event);
|
|
80
|
+
onEvent?.(event);
|
|
81
|
+
heartbeat();
|
|
82
|
+
if (event.type === "done" || event.type === "fail")
|
|
83
|
+
stopHeartbeatTimer();
|
|
84
|
+
}
|
|
85
|
+
}
|
|
86
|
+
catch (error) {
|
|
87
|
+
if (error instanceof Error && error.code === "PROJECTION_STATE_ERROR") {
|
|
88
|
+
publicChannel.push({
|
|
89
|
+
type: "fail",
|
|
90
|
+
code: "PROJECTION_STATE_ERROR",
|
|
91
|
+
message: error.message,
|
|
92
|
+
});
|
|
93
|
+
}
|
|
94
|
+
else {
|
|
95
|
+
publicChannel.push({
|
|
96
|
+
type: "diagnostic",
|
|
97
|
+
text: `pump error: ${String(error)}`,
|
|
98
|
+
});
|
|
99
|
+
}
|
|
100
|
+
}
|
|
101
|
+
finally {
|
|
102
|
+
stopHeartbeatTimer();
|
|
103
|
+
}
|
|
104
|
+
}
|
|
105
|
+
function readTellOriginalText(projectionDirPath, tellId, layer = "inflight") {
|
|
106
|
+
const content = JSON.parse(fs.readFileSync(path.join(projectionDirPath, layer, tellId), "utf8"));
|
|
107
|
+
return content.text;
|
|
108
|
+
}
|
|
109
|
+
/**
|
|
110
|
+
* Build an onSyncCheckpoint callback that claims all inbox tells
|
|
111
|
+
* and delivers them via the given SteerControl.
|
|
112
|
+
* Intended for use inside adapter start() calls as the onSyncCheckpoint callback.
|
|
113
|
+
*/
|
|
114
|
+
export function buildSyncCheckpointCallback(projectionDirectory) {
|
|
115
|
+
const submitting = new Set();
|
|
116
|
+
return (control, checkpoint) => {
|
|
117
|
+
claimAllInbox(projectionDirectory);
|
|
118
|
+
const executionId = readAdmissionLock(projectionDirectory)?.executionId;
|
|
119
|
+
for (const tellId of listTellIds(projectionDirectory, "inflight")) {
|
|
120
|
+
if (submitting.has(tellId))
|
|
121
|
+
continue;
|
|
122
|
+
submitting.add(tellId);
|
|
123
|
+
const original = readTellOriginal(projectionDirectory, "inflight", tellId);
|
|
124
|
+
const originalText = original.text;
|
|
125
|
+
const tell = {
|
|
126
|
+
id: tellId,
|
|
127
|
+
text: `--- tell ${tellId} ---\n${originalText}`,
|
|
128
|
+
...(original.effort ? { effort: original.effort } : {}),
|
|
129
|
+
};
|
|
130
|
+
void control.enqueue(tell)
|
|
131
|
+
.then((ack) => {
|
|
132
|
+
if (!executionId)
|
|
133
|
+
throw new Error("projection checkpoint has no execution admission");
|
|
134
|
+
if (!ack)
|
|
135
|
+
throw new Error("provider enqueue resolved without TellSubmitAck");
|
|
136
|
+
markTellSubmitted(projectionDirectory, tellId, { executionId, fence: ack.fence });
|
|
137
|
+
})
|
|
138
|
+
.catch(() => {
|
|
139
|
+
// Leave in inflight for the next checkpoint.
|
|
140
|
+
})
|
|
141
|
+
.finally(() => submitting.delete(tellId));
|
|
142
|
+
}
|
|
143
|
+
if (!executionId)
|
|
144
|
+
return;
|
|
145
|
+
for (const tellId of listTellIds(projectionDirectory, "submitted")) {
|
|
146
|
+
const submitted = readTellSubmitted(projectionDirectory, tellId);
|
|
147
|
+
if (submitted.executionId === executionId && submitted.fence && isTellCausallyConsumed(submitted.fence, checkpoint.fence)) {
|
|
148
|
+
markTellConsumed(projectionDirectory, tellId);
|
|
149
|
+
}
|
|
150
|
+
}
|
|
151
|
+
};
|
|
152
|
+
}
|
|
@@ -0,0 +1,68 @@
|
|
|
1
|
+
import { createHash } from "node:crypto";
|
|
2
|
+
import { getConfig } from "../../config/env.js";
|
|
3
|
+
import { DISABLE_SUBAGENT_SPAWN_ENV_KEY } from "../../config/env-keys.js";
|
|
4
|
+
export function sha256Utf8(value) {
|
|
5
|
+
const bytes = Buffer.from(value, "utf8");
|
|
6
|
+
return { sha256: createHash("sha256").update(bytes).digest("hex"), byteLength: bytes.byteLength };
|
|
7
|
+
}
|
|
8
|
+
/**
|
|
9
|
+
* Observer calls are synchronous at the native boundary. Their return values
|
|
10
|
+
* never gate provider progress; rejected thenables are reduced to diagnostics.
|
|
11
|
+
*/
|
|
12
|
+
export function emitProviderEvidence(callback, evidence, diagnostic) {
|
|
13
|
+
if (!callback)
|
|
14
|
+
return;
|
|
15
|
+
const reportFailure = () => {
|
|
16
|
+
try {
|
|
17
|
+
diagnostic?.("provider evidence callback failed");
|
|
18
|
+
}
|
|
19
|
+
catch {
|
|
20
|
+
// Evidence and its diagnostics are observation-only.
|
|
21
|
+
}
|
|
22
|
+
};
|
|
23
|
+
try {
|
|
24
|
+
const returned = callback(evidence);
|
|
25
|
+
if (returned !== null &&
|
|
26
|
+
(typeof returned === "object" || typeof returned === "function") &&
|
|
27
|
+
"then" in returned &&
|
|
28
|
+
typeof returned.then === "function") {
|
|
29
|
+
void Promise.resolve(returned).catch(reportFailure);
|
|
30
|
+
}
|
|
31
|
+
}
|
|
32
|
+
catch {
|
|
33
|
+
reportFailure();
|
|
34
|
+
}
|
|
35
|
+
}
|
|
36
|
+
export function createAbortError(message) {
|
|
37
|
+
const error = new Error(message);
|
|
38
|
+
error.name = "AbortError";
|
|
39
|
+
return error;
|
|
40
|
+
}
|
|
41
|
+
export function renderTruncationSuffix(truncatedChars) {
|
|
42
|
+
if (truncatedChars <= 0)
|
|
43
|
+
return "";
|
|
44
|
+
return `\n...[truncated ${truncatedChars} chars from start]...`;
|
|
45
|
+
}
|
|
46
|
+
export function renderCapturedText(buffer) {
|
|
47
|
+
const snapshot = buffer.snapshot();
|
|
48
|
+
return `${snapshot.text}${renderTruncationSuffix(snapshot.truncatedChars)}`;
|
|
49
|
+
}
|
|
50
|
+
export function linkAbortSignal(signal, controller, onAbort) {
|
|
51
|
+
if (!signal)
|
|
52
|
+
return undefined;
|
|
53
|
+
const handler = () => {
|
|
54
|
+
onAbort();
|
|
55
|
+
controller.abort(signal.reason);
|
|
56
|
+
};
|
|
57
|
+
signal.addEventListener("abort", handler, { once: true });
|
|
58
|
+
if (signal.aborted) {
|
|
59
|
+
handler();
|
|
60
|
+
}
|
|
61
|
+
return () => signal.removeEventListener("abort", handler);
|
|
62
|
+
}
|
|
63
|
+
export function buildSubagentEnv() {
|
|
64
|
+
return {
|
|
65
|
+
...getConfig().runtimeEnv,
|
|
66
|
+
[DISABLE_SUBAGENT_SPAWN_ENV_KEY]: "1",
|
|
67
|
+
};
|
|
68
|
+
}
|
package/build/agents/index.js
CHANGED
|
@@ -1,222 +1,114 @@
|
|
|
1
|
-
import { z } from "zod";
|
|
2
1
|
import { FlowError } from "../flow-error.js";
|
|
3
|
-
import {
|
|
4
|
-
import {
|
|
5
|
-
import {
|
|
6
|
-
import {
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
const claudeSessionHandleSchema = z.object({ sessionId: z.string().trim().min(1) });
|
|
13
|
-
const geminiSessionHandleSchema = z.object({ sessionId: z.string().trim().min(1) });
|
|
14
|
-
const opencodeSessionHandleSchema = z.object({ sessionId: z.string().trim().min(1) });
|
|
15
|
-
const codexCliAdapter = {
|
|
16
|
-
provider: "codex-cli",
|
|
17
|
-
handleSchema: codexThreadHandleSchema,
|
|
18
|
-
async run(prompt, cwd, config) {
|
|
19
|
-
const result = await runCodexCliExec(prompt, cwd, {
|
|
20
|
-
model: config.model,
|
|
21
|
-
profile: config.profile,
|
|
22
|
-
config: config.config,
|
|
23
|
-
executable: config.executable,
|
|
24
|
-
signal: config.signal,
|
|
25
|
-
});
|
|
26
|
-
return result;
|
|
27
|
-
},
|
|
28
|
-
async resume(prompt, cwd, config, handle) {
|
|
29
|
-
const result = await runCodexCliExec(prompt, cwd, {
|
|
30
|
-
model: config.model,
|
|
31
|
-
profile: config.profile,
|
|
32
|
-
config: config.config,
|
|
33
|
-
executable: config.executable,
|
|
34
|
-
signal: config.signal,
|
|
35
|
-
threadId: handle.threadId,
|
|
36
|
-
});
|
|
37
|
-
return result;
|
|
38
|
-
},
|
|
39
|
-
};
|
|
40
|
-
const codexSdkAdapter = {
|
|
41
|
-
provider: "codex-sdk",
|
|
42
|
-
handleSchema: codexThreadHandleSchema,
|
|
43
|
-
async run(prompt, cwd, config) {
|
|
44
|
-
const result = await runCodexSdkExec(prompt, cwd, {
|
|
45
|
-
model: config.model,
|
|
46
|
-
profile: config.profile,
|
|
47
|
-
config: config.config,
|
|
48
|
-
executable: config.executable,
|
|
49
|
-
signal: config.signal,
|
|
50
|
-
});
|
|
51
|
-
return result;
|
|
52
|
-
},
|
|
53
|
-
async resume(prompt, cwd, config, handle) {
|
|
54
|
-
const result = await runCodexSdkExec(prompt, cwd, {
|
|
55
|
-
model: config.model,
|
|
56
|
-
profile: config.profile,
|
|
57
|
-
config: config.config,
|
|
58
|
-
executable: config.executable,
|
|
59
|
-
signal: config.signal,
|
|
60
|
-
threadId: handle.threadId,
|
|
61
|
-
});
|
|
62
|
-
return result;
|
|
63
|
-
},
|
|
64
|
-
};
|
|
65
|
-
const claudeAgentSdkAdapter = {
|
|
66
|
-
provider: "claude-agent-sdk",
|
|
67
|
-
handleSchema: claudeSessionHandleSchema,
|
|
68
|
-
async run(prompt, cwd, config) {
|
|
69
|
-
const result = await runClaudeAgentSdkExec(prompt, cwd, {
|
|
70
|
-
model: config.model,
|
|
71
|
-
executable: config.executable,
|
|
72
|
-
effort: config.effort,
|
|
73
|
-
signal: config.signal,
|
|
74
|
-
permissionMode: config.permissionMode,
|
|
75
|
-
settingSources: config.settingSources,
|
|
76
|
-
systemPromptFile: config.systemPromptFile,
|
|
77
|
-
});
|
|
78
|
-
return result;
|
|
79
|
-
},
|
|
80
|
-
async resume(prompt, cwd, config, handle) {
|
|
81
|
-
const result = await runClaudeAgentSdkExec(prompt, cwd, {
|
|
82
|
-
model: config.model,
|
|
83
|
-
executable: config.executable,
|
|
84
|
-
effort: config.effort,
|
|
85
|
-
signal: config.signal,
|
|
86
|
-
permissionMode: config.permissionMode,
|
|
87
|
-
settingSources: config.settingSources,
|
|
88
|
-
systemPromptFile: config.systemPromptFile,
|
|
89
|
-
sessionId: handle.sessionId,
|
|
90
|
-
});
|
|
91
|
-
return result;
|
|
92
|
-
},
|
|
93
|
-
};
|
|
94
|
-
const geminiCliCoreAdapter = {
|
|
95
|
-
provider: "gemini-cli-core",
|
|
96
|
-
handleSchema: geminiSessionHandleSchema,
|
|
97
|
-
run(prompt, cwd, config) {
|
|
98
|
-
return runGeminiCliCoreExec(prompt, cwd, {
|
|
99
|
-
model: config.model,
|
|
100
|
-
signal: config.signal,
|
|
101
|
-
systemPromptFile: config.systemPromptFile,
|
|
102
|
-
});
|
|
103
|
-
},
|
|
104
|
-
resume(prompt, cwd, config, handle) {
|
|
105
|
-
return runGeminiCliCoreExec(prompt, cwd, {
|
|
106
|
-
model: config.model,
|
|
107
|
-
signal: config.signal,
|
|
108
|
-
sessionId: handle.sessionId,
|
|
109
|
-
systemPromptFile: config.systemPromptFile,
|
|
110
|
-
});
|
|
111
|
-
},
|
|
112
|
-
};
|
|
113
|
-
const opencodeSdkAdapter = {
|
|
114
|
-
provider: "opencode-sdk",
|
|
115
|
-
handleSchema: opencodeSessionHandleSchema,
|
|
116
|
-
async run(prompt, cwd, config) {
|
|
117
|
-
const result = await runOpencodeExec(prompt, cwd, {
|
|
118
|
-
model: config.model,
|
|
119
|
-
executable: config.executable,
|
|
120
|
-
signal: config.signal,
|
|
121
|
-
});
|
|
122
|
-
return result;
|
|
123
|
-
},
|
|
124
|
-
async resume(prompt, cwd, config, handle) {
|
|
125
|
-
const result = await runOpencodeExec(prompt, cwd, {
|
|
126
|
-
model: config.model,
|
|
127
|
-
executable: config.executable,
|
|
128
|
-
signal: config.signal,
|
|
129
|
-
sessionId: handle.sessionId,
|
|
130
|
-
});
|
|
131
|
-
return result;
|
|
132
|
-
},
|
|
133
|
-
};
|
|
134
|
-
const SUBAGENT_ADAPTERS = {
|
|
135
|
-
"codex-cli": codexCliAdapter,
|
|
136
|
-
"codex-sdk": codexSdkAdapter,
|
|
137
|
-
"claude-agent-sdk": claudeAgentSdkAdapter,
|
|
138
|
-
"gemini-cli-core": geminiCliCoreAdapter,
|
|
139
|
-
"opencode-sdk": opencodeSdkAdapter,
|
|
140
|
-
};
|
|
141
|
-
function getSubagentAdapter(provider) {
|
|
142
|
-
return SUBAGENT_ADAPTERS[provider];
|
|
2
|
+
import { EFFORT_LEVELS } from "../keiyaku.js";
|
|
3
|
+
import { resolveEffectiveCodexSdkProfile } from "./effective-policy.js";
|
|
4
|
+
import { getConfig } from "../config/env.js";
|
|
5
|
+
import { selectSubagent } from "./selector.js";
|
|
6
|
+
export { formatAvailableAgentNames } from "./selector.js";
|
|
7
|
+
export function createAbortError(message) {
|
|
8
|
+
const error = new Error(message);
|
|
9
|
+
error.name = "AbortError";
|
|
10
|
+
return error;
|
|
143
11
|
}
|
|
144
|
-
function
|
|
145
|
-
|
|
146
|
-
try {
|
|
147
|
-
parsedSession = JSON.parse(sessionText);
|
|
148
|
-
}
|
|
149
|
-
catch {
|
|
150
|
-
throw new FlowError("INVALID_RESPONSE_PATH", `Resume path file '${resumePath}' includes invalid 'session' metadata; this conversation cannot be resumed.`);
|
|
151
|
-
}
|
|
152
|
-
const sessionResult = subagentSessionEnvelopeSchema.safeParse(parsedSession);
|
|
153
|
-
if (!sessionResult.success) {
|
|
154
|
-
throw new FlowError("INVALID_RESPONSE_PATH", `Resume path file '${resumePath}' includes invalid 'session' metadata; this conversation cannot be resumed.`);
|
|
155
|
-
}
|
|
156
|
-
return sessionResult.data;
|
|
12
|
+
export function coerceString(value) {
|
|
13
|
+
return typeof value === "string" && value.trim().length > 0 ? value : undefined;
|
|
157
14
|
}
|
|
158
|
-
|
|
159
|
-
const
|
|
160
|
-
|
|
161
|
-
return parseSessionEnvelopeText(sessionText, input.resumePath);
|
|
162
|
-
}
|
|
163
|
-
return undefined;
|
|
15
|
+
function codexEffort(config) {
|
|
16
|
+
const value = config?.model_reasoning_effort;
|
|
17
|
+
return typeof value === "string" && EFFORT_LEVELS.includes(value) ? value : undefined;
|
|
164
18
|
}
|
|
165
|
-
function
|
|
166
|
-
|
|
167
|
-
if (!envelopeResult.success) {
|
|
168
|
-
throw new FlowError("INVALID_RESPONSE_PATH", `Resume path file '${resumePath}' includes an invalid session envelope.`);
|
|
169
|
-
}
|
|
170
|
-
if (session.provider !== adapter.provider) {
|
|
171
|
-
throw new FlowError("INVALID_RESPONSE_PATH", `Resume path file '${resumePath}' was created with provider '${session.provider}', but the selected subagent resolves to '${adapter.provider}'. Cross-provider resume is not supported.`);
|
|
172
|
-
}
|
|
173
|
-
if (session.version !== SUBAGENT_SESSION_VERSION) {
|
|
174
|
-
throw new FlowError("INVALID_RESPONSE_PATH", `Resume path file '${resumePath}' uses unsupported session version '${session.version}' for provider '${session.provider}'.`);
|
|
175
|
-
}
|
|
176
|
-
const handleResult = adapter.handleSchema.safeParse(session.handle);
|
|
177
|
-
if (!handleResult.success) {
|
|
178
|
-
throw new FlowError("INVALID_RESPONSE_PATH", `Resume path file '${resumePath}' includes an invalid session handle for provider '${session.provider}'.`);
|
|
179
|
-
}
|
|
180
|
-
return handleResult.data;
|
|
19
|
+
export async function resolveSubagentConfig(agentName, cwd) {
|
|
20
|
+
return (await selectSubagent(agentName, cwd)).profile;
|
|
181
21
|
}
|
|
182
|
-
function
|
|
183
|
-
|
|
22
|
+
function maxCaptureCharsForProvider(provider) {
|
|
23
|
+
const config = getConfig();
|
|
24
|
+
switch (provider) {
|
|
25
|
+
case "codex-sdk":
|
|
26
|
+
return config.codex.execMaxCaptureChars;
|
|
27
|
+
case "codex-app-server":
|
|
28
|
+
return config.codex.execMaxCaptureChars;
|
|
29
|
+
case "claude-agent-sdk":
|
|
30
|
+
return config.claude.execMaxCaptureChars;
|
|
31
|
+
case "opencode-sdk":
|
|
32
|
+
return config.opencode.execMaxCaptureChars;
|
|
33
|
+
case "pi":
|
|
34
|
+
return config.codex.execMaxCaptureChars;
|
|
35
|
+
}
|
|
184
36
|
}
|
|
185
|
-
function
|
|
186
|
-
const
|
|
187
|
-
|
|
188
|
-
:
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
37
|
+
export function buildSummonTerms(config, prompt, cwd, options) {
|
|
38
|
+
const runtimeConfig = getConfig();
|
|
39
|
+
const baseTerms = {
|
|
40
|
+
workdir: cwd,
|
|
41
|
+
prompt,
|
|
42
|
+
timeoutMs: options.timeoutMs ?? runtimeConfig.subagent.execTimeoutMs,
|
|
43
|
+
idleTimeoutMs: options.idleTimeoutMs ?? runtimeConfig.subagent.execIdleTimeoutMs,
|
|
44
|
+
maxCaptureChars: maxCaptureCharsForProvider(config.provider),
|
|
45
|
+
signal: options.signal,
|
|
46
|
+
projectionDir: options.projectionDir,
|
|
47
|
+
projectionExecutionId: options.projectionExecutionId,
|
|
193
48
|
};
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
49
|
+
const body = options.akumaBody;
|
|
50
|
+
const replayPrompt = body ? `[Akuma system prompt]\n${body}\n\n[Operator prompt]\n${prompt}` : prompt;
|
|
51
|
+
switch (config.provider) {
|
|
52
|
+
case "codex-sdk":
|
|
53
|
+
{
|
|
54
|
+
const effective = "effectivePolicy" in config && config.threadOptions
|
|
55
|
+
? config
|
|
56
|
+
: resolveEffectiveCodexSdkProfile(config, cwd, {});
|
|
57
|
+
return {
|
|
58
|
+
...baseTerms, prompt: replayPrompt,
|
|
59
|
+
provider: "codex-sdk",
|
|
60
|
+
model: effective.model,
|
|
61
|
+
profile: effective.profile,
|
|
62
|
+
config: effective.config,
|
|
63
|
+
threadOptions: effective.threadOptions,
|
|
64
|
+
effectivePolicy: effective.effectivePolicy,
|
|
65
|
+
executable: effective.executable,
|
|
66
|
+
};
|
|
67
|
+
}
|
|
68
|
+
case "codex-app-server":
|
|
69
|
+
return {
|
|
70
|
+
...baseTerms,
|
|
71
|
+
provider: "codex-app-server",
|
|
72
|
+
model: config.model,
|
|
73
|
+
effort: codexEffort(config.config),
|
|
74
|
+
profile: config.profile,
|
|
75
|
+
config: config.config,
|
|
76
|
+
executable: config.executable,
|
|
77
|
+
developerInstructions: body || undefined,
|
|
78
|
+
onCheckpoint() { },
|
|
79
|
+
};
|
|
80
|
+
case "claude-agent-sdk":
|
|
81
|
+
return {
|
|
82
|
+
...baseTerms,
|
|
83
|
+
provider: "claude-agent-sdk",
|
|
84
|
+
model: config.model,
|
|
85
|
+
executable: config.executable,
|
|
86
|
+
effort: config.effort,
|
|
87
|
+
permissionMode: config.permissionMode,
|
|
88
|
+
settingSources: config.settingSources,
|
|
89
|
+
systemPrompt: body ? { type: "preset", preset: "claude_code", append: body } : undefined,
|
|
90
|
+
onCheckpoint() { },
|
|
91
|
+
};
|
|
92
|
+
case "opencode-sdk":
|
|
93
|
+
return {
|
|
94
|
+
...baseTerms, prompt: replayPrompt,
|
|
95
|
+
provider: "opencode-sdk",
|
|
96
|
+
model: config.model,
|
|
97
|
+
executable: config.executable,
|
|
98
|
+
};
|
|
99
|
+
case "pi":
|
|
100
|
+
return {
|
|
101
|
+
...baseTerms,
|
|
102
|
+
provider: "pi",
|
|
103
|
+
model: config.model,
|
|
104
|
+
thinkingLevel: config.thinkingLevel,
|
|
105
|
+
appendSystemPromptOverride: body ? () => [body] : undefined,
|
|
106
|
+
onCheckpoint() { },
|
|
107
|
+
};
|
|
108
|
+
default:
|
|
109
|
+
return assertNever(config);
|
|
212
110
|
}
|
|
213
111
|
}
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
const adapter = SUBAGENT_ADAPTERS[config.provider];
|
|
217
|
-
if (!options.resume) {
|
|
218
|
-
return adapter.run(prompt, cwd, { ...config, signal: options.signal });
|
|
219
|
-
}
|
|
220
|
-
const handle = validateResumeSession(adapter, options.resume.session, options.resume.resumePath);
|
|
221
|
-
return adapter.resume(prompt, cwd, { ...config, signal: options.signal }, handle);
|
|
112
|
+
function assertNever(_value) {
|
|
113
|
+
throw new FlowError("INVALID_SETTINGS", "Unsupported subagent provider.");
|
|
222
114
|
}
|
|
@@ -1,5 +1,7 @@
|
|
|
1
1
|
import { spawn } from "node:child_process";
|
|
2
2
|
import net from "node:net";
|
|
3
|
+
import { coerceString } from "./index.js";
|
|
4
|
+
import { createAbortError } from "./harness/runtime.js";
|
|
3
5
|
const DEFAULT_HOSTNAME = "127.0.0.1";
|
|
4
6
|
const DEFAULT_PORT = 4096;
|
|
5
7
|
const DEFAULT_STARTUP_TIMEOUT_MS = 10_000;
|
|
@@ -21,22 +23,14 @@ const EVENT_DATA_PREFIX = "data:";
|
|
|
21
23
|
function sleep(ms) {
|
|
22
24
|
return new Promise((resolve) => setTimeout(resolve, ms));
|
|
23
25
|
}
|
|
24
|
-
function coerceString(value) {
|
|
25
|
-
return typeof value === "string" && value.trim().length > 0 ? value : undefined;
|
|
26
|
-
}
|
|
27
26
|
function normalizeBaseUrl(baseUrl) {
|
|
28
27
|
return coerceString(baseUrl)?.replace(/\/+$/, "") ?? `http://${DEFAULT_HOSTNAME}:${DEFAULT_PORT}`;
|
|
29
28
|
}
|
|
30
|
-
function createAbortError(message) {
|
|
31
|
-
const error = new Error(message);
|
|
32
|
-
error.name = "AbortError";
|
|
33
|
-
return error;
|
|
34
|
-
}
|
|
35
29
|
function buildSessionPath(sessionId) {
|
|
36
30
|
return `${SESSION_PATH}/${encodeURIComponent(sessionId)}`;
|
|
37
31
|
}
|
|
38
|
-
async function createJsonRequest(
|
|
39
|
-
const response = await
|
|
32
|
+
async function createJsonRequest(fetcher, baseUrl, requestPath, init = {}) {
|
|
33
|
+
const response = await fetcher(`${baseUrl}${requestPath}`, {
|
|
40
34
|
...init,
|
|
41
35
|
headers: {
|
|
42
36
|
[RESPONSE_HEADER_ACCEPT]: JSON_CONTENT_TYPE,
|
|
@@ -95,40 +89,40 @@ function createSseStream(response) {
|
|
|
95
89
|
};
|
|
96
90
|
}
|
|
97
91
|
export function createOpencodeClient(options = {}) {
|
|
98
|
-
const
|
|
99
|
-
if (typeof
|
|
92
|
+
const fetcher = options.fetch ?? globalThis.fetch;
|
|
93
|
+
if (typeof fetcher !== "function") {
|
|
100
94
|
throw new Error("OpenCode SDK requires global fetch support");
|
|
101
95
|
}
|
|
102
96
|
const baseUrl = normalizeBaseUrl(options.baseUrl);
|
|
103
97
|
return {
|
|
104
98
|
config: {
|
|
105
99
|
async get() {
|
|
106
|
-
return createJsonRequest(
|
|
100
|
+
return createJsonRequest(fetcher, baseUrl, CONFIG_PATH);
|
|
107
101
|
},
|
|
108
102
|
},
|
|
109
103
|
session: {
|
|
110
104
|
async create(input = {}) {
|
|
111
|
-
return createJsonRequest(
|
|
105
|
+
return createJsonRequest(fetcher, baseUrl, SESSION_PATH, {
|
|
112
106
|
method: "POST",
|
|
113
107
|
body: JSON.stringify(input.body ?? {}),
|
|
114
108
|
});
|
|
115
109
|
},
|
|
116
110
|
async get(input) {
|
|
117
|
-
return createJsonRequest(
|
|
111
|
+
return createJsonRequest(fetcher, baseUrl, buildSessionPath(input.path.id));
|
|
118
112
|
},
|
|
119
113
|
async prompt(input) {
|
|
120
|
-
return createJsonRequest(
|
|
114
|
+
return createJsonRequest(fetcher, baseUrl, `${buildSessionPath(input.path.id)}${SESSION_MESSAGE_PATH_SUFFIX}`, {
|
|
121
115
|
method: "POST",
|
|
122
116
|
body: JSON.stringify(input.body),
|
|
123
117
|
});
|
|
124
118
|
},
|
|
125
119
|
async messages(input) {
|
|
126
|
-
return createJsonRequest(
|
|
120
|
+
return createJsonRequest(fetcher, baseUrl, `${buildSessionPath(input.path.id)}${SESSION_MESSAGE_PATH_SUFFIX}`);
|
|
127
121
|
},
|
|
128
122
|
},
|
|
129
123
|
event: {
|
|
130
124
|
async subscribe() {
|
|
131
|
-
const response = await
|
|
125
|
+
const response = await fetcher(`${baseUrl}${EVENT_PATH}`, {
|
|
132
126
|
method: "GET",
|
|
133
127
|
headers: {
|
|
134
128
|
[RESPONSE_HEADER_ACCEPT]: EVENT_STREAM_CONTENT_TYPE,
|