@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,193 @@
|
|
|
1
|
+
import { z } from "zod";
|
|
2
|
+
import { Codex } from "@openai/codex-sdk";
|
|
3
|
+
import { CODEX_MODEL_REASONING_EFFORT_CONFIG_KEY } from "../../keiyaku.js";
|
|
4
|
+
import { appendDebugBlock, appendDebugLog } from "../../telemetry/debug-log.js";
|
|
5
|
+
import { SUBAGENT_SESSION_VERSION } from "../session.js";
|
|
6
|
+
import { createProjectionDriver } from "../harness/execution-handle.js";
|
|
7
|
+
import { buildSubagentEnv, emitProviderEvidence, sha256Utf8 } from "../harness/runtime.js";
|
|
8
|
+
import { coerceString } from "../../agents/index.js";
|
|
9
|
+
let codexConstructor = Codex;
|
|
10
|
+
export function setCodexSdkConstructorForTests(constructor) {
|
|
11
|
+
codexConstructor = constructor;
|
|
12
|
+
}
|
|
13
|
+
export function resetCodexSdkConstructorForTests() {
|
|
14
|
+
codexConstructor = Codex;
|
|
15
|
+
}
|
|
16
|
+
export const codexThreadSchema = z.object({ threadId: z.string().trim().min(1) });
|
|
17
|
+
const CODEX_EVENT_THREAD_STARTED = "thread.started";
|
|
18
|
+
const CODEX_EVENT_TURN_STARTED = "turn.started";
|
|
19
|
+
const CODEX_EVENT_TURN_FAILED = "turn.failed";
|
|
20
|
+
const CODEX_EVENT_ERROR = "error";
|
|
21
|
+
const CODEX_EVENT_ITEM_COMPLETED = "item.completed";
|
|
22
|
+
const CODEX_EVENT_ITEM_UPDATED = "item.updated";
|
|
23
|
+
const CODEX_SDK_PROVIDER = "codex-sdk";
|
|
24
|
+
const MAX_RECORDED_EVENT_TYPES = 100;
|
|
25
|
+
function extractAgentMessage(event) {
|
|
26
|
+
if (event.type !== CODEX_EVENT_ITEM_COMPLETED && event.type !== CODEX_EVENT_ITEM_UPDATED) {
|
|
27
|
+
return undefined;
|
|
28
|
+
}
|
|
29
|
+
return event.item.type === "agent_message" ? coerceString(event.item.text) : undefined;
|
|
30
|
+
}
|
|
31
|
+
function appendEventType(recordedTypes, eventType) {
|
|
32
|
+
if (recordedTypes.length === MAX_RECORDED_EVENT_TYPES) {
|
|
33
|
+
recordedTypes.shift();
|
|
34
|
+
}
|
|
35
|
+
recordedTypes.push(eventType);
|
|
36
|
+
}
|
|
37
|
+
function renderEventTypeSummary(eventTypes, totalEventCount) {
|
|
38
|
+
if (eventTypes.length === 0)
|
|
39
|
+
return "(no streamed events)";
|
|
40
|
+
const startIndex = Math.max(totalEventCount - eventTypes.length, 0);
|
|
41
|
+
const summary = eventTypes.map((eventType, index) => `${startIndex + index + 1}. ${eventType}`).join("\n");
|
|
42
|
+
if (totalEventCount <= eventTypes.length)
|
|
43
|
+
return summary;
|
|
44
|
+
return `...[showing last ${eventTypes.length} of ${totalEventCount} events]...\n${summary}`;
|
|
45
|
+
}
|
|
46
|
+
export async function startCodexSdk(prompt, cwd, options) {
|
|
47
|
+
const threadId = options.threadId?.trim();
|
|
48
|
+
const recordedEventTypes = [];
|
|
49
|
+
let totalEventCount = 0;
|
|
50
|
+
let activeAbort;
|
|
51
|
+
appendDebugLog(`startCodexSdk: executable=${options.executable ?? "codex"} model=${options.model ?? "default"} ${CODEX_MODEL_REASONING_EFFORT_CONFIG_KEY}=${String(options.config?.[CODEX_MODEL_REASONING_EFFORT_CONFIG_KEY] ?? "default")} mode=${threadId ? "resume" : "fresh"}`, { cwd, section: CODEX_SDK_PROVIDER });
|
|
52
|
+
return createProjectionDriver({
|
|
53
|
+
steerMode: "replay",
|
|
54
|
+
async abort() {
|
|
55
|
+
activeAbort?.abort();
|
|
56
|
+
},
|
|
57
|
+
async run(builder) {
|
|
58
|
+
activeAbort = new AbortController();
|
|
59
|
+
const linked = options.signal ?? builder.signal;
|
|
60
|
+
const onOuterAbort = () => activeAbort?.abort();
|
|
61
|
+
linked.addEventListener("abort", onOuterAbort, { once: true });
|
|
62
|
+
if (linked.aborted)
|
|
63
|
+
onOuterAbort();
|
|
64
|
+
builder.signal.addEventListener("abort", onOuterAbort, { once: true });
|
|
65
|
+
try {
|
|
66
|
+
const env = buildSubagentEnv();
|
|
67
|
+
const codexOptions = {
|
|
68
|
+
codexPathOverride: options.executable,
|
|
69
|
+
profile: options.profile,
|
|
70
|
+
config: options.config,
|
|
71
|
+
env,
|
|
72
|
+
};
|
|
73
|
+
const sdk = new codexConstructor(codexOptions);
|
|
74
|
+
const threadOptions = options.threadOptions;
|
|
75
|
+
const thread = threadId ? sdk.resumeThread(threadId, threadOptions) : sdk.startThread(threadOptions);
|
|
76
|
+
const streamed = await thread.runStreamed(prompt, { signal: activeAbort.signal });
|
|
77
|
+
const eventIterator = streamed.events[Symbol.asyncIterator]();
|
|
78
|
+
// The SDK iterable is lazy: its first next() starts the native run.
|
|
79
|
+
// Record outbound bytes immediately before that crossing, then ack only
|
|
80
|
+
// after it settles (including a legitimate no-event completion).
|
|
81
|
+
if (options.onEvidence) {
|
|
82
|
+
emitProviderEvidence(options.onEvidence, {
|
|
83
|
+
type: "outbound",
|
|
84
|
+
provider: CODEX_SDK_PROVIDER,
|
|
85
|
+
direction: "input",
|
|
86
|
+
kind: "replay-prompt",
|
|
87
|
+
...sha256Utf8(prompt),
|
|
88
|
+
}, (text) => builder.emit({ type: "diagnostic", text }));
|
|
89
|
+
}
|
|
90
|
+
const firstEvent = await eventIterator.next();
|
|
91
|
+
if (options.onEvidence) {
|
|
92
|
+
emitProviderEvidence(options.onEvidence, {
|
|
93
|
+
type: "ack",
|
|
94
|
+
provider: CODEX_SDK_PROVIDER,
|
|
95
|
+
fence: { kind: "ordinal", ordinal: 0 },
|
|
96
|
+
outbound: "run",
|
|
97
|
+
}, (text) => builder.emit({ type: "diagnostic", text }));
|
|
98
|
+
}
|
|
99
|
+
const events = async function* () {
|
|
100
|
+
if (!firstEvent.done)
|
|
101
|
+
yield firstEvent.value;
|
|
102
|
+
while (true) {
|
|
103
|
+
const next = await eventIterator.next();
|
|
104
|
+
if (next.done)
|
|
105
|
+
return;
|
|
106
|
+
yield next.value;
|
|
107
|
+
}
|
|
108
|
+
};
|
|
109
|
+
let resumeToken;
|
|
110
|
+
let lastAgentMessage;
|
|
111
|
+
let turnFailedMessage;
|
|
112
|
+
let streamErrorMessage;
|
|
113
|
+
for await (const event of events()) {
|
|
114
|
+
if (activeAbort.signal.aborted) {
|
|
115
|
+
throw Object.assign(new Error(`${CODEX_SDK_PROVIDER} exec cancelled by client`), { name: "AbortError" });
|
|
116
|
+
}
|
|
117
|
+
totalEventCount += 1;
|
|
118
|
+
appendEventType(recordedEventTypes, event.type);
|
|
119
|
+
builder.emit({ type: "output", value: event });
|
|
120
|
+
if (event.type === CODEX_EVENT_THREAD_STARTED) {
|
|
121
|
+
resumeToken = event.thread_id;
|
|
122
|
+
if (options.onEvidence)
|
|
123
|
+
emitProviderEvidence(options.onEvidence, { type: "session", provider: CODEX_SDK_PROVIDER, threadId: resumeToken }, (text) => builder.emit({ type: "diagnostic", text }));
|
|
124
|
+
}
|
|
125
|
+
else if (event.type === CODEX_EVENT_TURN_STARTED && !resumeToken) {
|
|
126
|
+
builder.emit({
|
|
127
|
+
type: "fail",
|
|
128
|
+
message: `${CODEX_SDK_PROVIDER} exec emitted turn.started before thread.started with thread_id`,
|
|
129
|
+
});
|
|
130
|
+
throw new Error(`${CODEX_SDK_PROVIDER} exec emitted turn.started before thread.started with thread_id`);
|
|
131
|
+
}
|
|
132
|
+
else if (event.type === CODEX_EVENT_TURN_FAILED) {
|
|
133
|
+
turnFailedMessage = coerceString(event.error.message) ?? `${CODEX_SDK_PROVIDER} turn failed`;
|
|
134
|
+
}
|
|
135
|
+
else if (event.type === CODEX_EVENT_ERROR) {
|
|
136
|
+
streamErrorMessage = coerceString(event.message) ?? `${CODEX_SDK_PROVIDER} stream error`;
|
|
137
|
+
builder.emit({ type: "diagnostic", text: `${streamErrorMessage}\n` });
|
|
138
|
+
}
|
|
139
|
+
const message = extractAgentMessage(event);
|
|
140
|
+
if (message) {
|
|
141
|
+
lastAgentMessage = message;
|
|
142
|
+
builder.emit({ type: "message", text: message });
|
|
143
|
+
}
|
|
144
|
+
}
|
|
145
|
+
if (turnFailedMessage) {
|
|
146
|
+
builder.emit({ type: "diagnostic", text: turnFailedMessage });
|
|
147
|
+
builder.emit({ type: "fail", message: turnFailedMessage });
|
|
148
|
+
throw new Error(turnFailedMessage);
|
|
149
|
+
}
|
|
150
|
+
if (streamErrorMessage && !lastAgentMessage) {
|
|
151
|
+
builder.emit({ type: "fail", message: streamErrorMessage });
|
|
152
|
+
throw new Error(streamErrorMessage);
|
|
153
|
+
}
|
|
154
|
+
if (!resumeToken) {
|
|
155
|
+
appendDebugLog(`Missing required codex event: ${CODEX_EVENT_THREAD_STARTED} with thread_id.`, {
|
|
156
|
+
cwd,
|
|
157
|
+
section: CODEX_SDK_PROVIDER,
|
|
158
|
+
});
|
|
159
|
+
appendDebugBlock("codex-sdk stdout event types", renderEventTypeSummary(recordedEventTypes, totalEventCount), {
|
|
160
|
+
cwd,
|
|
161
|
+
section: CODEX_SDK_PROVIDER,
|
|
162
|
+
});
|
|
163
|
+
builder.emit({
|
|
164
|
+
type: "fail",
|
|
165
|
+
message: `${CODEX_SDK_PROVIDER} exec succeeded but did not emit a ${CODEX_EVENT_THREAD_STARTED} event with thread_id`,
|
|
166
|
+
});
|
|
167
|
+
throw new Error(`${CODEX_SDK_PROVIDER} exec succeeded but did not emit a ${CODEX_EVENT_THREAD_STARTED} event with thread_id`);
|
|
168
|
+
}
|
|
169
|
+
const finalMessage = lastAgentMessage ?? "";
|
|
170
|
+
const session = {
|
|
171
|
+
provider: "codex-sdk",
|
|
172
|
+
version: SUBAGENT_SESSION_VERSION,
|
|
173
|
+
handle: { threadId: resumeToken },
|
|
174
|
+
};
|
|
175
|
+
if (options.onEvidence)
|
|
176
|
+
emitProviderEvidence(options.onEvidence, { type: "checkpoint", provider: CODEX_SDK_PROVIDER, fence: { kind: "ordinal", ordinal: 1 }, source: "response" }, (text) => builder.emit({ type: "diagnostic", text }));
|
|
177
|
+
builder.emit({
|
|
178
|
+
type: "done",
|
|
179
|
+
finalMessage,
|
|
180
|
+
session,
|
|
181
|
+
});
|
|
182
|
+
return { finalMessage, session };
|
|
183
|
+
}
|
|
184
|
+
catch (error) {
|
|
185
|
+
appendDebugLog(`startCodexSdk error: ${error instanceof Error ? `${error.name}: ${error.message}` : String(error)}`, { cwd, section: CODEX_SDK_PROVIDER });
|
|
186
|
+
throw error;
|
|
187
|
+
}
|
|
188
|
+
finally {
|
|
189
|
+
linked.removeEventListener("abort", onOuterAbort);
|
|
190
|
+
}
|
|
191
|
+
},
|
|
192
|
+
});
|
|
193
|
+
}
|
|
@@ -0,0 +1,257 @@
|
|
|
1
|
+
import { z } from "zod";
|
|
2
|
+
import { appendDebugBlock, appendDebugLog } from "../../telemetry/debug-log.js";
|
|
3
|
+
import { createOpencode } from "../opencode-sdk.js";
|
|
4
|
+
import { SUBAGENT_SESSION_VERSION } from "../session.js";
|
|
5
|
+
import { createProjectionDriver } from "../harness/execution-handle.js";
|
|
6
|
+
import { buildSubagentEnv, createAbortError } from "../harness/runtime.js";
|
|
7
|
+
import { emitProviderEvidence, sha256Utf8 } from "../harness/runtime.js";
|
|
8
|
+
import { coerceString } from "../index.js";
|
|
9
|
+
export const opencodeSessionSchema = z.object({ sessionId: z.string().trim().min(1) });
|
|
10
|
+
const OPENCODE_EVENT_SESSION_IDLE = "session.idle";
|
|
11
|
+
const OPENCODE_EVENT_SESSION_ERROR = "session.error";
|
|
12
|
+
const OPENCODE_EVENT_MESSAGE_UPDATED = "message.updated";
|
|
13
|
+
const OPENCODE_EVENT_MESSAGE_PART_UPDATED = "message.part.updated";
|
|
14
|
+
const OPENCODE_EVENT_MESSAGE_PART_ADDED = "message.part.added";
|
|
15
|
+
const OPENCODE_SDK_PROVIDER = "opencode-sdk";
|
|
16
|
+
const OPENCODE_DEFAULT_EXECUTABLE = "opencode";
|
|
17
|
+
const OPENCODE_MAX_RECORDED_EVENT_TYPES = 100;
|
|
18
|
+
const OPENCODE_ASSISTANT_ROLE = "assistant";
|
|
19
|
+
const OPENCODE_TEXT_PART_TYPE = "text";
|
|
20
|
+
let opencodeSdkLoader;
|
|
21
|
+
function appendEventType(recordedTypes, eventType) {
|
|
22
|
+
if (recordedTypes.length === OPENCODE_MAX_RECORDED_EVENT_TYPES) {
|
|
23
|
+
recordedTypes.shift();
|
|
24
|
+
}
|
|
25
|
+
recordedTypes.push(eventType);
|
|
26
|
+
}
|
|
27
|
+
function renderEventTypeSummary(eventTypes, totalEventCount) {
|
|
28
|
+
if (eventTypes.length === 0)
|
|
29
|
+
return "(no streamed events)";
|
|
30
|
+
const startIndex = Math.max(totalEventCount - eventTypes.length, 0);
|
|
31
|
+
const summary = eventTypes.map((eventType, index) => `${startIndex + index + 1}. ${eventType}`).join("\n");
|
|
32
|
+
if (totalEventCount <= eventTypes.length)
|
|
33
|
+
return summary;
|
|
34
|
+
return `...[showing last ${eventTypes.length} of ${totalEventCount} events]...\n${summary}`;
|
|
35
|
+
}
|
|
36
|
+
function extractTextFromMessage(message) {
|
|
37
|
+
if (!message?.parts)
|
|
38
|
+
return undefined;
|
|
39
|
+
const text = message.parts
|
|
40
|
+
.filter((part) => !part.type || part.type === OPENCODE_TEXT_PART_TYPE)
|
|
41
|
+
.map((part) => coerceString(part.text))
|
|
42
|
+
.filter((part) => Boolean(part))
|
|
43
|
+
.join("");
|
|
44
|
+
return text.trim() || undefined;
|
|
45
|
+
}
|
|
46
|
+
function extractFinalAssistantMessage(messages) {
|
|
47
|
+
let lastText;
|
|
48
|
+
let lastAssistantText;
|
|
49
|
+
for (const message of messages) {
|
|
50
|
+
const text = extractTextFromMessage(message);
|
|
51
|
+
if (!text)
|
|
52
|
+
continue;
|
|
53
|
+
lastText = text;
|
|
54
|
+
if (message.info?.role === OPENCODE_ASSISTANT_ROLE) {
|
|
55
|
+
lastAssistantText = text;
|
|
56
|
+
}
|
|
57
|
+
}
|
|
58
|
+
return lastAssistantText ?? lastText;
|
|
59
|
+
}
|
|
60
|
+
async function withAbort(promise, signal, message) {
|
|
61
|
+
if (signal.aborted) {
|
|
62
|
+
throw createAbortError(message);
|
|
63
|
+
}
|
|
64
|
+
return new Promise((resolve, reject) => {
|
|
65
|
+
const cleanup = () => {
|
|
66
|
+
signal.removeEventListener("abort", onAbort);
|
|
67
|
+
};
|
|
68
|
+
const onAbort = () => {
|
|
69
|
+
cleanup();
|
|
70
|
+
reject(createAbortError(message));
|
|
71
|
+
};
|
|
72
|
+
signal.addEventListener("abort", onAbort, { once: true });
|
|
73
|
+
promise.then((value) => {
|
|
74
|
+
cleanup();
|
|
75
|
+
resolve(value);
|
|
76
|
+
}, (error) => {
|
|
77
|
+
cleanup();
|
|
78
|
+
reject(error);
|
|
79
|
+
});
|
|
80
|
+
});
|
|
81
|
+
}
|
|
82
|
+
async function loadOpencodeSdk(executable, cwd, env, signal) {
|
|
83
|
+
if (opencodeSdkLoader) {
|
|
84
|
+
return opencodeSdkLoader();
|
|
85
|
+
}
|
|
86
|
+
const sdk = await createOpencode({
|
|
87
|
+
executable,
|
|
88
|
+
directory: cwd,
|
|
89
|
+
env,
|
|
90
|
+
signal,
|
|
91
|
+
});
|
|
92
|
+
return {
|
|
93
|
+
client: sdk.client,
|
|
94
|
+
close: sdk.server?.close,
|
|
95
|
+
};
|
|
96
|
+
}
|
|
97
|
+
async function awaitSessionIdle(input) {
|
|
98
|
+
const subscribed = await input.client.event.subscribe();
|
|
99
|
+
for await (const event of subscribed.stream) {
|
|
100
|
+
input.emit({ type: "output", value: event });
|
|
101
|
+
input.incrementEventCount();
|
|
102
|
+
appendEventType(input.recordedEventTypes, event.type);
|
|
103
|
+
if (event.properties?.sessionID !== input.sessionId) {
|
|
104
|
+
continue;
|
|
105
|
+
}
|
|
106
|
+
if (event.type === OPENCODE_EVENT_SESSION_ERROR) {
|
|
107
|
+
const errorMessage = coerceString(event.error?.message) ??
|
|
108
|
+
coerceString(event.properties?.message) ??
|
|
109
|
+
`${OPENCODE_SDK_PROVIDER} session error`;
|
|
110
|
+
input.emit({ type: "diagnostic", text: errorMessage });
|
|
111
|
+
input.emit({ type: "fail", message: errorMessage });
|
|
112
|
+
throw new Error(errorMessage);
|
|
113
|
+
}
|
|
114
|
+
if (event.type === OPENCODE_EVENT_SESSION_IDLE ||
|
|
115
|
+
event.type === OPENCODE_EVENT_MESSAGE_UPDATED ||
|
|
116
|
+
event.type === OPENCODE_EVENT_MESSAGE_PART_UPDATED ||
|
|
117
|
+
event.type === OPENCODE_EVENT_MESSAGE_PART_ADDED) {
|
|
118
|
+
if (event.type === OPENCODE_EVENT_SESSION_IDLE) {
|
|
119
|
+
return;
|
|
120
|
+
}
|
|
121
|
+
}
|
|
122
|
+
}
|
|
123
|
+
const message = `${OPENCODE_SDK_PROVIDER} event stream ended before ${OPENCODE_EVENT_SESSION_IDLE}`;
|
|
124
|
+
input.emit({ type: "fail", message });
|
|
125
|
+
throw new Error(message);
|
|
126
|
+
}
|
|
127
|
+
export function setOpencodeSdkLoaderForTests(loader) {
|
|
128
|
+
opencodeSdkLoader = loader;
|
|
129
|
+
}
|
|
130
|
+
export function resetOpencodeSdkLoaderForTests() {
|
|
131
|
+
opencodeSdkLoader = undefined;
|
|
132
|
+
}
|
|
133
|
+
export async function startOpencode(prompt, cwd, options = {}) {
|
|
134
|
+
const sessionId = options.sessionId?.trim();
|
|
135
|
+
const executable = options.executable ?? OPENCODE_DEFAULT_EXECUTABLE;
|
|
136
|
+
const recordedEventTypes = [];
|
|
137
|
+
let totalEventCount = 0;
|
|
138
|
+
let resumedSessionId;
|
|
139
|
+
let sdk;
|
|
140
|
+
let activeAbort;
|
|
141
|
+
appendDebugLog(`startOpencode: executable=${executable} model=${options.model ?? "default"} mode=${sessionId ? "resume" : "fresh"}`, { cwd, section: OPENCODE_SDK_PROVIDER });
|
|
142
|
+
return createProjectionDriver({
|
|
143
|
+
steerMode: "replay",
|
|
144
|
+
async abort() {
|
|
145
|
+
activeAbort?.abort();
|
|
146
|
+
},
|
|
147
|
+
async run(builder) {
|
|
148
|
+
activeAbort = new AbortController();
|
|
149
|
+
const combined = new AbortController();
|
|
150
|
+
const forward = () => combined.abort();
|
|
151
|
+
builder.signal.addEventListener("abort", forward, { once: true });
|
|
152
|
+
options.signal?.addEventListener("abort", forward, { once: true });
|
|
153
|
+
activeAbort.signal.addEventListener("abort", forward, { once: true });
|
|
154
|
+
if (builder.signal.aborted || options.signal?.aborted || activeAbort.signal.aborted) {
|
|
155
|
+
forward();
|
|
156
|
+
}
|
|
157
|
+
try {
|
|
158
|
+
const env = buildSubagentEnv();
|
|
159
|
+
sdk = await withAbort(loadOpencodeSdk(executable, cwd, env, combined.signal), combined.signal, `${OPENCODE_SDK_PROVIDER} exec cancelled by client`);
|
|
160
|
+
const session = sessionId
|
|
161
|
+
? await withAbort(sdk.client.session.get({ path: { id: sessionId } }), combined.signal, `${OPENCODE_SDK_PROVIDER} exec cancelled by client`)
|
|
162
|
+
: await withAbort(sdk.client.session.create({
|
|
163
|
+
body: {
|
|
164
|
+
...(options.model ? { model: options.model } : {}),
|
|
165
|
+
cwd,
|
|
166
|
+
},
|
|
167
|
+
}), combined.signal, `${OPENCODE_SDK_PROVIDER} exec cancelled by client`);
|
|
168
|
+
resumedSessionId = session.id || sessionId;
|
|
169
|
+
if (!resumedSessionId) {
|
|
170
|
+
const message = `${OPENCODE_SDK_PROVIDER} exec could not determine session id`;
|
|
171
|
+
builder.emit({ type: "fail", message });
|
|
172
|
+
throw new Error(message);
|
|
173
|
+
}
|
|
174
|
+
const activeSessionId = resumedSessionId;
|
|
175
|
+
if (options.onEvidence)
|
|
176
|
+
emitProviderEvidence(options.onEvidence, { type: "session", provider: OPENCODE_SDK_PROVIDER, sessionId: activeSessionId }, (text) => builder.emit({ type: "diagnostic", text }));
|
|
177
|
+
const idlePromise = awaitSessionIdle({
|
|
178
|
+
client: sdk.client,
|
|
179
|
+
emit: builder.emit,
|
|
180
|
+
sessionId: activeSessionId,
|
|
181
|
+
recordedEventTypes,
|
|
182
|
+
incrementEventCount: () => {
|
|
183
|
+
totalEventCount += 1;
|
|
184
|
+
},
|
|
185
|
+
});
|
|
186
|
+
// session.prompt is the OpenCode SDK input crossing. Emitting the run
|
|
187
|
+
// acknowledgement after the call returns keeps synchronous startup
|
|
188
|
+
// failures replayable without delaying completion of the request.
|
|
189
|
+
if (options.onEvidence) {
|
|
190
|
+
emitProviderEvidence(options.onEvidence, {
|
|
191
|
+
type: "outbound",
|
|
192
|
+
provider: OPENCODE_SDK_PROVIDER,
|
|
193
|
+
direction: "input",
|
|
194
|
+
kind: "replay-prompt",
|
|
195
|
+
...sha256Utf8(prompt),
|
|
196
|
+
}, (text) => builder.emit({ type: "diagnostic", text }));
|
|
197
|
+
}
|
|
198
|
+
const promptPromise = sdk.client.session.prompt({
|
|
199
|
+
path: { id: activeSessionId },
|
|
200
|
+
body: {
|
|
201
|
+
...(options.model ? { model: options.model } : {}),
|
|
202
|
+
parts: [{ type: OPENCODE_TEXT_PART_TYPE, text: prompt }],
|
|
203
|
+
},
|
|
204
|
+
});
|
|
205
|
+
if (options.onEvidence) {
|
|
206
|
+
emitProviderEvidence(options.onEvidence, {
|
|
207
|
+
type: "ack",
|
|
208
|
+
provider: OPENCODE_SDK_PROVIDER,
|
|
209
|
+
fence: { kind: "ordinal", ordinal: 0 },
|
|
210
|
+
outbound: "run",
|
|
211
|
+
}, (text) => builder.emit({ type: "diagnostic", text }));
|
|
212
|
+
}
|
|
213
|
+
const [promptResult] = await withAbort(Promise.all([promptPromise, idlePromise]), combined.signal, `${OPENCODE_SDK_PROVIDER} exec cancelled by client`);
|
|
214
|
+
let finalMessage = extractTextFromMessage(promptResult);
|
|
215
|
+
if (!finalMessage) {
|
|
216
|
+
const messages = await withAbort(sdk.client.session.messages({ path: { id: activeSessionId } }), combined.signal, `${OPENCODE_SDK_PROVIDER} exec cancelled by client`);
|
|
217
|
+
finalMessage = extractFinalAssistantMessage(messages);
|
|
218
|
+
}
|
|
219
|
+
if (finalMessage) {
|
|
220
|
+
builder.emit({ type: "message", text: finalMessage });
|
|
221
|
+
}
|
|
222
|
+
const sessionEnvelope = {
|
|
223
|
+
provider: "opencode-sdk",
|
|
224
|
+
version: SUBAGENT_SESSION_VERSION,
|
|
225
|
+
handle: { sessionId: resumedSessionId },
|
|
226
|
+
};
|
|
227
|
+
const resolvedFinal = finalMessage ?? "";
|
|
228
|
+
if (options.onEvidence) {
|
|
229
|
+
emitProviderEvidence(options.onEvidence, {
|
|
230
|
+
type: "checkpoint",
|
|
231
|
+
provider: OPENCODE_SDK_PROVIDER,
|
|
232
|
+
fence: { kind: "ordinal", ordinal: 1 },
|
|
233
|
+
source: "response",
|
|
234
|
+
}, (text) => builder.emit({ type: "diagnostic", text }));
|
|
235
|
+
}
|
|
236
|
+
builder.emit({
|
|
237
|
+
type: "done",
|
|
238
|
+
finalMessage: resolvedFinal,
|
|
239
|
+
session: sessionEnvelope,
|
|
240
|
+
});
|
|
241
|
+
return { finalMessage: resolvedFinal, session: sessionEnvelope };
|
|
242
|
+
}
|
|
243
|
+
catch (error) {
|
|
244
|
+
appendDebugLog(`startOpencode error: ${error instanceof Error ? `${error.name}: ${error.message}` : String(error)}`, { cwd, section: OPENCODE_SDK_PROVIDER });
|
|
245
|
+
throw error;
|
|
246
|
+
}
|
|
247
|
+
finally {
|
|
248
|
+
if (resumedSessionId) {
|
|
249
|
+
appendDebugBlock("opencode-sdk stdout event types", renderEventTypeSummary(recordedEventTypes, totalEventCount), { cwd, section: OPENCODE_SDK_PROVIDER });
|
|
250
|
+
}
|
|
251
|
+
await sdk?.close?.();
|
|
252
|
+
builder.signal.removeEventListener("abort", forward);
|
|
253
|
+
options.signal?.removeEventListener("abort", forward);
|
|
254
|
+
}
|
|
255
|
+
},
|
|
256
|
+
});
|
|
257
|
+
}
|