@astrosheep/keiyaku 4.0.5 → 4.0.6
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 +11 -6
- 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.d.ts +3 -1
- package/build/src/akuma/providers/acp/config.d.ts +8 -0
- package/build/src/akuma/providers/acp/config.js +37 -0
- package/build/src/akuma/providers/acp/core.js +6 -0
- package/build/src/akuma/providers/acp/index.d.ts +1 -8
- package/build/src/akuma/providers/acp/index.js +2 -37
- package/build/src/akuma/providers/claude/index.js +5 -0
- package/build/src/akuma/providers/codex-app-server/index.js +5 -0
- package/build/src/akuma/providers/grok-build/index.js +1 -1
- package/build/src/akuma/providers/index.d.ts +3 -2
- package/build/src/akuma/providers/index.js +36 -36
- package/build/src/akuma/providers/opencode-sdk/index.js +6 -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 +27 -11
- 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/cli/accepted.js +4 -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 +25 -14
- package/build/src/cli/commands/contract.js +23 -17
- 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 +15 -0
- package/build/src/cli/commands/task-invoke.js +57 -26
- 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 +22 -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 +55 -40
- package/build/src/cli/render/contract.js +49 -48
- package/build/src/cli/render/kanshi.js +15 -5
- package/build/src/cli/render/nuke.d.ts +3 -0
- package/build/src/cli/render/nuke.js +8 -0
- package/build/src/cli/render/refusal.d.ts +2 -1
- package/build/src/cli/render/refusal.js +31 -0
- package/build/src/cli/render/text.js +6 -1
- package/build/src/cli/result.d.ts +21 -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 +28 -12
- package/build/src/contract-worktree.js +129 -63
- 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 +97 -0
- package/build/src/git/process.d.ts +2 -0
- package/build/src/git/reconcile.d.ts +2 -0
- package/build/src/git/reconcile.js +33 -2
- 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 +1 -0
- package/build/src/git/tender.js +2 -2
- package/build/src/git/workspace.d.ts +3 -0
- package/build/src/kanshi/read.js +5 -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/contract.d.ts +6 -4
- package/build/src/library/contract.js +56 -34
- package/build/src/library/delivery.d.ts +3 -1
- 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 +3 -0
- 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 +14 -2
- package/build/src/protocol/deliver.js +107 -6
- package/build/src/protocol/intent.d.ts +1 -0
- package/build/src/protocol/intent.js +7 -1
- package/build/src/protocol/operations.d.ts +30 -3
- 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/fence.d.ts +2 -0
- package/build/src/settlement/fence.js +24 -0
- package/build/src/settlement/settle.js +1 -2
- 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/index.d.ts +6 -5
- package/build/src/task/index.js +15 -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 +12 -6
- package/build/src/task/operations.js +22 -46
- package/build/src/task/store.d.ts +1 -0
- package/build/src/task/store.js +91 -1
- 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
|
+
}
|
|
@@ -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,37 @@
|
|
|
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"].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
|
+
return Object.freeze({
|
|
31
|
+
argvBefore: Object.freeze([...config.argvBefore]),
|
|
32
|
+
argvAfter: Object.freeze([...config.argvAfter]),
|
|
33
|
+
...(modelArg === undefined ? {} : { modelArg }),
|
|
34
|
+
...(effortArg === undefined ? {} : { effortArg }),
|
|
35
|
+
...(systemPromptArg === undefined ? {} : { systemPromptArg }),
|
|
36
|
+
});
|
|
37
|
+
}
|
|
@@ -122,6 +122,12 @@ function beginAcpPrompt(agent, child, turn, sessionId, input) {
|
|
|
122
122
|
else
|
|
123
123
|
await turn.finish(result, () => child.close(true));
|
|
124
124
|
},
|
|
125
|
+
forceDispose: async () => {
|
|
126
|
+
if (!turn.open())
|
|
127
|
+
await child.close(true);
|
|
128
|
+
else
|
|
129
|
+
await turn.finish({ kind: "failed", diagnostic: "ACP turn force-disposed" }, () => child.close(true));
|
|
130
|
+
},
|
|
125
131
|
};
|
|
126
132
|
}
|
|
127
133
|
export async function startAcpSession(launch, input, dependencies = {}) {
|
|
@@ -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" };
|
|
@@ -269,6 +269,11 @@ async function driveClaude(load, execution, drive) {
|
|
|
269
269
|
},
|
|
270
270
|
async abort() {
|
|
271
271
|
shutDown(new Error("Claude query aborted"));
|
|
272
|
+
await observed.completion;
|
|
273
|
+
},
|
|
274
|
+
async forceDispose() {
|
|
275
|
+
shutDown(new Error("Claude query force-disposed"));
|
|
276
|
+
await observed.completion;
|
|
272
277
|
},
|
|
273
278
|
};
|
|
274
279
|
}
|
|
@@ -182,6 +182,11 @@ async function startCodex(execution, input) {
|
|
|
182
182
|
events,
|
|
183
183
|
completion,
|
|
184
184
|
abort: () => abortTurn(server, state, finish),
|
|
185
|
+
forceDispose: async () => {
|
|
186
|
+
if (!state.settled)
|
|
187
|
+
settle({ kind: "failed", diagnostic: "codex app-server force-disposed" });
|
|
188
|
+
await server.close(true);
|
|
189
|
+
},
|
|
185
190
|
tell: (tell) => steerTurn(server, state, tell),
|
|
186
191
|
};
|
|
187
192
|
}
|
|
@@ -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")
|
|
@@ -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
|
+
}>>;
|
|
@@ -1,50 +1,50 @@
|
|
|
1
1
|
import { decodeProviderRecipe } from "../provider-recipe.js";
|
|
2
|
-
import {
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
2
|
+
import { decodeAcpConfig } from "./acp/config.js";
|
|
3
|
+
export function decodeProviderExecution(input) {
|
|
4
|
+
const decoded = decodeProviderRecipe(input);
|
|
5
|
+
if ((decoded.kind === "claude-agent-sdk" || decoded.kind === "opencode-sdk") && decoded.config !== undefined) {
|
|
6
|
+
throw new TypeError(`provider execution config is unsupported by ${decoded.kind}`);
|
|
7
|
+
}
|
|
8
|
+
if (decoded.kind === "pi") {
|
|
9
|
+
if (decoded.executable !== undefined || decoded.config !== undefined) {
|
|
10
|
+
throw new TypeError("Pi provider does not support executable or config");
|
|
11
|
+
}
|
|
12
|
+
if (decoded.env !== undefined && Object.keys(decoded.env).length > 0) {
|
|
13
|
+
throw new TypeError("env injection not supported for provider pi");
|
|
14
|
+
}
|
|
15
|
+
}
|
|
16
|
+
if (decoded.kind === "grok-build" && decoded.config !== undefined) {
|
|
17
|
+
throw new TypeError("Grok Build does not support execution config");
|
|
18
|
+
}
|
|
19
|
+
const config = decoded.kind === "acp" ? decodeAcpConfig(decoded.config) : decoded.config;
|
|
20
|
+
return Object.freeze({
|
|
21
|
+
name: decoded.name,
|
|
22
|
+
kind: decoded.kind,
|
|
23
|
+
...(decoded.executable === undefined ? {} : { executable: decoded.executable }),
|
|
24
|
+
...(config === undefined ? {} : { config }),
|
|
25
|
+
...(decoded.env === undefined ? {} : { env: decoded.env }),
|
|
26
|
+
});
|
|
20
27
|
}
|
|
21
|
-
function adapterFor(execution) {
|
|
28
|
+
async function adapterFor(execution) {
|
|
22
29
|
if (execution.kind === "acp")
|
|
23
|
-
return createAcpProvider(execution);
|
|
30
|
+
return (await import("./acp/index.js")).createAcpProvider(execution);
|
|
24
31
|
if (execution.kind === "claude-agent-sdk") {
|
|
32
|
+
const { claudeProvider, createClaudeProvider } = await import("./claude/index.js");
|
|
25
33
|
return execution.executable === undefined && execution.env === undefined
|
|
26
|
-
?
|
|
34
|
+
? claudeProvider
|
|
27
35
|
: createClaudeProvider(async () => await import("@anthropic-ai/claude-agent-sdk"), execution);
|
|
28
36
|
}
|
|
29
37
|
if (execution.kind === "codex-app-server")
|
|
30
|
-
return createCodexAppServerProvider(execution);
|
|
38
|
+
return (await import("./codex-app-server/index.js")).createCodexAppServerProvider(execution);
|
|
31
39
|
if (execution.kind === "grok-build")
|
|
32
|
-
return createGrokBuildProvider(execution);
|
|
40
|
+
return (await import("./grok-build/index.js")).createGrokBuildProvider(execution);
|
|
33
41
|
if (execution.kind === "opencode-sdk")
|
|
34
|
-
return createOpencodeProvider(execution);
|
|
42
|
+
return (await import("./opencode-sdk/index.js")).createOpencodeProvider(execution);
|
|
35
43
|
if (execution.kind === "pi")
|
|
36
|
-
return createPiProvider(execution);
|
|
44
|
+
return (await import("./pi/index.js")).createPiProvider(execution);
|
|
37
45
|
throw new TypeError(`unknown Akuma provider kind ${execution.kind}`);
|
|
38
46
|
}
|
|
39
|
-
export function resolveProviderExecution(input) {
|
|
40
|
-
const
|
|
41
|
-
|
|
42
|
-
const execution = Object.freeze({
|
|
43
|
-
name: decoded.name,
|
|
44
|
-
kind: decoded.kind,
|
|
45
|
-
...(decoded.executable === undefined ? {} : { executable: decoded.executable }),
|
|
46
|
-
...(config === undefined ? {} : { config }),
|
|
47
|
-
...(decoded.env === undefined ? {} : { env: decoded.env }),
|
|
48
|
-
});
|
|
49
|
-
return Object.freeze({ execution, adapter: adapterFor(execution) });
|
|
47
|
+
export async function resolveProviderExecution(input) {
|
|
48
|
+
const execution = decodeProviderExecution(input);
|
|
49
|
+
return Object.freeze({ execution, adapter: await adapterFor(execution) });
|
|
50
50
|
}
|
|
@@ -195,6 +195,11 @@ function terminalSettlement(events, close) {
|
|
|
195
195
|
});
|
|
196
196
|
return { completion, finish };
|
|
197
197
|
}
|
|
198
|
+
async function forceDisposeOpencode(abortController, close, finish) {
|
|
199
|
+
abortController.abort();
|
|
200
|
+
await close();
|
|
201
|
+
await finish({ kind: "failed", diagnostic: "OpenCode session force-disposed" });
|
|
202
|
+
}
|
|
198
203
|
async function drive(execution, input, loader) {
|
|
199
204
|
admit(input.options);
|
|
200
205
|
const signal = input.signal ?? new AbortController().signal;
|
|
@@ -256,6 +261,7 @@ async function drive(execution, input, loader) {
|
|
|
256
261
|
void session.abort({ path: { id: sessionId }, query: { directory: input.cwd }, throwOnError: true }).catch(() => undefined);
|
|
257
262
|
await finish({ kind: "failed", diagnostic: "OpenCode session interrupted" });
|
|
258
263
|
},
|
|
264
|
+
forceDispose: () => forceDisposeOpencode(abortController, closeOnce, finish),
|
|
259
265
|
};
|
|
260
266
|
}
|
|
261
267
|
catch (error) {
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import type { OpencodeClient } from "@opencode-ai/sdk";
|
|
2
2
|
import type { ResumeCoordinate } from "../../heart/index.js";
|
|
3
3
|
import type { ProviderExecution } from "../../provider-recipe.js";
|
|
4
4
|
export type OpencodeSdkSession = Pick<OpencodeClient["session"], "create" | "get" | "fork" | "abort" | "promptAsync" | "messages">;
|
|
@@ -1,7 +1,6 @@
|
|
|
1
1
|
import net from "node:net";
|
|
2
2
|
import { abortable } from "../../abort.js";
|
|
3
3
|
import { spawnDetachedProcess } from "../../../runtime/proc/run.js";
|
|
4
|
-
import { createOpencodeClient } from "@opencode-ai/sdk";
|
|
5
4
|
export const OPENCODE_SDK_PROVIDER = "opencode-sdk";
|
|
6
5
|
export function parseModel(model) {
|
|
7
6
|
const slash = model.indexOf("/");
|
|
@@ -50,6 +49,7 @@ export async function loadOpencode(execution, cwd, signal, loader) {
|
|
|
50
49
|
env: { ...process.env, ...execution.env },
|
|
51
50
|
log: `${cwd}/.opencode.log`,
|
|
52
51
|
});
|
|
52
|
+
const { createOpencodeClient } = await import("@opencode-ai/sdk");
|
|
53
53
|
const client = createOpencodeClient({ baseUrl: `http://127.0.0.1:${port}`, directory: cwd });
|
|
54
54
|
try {
|
|
55
55
|
await waitReady(client, cwd, signal);
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { createAgentSession, DefaultResourceLoader, getAgentDir, ModelRuntime, SessionManager,
|
|
1
|
+
import type { createAgentSession, DefaultResourceLoader, getAgentDir, ModelRuntime, SessionManager, CreateAgentSessionOptions } from "@earendil-works/pi-coding-agent";
|
|
2
2
|
import type { ProviderExecution } from "../../provider-recipe.js";
|
|
3
3
|
import { type ProviderAdapter } from "../../provider.js";
|
|
4
4
|
export type PiSdk = Readonly<{
|
|
@@ -1,4 +1,3 @@
|
|
|
1
|
-
import { createAgentSession, createBashToolDefinition, DefaultResourceLoader, getAgentDir, ModelRuntime, SessionManager, } from "@earendil-works/pi-coding-agent";
|
|
2
1
|
import { abortable } from "../../abort.js";
|
|
3
2
|
import { AKUMA_REQUESTS_ENV, AgentEventChannel, } from "../../provider.js";
|
|
4
3
|
import { piTerminalFailure, translatePiEvent } from "./events.js";
|
|
@@ -41,6 +40,14 @@ async function piCreateOptions(sdk, input) {
|
|
|
41
40
|
: "sessionFile" in input.session.coordinate
|
|
42
41
|
? sdk.SessionManager.open(input.session.coordinate.sessionFile)
|
|
43
42
|
: (() => { throw new Error("Pi resume requires sessionFile"); })();
|
|
43
|
+
const customTools = input.requests === undefined || input.options.readonly === true
|
|
44
|
+
? undefined
|
|
45
|
+
: [(await import("@earendil-works/pi-coding-agent")).createBashToolDefinition(input.cwd, {
|
|
46
|
+
spawnHook: (context) => ({
|
|
47
|
+
...context,
|
|
48
|
+
env: { ...context.env, [AKUMA_REQUESTS_ENV]: input.requests.dir },
|
|
49
|
+
}),
|
|
50
|
+
})];
|
|
44
51
|
return {
|
|
45
52
|
cwd: input.cwd,
|
|
46
53
|
sessionManager,
|
|
@@ -48,20 +55,19 @@ async function piCreateOptions(sdk, input) {
|
|
|
48
55
|
...(resourceLoader === undefined ? {} : { resourceLoader }),
|
|
49
56
|
...(input.options.effort === undefined ? {} : { thinkingLevel: input.options.effort }),
|
|
50
57
|
...(input.options.readonly === undefined ? {} : { tools: ["read", "grep", "find", "ls"] }),
|
|
51
|
-
...(
|
|
52
|
-
? {}
|
|
53
|
-
: { customTools: [createBashToolDefinition(input.cwd, {
|
|
54
|
-
spawnHook: (context) => ({
|
|
55
|
-
...context,
|
|
56
|
-
env: { ...context.env, [AKUMA_REQUESTS_ENV]: input.requests.dir },
|
|
57
|
-
}),
|
|
58
|
-
})] }),
|
|
58
|
+
...(customTools === undefined ? {} : { customTools }),
|
|
59
59
|
};
|
|
60
60
|
}
|
|
61
61
|
async function createPiSession(sdk, input, signal) {
|
|
62
62
|
const setup = piCreateOptions(sdk, input).then(async (options) => await sdk.createAgentSession(options));
|
|
63
63
|
return await abortable(setup, signal, (created) => created.session.dispose());
|
|
64
64
|
}
|
|
65
|
+
function forceDisposePi(dispose, settle, setAborting) {
|
|
66
|
+
setAborting();
|
|
67
|
+
dispose();
|
|
68
|
+
settle({ kind: "failed", diagnostic: "Pi session force-disposed" });
|
|
69
|
+
return Promise.resolve();
|
|
70
|
+
}
|
|
65
71
|
async function drivePi(sdk, input, signal) {
|
|
66
72
|
const created = await createPiSession(sdk, input, signal);
|
|
67
73
|
const native = created.session;
|
|
@@ -155,6 +161,7 @@ async function drivePi(sdk, input, signal) {
|
|
|
155
161
|
})();
|
|
156
162
|
return abortRequest;
|
|
157
163
|
},
|
|
164
|
+
forceDispose: () => forceDisposePi(dispose, settle, () => { aborting = true; }),
|
|
158
165
|
};
|
|
159
166
|
}
|
|
160
167
|
function piSessionFile(coordinate) {
|
|
@@ -170,8 +177,17 @@ async function forkPi(sdk, input) {
|
|
|
170
177
|
throw new Error("Pi fork did not create a distinct child session file");
|
|
171
178
|
return { session: { sessionFile: child } };
|
|
172
179
|
}
|
|
173
|
-
|
|
174
|
-
|
|
180
|
+
async function loadPiSdk() {
|
|
181
|
+
const sdk = await import("@earendil-works/pi-coding-agent");
|
|
182
|
+
return {
|
|
183
|
+
createAgentSession: sdk.createAgentSession,
|
|
184
|
+
DefaultResourceLoader: sdk.DefaultResourceLoader,
|
|
185
|
+
getAgentDir: sdk.getAgentDir,
|
|
186
|
+
ModelRuntime: sdk.ModelRuntime,
|
|
187
|
+
SessionManager: sdk.SessionManager,
|
|
188
|
+
};
|
|
189
|
+
}
|
|
190
|
+
export function createPiProvider(execution = { name: "pi", kind: "pi" }, load = loadPiSdk) {
|
|
175
191
|
if (execution.executable !== undefined || execution.config !== undefined) {
|
|
176
192
|
throw new TypeError("Pi provider does not support executable or config");
|
|
177
193
|
}
|
|
@@ -222,6 +222,7 @@ async function executeDeliver(input, request) {
|
|
|
222
222
|
contractId: request.contractId,
|
|
223
223
|
...(request.message === undefined ? {} : { message: request.message }),
|
|
224
224
|
includeDirty: request.includeDirty,
|
|
225
|
+
materializeConflict: request.materializeConflict,
|
|
225
226
|
requester: request.requester,
|
|
226
227
|
signal: input.signal,
|
|
227
228
|
});
|
|
@@ -303,12 +304,8 @@ async function serveUpstream(input) {
|
|
|
303
304
|
try {
|
|
304
305
|
const served = await execute(input, request);
|
|
305
306
|
outcome = { kind: "returned", result: served.result };
|
|
306
|
-
if (served.service
|
|
307
|
-
|
|
308
|
-
await projectReceipt(input.directory, fact);
|
|
309
|
-
return;
|
|
310
|
-
}
|
|
311
|
-
service = served.service;
|
|
307
|
+
if (served.service !== undefined)
|
|
308
|
+
service = served.service;
|
|
312
309
|
}
|
|
313
310
|
catch (error) {
|
|
314
311
|
outcome = { kind: "failed", failure: failure(error) };
|
|
@@ -368,37 +365,40 @@ export class BodyRequestPump {
|
|
|
368
365
|
return new BodyRequestPump(input);
|
|
369
366
|
}
|
|
370
367
|
async run() {
|
|
371
|
-
|
|
372
|
-
|
|
373
|
-
|
|
374
|
-
|
|
375
|
-
|
|
376
|
-
|
|
377
|
-
|
|
378
|
-
|
|
379
|
-
|
|
380
|
-
|
|
381
|
-
|
|
382
|
-
directory: this.directory,
|
|
383
|
-
claim,
|
|
384
|
-
...this.input,
|
|
385
|
-
signal: this.executionSignal.signal,
|
|
386
|
-
admissionOpen: () => !this.admissionClosed,
|
|
387
|
-
});
|
|
388
|
-
}
|
|
368
|
+
while (!this.closeSignal.signal.aborted) {
|
|
369
|
+
for (const name of await requestFiles(this.directory)) {
|
|
370
|
+
if (this.closeSignal.signal.aborted)
|
|
371
|
+
return;
|
|
372
|
+
const id = name.slice(0, -".request.json".length);
|
|
373
|
+
if (this.handled.has(id))
|
|
374
|
+
continue;
|
|
375
|
+
const path = join(this.directory, name);
|
|
376
|
+
const claim = decodeClaim(await readFile(path, "utf8"), id);
|
|
377
|
+
if (claim === null) {
|
|
378
|
+
await rm(path, { force: true });
|
|
389
379
|
this.handled.add(id);
|
|
380
|
+
continue;
|
|
390
381
|
}
|
|
391
|
-
|
|
392
|
-
await
|
|
382
|
+
if (!this.admissionClosed) {
|
|
383
|
+
await serve({
|
|
384
|
+
directory: this.directory,
|
|
385
|
+
claim,
|
|
386
|
+
...this.input,
|
|
387
|
+
signal: this.executionSignal.signal,
|
|
388
|
+
admissionOpen: () => !this.admissionClosed,
|
|
389
|
+
});
|
|
393
390
|
}
|
|
394
|
-
|
|
391
|
+
this.handled.add(id);
|
|
392
|
+
}
|
|
393
|
+
try {
|
|
394
|
+
await abortableDelay(POLL_MS, this.closeSignal.signal);
|
|
395
|
+
}
|
|
396
|
+
catch (error) {
|
|
397
|
+
if (this.closeSignal.signal.aborted)
|
|
395
398
|
return;
|
|
396
|
-
|
|
399
|
+
throw error;
|
|
397
400
|
}
|
|
398
401
|
}
|
|
399
|
-
catch {
|
|
400
|
-
// Request transport loss is recovered from Heart on the next Body.
|
|
401
|
-
}
|
|
402
402
|
}
|
|
403
403
|
async close() {
|
|
404
404
|
this.stopAdmission();
|