@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,13 +1,14 @@
|
|
|
1
1
|
import { randomUUID } from "node:crypto";
|
|
2
2
|
import { access, mkdir, readFile, readdir, rename, rm, writeFile } from "node:fs/promises";
|
|
3
3
|
import { isAbsolute, join, resolve } from "node:path";
|
|
4
|
-
import { HeldAkumaLeash, admitRequest, readNonterminalRequests, readRequest, readSoul, refuseRequest, reserveRequest, serveRequest, voidRequest, } from "./heart/index.js";
|
|
4
|
+
import { HeldAkumaLeash, admitRequest, readNonterminalRequests, readRequest, readSoul, refuseRequest, reserveRequest, serveRequest, serveUpstreamRequest, voidRequest, } from "./heart/index.js";
|
|
5
5
|
import { parseAkuId, pathsForAkuId, archetypeName, worldRootForAkumaPaths, } from "./identity.js";
|
|
6
6
|
import { BIRTH_TIMEOUT_MS, publishAkuma } from "./publication.js";
|
|
7
7
|
import { abortableDelay } from "./abort.js";
|
|
8
8
|
import { AKUMA_REQUESTS_ENV } from "./provider.js";
|
|
9
9
|
import { decodeProviderOptions, decodeReadonlyRestraint } from "./provider-recipe.js";
|
|
10
10
|
import { resolveProviderExecution } from "./providers/index.js";
|
|
11
|
+
import { decodeAllowedActions } from "./allowed.js";
|
|
11
12
|
const POLL_MS = 25;
|
|
12
13
|
const UUID = /^[0-9a-f]{8}-[0-9a-f]{4}-4[0-9a-f]{3}-[89ab][0-9a-f]{3}-[0-9a-f]{12}$/u;
|
|
13
14
|
export class AkumaBodyRequestError extends Error {
|
|
@@ -36,24 +37,13 @@ function exactKeys(value, expected) {
|
|
|
36
37
|
function absolute(value) {
|
|
37
38
|
return typeof value === "string" && isAbsolute(value) && resolve(value) === value;
|
|
38
39
|
}
|
|
39
|
-
function matchingConfinement(value, expected) {
|
|
40
|
-
const confinement = object(value);
|
|
41
|
-
if (confinement === null || confinement.kind !== expected.kind)
|
|
42
|
-
return false;
|
|
43
|
-
if (expected.kind === "unconfined")
|
|
44
|
-
return exactKeys(confinement, ["kind"]);
|
|
45
|
-
if (!exactKeys(confinement, ["kind", "writableRoots"]) || !Array.isArray(confinement.writableRoots))
|
|
46
|
-
return false;
|
|
47
|
-
return confinement.writableRoots.length === expected.writableRoots.length
|
|
48
|
-
&& confinement.writableRoots.every((root, index) => root === expected.writableRoots[index]);
|
|
49
|
-
}
|
|
50
40
|
function decodeRecipe(value, cwd) {
|
|
51
41
|
const recipe = object(value);
|
|
52
42
|
if (recipe === null)
|
|
53
43
|
return null;
|
|
54
44
|
const expectedKeys = [
|
|
55
|
-
"confinement",
|
|
56
45
|
...(recipe.description === undefined ? [] : ["description"]),
|
|
46
|
+
"allowed",
|
|
57
47
|
"options",
|
|
58
48
|
"provider",
|
|
59
49
|
...(recipe.readonly === undefined ? [] : ["readonly"]),
|
|
@@ -72,10 +62,9 @@ function decodeRecipe(value, cwd) {
|
|
|
72
62
|
if ((decodedOptions.readonly === true) !== (readonly !== undefined))
|
|
73
63
|
return null;
|
|
74
64
|
const confinement = adapter.confinement({ cwd, options: decodedOptions });
|
|
75
|
-
if (!matchingConfinement(recipe.confinement, confinement))
|
|
76
|
-
return null;
|
|
77
65
|
return Object.freeze({
|
|
78
66
|
...(recipe.description === undefined ? {} : { description: recipe.description }),
|
|
67
|
+
allowed: decodeAllowedActions(recipe.allowed),
|
|
79
68
|
provider,
|
|
80
69
|
options: decodedOptions,
|
|
81
70
|
...(readonly === undefined ? {} : { readonly }),
|
|
@@ -86,52 +75,98 @@ function decodeRecipe(value, cwd) {
|
|
|
86
75
|
return null;
|
|
87
76
|
}
|
|
88
77
|
}
|
|
89
|
-
function
|
|
90
|
-
|
|
78
|
+
function canonicalAkuId(value) {
|
|
79
|
+
if (typeof value !== "string")
|
|
80
|
+
return null;
|
|
91
81
|
try {
|
|
92
|
-
|
|
82
|
+
const id = parseAkuId(value).id;
|
|
83
|
+
return id === value ? id : null;
|
|
93
84
|
}
|
|
94
85
|
catch {
|
|
95
86
|
return null;
|
|
96
87
|
}
|
|
97
|
-
|
|
98
|
-
|
|
88
|
+
}
|
|
89
|
+
function canonicalTargets(value) {
|
|
90
|
+
if (!Array.isArray(value) || value.length === 0)
|
|
99
91
|
return null;
|
|
92
|
+
const ids = [];
|
|
93
|
+
for (const item of value) {
|
|
94
|
+
const id = canonicalAkuId(item);
|
|
95
|
+
if (id === null)
|
|
96
|
+
return null;
|
|
97
|
+
if (ids.length > 0 && Buffer.compare(Buffer.from(ids.at(-1)), Buffer.from(id)) >= 0)
|
|
98
|
+
return null;
|
|
99
|
+
ids.push(id);
|
|
100
|
+
}
|
|
101
|
+
return ids;
|
|
102
|
+
}
|
|
103
|
+
function decodeCallClaim(id, payload) {
|
|
100
104
|
const expected = [
|
|
101
105
|
"archetype",
|
|
102
106
|
"body",
|
|
103
|
-
...(
|
|
104
|
-
"id",
|
|
107
|
+
...(payload.cwd === undefined ? [] : ["cwd"]),
|
|
105
108
|
"recipe",
|
|
106
109
|
"world",
|
|
107
110
|
];
|
|
108
|
-
if (!exactKeys(
|
|
111
|
+
if (!exactKeys(payload, expected)
|
|
112
|
+
|| typeof payload.body !== "string"
|
|
113
|
+
|| !absolute(payload.world))
|
|
109
114
|
return null;
|
|
110
|
-
if (
|
|
111
|
-
return null;
|
|
112
|
-
if (typeof value.body !== "string" || !absolute(value.world))
|
|
113
|
-
return null;
|
|
114
|
-
if (value.cwd !== undefined && !absolute(value.cwd))
|
|
115
|
+
if (payload.cwd !== undefined && !absolute(payload.cwd))
|
|
115
116
|
return null;
|
|
116
117
|
try {
|
|
117
|
-
archetypeName(
|
|
118
|
+
archetypeName(payload.archetype);
|
|
118
119
|
}
|
|
119
120
|
catch {
|
|
120
121
|
return null;
|
|
121
122
|
}
|
|
122
|
-
|
|
123
|
-
|
|
123
|
+
return {
|
|
124
|
+
id,
|
|
125
|
+
action: "akuma.call",
|
|
126
|
+
world: payload.world,
|
|
127
|
+
archetype: payload.archetype,
|
|
128
|
+
body: payload.body,
|
|
129
|
+
recipe: payload.recipe,
|
|
130
|
+
...(payload.cwd === undefined ? {} : { cwd: payload.cwd }),
|
|
131
|
+
};
|
|
132
|
+
}
|
|
133
|
+
function decodeWaitClaim(id, payload) {
|
|
134
|
+
const expected = [
|
|
135
|
+
"completion",
|
|
136
|
+
"targets",
|
|
137
|
+
...(payload.timeoutMs === undefined ? [] : ["timeoutMs"]),
|
|
138
|
+
];
|
|
139
|
+
const targets = canonicalTargets(payload.targets);
|
|
140
|
+
if (!exactKeys(payload, expected)
|
|
141
|
+
|| targets === null
|
|
142
|
+
|| (payload.completion !== "any" && payload.completion !== "all"))
|
|
143
|
+
return null;
|
|
144
|
+
if (payload.timeoutMs !== undefined
|
|
145
|
+
&& (!Number.isSafeInteger(payload.timeoutMs) || payload.timeoutMs < 0))
|
|
124
146
|
return null;
|
|
125
147
|
return {
|
|
126
|
-
id
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
...(value.cwd === undefined ? {} : { cwd: value.cwd }),
|
|
148
|
+
id,
|
|
149
|
+
action: "akuma.wait",
|
|
150
|
+
targets,
|
|
151
|
+
completion: payload.completion,
|
|
152
|
+
...(payload.timeoutMs === undefined ? {} : { timeoutMs: payload.timeoutMs }),
|
|
132
153
|
};
|
|
133
154
|
}
|
|
134
|
-
function
|
|
155
|
+
function decodeTellClaim(id, payload) {
|
|
156
|
+
const target = canonicalAkuId(payload.target);
|
|
157
|
+
if (!exactKeys(payload, ["body", "target"])
|
|
158
|
+
|| target === null
|
|
159
|
+
|| typeof payload.body !== "string")
|
|
160
|
+
return null;
|
|
161
|
+
return { id, action: "akuma.tell", target, body: payload.body };
|
|
162
|
+
}
|
|
163
|
+
function decodeKillClaim(id, payload) {
|
|
164
|
+
const targets = canonicalTargets(payload.targets);
|
|
165
|
+
return exactKeys(payload, ["targets"]) && targets !== null
|
|
166
|
+
? { id, action: "akuma.kill", targets }
|
|
167
|
+
: null;
|
|
168
|
+
}
|
|
169
|
+
function decodeClaim(bytes, fileId) {
|
|
135
170
|
let decoded;
|
|
136
171
|
try {
|
|
137
172
|
decoded = JSON.parse(bytes);
|
|
@@ -140,23 +175,82 @@ function decodeReceipt(bytes, requestId) {
|
|
|
140
175
|
return null;
|
|
141
176
|
}
|
|
142
177
|
const value = object(decoded);
|
|
143
|
-
if (value === null
|
|
178
|
+
if (value === null
|
|
179
|
+
|| !exactKeys(value, ["action", "id", "payload"])
|
|
180
|
+
|| value.id !== fileId
|
|
181
|
+
|| typeof value.id !== "string"
|
|
182
|
+
|| !UUID.test(value.id))
|
|
144
183
|
return null;
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
184
|
+
const payload = object(value.payload);
|
|
185
|
+
if (payload === null)
|
|
186
|
+
return null;
|
|
187
|
+
if (value.action === "akuma.call")
|
|
188
|
+
return decodeCallClaim(value.id, payload);
|
|
189
|
+
if (value.action === "akuma.wait")
|
|
190
|
+
return decodeWaitClaim(value.id, payload);
|
|
191
|
+
if (value.action === "akuma.tell")
|
|
192
|
+
return decodeTellClaim(value.id, payload);
|
|
193
|
+
if (value.action === "akuma.kill")
|
|
194
|
+
return decodeKillClaim(value.id, payload);
|
|
195
|
+
return null;
|
|
196
|
+
}
|
|
197
|
+
function decodeUpstreamFailure(value) {
|
|
198
|
+
const failure = object(value);
|
|
199
|
+
if (failure === null)
|
|
200
|
+
return null;
|
|
201
|
+
if (failure.kind === "akuma-not-born" && exactKeys(failure, ["id", "kind"])) {
|
|
202
|
+
const id = canonicalAkuId(failure.id);
|
|
203
|
+
return id === null ? null : { kind: failure.kind, id };
|
|
204
|
+
}
|
|
205
|
+
if (failure.kind === "failed"
|
|
206
|
+
&& exactKeys(failure, ["diagnostic", "kind"])
|
|
207
|
+
&& typeof failure.diagnostic === "string") {
|
|
208
|
+
return { kind: failure.kind, diagnostic: failure.diagnostic };
|
|
209
|
+
}
|
|
210
|
+
return null;
|
|
211
|
+
}
|
|
212
|
+
function decodeUpstreamOutcome(value) {
|
|
213
|
+
const outcome = object(value);
|
|
214
|
+
if (outcome?.kind === "returned" && exactKeys(outcome, ["kind", "result"])) {
|
|
215
|
+
return { kind: outcome.kind, result: outcome.result };
|
|
216
|
+
}
|
|
217
|
+
if (outcome?.kind !== "failed" || !exactKeys(outcome, ["failure", "kind"]))
|
|
218
|
+
return null;
|
|
219
|
+
const failure = decodeUpstreamFailure(outcome.failure);
|
|
220
|
+
return failure === null ? null : { kind: outcome.kind, failure };
|
|
221
|
+
}
|
|
222
|
+
function decodeReceipt(bytes, requestId, action) {
|
|
223
|
+
let decoded;
|
|
224
|
+
try {
|
|
225
|
+
decoded = JSON.parse(bytes);
|
|
226
|
+
}
|
|
227
|
+
catch {
|
|
228
|
+
return null;
|
|
229
|
+
}
|
|
230
|
+
const value = object(decoded);
|
|
231
|
+
if (value === null || value.id !== requestId || value.action !== action)
|
|
232
|
+
return null;
|
|
233
|
+
if (action === "akuma.call"
|
|
234
|
+
&& value.state === "served"
|
|
235
|
+
&& exactKeys(value, ["action", "child", "id", "state"])) {
|
|
236
|
+
const child = canonicalAkuId(value.child);
|
|
237
|
+
return child === null ? null : { id: requestId, action, state: value.state, child };
|
|
238
|
+
}
|
|
239
|
+
if (action !== "akuma.call"
|
|
240
|
+
&& value.state === "served"
|
|
241
|
+
&& exactKeys(value, ["action", "id", "outcome", "state"])) {
|
|
242
|
+
const outcome = decodeUpstreamOutcome(value.outcome);
|
|
243
|
+
return outcome === null ? null : { id: requestId, action, state: value.state, outcome };
|
|
152
244
|
}
|
|
153
|
-
if (value.state === "refused"
|
|
245
|
+
if (value.state === "refused"
|
|
246
|
+
&& exactKeys(value, ["action", "diagnostic", "id", "state"])
|
|
154
247
|
&& typeof value.diagnostic === "string") {
|
|
155
|
-
return { id: requestId, state:
|
|
248
|
+
return { id: requestId, action, state: value.state, diagnostic: value.diagnostic };
|
|
156
249
|
}
|
|
157
|
-
if (value.state === "voided"
|
|
250
|
+
if (value.state === "voided"
|
|
251
|
+
&& exactKeys(value, ["action", "evidence", "id", "state"])
|
|
158
252
|
&& typeof value.evidence === "string") {
|
|
159
|
-
return { id: requestId, state:
|
|
253
|
+
return { id: requestId, action, state: value.state, evidence: value.evidence };
|
|
160
254
|
}
|
|
161
255
|
return null;
|
|
162
256
|
}
|
|
@@ -171,16 +265,22 @@ async function atomicJson(path, value) {
|
|
|
171
265
|
}
|
|
172
266
|
}
|
|
173
267
|
function receiptFor(fact) {
|
|
174
|
-
if (fact.state === "served")
|
|
175
|
-
return { id: fact.id, state: fact.state, child: fact.child };
|
|
176
|
-
|
|
177
|
-
|
|
268
|
+
if (fact.action === "akuma.call" && fact.state === "served") {
|
|
269
|
+
return { id: fact.id, action: fact.action, state: fact.state, child: fact.child };
|
|
270
|
+
}
|
|
271
|
+
if (fact.state === "refused") {
|
|
272
|
+
return { id: fact.id, action: fact.action, state: fact.state, diagnostic: fact.diagnostic };
|
|
273
|
+
}
|
|
178
274
|
if (fact.state === "voided")
|
|
179
|
-
return { id: fact.id, state: fact.state, evidence: fact.evidence };
|
|
275
|
+
return { id: fact.id, action: fact.action, state: fact.state, evidence: fact.evidence };
|
|
180
276
|
return null;
|
|
181
277
|
}
|
|
182
|
-
async function projectReceipt(directory, fact) {
|
|
183
|
-
const receipt =
|
|
278
|
+
async function projectReceipt(directory, fact, outcome) {
|
|
279
|
+
const receipt = outcome === undefined
|
|
280
|
+
? receiptFor(fact)
|
|
281
|
+
: fact.action === "akuma.call" || fact.state !== "served"
|
|
282
|
+
? null
|
|
283
|
+
: { id: fact.id, action: fact.action, state: fact.state, outcome };
|
|
184
284
|
if (receipt !== null)
|
|
185
285
|
await atomicJson(join(directory, `${fact.id}.receipt.json`), receipt);
|
|
186
286
|
}
|
|
@@ -192,45 +292,74 @@ export function injectedBodyRequests() {
|
|
|
192
292
|
throw new Error(`${AKUMA_REQUESTS_ENV} must be an absolute normalized path`);
|
|
193
293
|
return directory;
|
|
194
294
|
}
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
archetype: input.archetype,
|
|
200
|
-
body: input.body,
|
|
201
|
-
recipe: input.recipe,
|
|
202
|
-
...(input.cwd === undefined ? {} : { cwd: input.cwd }),
|
|
203
|
-
});
|
|
204
|
-
const receiptPath = join(input.directory, `${input.id}.receipt.json`);
|
|
295
|
+
async function requestBody(input) {
|
|
296
|
+
const { id, action, ...payload } = input.claim;
|
|
297
|
+
await atomicJson(join(input.directory, `${id}.request.json`), { id, action, payload });
|
|
298
|
+
const receiptPath = join(input.directory, `${id}.receipt.json`);
|
|
205
299
|
for (;;) {
|
|
206
300
|
try {
|
|
207
|
-
const receipt = decodeReceipt(await readFile(receiptPath, "utf8"),
|
|
301
|
+
const receipt = decodeReceipt(await readFile(receiptPath, "utf8"), id, action);
|
|
208
302
|
if (receipt === null)
|
|
209
|
-
throw new Error(`Akuma body request ${
|
|
210
|
-
if (receipt.state === "served")
|
|
211
|
-
return receipt.child;
|
|
303
|
+
throw new Error(`Akuma body request ${id} has an invalid receipt`);
|
|
212
304
|
if (receipt.state === "refused")
|
|
213
305
|
throw new AkumaBodyRequestError("refused", receipt.diagnostic);
|
|
214
|
-
|
|
306
|
+
if (receipt.state === "voided")
|
|
307
|
+
throw new AkumaBodyRequestError("voided", receipt.evidence);
|
|
308
|
+
return receipt;
|
|
215
309
|
}
|
|
216
310
|
catch (error) {
|
|
217
311
|
if (error.code !== "ENOENT")
|
|
218
312
|
throw error;
|
|
219
313
|
}
|
|
314
|
+
if (!await access(input.directory).then(() => true, () => false)) {
|
|
315
|
+
throw new AkumaBodyRequestError("voided", "parent request channel closed before a receipt");
|
|
316
|
+
}
|
|
220
317
|
await abortableDelay(POLL_MS);
|
|
221
318
|
}
|
|
222
319
|
}
|
|
223
|
-
async function
|
|
320
|
+
export async function requestBodyCall(input) {
|
|
321
|
+
const { directory, ...claim } = input;
|
|
322
|
+
const receipt = await requestBody({ directory, claim: { ...claim, action: "akuma.call" } });
|
|
323
|
+
if (receipt.action !== "akuma.call" || receipt.state !== "served") {
|
|
324
|
+
throw new Error(`Akuma body request ${input.id} returned the wrong action`);
|
|
325
|
+
}
|
|
326
|
+
return receipt.child;
|
|
327
|
+
}
|
|
328
|
+
async function requestUpstream(directory, claim) {
|
|
329
|
+
const receipt = await requestBody({ directory, claim });
|
|
330
|
+
if (receipt.action === "akuma.call" || receipt.state !== "served") {
|
|
331
|
+
throw new Error(`Akuma body request ${claim.id} returned the wrong action`);
|
|
332
|
+
}
|
|
333
|
+
return receipt.outcome;
|
|
334
|
+
}
|
|
335
|
+
export async function requestBodyWait(input) {
|
|
336
|
+
const { directory, id = randomUUID(), ...claim } = input;
|
|
337
|
+
return await requestUpstream(directory, { ...claim, id, action: "akuma.wait" });
|
|
338
|
+
}
|
|
339
|
+
export async function requestBodyTell(input) {
|
|
340
|
+
const { directory, id = randomUUID(), ...claim } = input;
|
|
341
|
+
return await requestUpstream(directory, { ...claim, id, action: "akuma.tell" });
|
|
342
|
+
}
|
|
343
|
+
export async function requestBodyKill(input) {
|
|
344
|
+
const { directory, id = randomUUID(), ...claim } = input;
|
|
345
|
+
return await requestUpstream(directory, { ...claim, id, action: "akuma.kill" });
|
|
346
|
+
}
|
|
347
|
+
async function serveCallClaim(input) {
|
|
224
348
|
input.signal.throwIfAborted();
|
|
225
|
-
|
|
349
|
+
const cwd = input.claim.cwd ?? input.parent.cwd;
|
|
350
|
+
const recipe = decodeRecipe(input.claim.recipe, cwd);
|
|
351
|
+
if (recipe === null)
|
|
352
|
+
return;
|
|
353
|
+
let fact = await admitRequest(input.paths, { ...input.claim, recipe, admittedAt: input.now() });
|
|
226
354
|
const existing = receiptFor(fact);
|
|
227
355
|
if (existing !== null) {
|
|
228
356
|
if (!input.signal.aborted)
|
|
229
357
|
await projectReceipt(input.directory, fact);
|
|
230
358
|
return;
|
|
231
359
|
}
|
|
232
|
-
if (fact.state !== "admitted")
|
|
360
|
+
if (fact.action !== "akuma.call" || fact.state !== "admitted") {
|
|
233
361
|
throw new Error(`Akuma request ${fact.id} cannot be replayed from ${fact.state}`);
|
|
362
|
+
}
|
|
234
363
|
const request = fact;
|
|
235
364
|
const servingWorld = worldRootForAkumaPaths(input.paths);
|
|
236
365
|
if (request.world !== servingWorld) {
|
|
@@ -238,7 +367,6 @@ async function serveClaim(input) {
|
|
|
238
367
|
await projectReceipt(input.directory, fact);
|
|
239
368
|
return;
|
|
240
369
|
}
|
|
241
|
-
const cwd = resolve(request.cwd ?? servingWorld);
|
|
242
370
|
let child;
|
|
243
371
|
try {
|
|
244
372
|
input.signal.throwIfAborted();
|
|
@@ -256,6 +384,7 @@ async function serveClaim(input) {
|
|
|
256
384
|
provider: request.recipe.provider,
|
|
257
385
|
options: request.recipe.options,
|
|
258
386
|
...(request.recipe.readonly === undefined ? {} : { readonly: request.recipe.readonly }),
|
|
387
|
+
allowed: request.recipe.allowed,
|
|
259
388
|
cwd,
|
|
260
389
|
origin: { kind: "request", parent: input.parent.id, requestId: request.id },
|
|
261
390
|
confinement: request.recipe.confinement,
|
|
@@ -280,6 +409,87 @@ async function serveClaim(input) {
|
|
|
280
409
|
fact = await serveRequest(input.paths, request.id, child);
|
|
281
410
|
await projectReceipt(input.directory, fact);
|
|
282
411
|
}
|
|
412
|
+
function upstreamFailure(error) {
|
|
413
|
+
const value = object(error);
|
|
414
|
+
if (value?.kind === "akuma-not-born") {
|
|
415
|
+
const id = canonicalAkuId(value.id);
|
|
416
|
+
if (id !== null)
|
|
417
|
+
return { kind: "akuma-not-born", id };
|
|
418
|
+
}
|
|
419
|
+
return { kind: "failed", diagnostic: diagnostic(error) };
|
|
420
|
+
}
|
|
421
|
+
async function serveUpstreamClaim(input) {
|
|
422
|
+
input.signal.throwIfAborted();
|
|
423
|
+
let fact = await admitRequest(input.paths, { ...input.claim, admittedAt: input.now() });
|
|
424
|
+
const existing = receiptFor(fact);
|
|
425
|
+
if (existing !== null) {
|
|
426
|
+
if (!input.signal.aborted)
|
|
427
|
+
await projectReceipt(input.directory, fact);
|
|
428
|
+
return;
|
|
429
|
+
}
|
|
430
|
+
if (fact.state === "served") {
|
|
431
|
+
await projectReceipt(input.directory, fact, {
|
|
432
|
+
kind: "failed",
|
|
433
|
+
failure: { kind: "failed", diagnostic: "served request receipt is no longer available" },
|
|
434
|
+
});
|
|
435
|
+
return;
|
|
436
|
+
}
|
|
437
|
+
if (fact.state !== "admitted" || fact.action === "akuma.call") {
|
|
438
|
+
throw new Error(`Akuma request ${fact.id} cannot be served from ${fact.state}`);
|
|
439
|
+
}
|
|
440
|
+
const request = fact;
|
|
441
|
+
let outcome;
|
|
442
|
+
let killService = [];
|
|
443
|
+
try {
|
|
444
|
+
if (input.upstream === undefined)
|
|
445
|
+
throw new Error("upstream execution port is unavailable");
|
|
446
|
+
let result;
|
|
447
|
+
if (request.action === "akuma.wait") {
|
|
448
|
+
const waitRequest = request;
|
|
449
|
+
result = await input.upstream.wait({
|
|
450
|
+
targets: waitRequest.targets,
|
|
451
|
+
completion: waitRequest.completion,
|
|
452
|
+
...(waitRequest.timeoutMs === undefined ? {} : { timeoutMs: waitRequest.timeoutMs }),
|
|
453
|
+
signal: input.signal,
|
|
454
|
+
});
|
|
455
|
+
}
|
|
456
|
+
else if (request.action === "akuma.tell") {
|
|
457
|
+
const tellRequest = request;
|
|
458
|
+
result = await input.upstream.tell({
|
|
459
|
+
target: tellRequest.target,
|
|
460
|
+
body: tellRequest.body,
|
|
461
|
+
tellId: tellRequest.id,
|
|
462
|
+
recordedAt: tellRequest.admittedAt,
|
|
463
|
+
signal: input.signal,
|
|
464
|
+
});
|
|
465
|
+
}
|
|
466
|
+
else {
|
|
467
|
+
const killRequest = request;
|
|
468
|
+
const served = await input.upstream.kill({ targets: killRequest.targets, signal: input.signal });
|
|
469
|
+
killService = served.service;
|
|
470
|
+
result = served.result;
|
|
471
|
+
}
|
|
472
|
+
outcome = { kind: "returned", result };
|
|
473
|
+
}
|
|
474
|
+
catch (error) {
|
|
475
|
+
if (input.signal.aborted)
|
|
476
|
+
return;
|
|
477
|
+
outcome = { kind: "failed", failure: upstreamFailure(error) };
|
|
478
|
+
}
|
|
479
|
+
input.signal.throwIfAborted();
|
|
480
|
+
const service = request.action === "akuma.wait"
|
|
481
|
+
? { action: request.action }
|
|
482
|
+
: request.action === "akuma.tell"
|
|
483
|
+
? { action: request.action, target: request.target, tellId: request.id }
|
|
484
|
+
: { action: request.action, results: killService };
|
|
485
|
+
fact = await serveUpstreamRequest(input.paths, request.id, service);
|
|
486
|
+
await projectReceipt(input.directory, fact, outcome);
|
|
487
|
+
}
|
|
488
|
+
async function serveClaim(input) {
|
|
489
|
+
return input.claim.action === "akuma.call"
|
|
490
|
+
? await serveCallClaim({ ...input, claim: input.claim })
|
|
491
|
+
: await serveUpstreamClaim({ ...input, claim: input.claim });
|
|
492
|
+
}
|
|
283
493
|
async function requestFiles(directory) {
|
|
284
494
|
try {
|
|
285
495
|
return (await readdir(directory))
|
|
@@ -411,7 +621,8 @@ export async function settleBodyRequests(paths, parent, now, signal) {
|
|
|
411
621
|
if (request.state === "admitted") {
|
|
412
622
|
await voidRequest(paths, request.id, "body died before serving the request");
|
|
413
623
|
}
|
|
414
|
-
else if (request.state === "reserved"
|
|
624
|
+
else if (request.state === "reserved"
|
|
625
|
+
&& !await settleReserved(paths, parent, request, now, signal))
|
|
415
626
|
pending = true;
|
|
416
627
|
}
|
|
417
628
|
return pending ? "pending" : "settled";
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
import { runAkumaBody } from "./akuma/body.js";
|
|
2
|
+
import { worldRootForAkumaPaths } from "./akuma/identity.js";
|
|
3
|
+
import { executeKillAkuma, executeTellAkuma, executeWaitAkuma, } from "./library/fleet.js";
|
|
4
|
+
function upstreamFor(launch) {
|
|
5
|
+
const path = worldRootForAkumaPaths(launch.paths);
|
|
6
|
+
return {
|
|
7
|
+
wait: async (input) => await executeWaitAkuma({
|
|
8
|
+
path,
|
|
9
|
+
ids: input.targets,
|
|
10
|
+
completion: input.completion,
|
|
11
|
+
...(input.timeoutMs === undefined ? {} : { timeoutMs: input.timeoutMs }),
|
|
12
|
+
signal: input.signal,
|
|
13
|
+
}),
|
|
14
|
+
tell: async (input) => await executeTellAkuma({
|
|
15
|
+
path,
|
|
16
|
+
id: input.target,
|
|
17
|
+
body: input.body,
|
|
18
|
+
tellId: input.tellId,
|
|
19
|
+
recordedAt: input.recordedAt,
|
|
20
|
+
signal: input.signal,
|
|
21
|
+
}),
|
|
22
|
+
kill: async (input) => {
|
|
23
|
+
const result = await executeKillAkuma({ path, ids: input.targets, signal: input.signal });
|
|
24
|
+
return {
|
|
25
|
+
result,
|
|
26
|
+
service: result.results.map(({ id, evidence }) => ({ id, evidence })),
|
|
27
|
+
};
|
|
28
|
+
},
|
|
29
|
+
};
|
|
30
|
+
}
|
|
31
|
+
await runAkumaBody(upstreamFor);
|
|
@@ -5,7 +5,10 @@ type MutationCallOptions = Readonly<{
|
|
|
5
5
|
coordinate?: ContractId;
|
|
6
6
|
projectRefusal?: (refusal: unknown) => unknown;
|
|
7
7
|
}>;
|
|
8
|
-
export declare function acceptedBind(result: BindResult,
|
|
8
|
+
export declare function acceptedBind(result: BindResult, coordinates: Readonly<{
|
|
9
|
+
workspace: "worktree" | "here";
|
|
10
|
+
target?: string;
|
|
11
|
+
}>): AcceptedBindResult;
|
|
9
12
|
export declare function acceptedAmend(result: AmendResult, coordinate: ContractId): AcceptedAmendResult;
|
|
10
13
|
export declare function acceptedDeliver(result: MutationResult<Delivery>, coordinate: ContractId): AcceptedDeliverResult;
|
|
11
14
|
export declare function acceptedReview(result: MutationResult<Review>, coordinate: ContractId): AcceptedReviewResult;
|
|
@@ -26,11 +26,12 @@ function acceptedRegion(result) {
|
|
|
26
26
|
return { overlapFailure: result.overlapFailure };
|
|
27
27
|
return { overlaps: result.overlaps };
|
|
28
28
|
}
|
|
29
|
-
export function acceptedBind(result,
|
|
29
|
+
export function acceptedBind(result, coordinates) {
|
|
30
30
|
return {
|
|
31
31
|
...acceptedEnvelope(result, undefined),
|
|
32
32
|
verb: "bind",
|
|
33
|
-
|
|
33
|
+
workspace: coordinates.workspace,
|
|
34
|
+
target: coordinates.target ?? null,
|
|
34
35
|
...acceptedRegion(result),
|
|
35
36
|
};
|
|
36
37
|
}
|
|
@@ -44,9 +45,12 @@ export function acceptedAmend(result, coordinate) {
|
|
|
44
45
|
}
|
|
45
46
|
export function acceptedDeliver(result, coordinate) {
|
|
46
47
|
const value = result.value;
|
|
48
|
+
const attestation = result.facts.find((fact) => fact.kind === "attestation");
|
|
49
|
+
const verificationVerdict = attestation?.data.verdict ?? value.verificationReuse?.verdict;
|
|
47
50
|
return {
|
|
48
51
|
...acceptedEnvelope(result, coordinate),
|
|
49
52
|
verb: "deliver",
|
|
53
|
+
...(verificationVerdict === undefined ? {} : { verificationVerdict }),
|
|
50
54
|
...(value.verification === undefined ? {} : { verification: value.verification }),
|
|
51
55
|
...(value.verificationReuse === undefined ? {} : { verificationReuse: value.verificationReuse }),
|
|
52
56
|
...(value.placement === undefined ? {} : { placement: value.placement }),
|
|
@@ -56,18 +60,36 @@ export function acceptedDeliver(result, coordinate) {
|
|
|
56
60
|
}
|
|
57
61
|
export function acceptedReview(result, coordinate) {
|
|
58
62
|
const value = result.value;
|
|
63
|
+
const attestation = result.facts.find((fact) => fact.kind === "attestation");
|
|
64
|
+
if (attestation === undefined)
|
|
65
|
+
throw new Error("accepted review is missing its attestation fact");
|
|
59
66
|
return {
|
|
60
67
|
...acceptedEnvelope(result, coordinate),
|
|
61
68
|
verb: "review",
|
|
69
|
+
verdict: attestation.data.verdict,
|
|
62
70
|
...(value.placement === undefined ? {} : { placement: value.placement }),
|
|
63
71
|
...(value.workspace === undefined ? {} : { workspace: value.workspace }),
|
|
64
72
|
};
|
|
65
73
|
}
|
|
66
74
|
export function acceptedArc(result, coordinate) {
|
|
67
|
-
|
|
75
|
+
const arc = result.facts.find((fact) => fact.kind === "arc");
|
|
76
|
+
if (arc === undefined)
|
|
77
|
+
throw new Error("accepted arc is missing its arc fact");
|
|
78
|
+
return {
|
|
79
|
+
...acceptedEnvelope(result, coordinate),
|
|
80
|
+
verb: "arc",
|
|
81
|
+
chapter: { seq: arc.data.seq, title: arc.data.title },
|
|
82
|
+
};
|
|
68
83
|
}
|
|
69
84
|
export function acceptedAbandon(result, coordinate) {
|
|
70
|
-
|
|
85
|
+
const abandoned = result.facts.find((fact) => fact.kind === "abandoned");
|
|
86
|
+
if (abandoned === undefined)
|
|
87
|
+
throw new Error("accepted abandon is missing its abandoned fact");
|
|
88
|
+
return {
|
|
89
|
+
...acceptedEnvelope(result, coordinate),
|
|
90
|
+
verb: "abandon",
|
|
91
|
+
...(abandoned.data.note === undefined ? {} : { note: abandoned.data.note }),
|
|
92
|
+
};
|
|
71
93
|
}
|
|
72
94
|
export function acceptedAudit(result, coordinate) {
|
|
73
95
|
return {
|
|
@@ -1,8 +1,8 @@
|
|
|
1
1
|
import { type AkuId, type ActivityHistory } from "../../akuma/index.js";
|
|
2
|
-
import { Keiyaku, type AkumaKillResult, type AkumaHistoryResult, type
|
|
2
|
+
import { Keiyaku, type AkumaKillResult, type AkumaHistoryResult, type AkumaObservation, type AkumaTellResult, type AkumaWaitResult, type CallResult, type ForkResult, type Keiyaku as KeiyakuContract, type Repo } from "../../index.js";
|
|
3
3
|
import type { Settings } from "../../settings.js";
|
|
4
4
|
import type { WorldRoot } from "../../world.js";
|
|
5
|
-
import type {
|
|
5
|
+
import type { InvokedAkumaCommand } from "./akuma.js";
|
|
6
6
|
export type AkumaInvocationResult = Readonly<{
|
|
7
7
|
kind: "akuma";
|
|
8
8
|
action: "call";
|
|
@@ -11,7 +11,7 @@ export type AkumaInvocationResult = Readonly<{
|
|
|
11
11
|
}> | Readonly<{
|
|
12
12
|
kind: "akuma";
|
|
13
13
|
action: "status";
|
|
14
|
-
status:
|
|
14
|
+
status: AkumaObservation;
|
|
15
15
|
alias?: string;
|
|
16
16
|
}> | Readonly<{
|
|
17
17
|
kind: "akuma";
|
|
@@ -67,12 +67,13 @@ export type AkumaInvocationResult = Readonly<{
|
|
|
67
67
|
}>;
|
|
68
68
|
type InvokeInput = Readonly<{
|
|
69
69
|
path: WorldRoot;
|
|
70
|
-
|
|
71
|
-
|
|
70
|
+
statedCwd?: string;
|
|
71
|
+
home?: string;
|
|
72
|
+
settings?: Settings;
|
|
72
73
|
contract?: KeiyakuContract;
|
|
73
74
|
repo?: Repo;
|
|
74
75
|
readStdin(): Promise<string>;
|
|
75
76
|
}>;
|
|
76
|
-
export declare function invokeAkuma(command:
|
|
77
|
-
export declare function invokeAkumaStatus(path: WorldRoot, akuma: string,
|
|
77
|
+
export declare function invokeAkuma(command: InvokedAkumaCommand, input: InvokeInput): Promise<AkumaInvocationResult>;
|
|
78
|
+
export declare function invokeAkumaStatus(path: WorldRoot, akuma: string, alias?: string, repo?: Repo): Promise<AkumaInvocationResult>;
|
|
78
79
|
export {};
|