@astrosheep/keiyaku 0.1.87 → 1.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +12 -22
- package/build/.tsbuildinfo +1 -1
- package/build/agents/effective-policy.js +32 -0
- package/build/agents/harness/event-channel.js +178 -0
- package/build/agents/harness/execution-handle.js +117 -0
- package/build/agents/harness/index.js +622 -0
- package/build/agents/harness/pump.js +152 -0
- package/build/agents/harness/runtime.js +68 -0
- package/build/agents/index.js +104 -213
- package/build/agents/opencode-sdk.js +12 -18
- package/build/agents/providers/claude-agent-sdk.js +529 -0
- package/build/agents/providers/codex-app-server.js +656 -0
- package/build/agents/providers/codex-sdk.js +193 -0
- package/build/agents/providers/opencode-sdk.js +257 -0
- package/build/agents/providers/pi.js +345 -0
- package/build/agents/selector.js +15 -6
- package/build/agents/session.js +29 -0
- package/build/agents/types.js +5 -11
- package/build/cli/commands/akuma.js +79 -0
- package/build/cli/completion.js +71 -0
- package/build/cli/draft-artifact.js +50 -0
- package/build/cli/flags.js +96 -0
- package/build/cli/help.js +108 -0
- package/build/cli/index.js +204 -912
- package/build/cli/parse.js +280 -0
- package/build/cli/render/arc.js +63 -0
- package/build/{responses/tool-errors.js → cli/render/errors.js} +15 -16
- package/build/cli/render/format.js +199 -0
- package/build/cli/render/misc.js +40 -0
- package/build/cli/render/petition.js +32 -0
- package/build/{responses → cli/render}/response-style.js +4 -4
- package/build/cli/render/shared.js +122 -0
- package/build/cli/render/status.js +69 -0
- package/build/cli/render/summon.js +17 -0
- package/build/cli/render/tell.js +11 -0
- package/build/cli/skills-install.js +237 -0
- package/build/cli/subagent-guard.js +6 -3
- package/build/config/akuma-loader.js +268 -0
- package/build/config/env-keys.js +0 -10
- package/build/config/{schema.js → env.js} +117 -23
- package/build/config/settings.js +137 -260
- package/build/core/amend.js +120 -0
- package/build/core/arc.js +225 -0
- package/build/core/atomic-publish.js +64 -0
- package/build/core/bind.js +281 -0
- package/build/core/claim.js +360 -0
- package/build/core/command-io.js +174 -0
- package/build/core/context.js +108 -0
- package/build/core/contract.js +14 -0
- package/build/core/draft.js +82 -0
- package/build/core/entry.js +203 -0
- package/build/core/forfeit.js +60 -0
- package/build/core/hints.js +108 -0
- package/build/core/ids.js +70 -0
- package/build/core/ledger.js +117 -0
- package/build/core/log.js +24 -0
- package/build/{protocol → core}/markdown/parser.js +68 -0
- package/build/{protocol → core}/markdown/sections.js +2 -28
- package/build/core/markdown/titles.js +9 -0
- package/build/{tools/petition/claim-gates.js → core/petition-claim-gates.js} +43 -29
- package/build/core/petition-claim.js +116 -0
- package/build/core/petition-forfeit.js +39 -0
- package/build/{tools/round/head-guard.js → core/petition-head-guard.js} +2 -2
- package/build/core/petition-run.js +81 -0
- package/build/core/petition.js +96 -0
- package/build/core/places.js +82 -0
- package/build/core/projection-core.js +541 -0
- package/build/core/projection-mint.js +79 -0
- package/build/core/projection-status.js +179 -0
- package/build/core/projection-wake.js +252 -0
- package/build/core/queue.js +73 -0
- package/build/core/ref-log.js +94 -0
- package/build/core/registry.js +96 -0
- package/build/core/render.js +326 -0
- package/build/core/renew.js +195 -0
- package/build/core/scope.js +163 -0
- package/build/core/seal.js +195 -0
- package/build/core/status.js +476 -0
- package/build/{tools/summon/persist.js → core/summon-persist.js} +10 -9
- package/build/core/summon.js +377 -0
- package/build/{protocol/response-history.js → core/transcripts.js} +83 -39
- package/build/core/verdict.js +96 -0
- package/build/core/worktree-path.js +134 -0
- package/build/flow-error.js +80 -4
- package/build/generated/version.js +1 -1
- package/build/git/branches.js +8 -9
- package/build/git/core.js +1 -1
- package/build/git/diff/pathspec.js +2 -3
- package/build/git/diff/preview.js +7 -2
- package/build/git/diff/read.js +4 -3
- package/build/git/staging.js +1 -1
- package/build/index.js +12 -6
- package/build/keiyaku.js +17 -13
- package/build/telemetry/debug-log.js +1 -1
- package/package.json +16 -9
- package/skills/keiyaku/SKILL.md +16 -13
- package/build/agents/claude-agent-sdk-exec.js +0 -352
- package/build/agents/codex-cli-exec.js +0 -237
- package/build/agents/codex-sdk-exec.js +0 -291
- package/build/agents/gemini-cli-core-exec.js +0 -499
- package/build/agents/opencode-exec.js +0 -297
- package/build/agents/process-runner.js +0 -239
- package/build/agents/progress-reporter.js +0 -137
- package/build/agents/round-runner.js +0 -53
- package/build/config/apply-argument-descriptions.js +0 -16
- package/build/config/architect-hints.js +0 -374
- package/build/config/base-rules.js +0 -23
- package/build/config/config.js +0 -50
- package/build/config/dotenv.js +0 -61
- package/build/config/keiyaku-home.js +0 -9
- package/build/config/path-utils.js +0 -11
- package/build/config/render-template.js +0 -8
- package/build/git/diff/filter.js +0 -67
- package/build/git/keiyaku-state.js +0 -58
- package/build/git/snapshot.js +0 -41
- package/build/git/stash.js +0 -62
- package/build/protocol/bind-draft.js +0 -195
- package/build/protocol/draft-artifacts.js +0 -99
- package/build/protocol/file-guards.js +0 -24
- package/build/protocol/keiyaku-draft.js +0 -168
- package/build/protocol/ledger.js +0 -374
- package/build/protocol/markdown/index.js +0 -14
- package/build/protocol/markdown/normalization.js +0 -71
- package/build/protocol/markdown/render.js +0 -68
- package/build/protocol/oath.js +0 -36
- package/build/protocol/render-keiyaku.js +0 -37
- package/build/protocol/review.js +0 -1
- package/build/protocol/status-previews.js +0 -68
- package/build/responses/responses.js +0 -515
- package/build/tools/amend/index.js +0 -18
- package/build/tools/amend/run.js +0 -44
- package/build/tools/bind/index.js +0 -44
- package/build/tools/bind/run.js +0 -237
- package/build/tools/contracts.js +0 -1
- package/build/tools/create-handler.js +0 -31
- package/build/tools/help.js +0 -9
- package/build/tools/petition/claim.js +0 -174
- package/build/tools/petition/forfeit.js +0 -100
- package/build/tools/petition/index.js +0 -83
- package/build/tools/petition/run.js +0 -66
- package/build/tools/round/guidance.js +0 -7
- package/build/tools/round/prompt.js +0 -70
- package/build/tools/round/report.js +0 -153
- package/build/tools/round/worktree.js +0 -55
- package/build/tools/round-close/index.js +0 -19
- package/build/tools/round-close/run.js +0 -98
- package/build/tools/round-open/index.js +0 -20
- package/build/tools/round-open/run.js +0 -77
- package/build/tools/schema.js +0 -69
- package/build/tools/status/index.js +0 -16
- package/build/tools/status/read.js +0 -155
- package/build/tools/summon/index.js +0 -27
- package/build/tools/summon/run.js +0 -80
- /package/build/{config/toml.js → agents/harness/events.js} +0 -0
- /package/build/{git/diff/contracts.js → cli/render/types.js} +0 -0
- /package/build/{protocol/bind-input.js → cli/types.js} +0 -0
- /package/build/{protocol → core}/markdown/lex.js +0 -0
- /package/build/{protocol → core}/markdown/types.js +0 -0
|
@@ -0,0 +1,345 @@
|
|
|
1
|
+
import { z } from "zod";
|
|
2
|
+
import { FlowError } from "../../flow-error.js";
|
|
3
|
+
import { appendDebugLog } from "../../telemetry/debug-log.js";
|
|
4
|
+
import { SUBAGENT_SESSION_VERSION } from "../session.js";
|
|
5
|
+
import { createProjectionDriver } from "../harness/execution-handle.js";
|
|
6
|
+
import { createAbortError, emitProviderEvidence, sha256Utf8 } from "../harness/runtime.js";
|
|
7
|
+
import { SubagentFailure, } from "../types.js";
|
|
8
|
+
/** Opaque Pi resume handle: exact session file path (UUID alone is ambiguous). */
|
|
9
|
+
export const piSessionSchema = z.object({
|
|
10
|
+
sessionFile: z.string().trim().min(1),
|
|
11
|
+
sessionId: z.string().trim().min(1).optional(),
|
|
12
|
+
});
|
|
13
|
+
const PI_PROVIDER = "pi";
|
|
14
|
+
/** Exact model shape: `<provider>/<id>` (single slash separating non-empty sides). */
|
|
15
|
+
const PI_MODEL_PATTERN = /^[^/]+\/[^/]+$/;
|
|
16
|
+
let piSdkLoader;
|
|
17
|
+
const importPiSdk = new Function("specifier", "return import(specifier)");
|
|
18
|
+
async function loadPiSdk() {
|
|
19
|
+
if (piSdkLoader) {
|
|
20
|
+
return piSdkLoader();
|
|
21
|
+
}
|
|
22
|
+
return (await importPiSdk("@earendil-works/pi-coding-agent"));
|
|
23
|
+
}
|
|
24
|
+
export function setPiSdkLoaderForTests(loader) {
|
|
25
|
+
piSdkLoader = loader;
|
|
26
|
+
}
|
|
27
|
+
export function resetPiSdkLoaderForTests() {
|
|
28
|
+
piSdkLoader = undefined;
|
|
29
|
+
}
|
|
30
|
+
export function parsePiModelRef(model) {
|
|
31
|
+
const trimmed = model.trim();
|
|
32
|
+
if (!PI_MODEL_PATTERN.test(trimmed)) {
|
|
33
|
+
throw new FlowError("INVALID_SETTINGS", `Invalid pi model '${model}'. Expected exact shape '<provider>/<id>'.`);
|
|
34
|
+
}
|
|
35
|
+
const slash = trimmed.indexOf("/");
|
|
36
|
+
return {
|
|
37
|
+
provider: trimmed.slice(0, slash),
|
|
38
|
+
id: trimmed.slice(slash + 1),
|
|
39
|
+
};
|
|
40
|
+
}
|
|
41
|
+
function extractAssistantText(event) {
|
|
42
|
+
if (event.type === "message_update" || event.type === "message_end") {
|
|
43
|
+
const message = event.message;
|
|
44
|
+
if (message?.role !== "assistant")
|
|
45
|
+
return undefined;
|
|
46
|
+
return extractContentText(message.content);
|
|
47
|
+
}
|
|
48
|
+
return undefined;
|
|
49
|
+
}
|
|
50
|
+
function extractContentText(content) {
|
|
51
|
+
if (typeof content === "string") {
|
|
52
|
+
const trimmed = content.trim();
|
|
53
|
+
return trimmed || undefined;
|
|
54
|
+
}
|
|
55
|
+
if (!Array.isArray(content))
|
|
56
|
+
return undefined;
|
|
57
|
+
const text = content
|
|
58
|
+
.map((block) => {
|
|
59
|
+
if (typeof block === "string")
|
|
60
|
+
return block;
|
|
61
|
+
if (block && typeof block === "object" && "text" in block) {
|
|
62
|
+
const candidate = block.text;
|
|
63
|
+
return typeof candidate === "string" ? candidate : "";
|
|
64
|
+
}
|
|
65
|
+
return "";
|
|
66
|
+
})
|
|
67
|
+
.join("")
|
|
68
|
+
.trim();
|
|
69
|
+
return text || undefined;
|
|
70
|
+
}
|
|
71
|
+
/** True when native messages prove the run stopped via abort (stopReason aborted). */
|
|
72
|
+
function agentEndLooksAborted(event) {
|
|
73
|
+
if (event.aborted === true)
|
|
74
|
+
return true;
|
|
75
|
+
if (event.stopReason === "aborted")
|
|
76
|
+
return true;
|
|
77
|
+
const messages = event.messages;
|
|
78
|
+
if (!Array.isArray(messages))
|
|
79
|
+
return false;
|
|
80
|
+
for (let i = messages.length - 1; i >= 0; i -= 1) {
|
|
81
|
+
const message = messages[i];
|
|
82
|
+
if (!message || message.role !== "assistant")
|
|
83
|
+
continue;
|
|
84
|
+
if (message.stopReason === "aborted")
|
|
85
|
+
return true;
|
|
86
|
+
// Last assistant message is the terminal one for this run.
|
|
87
|
+
break;
|
|
88
|
+
}
|
|
89
|
+
return false;
|
|
90
|
+
}
|
|
91
|
+
function normalizeClearQueue(result) {
|
|
92
|
+
if (Array.isArray(result))
|
|
93
|
+
return result;
|
|
94
|
+
return [...(result.steering ?? []), ...(result.followUp ?? [])];
|
|
95
|
+
}
|
|
96
|
+
function buildPiSessionOutcome(session, finalMessage) {
|
|
97
|
+
const sessionFile = session.sessionFile;
|
|
98
|
+
if (!sessionFile) {
|
|
99
|
+
return new SubagentFailure("SUBAGENT_EXEC_ERROR", `${PI_PROVIDER} exec ended without a session file`);
|
|
100
|
+
}
|
|
101
|
+
return {
|
|
102
|
+
finalMessage,
|
|
103
|
+
session: {
|
|
104
|
+
provider: "pi",
|
|
105
|
+
version: SUBAGENT_SESSION_VERSION,
|
|
106
|
+
handle: {
|
|
107
|
+
sessionFile,
|
|
108
|
+
sessionId: session.sessionId,
|
|
109
|
+
},
|
|
110
|
+
},
|
|
111
|
+
};
|
|
112
|
+
}
|
|
113
|
+
function bindPiSessionEvents(session, builder, options, steerControl, state, setTurnOrdinal, settle) {
|
|
114
|
+
return session.subscribe((event) => {
|
|
115
|
+
builder.emit({ type: "output", value: event });
|
|
116
|
+
const assistantText = extractAssistantText(event);
|
|
117
|
+
if (assistantText) {
|
|
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 }));
|
|
129
|
+
}
|
|
130
|
+
// Sync checkpoint window: claim/steer must run before returning from turn_end.
|
|
131
|
+
options.onSyncCheckpoint?.(steerControl, checkpoint);
|
|
132
|
+
}
|
|
133
|
+
if (event.type !== "agent_end" || state.settled) {
|
|
134
|
+
return;
|
|
135
|
+
}
|
|
136
|
+
if (state.abortIntent || agentEndLooksAborted(event)) {
|
|
137
|
+
settle.reject(createAbortError(`${PI_PROVIDER} exec cancelled by client`));
|
|
138
|
+
return;
|
|
139
|
+
}
|
|
140
|
+
const outcome = buildPiSessionOutcome(session, state.lastAssistantMessage);
|
|
141
|
+
if (outcome instanceof SubagentFailure) {
|
|
142
|
+
builder.emit({ type: "fail", message: outcome.message });
|
|
143
|
+
settle.reject(outcome);
|
|
144
|
+
return;
|
|
145
|
+
}
|
|
146
|
+
builder.emit({ type: "done", finalMessage: outcome.finalMessage, session: outcome.session });
|
|
147
|
+
settle.resolve(outcome);
|
|
148
|
+
});
|
|
149
|
+
}
|
|
150
|
+
export async function startPi(prompt, cwd, options = {}) {
|
|
151
|
+
const resumeSessionFile = options.sessionFile?.trim();
|
|
152
|
+
appendDebugLog(`startPi: mode=${resumeSessionFile ? "resume" : "fresh"} model=${options.model ?? "default"}`, { cwd, section: PI_PROVIDER });
|
|
153
|
+
const sdk = await loadPiSdk();
|
|
154
|
+
const sessionManagerApi = readSessionManager(sdk);
|
|
155
|
+
const sessionManager = resumeSessionFile
|
|
156
|
+
? sessionManagerApi.open(resumeSessionFile)
|
|
157
|
+
: sessionManagerApi.create(cwd);
|
|
158
|
+
let model;
|
|
159
|
+
let authStorage;
|
|
160
|
+
let modelRegistry;
|
|
161
|
+
if (options.model !== undefined && options.model.trim().length > 0) {
|
|
162
|
+
const original = options.model.trim();
|
|
163
|
+
const ref = parsePiModelRef(original);
|
|
164
|
+
authStorage = sdk.AuthStorage.create();
|
|
165
|
+
modelRegistry = sdk.ModelRegistry.create(authStorage);
|
|
166
|
+
model = modelRegistry.find(ref.provider, ref.id);
|
|
167
|
+
if (!model) {
|
|
168
|
+
throw new SubagentFailure("SUBAGENT_PROVIDER_UNAVAILABLE", `${PI_PROVIDER} model '${original}' is not available`);
|
|
169
|
+
}
|
|
170
|
+
}
|
|
171
|
+
// With an Akuma body, retain the default loader's discovery and override only its append list.
|
|
172
|
+
// The override deliberately suppresses discovered APPEND_SYSTEM.md content.
|
|
173
|
+
if (options.appendSystemPromptOverride && (!sdk.DefaultResourceLoader || !sdk.getAgentDir)) {
|
|
174
|
+
throw new Error(`${PI_PROVIDER} SDK does not expose DefaultResourceLoader and getAgentDir`);
|
|
175
|
+
}
|
|
176
|
+
let emittedAppendEvidence = false;
|
|
177
|
+
let pendingEvidenceDiagnostics = 0;
|
|
178
|
+
let reportEvidenceDiagnostic = () => {
|
|
179
|
+
pendingEvidenceDiagnostics += 1;
|
|
180
|
+
appendDebugLog("provider evidence callback failed before Pi driver start", { cwd, section: PI_PROVIDER });
|
|
181
|
+
};
|
|
182
|
+
const observedAppendOverride = options.appendSystemPromptOverride && options.onEvidence
|
|
183
|
+
? () => {
|
|
184
|
+
const body = options.appendSystemPromptOverride();
|
|
185
|
+
if (!emittedAppendEvidence) {
|
|
186
|
+
emittedAppendEvidence = true;
|
|
187
|
+
emitProviderEvidence(options.onEvidence, { type: "outbound", provider: PI_PROVIDER, direction: "input", kind: "system-prompt", ...sha256Utf8(body.join("\n")) }, reportEvidenceDiagnostic);
|
|
188
|
+
}
|
|
189
|
+
return body;
|
|
190
|
+
}
|
|
191
|
+
: options.appendSystemPromptOverride;
|
|
192
|
+
const resourceLoader = observedAppendOverride
|
|
193
|
+
? new sdk.DefaultResourceLoader({
|
|
194
|
+
cwd,
|
|
195
|
+
agentDir: sdk.getAgentDir(),
|
|
196
|
+
appendSystemPromptOverride: observedAppendOverride,
|
|
197
|
+
})
|
|
198
|
+
: undefined;
|
|
199
|
+
await resourceLoader?.reload();
|
|
200
|
+
// Empty bodies retain the SDK's default ResourceLoader path unchanged.
|
|
201
|
+
const created = await sdk.createAgentSession({
|
|
202
|
+
cwd,
|
|
203
|
+
sessionManager,
|
|
204
|
+
...(options.thinkingLevel ? { thinkingLevel: options.thinkingLevel } : {}),
|
|
205
|
+
...(model ? { model, authStorage, modelRegistry } : {}),
|
|
206
|
+
...(resourceLoader ? { resourceLoader } : {}),
|
|
207
|
+
});
|
|
208
|
+
const session = created.session;
|
|
209
|
+
if (options.onEvidence)
|
|
210
|
+
emitProviderEvidence(options.onEvidence, { type: "session", provider: PI_PROVIDER, sessionId: session.sessionId, ...(session.sessionFile ? { sessionFile: session.sessionFile } : {}) }, reportEvidenceDiagnostic);
|
|
211
|
+
session.setSteeringMode("one-at-a-time");
|
|
212
|
+
let submittedAtTurn = 0;
|
|
213
|
+
const enqueue = async (tell) => {
|
|
214
|
+
// Acceptance into volatile queue only. text is core-rendered; send byte-for-byte.
|
|
215
|
+
if (options.onEvidence)
|
|
216
|
+
emitProviderEvidence(options.onEvidence, { type: "outbound", provider: PI_PROVIDER, direction: "input", kind: "steer", ...sha256Utf8(tell.text) }, reportEvidenceDiagnostic);
|
|
217
|
+
if (tell.effort) {
|
|
218
|
+
if (!session.setThinkingLevel)
|
|
219
|
+
throw new Error(`${PI_PROVIDER} session does not support thinking level control`);
|
|
220
|
+
session.setThinkingLevel(tell.effort === "max" ? "xhigh" : tell.effort);
|
|
221
|
+
}
|
|
222
|
+
await session.steer(tell.text);
|
|
223
|
+
if (options.onEvidence)
|
|
224
|
+
emitProviderEvidence(options.onEvidence, { type: "ack", provider: PI_PROVIDER, fence: { kind: "ordinal", ordinal: submittedAtTurn }, outbound: "steer" }, reportEvidenceDiagnostic);
|
|
225
|
+
return { fence: { kind: "ordinal", ordinal: submittedAtTurn } };
|
|
226
|
+
};
|
|
227
|
+
const steerControl = { enqueue };
|
|
228
|
+
let disposed = false;
|
|
229
|
+
let unsub;
|
|
230
|
+
const disposeSession = () => {
|
|
231
|
+
if (disposed)
|
|
232
|
+
return;
|
|
233
|
+
disposed = true;
|
|
234
|
+
unsub?.();
|
|
235
|
+
try {
|
|
236
|
+
session.dispose();
|
|
237
|
+
}
|
|
238
|
+
catch {
|
|
239
|
+
// best-effort
|
|
240
|
+
}
|
|
241
|
+
};
|
|
242
|
+
let abortRequested;
|
|
243
|
+
const requestAbort = async (state) => {
|
|
244
|
+
state.abortIntent = true;
|
|
245
|
+
if (abortRequested) {
|
|
246
|
+
await abortRequested;
|
|
247
|
+
return;
|
|
248
|
+
}
|
|
249
|
+
abortRequested = (async () => {
|
|
250
|
+
try {
|
|
251
|
+
session.clearQueue();
|
|
252
|
+
await session.abort();
|
|
253
|
+
}
|
|
254
|
+
finally {
|
|
255
|
+
if (state.settled) {
|
|
256
|
+
disposeSession();
|
|
257
|
+
}
|
|
258
|
+
}
|
|
259
|
+
})();
|
|
260
|
+
await abortRequested;
|
|
261
|
+
};
|
|
262
|
+
return createProjectionDriver({
|
|
263
|
+
steerMode: "mid-execution",
|
|
264
|
+
enqueue,
|
|
265
|
+
async abort() {
|
|
266
|
+
await requestAbort({ abortIntent: true, settled: false });
|
|
267
|
+
},
|
|
268
|
+
async run(builder) {
|
|
269
|
+
reportEvidenceDiagnostic = () => builder.emit({ type: "diagnostic", text: "provider evidence callback failed" });
|
|
270
|
+
while (pendingEvidenceDiagnostics > 0) {
|
|
271
|
+
pendingEvidenceDiagnostics -= 1;
|
|
272
|
+
reportEvidenceDiagnostic();
|
|
273
|
+
}
|
|
274
|
+
const state = {
|
|
275
|
+
abortIntent: false,
|
|
276
|
+
lastAssistantMessage: "",
|
|
277
|
+
settled: false,
|
|
278
|
+
turnOrdinal: 0,
|
|
279
|
+
};
|
|
280
|
+
let settleOutcome;
|
|
281
|
+
const outcome = new Promise((resolve, reject) => {
|
|
282
|
+
settleOutcome = {
|
|
283
|
+
resolve(value) {
|
|
284
|
+
if (state.settled)
|
|
285
|
+
return;
|
|
286
|
+
state.settled = true;
|
|
287
|
+
resolve(value);
|
|
288
|
+
},
|
|
289
|
+
reject(error) {
|
|
290
|
+
if (state.settled)
|
|
291
|
+
return;
|
|
292
|
+
state.settled = true;
|
|
293
|
+
reject(error);
|
|
294
|
+
},
|
|
295
|
+
};
|
|
296
|
+
unsub = bindPiSessionEvents(session, builder, options, steerControl, state, (ordinal) => { submittedAtTurn = ordinal; }, settleOutcome);
|
|
297
|
+
});
|
|
298
|
+
outcome.catch(() => undefined);
|
|
299
|
+
const onAbort = () => {
|
|
300
|
+
void requestAbort(state)
|
|
301
|
+
.catch((error) => {
|
|
302
|
+
settleOutcome.reject(error);
|
|
303
|
+
})
|
|
304
|
+
.finally(() => {
|
|
305
|
+
if (!state.settled) {
|
|
306
|
+
settleOutcome.reject(createAbortError(`${PI_PROVIDER} exec cancelled by client`));
|
|
307
|
+
}
|
|
308
|
+
});
|
|
309
|
+
};
|
|
310
|
+
builder.signal.addEventListener("abort", onAbort, { once: true });
|
|
311
|
+
options.signal?.addEventListener("abort", onAbort, { once: true });
|
|
312
|
+
try {
|
|
313
|
+
if (builder.signal.aborted || options.signal?.aborted) {
|
|
314
|
+
await requestAbort(state);
|
|
315
|
+
throw createAbortError(`${PI_PROVIDER} exec cancelled by client`);
|
|
316
|
+
}
|
|
317
|
+
const promptPromise = session.prompt(prompt).catch((error) => {
|
|
318
|
+
if (state.settled) {
|
|
319
|
+
return;
|
|
320
|
+
}
|
|
321
|
+
settleOutcome.reject(state.abortIntent ? createAbortError(`${PI_PROVIDER} exec cancelled by client`) : error);
|
|
322
|
+
});
|
|
323
|
+
const result = await outcome;
|
|
324
|
+
await promptPromise;
|
|
325
|
+
return result;
|
|
326
|
+
}
|
|
327
|
+
catch (error) {
|
|
328
|
+
if (state.abortIntent) {
|
|
329
|
+
throw createAbortError(`${PI_PROVIDER} exec cancelled by client`);
|
|
330
|
+
}
|
|
331
|
+
throw error;
|
|
332
|
+
}
|
|
333
|
+
finally {
|
|
334
|
+
builder.signal.removeEventListener("abort", onAbort);
|
|
335
|
+
options.signal?.removeEventListener("abort", onAbort);
|
|
336
|
+
await abortRequested?.catch(() => undefined);
|
|
337
|
+
disposeSession();
|
|
338
|
+
}
|
|
339
|
+
},
|
|
340
|
+
});
|
|
341
|
+
}
|
|
342
|
+
function readSessionManager(sdk) {
|
|
343
|
+
const store = sdk[`Session${"Man"}${"ager"}`];
|
|
344
|
+
return store;
|
|
345
|
+
}
|
package/build/agents/selector.js
CHANGED
|
@@ -1,19 +1,28 @@
|
|
|
1
|
-
import { getConfig } from "../config/
|
|
2
|
-
import {
|
|
1
|
+
import { getConfig } from "../config/env.js";
|
|
2
|
+
import { assertSettingsKnobUsable, loadKeiyakuSettings, resolveDefaultAgentName, } from "../config/settings.js";
|
|
3
|
+
import { loadAkumaCatalog, normalizeAkumaProfile } from "../config/akuma-loader.js";
|
|
3
4
|
import { FlowError } from "../flow-error.js";
|
|
4
|
-
function formatAvailableAgentNames(names) {
|
|
5
|
+
export function formatAvailableAgentNames(names) {
|
|
5
6
|
return names.length > 0 ? names.join(", ") : "(none configured)";
|
|
6
7
|
}
|
|
7
8
|
export async function selectSubagent(agentName, cwd) {
|
|
8
|
-
const
|
|
9
|
+
const loaded = await loadKeiyakuSettings(cwd);
|
|
10
|
+
assertSettingsKnobUsable(loaded, "agents");
|
|
11
|
+
const settings = loaded.knobs;
|
|
9
12
|
const requested = agentName?.trim();
|
|
13
|
+
if (!requested && !getConfig().subagent.nameOverride) {
|
|
14
|
+
assertSettingsKnobUsable(loaded, "default");
|
|
15
|
+
}
|
|
10
16
|
const selected = requested || getConfig().subagent.nameOverride || resolveDefaultAgentName(settings);
|
|
11
|
-
const
|
|
17
|
+
const catalog = await loadAkumaCatalog(cwd);
|
|
18
|
+
const availableNames = Object.keys(catalog.definitions);
|
|
12
19
|
if (!availableNames.includes(selected)) {
|
|
13
20
|
throw new FlowError("UNKNOWN_SUBAGENT", `Unknown agent '${selected}'. Expected one of: ${formatAvailableAgentNames(availableNames)}`);
|
|
14
21
|
}
|
|
22
|
+
const definition = catalog.definitions[selected];
|
|
15
23
|
return {
|
|
16
24
|
name: selected,
|
|
17
|
-
|
|
25
|
+
definition,
|
|
26
|
+
profile: normalizeAkumaProfile(definition.profile),
|
|
18
27
|
};
|
|
19
28
|
}
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
import { z } from "zod";
|
|
2
|
+
import { FlowError } from "../flow-error.js";
|
|
3
|
+
export const SUBAGENT_SESSION_VERSION = 1;
|
|
4
|
+
export const subagentSessionEnvelopeSchema = z.object({
|
|
5
|
+
provider: z.string(),
|
|
6
|
+
version: z.number().int().positive(),
|
|
7
|
+
handle: z.unknown(),
|
|
8
|
+
});
|
|
9
|
+
export function parseSessionEnvelopeText(sessionText, resumePath) {
|
|
10
|
+
let parsedSession;
|
|
11
|
+
try {
|
|
12
|
+
parsedSession = JSON.parse(sessionText);
|
|
13
|
+
}
|
|
14
|
+
catch {
|
|
15
|
+
throw new FlowError("INVALID_RESPONSE_PATH", `Resume path file '${resumePath}' includes invalid 'session' metadata; this conversation cannot be resumed.`);
|
|
16
|
+
}
|
|
17
|
+
const sessionResult = subagentSessionEnvelopeSchema.safeParse(parsedSession);
|
|
18
|
+
if (!sessionResult.success) {
|
|
19
|
+
throw new FlowError("INVALID_RESPONSE_PATH", `Resume path file '${resumePath}' includes invalid 'session' metadata; this conversation cannot be resumed.`);
|
|
20
|
+
}
|
|
21
|
+
return sessionResult.data;
|
|
22
|
+
}
|
|
23
|
+
export function resolvePersistedSubagentSession(input) {
|
|
24
|
+
const sessionText = input.sessionText?.trim();
|
|
25
|
+
if (!sessionText) {
|
|
26
|
+
return undefined;
|
|
27
|
+
}
|
|
28
|
+
return parseSessionEnvelopeText(sessionText, input.resumePath);
|
|
29
|
+
}
|
package/build/agents/types.js
CHANGED
|
@@ -1,11 +1,4 @@
|
|
|
1
|
-
|
|
2
|
-
export const SUBAGENT_SESSION_VERSION = 1;
|
|
3
|
-
export const subagentSessionEnvelopeSchema = z.object({
|
|
4
|
-
provider: z.string(),
|
|
5
|
-
version: z.number().int().positive(),
|
|
6
|
-
handle: z.unknown(),
|
|
7
|
-
});
|
|
8
|
-
export class SubagentExecError extends Error {
|
|
1
|
+
export class SubagentFailure extends Error {
|
|
9
2
|
code;
|
|
10
3
|
timeoutMs;
|
|
11
4
|
exitCode;
|
|
@@ -13,7 +6,7 @@ export class SubagentExecError extends Error {
|
|
|
13
6
|
diagnostics;
|
|
14
7
|
constructor(code, message, options = {}) {
|
|
15
8
|
super(message, options.cause === undefined ? undefined : { cause: options.cause });
|
|
16
|
-
this.name = "
|
|
9
|
+
this.name = "SubagentFailure";
|
|
17
10
|
this.code = code;
|
|
18
11
|
this.timeoutMs = options.timeoutMs ?? null;
|
|
19
12
|
this.exitCode = options.exitCode ?? null;
|
|
@@ -21,6 +14,7 @@ export class SubagentExecError extends Error {
|
|
|
21
14
|
this.diagnostics = options.diagnostics ?? "";
|
|
22
15
|
}
|
|
23
16
|
}
|
|
24
|
-
export function
|
|
25
|
-
return error instanceof
|
|
17
|
+
export function isSubagentFailure(error) {
|
|
18
|
+
return error instanceof SubagentFailure;
|
|
26
19
|
}
|
|
20
|
+
export { SUBAGENT_SESSION_VERSION, subagentSessionEnvelopeSchema } from "./session.js";
|
|
@@ -0,0 +1,79 @@
|
|
|
1
|
+
import { resolveDefaultAgentName, resolveReviewerAgentName, } from "../../config/settings.js";
|
|
2
|
+
import { normalizeAkumaProfile } from "../../config/akuma-loader.js";
|
|
3
|
+
import { FlowError } from "../../flow-error.js";
|
|
4
|
+
function formatRoleMarkers(settings, name) {
|
|
5
|
+
const markers = [];
|
|
6
|
+
if (name === resolveDefaultAgentName(settings))
|
|
7
|
+
markers.push("default");
|
|
8
|
+
if (name === resolveReviewerAgentName(settings))
|
|
9
|
+
markers.push("reviewer");
|
|
10
|
+
return markers.length > 0 ? ` (${markers.join(", ")})` : "";
|
|
11
|
+
}
|
|
12
|
+
function formatProfileSummary(profile) {
|
|
13
|
+
const model = profile.model ? `, model ${profile.model}` : "";
|
|
14
|
+
return `provider ${profile.provider}, executable ${profile.executable}${model}`;
|
|
15
|
+
}
|
|
16
|
+
function sourceBadge(definition) {
|
|
17
|
+
return `[${definition.source}]`;
|
|
18
|
+
}
|
|
19
|
+
export function renderAkumaList(catalog, settings) {
|
|
20
|
+
const lines = ["Available Akumas:", ""];
|
|
21
|
+
for (const definition of Object.values(catalog.definitions).sort((a, b) => a.name.localeCompare(b.name))) {
|
|
22
|
+
const profile = normalizeAkumaProfile(definition.profile);
|
|
23
|
+
lines.push(`- ${definition.name}${formatRoleMarkers(settings, definition.name)} ${sourceBadge(definition)}: ${formatProfileSummary(profile)}`);
|
|
24
|
+
if (profile.description) {
|
|
25
|
+
lines.push(` ${profile.description}`);
|
|
26
|
+
}
|
|
27
|
+
}
|
|
28
|
+
for (const shadow of catalog.shadows) {
|
|
29
|
+
lines.push(`- ${shadow.name} [${shadow.source}] (shadowed by ${shadow.shadowedBy})`);
|
|
30
|
+
}
|
|
31
|
+
for (const invalid of catalog.invalid) {
|
|
32
|
+
lines.push(`- ${invalid.name} (invalid: ${invalid.reason})`);
|
|
33
|
+
}
|
|
34
|
+
return lines.join("\n");
|
|
35
|
+
}
|
|
36
|
+
function appendOptionalLine(lines, label, value) {
|
|
37
|
+
if (value === undefined)
|
|
38
|
+
return;
|
|
39
|
+
if (Array.isArray(value)) {
|
|
40
|
+
lines.push(`${label}: ${value.length > 0 ? value.join(", ") : "(none)"}`);
|
|
41
|
+
return;
|
|
42
|
+
}
|
|
43
|
+
if (typeof value === "object" && value !== null) {
|
|
44
|
+
lines.push(`${label}: ${JSON.stringify(value)}`);
|
|
45
|
+
return;
|
|
46
|
+
}
|
|
47
|
+
lines.push(`${label}: ${String(value)}`);
|
|
48
|
+
}
|
|
49
|
+
export function renderAkumaShow(catalog, settings, name) {
|
|
50
|
+
const definition = catalog.definitions[name];
|
|
51
|
+
if (definition === undefined) {
|
|
52
|
+
throw new FlowError("UNKNOWN_SUBAGENT", `Unknown agent '${name}'.`);
|
|
53
|
+
}
|
|
54
|
+
const profile = normalizeAkumaProfile(definition.profile);
|
|
55
|
+
const promptLines = definition.body === "" ? [] : definition.body.split(/\r?\n/);
|
|
56
|
+
const lines = [
|
|
57
|
+
`Akuma: ${name}`,
|
|
58
|
+
`source: ${sourceBadge(definition)}`,
|
|
59
|
+
`definition file: ${definition.path ?? "(built-in)"}`,
|
|
60
|
+
`roles: ${formatRoleMarkers(settings, name).replace(/^\s*\(|\)$/g, "") || "(none)"}`,
|
|
61
|
+
`provider: ${profile.provider}`,
|
|
62
|
+
`executable: ${profile.executable}`,
|
|
63
|
+
];
|
|
64
|
+
appendOptionalLine(lines, "description", profile.description);
|
|
65
|
+
appendOptionalLine(lines, "model", profile.model);
|
|
66
|
+
if (profile.provider === "codex-sdk") {
|
|
67
|
+
appendOptionalLine(lines, "profile", profile.profile);
|
|
68
|
+
appendOptionalLine(lines, "config", profile.config);
|
|
69
|
+
}
|
|
70
|
+
if (profile.provider === "claude-agent-sdk") {
|
|
71
|
+
appendOptionalLine(lines, "effort", profile.effort);
|
|
72
|
+
appendOptionalLine(lines, "permissionMode", profile.permissionMode);
|
|
73
|
+
appendOptionalLine(lines, "settingSources", profile.settingSources);
|
|
74
|
+
}
|
|
75
|
+
lines.push("prompt:");
|
|
76
|
+
lines.push(...promptLines.slice(0, 8).map((line) => ` ${line}`));
|
|
77
|
+
lines.push(`(${promptLines.length} lines total)`);
|
|
78
|
+
return lines.join("\n");
|
|
79
|
+
}
|
|
@@ -0,0 +1,71 @@
|
|
|
1
|
+
import { assertSettingsKnobUsable, loadKeiyakuSettings } from "../config/settings.js";
|
|
2
|
+
import { loadAkumaCatalog } from "../config/akuma-loader.js";
|
|
3
|
+
import { listContractIds } from "../core/status.js";
|
|
4
|
+
const TOP_LEVEL_COMMANDS = [
|
|
5
|
+
"bind",
|
|
6
|
+
"arc",
|
|
7
|
+
"summon",
|
|
8
|
+
"ask",
|
|
9
|
+
"tell",
|
|
10
|
+
"resume",
|
|
11
|
+
"log",
|
|
12
|
+
"akuma",
|
|
13
|
+
"skills",
|
|
14
|
+
"amend",
|
|
15
|
+
"renew",
|
|
16
|
+
"petition",
|
|
17
|
+
"forfeit",
|
|
18
|
+
"guide",
|
|
19
|
+
"completion",
|
|
20
|
+
"status",
|
|
21
|
+
"dump-env",
|
|
22
|
+
];
|
|
23
|
+
function filterPrefix(items, word) {
|
|
24
|
+
return items.filter((item) => item.startsWith(word));
|
|
25
|
+
}
|
|
26
|
+
async function contractAddressCandidates(cwd, word) {
|
|
27
|
+
return filterPrefix((await listContractIds(cwd)).map((id) => `@${id}`), word);
|
|
28
|
+
}
|
|
29
|
+
async function akumaCandidates(cwd, word) {
|
|
30
|
+
const loaded = await loadKeiyakuSettings(cwd);
|
|
31
|
+
assertSettingsKnobUsable(loaded, "agents");
|
|
32
|
+
return filterPrefix(Object.keys((await loadAkumaCatalog(cwd)).definitions), word);
|
|
33
|
+
}
|
|
34
|
+
export async function renderCompletionCandidates(input) {
|
|
35
|
+
const word = input.word ?? "";
|
|
36
|
+
if (word.startsWith("@")) {
|
|
37
|
+
return (await contractAddressCandidates(input.cwd, word)).join("\n");
|
|
38
|
+
}
|
|
39
|
+
if (input.previous === "summon" || input.previous === "ask" || input.previous === "show") {
|
|
40
|
+
return (await akumaCandidates(input.cwd, word)).join("\n");
|
|
41
|
+
}
|
|
42
|
+
if (input.previous === "akuma") {
|
|
43
|
+
return filterPrefix(["list", "ls", "show"], word).join("\n");
|
|
44
|
+
}
|
|
45
|
+
if (input.previous === "skills") {
|
|
46
|
+
return filterPrefix(["install"], word).join("\n");
|
|
47
|
+
}
|
|
48
|
+
return filterPrefix(TOP_LEVEL_COMMANDS, word).join("\n");
|
|
49
|
+
}
|
|
50
|
+
export function renderCompletionScript(shell = "bash") {
|
|
51
|
+
if (shell === "zsh") {
|
|
52
|
+
return [
|
|
53
|
+
"#compdef keiyaku",
|
|
54
|
+
"_keiyaku() {",
|
|
55
|
+
" local -a candidates",
|
|
56
|
+
" candidates=(${(f)\"$(keiyaku completion --complete --word \"${words[CURRENT]}\" --previous \"${words[CURRENT-1]}\" 2>/dev/null)\"})",
|
|
57
|
+
" compadd -- $candidates",
|
|
58
|
+
"}",
|
|
59
|
+
"_keiyaku \"$@\"",
|
|
60
|
+
].join("\n");
|
|
61
|
+
}
|
|
62
|
+
return [
|
|
63
|
+
"_keiyaku_completion() {",
|
|
64
|
+
" local cur prev",
|
|
65
|
+
" cur=\"${COMP_WORDS[COMP_CWORD]}\"",
|
|
66
|
+
" prev=\"${COMP_WORDS[COMP_CWORD-1]}\"",
|
|
67
|
+
" COMPREPLY=( $(compgen -W \"$(keiyaku completion --complete --word \"$cur\" --previous \"$prev\" 2>/dev/null)\" -- \"$cur\") )",
|
|
68
|
+
"}",
|
|
69
|
+
"complete -F _keiyaku_completion keiyaku",
|
|
70
|
+
].join("\n");
|
|
71
|
+
}
|
|
@@ -0,0 +1,50 @@
|
|
|
1
|
+
import * as fs from "fs/promises";
|
|
2
|
+
import * as path from "path";
|
|
3
|
+
import { DRAFT_ARTIFACT_NOTICE, KEIYAKU_DRAFT_DIR } from "../keiyaku.js";
|
|
4
|
+
const DRAFT_SLUG_FALLBACK = "keiyaku-draft";
|
|
5
|
+
function normalizeDraftRelativePath(relativePath) {
|
|
6
|
+
return relativePath.split(path.sep).join("/");
|
|
7
|
+
}
|
|
8
|
+
function formatDraftTimestamp(now) {
|
|
9
|
+
const year = String(now.getFullYear());
|
|
10
|
+
const month = String(now.getMonth() + 1).padStart(2, "0");
|
|
11
|
+
const day = String(now.getDate()).padStart(2, "0");
|
|
12
|
+
const hour = String(now.getHours()).padStart(2, "0");
|
|
13
|
+
const minute = String(now.getMinutes()).padStart(2, "0");
|
|
14
|
+
const second = String(now.getSeconds()).padStart(2, "0");
|
|
15
|
+
return `${year}${month}${day}-${hour}${minute}${second}`;
|
|
16
|
+
}
|
|
17
|
+
function toDraftSlug(title) {
|
|
18
|
+
const normalized = title
|
|
19
|
+
.trim()
|
|
20
|
+
.toLowerCase()
|
|
21
|
+
.replace(/[^\p{Script=Han}a-z0-9]+/gu, "-")
|
|
22
|
+
.replace(/-+/g, "-")
|
|
23
|
+
.replace(/^-+/, "")
|
|
24
|
+
.replace(/-+$/, "");
|
|
25
|
+
return normalized || DRAFT_SLUG_FALLBACK;
|
|
26
|
+
}
|
|
27
|
+
function buildDraftArtifactRelativePath(title, now = new Date()) {
|
|
28
|
+
const filename = `${formatDraftTimestamp(now)}_${toDraftSlug(title)}.md`;
|
|
29
|
+
return normalizeDraftRelativePath(path.join(KEIYAKU_DRAFT_DIR, filename));
|
|
30
|
+
}
|
|
31
|
+
function buildDraftArtifactMarkdown(content, now) {
|
|
32
|
+
return [
|
|
33
|
+
"---",
|
|
34
|
+
"generated_by: keiyaku",
|
|
35
|
+
"type: draft",
|
|
36
|
+
`time: '${now.toISOString()}'`,
|
|
37
|
+
`notice: '${DRAFT_ARTIFACT_NOTICE}'`,
|
|
38
|
+
"---",
|
|
39
|
+
"",
|
|
40
|
+
content,
|
|
41
|
+
].join("\n");
|
|
42
|
+
}
|
|
43
|
+
export async function writeDraftArtifact(input) {
|
|
44
|
+
const now = input.now ?? new Date();
|
|
45
|
+
const relativePath = buildDraftArtifactRelativePath(input.title, now);
|
|
46
|
+
const absolutePath = path.join(input.cwd, relativePath);
|
|
47
|
+
await fs.mkdir(path.dirname(absolutePath), { recursive: true });
|
|
48
|
+
await fs.writeFile(absolutePath, buildDraftArtifactMarkdown(input.content, now), "utf-8");
|
|
49
|
+
return relativePath;
|
|
50
|
+
}
|