@astrosheep/keiyaku 4.0.5 → 4.1.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/build/integrations/marketplace/plugins/keiyaku/.claude-plugin/plugin.json +1 -1
- package/build/integrations/marketplace/plugins/keiyaku/.codex-plugin/plugin.json +1 -1
- package/build/integrations/marketplace/plugins/keiyaku/package.json +1 -1
- package/build/integrations/marketplace/plugins/keiyaku/skills/keiyaku/SKILL.md +1 -1
- package/build/integrations/marketplace/plugins/keiyaku/skills/keiyaku-akuma/SKILL.md +15 -7
- package/build/integrations/marketplace/plugins/keiyaku/skills/keiyaku-bind/SKILL.md +5 -3
- package/build/integrations/marketplace/plugins/keiyaku/skills/keiyaku-workflow/SKILL.md +10 -4
- package/build/src/akuma/akuma.d.ts +2 -0
- package/build/src/akuma/akuma.js +26 -3
- package/build/src/akuma/archetype.d.ts +3 -1
- package/build/src/akuma/archetype.js +19 -7
- package/build/src/akuma/body-turn.d.ts +64 -0
- package/build/src/akuma/body-turn.js +449 -0
- package/build/src/akuma/body.d.ts +3 -7
- package/build/src/akuma/body.js +9 -420
- package/build/src/akuma/heart/facts.d.ts +2 -0
- package/build/src/akuma/heart/facts.js +3 -1
- package/build/src/akuma/heart/index.js +12 -2
- package/build/src/akuma/heart/rows.js +4 -1
- package/build/src/akuma/heart/schema.d.ts +1 -1
- package/build/src/akuma/heart/schema.js +3 -2
- package/build/src/akuma/heart/timeline.d.ts +1 -0
- package/build/src/akuma/heart/timeline.js +19 -0
- package/build/src/akuma/nuke.d.ts +2 -0
- package/build/src/akuma/nuke.js +152 -0
- package/build/src/akuma/provider-recipe.d.ts +2 -0
- package/build/src/akuma/provider-recipe.js +10 -0
- package/build/src/akuma/provider.d.ts +3 -1
- package/build/src/akuma/providers/acp/config.d.ts +10 -0
- package/build/src/akuma/providers/acp/config.js +45 -0
- package/build/src/akuma/providers/acp/core.d.ts +5 -2
- package/build/src/akuma/providers/acp/core.js +35 -16
- package/build/src/akuma/providers/acp/index.d.ts +1 -8
- package/build/src/akuma/providers/acp/index.js +10 -39
- package/build/src/akuma/providers/claude/index.d.ts +1 -1
- package/build/src/akuma/providers/claude/index.js +14 -5
- package/build/src/akuma/providers/codex-app-server/index.js +9 -1
- package/build/src/akuma/providers/grok-build/index.js +16 -2
- package/build/src/akuma/providers/index.d.ts +3 -2
- package/build/src/akuma/providers/index.js +37 -37
- package/build/src/akuma/providers/opencode-sdk/index.js +9 -0
- package/build/src/akuma/providers/opencode-sdk/session.d.ts +1 -1
- package/build/src/akuma/providers/opencode-sdk/session.js +1 -1
- package/build/src/akuma/providers/pi/index.d.ts +1 -1
- package/build/src/akuma/providers/pi/index.js +30 -12
- package/build/src/akuma/request-serve.d.ts +1 -0
- package/build/src/akuma/request-serve.js +31 -31
- package/build/src/akuma/request-wire.d.ts +3 -2
- package/build/src/akuma/request-wire.js +5 -2
- package/build/src/akuma-body.js +15 -1
- package/build/src/alias/index.d.ts +1 -0
- package/build/src/alias/index.js +34 -1
- package/build/src/body/amend.js +0 -9
- package/build/src/body/region.js +2 -2
- package/build/src/cli/accepted.js +6 -0
- package/build/src/cli/commands/akuma-invoke.js +1 -0
- package/build/src/cli/commands/akuma.d.ts +1 -0
- package/build/src/cli/commands/akuma.js +11 -8
- package/build/src/cli/commands/contract.d.ts +28 -15
- package/build/src/cli/commands/contract.js +34 -18
- package/build/src/cli/commands/install.d.ts +2 -2
- package/build/src/cli/commands/install.js +6 -6
- package/build/src/cli/commands/nuke.d.ts +10 -0
- package/build/src/cli/commands/nuke.js +20 -0
- package/build/src/cli/commands/task-invoke.d.ts +19 -1
- package/build/src/cli/commands/task-invoke.js +63 -29
- package/build/src/cli/commands/task-query.d.ts +2 -2
- package/build/src/cli/commands/task-query.js +1 -2
- package/build/src/cli/commands/task.js +30 -22
- package/build/src/cli/coordinates.d.ts +5 -2
- package/build/src/cli/coordinates.js +5 -52
- package/build/src/cli/invoke.d.ts +5 -4
- package/build/src/cli/invoke.js +89 -59
- package/build/src/cli/main.d.ts +0 -2
- package/build/src/cli/main.js +4 -140
- package/build/src/cli/parse.d.ts +15 -2
- package/build/src/cli/parse.js +73 -8
- package/build/src/cli/render/akuma.js +95 -49
- package/build/src/cli/render/catalog.js +5 -1
- package/build/src/cli/render/contract.js +72 -49
- package/build/src/cli/render/kanshi.js +157 -99
- package/build/src/cli/render/nuke.d.ts +3 -0
- package/build/src/cli/render/nuke.js +8 -0
- package/build/src/cli/render/receipt.d.ts +1 -0
- package/build/src/cli/render/receipt.js +18 -14
- package/build/src/cli/render/refusal.d.ts +2 -1
- package/build/src/cli/render/refusal.js +37 -0
- package/build/src/cli/render/task.js +63 -11
- package/build/src/cli/render/text.js +6 -1
- package/build/src/cli/result.d.ts +28 -2
- package/build/src/cli/runtime.d.ts +2 -0
- package/build/src/cli/runtime.js +140 -0
- package/build/src/cli/usage.d.ts +0 -2
- package/build/src/cli/usage.js +0 -4
- package/build/src/contract-worktree.d.ts +26 -12
- package/build/src/contract-worktree.js +119 -64
- package/build/src/git/hooks.d.ts +2 -0
- package/build/src/git/hooks.js +34 -1
- package/build/src/git/integration.d.ts +4 -0
- package/build/src/git/integration.js +38 -0
- package/build/src/git/nuke.d.ts +2 -0
- package/build/src/git/nuke.js +92 -0
- package/build/src/git/process.d.ts +2 -0
- package/build/src/git/repository.js +4 -1
- package/build/src/git/target-placement.d.ts +2 -1
- package/build/src/git/target-placement.js +6 -7
- package/build/src/git/tender.d.ts +14 -2
- package/build/src/git/tender.js +45 -6
- package/build/src/git/workspace.d.ts +3 -0
- package/build/src/kanshi/read.js +37 -13
- package/build/src/kanshi/report.d.ts +2 -1
- package/build/src/library/akuma-creation.d.ts +1 -0
- package/build/src/library/akuma-creation.js +8 -1
- package/build/src/library/audit.d.ts +1 -0
- package/build/src/library/audit.js +1 -0
- package/build/src/library/continuation.d.ts +26 -0
- package/build/src/library/continuation.js +78 -0
- package/build/src/library/contract.d.ts +14 -8
- package/build/src/library/contract.js +82 -35
- package/build/src/library/delivery.d.ts +18 -11
- package/build/src/library/keiyaku.d.ts +5 -1
- package/build/src/library/keiyaku.js +2 -0
- package/build/src/library/mutation.d.ts +1 -0
- package/build/src/library/mutation.js +14 -4
- package/build/src/library/nuke.d.ts +14 -0
- package/build/src/library/nuke.js +54 -0
- package/build/src/library/reconcile.js +5 -2
- package/build/src/library/refusal.d.ts +10 -1
- package/build/src/library/repo.d.ts +1 -0
- package/build/src/library/repo.js +2 -0
- package/build/src/protocol/audit.js +12 -5
- package/build/src/protocol/completion.d.ts +11 -1
- package/build/src/protocol/completion.js +68 -15
- package/build/src/protocol/deliver.d.ts +25 -3
- package/build/src/protocol/deliver.js +148 -14
- package/build/src/protocol/intent.d.ts +1 -0
- package/build/src/protocol/intent.js +7 -1
- package/build/src/protocol/operations.d.ts +36 -4
- package/build/src/protocol/operations.js +2 -2
- package/build/src/protocol/placement.d.ts +9 -1
- package/build/src/protocol/placement.js +5 -4
- package/build/src/protocol/read/status.d.ts +16 -4
- package/build/src/protocol/read/status.js +54 -12
- package/build/src/protocol/review.d.ts +1 -0
- package/build/src/protocol/review.js +8 -0
- package/build/src/runtime/proc/run.js +9 -5
- package/build/src/settlement/settle.js +1 -2
- package/build/src/task/board.d.ts +21 -2
- package/build/src/task/board.js +25 -12
- package/build/src/task/compose.d.ts +1 -1
- package/build/src/task/compose.js +2 -7
- package/build/src/task/context.d.ts +14 -2
- package/build/src/task/context.js +30 -7
- package/build/src/task/identity.js +14 -4
- package/build/src/task/index.d.ts +7 -5
- package/build/src/task/index.js +21 -18
- package/build/src/task/mutation.d.ts +7 -2
- package/build/src/task/mutation.js +14 -6
- package/build/src/task/operations.d.ts +14 -7
- package/build/src/task/operations.js +37 -50
- package/build/src/task/query.js +2 -2
- package/build/src/task/store.d.ts +3 -0
- package/build/src/task/store.js +21 -2
- package/build/src/workspace-place.d.ts +2 -0
- package/build/src/workspace-place.js +27 -1
- package/package.json +1 -1
|
@@ -0,0 +1,152 @@
|
|
|
1
|
+
import { lstat, readdir, rm } from "node:fs/promises";
|
|
2
|
+
import { nukeAliases } from "../alias/index.js";
|
|
3
|
+
import { CONTROL_RESPONSE_MS } from "./body.js";
|
|
4
|
+
import { HeldAkumaLeash, readHeart, readKill, requestStop, } from "./heart/index.js";
|
|
5
|
+
import { akuIdFromDirectoryName, akumaPaths, akumaRunRoot, } from "./identity.js";
|
|
6
|
+
const POLL_MS = 25;
|
|
7
|
+
async function hasAkumaCustody(paths) {
|
|
8
|
+
try {
|
|
9
|
+
const value = await lstat(paths.heart);
|
|
10
|
+
return value.isFile() && !value.isSymbolicLink();
|
|
11
|
+
}
|
|
12
|
+
catch (error) {
|
|
13
|
+
if (error.code !== "ENOENT")
|
|
14
|
+
throw error;
|
|
15
|
+
return false;
|
|
16
|
+
}
|
|
17
|
+
}
|
|
18
|
+
function wait(milliseconds) {
|
|
19
|
+
return new Promise((resolve) => setTimeout(resolve, milliseconds));
|
|
20
|
+
}
|
|
21
|
+
async function takeLeashUntil(paths, deadline) {
|
|
22
|
+
for (;;) {
|
|
23
|
+
const leash = await HeldAkumaLeash.try(paths);
|
|
24
|
+
if (leash !== null)
|
|
25
|
+
return leash;
|
|
26
|
+
if (performance.now() >= deadline)
|
|
27
|
+
return null;
|
|
28
|
+
await wait(POLL_MS);
|
|
29
|
+
}
|
|
30
|
+
}
|
|
31
|
+
async function stopRunningAkuma(entry) {
|
|
32
|
+
const request = await requestStop(entry.paths, new Date().toISOString());
|
|
33
|
+
const leash = await takeLeashUntil(entry.paths, performance.now() + CONTROL_RESPONSE_MS);
|
|
34
|
+
if (leash === null)
|
|
35
|
+
throw new Error(`Akuma ${entry.id} could not be stopped: unavailable`);
|
|
36
|
+
try {
|
|
37
|
+
if (request.kind === "already-killed" || request.kind === "already-stopped")
|
|
38
|
+
return leash;
|
|
39
|
+
if (await readKill(entry.paths, request.body.sequence) !== null)
|
|
40
|
+
return leash;
|
|
41
|
+
const current = await readHeart(entry.paths);
|
|
42
|
+
if (current.latestBody?.sequence !== request.body.sequence) {
|
|
43
|
+
if (await readKill(entry.paths, request.body.sequence) !== null)
|
|
44
|
+
return leash;
|
|
45
|
+
throw new Error(`Akuma ${entry.id} could not be stopped: unavailable`);
|
|
46
|
+
}
|
|
47
|
+
if (current.latestBody.end !== "put-down") {
|
|
48
|
+
await leash.clearStop(entry.paths);
|
|
49
|
+
throw new Error(`Akuma ${entry.id} could not be stopped: untidy`);
|
|
50
|
+
}
|
|
51
|
+
if (await leash.settleStop(entry.paths, request.body.sequence) === null) {
|
|
52
|
+
throw new Error(`Akuma ${entry.id} could not be stopped: unavailable`);
|
|
53
|
+
}
|
|
54
|
+
return leash;
|
|
55
|
+
}
|
|
56
|
+
catch (error) {
|
|
57
|
+
leash.release();
|
|
58
|
+
throw error;
|
|
59
|
+
}
|
|
60
|
+
}
|
|
61
|
+
async function removeRegularFile(path) {
|
|
62
|
+
try {
|
|
63
|
+
const value = await lstat(path);
|
|
64
|
+
if (value.isFile() && !value.isSymbolicLink())
|
|
65
|
+
await rm(path, { force: true });
|
|
66
|
+
}
|
|
67
|
+
catch (error) {
|
|
68
|
+
if (error.code !== "ENOENT")
|
|
69
|
+
throw error;
|
|
70
|
+
}
|
|
71
|
+
}
|
|
72
|
+
async function removeAkumaEntry(entry) {
|
|
73
|
+
for (const path of [entry.paths.heart, entry.paths.log,
|
|
74
|
+
`${entry.paths.heart}-wal`, `${entry.paths.heart}-shm`]) {
|
|
75
|
+
await removeRegularFile(path);
|
|
76
|
+
}
|
|
77
|
+
}
|
|
78
|
+
async function nukeAkumaEntries(world) {
|
|
79
|
+
const runRoot = akumaRunRoot(world);
|
|
80
|
+
let names;
|
|
81
|
+
try {
|
|
82
|
+
names = (await readdir(runRoot, { withFileTypes: true }))
|
|
83
|
+
.filter((entry) => entry.isDirectory())
|
|
84
|
+
.map((entry) => entry.name)
|
|
85
|
+
.sort();
|
|
86
|
+
}
|
|
87
|
+
catch (error) {
|
|
88
|
+
if (error.code === "ENOENT")
|
|
89
|
+
names = [];
|
|
90
|
+
else
|
|
91
|
+
throw error;
|
|
92
|
+
}
|
|
93
|
+
const entries = [];
|
|
94
|
+
for (const name of names) {
|
|
95
|
+
let physical;
|
|
96
|
+
try {
|
|
97
|
+
physical = akuIdFromDirectoryName(name);
|
|
98
|
+
}
|
|
99
|
+
catch {
|
|
100
|
+
// The run root can contain bytes not produced by Keiyaku. Preserve them.
|
|
101
|
+
continue;
|
|
102
|
+
}
|
|
103
|
+
const paths = akumaPaths({ runRoot, archetype: physical.archetype, suffix: physical.suffix });
|
|
104
|
+
if (!(await hasAkumaCustody(paths)))
|
|
105
|
+
continue;
|
|
106
|
+
entries.push({ id: physical.id, paths });
|
|
107
|
+
}
|
|
108
|
+
return entries;
|
|
109
|
+
}
|
|
110
|
+
export async function stopAkuma(world) {
|
|
111
|
+
const entries = await nukeAkumaEntries(world);
|
|
112
|
+
const held = [];
|
|
113
|
+
try {
|
|
114
|
+
for (const entry of entries) {
|
|
115
|
+
const snapshot = await readHeart(entry.paths);
|
|
116
|
+
const leash = snapshot.soul !== null && snapshot.latestBody?.end === undefined
|
|
117
|
+
? await stopRunningAkuma(entry)
|
|
118
|
+
: await takeLeashUntil(entry.paths, performance.now() + CONTROL_RESPONSE_MS);
|
|
119
|
+
if (leash === null)
|
|
120
|
+
throw new Error(`Akuma ${entry.id} could not be verified stopped`);
|
|
121
|
+
try {
|
|
122
|
+
const after = await readHeart(entry.paths);
|
|
123
|
+
if (after.soul === null || after.latestBody?.end !== undefined) {
|
|
124
|
+
held.push(leash);
|
|
125
|
+
continue;
|
|
126
|
+
}
|
|
127
|
+
throw new Error(`Akuma ${entry.id} stopped without a durable end`);
|
|
128
|
+
}
|
|
129
|
+
catch (error) {
|
|
130
|
+
leash.release();
|
|
131
|
+
throw error;
|
|
132
|
+
}
|
|
133
|
+
}
|
|
134
|
+
return async () => {
|
|
135
|
+
try {
|
|
136
|
+
await nukeAliases(world);
|
|
137
|
+
for (const entry of entries)
|
|
138
|
+
await removeAkumaEntry(entry);
|
|
139
|
+
}
|
|
140
|
+
finally {
|
|
141
|
+
for (const leash of held.reverse())
|
|
142
|
+
leash.release();
|
|
143
|
+
}
|
|
144
|
+
};
|
|
145
|
+
}
|
|
146
|
+
finally {
|
|
147
|
+
if (held.length !== entries.length) {
|
|
148
|
+
for (const leash of held.reverse())
|
|
149
|
+
leash.release();
|
|
150
|
+
}
|
|
151
|
+
}
|
|
152
|
+
}
|
|
@@ -1,9 +1,11 @@
|
|
|
1
|
+
export type SystemPromptMode = "append" | "replace";
|
|
1
2
|
export type ProviderOptions = Readonly<{
|
|
2
3
|
model?: string;
|
|
3
4
|
effort?: string;
|
|
4
5
|
readonly?: true;
|
|
5
6
|
network?: "disabled" | "enabled";
|
|
6
7
|
systemPrompt?: string;
|
|
8
|
+
systemPromptMode?: SystemPromptMode;
|
|
7
9
|
}>;
|
|
8
10
|
export type ReadonlyRestraint = Readonly<{
|
|
9
11
|
enforcement: "native";
|
|
@@ -34,12 +34,22 @@ export function decodeProviderOptions(value) {
|
|
|
34
34
|
throw new TypeError("provider option network must be disabled, enabled");
|
|
35
35
|
}
|
|
36
36
|
const systemPrompt = optionText(options, "systemPrompt", "allow");
|
|
37
|
+
const systemPromptMode = options.systemPromptMode;
|
|
38
|
+
if (systemPromptMode !== undefined) {
|
|
39
|
+
if (systemPromptMode !== "append" && systemPromptMode !== "replace") {
|
|
40
|
+
throw new TypeError("provider option systemPromptMode must be append, replace");
|
|
41
|
+
}
|
|
42
|
+
if (systemPrompt === undefined) {
|
|
43
|
+
throw new TypeError("provider option systemPromptMode requires systemPrompt");
|
|
44
|
+
}
|
|
45
|
+
}
|
|
37
46
|
return Object.freeze({
|
|
38
47
|
...(model === undefined ? {} : { model }),
|
|
39
48
|
...(effort === undefined ? {} : { effort }),
|
|
40
49
|
...(options.readonly === undefined ? {} : { readonly: true }),
|
|
41
50
|
...(network === undefined ? {} : { network }),
|
|
42
51
|
...(systemPrompt === undefined ? {} : { systemPrompt }),
|
|
52
|
+
...(systemPromptMode === undefined ? {} : { systemPromptMode }),
|
|
43
53
|
});
|
|
44
54
|
}
|
|
45
55
|
export function decodeReadonlyRestraint(value) {
|
|
@@ -121,8 +121,10 @@ export type Session = Readonly<{
|
|
|
121
121
|
events: AsyncIterable<AgentEvent>;
|
|
122
122
|
receipts?: AsyncIterable<TellReceipt>;
|
|
123
123
|
completion: Promise<TurnResult>;
|
|
124
|
-
/**
|
|
124
|
+
/** Requests graceful adapter-owned cancellation. */
|
|
125
125
|
abort(): Promise<void>;
|
|
126
|
+
/** Fulfills only after forced adapter-owned disposal is proved. */
|
|
127
|
+
forceDispose(): Promise<void>;
|
|
126
128
|
tell?(tell: Readonly<{
|
|
127
129
|
id: string;
|
|
128
130
|
text: string;
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
import type { SystemPromptMode } from "../../provider-recipe.js";
|
|
2
|
+
export type AcpExecutionConfig = Readonly<{
|
|
3
|
+
argvBefore: readonly string[];
|
|
4
|
+
argvAfter: readonly string[];
|
|
5
|
+
modelArg?: string;
|
|
6
|
+
effortArg?: string;
|
|
7
|
+
systemPromptArg?: string;
|
|
8
|
+
systemPromptMode?: SystemPromptMode;
|
|
9
|
+
}>;
|
|
10
|
+
export declare function decodeAcpConfig(value: unknown): AcpExecutionConfig;
|
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
function argumentName(value, key) {
|
|
2
|
+
const selected = value[key];
|
|
3
|
+
if (selected === undefined)
|
|
4
|
+
return undefined;
|
|
5
|
+
if (typeof selected !== "string" || selected.trim().length === 0) {
|
|
6
|
+
throw new TypeError(`ACP provider config ${key} must be a nonblank string`);
|
|
7
|
+
}
|
|
8
|
+
return selected;
|
|
9
|
+
}
|
|
10
|
+
export function decodeAcpConfig(value) {
|
|
11
|
+
if (value === null || typeof value !== "object" || Array.isArray(value)) {
|
|
12
|
+
throw new TypeError("ACP provider config must be an object");
|
|
13
|
+
}
|
|
14
|
+
const config = value;
|
|
15
|
+
const unknown = Object.keys(config)
|
|
16
|
+
.find((key) => !["argvBefore", "argvAfter", "effortArg", "modelArg", "systemPromptArg", "systemPromptMode"].includes(key));
|
|
17
|
+
if (unknown !== undefined)
|
|
18
|
+
throw new TypeError(`ACP provider config has unknown field ${unknown}`);
|
|
19
|
+
if (!Array.isArray(config.argvBefore)
|
|
20
|
+
|| config.argvBefore.some((arg) => typeof arg !== "string" || arg.trim().length === 0)) {
|
|
21
|
+
throw new TypeError("ACP provider config argvBefore must be an array of nonblank strings");
|
|
22
|
+
}
|
|
23
|
+
if (!Array.isArray(config.argvAfter)
|
|
24
|
+
|| config.argvAfter.some((arg) => typeof arg !== "string" || arg.trim().length === 0)) {
|
|
25
|
+
throw new TypeError("ACP provider config argvAfter must be an array of nonblank strings");
|
|
26
|
+
}
|
|
27
|
+
const modelArg = argumentName(config, "modelArg");
|
|
28
|
+
const effortArg = argumentName(config, "effortArg");
|
|
29
|
+
const systemPromptArg = argumentName(config, "systemPromptArg");
|
|
30
|
+
const systemPromptMode = config.systemPromptMode;
|
|
31
|
+
if (systemPromptMode !== undefined && systemPromptMode !== "append" && systemPromptMode !== "replace") {
|
|
32
|
+
throw new TypeError("ACP provider config systemPromptMode must be append, replace");
|
|
33
|
+
}
|
|
34
|
+
if (systemPromptMode !== undefined && systemPromptArg === undefined) {
|
|
35
|
+
throw new TypeError("ACP provider config systemPromptMode requires systemPromptArg");
|
|
36
|
+
}
|
|
37
|
+
return Object.freeze({
|
|
38
|
+
argvBefore: Object.freeze([...config.argvBefore]),
|
|
39
|
+
argvAfter: Object.freeze([...config.argvAfter]),
|
|
40
|
+
...(modelArg === undefined ? {} : { modelArg }),
|
|
41
|
+
...(effortArg === undefined ? {} : { effortArg }),
|
|
42
|
+
...(systemPromptArg === undefined ? {} : { systemPromptArg }),
|
|
43
|
+
...(systemPromptMode === undefined ? {} : { systemPromptMode }),
|
|
44
|
+
});
|
|
45
|
+
}
|
|
@@ -1,16 +1,19 @@
|
|
|
1
|
-
import * as
|
|
1
|
+
import type * as AcpSdk from "@agentclientprotocol/sdk";
|
|
2
2
|
import { spawnStdioProcess } from "../../../runtime/proc/stdio.js";
|
|
3
3
|
import { type ProviderAdapter, type Session } from "../../provider.js";
|
|
4
4
|
import { type AcpToolInterpreter, type AcpToolUpdate } from "./events.js";
|
|
5
5
|
export type { AcpToolInterpreter, AcpToolUpdate };
|
|
6
6
|
export type AcpStartInput = Parameters<ProviderAdapter["start"]>[0] | Parameters<NonNullable<ProviderAdapter["resume"]>>[0];
|
|
7
|
+
export type AcpSessionMeta = Readonly<Record<string, unknown>>;
|
|
7
8
|
export type AcpDependencies = Readonly<{
|
|
8
9
|
spawnProcess?: typeof spawnStdioProcess;
|
|
9
10
|
interpretTool?: AcpToolInterpreter;
|
|
11
|
+
freshSessionMeta?: AcpSessionMeta;
|
|
12
|
+
loadSessionMeta?: AcpSessionMeta;
|
|
10
13
|
}>;
|
|
11
14
|
export type AcpLiveSession = Readonly<{
|
|
12
15
|
session: Session;
|
|
13
|
-
agent:
|
|
16
|
+
agent: AcpSdk.ClientContext;
|
|
14
17
|
sessionId: string;
|
|
15
18
|
open(): boolean;
|
|
16
19
|
}>;
|
|
@@ -1,10 +1,9 @@
|
|
|
1
|
-
import * as acp from "@agentclientprotocol/sdk";
|
|
2
1
|
import { Readable, Writable } from "node:stream";
|
|
3
2
|
import { spawnStdioProcess } from "../../../runtime/proc/stdio.js";
|
|
4
3
|
import { abortable } from "../../abort.js";
|
|
5
4
|
import { AKUMA_REQUESTS_ENV, AgentEventChannel, } from "../../provider.js";
|
|
6
5
|
import { EMPTY_ACP_EVENT_STATE, flushAcpEvents, mapAcpUpdate, } from "./events.js";
|
|
7
|
-
function diagnostic(error) {
|
|
6
|
+
function diagnostic(acp, error) {
|
|
8
7
|
if (!(error instanceof Error))
|
|
9
8
|
return String(error);
|
|
10
9
|
if (!(error instanceof acp.RequestError) || error.data === undefined)
|
|
@@ -18,7 +17,7 @@ function diagnostic(error) {
|
|
|
18
17
|
}
|
|
19
18
|
return data.length === 0 ? `${error.message} [${error.code}]` : `${error.message} [${error.code}]: ${data}`;
|
|
20
19
|
}
|
|
21
|
-
function createAcpClient(onUpdate) {
|
|
20
|
+
function createAcpClient(acp, onUpdate) {
|
|
22
21
|
return acp.client({ name: "keiyaku" })
|
|
23
22
|
.onNotification(acp.methods.client.session.update, ({ params }) => onUpdate(params));
|
|
24
23
|
}
|
|
@@ -29,7 +28,10 @@ function promptResult(response) {
|
|
|
29
28
|
}
|
|
30
29
|
return { kind: "failed", diagnostic: `ACP prompt ended ${response.stopReason}` };
|
|
31
30
|
}
|
|
32
|
-
|
|
31
|
+
function requestMeta(meta) {
|
|
32
|
+
return meta === undefined || Object.keys(meta).length === 0 ? {} : { _meta: meta };
|
|
33
|
+
}
|
|
34
|
+
async function establishSession(acp, agent, input, dependencies) {
|
|
33
35
|
const initialized = await agent.request(acp.methods.agent.initialize, {
|
|
34
36
|
protocolVersion: acp.PROTOCOL_VERSION,
|
|
35
37
|
clientInfo: { name: "keiyaku", version: "4" },
|
|
@@ -38,15 +40,24 @@ async function establishSession(agent, input) {
|
|
|
38
40
|
throw new Error("ACP agent does not advertise session/load");
|
|
39
41
|
}
|
|
40
42
|
if (input.session.kind === "fresh") {
|
|
41
|
-
return (await agent.request(acp.methods.agent.session.new, {
|
|
43
|
+
return (await agent.request(acp.methods.agent.session.new, {
|
|
44
|
+
cwd: input.cwd,
|
|
45
|
+
mcpServers: [],
|
|
46
|
+
...requestMeta(dependencies.freshSessionMeta),
|
|
47
|
+
})).sessionId;
|
|
42
48
|
}
|
|
43
49
|
const sessionId = input.session.coordinate.sessionId;
|
|
44
50
|
if (sessionId === undefined)
|
|
45
51
|
throw new Error("ACP resume coordinate has no session id");
|
|
46
|
-
await agent.request(acp.methods.agent.session.load, {
|
|
52
|
+
await agent.request(acp.methods.agent.session.load, {
|
|
53
|
+
cwd: input.cwd,
|
|
54
|
+
mcpServers: [],
|
|
55
|
+
sessionId,
|
|
56
|
+
...requestMeta(dependencies.loadSessionMeta),
|
|
57
|
+
});
|
|
47
58
|
return sessionId;
|
|
48
59
|
}
|
|
49
|
-
function createAcpTurn(connection, interpret) {
|
|
60
|
+
function createAcpTurn(acp, connection, interpret) {
|
|
50
61
|
const events = new AgentEventChannel();
|
|
51
62
|
let state = EMPTY_ACP_EVENT_STATE;
|
|
52
63
|
let terminal = false;
|
|
@@ -66,7 +77,7 @@ function createAcpTurn(connection, interpret) {
|
|
|
66
77
|
terminal = true;
|
|
67
78
|
let completionResult = result;
|
|
68
79
|
const failCleanup = (error) => {
|
|
69
|
-
completionResult = { kind: "failed", diagnostic: `ACP cleanup failed: ${diagnostic(error)}` };
|
|
80
|
+
completionResult = { kind: "failed", diagnostic: `ACP cleanup failed: ${diagnostic(acp, error)}` };
|
|
70
81
|
};
|
|
71
82
|
try {
|
|
72
83
|
await cleanup();
|
|
@@ -96,11 +107,12 @@ function createAcpTurn(connection, interpret) {
|
|
|
96
107
|
open: () => !terminal,
|
|
97
108
|
};
|
|
98
109
|
}
|
|
99
|
-
function beginAcpPrompt(
|
|
110
|
+
function beginAcpPrompt(context, child, turn, sessionId, input) {
|
|
111
|
+
const { acp, agent } = context;
|
|
100
112
|
void agent.request(acp.methods.agent.session.prompt, {
|
|
101
113
|
sessionId,
|
|
102
114
|
prompt: [{ type: "text", text: input.body }, ...input.launchTells.map(({ text }) => ({ type: "text", text }))],
|
|
103
|
-
}).then((response) => turn.finish(promptResult(response), () => child.endInputAndDrain()), (error) => turn.finish({ kind: "failed", diagnostic: diagnostic(error) }, () => child.endInputAndDrain()));
|
|
115
|
+
}).then((response) => turn.finish(promptResult(response), () => child.endInputAndDrain()), (error) => turn.finish({ kind: "failed", diagnostic: diagnostic(acp, error) }, () => child.endInputAndDrain()));
|
|
104
116
|
void child.exited.then((exit) => turn.finish({
|
|
105
117
|
kind: "failed",
|
|
106
118
|
diagnostic: exit.stderr || `ACP process exited${exit.code === null ? "" : ` with code ${exit.code}`}`,
|
|
@@ -115,16 +127,23 @@ function beginAcpPrompt(agent, child, turn, sessionId, input) {
|
|
|
115
127
|
await agent.notify(acp.methods.agent.session.cancel, { sessionId });
|
|
116
128
|
}
|
|
117
129
|
catch (error) {
|
|
118
|
-
result = { kind: "failed", diagnostic: diagnostic(error) };
|
|
130
|
+
result = { kind: "failed", diagnostic: diagnostic(acp, error) };
|
|
119
131
|
}
|
|
120
132
|
if (!turn.open())
|
|
121
133
|
await child.close(true);
|
|
122
134
|
else
|
|
123
135
|
await turn.finish(result, () => child.close(true));
|
|
124
136
|
},
|
|
137
|
+
forceDispose: async () => {
|
|
138
|
+
if (!turn.open())
|
|
139
|
+
await child.close(true);
|
|
140
|
+
else
|
|
141
|
+
await turn.finish({ kind: "failed", diagnostic: "ACP turn force-disposed" }, () => child.close(true));
|
|
142
|
+
},
|
|
125
143
|
};
|
|
126
144
|
}
|
|
127
145
|
export async function startAcpSession(launch, input, dependencies = {}) {
|
|
146
|
+
const acp = await import("@agentclientprotocol/sdk");
|
|
128
147
|
const signal = input.signal ?? new AbortController().signal;
|
|
129
148
|
signal.throwIfAborted();
|
|
130
149
|
const child = (dependencies.spawnProcess ?? spawnStdioProcess)({
|
|
@@ -138,15 +157,15 @@ export async function startAcpSession(launch, input, dependencies = {}) {
|
|
|
138
157
|
});
|
|
139
158
|
let sessionId;
|
|
140
159
|
let turn;
|
|
141
|
-
const connection = createAcpClient((notification) => {
|
|
160
|
+
const connection = createAcpClient(acp, (notification) => {
|
|
142
161
|
if (notification.sessionId === sessionId)
|
|
143
162
|
turn.update(notification.update);
|
|
144
163
|
}).connect(acp.ndJsonStream(Writable.toWeb(child.input), Readable.toWeb(child.output)));
|
|
145
|
-
turn = createAcpTurn(connection, dependencies.interpretTool);
|
|
164
|
+
turn = createAcpTurn(acp, connection, dependencies.interpretTool);
|
|
146
165
|
try {
|
|
147
|
-
sessionId = await abortable(establishSession(connection.agent, input), signal);
|
|
166
|
+
sessionId = await abortable(establishSession(acp, connection.agent, input, dependencies), signal);
|
|
148
167
|
turn.events.emit({ type: "session", coordinate: { sessionId } });
|
|
149
|
-
const session = beginAcpPrompt(connection.agent, child, turn, sessionId, input);
|
|
168
|
+
const session = beginAcpPrompt({ acp, agent: connection.agent }, child, turn, sessionId, input);
|
|
150
169
|
return { session, agent: connection.agent, sessionId, open: turn.open };
|
|
151
170
|
}
|
|
152
171
|
catch (error) {
|
|
@@ -155,7 +174,7 @@ export async function startAcpSession(launch, input, dependencies = {}) {
|
|
|
155
174
|
await child.close(true);
|
|
156
175
|
}
|
|
157
176
|
catch (cleanup) {
|
|
158
|
-
throw new Error(`${diagnostic(error)}; ACP cleanup failed: ${diagnostic(cleanup)}`, { cause: error });
|
|
177
|
+
throw new Error(`${diagnostic(acp, error)}; ACP cleanup failed: ${diagnostic(acp, cleanup)}`, { cause: error });
|
|
159
178
|
}
|
|
160
179
|
turn.events.end();
|
|
161
180
|
throw error;
|
|
@@ -1,12 +1,5 @@
|
|
|
1
1
|
import type { ProviderAdapter } from "../../provider.js";
|
|
2
2
|
import type { ProviderExecution } from "../../provider-recipe.js";
|
|
3
3
|
import { type AcpDependencies } from "./core.js";
|
|
4
|
-
export type AcpExecutionConfig
|
|
5
|
-
argvBefore: readonly string[];
|
|
6
|
-
argvAfter: readonly string[];
|
|
7
|
-
modelArg?: string;
|
|
8
|
-
effortArg?: string;
|
|
9
|
-
systemPromptArg?: string;
|
|
10
|
-
}>;
|
|
11
|
-
export declare function decodeAcpConfig(value: unknown): AcpExecutionConfig;
|
|
4
|
+
export { decodeAcpConfig, type AcpExecutionConfig } from "./config.js";
|
|
12
5
|
export declare function createAcpProvider(execution: ProviderExecution, dependencies?: AcpDependencies): ProviderAdapter;
|
|
@@ -1,41 +1,6 @@
|
|
|
1
|
+
import { decodeAcpConfig } from "./config.js";
|
|
1
2
|
import { startAcpSession } from "./core.js";
|
|
2
|
-
|
|
3
|
-
const selected = value[key];
|
|
4
|
-
if (selected === undefined)
|
|
5
|
-
return undefined;
|
|
6
|
-
if (typeof selected !== "string" || selected.trim().length === 0) {
|
|
7
|
-
throw new TypeError(`ACP provider config ${key} must be a nonblank string`);
|
|
8
|
-
}
|
|
9
|
-
return selected;
|
|
10
|
-
}
|
|
11
|
-
export function decodeAcpConfig(value) {
|
|
12
|
-
if (value === null || typeof value !== "object" || Array.isArray(value)) {
|
|
13
|
-
throw new TypeError("ACP provider config must be an object");
|
|
14
|
-
}
|
|
15
|
-
const config = value;
|
|
16
|
-
const unknown = Object.keys(config)
|
|
17
|
-
.find((key) => !["argvBefore", "argvAfter", "effortArg", "modelArg", "systemPromptArg"].includes(key));
|
|
18
|
-
if (unknown !== undefined)
|
|
19
|
-
throw new TypeError(`ACP provider config has unknown field ${unknown}`);
|
|
20
|
-
if (!Array.isArray(config.argvBefore)
|
|
21
|
-
|| config.argvBefore.some((arg) => typeof arg !== "string" || arg.trim().length === 0)) {
|
|
22
|
-
throw new TypeError("ACP provider config argvBefore must be an array of nonblank strings");
|
|
23
|
-
}
|
|
24
|
-
if (!Array.isArray(config.argvAfter)
|
|
25
|
-
|| config.argvAfter.some((arg) => typeof arg !== "string" || arg.trim().length === 0)) {
|
|
26
|
-
throw new TypeError("ACP provider config argvAfter must be an array of nonblank strings");
|
|
27
|
-
}
|
|
28
|
-
const modelArg = argumentName(config, "modelArg");
|
|
29
|
-
const effortArg = argumentName(config, "effortArg");
|
|
30
|
-
const systemPromptArg = argumentName(config, "systemPromptArg");
|
|
31
|
-
return Object.freeze({
|
|
32
|
-
argvBefore: Object.freeze([...config.argvBefore]),
|
|
33
|
-
argvAfter: Object.freeze([...config.argvAfter]),
|
|
34
|
-
...(modelArg === undefined ? {} : { modelArg }),
|
|
35
|
-
...(effortArg === undefined ? {} : { effortArg }),
|
|
36
|
-
...(systemPromptArg === undefined ? {} : { systemPromptArg }),
|
|
37
|
-
});
|
|
38
|
-
}
|
|
3
|
+
export { decodeAcpConfig } from "./config.js";
|
|
39
4
|
function optionAdmission(options, config) {
|
|
40
5
|
if (options.network !== undefined) {
|
|
41
6
|
return { kind: "refused", diagnostic: "ACP provider does not support the network option" };
|
|
@@ -46,8 +11,14 @@ function optionAdmission(options, config) {
|
|
|
46
11
|
if (options.effort !== undefined && config.effortArg === undefined) {
|
|
47
12
|
return { kind: "refused", diagnostic: "ACP provider has no effort argument mapping" };
|
|
48
13
|
}
|
|
49
|
-
if (options.systemPrompt !== undefined && options.systemPrompt.length > 0
|
|
50
|
-
|
|
14
|
+
if (options.systemPrompt !== undefined && options.systemPrompt.length > 0) {
|
|
15
|
+
if (config.systemPromptArg === undefined) {
|
|
16
|
+
return { kind: "refused", diagnostic: "ACP provider has no systemPrompt argument mapping" };
|
|
17
|
+
}
|
|
18
|
+
if (options.systemPromptMode !== undefined
|
|
19
|
+
&& options.systemPromptMode !== (config.systemPromptMode ?? "replace")) {
|
|
20
|
+
return { kind: "refused", diagnostic: "ACP provider systemPromptMode does not match the configured argument mode" };
|
|
21
|
+
}
|
|
51
22
|
}
|
|
52
23
|
return {
|
|
53
24
|
kind: "admitted",
|
|
@@ -19,7 +19,7 @@ export type ClaudeSdk = Readonly<{
|
|
|
19
19
|
sessionId: string;
|
|
20
20
|
}>>;
|
|
21
21
|
}>;
|
|
22
|
-
export declare function createClaudeProvider(
|
|
22
|
+
export declare function createClaudeProvider(loadOrExecution?: (() => Promise<ClaudeSdk>) | ClaudeExecution, execution?: ClaudeExecution): ProviderAdapter;
|
|
23
23
|
export declare const claudeProvider: Readonly<{
|
|
24
24
|
admitOptions(options: ProviderOptions): import("../../provider.js").ProviderOptionAdmission;
|
|
25
25
|
fork?(input: Readonly<{
|
|
@@ -74,7 +74,9 @@ function claudeQueryOptions(input, execution, abortController) {
|
|
|
74
74
|
...(input.options.model === undefined ? {} : { model: input.options.model }),
|
|
75
75
|
...(input.options.effort === undefined ? {} : { effort: input.options.effort }),
|
|
76
76
|
...(input.options.systemPrompt === undefined || input.options.systemPrompt.length === 0 ? {} : {
|
|
77
|
-
systemPrompt:
|
|
77
|
+
systemPrompt: input.options.systemPromptMode === "replace"
|
|
78
|
+
? input.options.systemPrompt
|
|
79
|
+
: { type: "preset", preset: "claude_code", append: input.options.systemPrompt },
|
|
78
80
|
}),
|
|
79
81
|
...(input.session.kind === "fresh" ? {} : { resume: claudeSessionId(input.session.coordinate) }),
|
|
80
82
|
};
|
|
@@ -269,15 +271,22 @@ async function driveClaude(load, execution, drive) {
|
|
|
269
271
|
},
|
|
270
272
|
async abort() {
|
|
271
273
|
shutDown(new Error("Claude query aborted"));
|
|
274
|
+
await observed.completion;
|
|
275
|
+
},
|
|
276
|
+
async forceDispose() {
|
|
277
|
+
shutDown(new Error("Claude query force-disposed"));
|
|
278
|
+
await observed.completion;
|
|
272
279
|
},
|
|
273
280
|
};
|
|
274
281
|
}
|
|
275
|
-
export function createClaudeProvider(
|
|
282
|
+
export function createClaudeProvider(loadOrExecution = async () => await import("@anthropic-ai/claude-agent-sdk"), execution = {}) {
|
|
283
|
+
const load = typeof loadOrExecution === "function" ? loadOrExecution : async () => await import("@anthropic-ai/claude-agent-sdk");
|
|
284
|
+
const selectedExecution = typeof loadOrExecution === "function" ? execution : loadOrExecution;
|
|
276
285
|
return {
|
|
277
286
|
admitOptions: admitClaudeOptions,
|
|
278
|
-
fork: (input) => forkClaude(load,
|
|
279
|
-
start: (input) => driveClaude(load,
|
|
280
|
-
resume: (input) => driveClaude(load,
|
|
287
|
+
fork: (input) => forkClaude(load, selectedExecution, input),
|
|
288
|
+
start: (input) => driveClaude(load, selectedExecution, input),
|
|
289
|
+
resume: (input) => driveClaude(load, selectedExecution, input),
|
|
281
290
|
};
|
|
282
291
|
}
|
|
283
292
|
export const claudeProvider = createClaudeProvider(async () => await import("@anthropic-ai/claude-agent-sdk"));
|
|
@@ -66,7 +66,10 @@ async function admitTurn(server, input, state, events, config) {
|
|
|
66
66
|
...(config === undefined ? {} : { config }),
|
|
67
67
|
...(input.options.model === undefined ? {} : { model: input.options.model }),
|
|
68
68
|
...(input.options.systemPrompt === undefined || input.options.systemPrompt.length === 0
|
|
69
|
-
? {}
|
|
69
|
+
? {}
|
|
70
|
+
: input.options.systemPromptMode === "replace"
|
|
71
|
+
? { baseInstructions: input.options.systemPrompt }
|
|
72
|
+
: { developerInstructions: input.options.systemPrompt }),
|
|
70
73
|
};
|
|
71
74
|
if (input.session.kind === "fresh") {
|
|
72
75
|
state.threadId = threadId(await server.request("thread/start", threadParams));
|
|
@@ -182,6 +185,11 @@ async function startCodex(execution, input) {
|
|
|
182
185
|
events,
|
|
183
186
|
completion,
|
|
184
187
|
abort: () => abortTurn(server, state, finish),
|
|
188
|
+
forceDispose: async () => {
|
|
189
|
+
if (!state.settled)
|
|
190
|
+
settle({ kind: "failed", diagnostic: "codex app-server force-disposed" });
|
|
191
|
+
await server.close(true);
|
|
192
|
+
},
|
|
185
193
|
tell: (tell) => steerTurn(server, state, tell),
|
|
186
194
|
};
|
|
187
195
|
}
|
|
@@ -10,7 +10,7 @@ function object(value) {
|
|
|
10
10
|
}
|
|
11
11
|
function nativeName(update) {
|
|
12
12
|
const wire = update;
|
|
13
|
-
return nonblank(update.name) ?? nonblank(wire.toolName);
|
|
13
|
+
return nonblank(update.name) ?? nonblank(update.title) ?? nonblank(wire.toolName);
|
|
14
14
|
}
|
|
15
15
|
function readCall(name, input) {
|
|
16
16
|
if (name !== "read_file" && name !== "hashline_read")
|
|
@@ -71,7 +71,9 @@ function optionAdmission(options) {
|
|
|
71
71
|
if (options.network !== undefined) {
|
|
72
72
|
return { kind: "refused", diagnostic: "Grok Build does not support the network option" };
|
|
73
73
|
}
|
|
74
|
-
if (options.systemPrompt !== undefined
|
|
74
|
+
if (options.systemPrompt !== undefined
|
|
75
|
+
&& options.systemPrompt.length > 0
|
|
76
|
+
&& options.systemPromptMode === undefined) {
|
|
75
77
|
return { kind: "refused", diagnostic: "Grok Build does not support the systemPrompt option" };
|
|
76
78
|
}
|
|
77
79
|
return {
|
|
@@ -85,6 +87,17 @@ function optionAdmission(options) {
|
|
|
85
87
|
}),
|
|
86
88
|
};
|
|
87
89
|
}
|
|
90
|
+
function grokSessionMeta(options) {
|
|
91
|
+
if (options.systemPrompt === undefined || options.systemPrompt.length === 0)
|
|
92
|
+
return {};
|
|
93
|
+
if (options.systemPromptMode === "append")
|
|
94
|
+
return { freshSessionMeta: { rules: options.systemPrompt } };
|
|
95
|
+
if (options.systemPromptMode === "replace") {
|
|
96
|
+
const meta = { systemPromptOverride: options.systemPrompt };
|
|
97
|
+
return { freshSessionMeta: meta, loadSessionMeta: meta };
|
|
98
|
+
}
|
|
99
|
+
return {};
|
|
100
|
+
}
|
|
88
101
|
function argv(execution, options) {
|
|
89
102
|
if (execution.executable === undefined)
|
|
90
103
|
throw new Error("Grok Build provider execution requires executable");
|
|
@@ -129,6 +142,7 @@ export function createGrokBuildProvider(execution, dependencies = {}) {
|
|
|
129
142
|
return withInterject(await startAcpSession(launch, input, {
|
|
130
143
|
...dependencies,
|
|
131
144
|
interpretTool: interpretGrokTool,
|
|
145
|
+
...grokSessionMeta(input.options),
|
|
132
146
|
}));
|
|
133
147
|
};
|
|
134
148
|
return {
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import type { ProviderAdapter } from "../provider.js";
|
|
2
2
|
import { type ProviderExecution } from "../provider-recipe.js";
|
|
3
|
-
export declare function
|
|
3
|
+
export declare function decodeProviderExecution(input: unknown): ProviderExecution;
|
|
4
|
+
export declare function resolveProviderExecution(input: unknown): Promise<Readonly<{
|
|
4
5
|
execution: ProviderExecution;
|
|
5
6
|
adapter: ProviderAdapter;
|
|
6
|
-
}
|
|
7
|
+
}>>;
|