@astrosheep/keiyaku 4.0.0 → 4.0.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +133 -8
- package/build/integrations/marketplace/plugins/keiyaku/.claude-plugin/plugin.json +2 -2
- package/build/integrations/marketplace/plugins/keiyaku/.codex-plugin/plugin.json +4 -4
- package/build/integrations/marketplace/plugins/keiyaku/opencode.js +2 -2
- package/build/integrations/marketplace/plugins/keiyaku/package.json +2 -2
- package/build/integrations/marketplace/plugins/keiyaku/skills/keiyaku/SKILL.md +13 -5
- package/build/integrations/marketplace/plugins/keiyaku/skills/keiyaku-akuma/SKILL.md +91 -32
- package/build/integrations/marketplace/plugins/keiyaku/skills/keiyaku-bind/SKILL.md +134 -0
- package/build/integrations/marketplace/plugins/keiyaku/skills/keiyaku-bind/agents/openai.yaml +4 -0
- package/build/integrations/marketplace/plugins/keiyaku/skills/keiyaku-task/SKILL.md +41 -6
- package/build/integrations/marketplace/plugins/keiyaku/skills/keiyaku-workflow/SKILL.md +262 -46
- package/build/src/akuma/abort.d.ts +2 -0
- package/build/src/akuma/abort.js +50 -0
- package/build/src/akuma/akuma.d.ts +50 -33
- package/build/src/akuma/akuma.js +175 -127
- package/build/src/akuma/archetype.d.ts +14 -5
- package/build/src/akuma/archetype.js +85 -39
- package/build/src/akuma/body.d.ts +3 -5
- package/build/src/akuma/body.js +490 -183
- package/build/src/akuma/coordinate.d.ts +9 -0
- package/build/src/akuma/coordinate.js +22 -0
- package/build/src/akuma/heart/facts.d.ts +88 -45
- package/build/src/akuma/heart/facts.js +19 -8
- package/build/src/akuma/heart/index.d.ts +64 -55
- package/build/src/akuma/heart/index.js +172 -252
- package/build/src/akuma/heart/rows.d.ts +38 -83
- package/build/src/akuma/heart/rows.js +117 -155
- package/build/src/akuma/heart/schema.d.ts +1 -1
- package/build/src/akuma/heart/schema.js +61 -22
- package/build/src/akuma/heart/soul.d.ts +8 -0
- package/build/src/akuma/heart/soul.js +136 -0
- package/build/src/akuma/heart/storage.d.ts +47 -0
- package/build/src/akuma/heart/storage.js +224 -0
- package/build/src/akuma/heart/tells.d.ts +12 -0
- package/build/src/akuma/heart/tells.js +79 -0
- package/build/src/akuma/heart/timeline.d.ts +15 -0
- package/build/src/akuma/heart/timeline.js +86 -0
- package/build/src/akuma/identity.d.ts +2 -2
- package/build/src/akuma/identity.js +9 -9
- package/build/src/akuma/index.d.ts +10 -3
- package/build/src/akuma/index.js +7 -1
- package/build/src/akuma/projection.d.ts +176 -0
- package/build/src/akuma/projection.js +256 -0
- package/build/src/akuma/provider-recipe.d.ts +24 -0
- package/build/src/akuma/provider-recipe.js +100 -0
- package/build/src/akuma/provider.d.ts +67 -13
- package/build/src/akuma/provider.js +172 -105
- package/build/src/akuma/providers/acp/core.d.ts +19 -0
- package/build/src/akuma/providers/acp/core.js +159 -0
- package/build/src/akuma/providers/acp/events.d.ts +20 -0
- package/build/src/akuma/providers/acp/events.js +73 -0
- package/build/src/akuma/providers/acp/index.d.ts +12 -0
- package/build/src/akuma/providers/acp/index.js +94 -0
- package/build/src/akuma/providers/{claude.d.ts → claude/events.d.ts} +9 -41
- package/build/src/akuma/providers/claude/events.js +238 -0
- package/build/src/akuma/providers/claude/index.d.ts +47 -0
- package/build/src/akuma/providers/claude/index.js +279 -0
- package/build/src/akuma/providers/claude/input.d.ts +13 -0
- package/build/src/akuma/providers/claude/input.js +109 -0
- package/build/src/akuma/providers/codex-app-server/events.d.ts +1 -1
- package/build/src/akuma/providers/codex-app-server/events.js +15 -12
- package/build/src/akuma/providers/codex-app-server/index.d.ts +15 -13
- package/build/src/akuma/providers/codex-app-server/index.js +77 -33
- package/build/src/akuma/providers/grok-build/index.d.ts +4 -0
- package/build/src/akuma/providers/grok-build/index.js +70 -0
- package/build/src/akuma/providers/index.d.ts +5 -3
- package/build/src/akuma/providers/index.js +35 -56
- package/build/src/akuma/providers/opencode-sdk/events.d.ts +54 -0
- package/build/src/akuma/providers/opencode-sdk/events.js +235 -0
- package/build/src/akuma/providers/opencode-sdk/index.d.ts +33 -0
- package/build/src/akuma/providers/opencode-sdk/index.js +308 -0
- package/build/src/akuma/providers/opencode-sdk/session.d.ts +26 -0
- package/build/src/akuma/providers/opencode-sdk/session.js +63 -0
- package/build/src/akuma/providers/pi/events.d.ts +33 -0
- package/build/src/akuma/providers/pi/events.js +166 -0
- package/build/src/akuma/providers/pi/index.d.ts +11 -0
- package/build/src/akuma/providers/pi/index.js +191 -0
- package/build/src/akuma/publication.d.ts +3 -3
- package/build/src/akuma/publication.js +28 -40
- package/build/src/akuma/requests.d.ts +9 -6
- package/build/src/akuma/requests.js +95 -65
- package/build/src/alias/index.d.ts +4 -3
- package/build/src/alias/index.js +12 -13
- package/build/src/body/decode.js +7 -1
- package/build/src/body/region.d.ts +2 -0
- package/build/src/body/region.js +77 -7
- package/build/src/body/render.js +2 -1
- package/build/src/body/shape.js +7 -1
- package/build/src/body/verification.js +19 -4
- package/build/src/cli/accepted.d.ts +18 -12
- package/build/src/cli/accepted.js +73 -34
- package/build/src/cli/actor.js +3 -3
- package/build/src/cli/commands/akuma-invoke.d.ts +44 -19
- package/build/src/cli/commands/akuma-invoke.js +101 -52
- package/build/src/cli/commands/akuma.d.ts +27 -9
- package/build/src/cli/commands/akuma.js +151 -80
- package/build/src/cli/commands/contract.d.ts +155 -0
- package/build/src/cli/commands/contract.js +20 -0
- package/build/src/cli/commands/install.js +2 -0
- package/build/src/cli/commands/task-invoke.d.ts +19 -4
- package/build/src/cli/commands/task-invoke.js +65 -17
- package/build/src/cli/commands/task-query.d.ts +5 -0
- package/build/src/cli/commands/task-query.js +247 -0
- package/build/src/cli/commands/task.d.ts +3 -1
- package/build/src/cli/commands/task.js +52 -14
- package/build/src/cli/coordinates.d.ts +18 -0
- package/build/src/cli/coordinates.js +120 -0
- package/build/src/cli/draft.d.ts +8 -0
- package/build/src/cli/draft.js +96 -0
- package/build/src/cli/invoke.d.ts +9 -3
- package/build/src/cli/invoke.js +287 -108
- package/build/src/cli/main.js +54 -7
- package/build/src/cli/parse.d.ts +21 -117
- package/build/src/cli/parse.js +79 -84
- package/build/src/cli/render/akuma-tool-command.d.ts +6 -0
- package/build/src/cli/render/akuma-tool-command.js +144 -0
- package/build/src/cli/render/akuma-tool.d.ts +4 -2
- package/build/src/cli/render/akuma-tool.js +35 -5
- package/build/src/cli/render/akuma.d.ts +3 -1
- package/build/src/cli/render/akuma.js +247 -212
- package/build/src/cli/render/audit.d.ts +3 -0
- package/build/src/cli/render/audit.js +104 -0
- package/build/src/cli/render/catalog.d.ts +2 -0
- package/build/src/cli/render/catalog.js +16 -0
- package/build/src/cli/render/contract.d.ts +3 -3
- package/build/src/cli/render/contract.js +200 -54
- package/build/src/cli/render/kanshi.js +272 -129
- package/build/src/cli/render/receipt.d.ts +19 -0
- package/build/src/cli/render/receipt.js +106 -0
- package/build/src/cli/render/refusal.d.ts +16 -2
- package/build/src/cli/render/refusal.js +88 -3
- package/build/src/cli/render/region.d.ts +2 -0
- package/build/src/cli/render/region.js +17 -0
- package/build/src/cli/render/task.d.ts +2 -1
- package/build/src/cli/render/task.js +223 -60
- package/build/src/cli/render/terminal.d.ts +20 -0
- package/build/src/cli/render/terminal.js +121 -0
- package/build/src/cli/render/text.js +11 -3
- package/build/src/cli/result.d.ts +126 -19
- package/build/src/cli/usage.d.ts +1 -0
- package/build/src/cli/usage.js +3 -0
- package/build/src/contract-worktree.d.ts +44 -0
- package/build/src/contract-worktree.js +251 -0
- package/build/src/coordination/durable-file.d.ts +5 -1
- package/build/src/coordination/durable-file.js +57 -10
- package/build/src/coordination/sqlite-transaction-lock.d.ts +4 -0
- package/build/src/coordination/sqlite-transaction-lock.js +22 -5
- package/build/src/core/facts/codec.js +23 -4
- package/build/src/core/facts/eligibility.d.ts +1 -10
- package/build/src/core/facts/eligibility.js +0 -49
- package/build/src/core/facts/fold.js +1 -6
- package/build/src/core/facts/gate.d.ts +1 -0
- package/build/src/core/facts/gate.js +1 -0
- package/build/src/core/facts/observation.d.ts +3 -0
- package/build/src/core/facts/observation.js +12 -0
- package/build/src/core/facts/types.d.ts +10 -3
- package/build/src/core/subject.js +2 -2
- package/build/src/core/verbs/amend.d.ts +1 -1
- package/build/src/core/verbs/amend.js +3 -18
- package/build/src/core/verbs/bind.js +2 -16
- package/build/src/core/verbs/deliver.d.ts +1 -1
- package/build/src/core/verbs/deliver.js +14 -4
- package/build/src/core/verbs/placement.d.ts +1 -1
- package/build/src/core/verbs/placement.js +7 -9
- package/build/src/dispatch/index.d.ts +5 -3
- package/build/src/dispatch/index.js +30 -21
- package/build/src/duration.d.ts +10 -0
- package/build/src/duration.js +22 -0
- package/build/src/git/admission.d.ts +2 -2
- package/build/src/git/admission.js +33 -23
- package/build/src/git/hooks.d.ts +15 -0
- package/build/src/git/hooks.js +70 -18
- package/build/src/git/identity.d.ts +5 -1
- package/build/src/git/identity.js +23 -4
- package/build/src/git/integration.d.ts +48 -0
- package/build/src/git/integration.js +250 -0
- package/build/src/git/observe.d.ts +29 -15
- package/build/src/git/observe.js +185 -76
- package/build/src/git/read-observation.d.ts +41 -0
- package/build/src/git/read-observation.js +361 -0
- package/build/src/git/reconcile.d.ts +17 -5
- package/build/src/git/reconcile.js +305 -109
- package/build/src/git/repository.d.ts +41 -18
- package/build/src/git/repository.js +220 -114
- package/build/src/git/scratch.d.ts +17 -0
- package/build/src/git/scratch.js +73 -0
- package/build/src/git/target-placement.d.ts +92 -0
- package/build/src/git/target-placement.js +375 -0
- package/build/src/git/tender.d.ts +49 -0
- package/build/src/git/tender.js +63 -0
- package/build/src/git/terminal-seal.d.ts +24 -0
- package/build/src/git/terminal-seal.js +89 -0
- package/build/src/git/tree.d.ts +2 -2
- package/build/src/git/tree.js +4 -0
- package/build/src/git/workspace.d.ts +53 -0
- package/build/src/git/workspace.js +139 -0
- package/build/src/identity/selector.d.ts +6 -0
- package/build/src/identity/selector.js +19 -0
- package/build/src/index.d.ts +4 -2
- package/build/src/index.js +2 -1
- package/build/src/kanshi/index.d.ts +2 -2
- package/build/src/kanshi/index.js +1 -1
- package/build/src/kanshi/read.d.ts +7 -3
- package/build/src/kanshi/read.js +245 -45
- package/build/src/kanshi/report.d.ts +81 -6
- package/build/src/kanshi/select.d.ts +5 -0
- package/build/src/kanshi/select.js +33 -1
- package/build/src/library/address.d.ts +57 -0
- package/build/src/library/address.js +165 -0
- package/build/src/library/akuma-creation.d.ts +16 -7
- package/build/src/library/akuma-creation.js +96 -22
- package/build/src/library/audit.d.ts +18 -0
- package/build/src/library/audit.js +44 -0
- package/build/src/library/bind.d.ts +21 -0
- package/build/src/library/bind.js +56 -0
- package/build/src/library/catalog.d.ts +58 -0
- package/build/src/library/catalog.js +69 -0
- package/build/src/library/configuration.d.ts +6 -15
- package/build/src/library/configuration.js +14 -17
- package/build/src/library/contract.d.ts +23 -49
- package/build/src/library/contract.js +171 -185
- package/build/src/library/delivery.d.ts +24 -0
- package/build/src/library/delivery.js +21 -0
- package/build/src/library/fleet.d.ts +65 -0
- package/build/src/library/fleet.js +153 -0
- package/build/src/library/input.d.ts +3 -0
- package/build/src/library/input.js +19 -0
- package/build/src/library/keiyaku.d.ts +18 -3
- package/build/src/library/keiyaku.js +12 -1
- package/build/src/library/mutation.d.ts +39 -0
- package/build/src/library/mutation.js +37 -0
- package/build/src/library/reconcile.d.ts +29 -0
- package/build/src/library/reconcile.js +179 -0
- package/build/src/library/refusal.d.ts +21 -0
- package/build/src/library/refusal.js +29 -0
- package/build/src/library/region.d.ts +14 -1
- package/build/src/library/region.js +18 -4
- package/build/src/library/repo.d.ts +4 -13
- package/build/src/library/repo.js +14 -24
- package/build/src/protocol/attempt.d.ts +16 -3
- package/build/src/protocol/attempt.js +41 -10
- package/build/src/protocol/bind.d.ts +9 -2
- package/build/src/protocol/bind.js +48 -40
- package/build/src/protocol/intent.d.ts +57 -20
- package/build/src/protocol/intent.js +55 -79
- package/build/src/protocol/operations.d.ts +152 -43
- package/build/src/protocol/operations.js +482 -166
- package/build/src/protocol/outcome.d.ts +14 -3
- package/build/src/protocol/outcome.js +11 -3
- package/build/src/protocol/placement.d.ts +41 -0
- package/build/src/protocol/placement.js +147 -0
- package/build/src/protocol/read/documents.d.ts +2 -2
- package/build/src/protocol/read/documents.js +4 -4
- package/build/src/protocol/read/status.d.ts +18 -5
- package/build/src/protocol/read/status.js +73 -13
- package/build/src/protocol/run.d.ts +49 -7
- package/build/src/protocol/run.js +53 -25
- package/build/src/runtime/proc/line-rpc.d.ts +4 -8
- package/build/src/runtime/proc/line-rpc.js +22 -28
- package/build/src/runtime/proc/run.d.ts +23 -21
- package/build/src/runtime/proc/run.js +123 -89
- package/build/src/runtime/proc/stdio.d.ts +18 -0
- package/build/src/runtime/proc/stdio.js +58 -0
- package/build/src/settings.d.ts +7 -1
- package/build/src/settings.js +32 -17
- package/build/src/settlement/fence.d.ts +5 -0
- package/build/src/settlement/fence.js +14 -0
- package/build/src/settlement/holder.d.ts +35 -4
- package/build/src/settlement/holder.js +127 -28
- package/build/src/settlement/settle.d.ts +9 -1
- package/build/src/settlement/settle.js +74 -37
- package/build/src/task/board.d.ts +23 -5
- package/build/src/task/board.js +17 -10
- package/build/src/task/compose.d.ts +2 -2
- package/build/src/task/compose.js +18 -10
- package/build/src/task/context.d.ts +3 -3
- package/build/src/task/context.js +15 -20
- package/build/src/task/document.d.ts +2 -1
- package/build/src/task/document.js +15 -5
- package/build/src/task/index.d.ts +27 -15
- package/build/src/task/index.js +57 -35
- package/build/src/task/operations.d.ts +38 -13
- package/build/src/task/operations.js +80 -46
- package/build/src/task/query.d.ts +67 -0
- package/build/src/task/query.js +279 -0
- package/build/src/task/store.d.ts +5 -7
- package/build/src/task/store.js +43 -28
- package/build/src/verification/declaration.d.ts +1 -0
- package/build/src/verification/execution.d.ts +51 -0
- package/build/src/verification/execution.js +108 -0
- package/build/src/workspace-place.d.ts +41 -0
- package/build/src/workspace-place.js +386 -0
- package/build/src/world.d.ts +24 -0
- package/build/src/world.js +136 -0
- package/package.json +8 -4
- package/build/src/akuma/activity.d.ts +0 -66
- package/build/src/akuma/activity.js +0 -139
- package/build/src/akuma/providers/claude.js +0 -305
- package/build/src/context-root.d.ts +0 -4
- package/build/src/context-root.js +0 -15
- package/build/src/git/delivery.d.ts +0 -20
- package/build/src/git/delivery.js +0 -109
- package/build/src/git/verification.d.ts +0 -14
- package/build/src/git/verification.js +0 -56
- package/build/src/protocol/read/audit.d.ts +0 -25
- package/build/src/protocol/read/audit.js +0 -39
- package/build/src/verification/producer.d.ts +0 -23
- package/build/src/verification/producer.js +0 -54
|
@@ -0,0 +1,159 @@
|
|
|
1
|
+
import * as acp from "@agentclientprotocol/sdk";
|
|
2
|
+
import { Readable, Writable } from "node:stream";
|
|
3
|
+
import { spawnStdioProcess } from "../../../runtime/proc/stdio.js";
|
|
4
|
+
import { abortable } from "../../abort.js";
|
|
5
|
+
import { AgentEventChannel, } from "../../provider.js";
|
|
6
|
+
import { EMPTY_ACP_EVENT_STATE, flushAcpEvents, mapAcpUpdate } from "./events.js";
|
|
7
|
+
function diagnostic(error) {
|
|
8
|
+
if (!(error instanceof Error))
|
|
9
|
+
return String(error);
|
|
10
|
+
if (!(error instanceof acp.RequestError) || error.data === undefined)
|
|
11
|
+
return error.message;
|
|
12
|
+
let data;
|
|
13
|
+
try {
|
|
14
|
+
data = typeof error.data === "string" ? error.data : JSON.stringify(error.data);
|
|
15
|
+
}
|
|
16
|
+
catch {
|
|
17
|
+
data = String(error.data);
|
|
18
|
+
}
|
|
19
|
+
return data.length === 0 ? `${error.message} [${error.code}]` : `${error.message} [${error.code}]: ${data}`;
|
|
20
|
+
}
|
|
21
|
+
function createAcpClient(onUpdate) {
|
|
22
|
+
return acp.client({ name: "keiyaku" })
|
|
23
|
+
.onNotification(acp.methods.client.session.update, ({ params }) => onUpdate(params));
|
|
24
|
+
}
|
|
25
|
+
function promptResult(response) {
|
|
26
|
+
if (response.stopReason === "end_turn" || response.stopReason === "max_tokens"
|
|
27
|
+
|| response.stopReason === "max_turn_requests") {
|
|
28
|
+
return { kind: "answered", answer: "" };
|
|
29
|
+
}
|
|
30
|
+
return { kind: "failed", diagnostic: `ACP prompt ended ${response.stopReason}` };
|
|
31
|
+
}
|
|
32
|
+
async function establishSession(agent, input) {
|
|
33
|
+
const initialized = await agent.request(acp.methods.agent.initialize, {
|
|
34
|
+
protocolVersion: acp.PROTOCOL_VERSION,
|
|
35
|
+
clientInfo: { name: "keiyaku", version: "4" },
|
|
36
|
+
});
|
|
37
|
+
if (input.session.kind === "resume" && initialized.agentCapabilities?.loadSession !== true) {
|
|
38
|
+
throw new Error("ACP agent does not advertise session/load");
|
|
39
|
+
}
|
|
40
|
+
if (input.session.kind === "fresh") {
|
|
41
|
+
return (await agent.request(acp.methods.agent.session.new, { cwd: input.cwd, mcpServers: [] })).sessionId;
|
|
42
|
+
}
|
|
43
|
+
const sessionId = input.session.coordinate.sessionId;
|
|
44
|
+
if (sessionId === undefined)
|
|
45
|
+
throw new Error("ACP resume coordinate has no session id");
|
|
46
|
+
await agent.request(acp.methods.agent.session.load, { cwd: input.cwd, mcpServers: [], sessionId });
|
|
47
|
+
return sessionId;
|
|
48
|
+
}
|
|
49
|
+
function createAcpTurn(connection) {
|
|
50
|
+
const events = new AgentEventChannel();
|
|
51
|
+
let state = EMPTY_ACP_EVENT_STATE;
|
|
52
|
+
let terminal = false;
|
|
53
|
+
let resolveCompletion;
|
|
54
|
+
const completion = new Promise((resolve) => { resolveCompletion = resolve; });
|
|
55
|
+
const update = (next) => {
|
|
56
|
+
if (terminal)
|
|
57
|
+
return;
|
|
58
|
+
const mapped = mapAcpUpdate(next, state);
|
|
59
|
+
state = mapped.state;
|
|
60
|
+
for (const event of mapped.events)
|
|
61
|
+
events.emit(event);
|
|
62
|
+
};
|
|
63
|
+
const finish = async (result, cleanup) => {
|
|
64
|
+
if (terminal)
|
|
65
|
+
return;
|
|
66
|
+
terminal = true;
|
|
67
|
+
let completionResult = result;
|
|
68
|
+
const failCleanup = (error) => {
|
|
69
|
+
completionResult = { kind: "failed", diagnostic: `ACP cleanup failed: ${diagnostic(error)}` };
|
|
70
|
+
};
|
|
71
|
+
try {
|
|
72
|
+
await cleanup();
|
|
73
|
+
}
|
|
74
|
+
catch (error) {
|
|
75
|
+
failCleanup(error);
|
|
76
|
+
}
|
|
77
|
+
try {
|
|
78
|
+
connection.close();
|
|
79
|
+
}
|
|
80
|
+
catch (error) {
|
|
81
|
+
failCleanup(error);
|
|
82
|
+
}
|
|
83
|
+
const flushed = flushAcpEvents(state);
|
|
84
|
+
for (const event of flushed.events)
|
|
85
|
+
events.emit(event);
|
|
86
|
+
events.end();
|
|
87
|
+
resolveCompletion(completionResult.kind === "answered"
|
|
88
|
+
? { ...completionResult, answer: flushed.state.answer }
|
|
89
|
+
: completionResult);
|
|
90
|
+
};
|
|
91
|
+
return {
|
|
92
|
+
events,
|
|
93
|
+
completion,
|
|
94
|
+
update,
|
|
95
|
+
finish,
|
|
96
|
+
open: () => !terminal,
|
|
97
|
+
};
|
|
98
|
+
}
|
|
99
|
+
function beginAcpPrompt(agent, child, turn, sessionId, input) {
|
|
100
|
+
void agent.request(acp.methods.agent.session.prompt, {
|
|
101
|
+
sessionId,
|
|
102
|
+
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()));
|
|
104
|
+
void child.exited.then((exit) => turn.finish({
|
|
105
|
+
kind: "failed",
|
|
106
|
+
diagnostic: exit.stderr || `ACP process exited${exit.code === null ? "" : ` with code ${exit.code}`}`,
|
|
107
|
+
}, () => child.endInputAndDrain()));
|
|
108
|
+
return {
|
|
109
|
+
admission: { fence: sessionId },
|
|
110
|
+
events: turn.events,
|
|
111
|
+
completion: turn.completion,
|
|
112
|
+
abort: async () => {
|
|
113
|
+
let result = { kind: "failed", diagnostic: "ACP turn cancelled" };
|
|
114
|
+
try {
|
|
115
|
+
await agent.notify(acp.methods.agent.session.cancel, { sessionId });
|
|
116
|
+
}
|
|
117
|
+
catch (error) {
|
|
118
|
+
result = { kind: "failed", diagnostic: diagnostic(error) };
|
|
119
|
+
}
|
|
120
|
+
if (!turn.open())
|
|
121
|
+
await child.close(true);
|
|
122
|
+
else
|
|
123
|
+
await turn.finish(result, () => child.close(true));
|
|
124
|
+
},
|
|
125
|
+
};
|
|
126
|
+
}
|
|
127
|
+
export async function startAcpSession(launch, input, dependencies = {}) {
|
|
128
|
+
const signal = input.signal ?? new AbortController().signal;
|
|
129
|
+
signal.throwIfAborted();
|
|
130
|
+
const child = (dependencies.spawnProcess ?? spawnStdioProcess)({
|
|
131
|
+
argv: launch.argv,
|
|
132
|
+
cwd: input.cwd,
|
|
133
|
+
env: { ...globalThis.process.env, ...launch.env },
|
|
134
|
+
});
|
|
135
|
+
let sessionId;
|
|
136
|
+
let turn;
|
|
137
|
+
const connection = createAcpClient((notification) => {
|
|
138
|
+
if (notification.sessionId === sessionId)
|
|
139
|
+
turn.update(notification.update);
|
|
140
|
+
}).connect(acp.ndJsonStream(Writable.toWeb(child.input), Readable.toWeb(child.output)));
|
|
141
|
+
turn = createAcpTurn(connection);
|
|
142
|
+
try {
|
|
143
|
+
sessionId = await abortable(establishSession(connection.agent, input), signal);
|
|
144
|
+
turn.events.emit({ type: "session", coordinate: { sessionId } });
|
|
145
|
+
const session = beginAcpPrompt(connection.agent, child, turn, sessionId, input);
|
|
146
|
+
return { session, agent: connection.agent, sessionId, open: turn.open };
|
|
147
|
+
}
|
|
148
|
+
catch (error) {
|
|
149
|
+
connection.close(error);
|
|
150
|
+
try {
|
|
151
|
+
await child.close(true);
|
|
152
|
+
}
|
|
153
|
+
catch (cleanup) {
|
|
154
|
+
throw new Error(`${diagnostic(error)}; ACP cleanup failed: ${diagnostic(cleanup)}`, { cause: error });
|
|
155
|
+
}
|
|
156
|
+
turn.events.end();
|
|
157
|
+
throw error;
|
|
158
|
+
}
|
|
159
|
+
}
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
import type { SessionUpdate } from "@agentclientprotocol/sdk";
|
|
2
|
+
import { type AgentEvent } from "../../provider.js";
|
|
3
|
+
type OpenBlock = Readonly<{
|
|
4
|
+
type: "assistant" | "thought";
|
|
5
|
+
text: string;
|
|
6
|
+
}> | null;
|
|
7
|
+
export type AcpEventState = Readonly<{
|
|
8
|
+
answer: string;
|
|
9
|
+
assistantMessageId: string | null;
|
|
10
|
+
open: OpenBlock;
|
|
11
|
+
tools: ReadonlyMap<string, string>;
|
|
12
|
+
}>;
|
|
13
|
+
type AcpEventMapping = Readonly<{
|
|
14
|
+
events: readonly AgentEvent[];
|
|
15
|
+
state: AcpEventState;
|
|
16
|
+
}>;
|
|
17
|
+
export declare const EMPTY_ACP_EVENT_STATE: AcpEventState;
|
|
18
|
+
export declare function flushAcpEvents(state: AcpEventState, boundary?: readonly AgentEvent[]): AcpEventMapping;
|
|
19
|
+
export declare function mapAcpUpdate(update: SessionUpdate, previous: AcpEventState): AcpEventMapping;
|
|
20
|
+
export {};
|
|
@@ -0,0 +1,73 @@
|
|
|
1
|
+
import { noteEvent, unknownEvent } from "../../provider.js";
|
|
2
|
+
export const EMPTY_ACP_EVENT_STATE = Object.freeze({
|
|
3
|
+
answer: "",
|
|
4
|
+
assistantMessageId: null,
|
|
5
|
+
open: null,
|
|
6
|
+
tools: new Map(),
|
|
7
|
+
});
|
|
8
|
+
export function flushAcpEvents(state, boundary = []) {
|
|
9
|
+
const events = state.open === null ? boundary : [{ type: state.open.type, text: state.open.text }, ...boundary];
|
|
10
|
+
return {
|
|
11
|
+
events,
|
|
12
|
+
state: state.open === null ? state : { ...state, open: null },
|
|
13
|
+
};
|
|
14
|
+
}
|
|
15
|
+
function appendBlock(state, type, text, messageId) {
|
|
16
|
+
const newAssistantMessage = type === "assistant"
|
|
17
|
+
&& messageId !== undefined
|
|
18
|
+
&& state.assistantMessageId !== messageId;
|
|
19
|
+
const flushed = state.open === null || (state.open.type === type && !newAssistantMessage)
|
|
20
|
+
? { events: [], state }
|
|
21
|
+
: flushAcpEvents(state);
|
|
22
|
+
return {
|
|
23
|
+
events: flushed.events,
|
|
24
|
+
state: {
|
|
25
|
+
...flushed.state,
|
|
26
|
+
answer: type === "assistant"
|
|
27
|
+
? `${newAssistantMessage ? "" : flushed.state.answer}${text}`
|
|
28
|
+
: flushed.state.answer,
|
|
29
|
+
assistantMessageId: type === "assistant" && messageId !== undefined
|
|
30
|
+
? messageId
|
|
31
|
+
: flushed.state.assistantMessageId,
|
|
32
|
+
open: { type, text: `${flushed.state.open?.text ?? ""}${text}` },
|
|
33
|
+
},
|
|
34
|
+
};
|
|
35
|
+
}
|
|
36
|
+
export function mapAcpUpdate(update, previous) {
|
|
37
|
+
switch (update.sessionUpdate) {
|
|
38
|
+
case "agent_message_chunk":
|
|
39
|
+
case "agent_thought_chunk": {
|
|
40
|
+
const text = update.content.type === "text" ? update.content.text : undefined;
|
|
41
|
+
if (text === undefined)
|
|
42
|
+
return flushAcpEvents(previous, [unknownEvent(`${update.sessionUpdate}/${update.content.type}`)]);
|
|
43
|
+
return appendBlock(previous, update.sessionUpdate === "agent_message_chunk" ? "assistant" : "thought", text, update.sessionUpdate === "agent_message_chunk" && update.messageId !== null
|
|
44
|
+
? update.messageId
|
|
45
|
+
: undefined);
|
|
46
|
+
}
|
|
47
|
+
case "user_message_chunk": return flushAcpEvents(previous);
|
|
48
|
+
case "tool_call":
|
|
49
|
+
case "tool_call_update": {
|
|
50
|
+
const name = update.title ?? (update.sessionUpdate === "tool_call" ? undefined : previous.tools.get(update.toolCallId)) ?? "ACP tool";
|
|
51
|
+
const observed = { name, call: { kind: "other", display: name } };
|
|
52
|
+
const tools = new Map(previous.tools);
|
|
53
|
+
if (update.status === "completed" || update.status === "failed") {
|
|
54
|
+
tools.delete(update.toolCallId);
|
|
55
|
+
return flushAcpEvents({ ...previous, tools }, [{ type: "tool", phase: "completed", id: update.toolCallId, ...observed, result: { status: update.status === "completed" ? "ok" : "error" } }]);
|
|
56
|
+
}
|
|
57
|
+
const started = tools.has(update.toolCallId);
|
|
58
|
+
tools.set(update.toolCallId, name);
|
|
59
|
+
return started
|
|
60
|
+
? flushAcpEvents({ ...previous, tools })
|
|
61
|
+
: flushAcpEvents({ ...previous, tools }, [{ type: "tool", phase: "started", id: update.toolCallId, ...observed }]);
|
|
62
|
+
}
|
|
63
|
+
case "plan": return flushAcpEvents(previous, [noteEvent(`Plan updated: ${update.entries.map((entry) => entry.content).join("; ")}`)]);
|
|
64
|
+
case "available_commands_update": return flushAcpEvents(previous, [noteEvent("ACP commands updated")]);
|
|
65
|
+
case "current_mode_update": return flushAcpEvents(previous, [noteEvent("ACP mode updated")]);
|
|
66
|
+
case "config_option_update": return flushAcpEvents(previous, [noteEvent("ACP configuration updated")]);
|
|
67
|
+
case "session_info_update": return flushAcpEvents(previous, [noteEvent("ACP session metadata updated")]);
|
|
68
|
+
case "usage_update": return flushAcpEvents(previous);
|
|
69
|
+
case "plan_update":
|
|
70
|
+
case "plan_removed": return flushAcpEvents(previous, [unknownEvent(update.sessionUpdate)]);
|
|
71
|
+
default: return { events: [unknownEvent(String(update.sessionUpdate))], state: previous };
|
|
72
|
+
}
|
|
73
|
+
}
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
import type { ProviderAdapter } from "../../provider.js";
|
|
2
|
+
import type { ProviderExecution } from "../../provider-recipe.js";
|
|
3
|
+
import { type AcpDependencies } from "./core.js";
|
|
4
|
+
export type AcpExecutionConfig = Readonly<{
|
|
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;
|
|
12
|
+
export declare function createAcpProvider(execution: ProviderExecution, dependencies?: AcpDependencies): ProviderAdapter;
|
|
@@ -0,0 +1,94 @@
|
|
|
1
|
+
import { startAcpSession } from "./core.js";
|
|
2
|
+
function argumentName(value, key) {
|
|
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
|
+
}
|
|
39
|
+
function optionAdmission(options, config) {
|
|
40
|
+
if (options.network !== undefined) {
|
|
41
|
+
return { kind: "refused", diagnostic: "ACP provider does not support the network option" };
|
|
42
|
+
}
|
|
43
|
+
if (options.model !== undefined && config.modelArg === undefined) {
|
|
44
|
+
return { kind: "refused", diagnostic: "ACP provider has no model argument mapping" };
|
|
45
|
+
}
|
|
46
|
+
if (options.effort !== undefined && config.effortArg === undefined) {
|
|
47
|
+
return { kind: "refused", diagnostic: "ACP provider has no effort argument mapping" };
|
|
48
|
+
}
|
|
49
|
+
if (options.systemPrompt !== undefined && options.systemPrompt.length > 0 && config.systemPromptArg === undefined) {
|
|
50
|
+
return { kind: "refused", diagnostic: "ACP provider has no systemPrompt argument mapping" };
|
|
51
|
+
}
|
|
52
|
+
return {
|
|
53
|
+
kind: "admitted",
|
|
54
|
+
options,
|
|
55
|
+
...(options.readonly === undefined ? {} : {
|
|
56
|
+
readonly: {
|
|
57
|
+
enforcement: "none",
|
|
58
|
+
diagnostic: "ACP cannot remove task-surface mutation capabilities",
|
|
59
|
+
},
|
|
60
|
+
}),
|
|
61
|
+
};
|
|
62
|
+
}
|
|
63
|
+
function argv(execution, config, options) {
|
|
64
|
+
if (execution.executable === undefined)
|
|
65
|
+
throw new Error("ACP provider execution requires executable");
|
|
66
|
+
const values = [execution.executable, ...config.argvBefore];
|
|
67
|
+
if (options.model !== undefined)
|
|
68
|
+
values.push(config.modelArg, options.model);
|
|
69
|
+
if (options.effort !== undefined)
|
|
70
|
+
values.push(config.effortArg, options.effort);
|
|
71
|
+
if (options.systemPrompt !== undefined && config.systemPromptArg !== undefined) {
|
|
72
|
+
values.push(config.systemPromptArg, options.systemPrompt);
|
|
73
|
+
}
|
|
74
|
+
values.push(...config.argvAfter);
|
|
75
|
+
return values;
|
|
76
|
+
}
|
|
77
|
+
export function createAcpProvider(execution, dependencies = {}) {
|
|
78
|
+
if (execution.executable === undefined)
|
|
79
|
+
throw new TypeError("ACP provider execution requires executable");
|
|
80
|
+
const config = decodeAcpConfig(execution.config);
|
|
81
|
+
const drive = async (input) => {
|
|
82
|
+
const launch = {
|
|
83
|
+
argv: argv(execution, config, input.options),
|
|
84
|
+
...(execution.env === undefined ? {} : { env: execution.env }),
|
|
85
|
+
};
|
|
86
|
+
return (await startAcpSession(launch, input, dependencies)).session;
|
|
87
|
+
};
|
|
88
|
+
return {
|
|
89
|
+
confinement: () => ({ kind: "unconfined" }),
|
|
90
|
+
admitOptions: (options) => optionAdmission(options, config),
|
|
91
|
+
start: async (input) => await drive(input),
|
|
92
|
+
resume: async (input) => await drive(input),
|
|
93
|
+
};
|
|
94
|
+
}
|
|
@@ -1,5 +1,11 @@
|
|
|
1
|
-
import type {
|
|
2
|
-
import {
|
|
1
|
+
import type { SDKMessage } from "@anthropic-ai/claude-agent-sdk";
|
|
2
|
+
import { AgentEventChannel, type ToolCall } from "../../provider.js";
|
|
3
|
+
export type ClaudeObservationState = {
|
|
4
|
+
tools: Map<string, Readonly<{
|
|
5
|
+
name: string;
|
|
6
|
+
call: ToolCall;
|
|
7
|
+
}>>;
|
|
8
|
+
};
|
|
3
9
|
export declare const CLAUDE_MESSAGE_DISPOSITIONS: {
|
|
4
10
|
readonly assistant: "assistant";
|
|
5
11
|
readonly auth_status: "auth";
|
|
@@ -43,42 +49,4 @@ export declare const CLAUDE_SYSTEM_DISPOSITIONS: {
|
|
|
43
49
|
readonly thinking_tokens: "drop";
|
|
44
50
|
readonly worker_shutting_down: "note";
|
|
45
51
|
};
|
|
46
|
-
export
|
|
47
|
-
query(input: Readonly<{
|
|
48
|
-
prompt: string;
|
|
49
|
-
options?: Options;
|
|
50
|
-
}>): Query;
|
|
51
|
-
forkSession?(sessionId: string, options: Readonly<{
|
|
52
|
-
dir: string;
|
|
53
|
-
upToMessageId: string;
|
|
54
|
-
}>): Promise<Readonly<{
|
|
55
|
-
sessionId: string;
|
|
56
|
-
}>>;
|
|
57
|
-
}>;
|
|
58
|
-
export declare function createClaudeProvider(load: () => Promise<ClaudeSdk>, execution?: Readonly<{
|
|
59
|
-
executable?: string;
|
|
60
|
-
env?: Readonly<Record<string, string>>;
|
|
61
|
-
}>): ProviderAdapter;
|
|
62
|
-
export declare const claudeProvider: Readonly<{
|
|
63
|
-
confinement(input: Readonly<{
|
|
64
|
-
cwd: string;
|
|
65
|
-
options: ProviderOptions;
|
|
66
|
-
}>): import("../index.js").Confinement;
|
|
67
|
-
admitOptions(options: ProviderOptions): import("../provider.js").ProviderOptionAdmission;
|
|
68
|
-
fork?(input: Readonly<{
|
|
69
|
-
session: import("../provider.js").ResumeCoordinate;
|
|
70
|
-
at: string;
|
|
71
|
-
cwd: string;
|
|
72
|
-
}>): Promise<Readonly<{
|
|
73
|
-
session: import("../provider.js").ResumeCoordinate;
|
|
74
|
-
}>>;
|
|
75
|
-
start(input: Readonly<{
|
|
76
|
-
prompt: string;
|
|
77
|
-
cwd: string;
|
|
78
|
-
options: ProviderOptions;
|
|
79
|
-
session?: import("../provider.js").ResumeCoordinate;
|
|
80
|
-
requests?: Readonly<{
|
|
81
|
-
dir: string;
|
|
82
|
-
}>;
|
|
83
|
-
}>): Promise<Drive>;
|
|
84
|
-
}>;
|
|
52
|
+
export declare function emitClaudeMessage(message: SDKMessage, events: AgentEventChannel, state: ClaudeObservationState): void;
|
|
@@ -0,0 +1,238 @@
|
|
|
1
|
+
import { noteEvent, unknownEvent, } from "../../provider.js";
|
|
2
|
+
export const CLAUDE_MESSAGE_DISPOSITIONS = {
|
|
3
|
+
assistant: "assistant",
|
|
4
|
+
auth_status: "auth",
|
|
5
|
+
conversation_reset: "note",
|
|
6
|
+
prompt_suggestion: "drop",
|
|
7
|
+
rate_limit_event: "drop",
|
|
8
|
+
result: "terminal",
|
|
9
|
+
stream_event: "drop",
|
|
10
|
+
system: "system",
|
|
11
|
+
tool_progress: "drop",
|
|
12
|
+
tool_use_summary: "drop",
|
|
13
|
+
user: "tool-results",
|
|
14
|
+
};
|
|
15
|
+
export const CLAUDE_SYSTEM_DISPOSITIONS = {
|
|
16
|
+
api_retry: "note",
|
|
17
|
+
background_tasks_changed: "note",
|
|
18
|
+
commands_changed: "drop",
|
|
19
|
+
compact_boundary: "drop",
|
|
20
|
+
control_request_progress: "control-progress",
|
|
21
|
+
elicitation_complete: "drop",
|
|
22
|
+
files_persisted: "note",
|
|
23
|
+
hook_progress: "drop",
|
|
24
|
+
hook_response: "drop",
|
|
25
|
+
hook_started: "note",
|
|
26
|
+
informational: "note",
|
|
27
|
+
init: "drop",
|
|
28
|
+
local_command_output: "drop",
|
|
29
|
+
memory_recall: "drop",
|
|
30
|
+
mirror_error: "note",
|
|
31
|
+
model_refusal_fallback: "note",
|
|
32
|
+
model_refusal_no_fallback: "note",
|
|
33
|
+
notification: "note",
|
|
34
|
+
permission_denied: "note",
|
|
35
|
+
plugin_install: "note",
|
|
36
|
+
session_state_changed: "drop",
|
|
37
|
+
status: "note",
|
|
38
|
+
task_notification: "note",
|
|
39
|
+
task_progress: "note",
|
|
40
|
+
task_started: "note",
|
|
41
|
+
task_updated: "note",
|
|
42
|
+
thinking_tokens: "drop",
|
|
43
|
+
worker_shutting_down: "note",
|
|
44
|
+
};
|
|
45
|
+
function object(value) {
|
|
46
|
+
return value !== null && typeof value === "object" && !Array.isArray(value)
|
|
47
|
+
? value
|
|
48
|
+
: undefined;
|
|
49
|
+
}
|
|
50
|
+
function nonblank(value) {
|
|
51
|
+
return typeof value === "string" && value.trim().length > 0 ? value.trim() : undefined;
|
|
52
|
+
}
|
|
53
|
+
function number(value) {
|
|
54
|
+
return typeof value === "number" && Number.isFinite(value) ? value : undefined;
|
|
55
|
+
}
|
|
56
|
+
function positiveLine(value) {
|
|
57
|
+
return typeof value === "number" && Number.isSafeInteger(value) && value >= 1 ? value : undefined;
|
|
58
|
+
}
|
|
59
|
+
function readRange(value) {
|
|
60
|
+
const offset = positiveLine(value.offset);
|
|
61
|
+
const limit = positiveLine(value.limit);
|
|
62
|
+
return {
|
|
63
|
+
...(offset === undefined ? {} : { offset }),
|
|
64
|
+
...(limit === undefined ? {} : { limit }),
|
|
65
|
+
};
|
|
66
|
+
}
|
|
67
|
+
function contentSearch(value, query) {
|
|
68
|
+
const path = nonblank(value.path);
|
|
69
|
+
const glob = nonblank(value.glob);
|
|
70
|
+
return {
|
|
71
|
+
kind: "search",
|
|
72
|
+
query,
|
|
73
|
+
scope: "content",
|
|
74
|
+
...(path === undefined ? {} : { path }),
|
|
75
|
+
...(glob === undefined ? {} : { glob }),
|
|
76
|
+
};
|
|
77
|
+
}
|
|
78
|
+
const CLAUDE_SYSTEM_NOTES = {
|
|
79
|
+
api_retry: (message) => {
|
|
80
|
+
const attempt = number(message.attempt);
|
|
81
|
+
const maximum = number(message.max_retries);
|
|
82
|
+
return attempt === undefined || maximum === undefined
|
|
83
|
+
? "Retrying request"
|
|
84
|
+
: `Retrying request ${attempt}/${maximum}`;
|
|
85
|
+
},
|
|
86
|
+
background_tasks_changed: (message) => `Background tasks: ${Array.isArray(message.tasks) ? message.tasks.length : 0}`,
|
|
87
|
+
files_persisted: (message) => {
|
|
88
|
+
const files = Array.isArray(message.files) ? message.files.length : 0;
|
|
89
|
+
const failed = Array.isArray(message.failed) ? message.failed.length : 0;
|
|
90
|
+
return failed === 0 ? `Persisted ${files} files` : `Persisted ${files} files; ${failed} failed`;
|
|
91
|
+
},
|
|
92
|
+
hook_started: (message) => `Hook ${nonblank(message.hook_name) ?? "unknown"} started`,
|
|
93
|
+
informational: (message) => nonblank(message.content) ?? "Informational notice",
|
|
94
|
+
mirror_error: (message) => `Transcript mirror warning: ${nonblank(message.error) ?? nonblank(message.message) ?? "unknown error"}`,
|
|
95
|
+
model_refusal_fallback: () => "Model refusal; using fallback",
|
|
96
|
+
model_refusal_no_fallback: () => "Model refusal; no fallback available",
|
|
97
|
+
notification: (message) => nonblank(message.message) ?? nonblank(message.content) ?? "Notification",
|
|
98
|
+
permission_denied: (message) => `Permission refused${nonblank(message.message) === undefined ? "" : `: ${nonblank(message.message)}`}`,
|
|
99
|
+
plugin_install: (message) => `Plugin install ${nonblank(message.status) ?? "updated"}${nonblank(message.name) === undefined ? "" : `: ${nonblank(message.name)}`}`,
|
|
100
|
+
status: (message) => `Status: ${nonblank(message.status) ?? "idle"}${nonblank(message.error) === undefined ? "" : ` (${nonblank(message.error)})`}`,
|
|
101
|
+
task_notification: (message) => `Task ${nonblank(message.task_id) ?? "unknown"} ${nonblank(message.status) ?? "updated"}`,
|
|
102
|
+
task_progress: (message) => nonblank(message.description) ?? `Task ${nonblank(message.task_id) ?? "unknown"} progressed`,
|
|
103
|
+
task_started: (message) => nonblank(message.description) ?? `Task ${nonblank(message.task_id) ?? "unknown"} started`,
|
|
104
|
+
task_updated: (message) => `Task ${nonblank(message.task_id) ?? "unknown"} updated`,
|
|
105
|
+
worker_shutting_down: (message) => `Worker stopping: ${nonblank(message.reason) ?? "unknown reason"}`,
|
|
106
|
+
};
|
|
107
|
+
function runCall(name, value) {
|
|
108
|
+
if (name !== "Bash")
|
|
109
|
+
return undefined;
|
|
110
|
+
const command = nonblank(value.command);
|
|
111
|
+
return command === undefined ? { kind: "other", display: name } : { kind: "run", command };
|
|
112
|
+
}
|
|
113
|
+
function readCall(name, path, value) {
|
|
114
|
+
return name === "Read" && path !== undefined ? { kind: "read", path, ...readRange(value) } : undefined;
|
|
115
|
+
}
|
|
116
|
+
function searchCall(name, value) {
|
|
117
|
+
if (name === "Grep") {
|
|
118
|
+
const query = nonblank(value.pattern) ?? nonblank(value.query);
|
|
119
|
+
return query === undefined ? undefined : contentSearch(value, query);
|
|
120
|
+
}
|
|
121
|
+
if (name === "Glob") {
|
|
122
|
+
const query = nonblank(value.pattern);
|
|
123
|
+
const globPath = nonblank(value.path);
|
|
124
|
+
return query === undefined
|
|
125
|
+
? undefined
|
|
126
|
+
: { kind: "search", query, scope: "files", ...(globPath === undefined ? {} : { path: globPath }) };
|
|
127
|
+
}
|
|
128
|
+
if (name === "WebSearch") {
|
|
129
|
+
const query = nonblank(value.query);
|
|
130
|
+
return query === undefined ? undefined : { kind: "search", query, scope: "web" };
|
|
131
|
+
}
|
|
132
|
+
return undefined;
|
|
133
|
+
}
|
|
134
|
+
function fileChangeCall(name, path) {
|
|
135
|
+
if (path === undefined)
|
|
136
|
+
return undefined;
|
|
137
|
+
if (name === "Write")
|
|
138
|
+
return { kind: "fileChange", changes: [{ op: "add", path }] };
|
|
139
|
+
if (name === "Edit" || name === "NotebookEdit") {
|
|
140
|
+
return { kind: "fileChange", changes: [{ op: "update", path }] };
|
|
141
|
+
}
|
|
142
|
+
return undefined;
|
|
143
|
+
}
|
|
144
|
+
function toolCall(name, input) {
|
|
145
|
+
const value = object(input) ?? {};
|
|
146
|
+
const run = runCall(name, value);
|
|
147
|
+
if (run !== undefined)
|
|
148
|
+
return run;
|
|
149
|
+
const path = nonblank(value.file_path) ?? (name === "Read" ? nonblank(value.path) : undefined);
|
|
150
|
+
return readCall(name, path, value)
|
|
151
|
+
?? searchCall(name, value)
|
|
152
|
+
?? fileChangeCall(name, path)
|
|
153
|
+
?? { kind: "other", display: name };
|
|
154
|
+
}
|
|
155
|
+
function assistantEvents(message, events, state) {
|
|
156
|
+
if (message.aborted === true)
|
|
157
|
+
return;
|
|
158
|
+
if (message.error !== undefined) {
|
|
159
|
+
events.emit(noteEvent(`Assistant error: ${message.error}`));
|
|
160
|
+
return;
|
|
161
|
+
}
|
|
162
|
+
const text = message.message.content
|
|
163
|
+
.filter((block) => block.type === "text")
|
|
164
|
+
.map((block) => block.text).join("");
|
|
165
|
+
if (text.length > 0)
|
|
166
|
+
events.emit({ type: "assistant", text });
|
|
167
|
+
for (const block of message.message.content) {
|
|
168
|
+
const value = object(block);
|
|
169
|
+
if (value?.type === "thinking" && typeof value.thinking === "string" && value.thinking.trim().length > 0) {
|
|
170
|
+
events.emit({ type: "thought", text: value.thinking });
|
|
171
|
+
continue;
|
|
172
|
+
}
|
|
173
|
+
if (block.type !== "tool_use")
|
|
174
|
+
continue;
|
|
175
|
+
const call = toolCall(block.name, block.input);
|
|
176
|
+
state.tools.set(block.id, { name: block.name, call });
|
|
177
|
+
events.emit({ type: "tool", phase: "started", id: block.id, name: block.name, call });
|
|
178
|
+
}
|
|
179
|
+
}
|
|
180
|
+
function toolResultEvents(message, events, state) {
|
|
181
|
+
const content = object(message.message)?.content;
|
|
182
|
+
if (!Array.isArray(content))
|
|
183
|
+
return;
|
|
184
|
+
for (const block of content) {
|
|
185
|
+
const value = object(block);
|
|
186
|
+
if (value?.type !== "tool_result")
|
|
187
|
+
continue;
|
|
188
|
+
const id = nonblank(value.tool_use_id);
|
|
189
|
+
if (id === undefined)
|
|
190
|
+
continue;
|
|
191
|
+
const observed = state.tools.get(id);
|
|
192
|
+
if (observed === undefined)
|
|
193
|
+
continue;
|
|
194
|
+
state.tools.delete(id);
|
|
195
|
+
events.emit({
|
|
196
|
+
type: "tool", phase: "completed", id, name: observed.name, call: observed.call,
|
|
197
|
+
result: { status: value.is_error === true ? "error" : "ok" },
|
|
198
|
+
});
|
|
199
|
+
}
|
|
200
|
+
}
|
|
201
|
+
function emitSystemMessage(value, events) {
|
|
202
|
+
const subtype = nonblank(value?.subtype) ?? "unknown";
|
|
203
|
+
if (!Object.hasOwn(CLAUDE_SYSTEM_DISPOSITIONS, subtype)) {
|
|
204
|
+
events.emit(unknownEvent(subtype));
|
|
205
|
+
return;
|
|
206
|
+
}
|
|
207
|
+
const systemDisposition = CLAUDE_SYSTEM_DISPOSITIONS[subtype];
|
|
208
|
+
if (systemDisposition === "drop")
|
|
209
|
+
return;
|
|
210
|
+
if (systemDisposition === "control-progress" && value?.status !== "api_retry")
|
|
211
|
+
return;
|
|
212
|
+
const note = CLAUDE_SYSTEM_NOTES[subtype];
|
|
213
|
+
events.emit(noteEvent(note?.(value ?? {}) ?? subtype));
|
|
214
|
+
}
|
|
215
|
+
export function emitClaudeMessage(message, events, state) {
|
|
216
|
+
const value = object(message) ?? {};
|
|
217
|
+
const kind = nonblank(value.type) ?? "unknown";
|
|
218
|
+
if (!Object.hasOwn(CLAUDE_MESSAGE_DISPOSITIONS, kind))
|
|
219
|
+
return events.emit(unknownEvent(kind));
|
|
220
|
+
const disposition = CLAUDE_MESSAGE_DISPOSITIONS[kind];
|
|
221
|
+
if (disposition === "assistant") {
|
|
222
|
+
assistantEvents(message, events, state);
|
|
223
|
+
}
|
|
224
|
+
else if (disposition === "tool-results") {
|
|
225
|
+
toolResultEvents(message, events, state);
|
|
226
|
+
}
|
|
227
|
+
else if (disposition === "auth") {
|
|
228
|
+
const error = nonblank(value.error);
|
|
229
|
+
if (error !== undefined)
|
|
230
|
+
events.emit(noteEvent(`Authentication warning: ${error}`));
|
|
231
|
+
}
|
|
232
|
+
else if (disposition === "note") {
|
|
233
|
+
events.emit(noteEvent("Conversation reset"));
|
|
234
|
+
}
|
|
235
|
+
else if (disposition === "system") {
|
|
236
|
+
emitSystemMessage(value, events);
|
|
237
|
+
}
|
|
238
|
+
}
|