@astrosheep/keiyaku 4.0.2 → 4.0.4
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 +1 -1
- package/build/integrations/marketplace/plugins/keiyaku/skills/keiyaku-bind/SKILL.md +5 -0
- package/build/integrations/marketplace/plugins/keiyaku/skills/keiyaku-task/SKILL.md +3 -1
- package/build/integrations/marketplace/plugins/keiyaku/skills/keiyaku-workflow/SKILL.md +9 -8
- package/build/src/akuma/akuma.d.ts +34 -11
- package/build/src/akuma/akuma.js +121 -61
- package/build/src/akuma/allowed.d.ts +7 -0
- package/build/src/akuma/allowed.js +40 -0
- package/build/src/akuma/archetype.d.ts +7 -4
- package/build/src/akuma/archetype.js +15 -16
- package/build/src/akuma/body.d.ts +3 -1
- package/build/src/akuma/body.js +19 -8
- package/build/src/akuma/heart/facts.d.ts +49 -7
- package/build/src/akuma/heart/index.d.ts +4 -8
- package/build/src/akuma/heart/index.js +76 -15
- package/build/src/akuma/heart/rows.d.ts +7 -5
- package/build/src/akuma/heart/rows.js +98 -11
- package/build/src/akuma/heart/schema.d.ts +1 -1
- package/build/src/akuma/heart/schema.js +15 -10
- package/build/src/akuma/heart/soul.js +3 -1
- package/build/src/akuma/heart/storage.d.ts +2 -0
- package/build/src/akuma/heart/storage.js +20 -0
- package/build/src/akuma/heart/timeline.d.ts +1 -5
- package/build/src/akuma/heart/timeline.js +8 -17
- package/build/src/akuma/index.d.ts +4 -1
- package/build/src/akuma/index.js +1 -0
- package/build/src/akuma/projection.d.ts +17 -3
- package/build/src/akuma/projection.js +40 -3
- package/build/src/akuma/provider.d.ts +1 -1
- package/build/src/akuma/provider.js +2 -1
- package/build/src/akuma/providers/acp/core.d.ts +3 -1
- package/build/src/akuma/providers/acp/core.js +4 -4
- package/build/src/akuma/providers/acp/events.d.ts +11 -3
- package/build/src/akuma/providers/acp/events.js +78 -4
- package/build/src/akuma/providers/claude/events.js +62 -3
- package/build/src/akuma/providers/codex-app-server/events.js +1 -13
- package/build/src/akuma/providers/grok-build/index.d.ts +2 -1
- package/build/src/akuma/providers/grok-build/index.js +72 -2
- package/build/src/akuma/providers/opencode-sdk/events.js +71 -6
- package/build/src/akuma/providers/pi/events.js +17 -3
- package/build/src/akuma/providers/unified-patch.d.ts +4 -0
- package/build/src/akuma/providers/unified-patch.js +14 -0
- package/build/src/akuma/publication.d.ts +1 -1
- package/build/src/akuma/publication.js +36 -7
- package/build/src/akuma/requests.d.ts +70 -4
- package/build/src/akuma/requests.js +288 -77
- package/build/src/akuma-body.d.ts +1 -0
- package/build/src/akuma-body.js +31 -0
- package/build/src/cli/accepted.d.ts +4 -1
- package/build/src/cli/accepted.js +26 -4
- package/build/src/cli/commands/akuma-invoke.d.ts +8 -7
- package/build/src/cli/commands/akuma-invoke.js +18 -10
- package/build/src/cli/commands/akuma.d.ts +11 -2
- package/build/src/cli/commands/akuma.js +67 -28
- package/build/src/cli/commands/amend.d.ts +1 -1
- package/build/src/cli/commands/amend.js +1 -1
- package/build/src/cli/commands/contract.d.ts +3 -3
- package/build/src/cli/commands/contract.js +1 -1
- package/build/src/cli/commands/install.js +4 -4
- package/build/src/cli/commands/task-query.d.ts +1 -1
- package/build/src/cli/commands/task-query.js +11 -5
- package/build/src/cli/commands/task.js +4 -4
- package/build/src/cli/coordinates.d.ts +1 -0
- package/build/src/cli/coordinates.js +5 -1
- package/build/src/cli/invoke.d.ts +1 -0
- package/build/src/cli/invoke.js +90 -73
- package/build/src/cli/main.d.ts +2 -0
- package/build/src/cli/main.js +29 -4
- package/build/src/cli/parse.d.ts +1 -0
- package/build/src/cli/parse.js +8 -4
- package/build/src/cli/render/akuma.d.ts +2 -2
- package/build/src/cli/render/akuma.js +84 -13
- package/build/src/cli/render/audit.js +16 -2
- package/build/src/cli/render/contract.d.ts +2 -0
- package/build/src/cli/render/contract.js +203 -36
- package/build/src/cli/render/kanshi.js +64 -29
- package/build/src/cli/render/receipt.d.ts +2 -1
- package/build/src/cli/render/receipt.js +9 -1
- package/build/src/cli/render/text.js +3 -1
- package/build/src/cli/result.d.ts +38 -3
- package/build/src/cli/selectors.d.ts +1 -0
- package/build/src/cli/selectors.js +9 -6
- package/build/src/cli/usage.d.ts +2 -0
- package/build/src/cli/usage.js +4 -0
- package/build/src/contract-worktree.js +3 -0
- package/build/src/dispatch/index.js +50 -39
- package/build/src/git/hooks.d.ts +0 -4
- package/build/src/git/hooks.js +0 -36
- package/build/src/git/integration.d.ts +2 -6
- package/build/src/git/integration.js +8 -8
- package/build/src/git/read-observation.js +1 -0
- package/build/src/git/reconcile.d.ts +6 -1
- package/build/src/git/reconcile.js +55 -9
- package/build/src/git/tender.d.ts +19 -4
- package/build/src/git/tender.js +44 -13
- package/build/src/git/terminal-seal.d.ts +14 -0
- package/build/src/git/terminal-seal.js +15 -2
- package/build/src/git/workspace.d.ts +1 -0
- package/build/src/git/workspace.js +12 -4
- package/build/src/index.d.ts +1 -1
- package/build/src/kanshi/index.d.ts +1 -1
- package/build/src/kanshi/index.js +1 -1
- package/build/src/kanshi/read.d.ts +9 -1
- package/build/src/kanshi/read.js +91 -37
- package/build/src/kanshi/report.d.ts +1 -0
- package/build/src/library/address.d.ts +5 -14
- package/build/src/library/address.js +20 -22
- package/build/src/library/akuma-creation.d.ts +4 -2
- package/build/src/library/akuma-creation.js +43 -26
- package/build/src/library/bind.js +3 -3
- package/build/src/library/catalog.d.ts +1 -2
- package/build/src/library/catalog.js +11 -6
- package/build/src/library/configuration.d.ts +4 -0
- package/build/src/library/configuration.js +19 -0
- package/build/src/library/contract.d.ts +15 -1
- package/build/src/library/contract.js +51 -4
- package/build/src/library/fleet.d.ts +47 -9
- package/build/src/library/fleet.js +184 -44
- package/build/src/library/input.js +1 -0
- package/build/src/library/keiyaku.d.ts +4 -4
- package/build/src/library/keiyaku.js +1 -2
- package/build/src/protocol/bind.d.ts +1 -2
- package/build/src/protocol/bind.js +2 -16
- package/build/src/protocol/operations.d.ts +4 -1
- package/build/src/protocol/operations.js +33 -27
- package/build/src/protocol/read/status.d.ts +3 -1
- package/build/src/protocol/read/status.js +24 -14
- package/build/src/runtime/proc/run.d.ts +1 -0
- package/build/src/runtime/proc/run.js +9 -2
- package/build/src/settlement/settle.d.ts +1 -0
- package/build/src/settlement/settle.js +4 -1
- package/build/src/task/board.d.ts +18 -3
- package/build/src/task/board.js +73 -26
- package/build/src/task/index.d.ts +3 -3
- package/build/src/task/index.js +7 -5
- package/build/src/task/operations.d.ts +2 -19
- package/build/src/task/operations.js +29 -7
- package/build/src/task/query.d.ts +9 -2
- package/build/src/task/query.js +32 -64
- package/build/src/verification/execution.js +2 -1
- package/package.json +3 -1
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import type { DatabaseSync } from "node:sqlite";
|
|
2
2
|
export declare function assertHeartSchemaVersion(database: DatabaseSync): void;
|
|
3
3
|
export declare function assertLeashSchemaVersion(database: DatabaseSync): void;
|
|
4
|
-
export declare const HEART_SCHEMA = "\n CREATE TABLE IF NOT EXISTS akuma_schema (\n singleton INTEGER PRIMARY KEY CHECK (singleton = 1),\n version INTEGER NOT NULL CHECK (version =
|
|
4
|
+
export declare const HEART_SCHEMA = "\n CREATE TABLE IF NOT EXISTS akuma_schema (\n singleton INTEGER PRIMARY KEY CHECK (singleton = 1),\n version INTEGER NOT NULL CHECK (version = 16)\n ) STRICT;\n INSERT OR IGNORE INTO akuma_schema(singleton, version) VALUES (1, 16);\n CREATE TABLE IF NOT EXISTS soul (\n singleton INTEGER PRIMARY KEY CHECK (singleton = 1),\n soul_json TEXT NOT NULL CHECK (json_valid(soul_json))\n ) STRICT;\n CREATE TABLE IF NOT EXISTS bodies (\n sequence INTEGER PRIMARY KEY AUTOINCREMENT,\n leash_taken_at TEXT NOT NULL,\n hung_diagnostic TEXT,\n hung_at TEXT,\n end TEXT CHECK (end IN ('exited', 'broke-off', 'put-down')),\n ended_at TEXT,\n CHECK ((hung_diagnostic IS NULL AND hung_at IS NULL) OR (hung_diagnostic IS NOT NULL AND hung_at IS NOT NULL AND end IS NULL))\n ) STRICT;\n CREATE TABLE IF NOT EXISTS sessions (\n sequence INTEGER PRIMARY KEY AUTOINCREMENT,\n provider TEXT NOT NULL,\n coordinate_json TEXT NOT NULL CHECK (json_valid(coordinate_json)),\n cwd TEXT NOT NULL,\n options_json TEXT NOT NULL CHECK (json_valid(options_json)),\n admitted_at TEXT NOT NULL\n ) STRICT;\n CREATE TABLE IF NOT EXISTS turns (\n sequence INTEGER PRIMARY KEY REFERENCES timeline(sequence) ON DELETE CASCADE,\n body_sequence INTEGER NOT NULL REFERENCES bodies(sequence),\n started_at TEXT NOT NULL,\n end_sequence INTEGER UNIQUE REFERENCES timeline(sequence) ON DELETE SET NULL,\n outcome TEXT CHECK (outcome IN ('answered', 'failed')),\n history_id TEXT UNIQUE,\n session_json TEXT CHECK (session_json IS NULL OR json_valid(session_json)),\n answer TEXT,\n diagnostic TEXT,\n completed_at TEXT,\n CHECK (\n (outcome = 'answered' AND session_json IS NOT NULL AND answer IS NOT NULL AND diagnostic IS NULL)\n OR (outcome = 'failed' AND history_id IS NULL AND session_json IS NULL AND answer IS NULL AND diagnostic IS NOT NULL)\n OR (outcome IS NULL AND end_sequence IS NULL AND history_id IS NULL AND session_json IS NULL AND answer IS NULL AND diagnostic IS NULL AND completed_at IS NULL)\n )\n ) STRICT;\n CREATE TABLE IF NOT EXISTS timeline (\n sequence INTEGER PRIMARY KEY AUTOINCREMENT,\n kind TEXT NOT NULL CHECK (kind IN ('turn-start', 'call', 'activity', 'tell', 'turn-end'))\n ) STRICT;\n CREATE TABLE IF NOT EXISTS calls (\n sequence INTEGER PRIMARY KEY REFERENCES timeline(sequence) ON DELETE CASCADE,\n turn_sequence INTEGER NOT NULL REFERENCES turns(sequence) ON DELETE CASCADE,\n body TEXT NOT NULL,\n at TEXT NOT NULL\n ) STRICT;\n CREATE TABLE IF NOT EXISTS activity (\n sequence INTEGER PRIMARY KEY REFERENCES timeline(sequence) ON DELETE CASCADE,\n turn_sequence INTEGER NOT NULL REFERENCES turns(sequence) ON DELETE CASCADE,\n event_json TEXT NOT NULL CHECK (json_valid(event_json)),\n at TEXT NOT NULL\n ) STRICT;\n CREATE TABLE IF NOT EXISTS tells (\n id TEXT PRIMARY KEY,\n sequence INTEGER NOT NULL UNIQUE REFERENCES timeline(sequence) ON DELETE CASCADE,\n body TEXT NOT NULL,\n recorded_at TEXT NOT NULL\n ) STRICT;\n CREATE TABLE IF NOT EXISTS tell_deliveries (\n sequence INTEGER PRIMARY KEY AUTOINCREMENT,\n tell_id TEXT NOT NULL REFERENCES tells(id) ON DELETE CASCADE,\n route TEXT NOT NULL CHECK (route IN ('launch', 'live')),\n turn_sequence INTEGER NOT NULL REFERENCES turns(sequence) ON DELETE CASCADE,\n fence TEXT NOT NULL,\n receipt TEXT CHECK (receipt IN ('unavailable', 'required')),\n delivered_at TEXT NOT NULL,\n CHECK ((route = 'launch' AND receipt IS NULL) OR (route = 'live' AND receipt IS NOT NULL)),\n UNIQUE (tell_id, turn_sequence, fence)\n ) STRICT;\n CREATE TABLE IF NOT EXISTS tell_receipts (\n sequence INTEGER PRIMARY KEY AUTOINCREMENT,\n evidence TEXT NOT NULL CHECK (evidence IN ('exact', 'fence')),\n tell_id TEXT REFERENCES tells(id) ON DELETE CASCADE,\n turn_sequence INTEGER REFERENCES turns(sequence) ON DELETE CASCADE,\n fence TEXT,\n kind TEXT NOT NULL,\n received_at TEXT NOT NULL,\n CHECK (\n (evidence = 'exact' AND tell_id IS NOT NULL AND turn_sequence IS NULL AND fence IS NULL)\n OR (evidence = 'fence' AND tell_id IS NULL AND turn_sequence IS NOT NULL AND fence IS NOT NULL)\n )\n ) STRICT;\n CREATE UNIQUE INDEX IF NOT EXISTS tell_receipts_exact\n ON tell_receipts(tell_id, kind) WHERE evidence = 'exact';\n CREATE UNIQUE INDEX IF NOT EXISTS tell_receipts_fence\n ON tell_receipts(turn_sequence, fence, kind) WHERE evidence = 'fence';\n CREATE TABLE IF NOT EXISTS requests (\n sequence INTEGER PRIMARY KEY AUTOINCREMENT,\n id TEXT NOT NULL UNIQUE,\n requester TEXT NOT NULL,\n action TEXT NOT NULL CHECK (action IN ('akuma.call', 'akuma.wait', 'akuma.tell', 'akuma.kill')),\n payload_json TEXT NOT NULL CHECK (json_valid(payload_json)),\n admitted_at TEXT NOT NULL,\n state TEXT NOT NULL CHECK (state IN ('admitted', 'reserved', 'served', 'refused', 'voided')),\n child TEXT,\n service_json TEXT CHECK (service_json IS NULL OR json_valid(service_json)),\n diagnostic TEXT,\n evidence TEXT,\n CHECK (\n (state = 'admitted' AND child IS NULL AND service_json IS NULL AND diagnostic IS NULL AND evidence IS NULL)\n OR (state = 'reserved' AND action = 'akuma.call'\n AND child IS NOT NULL AND service_json IS NULL\n AND diagnostic IS NULL AND evidence IS NULL)\n OR (state = 'served'\n AND ((action = 'akuma.call' AND child IS NOT NULL AND service_json IS NULL)\n OR (action != 'akuma.call' AND child IS NULL AND service_json IS NOT NULL))\n AND diagnostic IS NULL AND evidence IS NULL)\n OR (state = 'refused' AND child IS NULL AND service_json IS NULL AND diagnostic IS NOT NULL AND evidence IS NULL)\n OR (state = 'voided' AND child IS NULL AND service_json IS NULL AND diagnostic IS NULL AND evidence IS NOT NULL)\n )\n ) STRICT;\n CREATE TABLE IF NOT EXISTS control (\n kind TEXT PRIMARY KEY CHECK (kind IN ('stop', 'pause')),\n value_json TEXT NOT NULL CHECK (json_valid(value_json)),\n at TEXT NOT NULL\n ) STRICT;\n CREATE TABLE IF NOT EXISTS kills (\n sequence INTEGER PRIMARY KEY AUTOINCREMENT,\n body_sequence INTEGER NOT NULL UNIQUE REFERENCES bodies(sequence),\n evidence TEXT NOT NULL CHECK (evidence = 'killed'),\n at TEXT NOT NULL\n ) STRICT;\n";
|
|
5
5
|
export declare const LEASH_SCHEMA = "\n PRAGMA journal_mode=DELETE;\n CREATE TABLE IF NOT EXISTS leash_schema (\n singleton INTEGER PRIMARY KEY CHECK (singleton = 1),\n version INTEGER NOT NULL CHECK (version = 4)\n ) STRICT;\n INSERT OR IGNORE INTO leash_schema(singleton, version) VALUES (1, 4);\n CREATE TABLE IF NOT EXISTS seal (\n singleton INTEGER PRIMARY KEY CHECK (singleton = 1),\n evidence TEXT NOT NULL,\n at TEXT NOT NULL\n ) STRICT;\n";
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
const HEART_SCHEMA_VERSION =
|
|
1
|
+
const HEART_SCHEMA_VERSION = 16;
|
|
2
2
|
const LEASH_SCHEMA_VERSION = 4;
|
|
3
3
|
function assertSchemaVersion(database, table, expected) {
|
|
4
4
|
const row = database.prepare(`SELECT version FROM ${table} WHERE singleton = 1`).get();
|
|
@@ -108,21 +108,26 @@ export const HEART_SCHEMA = `
|
|
|
108
108
|
CREATE TABLE IF NOT EXISTS requests (
|
|
109
109
|
sequence INTEGER PRIMARY KEY AUTOINCREMENT,
|
|
110
110
|
id TEXT NOT NULL UNIQUE,
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
world TEXT NOT NULL,
|
|
115
|
-
recipe_json TEXT NOT NULL CHECK (json_valid(recipe_json)),
|
|
111
|
+
requester TEXT NOT NULL,
|
|
112
|
+
action TEXT NOT NULL CHECK (action IN ('akuma.call', 'akuma.wait', 'akuma.tell', 'akuma.kill')),
|
|
113
|
+
payload_json TEXT NOT NULL CHECK (json_valid(payload_json)),
|
|
116
114
|
admitted_at TEXT NOT NULL,
|
|
117
115
|
state TEXT NOT NULL CHECK (state IN ('admitted', 'reserved', 'served', 'refused', 'voided')),
|
|
118
116
|
child TEXT,
|
|
117
|
+
service_json TEXT CHECK (service_json IS NULL OR json_valid(service_json)),
|
|
119
118
|
diagnostic TEXT,
|
|
120
119
|
evidence TEXT,
|
|
121
120
|
CHECK (
|
|
122
|
-
(state = 'admitted' AND child IS NULL AND diagnostic IS NULL AND evidence IS NULL)
|
|
123
|
-
OR (state
|
|
124
|
-
|
|
125
|
-
|
|
121
|
+
(state = 'admitted' AND child IS NULL AND service_json IS NULL AND diagnostic IS NULL AND evidence IS NULL)
|
|
122
|
+
OR (state = 'reserved' AND action = 'akuma.call'
|
|
123
|
+
AND child IS NOT NULL AND service_json IS NULL
|
|
124
|
+
AND diagnostic IS NULL AND evidence IS NULL)
|
|
125
|
+
OR (state = 'served'
|
|
126
|
+
AND ((action = 'akuma.call' AND child IS NOT NULL AND service_json IS NULL)
|
|
127
|
+
OR (action != 'akuma.call' AND child IS NULL AND service_json IS NOT NULL))
|
|
128
|
+
AND diagnostic IS NULL AND evidence IS NULL)
|
|
129
|
+
OR (state = 'refused' AND child IS NULL AND service_json IS NULL AND diagnostic IS NOT NULL AND evidence IS NULL)
|
|
130
|
+
OR (state = 'voided' AND child IS NULL AND service_json IS NULL AND diagnostic IS NULL AND evidence IS NOT NULL)
|
|
126
131
|
)
|
|
127
132
|
) STRICT;
|
|
128
133
|
CREATE TABLE IF NOT EXISTS control (
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import { parseAkuId } from "../identity.js";
|
|
2
2
|
import { decodeProviderOptions, decodeProviderRecipe, decodeReadonlyRestraint, } from "../provider-recipe.js";
|
|
3
|
+
import { effectiveAllowedActions } from "../allowed.js";
|
|
3
4
|
function record(value) {
|
|
4
5
|
return value !== null && typeof value === "object" && !Array.isArray(value)
|
|
5
6
|
? value
|
|
@@ -78,7 +79,7 @@ function validateSoul(value) {
|
|
|
78
79
|
const soul = record(value);
|
|
79
80
|
if (soul === null)
|
|
80
81
|
throw new Error("Akuma soul must be an object");
|
|
81
|
-
if (unknownKey(soul, ["archetype", "confinement", "createdAt", "cwd", "description", "id", "options", "origin", "provider", "readonly"]) !== undefined) {
|
|
82
|
+
if (unknownKey(soul, ["allowed", "archetype", "confinement", "createdAt", "cwd", "description", "id", "options", "origin", "provider", "readonly"]) !== undefined) {
|
|
82
83
|
throw new Error("Akuma soul has an unknown field");
|
|
83
84
|
}
|
|
84
85
|
const identity = soulAkuIdentity(soul.id, "id");
|
|
@@ -103,6 +104,7 @@ function validateSoul(value) {
|
|
|
103
104
|
cwd: nonblank(soul.cwd, "cwd"),
|
|
104
105
|
origin: decodeOrigin(soul.origin),
|
|
105
106
|
confinement: decodeConfinement(soul.confinement),
|
|
107
|
+
allowed: effectiveAllowedActions(soul.allowed),
|
|
106
108
|
createdAt: nonblank(soul.createdAt, "createdAt"),
|
|
107
109
|
};
|
|
108
110
|
}
|
|
@@ -9,6 +9,7 @@ export declare class HeartAbsentError extends Error {
|
|
|
9
9
|
export declare function isHeartAbsent(error: unknown): error is HeartAbsentError;
|
|
10
10
|
export declare function withHeart<T>(paths: AkumaPaths, body: (database: DatabaseSync) => T): Promise<T>;
|
|
11
11
|
export declare function transaction<T>(database: DatabaseSync, body: () => T): T;
|
|
12
|
+
export declare function readTransaction<T>(database: DatabaseSync, body: () => T): T;
|
|
12
13
|
export declare function initializeHeart(paths: AkumaPaths): Promise<void>;
|
|
13
14
|
export declare class HeldAkumaLeash {
|
|
14
15
|
private readonly database;
|
|
@@ -21,6 +22,7 @@ export declare class HeldAkumaLeash {
|
|
|
21
22
|
evidence: string;
|
|
22
23
|
at: string;
|
|
23
24
|
}>): Promise<"born" | "sealed">;
|
|
25
|
+
readSeal(): ReturnType<typeof sealFact>;
|
|
24
26
|
clearPause(paths: AkumaPaths): Promise<void>;
|
|
25
27
|
clearStop(paths: AkumaPaths): Promise<void>;
|
|
26
28
|
recordBody(paths: AkumaPaths, input: Readonly<{
|
|
@@ -84,6 +84,21 @@ export function transaction(database, body) {
|
|
|
84
84
|
throw error;
|
|
85
85
|
}
|
|
86
86
|
}
|
|
87
|
+
export function readTransaction(database, body) {
|
|
88
|
+
database.exec("BEGIN DEFERRED");
|
|
89
|
+
try {
|
|
90
|
+
const result = body();
|
|
91
|
+
database.exec("COMMIT");
|
|
92
|
+
return result;
|
|
93
|
+
}
|
|
94
|
+
catch (error) {
|
|
95
|
+
try {
|
|
96
|
+
database.exec("ROLLBACK");
|
|
97
|
+
}
|
|
98
|
+
catch { /* preserve the original failure */ }
|
|
99
|
+
throw error;
|
|
100
|
+
}
|
|
101
|
+
}
|
|
87
102
|
export async function initializeHeart(paths) {
|
|
88
103
|
const heart = new DatabaseSync(paths.heart);
|
|
89
104
|
try {
|
|
@@ -136,6 +151,8 @@ export class HeldAkumaLeash {
|
|
|
136
151
|
}));
|
|
137
152
|
}
|
|
138
153
|
async sealIfUnborn(paths, input) {
|
|
154
|
+
if (sealExists(this.database))
|
|
155
|
+
return "sealed";
|
|
139
156
|
if (await withHeart(paths, (heart) => soulFact(heart)) !== null)
|
|
140
157
|
return "born";
|
|
141
158
|
insertSealFact(this.database, input);
|
|
@@ -144,6 +161,9 @@ export class HeldAkumaLeash {
|
|
|
144
161
|
this.database.close();
|
|
145
162
|
return "sealed";
|
|
146
163
|
}
|
|
164
|
+
readSeal() {
|
|
165
|
+
return sealFact(this.database);
|
|
166
|
+
}
|
|
147
167
|
async clearPause(paths) { await withHeart(paths, deletePauseControl); }
|
|
148
168
|
async clearStop(paths) { await withHeart(paths, deleteStopControl); }
|
|
149
169
|
async recordBody(paths, input) {
|
|
@@ -8,8 +8,4 @@ export type ActivityFactSlice = Readonly<{
|
|
|
8
8
|
highest: number | null;
|
|
9
9
|
}>;
|
|
10
10
|
export declare function pruneActivityFacts(database: DatabaseSync, limit: number): void;
|
|
11
|
-
export declare function activityFactSlice(database: DatabaseSync
|
|
12
|
-
before?: number;
|
|
13
|
-
since?: number;
|
|
14
|
-
limit: number;
|
|
15
|
-
}>): ActivityFactSlice;
|
|
11
|
+
export declare function activityFactSlice(database: DatabaseSync): ActivityFactSlice;
|
|
@@ -64,23 +64,14 @@ function decodeTimelineRow(database, row) {
|
|
|
64
64
|
}
|
|
65
65
|
return decodeTellAtSequence(database, row.sequence);
|
|
66
66
|
}
|
|
67
|
-
export function activityFactSlice(database
|
|
67
|
+
export function activityFactSlice(database) {
|
|
68
68
|
const bounds = database.prepare("SELECT MIN(sequence) AS lowest, MAX(sequence) AS highest FROM timeline")
|
|
69
69
|
.get();
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
rows = database.prepare(`SELECT sequence, kind FROM timeline WHERE sequence > ? ORDER BY sequence LIMIT ?`)
|
|
78
|
-
.all(input.since, input.limit);
|
|
79
|
-
}
|
|
80
|
-
else {
|
|
81
|
-
rows = database.prepare(`SELECT * FROM (
|
|
82
|
-
SELECT sequence, kind FROM timeline ORDER BY sequence DESC LIMIT ?
|
|
83
|
-
) ORDER BY sequence`).all(input.limit);
|
|
84
|
-
}
|
|
85
|
-
return { rows: rows.map((row) => decodeTimelineRow(database, row)), lowestRetained: bounds.lowest, highest: bounds.highest };
|
|
70
|
+
const rows = database.prepare("SELECT sequence, kind FROM timeline ORDER BY sequence")
|
|
71
|
+
.all();
|
|
72
|
+
return {
|
|
73
|
+
rows: rows.map((row) => decodeTimelineRow(database, row)),
|
|
74
|
+
lowestRetained: bounds.lowest,
|
|
75
|
+
highest: bounds.highest,
|
|
76
|
+
};
|
|
86
77
|
}
|
|
@@ -1,13 +1,16 @@
|
|
|
1
1
|
import { type AkuId } from "./identity.js";
|
|
2
2
|
import type { WorldRoot } from "../world.js";
|
|
3
3
|
export { Akuma, AkumaHandle, AkumaNotBornError } from "./akuma.js";
|
|
4
|
+
export type { AkumaCallInput } from "./akuma.js";
|
|
5
|
+
export { ALLOWED_ACTIONS } from "./allowed.js";
|
|
6
|
+
export type { AllowedAction, AllowedActions } from "./allowed.js";
|
|
4
7
|
export type { WorldRoot } from "../world.js";
|
|
5
8
|
export declare function probeBornAkuma(worldPath: WorldRoot, id: AkuId): Promise<boolean>;
|
|
6
9
|
export { AkumaArchetypeError, listArchetypeDefinitions, listArchetypes } from "./archetype.js";
|
|
7
10
|
export type { ArchetypeCatalogRow } from "./archetype.js";
|
|
8
11
|
export { AkumaBodyRequestError } from "./requests.js";
|
|
9
12
|
export type { AkumaList, AkumaListInput, AkumaListRow, AkumaStatus, ActivityHistory, ActivityRow, ActivitySnapshot, ActivitySnapshotEntry, ForkReceipt, InterruptReceipt, TellResult, UnbornAkumaListRow, } from "./akuma.js";
|
|
10
|
-
export type { ActiveToolRow, ClosedTurn, ClosedTurnRow, CompletedToolRow, HistoryCursor, HistoryPage, IdleSnapshotRow, OpenSnapshotRow, OpenTurn, OpenTurnRow, OutcomeRow, RetainedWindow, Snapshot, SnapshotRow, TellRow, TurnLedger, TurnOutcome, TurnStartRow, UnsettledToolRow, } from "./akuma.js";
|
|
13
|
+
export type { ActiveToolRow, ClosedTurn, ClosedTurnRow, CompletedToolRow, HistoryCursor, HistoryPage, IdleSnapshotRow, OpenSnapshotRow, OpenTurn, OpenTurnRow, OutcomeRow, ReportedFileChange, RetainedWindow, Snapshot, SnapshotRow, TellRow, TurnLedger, TurnOutcome, TurnStartRow, UnsettledToolRow, } from "./akuma.js";
|
|
11
14
|
export type { AgentEvent, ToolCall, ToolEvent, ToolResult } from "./provider.js";
|
|
12
15
|
export type { AkuId } from "./identity.js";
|
|
13
16
|
export type { Confinement, KillEvidence, ResumeCoordinate, TurnFact, } from "./heart/index.js";
|
package/build/src/akuma/index.js
CHANGED
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import { readSoul } from "./heart/index.js";
|
|
2
2
|
import { pathsForAkuId } from "./identity.js";
|
|
3
3
|
export { Akuma, AkumaHandle, AkumaNotBornError } from "./akuma.js";
|
|
4
|
+
export { ALLOWED_ACTIONS } from "./allowed.js";
|
|
4
5
|
export async function probeBornAkuma(worldPath, id) {
|
|
5
6
|
const soul = await readSoul(pathsForAkuId(worldPath, id));
|
|
6
7
|
return soul !== null;
|
|
@@ -114,21 +114,35 @@ export type ActivitySnapshotEntry<Row extends SnapshotRow = SnapshotRow> = Reado
|
|
|
114
114
|
kind: "gap";
|
|
115
115
|
count: number;
|
|
116
116
|
}>;
|
|
117
|
+
export type ReportedFileChange = Readonly<{
|
|
118
|
+
sequence: number;
|
|
119
|
+
at: string;
|
|
120
|
+
op: "add" | "update" | "delete" | "unspecified";
|
|
121
|
+
path: string;
|
|
122
|
+
diffstat?: Readonly<{
|
|
123
|
+
added: number;
|
|
124
|
+
removed: number;
|
|
125
|
+
}>;
|
|
126
|
+
}>;
|
|
127
|
+
type ReportedChangeSummary = Readonly<{
|
|
128
|
+
reportedChanges: readonly ReportedFileChange[];
|
|
129
|
+
reportedChangesOmitted: number;
|
|
130
|
+
}>;
|
|
117
131
|
export type Snapshot = Readonly<{
|
|
118
132
|
kind: "unborn";
|
|
119
133
|
entries: readonly [];
|
|
120
134
|
omitted: 0;
|
|
121
|
-
}> | Readonly<{
|
|
135
|
+
} & ReportedChangeSummary> | Readonly<{
|
|
122
136
|
kind: "open";
|
|
123
137
|
turn: TurnStartRow;
|
|
124
138
|
entries: readonly ActivitySnapshotEntry<OpenSnapshotRow>[];
|
|
125
139
|
omitted: number;
|
|
126
|
-
}> | Readonly<{
|
|
140
|
+
} & ReportedChangeSummary> | Readonly<{
|
|
127
141
|
kind: "idle";
|
|
128
142
|
outcome?: OutcomeRow;
|
|
129
143
|
entries: readonly ActivitySnapshotEntry<IdleSnapshotRow>[];
|
|
130
144
|
omitted: number;
|
|
131
|
-
}>;
|
|
145
|
+
} & ReportedChangeSummary>;
|
|
132
146
|
export type ActivitySnapshot = Snapshot;
|
|
133
147
|
export type ActivityHistory = Readonly<{
|
|
134
148
|
rows: readonly ActivityRow[];
|
|
@@ -1,6 +1,8 @@
|
|
|
1
1
|
import { decodeAgentEvent } from "./provider.js";
|
|
2
2
|
const DEFAULT_TAIL = 3;
|
|
3
3
|
const DEFAULT_VOICE = 3;
|
|
4
|
+
const REPORTED_CHANGE_LIMIT = 5;
|
|
5
|
+
const EMPTY_REPORTED = { reportedChanges: [], reportedChangesOmitted: 0 };
|
|
4
6
|
function duration(startedAt, completedAt) {
|
|
5
7
|
const start = Date.parse(startedAt);
|
|
6
8
|
const end = Date.parse(completedAt);
|
|
@@ -204,11 +206,45 @@ export function ordinarySelectedCount(snapshot) {
|
|
|
204
206
|
return count + 1;
|
|
205
207
|
}, 0);
|
|
206
208
|
}
|
|
209
|
+
function isSuccessfulFileChange(row) {
|
|
210
|
+
return row.kind === "tool"
|
|
211
|
+
&& typeof row.state === "object"
|
|
212
|
+
&& row.state.status === "ok"
|
|
213
|
+
&& row.call.kind === "fileChange";
|
|
214
|
+
}
|
|
215
|
+
function flattenReportedChanges(rows) {
|
|
216
|
+
return rows.flatMap((row) => {
|
|
217
|
+
if (!isSuccessfulFileChange(row))
|
|
218
|
+
return [];
|
|
219
|
+
return row.call.changes.map((change) => ({
|
|
220
|
+
sequence: row.sequence,
|
|
221
|
+
at: row.at,
|
|
222
|
+
op: change.op,
|
|
223
|
+
path: change.path,
|
|
224
|
+
...(change.diffstat === undefined ? {} : { diffstat: change.diffstat }),
|
|
225
|
+
}));
|
|
226
|
+
});
|
|
227
|
+
}
|
|
228
|
+
function summarizeReportedChanges(rows) {
|
|
229
|
+
const eligible = flattenReportedChanges(rows);
|
|
230
|
+
return {
|
|
231
|
+
reportedChanges: eligible.slice(-REPORTED_CHANGE_LIMIT),
|
|
232
|
+
reportedChangesOmitted: Math.max(0, eligible.length - REPORTED_CHANGE_LIMIT),
|
|
233
|
+
};
|
|
234
|
+
}
|
|
235
|
+
function frontierReportedChanges(ledger) {
|
|
236
|
+
if (ledger.openTurn !== undefined)
|
|
237
|
+
return summarizeReportedChanges(ledger.openTurn.rows);
|
|
238
|
+
const closed = ledger.turns.findLast((turn) => turn.kind === "closed");
|
|
239
|
+
return closed === undefined ? EMPTY_REPORTED : summarizeReportedChanges(closed.rows);
|
|
240
|
+
}
|
|
207
241
|
/** Select one current Turn, one latest outcome, or no focus; pending tells stay actionable. */
|
|
208
242
|
export function selectSnapshot(ledger, budget = { tail: DEFAULT_TAIL, voice: DEFAULT_VOICE }) {
|
|
209
|
-
if (ledger.turns.length === 0 && !ledger.rows.some((row) => row.kind === "tell"))
|
|
210
|
-
return { kind: "unborn", entries: [], omitted: 0 };
|
|
243
|
+
if (ledger.turns.length === 0 && !ledger.rows.some((row) => row.kind === "tell")) {
|
|
244
|
+
return { kind: "unborn", entries: [], omitted: 0, ...EMPTY_REPORTED };
|
|
245
|
+
}
|
|
211
246
|
const pending = ledger.rows.filter((row) => row.kind === "tell" && row.state === "pending");
|
|
247
|
+
const reported = frontierReportedChanges(ledger);
|
|
212
248
|
if (ledger.openTurn !== undefined) {
|
|
213
249
|
const window = ledger.openTurn.rows;
|
|
214
250
|
const active = window.filter((row) => row.kind === "tool" && row.state === "active");
|
|
@@ -222,13 +258,14 @@ export function selectSnapshot(ledger, budget = { tail: DEFAULT_TAIL, voice: DEF
|
|
|
222
258
|
const voice = voiceCount === 0 ? [] : voiceCandidates.slice(-voiceCount);
|
|
223
259
|
const selected = new Set([...tail, ...voice, ...active, ...pending]);
|
|
224
260
|
const omitted = window.filter((row) => !selected.has(row)).length;
|
|
225
|
-
return { kind: "open", turn: ledger.openTurn.turn, entries: openEntries(ledger, window, selected), omitted };
|
|
261
|
+
return { kind: "open", turn: ledger.openTurn.turn, entries: openEntries(ledger, window, selected), omitted, ...reported };
|
|
226
262
|
}
|
|
227
263
|
return {
|
|
228
264
|
kind: "idle",
|
|
229
265
|
...(ledger.latestOutcome === undefined ? {} : { outcome: ledger.latestOutcome }),
|
|
230
266
|
entries: entries(pending),
|
|
231
267
|
omitted: 0,
|
|
268
|
+
...reported,
|
|
232
269
|
};
|
|
233
270
|
}
|
|
234
271
|
/** Apply the one snapshot policy used by every public observation consumer. */
|
|
@@ -87,7 +87,8 @@ function decodeDiffstat(value) {
|
|
|
87
87
|
}
|
|
88
88
|
function decodeFileChangeMember(value) {
|
|
89
89
|
const change = object(value);
|
|
90
|
-
if (change === null || (change.op !== "add" && change.op !== "update"
|
|
90
|
+
if (change === null || (change.op !== "add" && change.op !== "update"
|
|
91
|
+
&& change.op !== "delete" && change.op !== "unspecified")
|
|
91
92
|
|| typeof change.path !== "string")
|
|
92
93
|
return null;
|
|
93
94
|
const diffstat = decodeDiffstat(change.diffstat);
|
|
@@ -1,9 +1,12 @@
|
|
|
1
1
|
import * as acp from "@agentclientprotocol/sdk";
|
|
2
2
|
import { spawnStdioProcess } from "../../../runtime/proc/stdio.js";
|
|
3
3
|
import { type ProviderAdapter, type Session } from "../../provider.js";
|
|
4
|
+
import { type AcpToolInterpreter, type AcpToolUpdate } from "./events.js";
|
|
5
|
+
export type { AcpToolInterpreter, AcpToolUpdate };
|
|
4
6
|
export type AcpStartInput = Parameters<ProviderAdapter["start"]>[0] | Parameters<NonNullable<ProviderAdapter["resume"]>>[0];
|
|
5
7
|
export type AcpDependencies = Readonly<{
|
|
6
8
|
spawnProcess?: typeof spawnStdioProcess;
|
|
9
|
+
interpretTool?: AcpToolInterpreter;
|
|
7
10
|
}>;
|
|
8
11
|
export type AcpLiveSession = Readonly<{
|
|
9
12
|
session: Session;
|
|
@@ -16,4 +19,3 @@ type AcpLaunch = Readonly<{
|
|
|
16
19
|
env?: Readonly<Record<string, string>>;
|
|
17
20
|
}>;
|
|
18
21
|
export declare function startAcpSession(launch: AcpLaunch, input: AcpStartInput, dependencies?: AcpDependencies): Promise<AcpLiveSession>;
|
|
19
|
-
export {};
|
|
@@ -3,7 +3,7 @@ import { Readable, Writable } from "node:stream";
|
|
|
3
3
|
import { spawnStdioProcess } from "../../../runtime/proc/stdio.js";
|
|
4
4
|
import { abortable } from "../../abort.js";
|
|
5
5
|
import { AgentEventChannel, } from "../../provider.js";
|
|
6
|
-
import { EMPTY_ACP_EVENT_STATE, flushAcpEvents, mapAcpUpdate } from "./events.js";
|
|
6
|
+
import { EMPTY_ACP_EVENT_STATE, flushAcpEvents, mapAcpUpdate, } from "./events.js";
|
|
7
7
|
function diagnostic(error) {
|
|
8
8
|
if (!(error instanceof Error))
|
|
9
9
|
return String(error);
|
|
@@ -46,7 +46,7 @@ async function establishSession(agent, input) {
|
|
|
46
46
|
await agent.request(acp.methods.agent.session.load, { cwd: input.cwd, mcpServers: [], sessionId });
|
|
47
47
|
return sessionId;
|
|
48
48
|
}
|
|
49
|
-
function createAcpTurn(connection) {
|
|
49
|
+
function createAcpTurn(connection, interpret) {
|
|
50
50
|
const events = new AgentEventChannel();
|
|
51
51
|
let state = EMPTY_ACP_EVENT_STATE;
|
|
52
52
|
let terminal = false;
|
|
@@ -55,7 +55,7 @@ function createAcpTurn(connection) {
|
|
|
55
55
|
const update = (next) => {
|
|
56
56
|
if (terminal)
|
|
57
57
|
return;
|
|
58
|
-
const mapped = mapAcpUpdate(next, state);
|
|
58
|
+
const mapped = mapAcpUpdate(next, state, interpret);
|
|
59
59
|
state = mapped.state;
|
|
60
60
|
for (const event of mapped.events)
|
|
61
61
|
events.emit(event);
|
|
@@ -138,7 +138,7 @@ export async function startAcpSession(launch, input, dependencies = {}) {
|
|
|
138
138
|
if (notification.sessionId === sessionId)
|
|
139
139
|
turn.update(notification.update);
|
|
140
140
|
}).connect(acp.ndJsonStream(Writable.toWeb(child.input), Readable.toWeb(child.output)));
|
|
141
|
-
turn = createAcpTurn(connection);
|
|
141
|
+
turn = createAcpTurn(connection, dependencies.interpretTool);
|
|
142
142
|
try {
|
|
143
143
|
sessionId = await abortable(establishSession(connection.agent, input), signal);
|
|
144
144
|
turn.events.emit({ type: "session", coordinate: { sessionId } });
|
|
@@ -1,14 +1,22 @@
|
|
|
1
1
|
import type { SessionUpdate } from "@agentclientprotocol/sdk";
|
|
2
|
-
import { type AgentEvent } from "../../provider.js";
|
|
2
|
+
import { type AgentEvent, type ToolCall } from "../../provider.js";
|
|
3
3
|
type OpenBlock = Readonly<{
|
|
4
4
|
type: "assistant" | "thought";
|
|
5
5
|
text: string;
|
|
6
6
|
}> | null;
|
|
7
|
+
export type AcpToolObservation = Readonly<{
|
|
8
|
+
name: string;
|
|
9
|
+
call: ToolCall;
|
|
10
|
+
}>;
|
|
11
|
+
export type AcpToolUpdate = Extract<SessionUpdate, {
|
|
12
|
+
sessionUpdate: "tool_call" | "tool_call_update";
|
|
13
|
+
}>;
|
|
14
|
+
export type AcpToolInterpreter = (update: AcpToolUpdate) => ToolCall | undefined;
|
|
7
15
|
export type AcpEventState = Readonly<{
|
|
8
16
|
answer: string;
|
|
9
17
|
assistantMessageId: string | null;
|
|
10
18
|
open: OpenBlock;
|
|
11
|
-
tools: ReadonlyMap<string,
|
|
19
|
+
tools: ReadonlyMap<string, AcpToolObservation>;
|
|
12
20
|
}>;
|
|
13
21
|
type AcpEventMapping = Readonly<{
|
|
14
22
|
events: readonly AgentEvent[];
|
|
@@ -16,5 +24,5 @@ type AcpEventMapping = Readonly<{
|
|
|
16
24
|
}>;
|
|
17
25
|
export declare const EMPTY_ACP_EVENT_STATE: AcpEventState;
|
|
18
26
|
export declare function flushAcpEvents(state: AcpEventState, boundary?: readonly AgentEvent[]): AcpEventMapping;
|
|
19
|
-
export declare function mapAcpUpdate(update: SessionUpdate, previous: AcpEventState): AcpEventMapping;
|
|
27
|
+
export declare function mapAcpUpdate(update: SessionUpdate, previous: AcpEventState, interpret?: AcpToolInterpreter): AcpEventMapping;
|
|
20
28
|
export {};
|
|
@@ -33,7 +33,82 @@ function appendBlock(state, type, text, messageId) {
|
|
|
33
33
|
},
|
|
34
34
|
};
|
|
35
35
|
}
|
|
36
|
-
|
|
36
|
+
function nonblank(value) {
|
|
37
|
+
return typeof value === "string" && value.trim().length > 0 ? value : undefined;
|
|
38
|
+
}
|
|
39
|
+
function object(value) {
|
|
40
|
+
return value !== null && typeof value === "object" && !Array.isArray(value)
|
|
41
|
+
? value
|
|
42
|
+
: undefined;
|
|
43
|
+
}
|
|
44
|
+
function positiveLine(value) {
|
|
45
|
+
return typeof value === "number" && Number.isSafeInteger(value) && value >= 1
|
|
46
|
+
? value
|
|
47
|
+
: undefined;
|
|
48
|
+
}
|
|
49
|
+
function otherCall(name) {
|
|
50
|
+
return { kind: "other", display: name };
|
|
51
|
+
}
|
|
52
|
+
function locationFact(locations) {
|
|
53
|
+
if (locations === null || locations === undefined)
|
|
54
|
+
return {};
|
|
55
|
+
for (const location of locations) {
|
|
56
|
+
const path = nonblank(location.path);
|
|
57
|
+
if (path === undefined)
|
|
58
|
+
continue;
|
|
59
|
+
const offset = positiveLine(location.line);
|
|
60
|
+
return offset === undefined ? { path } : { path, offset };
|
|
61
|
+
}
|
|
62
|
+
return {};
|
|
63
|
+
}
|
|
64
|
+
function standardCall(update, name) {
|
|
65
|
+
const location = locationFact(update.locations);
|
|
66
|
+
if (update.kind === "read") {
|
|
67
|
+
return location.path === undefined
|
|
68
|
+
? otherCall(name)
|
|
69
|
+
: {
|
|
70
|
+
kind: "read",
|
|
71
|
+
path: location.path,
|
|
72
|
+
...(location.offset === undefined ? {} : { offset: location.offset }),
|
|
73
|
+
};
|
|
74
|
+
}
|
|
75
|
+
const input = object(update.rawInput);
|
|
76
|
+
if (update.kind === "execute") {
|
|
77
|
+
const command = nonblank(input?.command);
|
|
78
|
+
return command === undefined ? otherCall(name) : { kind: "run", command };
|
|
79
|
+
}
|
|
80
|
+
if (update.kind === "search") {
|
|
81
|
+
const query = nonblank(input?.query);
|
|
82
|
+
if (query === undefined)
|
|
83
|
+
return otherCall(name);
|
|
84
|
+
const path = nonblank(input?.path);
|
|
85
|
+
const glob = nonblank(input?.glob);
|
|
86
|
+
const scope = input?.scope;
|
|
87
|
+
const searchScope = scope === "content" || scope === "files" || scope === "web"
|
|
88
|
+
? scope
|
|
89
|
+
: undefined;
|
|
90
|
+
return {
|
|
91
|
+
kind: "search",
|
|
92
|
+
query,
|
|
93
|
+
...(searchScope === undefined ? {} : { scope: searchScope }),
|
|
94
|
+
...(path === undefined ? {} : { path }),
|
|
95
|
+
...(glob === undefined ? {} : { glob }),
|
|
96
|
+
};
|
|
97
|
+
}
|
|
98
|
+
return otherCall(name);
|
|
99
|
+
}
|
|
100
|
+
function strongest(previous, next) {
|
|
101
|
+
if (previous === undefined || previous.kind === "other")
|
|
102
|
+
return next;
|
|
103
|
+
return next.kind === "other" ? previous : next;
|
|
104
|
+
}
|
|
105
|
+
function observeTool(update, previous, interpret) {
|
|
106
|
+
const name = nonblank(update.name) ?? nonblank(update.title) ?? previous?.name ?? "ACP tool";
|
|
107
|
+
const standard = standardCall(update, name);
|
|
108
|
+
const dialect = standard.kind === "other" ? interpret?.(update) : undefined;
|
|
109
|
+
return { name, call: strongest(previous?.call, dialect ?? standard) };
|
|
110
|
+
}
|
|
111
|
+
export function mapAcpUpdate(update, previous, interpret) {
|
|
37
112
|
switch (update.sessionUpdate) {
|
|
38
113
|
case "agent_message_chunk":
|
|
39
114
|
case "agent_thought_chunk": {
|
|
@@ -47,15 +122,14 @@ export function mapAcpUpdate(update, previous) {
|
|
|
47
122
|
case "user_message_chunk": return flushAcpEvents(previous);
|
|
48
123
|
case "tool_call":
|
|
49
124
|
case "tool_call_update": {
|
|
50
|
-
const
|
|
51
|
-
const observed = { name, call: { kind: "other", display: name } };
|
|
125
|
+
const observed = observeTool(update, previous.tools.get(update.toolCallId), interpret);
|
|
52
126
|
const tools = new Map(previous.tools);
|
|
53
127
|
if (update.status === "completed" || update.status === "failed") {
|
|
54
128
|
tools.delete(update.toolCallId);
|
|
55
129
|
return flushAcpEvents({ ...previous, tools }, [{ type: "tool", phase: "completed", id: update.toolCallId, ...observed, result: { status: update.status === "completed" ? "ok" : "error" } }]);
|
|
56
130
|
}
|
|
57
131
|
const started = tools.has(update.toolCallId);
|
|
58
|
-
tools.set(update.toolCallId,
|
|
132
|
+
tools.set(update.toolCallId, observed);
|
|
59
133
|
return started
|
|
60
134
|
? flushAcpEvents({ ...previous, tools })
|
|
61
135
|
: flushAcpEvents({ ...previous, tools }, [{ type: "tool", phase: "started", id: update.toolCallId, ...observed }]);
|
|
@@ -141,12 +141,63 @@ function fileChangeCall(name, path) {
|
|
|
141
141
|
}
|
|
142
142
|
return undefined;
|
|
143
143
|
}
|
|
144
|
+
function startedPath(name, value) {
|
|
145
|
+
if (name === "NotebookEdit")
|
|
146
|
+
return nonblank(value.notebook_path);
|
|
147
|
+
return nonblank(value.file_path) ?? (name === "Read" ? nonblank(value.path) : undefined);
|
|
148
|
+
}
|
|
149
|
+
function nonnegativeInt(value) {
|
|
150
|
+
return typeof value === "number" && Number.isInteger(value) && value >= 0 ? value : undefined;
|
|
151
|
+
}
|
|
152
|
+
function fileChange(op, path, gitDiff) {
|
|
153
|
+
const diff = object(gitDiff);
|
|
154
|
+
const added = nonnegativeInt(diff?.additions);
|
|
155
|
+
const removed = nonnegativeInt(diff?.deletions);
|
|
156
|
+
const diffstat = added === undefined || removed === undefined ? undefined : { added, removed };
|
|
157
|
+
return {
|
|
158
|
+
kind: "fileChange",
|
|
159
|
+
changes: [{ op, path, ...(diffstat === undefined ? {} : { diffstat }) }],
|
|
160
|
+
};
|
|
161
|
+
}
|
|
162
|
+
function writeResult(result) {
|
|
163
|
+
const value = object(result);
|
|
164
|
+
if (value === undefined || (value.type !== "create" && value.type !== "update"))
|
|
165
|
+
return undefined;
|
|
166
|
+
const path = nonblank(value.filePath);
|
|
167
|
+
if (path === undefined)
|
|
168
|
+
return undefined;
|
|
169
|
+
const op = value.type === "create" ? "add" : "update";
|
|
170
|
+
return fileChange(op, path, value.gitDiff);
|
|
171
|
+
}
|
|
172
|
+
function editResult(result) {
|
|
173
|
+
const value = object(result);
|
|
174
|
+
if (value === undefined || value.type === "create" || value.type === "update")
|
|
175
|
+
return undefined;
|
|
176
|
+
const path = nonblank(value.filePath);
|
|
177
|
+
if (path === undefined)
|
|
178
|
+
return undefined;
|
|
179
|
+
const status = object(value.gitDiff)?.status;
|
|
180
|
+
return fileChange(status === "added" ? "add" : "update", path, value.gitDiff);
|
|
181
|
+
}
|
|
182
|
+
function notebookResult(result) {
|
|
183
|
+
const path = nonblank(object(result)?.notebook_path);
|
|
184
|
+
return path === undefined
|
|
185
|
+
? undefined
|
|
186
|
+
: { kind: "fileChange", changes: [{ op: "update", path }] };
|
|
187
|
+
}
|
|
188
|
+
function structuredFileChange(name, result) {
|
|
189
|
+
if (name === "Write")
|
|
190
|
+
return writeResult(result);
|
|
191
|
+
if (name === "Edit")
|
|
192
|
+
return editResult(result);
|
|
193
|
+
return name === "NotebookEdit" ? notebookResult(result) : undefined;
|
|
194
|
+
}
|
|
144
195
|
function toolCall(name, input) {
|
|
145
196
|
const value = object(input) ?? {};
|
|
146
197
|
const run = runCall(name, value);
|
|
147
198
|
if (run !== undefined)
|
|
148
199
|
return run;
|
|
149
|
-
const path =
|
|
200
|
+
const path = startedPath(name, value);
|
|
150
201
|
return readCall(name, path, value)
|
|
151
202
|
?? searchCall(name, value)
|
|
152
203
|
?? fileChangeCall(name, path)
|
|
@@ -192,9 +243,17 @@ function toolResultEvents(message, events, state) {
|
|
|
192
243
|
if (observed === undefined)
|
|
193
244
|
continue;
|
|
194
245
|
state.tools.delete(id);
|
|
246
|
+
const failed = value.is_error === true;
|
|
247
|
+
const structured = failed
|
|
248
|
+
? undefined
|
|
249
|
+
: structuredFileChange(observed.name, message.tool_use_result);
|
|
195
250
|
events.emit({
|
|
196
|
-
type: "tool",
|
|
197
|
-
|
|
251
|
+
type: "tool",
|
|
252
|
+
phase: "completed",
|
|
253
|
+
id,
|
|
254
|
+
name: observed.name,
|
|
255
|
+
call: structured ?? observed.call,
|
|
256
|
+
result: { status: failed ? "error" : "ok" },
|
|
198
257
|
});
|
|
199
258
|
}
|
|
200
259
|
}
|