@astrosheep/keiyaku 4.6.11 → 4.6.13
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/build/plugins/claude/skills/keiyaku/SKILL.md +3 -1
- package/build/plugins/claude/skills/keiyaku-babysit/SKILL.md +1 -1
- package/build/plugins/claude/skills/keiyaku-bind/SKILL.md +3 -2
- package/build/plugins/claude/skills/keiyaku-workflow/SKILL.md +9 -8
- package/build/plugins/codex/skills/keiyaku/SKILL.md +3 -1
- package/build/plugins/codex/skills/keiyaku-babysit/SKILL.md +1 -1
- package/build/plugins/codex/skills/keiyaku-bind/SKILL.md +3 -2
- package/build/plugins/codex/skills/keiyaku-workflow/SKILL.md +9 -8
- package/build/plugins/opencode/skills/keiyaku/SKILL.md +3 -1
- package/build/plugins/opencode/skills/keiyaku-babysit/SKILL.md +1 -1
- package/build/plugins/opencode/skills/keiyaku-bind/SKILL.md +3 -2
- package/build/plugins/opencode/skills/keiyaku-workflow/SKILL.md +9 -8
- package/build/skills/keiyaku/SKILL.md +3 -1
- package/build/skills/keiyaku-babysit/SKILL.md +1 -1
- package/build/skills/keiyaku-bind/SKILL.md +3 -2
- package/build/skills/keiyaku-workflow/SKILL.md +9 -8
- package/build/src/akuma/akuma-errors.d.ts +5 -0
- package/build/src/akuma/akuma-errors.js +9 -0
- package/build/src/akuma/akuma-owner.d.ts +14 -19
- package/build/src/akuma/akuma-owner.js +56 -54
- package/build/src/akuma/akuma-selection-execution.d.ts +5 -4
- package/build/src/akuma/akuma-selection-execution.js +59 -17
- package/build/src/akuma/akuma.d.ts +1 -2
- package/build/src/akuma/allowed.d.ts +2 -3
- package/build/src/akuma/allowed.js +1 -3
- package/build/src/akuma/body.js +17 -7
- package/build/src/akuma/call-initial-tell.d.ts +18 -0
- package/build/src/akuma/call-initial-tell.js +18 -0
- package/build/src/akuma/call-request.d.ts +20 -4
- package/build/src/akuma/heart/facts.d.ts +13 -0
- package/build/src/akuma/heart/facts.js +6 -0
- package/build/src/akuma/heart/index.d.ts +29 -14
- package/build/src/akuma/heart/index.js +75 -29
- package/build/src/akuma/heart/rows.d.ts +9 -2
- package/build/src/akuma/heart/rows.js +72 -11
- package/build/src/akuma/heart/storage.d.ts +2 -8
- package/build/src/akuma/heart/storage.js +13 -13
- package/build/src/akuma/heart/tells.d.ts +3 -1
- package/build/src/akuma/heart/tells.js +18 -2
- package/build/src/akuma/provider.d.ts +2 -4
- package/build/src/akuma/provider.js +29 -6
- package/build/src/akuma/providers/acp/core.js +23 -21
- package/build/src/akuma/providers/claude/index.js +0 -8
- package/build/src/akuma/providers/codex-app-server/index.js +9 -8
- package/build/src/akuma/providers/execution-environment.d.ts +1 -1
- package/build/src/akuma/providers/execution-environment.js +1 -0
- package/build/src/akuma/providers/opencode-sdk/events.js +111 -81
- package/build/src/akuma/providers/opencode-sdk/index.js +179 -150
- package/build/src/akuma/providers/opencode-sdk/native-http.js +7 -3
- package/build/src/akuma/providers/opencode-sdk/session.d.ts +5 -3
- package/build/src/akuma/providers/opencode-sdk/session.js +32 -27
- package/build/src/akuma/providers/paseo/index.js +0 -2
- package/build/src/akuma/providers/pi/index.js +23 -20
- package/build/src/akuma/selection-observation.d.ts +78 -9
- package/build/src/akuma/selection-owner-port.d.ts +2 -1
- package/build/src/akuma/selection-owner-port.js +6 -1
- package/build/src/akuma/selection-request.d.ts +1 -1
- package/build/src/akuma/selection-request.js +8 -3
- package/build/src/akuma/turn-drive.js +79 -48
- package/build/src/akuma-body.js +5 -3
- package/build/src/cli/commands/akuma-grammar.js +2 -2
- package/build/src/cli/commands/akuma.js +24 -16
- package/build/src/cli/commands/contract-grammar.d.ts +3 -7
- package/build/src/cli/commands/contract-grammar.js +12 -10
- package/build/src/cli/commands/contract-help.d.ts +3 -13
- package/build/src/cli/commands/contract-help.js +7 -16
- package/build/src/cli/commands/contract.js +19 -35
- package/build/src/cli/commands/debt.js +30 -29
- package/build/src/cli/parse.js +0 -1
- package/build/src/cli/render/akuma-activity.d.ts +36 -9
- package/build/src/cli/render/akuma-activity.js +142 -65
- package/build/src/cli/render/akuma.d.ts +6 -0
- package/build/src/cli/render/akuma.js +67 -24
- package/build/src/cli/render/catalog.js +0 -1
- package/build/src/cli/render/contract-observation.d.ts +16 -3
- package/build/src/cli/render/contract-observation.js +3 -0
- package/build/src/cli/render/contract.d.ts +2 -2
- package/build/src/cli/render/contract.js +4 -4
- package/build/src/cli/render/delivery-preview.d.ts +7 -0
- package/build/src/cli/render/{audit.js → delivery-preview.js} +29 -27
- package/build/src/cli/render/kanshi-akuma.d.ts +3 -1
- package/build/src/cli/render/kanshi-akuma.js +23 -8
- package/build/src/cli/render/kanshi.js +4 -1
- package/build/src/cli/render/receipt.js +3 -3
- package/build/src/cli/render/refusal.js +2 -2
- package/build/src/cli/render/terminal.d.ts +0 -1
- package/build/src/cli/render/terminal.js +1 -1
- package/build/src/cli/square-identity.d.ts +15 -0
- package/build/src/cli/square-identity.js +180 -0
- package/build/src/contract-worktree.js +1 -1
- package/build/src/core/verbs/placement.d.ts +2 -1
- package/build/src/core/verbs/placement.js +1 -1
- package/build/src/debt/context.d.ts +0 -5
- package/build/src/debt/context.js +32 -44
- package/build/src/git/process.d.ts +1 -0
- package/build/src/git/process.js +18 -1
- package/build/src/git/read-observation.js +2 -1
- package/build/src/git/scratch.d.ts +5 -1
- package/build/src/git/scratch.js +41 -12
- package/build/src/git/target-placement.d.ts +22 -4
- package/build/src/git/target-placement.js +44 -20
- package/build/src/index.d.ts +2 -2
- package/build/src/kanshi/read.js +187 -105
- package/build/src/kanshi/report.d.ts +5 -0
- package/build/src/library/address.d.ts +18 -4
- package/build/src/library/address.js +27 -6
- package/build/src/library/contract-address.d.ts +6 -0
- package/build/src/library/contract-address.js +12 -1
- package/build/src/library/contract-execution.d.ts +2 -2
- package/build/src/library/contract-execution.js +13 -17
- package/build/src/library/contract-operations.d.ts +59 -94
- package/build/src/library/contract-operations.js +23 -19
- package/build/src/library/contract-outcomes.d.ts +3 -3
- package/build/src/library/contract-settings.d.ts +1 -0
- package/build/src/library/contract-settings.js +1 -1
- package/build/src/library/invocation.d.ts +2 -2
- package/build/src/library/invocation.js +1 -1
- package/build/src/library/keiyaku.d.ts +2 -2
- package/build/src/library/keiyaku.js +2 -2
- package/build/src/library/outcome.d.ts +148 -268
- package/build/src/library/outcome.js +4 -8
- package/build/src/library/refusal.d.ts +2 -2
- package/build/src/library/refusal.js +2 -2
- package/build/src/plugin/runtime.js +15 -19
- package/build/src/protocol/{audit.d.ts → delivery-preview.d.ts} +62 -97
- package/build/src/protocol/delivery-preview.js +167 -0
- package/build/src/protocol/progress.d.ts +1 -1
- package/build/src/runtime/proc/run.js +22 -4
- package/build/src/runtime/proc/windows-launch.exe +0 -0
- package/build/src/verification/execution.d.ts +1 -1
- package/build/src/verification/execution.js +5 -3
- package/package.json +5 -2
- package/skills/keiyaku/SKILL.md +3 -1
- package/skills/keiyaku-babysit/SKILL.md +1 -1
- package/skills/keiyaku-bind/SKILL.md +3 -2
- package/skills/keiyaku-workflow/SKILL.md +9 -8
- package/build/src/cli/render/audit.d.ts +0 -7
- package/build/src/protocol/audit.js +0 -203
|
@@ -4,37 +4,53 @@ export function diagnostic(value) {
|
|
|
4
4
|
const data = object(value);
|
|
5
5
|
return text(data?.message) ?? text(object(data?.data)?.message) ?? "OpenCode execution failed";
|
|
6
6
|
}
|
|
7
|
+
function shellCall(value) {
|
|
8
|
+
return { kind: "run", command: text(value?.command) ?? "shell" };
|
|
9
|
+
}
|
|
10
|
+
function readCall(value) {
|
|
11
|
+
const path = text(value?.filePath) ?? text(value?.path);
|
|
12
|
+
if (path === undefined)
|
|
13
|
+
return undefined;
|
|
14
|
+
return {
|
|
15
|
+
kind: "read",
|
|
16
|
+
path,
|
|
17
|
+
...(positiveLine(value?.offset) === undefined ? {} : { offset: positiveLine(value?.offset) }),
|
|
18
|
+
...(positiveLine(value?.limit) === undefined ? {} : { limit: positiveLine(value?.limit) }),
|
|
19
|
+
};
|
|
20
|
+
}
|
|
21
|
+
function searchCall(name, value) {
|
|
22
|
+
const query = text(value?.pattern);
|
|
23
|
+
const path = text(value?.path);
|
|
24
|
+
if (query === undefined)
|
|
25
|
+
return undefined;
|
|
26
|
+
return {
|
|
27
|
+
kind: "search",
|
|
28
|
+
scope: name === "glob" ? "files" : "content",
|
|
29
|
+
query,
|
|
30
|
+
...(path === undefined ? {} : { path }),
|
|
31
|
+
};
|
|
32
|
+
}
|
|
33
|
+
function editCall(value) {
|
|
34
|
+
const path = text(value?.path);
|
|
35
|
+
if (path !== undefined)
|
|
36
|
+
return { kind: "fileChange", changes: [{ op: "update", path }] };
|
|
37
|
+
return undefined;
|
|
38
|
+
}
|
|
7
39
|
function callFor(name, input) {
|
|
8
40
|
const value = object(input);
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
if (name === "grep" || name === "glob") {
|
|
22
|
-
const query = text(value?.pattern);
|
|
23
|
-
const path = text(value?.path);
|
|
24
|
-
if (query !== undefined)
|
|
25
|
-
return {
|
|
26
|
-
kind: "search",
|
|
27
|
-
scope: name === "glob" ? "files" : "content",
|
|
28
|
-
query,
|
|
29
|
-
...(path === undefined ? {} : { path }),
|
|
30
|
-
};
|
|
31
|
-
}
|
|
32
|
-
if (name === "edit") {
|
|
33
|
-
const path = text(value?.path);
|
|
34
|
-
if (path !== undefined)
|
|
35
|
-
return { kind: "fileChange", changes: [{ op: "update", path }] };
|
|
41
|
+
switch (name) {
|
|
42
|
+
case "shell":
|
|
43
|
+
return shellCall(value);
|
|
44
|
+
case "read":
|
|
45
|
+
return readCall(value) ?? otherToolCall(name, input);
|
|
46
|
+
case "grep":
|
|
47
|
+
case "glob":
|
|
48
|
+
return searchCall(name, value) ?? otherToolCall(name, input);
|
|
49
|
+
case "edit":
|
|
50
|
+
return editCall(value) ?? otherToolCall(name, input);
|
|
51
|
+
default:
|
|
52
|
+
return otherToolCall(name, input);
|
|
36
53
|
}
|
|
37
|
-
return otherToolCall(name, input);
|
|
38
54
|
}
|
|
39
55
|
function observedChanges(metadata) {
|
|
40
56
|
const files = object(metadata)?.files;
|
|
@@ -101,6 +117,46 @@ const DROPPED = new Set([
|
|
|
101
117
|
"session.tool.progress",
|
|
102
118
|
"session.retry.scheduled",
|
|
103
119
|
]);
|
|
120
|
+
function toolResult(type, data) {
|
|
121
|
+
const observed = output(data.content);
|
|
122
|
+
const nativeExit = object(data.metadata)?.exit;
|
|
123
|
+
const exit = typeof nativeExit === "number" && Number.isInteger(nativeExit) ? nativeExit : undefined;
|
|
124
|
+
return {
|
|
125
|
+
status: type === "session.tool.failed" || (exit !== undefined && exit !== 0) ? "error" : "ok",
|
|
126
|
+
...(type === "session.tool.failed" ? { message: diagnostic(data.error) } : {}),
|
|
127
|
+
...(observed === undefined ? {} : { output: observed }),
|
|
128
|
+
...(exit === undefined ? {} : { exitCode: exit }),
|
|
129
|
+
};
|
|
130
|
+
}
|
|
131
|
+
function toolEvent(type, data, events, state) {
|
|
132
|
+
const id = text(data.id);
|
|
133
|
+
const started = id === undefined ? undefined : state.tools.get(id);
|
|
134
|
+
if (id === undefined || started === undefined || state.completed.has(id))
|
|
135
|
+
return;
|
|
136
|
+
if (started.call === undefined) {
|
|
137
|
+
started.call = callFor(started.name, data.input);
|
|
138
|
+
events.emit({ type: "tool", phase: "started", id, name: started.name, call: started.call });
|
|
139
|
+
}
|
|
140
|
+
if (type === "session.tool.called")
|
|
141
|
+
return;
|
|
142
|
+
if (started.name === "edit" || started.name === "patch" || started.name === "write") {
|
|
143
|
+
started.call = observedChanges(data.metadata) ?? started.call;
|
|
144
|
+
}
|
|
145
|
+
const result = toolResult(type, data);
|
|
146
|
+
state.completed.add(id);
|
|
147
|
+
events.emit({ type: "tool", phase: "completed", id, name: started.name, call: started.call, result });
|
|
148
|
+
}
|
|
149
|
+
function stepStarted(data, events) {
|
|
150
|
+
const model = object(data.model);
|
|
151
|
+
const provider = text(model?.providerID);
|
|
152
|
+
const id = text(model?.id);
|
|
153
|
+
if (provider !== undefined && id !== undefined)
|
|
154
|
+
events.emit(modelEvent(`${provider}/${id}`));
|
|
155
|
+
}
|
|
156
|
+
function toolInputStarted(data, state) {
|
|
157
|
+
if (typeof data.id === "string" && typeof data.name === "string")
|
|
158
|
+
state.tools.set(data.id, { name: data.name });
|
|
159
|
+
}
|
|
104
160
|
/** Only complete native values enter narration. The caller enforces submission/step correlation. */
|
|
105
161
|
export function mapEvent(value, events, state) {
|
|
106
162
|
const event = object(value);
|
|
@@ -110,62 +166,36 @@ export function mapEvent(value, events, state) {
|
|
|
110
166
|
if (state.sessionId !== undefined && data.sessionID !== state.sessionId)
|
|
111
167
|
return;
|
|
112
168
|
const type = event.type;
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
const id = text(model?.id);
|
|
117
|
-
if (provider !== undefined && id !== undefined)
|
|
118
|
-
events.emit(modelEvent(`${provider}/${id}`));
|
|
119
|
-
return;
|
|
120
|
-
}
|
|
121
|
-
if (type === "session.text.ended" || type === "session.reasoning.ended") {
|
|
122
|
-
if (typeof data.text === "string")
|
|
123
|
-
events.emit({ type: type === "session.text.ended" ? "assistant" : "thought", text: data.text });
|
|
124
|
-
return;
|
|
125
|
-
}
|
|
126
|
-
if (type === "session.tool.input.started") {
|
|
127
|
-
if (typeof data.id === "string" && typeof data.name === "string")
|
|
128
|
-
state.tools.set(data.id, { name: data.name });
|
|
129
|
-
return;
|
|
130
|
-
}
|
|
131
|
-
if (type === "session.tool.called" || type === "session.tool.success" || type === "session.tool.failed") {
|
|
132
|
-
const id = text(data.id);
|
|
133
|
-
const started = id === undefined ? undefined : state.tools.get(id);
|
|
134
|
-
if (id === undefined || started === undefined || state.completed.has(id))
|
|
169
|
+
switch (type) {
|
|
170
|
+
case "session.step.started":
|
|
171
|
+
stepStarted(data, events);
|
|
135
172
|
return;
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
if (type === "session.tool.called")
|
|
173
|
+
case "session.text.ended":
|
|
174
|
+
case "session.reasoning.ended":
|
|
175
|
+
if (typeof data.text === "string")
|
|
176
|
+
events.emit({ type: type === "session.text.ended" ? "assistant" : "thought", text: data.text });
|
|
141
177
|
return;
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
if (type === "session.compaction.failed") {
|
|
163
|
-
events.emit(noteEvent(diagnostic(data.error)));
|
|
164
|
-
return;
|
|
178
|
+
case "session.tool.input.started":
|
|
179
|
+
toolInputStarted(data, state);
|
|
180
|
+
return;
|
|
181
|
+
case "session.tool.called":
|
|
182
|
+
case "session.tool.success":
|
|
183
|
+
case "session.tool.failed":
|
|
184
|
+
toolEvent(type, data, events, state);
|
|
185
|
+
return;
|
|
186
|
+
case "session.compaction.ended":
|
|
187
|
+
events.emit(noteEvent("Context compacted"));
|
|
188
|
+
return;
|
|
189
|
+
case "session.compaction.failed":
|
|
190
|
+
events.emit(noteEvent(diagnostic(data.error)));
|
|
191
|
+
return;
|
|
192
|
+
case "session.compaction.started":
|
|
193
|
+
case "session.compaction.delta":
|
|
194
|
+
return;
|
|
195
|
+
default:
|
|
196
|
+
if (!DROPPED.has(type))
|
|
197
|
+
events.emit({ type: "unknown", kind: type });
|
|
165
198
|
}
|
|
166
|
-
if (type === "session.compaction.started" || type === "session.compaction.delta" || DROPPED.has(type))
|
|
167
|
-
return;
|
|
168
|
-
events.emit({ type: "unknown", kind: type });
|
|
169
199
|
}
|
|
170
200
|
export function createEventState(sessionId) {
|
|
171
201
|
return { ...(sessionId === undefined ? {} : { sessionId }), tools: new Map(), completed: new Set() };
|
|
@@ -34,8 +34,16 @@ function ownRuntime(custody, runtime) {
|
|
|
34
34
|
}
|
|
35
35
|
return closing;
|
|
36
36
|
};
|
|
37
|
-
|
|
38
|
-
|
|
37
|
+
let abort = close;
|
|
38
|
+
let forceDispose = close;
|
|
39
|
+
custody.own({ closed, abort: () => abort(), forceDispose: () => forceDispose() });
|
|
40
|
+
return {
|
|
41
|
+
close,
|
|
42
|
+
setControls(controls) {
|
|
43
|
+
abort = controls.abort;
|
|
44
|
+
forceDispose = controls.forceDispose;
|
|
45
|
+
},
|
|
46
|
+
};
|
|
39
47
|
}
|
|
40
48
|
function liveAdmission(initial) {
|
|
41
49
|
let latest = initial;
|
|
@@ -88,10 +96,52 @@ async function answer(runtime, id, assistantID, terminalID) {
|
|
|
88
96
|
}
|
|
89
97
|
return { kind: "answered", answer: text, historyId: boundary };
|
|
90
98
|
}
|
|
99
|
+
function executionResult(input, state, event, data) {
|
|
100
|
+
const type = event?.type;
|
|
101
|
+
if (type === "session.step.failed" || type === "session.execution.failed") {
|
|
102
|
+
return { kind: "failed", diagnostic: diagnostic(data.error) };
|
|
103
|
+
}
|
|
104
|
+
if (type === "session.execution.interrupted")
|
|
105
|
+
return { kind: "failed", diagnostic: "OpenCode native execution interrupted" };
|
|
106
|
+
if (type !== "session.execution.succeeded" || state.delivered === undefined)
|
|
107
|
+
return;
|
|
108
|
+
const step = state.step;
|
|
109
|
+
if (step === undefined || !step.ended || step.input !== input.admission.latest()) {
|
|
110
|
+
return { kind: "failed", diagnostic: "OpenCode completed without an assistant for the admitted input" };
|
|
111
|
+
}
|
|
112
|
+
const terminalID = nonblank(event?.id);
|
|
113
|
+
if (terminalID === undefined || !terminalID.startsWith("evt_"))
|
|
114
|
+
return { kind: "failed", diagnostic: "OpenCode terminal identity missing" };
|
|
115
|
+
return answer(input.runtime, input.id, step.id, terminalID);
|
|
116
|
+
}
|
|
117
|
+
function observeEvent(input, state, event, data) {
|
|
118
|
+
const type = event?.type;
|
|
119
|
+
if (type === "session.inbox.enqueued" && typeof data.inboxID === "string" && object(data.item)?.type === "user") {
|
|
120
|
+
input.admission.admit(data.inboxID);
|
|
121
|
+
}
|
|
122
|
+
if (type === "session.inbox.delivered" && typeof data.inboxID === "string") {
|
|
123
|
+
if (!input.admission.owns(data.inboxID)) {
|
|
124
|
+
return {
|
|
125
|
+
kind: "failed",
|
|
126
|
+
diagnostic: "OpenCode consumed another writer's input; exact answer attribution unavailable",
|
|
127
|
+
};
|
|
128
|
+
}
|
|
129
|
+
input.admission.admit(data.inboxID);
|
|
130
|
+
state.delivered = data.inboxID;
|
|
131
|
+
}
|
|
132
|
+
if (type === "session.step.started" && state.delivered !== undefined && typeof data.assistantMessageID === "string") {
|
|
133
|
+
state.step = { id: data.assistantMessageID, input: state.delivered, ended: false };
|
|
134
|
+
}
|
|
135
|
+
if (data.assistantMessageID !== undefined && data.assistantMessageID !== state.step?.id)
|
|
136
|
+
return;
|
|
137
|
+
if (state.delivered !== undefined)
|
|
138
|
+
mapEvent(event, input.events, state.narration);
|
|
139
|
+
if (type === "session.step.ended" && state.step !== undefined)
|
|
140
|
+
state.step.ended = true;
|
|
141
|
+
return executionResult(input, state, event, data);
|
|
142
|
+
}
|
|
91
143
|
async function observe(input) {
|
|
92
|
-
const state = createEventState(input.id);
|
|
93
|
-
let delivered;
|
|
94
|
-
let step;
|
|
144
|
+
const state = { narration: createEventState(input.id), delivered: undefined, step: undefined };
|
|
95
145
|
try {
|
|
96
146
|
for (;;) {
|
|
97
147
|
const next = await input.stream.next();
|
|
@@ -101,43 +151,9 @@ async function observe(input) {
|
|
|
101
151
|
const data = object(event?.data);
|
|
102
152
|
if (data?.sessionID !== input.id)
|
|
103
153
|
continue;
|
|
104
|
-
const
|
|
105
|
-
if (
|
|
106
|
-
|
|
107
|
-
}
|
|
108
|
-
if (type === "session.inbox.delivered" && typeof data.inboxID === "string") {
|
|
109
|
-
if (!input.admission.owns(data.inboxID)) {
|
|
110
|
-
return {
|
|
111
|
-
kind: "failed",
|
|
112
|
-
diagnostic: "OpenCode consumed another writer's input; exact answer attribution unavailable",
|
|
113
|
-
};
|
|
114
|
-
}
|
|
115
|
-
input.admission.admit(data.inboxID);
|
|
116
|
-
delivered = data.inboxID;
|
|
117
|
-
}
|
|
118
|
-
if (type === "session.step.started" && delivered !== undefined && typeof data.assistantMessageID === "string") {
|
|
119
|
-
step = { id: data.assistantMessageID, input: delivered, ended: false };
|
|
120
|
-
}
|
|
121
|
-
if (data.assistantMessageID !== undefined && data.assistantMessageID !== step?.id)
|
|
122
|
-
continue;
|
|
123
|
-
if (delivered !== undefined)
|
|
124
|
-
mapEvent(event, input.events, state);
|
|
125
|
-
if (type === "session.step.ended" && step !== undefined)
|
|
126
|
-
step.ended = true;
|
|
127
|
-
if (type === "session.step.failed" || type === "session.execution.failed") {
|
|
128
|
-
return { kind: "failed", diagnostic: diagnostic(data.error) };
|
|
129
|
-
}
|
|
130
|
-
if (type === "session.execution.interrupted")
|
|
131
|
-
return { kind: "failed", diagnostic: "OpenCode native execution interrupted" };
|
|
132
|
-
if (type !== "session.execution.succeeded" || delivered === undefined)
|
|
133
|
-
continue;
|
|
134
|
-
if (step === undefined || !step.ended || step.input !== input.admission.latest()) {
|
|
135
|
-
return { kind: "failed", diagnostic: "OpenCode completed without an assistant for the admitted input" };
|
|
136
|
-
}
|
|
137
|
-
const terminalID = nonblank(event?.id);
|
|
138
|
-
if (terminalID === undefined || !terminalID.startsWith("evt_"))
|
|
139
|
-
return { kind: "failed", diagnostic: "OpenCode terminal identity missing" };
|
|
140
|
-
return await answer(input.runtime, input.id, step.id, terminalID);
|
|
154
|
+
const result = observeEvent(input, state, event, data);
|
|
155
|
+
if (result !== undefined)
|
|
156
|
+
return await result;
|
|
141
157
|
}
|
|
142
158
|
}
|
|
143
159
|
catch (error) {
|
|
@@ -147,6 +163,25 @@ async function observe(input) {
|
|
|
147
163
|
input.admission.end();
|
|
148
164
|
}
|
|
149
165
|
}
|
|
166
|
+
async function configureSession(runtime, input, id, info, model) {
|
|
167
|
+
const session = runtime.client.session;
|
|
168
|
+
let selected = object(info?.model);
|
|
169
|
+
if (input.session.kind === "resume" && model !== undefined) {
|
|
170
|
+
await session.switchModel({ id, model });
|
|
171
|
+
selected = object(object(object(await session.get({ id }))?.data)?.model);
|
|
172
|
+
}
|
|
173
|
+
if (model !== undefined &&
|
|
174
|
+
(selected?.providerID !== model.providerID ||
|
|
175
|
+
selected.id !== model.id ||
|
|
176
|
+
(selected.variant ?? "default") !== (input.options.effort ?? "default"))) {
|
|
177
|
+
throw new Error("OpenCode did not select the requested model");
|
|
178
|
+
}
|
|
179
|
+
if (input.options.systemPrompt !== undefined) {
|
|
180
|
+
// Native instruction entries join the system transcript and survive resume
|
|
181
|
+
// and fork. Updating the same entry announces an upstream instruction change.
|
|
182
|
+
await session.instructions({ id, text: input.options.systemPrompt });
|
|
183
|
+
}
|
|
184
|
+
}
|
|
150
185
|
async function openSession(runtime, input) {
|
|
151
186
|
const model = input.options.model === undefined
|
|
152
187
|
? undefined
|
|
@@ -169,38 +204,18 @@ async function openSession(runtime, input) {
|
|
|
169
204
|
throw new Error("OpenCode active execution response missing");
|
|
170
205
|
if (active[id] !== undefined)
|
|
171
206
|
throw new Error("OpenCode native session is already busy");
|
|
172
|
-
|
|
173
|
-
if (input.session.kind === "resume" && model !== undefined) {
|
|
174
|
-
await session.switchModel({ id, model });
|
|
175
|
-
selected = object(object(object(await session.get({ id }))?.data)?.model);
|
|
176
|
-
}
|
|
177
|
-
if (model !== undefined &&
|
|
178
|
-
(selected?.providerID !== model.providerID ||
|
|
179
|
-
selected.id !== model.id ||
|
|
180
|
-
(selected.variant ?? "default") !== (input.options.effort ?? "default"))) {
|
|
181
|
-
throw new Error("OpenCode did not select the requested model");
|
|
182
|
-
}
|
|
183
|
-
if (input.options.systemPrompt !== undefined) {
|
|
184
|
-
// Native instruction entries join the system transcript and survive resume
|
|
185
|
-
// and fork. Updating the same entry announces an upstream instruction change.
|
|
186
|
-
await session.instructions({ id, text: input.options.systemPrompt });
|
|
187
|
-
}
|
|
207
|
+
await configureSession(runtime, input, id, info, model);
|
|
188
208
|
return id;
|
|
189
209
|
}
|
|
190
|
-
async function
|
|
191
|
-
|
|
192
|
-
if (input.schemaJson !== undefined)
|
|
193
|
-
throw new Error("OpenCode v2 does not expose native structured answer schemas");
|
|
194
|
-
if (input.session.kind === "resume")
|
|
195
|
-
sessionId(input.session.coordinate);
|
|
196
|
-
let closeRuntime;
|
|
210
|
+
async function openResources(execution, input, loader, custody) {
|
|
211
|
+
let runtimeOwner;
|
|
197
212
|
const runtime = await loadOpencode({
|
|
198
213
|
execution,
|
|
199
214
|
cwd: input.cwd,
|
|
200
215
|
signal: custody.signal,
|
|
201
216
|
...(loader === undefined ? {} : { loader }),
|
|
202
217
|
onRuntime: (ready) => {
|
|
203
|
-
|
|
218
|
+
runtimeOwner = ownRuntime(custody, ready);
|
|
204
219
|
},
|
|
205
220
|
...(input.requests === undefined ? {} : { requests: input.requests.dir }),
|
|
206
221
|
});
|
|
@@ -211,91 +226,105 @@ async function drive(execution, input, loader, custody) {
|
|
|
211
226
|
subscribe: runtime.client.event.subscribe,
|
|
212
227
|
});
|
|
213
228
|
let closing;
|
|
214
|
-
const close = () => (closing ??= Promise.all([stream.retire(),
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
.then(async (result) => {
|
|
242
|
-
// Explicit interrupt owns its response too: a terminal broadcast can be
|
|
243
|
-
// published before that response, and must not close its connection early.
|
|
244
|
-
if (control !== undefined)
|
|
245
|
-
await control;
|
|
246
|
-
await finish(result);
|
|
247
|
-
})
|
|
248
|
-
.catch(() => undefined);
|
|
249
|
-
const prompt = async (nativeID, text) => {
|
|
250
|
-
const response = object(object(await runtime.client.session.prompt({ id, body: { id: nativeID, text } }))?.data);
|
|
251
|
-
if (response?.id !== nativeID || response.sessionID !== id || response.type !== "user") {
|
|
252
|
-
throw new Error("OpenCode returned no exact native inbox admission");
|
|
253
|
-
}
|
|
229
|
+
const close = () => (closing ??= Promise.all([stream.retire(), runtimeOwner.close()]).then(() => undefined));
|
|
230
|
+
return { runtime, stream, runtimeOwner, close };
|
|
231
|
+
}
|
|
232
|
+
async function submitPrompt(runtime, id, nativeID, text) {
|
|
233
|
+
const response = object(object(await runtime.client.session.prompt({ id, body: { id: nativeID, text } }))?.data);
|
|
234
|
+
if (response?.id !== nativeID || response.sessionID !== id || response.type !== "user") {
|
|
235
|
+
throw new Error("OpenCode returned no exact native inbox admission");
|
|
236
|
+
}
|
|
237
|
+
}
|
|
238
|
+
async function openTurn(input, signal, resources, id) {
|
|
239
|
+
const { runtime, stream, runtimeOwner, close } = resources;
|
|
240
|
+
const events = new AgentEventChannel();
|
|
241
|
+
events.emit({ type: "session", coordinate: coordinate(id) });
|
|
242
|
+
const initial = messageId();
|
|
243
|
+
const admission = liveAdmission(initial);
|
|
244
|
+
let control;
|
|
245
|
+
let finishing = false;
|
|
246
|
+
let finish;
|
|
247
|
+
const completion = new Promise((resolve) => {
|
|
248
|
+
let settlement;
|
|
249
|
+
finish = (result) => {
|
|
250
|
+
finishing = true;
|
|
251
|
+
return (settlement ??= (async () => {
|
|
252
|
+
await close();
|
|
253
|
+
events.end();
|
|
254
|
+
resolve(result);
|
|
255
|
+
})());
|
|
254
256
|
};
|
|
255
|
-
|
|
257
|
+
});
|
|
258
|
+
const observed = observe({ runtime, stream, id, admission, events });
|
|
259
|
+
// Start observation before submission: admission broadcasts can precede HTTP completion.
|
|
260
|
+
void observed
|
|
261
|
+
.then(async (result) => {
|
|
262
|
+
// Explicit interrupt owns its response too: a terminal broadcast can be
|
|
263
|
+
// published before that response, and must not close its connection early.
|
|
264
|
+
if (control !== undefined)
|
|
265
|
+
await control;
|
|
266
|
+
await finish(result);
|
|
267
|
+
})
|
|
268
|
+
.catch(() => undefined);
|
|
269
|
+
const prompt = (nativeID, text) => submitPrompt(runtime, id, nativeID, text);
|
|
270
|
+
await prompt(initial, launchPrompt(input.body, input.launchTells));
|
|
271
|
+
signal.throwIfAborted();
|
|
272
|
+
let submissions = admission.initial.then(() => undefined);
|
|
273
|
+
const abort = async () => {
|
|
274
|
+
admission.end();
|
|
275
|
+
if (finishing) {
|
|
276
|
+
await close();
|
|
277
|
+
return;
|
|
278
|
+
}
|
|
279
|
+
// The v2 interrupt response follows execution settlement. Do not hide its
|
|
280
|
+
// refusal; server and connection retirement must still run on failure.
|
|
281
|
+
control ??= runtime.client.session.interrupt({ id });
|
|
282
|
+
try {
|
|
283
|
+
await control;
|
|
284
|
+
}
|
|
285
|
+
finally {
|
|
286
|
+
await finish({ kind: "failed", diagnostic: "OpenCode session interrupted" });
|
|
287
|
+
}
|
|
288
|
+
};
|
|
289
|
+
const forceDispose = async () => {
|
|
290
|
+
admission.end();
|
|
291
|
+
await finish({ kind: "failed", diagnostic: "OpenCode session force-disposed" });
|
|
292
|
+
};
|
|
293
|
+
runtimeOwner.setControls({ abort, forceDispose });
|
|
294
|
+
return {
|
|
295
|
+
admission: { fence: initial },
|
|
296
|
+
events,
|
|
297
|
+
completion,
|
|
298
|
+
tell(tell) {
|
|
299
|
+
const submit = async () => {
|
|
300
|
+
if (!admission.open())
|
|
301
|
+
return { kind: "turn-ended" };
|
|
302
|
+
const nativeID = messageId();
|
|
303
|
+
const receipt = admission.wait(nativeID);
|
|
304
|
+
void prompt(nativeID, tell.text).catch((error) => admission.fail(nativeID, error));
|
|
305
|
+
return await receipt;
|
|
306
|
+
};
|
|
307
|
+
const next = submissions.then(submit);
|
|
308
|
+
submissions = next.then(() => undefined, () => undefined);
|
|
309
|
+
return next;
|
|
310
|
+
},
|
|
311
|
+
};
|
|
312
|
+
}
|
|
313
|
+
async function drive(execution, input, loader, custody) {
|
|
314
|
+
admit(input.options);
|
|
315
|
+
if (input.schemaJson !== undefined)
|
|
316
|
+
throw new Error("OpenCode v2 does not expose native structured answer schemas");
|
|
317
|
+
if (input.session.kind === "resume")
|
|
318
|
+
sessionId(input.session.coordinate);
|
|
319
|
+
const resources = await openResources(execution, input, loader, custody);
|
|
320
|
+
try {
|
|
321
|
+
const id = await openSession(resources.runtime, input);
|
|
322
|
+
await resources.stream.ready;
|
|
256
323
|
custody.signal.throwIfAborted();
|
|
257
|
-
|
|
258
|
-
return {
|
|
259
|
-
admission: { fence: initial },
|
|
260
|
-
events,
|
|
261
|
-
completion,
|
|
262
|
-
tell(tell) {
|
|
263
|
-
const submit = async () => {
|
|
264
|
-
if (!admission.open())
|
|
265
|
-
return { kind: "turn-ended" };
|
|
266
|
-
const nativeID = messageId(sequence++);
|
|
267
|
-
const receipt = admission.wait(nativeID);
|
|
268
|
-
void prompt(nativeID, tell.text).catch((error) => admission.fail(nativeID, error));
|
|
269
|
-
return await receipt;
|
|
270
|
-
};
|
|
271
|
-
const next = submissions.then(submit);
|
|
272
|
-
submissions = next.then(() => undefined, () => undefined);
|
|
273
|
-
return next;
|
|
274
|
-
},
|
|
275
|
-
abort: async () => {
|
|
276
|
-
admission.end();
|
|
277
|
-
if (finishing) {
|
|
278
|
-
await close();
|
|
279
|
-
return;
|
|
280
|
-
}
|
|
281
|
-
// The v2 interrupt response follows execution settlement. Do not hide its
|
|
282
|
-
// refusal; server and connection retirement must still run on failure.
|
|
283
|
-
control ??= runtime.client.session.interrupt({ id });
|
|
284
|
-
try {
|
|
285
|
-
await control;
|
|
286
|
-
}
|
|
287
|
-
finally {
|
|
288
|
-
await finish({ kind: "failed", diagnostic: "OpenCode session interrupted" });
|
|
289
|
-
}
|
|
290
|
-
},
|
|
291
|
-
forceDispose: async () => {
|
|
292
|
-
admission.end();
|
|
293
|
-
await finish({ kind: "failed", diagnostic: "OpenCode session force-disposed" });
|
|
294
|
-
},
|
|
295
|
-
};
|
|
324
|
+
return await openTurn(input, custody.signal, resources, id);
|
|
296
325
|
}
|
|
297
326
|
catch (error) {
|
|
298
|
-
await close();
|
|
327
|
+
await resources.close();
|
|
299
328
|
throw error;
|
|
300
329
|
}
|
|
301
330
|
}
|
|
@@ -315,14 +344,14 @@ export function createOpencodeProvider(input = { name: OPENCODE_SDK_PROVIDER, ki
|
|
|
315
344
|
...providerLifecycle((input, custody) => drive(execution, input, loader, custody)),
|
|
316
345
|
fork: (input) => createProviderAttempt(new AbortController().signal, async (custody) => {
|
|
317
346
|
const id = sessionId(input.session);
|
|
318
|
-
let
|
|
347
|
+
let runtimeOwner;
|
|
319
348
|
const runtime = await loadOpencode({
|
|
320
349
|
execution,
|
|
321
350
|
cwd: input.cwd,
|
|
322
351
|
signal: custody.signal,
|
|
323
352
|
...(loader === undefined ? {} : { loader }),
|
|
324
353
|
onRuntime: (ready) => {
|
|
325
|
-
|
|
354
|
+
runtimeOwner = ownRuntime(custody, ready);
|
|
326
355
|
},
|
|
327
356
|
});
|
|
328
357
|
try {
|
|
@@ -345,7 +374,7 @@ export function createOpencodeProvider(input = { name: OPENCODE_SDK_PROVIDER, ki
|
|
|
345
374
|
return { session: coordinate(child) };
|
|
346
375
|
}
|
|
347
376
|
finally {
|
|
348
|
-
await close();
|
|
377
|
+
await runtimeOwner.close();
|
|
349
378
|
}
|
|
350
379
|
}),
|
|
351
380
|
};
|
|
@@ -1,11 +1,15 @@
|
|
|
1
1
|
import { OpenCode } from "@opencode/client";
|
|
2
|
-
import { Agent } from "undici";
|
|
2
|
+
import { Agent, fetch as undiciFetch } from "undici";
|
|
3
3
|
import { object } from "../decode.js";
|
|
4
|
+
// A dispatcher is only understood by the fetch built for the same Undici, and
|
|
5
|
+
// Node's global fetch carries its own copy. Take both halves from the declared
|
|
6
|
+
// dependency so the generated SDK cannot hand this Agent to a foreign Undici.
|
|
7
|
+
const matchedFetch = undiciFetch;
|
|
4
8
|
/** The matching native SDK owns the dialect; this attempt owns its HTTP connections. */
|
|
5
9
|
export function createNativeClient(baseUrl, password) {
|
|
6
10
|
const dispatcher = new Agent();
|
|
7
11
|
const controller = new AbortController();
|
|
8
|
-
//
|
|
12
|
+
// Undici's fetch accepts a dispatcher in addition to standard RequestInit.
|
|
9
13
|
// Keep that native extension at this one SDK seam.
|
|
10
14
|
const ownedFetch = (input, init) => {
|
|
11
15
|
controller.signal.throwIfAborted();
|
|
@@ -14,7 +18,7 @@ export function createNativeClient(baseUrl, password) {
|
|
|
14
18
|
dispatcher,
|
|
15
19
|
signal: init?.signal == null ? controller.signal : AbortSignal.any([controller.signal, init.signal]),
|
|
16
20
|
};
|
|
17
|
-
return
|
|
21
|
+
return matchedFetch(input, options);
|
|
18
22
|
};
|
|
19
23
|
const sdk = OpenCode.make({
|
|
20
24
|
baseUrl,
|