@astrosheep/keiyaku 4.5.0 → 4.5.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/build/integrations/marketplace/plugins/keiyaku/.claude-plugin/plugin.json +4 -2
- package/build/integrations/marketplace/plugins/keiyaku/.codex-plugin/plugin.json +1 -1
- package/build/integrations/marketplace/plugins/keiyaku/package.json +9 -3
- package/build/integrations/marketplace/plugins/keiyaku/skills/keiyaku-akuma/SKILL.md +16 -5
- package/build/integrations/pi/keiyaku.ts +159 -0
- package/build/src/akuma/abort.js +9 -9
- package/build/src/akuma/akuma-handle.d.ts +4 -2
- package/build/src/akuma/akuma-handle.js +14 -2
- package/build/src/akuma/akuma-product.d.ts +8 -2
- package/build/src/akuma/akuma-product.js +9 -4
- package/build/src/akuma/body.d.ts +4 -0
- package/build/src/akuma/body.js +21 -14
- package/build/src/akuma/heart/index.js +7 -3
- package/build/src/akuma/heart/rows.d.ts +1 -1
- package/build/src/akuma/heart/rows.js +3 -3
- package/build/src/akuma/heart/storage.d.ts +1 -0
- package/build/src/akuma/heart/storage.js +28 -6
- package/build/src/akuma/identity.d.ts +1 -0
- package/build/src/akuma/identity.js +7 -0
- package/build/src/akuma/index.d.ts +1 -1
- package/build/src/akuma/projection.d.ts +18 -2
- package/build/src/akuma/projection.js +11 -1
- package/build/src/akuma/providers/claude/index.js +68 -37
- package/build/src/akuma/publication.js +3 -0
- package/build/src/akuma/request-lifecycle.d.ts +45 -0
- package/build/src/akuma/request-lifecycle.js +171 -0
- package/build/src/akuma/request-serve.d.ts +9 -32
- package/build/src/akuma/request-serve.js +47 -173
- package/build/src/akuma/request-wire.js +1 -1
- package/build/src/akuma/requests.js +3 -2
- package/build/src/cli/commands/akuma-invoke.d.ts +9 -0
- package/build/src/cli/commands/akuma-invoke.js +9 -6
- package/build/src/cli/commands/akuma.d.ts +1 -0
- package/build/src/cli/commands/akuma.js +16 -6
- package/build/src/cli/commands/contract.d.ts +3 -2
- package/build/src/cli/commands/contract.js +5 -2
- package/build/src/cli/commands/status-set.d.ts +4 -0
- package/build/src/cli/commands/status-set.js +61 -0
- package/build/src/cli/invoke.js +5 -0
- package/build/src/cli/parse.js +1 -1
- package/build/src/cli/render/akuma-activity.js +11 -0
- package/build/src/cli/render/akuma.js +7 -0
- package/build/src/cli/render/status-set.d.ts +5 -0
- package/build/src/cli/render/status-set.js +14 -0
- package/build/src/cli/render/text.js +3 -0
- package/build/src/cli/result.d.ts +15 -1
- package/build/src/cli/runtime.js +2 -0
- package/build/src/cli/square-edge.d.ts +2 -1
- package/build/src/cli/square-edge.js +60 -33
- package/build/src/contract-worktree.js +18 -5
- package/build/src/library/akuma-creation.d.ts +1 -1
- package/build/src/library/akuma-creation.js +6 -2
- package/build/src/library/fleet.d.ts +12 -1
- package/build/src/library/fleet.js +21 -5
- package/build/src/library/repo.js +3 -1
- package/build/src/runtime/proc/windows-launch.exe +0 -0
- package/build/src/world.d.ts +2 -0
- package/build/src/world.js +4 -0
- package/package.json +8 -3
|
@@ -1,19 +1,26 @@
|
|
|
1
|
-
import {
|
|
2
|
-
import {
|
|
3
|
-
import {
|
|
4
|
-
import {
|
|
5
|
-
import { pathsForAkuId, worldRootForAkumaPaths } from "./identity.js";
|
|
6
|
-
import { BIRTH_TIMEOUT_MS, publishAkuma } from "./publication.js";
|
|
7
|
-
import { atomicJson, canonicalAkuId, decodeClaim, decodeRecipe, receiptPath, } from "./request-wire.js";
|
|
1
|
+
import { admitRequest, readRequest, refuseRequest, reserveRequest, serveRequest as serveChildRequest, serveUpstreamRequest, voidRequest, } from "./heart/index.js";
|
|
2
|
+
import { worldRootForAkumaPaths } from "./identity.js";
|
|
3
|
+
import { publishAkuma } from "./publication.js";
|
|
4
|
+
import { atomicJson, canonicalAkuId, decodeRecipe, receiptPath, } from "./request-wire.js";
|
|
8
5
|
import { isTaskMutationAction } from "../task/mutation.js";
|
|
9
6
|
import { executeRequest } from "./request-execution.js";
|
|
10
|
-
|
|
7
|
+
import { BodyRequestPump as LifecycleBodyRequestPump, } from "./request-lifecycle.js";
|
|
8
|
+
export { clearBodyRequestTransport, settleBodyRequests } from "./request-lifecycle.js";
|
|
11
9
|
function diagnostic(error) {
|
|
12
10
|
return error instanceof Error ? error.message : String(error);
|
|
13
11
|
}
|
|
14
12
|
function isTask(value) {
|
|
15
13
|
return isTaskMutationAction(value.action);
|
|
16
14
|
}
|
|
15
|
+
function refusalRecipe(value) {
|
|
16
|
+
if (value !== null && typeof value === "object" && !Array.isArray(value)) {
|
|
17
|
+
const recipe = value;
|
|
18
|
+
if (Array.isArray(recipe.allowed))
|
|
19
|
+
return recipe;
|
|
20
|
+
return { ...recipe, allowed: [] };
|
|
21
|
+
}
|
|
22
|
+
return { allowed: [] };
|
|
23
|
+
}
|
|
17
24
|
function acceptedReceipt(fact, service) {
|
|
18
25
|
return {
|
|
19
26
|
id: fact.id,
|
|
@@ -62,7 +69,7 @@ function receiptFor(fact) {
|
|
|
62
69
|
}
|
|
63
70
|
return null;
|
|
64
71
|
}
|
|
65
|
-
async function projectReceipt(directory, fact, outcome) {
|
|
72
|
+
async function projectReceipt(directory, transportId, fact, outcome) {
|
|
66
73
|
const receipt = outcome === undefined
|
|
67
74
|
? receiptFor(fact)
|
|
68
75
|
: fact.action === "akuma.call"
|
|
@@ -71,29 +78,45 @@ async function projectReceipt(directory, fact, outcome) {
|
|
|
71
78
|
if (receipt === null)
|
|
72
79
|
return;
|
|
73
80
|
try {
|
|
74
|
-
await atomicJson(receiptPath(directory,
|
|
81
|
+
await atomicJson(receiptPath(directory, transportId), receipt);
|
|
75
82
|
}
|
|
76
83
|
catch (error) {
|
|
77
84
|
if (error.code !== "ENOENT")
|
|
78
85
|
throw error;
|
|
79
86
|
}
|
|
80
87
|
}
|
|
88
|
+
async function refuseInvalidCall(input) {
|
|
89
|
+
let fact = await admitRequest(input.paths, {
|
|
90
|
+
...input.claim,
|
|
91
|
+
recipe: refusalRecipe(input.claim.recipe),
|
|
92
|
+
admittedAt: input.now(),
|
|
93
|
+
});
|
|
94
|
+
const existing = receiptFor(fact);
|
|
95
|
+
if (existing !== null) {
|
|
96
|
+
await projectReceipt(input.directory, input.transportId, fact);
|
|
97
|
+
return;
|
|
98
|
+
}
|
|
99
|
+
fact = await refuseRequest(input.paths, input.claim.id, "invalid akuma.call recipe");
|
|
100
|
+
await projectReceipt(input.directory, input.transportId, fact);
|
|
101
|
+
}
|
|
81
102
|
async function serveCall(input) {
|
|
82
103
|
input.signal.throwIfAborted();
|
|
83
104
|
if (!input.admissionOpen())
|
|
84
105
|
return;
|
|
85
106
|
const recipe = decodeRecipe(input.claim.recipe);
|
|
86
|
-
if (recipe === null)
|
|
107
|
+
if (recipe === null) {
|
|
108
|
+
await refuseInvalidCall(input);
|
|
87
109
|
return;
|
|
110
|
+
}
|
|
88
111
|
let fact = await admitRequest(input.paths, { ...input.claim, recipe, admittedAt: input.now() });
|
|
89
112
|
const existing = receiptFor(fact);
|
|
90
113
|
if (existing !== null) {
|
|
91
|
-
await projectReceipt(input.directory, fact);
|
|
114
|
+
await projectReceipt(input.directory, input.transportId, fact);
|
|
92
115
|
return;
|
|
93
116
|
}
|
|
94
117
|
if (!input.admissionOpen()) {
|
|
95
118
|
fact = await voidRequest(input.paths, input.claim.id, "body closed request admission");
|
|
96
|
-
await projectReceipt(input.directory, fact);
|
|
119
|
+
await projectReceipt(input.directory, input.transportId, fact);
|
|
97
120
|
return;
|
|
98
121
|
}
|
|
99
122
|
if (fact.action !== "akuma.call" || fact.state !== "admitted") {
|
|
@@ -103,7 +126,7 @@ async function serveCall(input) {
|
|
|
103
126
|
const world = worldRootForAkumaPaths(input.paths);
|
|
104
127
|
if (request.world !== world) {
|
|
105
128
|
fact = await refuseRequest(input.paths, request.id, `request world ${request.world} does not match ${world}`);
|
|
106
|
-
await projectReceipt(input.directory, fact);
|
|
129
|
+
await projectReceipt(input.directory, input.transportId, fact);
|
|
107
130
|
return;
|
|
108
131
|
}
|
|
109
132
|
try {
|
|
@@ -136,7 +159,7 @@ async function serveCall(input) {
|
|
|
136
159
|
});
|
|
137
160
|
},
|
|
138
161
|
});
|
|
139
|
-
fact = await
|
|
162
|
+
fact = await serveChildRequest(input.paths, request.id, published.id);
|
|
140
163
|
}
|
|
141
164
|
catch (error) {
|
|
142
165
|
const current = await readRequest(input.paths, request.id);
|
|
@@ -147,7 +170,7 @@ async function serveCall(input) {
|
|
|
147
170
|
? await voidRequest(input.paths, request.id, diagnostic(error))
|
|
148
171
|
: current;
|
|
149
172
|
}
|
|
150
|
-
await projectReceipt(input.directory, fact);
|
|
173
|
+
await projectReceipt(input.directory, input.transportId, fact);
|
|
151
174
|
}
|
|
152
175
|
function failure(error) {
|
|
153
176
|
const value = error !== null && typeof error === "object" ? error : null;
|
|
@@ -174,11 +197,11 @@ async function serveUpstream(input) {
|
|
|
174
197
|
});
|
|
175
198
|
const existing = receiptFor(fact);
|
|
176
199
|
if (existing !== null) {
|
|
177
|
-
await projectReceipt(input.directory, fact);
|
|
200
|
+
await projectReceipt(input.directory, input.transportId, fact);
|
|
178
201
|
return;
|
|
179
202
|
}
|
|
180
203
|
if (fact.state === "served") {
|
|
181
|
-
await projectReceipt(input.directory, fact, {
|
|
204
|
+
await projectReceipt(input.directory, input.transportId, fact, {
|
|
182
205
|
kind: "failed",
|
|
183
206
|
failure: {
|
|
184
207
|
kind: "failed",
|
|
@@ -189,7 +212,7 @@ async function serveUpstream(input) {
|
|
|
189
212
|
}
|
|
190
213
|
if (!input.admissionOpen()) {
|
|
191
214
|
fact = await voidRequest(input.paths, input.claim.id, "body closed request admission");
|
|
192
|
-
await projectReceipt(input.directory, fact);
|
|
215
|
+
await projectReceipt(input.directory, input.transportId, fact);
|
|
193
216
|
return;
|
|
194
217
|
}
|
|
195
218
|
if (fact.state !== "admitted" || fact.action === "akuma.call") {
|
|
@@ -207,171 +230,22 @@ async function serveUpstream(input) {
|
|
|
207
230
|
catch (error) {
|
|
208
231
|
outcome = { kind: "failed", failure: failure(error) };
|
|
209
232
|
fact = await voidRequest(input.paths, request.id, "body failed before serving the request");
|
|
210
|
-
await projectReceipt(input.directory, fact);
|
|
233
|
+
await projectReceipt(input.directory, input.transportId, fact);
|
|
211
234
|
return;
|
|
212
235
|
}
|
|
213
236
|
fact =
|
|
214
237
|
service === undefined
|
|
215
238
|
? await voidRequest(input.paths, request.id, "body completed without a durable Contract fact reference")
|
|
216
239
|
: await serveUpstreamRequest(input.paths, request.id, service);
|
|
217
|
-
await projectReceipt(input.directory, fact, outcome);
|
|
240
|
+
await projectReceipt(input.directory, input.transportId, fact, outcome);
|
|
218
241
|
}
|
|
219
|
-
async function
|
|
242
|
+
export async function serveRequest(input) {
|
|
220
243
|
return input.claim.action === "akuma.call"
|
|
221
244
|
? await serveCall({ ...input, claim: input.claim })
|
|
222
245
|
: await serveUpstream({ ...input, claim: input.claim });
|
|
223
246
|
}
|
|
224
|
-
|
|
225
|
-
try {
|
|
226
|
-
return (await readdir(directory)).filter((name) => name.endsWith(".request.json")).sort();
|
|
227
|
-
}
|
|
228
|
-
catch (error) {
|
|
229
|
-
if (error.code === "ENOENT")
|
|
230
|
-
return [];
|
|
231
|
-
throw error;
|
|
232
|
-
}
|
|
233
|
-
}
|
|
234
|
-
export class BodyRequestPump {
|
|
235
|
-
input;
|
|
236
|
-
directory;
|
|
237
|
-
failure;
|
|
238
|
-
closeSignal = new AbortController();
|
|
239
|
-
executionSignal = new AbortController();
|
|
240
|
-
handled = new Set();
|
|
241
|
-
running;
|
|
242
|
-
cancelBody;
|
|
243
|
-
admissionClosed = false;
|
|
244
|
-
constructor(input) {
|
|
245
|
-
this.input = input;
|
|
246
|
-
this.directory = join(input.paths.directory, "requests", String(input.bodySequence));
|
|
247
|
-
this.cancelBody = () => {
|
|
248
|
-
this.stopAdmission();
|
|
249
|
-
if (!this.executionSignal.signal.aborted) {
|
|
250
|
-
this.executionSignal.abort(input.signal.reason);
|
|
251
|
-
}
|
|
252
|
-
};
|
|
253
|
-
input.signal.addEventListener("abort", this.cancelBody, { once: true });
|
|
254
|
-
if (input.signal.aborted)
|
|
255
|
-
this.cancelBody();
|
|
256
|
-
this.running = this.run();
|
|
257
|
-
this.failure = this.running.then(() => new Promise(() => { }), (error) => Promise.reject(error));
|
|
258
|
-
}
|
|
247
|
+
export class BodyRequestPump extends LifecycleBodyRequestPump {
|
|
259
248
|
static async open(input) {
|
|
260
|
-
await
|
|
261
|
-
return new BodyRequestPump(input);
|
|
262
|
-
}
|
|
263
|
-
async run() {
|
|
264
|
-
while (!this.closeSignal.signal.aborted) {
|
|
265
|
-
for (const name of await requestFiles(this.directory)) {
|
|
266
|
-
if (this.closeSignal.signal.aborted)
|
|
267
|
-
return;
|
|
268
|
-
const id = name.slice(0, -".request.json".length);
|
|
269
|
-
if (this.handled.has(id))
|
|
270
|
-
continue;
|
|
271
|
-
const path = join(this.directory, name);
|
|
272
|
-
const claim = decodeClaim(await readFile(path, "utf8"), id);
|
|
273
|
-
if (claim === null) {
|
|
274
|
-
await rm(path, { force: true });
|
|
275
|
-
this.handled.add(id);
|
|
276
|
-
continue;
|
|
277
|
-
}
|
|
278
|
-
if (!this.admissionClosed) {
|
|
279
|
-
await serve({
|
|
280
|
-
directory: this.directory,
|
|
281
|
-
claim,
|
|
282
|
-
...this.input,
|
|
283
|
-
signal: this.executionSignal.signal,
|
|
284
|
-
admissionOpen: () => !this.admissionClosed,
|
|
285
|
-
});
|
|
286
|
-
}
|
|
287
|
-
this.handled.add(id);
|
|
288
|
-
}
|
|
289
|
-
try {
|
|
290
|
-
await abortableDelay(POLL_MS, this.closeSignal.signal);
|
|
291
|
-
}
|
|
292
|
-
catch (error) {
|
|
293
|
-
if (this.closeSignal.signal.aborted)
|
|
294
|
-
return;
|
|
295
|
-
throw error;
|
|
296
|
-
}
|
|
297
|
-
}
|
|
298
|
-
}
|
|
299
|
-
async close() {
|
|
300
|
-
this.stopAdmission();
|
|
301
|
-
try {
|
|
302
|
-
await this.running;
|
|
303
|
-
}
|
|
304
|
-
finally {
|
|
305
|
-
this.input.signal.removeEventListener("abort", this.cancelBody);
|
|
306
|
-
await rm(this.directory, { recursive: true, force: true });
|
|
307
|
-
}
|
|
308
|
-
}
|
|
309
|
-
stopAdmission() {
|
|
310
|
-
this.admissionClosed = true;
|
|
311
|
-
if (!this.closeSignal.signal.aborted) {
|
|
312
|
-
this.closeSignal.abort(new Error("Body request pump closed"));
|
|
313
|
-
}
|
|
314
|
-
}
|
|
315
|
-
}
|
|
316
|
-
export async function clearBodyRequestTransport(paths) {
|
|
317
|
-
await rm(join(paths.directory, "requests"), { recursive: true, force: true });
|
|
318
|
-
}
|
|
319
|
-
function matchingRequestOrigin(soul, parent, id) {
|
|
320
|
-
return soul.origin.kind === "request" && soul.origin.parent === parent && soul.origin.requestId === id;
|
|
321
|
-
}
|
|
322
|
-
async function settleReservedSoul(paths, parent, request, soul) {
|
|
323
|
-
await (matchingRequestOrigin(soul, parent.id, request.id)
|
|
324
|
-
? serveRequest(paths, request.id, request.child)
|
|
325
|
-
: voidRequest(paths, request.id, "reserved child origin does not match the request"));
|
|
326
|
-
}
|
|
327
|
-
async function settleReserved(paths, parent, request, now, signal) {
|
|
328
|
-
const childPaths = pathsForAkuId(request.world, request.child);
|
|
329
|
-
const deadline = performance.now() + BIRTH_TIMEOUT_MS;
|
|
330
|
-
for (;;) {
|
|
331
|
-
signal?.throwIfAborted();
|
|
332
|
-
if (!(await access(childPaths.directory).then(() => true, () => false))) {
|
|
333
|
-
await voidRequest(paths, request.id, "reserved child directory is absent");
|
|
334
|
-
return true;
|
|
335
|
-
}
|
|
336
|
-
const soul = await readSoul(childPaths);
|
|
337
|
-
if (soul !== null) {
|
|
338
|
-
await settleReservedSoul(paths, parent, request, soul);
|
|
339
|
-
return true;
|
|
340
|
-
}
|
|
341
|
-
const leash = await HeldAkumaLeash.try(childPaths);
|
|
342
|
-
if (leash !== null) {
|
|
343
|
-
try {
|
|
344
|
-
const settled = await readSoul(childPaths);
|
|
345
|
-
if (settled !== null) {
|
|
346
|
-
await settleReservedSoul(paths, parent, request, settled);
|
|
347
|
-
}
|
|
348
|
-
else {
|
|
349
|
-
await leash.sealIfUnborn(childPaths, {
|
|
350
|
-
evidence: "request settlement",
|
|
351
|
-
at: now(),
|
|
352
|
-
});
|
|
353
|
-
await voidRequest(paths, request.id, "reserved child was sealed unborn");
|
|
354
|
-
}
|
|
355
|
-
}
|
|
356
|
-
finally {
|
|
357
|
-
leash.release();
|
|
358
|
-
}
|
|
359
|
-
return true;
|
|
360
|
-
}
|
|
361
|
-
if (performance.now() >= deadline)
|
|
362
|
-
return false;
|
|
363
|
-
await abortableDelay(Math.min(POLL_MS, Math.max(0, deadline - performance.now())), signal);
|
|
364
|
-
}
|
|
365
|
-
}
|
|
366
|
-
export async function settleBodyRequests(paths, parent, now, signal) {
|
|
367
|
-
let pending = false;
|
|
368
|
-
for (const request of await readNonterminalRequests(paths)) {
|
|
369
|
-
signal?.throwIfAborted();
|
|
370
|
-
if (request.state === "admitted") {
|
|
371
|
-
await voidRequest(paths, request.id, "body died before serving the request");
|
|
372
|
-
}
|
|
373
|
-
else if (request.state === "reserved" && !(await settleReserved(paths, parent, request, now, signal)))
|
|
374
|
-
pending = true;
|
|
249
|
+
return (await LifecycleBodyRequestPump.openWithService(input, async (claim) => await serveRequest(claim)));
|
|
375
250
|
}
|
|
376
|
-
return pending ? "pending" : "settled";
|
|
377
251
|
}
|
|
@@ -210,7 +210,7 @@ export function decodeClaim(bytes, fileId) {
|
|
|
210
210
|
const value = object(decoded);
|
|
211
211
|
if (value === null ||
|
|
212
212
|
!exactKeys(value, ["action", "id", "payload"]) ||
|
|
213
|
-
|
|
213
|
+
!UUID.test(fileId) ||
|
|
214
214
|
typeof value.id !== "string" ||
|
|
215
215
|
!UUID.test(value.id))
|
|
216
216
|
return null;
|
|
@@ -26,12 +26,13 @@ export function injectedBodyRequests() {
|
|
|
26
26
|
}
|
|
27
27
|
async function requestBody(input) {
|
|
28
28
|
input.signal?.throwIfAborted();
|
|
29
|
-
|
|
29
|
+
const transportId = randomUUID();
|
|
30
|
+
await atomicJson(requestPath(input.directory, transportId), {
|
|
30
31
|
id: input.claim.id,
|
|
31
32
|
action: input.claim.action,
|
|
32
33
|
payload: requestPayload(input.claim),
|
|
33
34
|
});
|
|
34
|
-
const path = receiptPath(input.directory,
|
|
35
|
+
const path = receiptPath(input.directory, transportId);
|
|
35
36
|
for (;;) {
|
|
36
37
|
try {
|
|
37
38
|
const receipt = decodeReceipt(await readFile(path, "utf8"), input.claim.id, input.claim.action);
|
|
@@ -3,6 +3,7 @@ import { Keiyaku, type AkumaKillResult, type AkumaHistoryResult, type AkumaObser
|
|
|
3
3
|
import type { Settings } from "../../settings.js";
|
|
4
4
|
import type { WorldRoot } from "../../world.js";
|
|
5
5
|
import type { InvokedAkumaCommand } from "./akuma.js";
|
|
6
|
+
import { finishCall } from "../../library/akuma-creation.js";
|
|
6
7
|
export type AkumaInvocationResult = Readonly<{
|
|
7
8
|
kind: "akuma";
|
|
8
9
|
action: "call";
|
|
@@ -55,6 +56,13 @@ export type AkumaInvocationResult = Readonly<{
|
|
|
55
56
|
mode: "no-answer";
|
|
56
57
|
historyResult: AkumaHistoryResult;
|
|
57
58
|
alias?: string;
|
|
59
|
+
}> | Readonly<{
|
|
60
|
+
kind: "akuma";
|
|
61
|
+
action: "history";
|
|
62
|
+
akuma: AkuId;
|
|
63
|
+
mode: "exact";
|
|
64
|
+
historyResult: AkumaHistoryResult;
|
|
65
|
+
alias?: string;
|
|
58
66
|
}> | Readonly<{
|
|
59
67
|
kind: "akuma";
|
|
60
68
|
action: "fork";
|
|
@@ -74,6 +82,7 @@ type InvokeInput = Readonly<{
|
|
|
74
82
|
repo?: Repo;
|
|
75
83
|
environment: NodeJS.ProcessEnv;
|
|
76
84
|
readStdin(): Promise<string>;
|
|
85
|
+
finishCall?: typeof finishCall;
|
|
77
86
|
}>;
|
|
78
87
|
export declare function invokeAkuma(command: InvokedAkumaCommand, input: InvokeInput): Promise<AkumaInvocationResult>;
|
|
79
88
|
export declare function invokeAkumaStatus(path: WorldRoot, akuma: string, alias?: string, repo?: Repo): Promise<AkumaInvocationResult>;
|
|
@@ -58,6 +58,7 @@ async function invokeHistory(command, input) {
|
|
|
58
58
|
...(command.before === undefined ? {} : { before: command.before }),
|
|
59
59
|
...(command.since === undefined ? {} : { since: command.since }),
|
|
60
60
|
...(command.limit === undefined ? {} : { limit: command.limit }),
|
|
61
|
+
...(command.id === undefined ? {} : { id: command.id }),
|
|
61
62
|
last: command.last,
|
|
62
63
|
});
|
|
63
64
|
return {
|
|
@@ -66,11 +67,13 @@ async function invokeHistory(command, input) {
|
|
|
66
67
|
akuma: result.id,
|
|
67
68
|
historyResult: result,
|
|
68
69
|
...(inputAlias(command.akuma) === undefined ? {} : { alias: command.akuma }),
|
|
69
|
-
...(
|
|
70
|
-
? { mode: "
|
|
71
|
-
: result.kind === "
|
|
72
|
-
? { mode: "
|
|
73
|
-
:
|
|
70
|
+
...(command.id !== undefined
|
|
71
|
+
? { mode: "exact" }
|
|
72
|
+
: result.kind === "history"
|
|
73
|
+
? { mode: "page", history: result.history }
|
|
74
|
+
: result.kind === "last"
|
|
75
|
+
? { mode: "last", answer: result.answer }
|
|
76
|
+
: { mode: "no-answer" }),
|
|
74
77
|
};
|
|
75
78
|
}
|
|
76
79
|
async function invokeFork(command, input) {
|
|
@@ -121,7 +124,7 @@ export async function invokeAkuma(command, input) {
|
|
|
121
124
|
: undefined;
|
|
122
125
|
let result;
|
|
123
126
|
try {
|
|
124
|
-
result = await finishCall(born);
|
|
127
|
+
result = await (input.finishCall ?? finishCall)(born, listener?.participantName);
|
|
125
128
|
}
|
|
126
129
|
catch (error) {
|
|
127
130
|
if (listener?.committed === true) {
|
|
@@ -3,6 +3,7 @@ import { parseAkuId } from "../../akuma/identity.js";
|
|
|
3
3
|
import { parseDuration as decodeDuration } from "../../duration.js";
|
|
4
4
|
import { parseAkumaAlias, parseAkumaGlob } from "../../identity/selector.js";
|
|
5
5
|
import { decodeAllowedActions } from "../../akuma/allowed.js";
|
|
6
|
+
import { parsePublicHistoryId } from "../../akuma/identity.js";
|
|
6
7
|
const AKUMA_COMMAND_SPECS = {
|
|
7
8
|
call: {
|
|
8
9
|
arity: 1,
|
|
@@ -49,8 +50,8 @@ const AKUMA_COMMAND_SPECS = {
|
|
|
49
50
|
history: {
|
|
50
51
|
arity: 1,
|
|
51
52
|
stdin: false,
|
|
52
|
-
flags: { before: "value", since: "value", limit: "value", last: "boolean", json: "boolean" },
|
|
53
|
-
usage: "history <aku/...|@alias|kei/...> [--before <index> | --since <index>] [--limit <count>] [--last]",
|
|
53
|
+
flags: { id: "value", before: "value", since: "value", limit: "value", last: "boolean", json: "boolean" },
|
|
54
|
+
usage: "history <aku/...|@alias|kei/...> [--id <historyId> | --before <index> | --since <index>] [--limit <count>] [--last]",
|
|
54
55
|
purpose: "Read Akuma execution history or one complete Contract journal and Dispatch timeline.",
|
|
55
56
|
},
|
|
56
57
|
fork: {
|
|
@@ -203,22 +204,31 @@ function parseWait(rawSelectors, flags, output, fail) {
|
|
|
203
204
|
}
|
|
204
205
|
function parseHistory(selector, flags, output, fail) {
|
|
205
206
|
if (selector.startsWith("kei/")) {
|
|
206
|
-
if (flags.before
|
|
207
|
-
fail("history kei/... does not accept --before, --since, --limit, or --last");
|
|
207
|
+
if ([flags.id, flags.before, flags.since, flags.limit, flags.last].some((value) => value !== undefined)) {
|
|
208
|
+
fail("history kei/... does not accept --id, --before, --since, --limit, or --last");
|
|
208
209
|
}
|
|
209
210
|
return { command: "history", contract: selector, output };
|
|
210
211
|
}
|
|
212
|
+
return parseAkumaHistory(selector, flags, output, fail);
|
|
213
|
+
}
|
|
214
|
+
function parseAkumaHistory(selector, flags, output, fail) {
|
|
215
|
+
const bounded = flags.before !== undefined || flags.since !== undefined || flags.limit !== undefined;
|
|
211
216
|
if (flags.before !== undefined && flags.since !== undefined)
|
|
212
217
|
fail("history --before and --since are mutually exclusive");
|
|
213
|
-
if (flags.
|
|
218
|
+
if (flags.id !== undefined && (flags.last === true || bounded))
|
|
219
|
+
fail("history --id cannot be combined with --last, --before, --since, or --limit");
|
|
220
|
+
if (flags.last === true && bounded)
|
|
214
221
|
fail("history --last cannot be combined with --before, --since, or --limit");
|
|
215
|
-
}
|
|
216
222
|
const limit = flags.limit === undefined ? undefined : positiveIndex(flags.limit, "--limit", fail);
|
|
217
223
|
if (limit !== undefined && limit > 5_000)
|
|
218
224
|
fail("--limit must be no greater than 5000");
|
|
225
|
+
const id = flags.id === undefined ? undefined : stringFlag(flags.id, "history --id requires a nonblank historyId", fail);
|
|
226
|
+
if (id !== undefined && parsePublicHistoryId(id) === null)
|
|
227
|
+
fail("history --id requires turn/<positive safe integer>");
|
|
219
228
|
return {
|
|
220
229
|
command: "history",
|
|
221
230
|
akuma: validateDirect(selector, fail),
|
|
231
|
+
...(id === undefined ? {} : { id }),
|
|
222
232
|
last: flags.last === true,
|
|
223
233
|
...(flags.before === undefined ? {} : { before: positiveIndex(flags.before, "--before", fail) }),
|
|
224
234
|
...(flags.since === undefined ? {} : { since: positiveIndex(flags.since, "--since", fail) }),
|
|
@@ -56,6 +56,7 @@ export type ParsedAbandon = Output & Actor & Readonly<{
|
|
|
56
56
|
export type ParsedStatus = Output & (Readonly<{
|
|
57
57
|
command: "status";
|
|
58
58
|
contract?: string;
|
|
59
|
+
selectors?: readonly string[];
|
|
59
60
|
akuma?: never;
|
|
60
61
|
}> | Readonly<{
|
|
61
62
|
command: "status";
|
|
@@ -178,8 +179,8 @@ export declare const CONTRACT_COMMAND_SPECS: {
|
|
|
178
179
|
readonly flags: {
|
|
179
180
|
readonly json: "boolean";
|
|
180
181
|
};
|
|
181
|
-
readonly usage: "status [<contract>|@name|<aku/...>]";
|
|
182
|
-
readonly purpose: "Read the world status board or one Contract
|
|
182
|
+
readonly usage: "status [<contract>|@name|<aku/...>]...";
|
|
183
|
+
readonly purpose: "Read the world status board or one or more Contract and Akuma projections.";
|
|
183
184
|
};
|
|
184
185
|
readonly show: {
|
|
185
186
|
readonly positional: "optional";
|
|
@@ -87,8 +87,8 @@ export const CONTRACT_COMMAND_SPECS = {
|
|
|
87
87
|
positional: "optional",
|
|
88
88
|
stdin: "none",
|
|
89
89
|
flags: { json: "boolean" },
|
|
90
|
-
usage: "status [<contract>|@name|<aku/...>]",
|
|
91
|
-
purpose: "Read the world status board or one Contract
|
|
90
|
+
usage: "status [<contract>|@name|<aku/...>]...",
|
|
91
|
+
purpose: "Read the world status board or one or more Contract and Akuma projections.",
|
|
92
92
|
},
|
|
93
93
|
show: {
|
|
94
94
|
positional: "optional",
|
|
@@ -244,6 +244,9 @@ function parseReview(parts) {
|
|
|
244
244
|
};
|
|
245
245
|
}
|
|
246
246
|
function parseStatus(parts) {
|
|
247
|
+
if (parts.positionals.length > 1) {
|
|
248
|
+
return { command: "status", selectors: parts.positionals, output: parts.output };
|
|
249
|
+
}
|
|
247
250
|
const contract = parts.positionals[0];
|
|
248
251
|
if (contract?.startsWith("aku/") === true) {
|
|
249
252
|
return { command: "status", contract, akuma: true, output: parts.output };
|
|
@@ -0,0 +1,4 @@
|
|
|
1
|
+
import type { StatusSetResult } from "../result.js";
|
|
2
|
+
import type { Repo } from "../../library/repo.js";
|
|
3
|
+
import type { WorldRoot } from "../../world.js";
|
|
4
|
+
export declare function invokeStatusSet(selectors: readonly string[], world: WorldRoot | null, repo: Repo | undefined): Promise<StatusSetResult>;
|
|
@@ -0,0 +1,61 @@
|
|
|
1
|
+
import { CliUsageError } from "../parse.js";
|
|
2
|
+
import { canonicalContractSelector } from "../selectors.js";
|
|
3
|
+
async function statusSetAkuma(selector, id, alias, world, repo) {
|
|
4
|
+
if (world === null)
|
|
5
|
+
throw new CliUsageError("no Keiyaku world contains the Akuma selector");
|
|
6
|
+
const result = await (await import("./akuma-invoke.js")).invokeAkumaStatus(world, id, alias, repo);
|
|
7
|
+
if (result.action !== "status")
|
|
8
|
+
throw new Error("status invocation returned a non-status Akuma result");
|
|
9
|
+
return {
|
|
10
|
+
selector,
|
|
11
|
+
kind: "akuma",
|
|
12
|
+
status: result.status,
|
|
13
|
+
...(result.alias === undefined ? {} : { alias: result.alias }),
|
|
14
|
+
};
|
|
15
|
+
}
|
|
16
|
+
async function statusSetEntry(selector, world, repo, observed) {
|
|
17
|
+
let resolved = selector;
|
|
18
|
+
let alias;
|
|
19
|
+
if (selector.startsWith("@")) {
|
|
20
|
+
if (observed === undefined)
|
|
21
|
+
throw new CliUsageError("cannot resolve a named status selector");
|
|
22
|
+
const { resolveNamedAddress } = await import("../../library/address.js");
|
|
23
|
+
const address = resolveNamedAddress({ selector, report: observed.report, aliases: observed.aliases });
|
|
24
|
+
if (address.kind === "akuma")
|
|
25
|
+
return await statusSetAkuma(selector, address.id, selector, world, repo);
|
|
26
|
+
resolved = address.id;
|
|
27
|
+
alias = selector;
|
|
28
|
+
}
|
|
29
|
+
if (resolved.startsWith("aku/"))
|
|
30
|
+
return await statusSetAkuma(selector, resolved, alias, world, repo);
|
|
31
|
+
if (repo === undefined)
|
|
32
|
+
throw new CliUsageError("cannot select a contract while the Contract world is absent");
|
|
33
|
+
if (observed === undefined)
|
|
34
|
+
throw new CliUsageError("cannot observe Contract status without a Kanshi report");
|
|
35
|
+
return {
|
|
36
|
+
selector,
|
|
37
|
+
kind: "contract",
|
|
38
|
+
report: (await import("../../kanshi/index.js")).selectKanshi({
|
|
39
|
+
report: observed.report,
|
|
40
|
+
contract: canonicalContractSelector(resolved),
|
|
41
|
+
}),
|
|
42
|
+
};
|
|
43
|
+
}
|
|
44
|
+
export async function invokeStatusSet(selectors, world, repo) {
|
|
45
|
+
if (selectors.length < 2)
|
|
46
|
+
throw new CliUsageError("status requires at least two selectors");
|
|
47
|
+
if (selectors.some((selector) => !selector.startsWith("aku/") && !selector.startsWith("@")) && repo === undefined) {
|
|
48
|
+
throw new CliUsageError("cannot select a contract while the Contract world is absent");
|
|
49
|
+
}
|
|
50
|
+
const needsKanshi = selectors.some((selector) => !selector.startsWith("aku/"));
|
|
51
|
+
const observed = needsKanshi
|
|
52
|
+
? await (await import("../../kanshi/index.js")).observeKanshi({
|
|
53
|
+
world,
|
|
54
|
+
...(repo === undefined ? {} : { repo }),
|
|
55
|
+
})
|
|
56
|
+
: undefined;
|
|
57
|
+
const entries = [];
|
|
58
|
+
for (const selector of selectors)
|
|
59
|
+
entries.push(await statusSetEntry(selector, world, repo, observed));
|
|
60
|
+
return { kind: "status-set", entries };
|
|
61
|
+
}
|
package/build/src/cli/invoke.js
CHANGED
|
@@ -158,6 +158,11 @@ async function invokeCatalog(parsed, world, repo, home) {
|
|
|
158
158
|
}
|
|
159
159
|
}
|
|
160
160
|
async function invokeStatus(parsed, world, repo) {
|
|
161
|
+
const selectors = "selectors" in parsed ? parsed.selectors : undefined;
|
|
162
|
+
if (selectors !== undefined) {
|
|
163
|
+
const { invokeStatusSet } = await import("./commands/status-set.js");
|
|
164
|
+
return await invokeStatusSet(selectors, world, repo);
|
|
165
|
+
}
|
|
161
166
|
if (parsed.akuma === true) {
|
|
162
167
|
if (world === null)
|
|
163
168
|
throw new CliUsageError("no Keiyaku world contains the invocation cwd");
|
package/build/src/cli/parse.js
CHANGED
|
@@ -164,7 +164,7 @@ function scanArgv(argv) {
|
|
|
164
164
|
if (spec.positional === "none" && state.positionals.length > 0) {
|
|
165
165
|
refuse(command, `${command} accepts no contract`);
|
|
166
166
|
}
|
|
167
|
-
if (spec.positional === "optional" && state.positionals.length > 1) {
|
|
167
|
+
if (spec.positional === "optional" && state.positionals.length > 1 && command !== "status") {
|
|
168
168
|
refuse(command, `${command} accepts at most one contract`);
|
|
169
169
|
}
|
|
170
170
|
if (spec.stdin === "required" && !state.stdin) {
|
|
@@ -316,6 +316,11 @@ export function akumaRawAnswer(result) {
|
|
|
316
316
|
}
|
|
317
317
|
if (result.action === "wait" && result.result.observations.length === 1)
|
|
318
318
|
return statusAnswer(result.result.observations[0]);
|
|
319
|
+
if (result.action === "history" && result.mode === "exact" && result.historyResult.kind === "exact") {
|
|
320
|
+
return result.historyResult.outcome.outcome.kind === "answered"
|
|
321
|
+
? result.historyResult.outcome.outcome.answer
|
|
322
|
+
: result.historyResult.outcome.outcome.diagnostic;
|
|
323
|
+
}
|
|
319
324
|
return undefined;
|
|
320
325
|
}
|
|
321
326
|
export function waitText(result, context) {
|
|
@@ -336,6 +341,12 @@ export function waitText(result, context) {
|
|
|
336
341
|
return [...blocks, `${done} of ${total} done`].join("\n\n");
|
|
337
342
|
}
|
|
338
343
|
export function historyText(command, result, context) {
|
|
344
|
+
if (result.mode === "exact") {
|
|
345
|
+
const exact = result.historyResult;
|
|
346
|
+
if (exact.kind !== "exact")
|
|
347
|
+
return `${exact.kind === "unknown-history" ? exact.historyId : "unknown"} has no matching retained outcome`;
|
|
348
|
+
return exact.outcome.outcome.kind === "answered" ? exact.outcome.outcome.answer : exact.outcome.outcome.diagnostic;
|
|
349
|
+
}
|
|
339
350
|
if (command.last)
|
|
340
351
|
return result.mode === "last" ? result.answer : "no answer retained";
|
|
341
352
|
if (result.mode !== "page")
|
|
@@ -123,6 +123,11 @@ function tellExitCode(result) {
|
|
|
123
123
|
function forkExitCode(result) {
|
|
124
124
|
return result.receipt.kind === "forked" ? 0 : result.receipt.kind === "upstream-forked" ? 2 : 1;
|
|
125
125
|
}
|
|
126
|
+
function historyExitCode(result) {
|
|
127
|
+
if (result.mode !== "exact")
|
|
128
|
+
return 0;
|
|
129
|
+
return result.historyResult.kind === "exact" ? 0 : 1;
|
|
130
|
+
}
|
|
126
131
|
export function akumaExitCode(result) {
|
|
127
132
|
switch (result.action) {
|
|
128
133
|
case "call":
|
|
@@ -133,6 +138,8 @@ export function akumaExitCode(result) {
|
|
|
133
138
|
return tellExitCode(result);
|
|
134
139
|
case "fork":
|
|
135
140
|
return forkExitCode(result);
|
|
141
|
+
case "history":
|
|
142
|
+
return historyExitCode(result);
|
|
136
143
|
default:
|
|
137
144
|
return 0;
|
|
138
145
|
}
|