@astrosheep/keiyaku 1.0.0 → 1.0.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +73 -56
- package/build/.tsbuildinfo +1 -1
- package/build/agents/call-terms_v2.js +103 -0
- package/build/agents/harness/activity-values.js +54 -0
- package/build/agents/harness/event-channel.js +55 -20
- package/build/agents/harness/events.js +47 -1
- package/build/agents/harness/execution-handle.js +292 -61
- package/build/agents/harness/index.js +103 -121
- package/build/agents/harness/pump.js +36 -47
- package/build/agents/harness/runtime.js +3 -0
- package/build/agents/launch-snapshot_v2.js +348 -0
- package/build/agents/opencode-sdk.js +42 -169
- package/build/agents/providers/claude-agent-sdk.js +240 -79
- package/build/agents/providers/codex-app-server.js +280 -72
- package/build/agents/providers/codex-sdk.js +159 -41
- package/build/agents/providers/event-type-tail.js +16 -0
- package/build/agents/providers/opencode-sdk.js +165 -59
- package/build/agents/providers/pi.js +254 -42
- package/build/agents/selector_v2.js +122 -0
- package/build/cli/commands/akuma.js +120 -42
- package/build/cli/completion.js +6 -7
- package/build/cli/flags.js +36 -10
- package/build/cli/help.js +42 -26
- package/build/cli/index.js +225 -93
- package/build/cli/parse.js +194 -75
- package/build/cli/projection-address.js +11 -0
- package/build/cli/render/address.js +88 -0
- package/build/cli/render/arc.js +10 -10
- package/build/cli/render/call.js +59 -0
- package/build/cli/render/compact-text.js +3 -0
- package/build/cli/render/errors.js +4 -2
- package/build/cli/render/format.js +6 -0
- package/build/cli/render/line-width.js +70 -0
- package/build/cli/render/misc.js +7 -4
- package/build/cli/render/petition.js +4 -3
- package/build/cli/render/projection-activity.js +201 -0
- package/build/cli/render/shared.js +46 -12
- package/build/cli/render/status.js +59 -10
- package/build/cli/render/tool-ledger-rollup.js +74 -0
- package/build/cli/render/tool-presentation.js +97 -0
- package/build/cli/render/wait.js +229 -0
- package/build/cli/skills-install.js +42 -24
- package/build/cli/subagent-guard.js +1 -1
- package/build/config/akuma-loader_v2.js +222 -0
- package/build/config/env-keys.js +0 -4
- package/build/config/env.js +0 -6
- package/build/config/settings.js +325 -12
- package/build/core/addressing.js +224 -0
- package/build/core/amend.js +30 -47
- package/build/core/arc.js +68 -60
- package/build/core/bind.js +105 -25
- package/build/core/call-persist.js +69 -0
- package/build/core/call.js +658 -0
- package/build/core/claim.js +72 -44
- package/build/core/command-io.js +19 -19
- package/build/core/context.js +11 -36
- package/build/core/draft.js +2 -3
- package/build/core/entry.js +101 -6
- package/build/core/execution-coordinate.js +62 -0
- package/build/core/execution-pact.js +141 -0
- package/build/core/forfeit.js +2 -3
- package/build/core/hints.js +10 -10
- package/build/core/ids.js +6 -0
- package/build/core/ledger.js +16 -1
- package/build/core/petition-claim-gates.js +14 -12
- package/build/core/petition-claim.js +1 -0
- package/build/core/petition-run.js +6 -2
- package/build/core/petition.js +63 -21
- package/build/core/places.js +106 -13
- package/build/core/projection/generation.js +412 -0
- package/build/core/projection/heart.js +316 -0
- package/build/core/projection/identity.js +81 -0
- package/build/core/projection/leash.js +87 -0
- package/build/core/projection/mint.js +154 -0
- package/build/core/projection-activity.js +219 -0
- package/build/core/projection-coordinate.js +35 -0
- package/build/core/projection-core.js +69 -463
- package/build/core/projection-generation-continuation.js +31 -0
- package/build/core/projection-generation-execution.js +148 -0
- package/build/core/projection-generation-identity.js +17 -0
- package/build/core/projection-generation-launcher.js +90 -0
- package/build/core/projection-generation-process.js +43 -0
- package/build/core/projection-generation-runner.js +144 -0
- package/build/core/projection-generation-runtime.js +15 -0
- package/build/core/projection-generation-store.js +707 -0
- package/build/core/projection-identity.js +11 -0
- package/build/core/projection-life-observer.js +87 -0
- package/build/core/projection-life-protocol.js +95 -0
- package/build/core/projection-mint.js +96 -24
- package/build/core/projection-runner-lock.js +230 -0
- package/build/core/projection-status.js +207 -107
- package/build/core/projection-tells.js +401 -0
- package/build/core/projection-wait.js +155 -0
- package/build/core/projection-wake.js +109 -231
- package/build/core/queue_v2.js +342 -0
- package/build/core/registry.js +6 -3
- package/build/core/render.js +63 -1
- package/build/core/renew.js +80 -53
- package/build/core/scope.js +186 -137
- package/build/core/seal.js +16 -19
- package/build/core/status.js +88 -25
- package/build/core/stored-agent-event.js +101 -0
- package/build/core/transcripts.js +214 -228
- package/build/core/verdict.js +27 -15
- package/build/core/worktree-bootstrap.js +187 -0
- package/build/core/worktree-path.js +72 -18
- package/build/flow-error.js +4 -6
- package/build/generated/version.js +1 -1
- package/build/git/branches.js +6 -1
- package/build/index.js +8 -5
- package/build/keiyaku.js +1 -3
- package/package.json +6 -5
- package/skills/keiyaku/SKILL.md +6 -47
- package/skills/keiyaku-akuma/SKILL.md +75 -0
- package/build/agents/effective-policy.js +0 -32
- package/build/agents/index.js +0 -114
- package/build/agents/selector.js +0 -28
- package/build/cli/render/summon.js +0 -17
- package/build/cli/render/tell.js +0 -11
- package/build/config/akuma-loader.js +0 -268
- package/build/core/queue.js +0 -73
- package/build/core/summon-persist.js +0 -73
- package/build/core/summon.js +0 -377
|
@@ -3,6 +3,8 @@ import { FlowError } from "../../flow-error.js";
|
|
|
3
3
|
import { appendDebugLog } from "../../telemetry/debug-log.js";
|
|
4
4
|
import { SUBAGENT_SESSION_VERSION } from "../session.js";
|
|
5
5
|
import { createProjectionDriver } from "../harness/execution-handle.js";
|
|
6
|
+
import { activityCallFromNamedTool, boundActivityExcerpt, diffstatFromUnifiedPatch } from "../harness/activity-values.js";
|
|
7
|
+
import { AGENT_THOUGHT_TEXT_MAX_CHARS } from "../harness/events.js";
|
|
6
8
|
import { createAbortError, emitProviderEvidence, sha256Utf8 } from "../harness/runtime.js";
|
|
7
9
|
import { SubagentFailure, } from "../types.js";
|
|
8
10
|
/** Opaque Pi resume handle: exact session file path (UUID alone is ambiguous). */
|
|
@@ -13,6 +15,34 @@ export const piSessionSchema = z.object({
|
|
|
13
15
|
const PI_PROVIDER = "pi";
|
|
14
16
|
/** Exact model shape: `<provider>/<id>` (single slash separating non-empty sides). */
|
|
15
17
|
const PI_MODEL_PATTERN = /^[^/]+\/[^/]+$/;
|
|
18
|
+
const PI_ACKNOWLEDGED_UNPERSISTED = new Set(["message_update", "queue_update"]);
|
|
19
|
+
const PI_MAPPED = new Set([
|
|
20
|
+
"agent_start",
|
|
21
|
+
"agent_end",
|
|
22
|
+
"turn_start",
|
|
23
|
+
"turn_end",
|
|
24
|
+
"message_start",
|
|
25
|
+
"message_end",
|
|
26
|
+
"tool_execution_start",
|
|
27
|
+
"tool_execution_update",
|
|
28
|
+
"tool_execution_end",
|
|
29
|
+
"compaction_start",
|
|
30
|
+
"compaction_end",
|
|
31
|
+
"session_info_changed",
|
|
32
|
+
"thinking_level_changed",
|
|
33
|
+
"auto_retry_start",
|
|
34
|
+
"auto_retry_end",
|
|
35
|
+
]);
|
|
36
|
+
function piIngressDisposition(type) {
|
|
37
|
+
if (PI_ACKNOWLEDGED_UNPERSISTED.has(type))
|
|
38
|
+
return "acknowledged-unpersisted";
|
|
39
|
+
if (PI_MAPPED.has(type))
|
|
40
|
+
return "mapped";
|
|
41
|
+
return "unknown";
|
|
42
|
+
}
|
|
43
|
+
function toPiSdkThinkingLevel(effort) {
|
|
44
|
+
return effort; // opacity boundary
|
|
45
|
+
}
|
|
16
46
|
let piSdkLoader;
|
|
17
47
|
const importPiSdk = new Function("specifier", "return import(specifier)");
|
|
18
48
|
async function loadPiSdk() {
|
|
@@ -39,7 +69,7 @@ export function parsePiModelRef(model) {
|
|
|
39
69
|
};
|
|
40
70
|
}
|
|
41
71
|
function extractAssistantText(event) {
|
|
42
|
-
if (event.type === "
|
|
72
|
+
if (event.type === "message_end") {
|
|
43
73
|
const message = event.message;
|
|
44
74
|
if (message?.role !== "assistant")
|
|
45
75
|
return undefined;
|
|
@@ -58,7 +88,9 @@ function extractContentText(content) {
|
|
|
58
88
|
.map((block) => {
|
|
59
89
|
if (typeof block === "string")
|
|
60
90
|
return block;
|
|
61
|
-
if (block &&
|
|
91
|
+
if (block &&
|
|
92
|
+
typeof block === "object" &&
|
|
93
|
+
block.type === "text") {
|
|
62
94
|
const candidate = block.text;
|
|
63
95
|
return typeof candidate === "string" ? candidate : "";
|
|
64
96
|
}
|
|
@@ -68,15 +100,123 @@ function extractContentText(content) {
|
|
|
68
100
|
.trim();
|
|
69
101
|
return text || undefined;
|
|
70
102
|
}
|
|
103
|
+
function emitPiThoughts(builder, event, turnOrdinal, messageOrdinal) {
|
|
104
|
+
if (event.type !== "message_end")
|
|
105
|
+
return;
|
|
106
|
+
const message = event.message;
|
|
107
|
+
if (message?.role !== "assistant" || !Array.isArray(message.content))
|
|
108
|
+
return;
|
|
109
|
+
message.content.forEach((block, blockIndex) => {
|
|
110
|
+
if (!block || typeof block !== "object")
|
|
111
|
+
return;
|
|
112
|
+
const record = block;
|
|
113
|
+
if (record.type !== "thinking" || record.redacted === true)
|
|
114
|
+
return;
|
|
115
|
+
if (typeof record.thinking !== "string" || record.thinking.trim().length === 0)
|
|
116
|
+
return;
|
|
117
|
+
const truncated = record.thinking.length > AGENT_THOUGHT_TEXT_MAX_CHARS;
|
|
118
|
+
builder.emit({
|
|
119
|
+
type: "thought",
|
|
120
|
+
id: `t${turnOrdinal}:m${messageOrdinal}:b${blockIndex}`,
|
|
121
|
+
text: record.thinking.slice(0, AGENT_THOUGHT_TEXT_MAX_CHARS),
|
|
122
|
+
final: true,
|
|
123
|
+
...(truncated ? { truncated: true } : {}),
|
|
124
|
+
});
|
|
125
|
+
});
|
|
126
|
+
}
|
|
127
|
+
function piRecord(value) {
|
|
128
|
+
return value !== null && typeof value === "object" && !Array.isArray(value)
|
|
129
|
+
? value
|
|
130
|
+
: undefined;
|
|
131
|
+
}
|
|
132
|
+
function piCallFacts(args) {
|
|
133
|
+
const value = piRecord(args);
|
|
134
|
+
if (!value)
|
|
135
|
+
return {};
|
|
136
|
+
return {
|
|
137
|
+
...(typeof value.command === "string" ? { command: value.command } : {}),
|
|
138
|
+
...(typeof value.description === "string" ? { description: value.description } : {}),
|
|
139
|
+
...(typeof value.path === "string" ? { path: value.path } : {}),
|
|
140
|
+
...(typeof value.offset === "number" && Number.isFinite(value.offset) ? { offset: value.offset } : {}),
|
|
141
|
+
...(typeof value.limit === "number" && Number.isFinite(value.limit) ? { limit: value.limit } : {}),
|
|
142
|
+
...(typeof value.query === "string" ? { query: value.query } : {}),
|
|
143
|
+
};
|
|
144
|
+
}
|
|
145
|
+
function piResult(value, isError) {
|
|
146
|
+
const payload = piRecord(value);
|
|
147
|
+
const content = Array.isArray(payload?.content)
|
|
148
|
+
? payload.content.flatMap((entry) => {
|
|
149
|
+
const block = piRecord(entry);
|
|
150
|
+
return block?.type === "text" && typeof block.text === "string" ? [block.text] : [];
|
|
151
|
+
}).join("\n")
|
|
152
|
+
: undefined;
|
|
153
|
+
const details = piRecord(payload?.details);
|
|
154
|
+
const diffstat = typeof details?.patch === "string" ? diffstatFromUnifiedPatch(details.patch) : undefined;
|
|
155
|
+
return {
|
|
156
|
+
status: isError ? "error" : "ok",
|
|
157
|
+
...(content ? { message: boundActivityExcerpt(content) } : {}),
|
|
158
|
+
...(diffstat ? { diffstat } : {}),
|
|
159
|
+
};
|
|
160
|
+
}
|
|
161
|
+
function piActivityBody(event, calls) {
|
|
162
|
+
if (event.type !== "tool_execution_start" &&
|
|
163
|
+
event.type !== "tool_execution_update" &&
|
|
164
|
+
event.type !== "tool_execution_end") {
|
|
165
|
+
return undefined;
|
|
166
|
+
}
|
|
167
|
+
if (typeof event.toolCallId !== "string" || typeof event.toolName !== "string")
|
|
168
|
+
return undefined;
|
|
169
|
+
const phase = event.type === "tool_execution_start"
|
|
170
|
+
? "started"
|
|
171
|
+
: event.type === "tool_execution_update"
|
|
172
|
+
? "updated"
|
|
173
|
+
: "completed";
|
|
174
|
+
if (event.type === "tool_execution_start") {
|
|
175
|
+
calls.set(event.toolCallId, activityCallFromNamedTool(event.toolName, piCallFacts(event.args)));
|
|
176
|
+
}
|
|
177
|
+
const call = calls.get(event.toolCallId) ?? (event.type === "tool_execution_end"
|
|
178
|
+
? { kind: "other", display: event.toolName }
|
|
179
|
+
: activityCallFromNamedTool(event.toolName, piCallFacts(event.args)));
|
|
180
|
+
if (event.type === "tool_execution_end")
|
|
181
|
+
calls.delete(event.toolCallId);
|
|
182
|
+
return {
|
|
183
|
+
type: "activity",
|
|
184
|
+
phase,
|
|
185
|
+
id: event.toolCallId,
|
|
186
|
+
tool: event.toolName,
|
|
187
|
+
call,
|
|
188
|
+
...(event.type === "tool_execution_start"
|
|
189
|
+
? {}
|
|
190
|
+
: { result: piResult(event.type === "tool_execution_update" ? event.partialResult : event.result, event.type === "tool_execution_end" && event.isError) }),
|
|
191
|
+
};
|
|
192
|
+
}
|
|
193
|
+
function piUsageBody(event) {
|
|
194
|
+
if (event.type !== "message_end")
|
|
195
|
+
return undefined;
|
|
196
|
+
const message = event.message;
|
|
197
|
+
if (message?.role !== "assistant")
|
|
198
|
+
return undefined;
|
|
199
|
+
const usage = message.usage;
|
|
200
|
+
if (!usage || typeof usage.input !== "number" || typeof usage.output !== "number")
|
|
201
|
+
return undefined;
|
|
202
|
+
return {
|
|
203
|
+
type: "usage",
|
|
204
|
+
inputTokens: usage.input,
|
|
205
|
+
outputTokens: usage.output,
|
|
206
|
+
...(typeof usage.cacheRead === "number" ? { cachedInputTokens: usage.cacheRead } : {}),
|
|
207
|
+
...(typeof usage.cost?.total === "number" ? { costUsd: usage.cost.total } : {}),
|
|
208
|
+
...(typeof message.responseModel === "string"
|
|
209
|
+
? { model: message.responseModel }
|
|
210
|
+
: typeof message.model === "string"
|
|
211
|
+
? { model: message.model }
|
|
212
|
+
: {}),
|
|
213
|
+
};
|
|
214
|
+
}
|
|
71
215
|
/** True when native messages prove the run stopped via abort (stopReason aborted). */
|
|
72
216
|
function agentEndLooksAborted(event) {
|
|
73
|
-
if (event.
|
|
74
|
-
return true;
|
|
75
|
-
if (event.stopReason === "aborted")
|
|
76
|
-
return true;
|
|
77
|
-
const messages = event.messages;
|
|
78
|
-
if (!Array.isArray(messages))
|
|
217
|
+
if (event.type !== "agent_end")
|
|
79
218
|
return false;
|
|
219
|
+
const messages = event.messages;
|
|
80
220
|
for (let i = messages.length - 1; i >= 0; i -= 1) {
|
|
81
221
|
const message = messages[i];
|
|
82
222
|
if (!message || message.role !== "assistant")
|
|
@@ -88,6 +228,24 @@ function agentEndLooksAborted(event) {
|
|
|
88
228
|
}
|
|
89
229
|
return false;
|
|
90
230
|
}
|
|
231
|
+
/** Returns the terminal Pi provider error, if the final assistant message ended in error. */
|
|
232
|
+
function agentEndProviderError(event) {
|
|
233
|
+
if (event.type !== "agent_end")
|
|
234
|
+
return undefined;
|
|
235
|
+
const messages = event.messages;
|
|
236
|
+
for (let i = messages.length - 1; i >= 0; i -= 1) {
|
|
237
|
+
const message = messages[i];
|
|
238
|
+
if (!message || message.role !== "assistant")
|
|
239
|
+
continue;
|
|
240
|
+
if (message.stopReason !== "error")
|
|
241
|
+
return undefined;
|
|
242
|
+
if (typeof message.errorMessage === "string" && message.errorMessage.trim()) {
|
|
243
|
+
return message.errorMessage;
|
|
244
|
+
}
|
|
245
|
+
return `${PI_PROVIDER} provider error (stopReason error)`;
|
|
246
|
+
}
|
|
247
|
+
return undefined;
|
|
248
|
+
}
|
|
91
249
|
function normalizeClearQueue(result) {
|
|
92
250
|
if (Array.isArray(result))
|
|
93
251
|
return result;
|
|
@@ -112,39 +270,83 @@ function buildPiSessionOutcome(session, finalMessage) {
|
|
|
112
270
|
}
|
|
113
271
|
function bindPiSessionEvents(session, builder, options, steerControl, state, setTurnOrdinal, settle) {
|
|
114
272
|
return session.subscribe((event) => {
|
|
115
|
-
builder.
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
state.lastAssistantMessage = assistantText;
|
|
119
|
-
builder.emit({ type: "message", text: assistantText });
|
|
120
|
-
}
|
|
121
|
-
if (event.type === "turn_end" && !state.abortIntent) {
|
|
122
|
-
state.turnOrdinal += 1;
|
|
123
|
-
setTurnOrdinal(state.turnOrdinal);
|
|
124
|
-
const checkpoint = { fence: { kind: "ordinal", ordinal: state.turnOrdinal } };
|
|
125
|
-
// Evidence identifies the native turn_end before the synchronous pump can
|
|
126
|
-
// consume tells at this checkpoint.
|
|
127
|
-
if (options.onEvidence) {
|
|
128
|
-
emitProviderEvidence(options.onEvidence, { type: "checkpoint", provider: PI_PROVIDER, ...checkpoint, source: "notification" }, (text) => builder.emit({ type: "diagnostic", text }));
|
|
273
|
+
builder.ingress(PI_PROVIDER, event.type, event, piIngressDisposition(event.type), () => {
|
|
274
|
+
if (event.type === "agent_start") {
|
|
275
|
+
builder.emit({ type: "turn", phase: "started" });
|
|
129
276
|
}
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
277
|
+
if (event.type === "turn_start") {
|
|
278
|
+
state.thoughtTurnOrdinal += 1;
|
|
279
|
+
state.assistantMessageOrdinal = -1;
|
|
280
|
+
state.activeAssistantMessageOrdinal = undefined;
|
|
281
|
+
}
|
|
282
|
+
if (event.type === "message_start") {
|
|
283
|
+
const message = event.message;
|
|
284
|
+
if (message?.role === "assistant") {
|
|
285
|
+
state.assistantMessageOrdinal += 1;
|
|
286
|
+
state.activeAssistantMessageOrdinal = state.assistantMessageOrdinal;
|
|
287
|
+
}
|
|
288
|
+
}
|
|
289
|
+
const activity = piActivityBody(event, state.activityCalls);
|
|
290
|
+
if (activity)
|
|
291
|
+
builder.emit(activity);
|
|
292
|
+
if (event.type === "message_end") {
|
|
293
|
+
const message = event.message;
|
|
294
|
+
if (message?.role === "assistant") {
|
|
295
|
+
if (state.activeAssistantMessageOrdinal === undefined) {
|
|
296
|
+
state.assistantMessageOrdinal += 1;
|
|
297
|
+
state.activeAssistantMessageOrdinal = state.assistantMessageOrdinal;
|
|
298
|
+
}
|
|
299
|
+
emitPiThoughts(builder, event, state.thoughtTurnOrdinal, state.activeAssistantMessageOrdinal);
|
|
300
|
+
state.activeAssistantMessageOrdinal = undefined;
|
|
301
|
+
}
|
|
302
|
+
}
|
|
303
|
+
const assistantText = extractAssistantText(event);
|
|
304
|
+
if (assistantText) {
|
|
305
|
+
state.lastAssistantMessage = assistantText;
|
|
306
|
+
builder.emit({ type: "message", text: assistantText });
|
|
307
|
+
}
|
|
308
|
+
const usage = piUsageBody(event);
|
|
309
|
+
if (usage)
|
|
310
|
+
builder.emit(usage);
|
|
311
|
+
if (event.type === "turn_end" && !state.abortIntent) {
|
|
312
|
+
state.turnOrdinal += 1;
|
|
313
|
+
setTurnOrdinal(state.turnOrdinal);
|
|
314
|
+
const checkpoint = { fence: { kind: "ordinal", ordinal: state.turnOrdinal } };
|
|
315
|
+
// Evidence identifies the native turn_end before the synchronous pump can
|
|
316
|
+
// consume tells at this checkpoint.
|
|
317
|
+
if (options.onEvidence) {
|
|
318
|
+
emitProviderEvidence(options.onEvidence, { type: "checkpoint", provider: PI_PROVIDER, ...checkpoint, source: "notification" }, (text) => builder.emit({ type: "diagnostic", text }));
|
|
319
|
+
}
|
|
320
|
+
// Sync checkpoint window: claim/steer must run before returning from turn_end.
|
|
321
|
+
options.onSyncCheckpoint?.(steerControl, checkpoint);
|
|
322
|
+
}
|
|
323
|
+
if (event.type !== "agent_end" || state.settled) {
|
|
324
|
+
return;
|
|
325
|
+
}
|
|
326
|
+
if (state.abortIntent || agentEndLooksAborted(event)) {
|
|
327
|
+
settle.reject(createAbortError(`${PI_PROVIDER} exec cancelled by client`));
|
|
328
|
+
return;
|
|
329
|
+
}
|
|
330
|
+
if (event.willRetry) {
|
|
331
|
+
return;
|
|
332
|
+
}
|
|
333
|
+
const providerError = agentEndProviderError(event);
|
|
334
|
+
if (providerError) {
|
|
335
|
+
const failure = new SubagentFailure("SUBAGENT_EXEC_ERROR", providerError);
|
|
336
|
+
builder.emit({ type: "fail", message: failure.message });
|
|
337
|
+
settle.reject(failure);
|
|
338
|
+
return;
|
|
339
|
+
}
|
|
340
|
+
const outcome = buildPiSessionOutcome(session, state.lastAssistantMessage);
|
|
341
|
+
if (outcome instanceof SubagentFailure) {
|
|
342
|
+
builder.emit({ type: "fail", message: outcome.message });
|
|
343
|
+
settle.reject(outcome);
|
|
344
|
+
return;
|
|
345
|
+
}
|
|
346
|
+
builder.emit({ type: "turn", phase: "completed" });
|
|
347
|
+
builder.emit({ type: "done", finalMessage: outcome.finalMessage, session: outcome.session });
|
|
348
|
+
settle.resolve(outcome);
|
|
349
|
+
});
|
|
148
350
|
});
|
|
149
351
|
}
|
|
150
352
|
export async function startPi(prompt, cwd, options = {}) {
|
|
@@ -201,7 +403,7 @@ export async function startPi(prompt, cwd, options = {}) {
|
|
|
201
403
|
const created = await sdk.createAgentSession({
|
|
202
404
|
cwd,
|
|
203
405
|
sessionManager,
|
|
204
|
-
...(options.thinkingLevel ? { thinkingLevel: options.thinkingLevel } : {}),
|
|
406
|
+
...(options.thinkingLevel ? { thinkingLevel: toPiSdkThinkingLevel(options.thinkingLevel) } : {}),
|
|
205
407
|
...(model ? { model, authStorage, modelRegistry } : {}),
|
|
206
408
|
...(resourceLoader ? { resourceLoader } : {}),
|
|
207
409
|
});
|
|
@@ -210,6 +412,7 @@ export async function startPi(prompt, cwd, options = {}) {
|
|
|
210
412
|
emitProviderEvidence(options.onEvidence, { type: "session", provider: PI_PROVIDER, sessionId: session.sessionId, ...(session.sessionFile ? { sessionFile: session.sessionFile } : {}) }, reportEvidenceDiagnostic);
|
|
211
413
|
session.setSteeringMode("one-at-a-time");
|
|
212
414
|
let submittedAtTurn = 0;
|
|
415
|
+
let emitTurnStarted;
|
|
213
416
|
const enqueue = async (tell) => {
|
|
214
417
|
// Acceptance into volatile queue only. text is core-rendered; send byte-for-byte.
|
|
215
418
|
if (options.onEvidence)
|
|
@@ -217,9 +420,10 @@ export async function startPi(prompt, cwd, options = {}) {
|
|
|
217
420
|
if (tell.effort) {
|
|
218
421
|
if (!session.setThinkingLevel)
|
|
219
422
|
throw new Error(`${PI_PROVIDER} session does not support thinking level control`);
|
|
220
|
-
session.setThinkingLevel(tell.effort
|
|
423
|
+
session.setThinkingLevel(toPiSdkThinkingLevel(tell.effort));
|
|
221
424
|
}
|
|
222
425
|
await session.steer(tell.text);
|
|
426
|
+
emitTurnStarted?.();
|
|
223
427
|
if (options.onEvidence)
|
|
224
428
|
emitProviderEvidence(options.onEvidence, { type: "ack", provider: PI_PROVIDER, fence: { kind: "ordinal", ordinal: submittedAtTurn }, outbound: "steer" }, reportEvidenceDiagnostic);
|
|
225
429
|
return { fence: { kind: "ordinal", ordinal: submittedAtTurn } };
|
|
@@ -247,6 +451,8 @@ export async function startPi(prompt, cwd, options = {}) {
|
|
|
247
451
|
return;
|
|
248
452
|
}
|
|
249
453
|
abortRequested = (async () => {
|
|
454
|
+
// Publish abortRequested before clearQueue can synchronously emit queue_update.
|
|
455
|
+
await Promise.resolve();
|
|
250
456
|
try {
|
|
251
457
|
session.clearQueue();
|
|
252
458
|
await session.abort();
|
|
@@ -261,12 +467,15 @@ export async function startPi(prompt, cwd, options = {}) {
|
|
|
261
467
|
};
|
|
262
468
|
return createProjectionDriver({
|
|
263
469
|
steerMode: "mid-execution",
|
|
470
|
+
initialTurn: options.initialTurn,
|
|
264
471
|
enqueue,
|
|
472
|
+
onIngress: options.onIngress,
|
|
265
473
|
async abort() {
|
|
266
474
|
await requestAbort({ abortIntent: true, settled: false });
|
|
267
475
|
},
|
|
268
476
|
async run(builder) {
|
|
269
477
|
reportEvidenceDiagnostic = () => builder.emit({ type: "diagnostic", text: "provider evidence callback failed" });
|
|
478
|
+
emitTurnStarted = () => builder.emit({ type: "turn", phase: "started" });
|
|
270
479
|
while (pendingEvidenceDiagnostics > 0) {
|
|
271
480
|
pendingEvidenceDiagnostics -= 1;
|
|
272
481
|
reportEvidenceDiagnostic();
|
|
@@ -276,6 +485,9 @@ export async function startPi(prompt, cwd, options = {}) {
|
|
|
276
485
|
lastAssistantMessage: "",
|
|
277
486
|
settled: false,
|
|
278
487
|
turnOrdinal: 0,
|
|
488
|
+
thoughtTurnOrdinal: 0,
|
|
489
|
+
assistantMessageOrdinal: -1,
|
|
490
|
+
activityCalls: new Map(),
|
|
279
491
|
};
|
|
280
492
|
let settleOutcome;
|
|
281
493
|
const outcome = new Promise((resolve, reject) => {
|
|
@@ -0,0 +1,122 @@
|
|
|
1
|
+
import { getConfig } from "../config/env.js";
|
|
2
|
+
import { loadAkumaCatalogV2, } from "../config/akuma-loader_v2.js";
|
|
3
|
+
import { assertSettingsKnobUsable, loadKeiyakuSettings, resolveDefaultAgentName, } from "../config/settings.js";
|
|
4
|
+
import { FlowError } from "../flow-error.js";
|
|
5
|
+
const ACCESS_VALUES = ["read", "write", "auto"];
|
|
6
|
+
const NETWORK_VALUES = ["disabled", "enabled"];
|
|
7
|
+
const SUPPORTED_EXPLICIT = {
|
|
8
|
+
"codex-sdk": {
|
|
9
|
+
access: new Set(ACCESS_VALUES),
|
|
10
|
+
network: new Set(NETWORK_VALUES),
|
|
11
|
+
webSearch: new Set(["disabled"]),
|
|
12
|
+
},
|
|
13
|
+
"codex-app-server": {
|
|
14
|
+
access: new Set(["write"]),
|
|
15
|
+
network: new Set(NETWORK_VALUES),
|
|
16
|
+
webSearch: new Set(),
|
|
17
|
+
},
|
|
18
|
+
"claude-agent-sdk": {
|
|
19
|
+
access: new Set(ACCESS_VALUES),
|
|
20
|
+
network: new Set(),
|
|
21
|
+
webSearch: new Set(["disabled"]),
|
|
22
|
+
},
|
|
23
|
+
"opencode-sdk": {
|
|
24
|
+
access: new Set(),
|
|
25
|
+
network: new Set(),
|
|
26
|
+
webSearch: new Set(),
|
|
27
|
+
},
|
|
28
|
+
pi: {
|
|
29
|
+
access: new Set(),
|
|
30
|
+
network: new Set(),
|
|
31
|
+
webSearch: new Set(),
|
|
32
|
+
},
|
|
33
|
+
};
|
|
34
|
+
export function formatAvailableAgentNamesV2(names) {
|
|
35
|
+
if (names.length === 0)
|
|
36
|
+
return "(none configured)";
|
|
37
|
+
return [...names].sort().join(", ");
|
|
38
|
+
}
|
|
39
|
+
/**
|
|
40
|
+
* Reject the first present portable-policy axis that the adapter kind does not
|
|
41
|
+
* support. Omitted axes are always legal and are never filled with defaults.
|
|
42
|
+
*/
|
|
43
|
+
export function assertPortablePolicySupported(adapter, policy) {
|
|
44
|
+
const supported = SUPPORTED_EXPLICIT[adapter];
|
|
45
|
+
const checks = [
|
|
46
|
+
["access", policy.access],
|
|
47
|
+
["network", policy.network],
|
|
48
|
+
["webSearch", policy.webSearch],
|
|
49
|
+
];
|
|
50
|
+
for (const [knob, value] of checks) {
|
|
51
|
+
if (value === undefined)
|
|
52
|
+
continue;
|
|
53
|
+
const allowed = supported[knob];
|
|
54
|
+
if (!allowed.has(value)) {
|
|
55
|
+
throw new FlowError("UNSUPPORTED_POLICY", `Provider kind '${adapter}' does not support ${knob} '${value}'.`);
|
|
56
|
+
}
|
|
57
|
+
}
|
|
58
|
+
}
|
|
59
|
+
function isValidAkumaCandidate(candidate) {
|
|
60
|
+
return !("reason" in candidate && candidate.reason !== undefined) && candidate.profile !== undefined && candidate.body !== undefined;
|
|
61
|
+
}
|
|
62
|
+
function freezeResolvedProfile(profile) {
|
|
63
|
+
if (profile.effortOptions === undefined) {
|
|
64
|
+
return Object.freeze({ ...profile });
|
|
65
|
+
}
|
|
66
|
+
return Object.freeze({
|
|
67
|
+
...profile,
|
|
68
|
+
effortOptions: Object.freeze([...profile.effortOptions]),
|
|
69
|
+
});
|
|
70
|
+
}
|
|
71
|
+
export async function selectSubagentV2(agentName, cwd) {
|
|
72
|
+
const loaded = await loadKeiyakuSettings(cwd);
|
|
73
|
+
assertSettingsKnobUsable(loaded, "agents");
|
|
74
|
+
const settings = loaded.knobs;
|
|
75
|
+
const requested = agentName?.trim();
|
|
76
|
+
if (!requested && !getConfig().subagent.nameOverride) {
|
|
77
|
+
assertSettingsKnobUsable(loaded, "default");
|
|
78
|
+
}
|
|
79
|
+
const selected = requested || getConfig().subagent.nameOverride || resolveDefaultAgentName(settings);
|
|
80
|
+
const catalog = await loadAkumaCatalogV2(cwd);
|
|
81
|
+
const availableNames = Object.keys(catalog.selected);
|
|
82
|
+
const candidate = catalog.selected[selected];
|
|
83
|
+
if (candidate === undefined) {
|
|
84
|
+
throw new FlowError("UNKNOWN_SUBAGENT", `Unknown agent '${selected}'. Expected one of: ${formatAvailableAgentNamesV2(availableNames)}`);
|
|
85
|
+
}
|
|
86
|
+
if (!isValidAkumaCandidate(candidate)) {
|
|
87
|
+
throw new FlowError("INVALID_SETTINGS", `Akuma '${selected}' is invalid: ${candidate.reason}`);
|
|
88
|
+
}
|
|
89
|
+
const instanceName = candidate.profile.provider;
|
|
90
|
+
const providerCandidate = loaded.providerInstances.selected[instanceName];
|
|
91
|
+
if (providerCandidate === undefined) {
|
|
92
|
+
throw new FlowError("INVALID_SETTINGS", `Akuma '${selected}' references missing provider instance '${instanceName}'.`);
|
|
93
|
+
}
|
|
94
|
+
if (!("instance" in providerCandidate) || providerCandidate.instance === undefined) {
|
|
95
|
+
throw new FlowError("INVALID_SETTINGS", `Akuma '${selected}' references invalid provider instance '${instanceName}': ${providerCandidate.reason}`);
|
|
96
|
+
}
|
|
97
|
+
const providerInstance = providerCandidate.instance;
|
|
98
|
+
const adapter = providerInstance.kind;
|
|
99
|
+
assertPortablePolicySupported(adapter, {
|
|
100
|
+
...(candidate.profile.access !== undefined ? { access: candidate.profile.access } : {}),
|
|
101
|
+
...(candidate.profile.network !== undefined ? { network: candidate.profile.network } : {}),
|
|
102
|
+
...(candidate.profile.webSearch !== undefined ? { webSearch: candidate.profile.webSearch } : {}),
|
|
103
|
+
});
|
|
104
|
+
const profile = freezeResolvedProfile({
|
|
105
|
+
provider: instanceName,
|
|
106
|
+
adapter,
|
|
107
|
+
providerSource: providerCandidate.source,
|
|
108
|
+
providerInstance,
|
|
109
|
+
...(candidate.profile.description !== undefined ? { description: candidate.profile.description } : {}),
|
|
110
|
+
...(candidate.profile.model !== undefined ? { model: candidate.profile.model } : {}),
|
|
111
|
+
...(candidate.profile.effort !== undefined ? { effort: candidate.profile.effort } : {}),
|
|
112
|
+
...(candidate.profile.effortOptions !== undefined ? { effortOptions: candidate.profile.effortOptions } : {}),
|
|
113
|
+
...(candidate.profile.access !== undefined ? { access: candidate.profile.access } : {}),
|
|
114
|
+
...(candidate.profile.network !== undefined ? { network: candidate.profile.network } : {}),
|
|
115
|
+
...(candidate.profile.webSearch !== undefined ? { webSearch: candidate.profile.webSearch } : {}),
|
|
116
|
+
});
|
|
117
|
+
return Object.freeze({
|
|
118
|
+
name: selected,
|
|
119
|
+
definition: candidate,
|
|
120
|
+
profile,
|
|
121
|
+
});
|
|
122
|
+
}
|