@astrosheep/keiyaku 4.1.1 → 4.1.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/src/akuma/akuma-errors.d.ts +6 -0
- package/build/src/akuma/akuma-errors.js +9 -0
- package/build/src/akuma/akuma-handle.d.ts +39 -0
- package/build/src/akuma/akuma-handle.js +263 -0
- package/build/src/akuma/akuma-observe.d.ts +19 -0
- package/build/src/akuma/akuma-observe.js +74 -0
- package/build/src/akuma/akuma-product-symbols.d.ts +1 -0
- package/build/src/akuma/akuma-product-symbols.js +1 -0
- package/build/src/akuma/akuma-product.d.ts +18 -0
- package/build/src/akuma/akuma-product.js +154 -0
- package/build/src/akuma/akuma.d.ts +23 -70
- package/build/src/akuma/akuma.js +17 -433
- package/build/src/akuma/body-supervisor.d.ts +26 -0
- package/build/src/akuma/body-supervisor.js +89 -0
- package/build/src/akuma/body.d.ts +1 -1
- package/build/src/akuma/body.js +3 -2
- package/build/src/akuma/projection-read.d.ts +17 -0
- package/build/src/akuma/projection-read.js +135 -0
- package/build/src/akuma/projection.d.ts +1 -18
- package/build/src/akuma/projection.js +21 -155
- package/build/src/akuma/request-execution.d.ts +6 -0
- package/build/src/akuma/request-execution.js +86 -0
- package/build/src/akuma/request-serve.d.ts +12 -1
- package/build/src/akuma/request-serve.js +2 -102
- package/build/src/akuma/{body-turn.d.ts → turn-drive.d.ts} +2 -25
- package/build/src/akuma/{body-turn.js → turn-drive.js} +4 -99
- package/build/src/cli/commands/akuma.d.ts +0 -6
- package/build/src/cli/commands/akuma.js +1 -11
- package/build/src/cli/commands/contract-invoke.d.ts +25 -0
- package/build/src/cli/commands/contract-invoke.js +193 -0
- package/build/src/cli/commands/contract.d.ts +12 -0
- package/build/src/cli/commands/contract.js +229 -0
- package/build/src/cli/invoke.js +24 -184
- package/build/src/cli/parse.d.ts +1 -1
- package/build/src/cli/parse.js +8 -222
- package/build/src/cli/render/akuma-activity.d.ts +38 -0
- package/build/src/cli/render/akuma-activity.js +362 -0
- package/build/src/cli/render/akuma.d.ts +1 -1
- package/build/src/cli/render/akuma.js +14 -371
- package/build/src/cli/render/contract-history.d.ts +2 -0
- package/build/src/cli/render/contract-history.js +83 -0
- package/build/src/cli/render/contract.d.ts +1 -2
- package/build/src/cli/render/contract.js +1 -83
- package/build/src/git/reconcile.d.ts +8 -1
- package/build/src/git/reconcile.js +13 -301
- package/build/src/git/scratch.d.ts +8 -5
- package/build/src/git/scratch.js +29 -2
- package/build/src/git/terminal-reconcile.d.ts +21 -0
- package/build/src/git/terminal-reconcile.js +278 -0
- package/build/src/library/contract-bind.d.ts +26 -0
- package/build/src/library/contract-bind.js +106 -0
- package/build/src/library/contract-operations.d.ts +77 -0
- package/build/src/library/contract-operations.js +130 -0
- package/build/src/library/contract-types.d.ts +95 -0
- package/build/src/library/contract-types.js +1 -0
- package/build/src/library/contract.d.ts +11 -136
- package/build/src/library/contract.js +9 -222
- package/package.json +1 -1
package/build/src/cli/parse.js
CHANGED
|
@@ -1,9 +1,10 @@
|
|
|
1
1
|
import { isTaskAction, parseTaskCommand, renderTaskHelp, renderTaskUsage, } from "./commands/task.js";
|
|
2
|
-
import { isAkumaAction,
|
|
2
|
+
import { isAkumaAction, parseAkumaCommand, renderAkumaHelp, renderAkumaRootRows, renderAkumaUsage, } from "./commands/akuma.js";
|
|
3
3
|
import { INSTALL_USAGE, parseInstallCommand, renderInstallHelp, } from "./commands/install.js";
|
|
4
|
-
import { CONTRACT_COMMAND_SPECS, } from "./commands/contract.js";
|
|
5
|
-
import { CliUsageError, isBlankInput
|
|
4
|
+
import { CONTRACT_COMMAND_SPECS, parseContractCommand, renderContractHelp as renderContractHelpForOwner, renderContractUsage, } from "./commands/contract.js";
|
|
5
|
+
import { CliUsageError, isBlankInput } from "./usage.js";
|
|
6
6
|
export { CliUsageError } from "./usage.js";
|
|
7
|
+
export { renderContractHelp } from "./commands/contract.js";
|
|
7
8
|
const ROOT_USAGE = "usage: keiyaku [-C <path>] [--repo <path>] <command> [<contract>|@<contract>] [--flag ...] [-]";
|
|
8
9
|
export function renderRootHelp() {
|
|
9
10
|
return [
|
|
@@ -22,13 +23,8 @@ export function renderRootHelp() {
|
|
|
22
23
|
...renderAkumaRootRows(),
|
|
23
24
|
].join("\n");
|
|
24
25
|
}
|
|
25
|
-
export function renderContractHelp(command) {
|
|
26
|
-
const spec = CONTRACT_COMMAND_SPECS[command];
|
|
27
|
-
const help = `${spec.purpose}\n\n${usageLine(spec.usage)}`;
|
|
28
|
-
return spec.details === undefined ? help : `${help}\n\n${spec.details}`;
|
|
29
|
-
}
|
|
30
26
|
function contractUsage(command) {
|
|
31
|
-
return
|
|
27
|
+
return renderContractUsage(command);
|
|
32
28
|
}
|
|
33
29
|
export function renderCommandUsage(command) {
|
|
34
30
|
if (command.command === "install")
|
|
@@ -44,7 +40,7 @@ export function renderHelp(coordinate) {
|
|
|
44
40
|
case "root":
|
|
45
41
|
return renderRootHelp();
|
|
46
42
|
case "contract":
|
|
47
|
-
return
|
|
43
|
+
return renderContractHelpForOwner(coordinate.command);
|
|
48
44
|
case "task":
|
|
49
45
|
return renderTaskHelp(coordinate.action);
|
|
50
46
|
case "install":
|
|
@@ -103,10 +99,6 @@ export function assertExplicitRepoUse(command, repo) {
|
|
|
103
99
|
if (repo !== undefined && !commandRepoPolicy(command).acceptsExplicit)
|
|
104
100
|
refuseUnusedRepo(command);
|
|
105
101
|
}
|
|
106
|
-
function optionalFlag(flags, name) {
|
|
107
|
-
const value = flags[name];
|
|
108
|
-
return typeof value === "string" ? value : undefined;
|
|
109
|
-
}
|
|
110
102
|
function refuse(command, message) {
|
|
111
103
|
throw new CliUsageError(message, contractUsage(command));
|
|
112
104
|
}
|
|
@@ -179,147 +171,9 @@ function scanArgv(argv) {
|
|
|
179
171
|
refuse(command, `${command} requires stdin`);
|
|
180
172
|
}
|
|
181
173
|
const output = state.flags.json === true ? "json" : "text";
|
|
182
|
-
const actor =
|
|
174
|
+
const actor = typeof state.flags.actor === "string" ? state.flags.actor : undefined;
|
|
183
175
|
return { command, ...state, output, ...(actor === undefined ? {} : { actor }) };
|
|
184
176
|
}
|
|
185
|
-
function parseBind(parts) {
|
|
186
|
-
const forkOf = optionalFlag(parts.flags, "fork-of");
|
|
187
|
-
if (forkOf !== undefined) {
|
|
188
|
-
if (parts.stdin)
|
|
189
|
-
refuse("bind", "fork bind reads no stdin");
|
|
190
|
-
for (const option of ["task", "after", "gates"]) {
|
|
191
|
-
if (parts.flags[option] !== undefined)
|
|
192
|
-
refuse("bind", `--${option} is not valid with --fork-of`);
|
|
193
|
-
}
|
|
194
|
-
const target = optionalFlag(parts.flags, "target");
|
|
195
|
-
return {
|
|
196
|
-
command: "bind",
|
|
197
|
-
forkOf,
|
|
198
|
-
...(target === undefined ? {} : { target }),
|
|
199
|
-
...(parts.actor === undefined ? {} : { actor: parts.actor }),
|
|
200
|
-
output: parts.output,
|
|
201
|
-
};
|
|
202
|
-
}
|
|
203
|
-
if (!parts.stdin)
|
|
204
|
-
refuse("bind", "bind requires stdin");
|
|
205
|
-
const task = optionalFlag(parts.flags, "task");
|
|
206
|
-
const target = optionalFlag(parts.flags, "target");
|
|
207
|
-
const after = parts.flags.after === undefined ? [] : Array.isArray(parts.flags.after) ? parts.flags.after : [parts.flags.after];
|
|
208
|
-
const gates = parseGateBundleNames(parts, "bind");
|
|
209
|
-
return {
|
|
210
|
-
command: "bind",
|
|
211
|
-
...(task === undefined ? {} : { task }),
|
|
212
|
-
...(target === undefined ? {} : { target }),
|
|
213
|
-
...(parts.flags.after === undefined ? {} : { after }),
|
|
214
|
-
...(gates === undefined ? {} : { gates }),
|
|
215
|
-
...(parts.actor === undefined ? {} : { actor: parts.actor }),
|
|
216
|
-
output: parts.output,
|
|
217
|
-
};
|
|
218
|
-
}
|
|
219
|
-
function parseAmend(parts) {
|
|
220
|
-
const contract = parts.positionals[0];
|
|
221
|
-
const after = parts.flags.after === undefined ? [] : Array.isArray(parts.flags.after) ? parts.flags.after : [parts.flags.after];
|
|
222
|
-
if (parts.flags["clear-after"] === true && after.length > 0)
|
|
223
|
-
refuse("amend", "--clear-after and --after are mutually exclusive");
|
|
224
|
-
const gates = parseGateBundleNames(parts, "amend");
|
|
225
|
-
if (!parts.stdin && parts.flags.after === undefined && parts.flags["clear-after"] !== true && gates === undefined) {
|
|
226
|
-
refuse("amend", "amend requires stdin or --after, --clear-after, or --gates");
|
|
227
|
-
}
|
|
228
|
-
return {
|
|
229
|
-
command: "amend",
|
|
230
|
-
...(contract === undefined ? {} : { contract }),
|
|
231
|
-
...(parts.flags.after === undefined ? {} : { after }),
|
|
232
|
-
...(parts.flags["clear-after"] === true ? { clearAfter: true } : {}),
|
|
233
|
-
...(gates === undefined ? {} : { gates }),
|
|
234
|
-
...(parts.actor === undefined ? {} : { actor: parts.actor }),
|
|
235
|
-
...(parts.stdin ? { stdin: true } : {}),
|
|
236
|
-
output: parts.output,
|
|
237
|
-
};
|
|
238
|
-
}
|
|
239
|
-
function parseGateBundleNames(parts, command) {
|
|
240
|
-
const value = optionalFlag(parts.flags, "gates");
|
|
241
|
-
if (value === undefined)
|
|
242
|
-
return undefined;
|
|
243
|
-
const names = value.split(",");
|
|
244
|
-
if (names.some((name) => name.length === 0)) {
|
|
245
|
-
refuse(command, "--gates requires nonempty comma-separated names");
|
|
246
|
-
}
|
|
247
|
-
return names;
|
|
248
|
-
}
|
|
249
|
-
function parseDeliver(parts) {
|
|
250
|
-
const contract = parts.positionals[0];
|
|
251
|
-
const message = optionalFlag(parts.flags, "message");
|
|
252
|
-
return {
|
|
253
|
-
command: "deliver",
|
|
254
|
-
...(contract === undefined ? {} : { contract }),
|
|
255
|
-
...(parts.actor === undefined ? {} : { actor: parts.actor }),
|
|
256
|
-
...(message === undefined ? {} : { message }),
|
|
257
|
-
includeDirty: parts.flags["include-dirty"] === true,
|
|
258
|
-
materializeConflict: parts.flags["materialize-conflict"] === true,
|
|
259
|
-
output: parts.output,
|
|
260
|
-
};
|
|
261
|
-
}
|
|
262
|
-
function parseReview(parts) {
|
|
263
|
-
if (Number(parts.flags.satisfied === true) + Number(parts.flags.unsatisfied === true) !== 1) {
|
|
264
|
-
refuse("review", "review requires exactly one verdict flag");
|
|
265
|
-
}
|
|
266
|
-
const summary = optionalFlag(parts.flags, "summary");
|
|
267
|
-
if (parts.stdin === (summary !== undefined)) {
|
|
268
|
-
refuse("review", "review requires exactly one of --summary <text> or stdin '-'");
|
|
269
|
-
}
|
|
270
|
-
const contract = parts.positionals[0];
|
|
271
|
-
return {
|
|
272
|
-
command: "review",
|
|
273
|
-
...(contract === undefined ? {} : { contract }),
|
|
274
|
-
verdict: parts.flags.satisfied === true ? "satisfied" : "unsatisfied",
|
|
275
|
-
...(summary === undefined ? {} : { summary }),
|
|
276
|
-
...(parts.stdin ? { summaryFromStdin: true } : {}),
|
|
277
|
-
...(parts.actor === undefined ? {} : { actor: parts.actor }),
|
|
278
|
-
output: parts.output,
|
|
279
|
-
};
|
|
280
|
-
}
|
|
281
|
-
function parseStatus(parts) {
|
|
282
|
-
const contract = parts.positionals[0];
|
|
283
|
-
if (contract?.startsWith("aku/") === true) {
|
|
284
|
-
return { command: "status", contract, akuma: true, output: parts.output };
|
|
285
|
-
}
|
|
286
|
-
return {
|
|
287
|
-
command: "status",
|
|
288
|
-
...(contract === undefined ? {} : { contract }),
|
|
289
|
-
output: parts.output,
|
|
290
|
-
};
|
|
291
|
-
}
|
|
292
|
-
function parseRegion(parts) {
|
|
293
|
-
const contract = parts.positionals[0];
|
|
294
|
-
const pathFlag = parts.flags.path;
|
|
295
|
-
const paths = pathFlag === undefined ? undefined : Array.isArray(pathFlag) ? pathFlag : [pathFlag];
|
|
296
|
-
if (paths !== undefined && contract !== undefined)
|
|
297
|
-
refuse("region", "--path cannot combine with a contract");
|
|
298
|
-
return {
|
|
299
|
-
command: "region",
|
|
300
|
-
...(contract === undefined ? {} : { contract }),
|
|
301
|
-
...(paths === undefined ? {} : { paths: paths }),
|
|
302
|
-
output: parts.output,
|
|
303
|
-
};
|
|
304
|
-
}
|
|
305
|
-
function parseLs(parts) {
|
|
306
|
-
const path = parts.positionals[0];
|
|
307
|
-
if (path === "task" || path === "task/")
|
|
308
|
-
return { command: "ls", query: { kind: "tasks" }, output: parts.output };
|
|
309
|
-
if (path === "kei" || path === "kei/")
|
|
310
|
-
return { command: "ls", query: { kind: "contracts" }, output: parts.output };
|
|
311
|
-
try {
|
|
312
|
-
const query = parseAkumaCatalogPath(path);
|
|
313
|
-
if (query === null)
|
|
314
|
-
refuse("ls", "ls requires a supported identity directory selector");
|
|
315
|
-
return { command: "ls", query, output: parts.output };
|
|
316
|
-
}
|
|
317
|
-
catch (error) {
|
|
318
|
-
if (error instanceof CliUsageError)
|
|
319
|
-
throw error;
|
|
320
|
-
refuse("ls", error instanceof Error ? error.message : "invalid ls directory");
|
|
321
|
-
}
|
|
322
|
-
}
|
|
323
177
|
function invocationOptions(argv) {
|
|
324
178
|
let cwd;
|
|
325
179
|
let repo;
|
|
@@ -371,74 +225,6 @@ function helpCoordinate(argv) {
|
|
|
371
225
|
}
|
|
372
226
|
return { kind: "root" };
|
|
373
227
|
}
|
|
374
|
-
function parseCommand(parts) {
|
|
375
|
-
switch (parts.command) {
|
|
376
|
-
case "bind":
|
|
377
|
-
return parseBind(parts);
|
|
378
|
-
case "amend":
|
|
379
|
-
return parseAmend(parts);
|
|
380
|
-
case "deliver":
|
|
381
|
-
return parseDeliver(parts);
|
|
382
|
-
case "review":
|
|
383
|
-
return parseReview(parts);
|
|
384
|
-
case "arc": {
|
|
385
|
-
const contract = parts.positionals[0];
|
|
386
|
-
return {
|
|
387
|
-
command: "arc",
|
|
388
|
-
...(contract === undefined ? {} : { contract }),
|
|
389
|
-
...(parts.actor === undefined ? {} : { actor: parts.actor }),
|
|
390
|
-
output: parts.output,
|
|
391
|
-
};
|
|
392
|
-
}
|
|
393
|
-
case "abandon": {
|
|
394
|
-
const contract = parts.positionals[0];
|
|
395
|
-
const note = optionalFlag(parts.flags, "note");
|
|
396
|
-
return {
|
|
397
|
-
command: "abandon",
|
|
398
|
-
...(contract === undefined ? {} : { contract }),
|
|
399
|
-
...(note === undefined ? {} : { note }),
|
|
400
|
-
...(parts.actor === undefined ? {} : { actor: parts.actor }),
|
|
401
|
-
output: parts.output,
|
|
402
|
-
};
|
|
403
|
-
}
|
|
404
|
-
case "status":
|
|
405
|
-
return parseStatus(parts);
|
|
406
|
-
case "show": {
|
|
407
|
-
const contract = parts.positionals[0];
|
|
408
|
-
return { command: "show", ...(contract === undefined ? {} : { contract }), output: parts.output };
|
|
409
|
-
}
|
|
410
|
-
case "ls":
|
|
411
|
-
return parseLs(parts);
|
|
412
|
-
case "audit": {
|
|
413
|
-
const contract = parts.positionals[0];
|
|
414
|
-
return {
|
|
415
|
-
command: "audit",
|
|
416
|
-
...(contract === undefined ? {} : { contract }),
|
|
417
|
-
includeDirty: parts.flags["include-dirty"] === true,
|
|
418
|
-
showDiff: parts.flags.diff === true,
|
|
419
|
-
...(parts.actor === undefined ? {} : { actor: parts.actor }),
|
|
420
|
-
output: parts.output,
|
|
421
|
-
};
|
|
422
|
-
}
|
|
423
|
-
case "reconcile": {
|
|
424
|
-
const contract = parts.positionals[0];
|
|
425
|
-
return {
|
|
426
|
-
command: "reconcile",
|
|
427
|
-
...(contract === undefined ? {} : { contract }),
|
|
428
|
-
retryHooks: parts.flags["retry-hooks"] === true,
|
|
429
|
-
output: parts.output,
|
|
430
|
-
};
|
|
431
|
-
}
|
|
432
|
-
case "nuke": {
|
|
433
|
-
const confirm = optionalFlag(parts.flags, "confirm");
|
|
434
|
-
return { command: "nuke", ...(confirm === undefined ? {} : { confirm }), output: parts.output };
|
|
435
|
-
}
|
|
436
|
-
case "settings":
|
|
437
|
-
return { command: "settings", output: parts.output };
|
|
438
|
-
case "region":
|
|
439
|
-
return parseRegion(parts);
|
|
440
|
-
}
|
|
441
|
-
}
|
|
442
228
|
export function parseArgv(argv) {
|
|
443
229
|
const invocation = invocationOptions(argv);
|
|
444
230
|
const help = helpCoordinate(invocation.commandArgv);
|
|
@@ -455,6 +241,6 @@ export function parseArgv(argv) {
|
|
|
455
241
|
return {
|
|
456
242
|
...(invocation.cwd === undefined ? {} : { cwd: invocation.cwd }),
|
|
457
243
|
...(invocation.repo === undefined ? {} : { repo: invocation.repo }),
|
|
458
|
-
command: task ?? akuma ?? install ??
|
|
244
|
+
command: task ?? akuma ?? install ?? parseContractCommand(scanArgv(invocation.commandArgv)),
|
|
459
245
|
};
|
|
460
246
|
}
|
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
import type { AkumaObservation, AkumaObservationStage, CreatedTaskObservation, DispatchAssociation } from "../../index.js";
|
|
2
|
+
import type { AkumaInvocationResult } from "../commands/akuma-invoke.js";
|
|
3
|
+
import type { ParsedCommand } from "../parse.js";
|
|
4
|
+
import { type TextRenderContext } from "./terminal.js";
|
|
5
|
+
export declare const DEFAULT_CONTEXT: TextRenderContext;
|
|
6
|
+
export declare function associatedIdentity(id: string, alias?: string, contract?: DispatchAssociation): string;
|
|
7
|
+
export declare function snapshotHeading(id: string, alias: string | undefined, contract: DispatchAssociation | undefined): readonly string[];
|
|
8
|
+
type SnapshotView = Readonly<{
|
|
9
|
+
status: AkumaObservation["status"];
|
|
10
|
+
contract: DispatchAssociation;
|
|
11
|
+
createdTasks?: CreatedTaskObservation;
|
|
12
|
+
}>;
|
|
13
|
+
type SnapshotCoreOptions = Readonly<{
|
|
14
|
+
alias?: string;
|
|
15
|
+
facts?: readonly string[];
|
|
16
|
+
}>;
|
|
17
|
+
export declare function snapshotText(view: SnapshotView, context: TextRenderContext, options?: SnapshotCoreOptions): string;
|
|
18
|
+
export declare function mutationObservationStageText(id: string, observation: AkumaObservationStage, context: TextRenderContext, options?: SnapshotCoreOptions & Readonly<{
|
|
19
|
+
showLife?: boolean;
|
|
20
|
+
}>): string;
|
|
21
|
+
export declare function statusAnswer(view: Readonly<{
|
|
22
|
+
status: AkumaObservation["status"];
|
|
23
|
+
}>): string | undefined;
|
|
24
|
+
export declare function akumaRawAnswer(result: AkumaInvocationResult): string | undefined;
|
|
25
|
+
export declare function waitText(result: Extract<AkumaInvocationResult, {
|
|
26
|
+
action: "wait";
|
|
27
|
+
}>, context: TextRenderContext): string;
|
|
28
|
+
export declare function historyText(command: Extract<ParsedCommand, {
|
|
29
|
+
command: "history";
|
|
30
|
+
last: boolean;
|
|
31
|
+
}>, result: Extract<AkumaInvocationResult, {
|
|
32
|
+
action: "history";
|
|
33
|
+
}>, context: TextRenderContext): string;
|
|
34
|
+
export declare function tellText(result: Extract<AkumaInvocationResult, {
|
|
35
|
+
action: "tell";
|
|
36
|
+
mode: "ordinary";
|
|
37
|
+
}>, context: TextRenderContext): string;
|
|
38
|
+
export {};
|
|
@@ -0,0 +1,362 @@
|
|
|
1
|
+
import { defaultWaitComplete } from "../../akuma/index.js";
|
|
2
|
+
import { toolRepr } from "./akuma-tool.js";
|
|
3
|
+
import { displayColumns, renderBoundedTextBlock, safeText, truncateMiddleDisplayText, } from "./terminal.js";
|
|
4
|
+
export const DEFAULT_CONTEXT = { columns: 80, color: false };
|
|
5
|
+
const SNAPSHOT_RULER = "────────────";
|
|
6
|
+
const TIME_WIDTH = 5;
|
|
7
|
+
const VERB_WIDTH = 6;
|
|
8
|
+
function identity(id, alias) {
|
|
9
|
+
return `${id}${alias === undefined ? "" : ` (${alias})`}`;
|
|
10
|
+
}
|
|
11
|
+
function associatedContractId(contract) {
|
|
12
|
+
return contract.kind === "associated" ? contract.contractId : undefined;
|
|
13
|
+
}
|
|
14
|
+
export function associatedIdentity(id, alias, contract) {
|
|
15
|
+
const contractId = contract === undefined ? undefined : associatedContractId(contract);
|
|
16
|
+
return `${identity(id, alias)}${contractId === undefined ? "" : ` [${contractId}]`}`;
|
|
17
|
+
}
|
|
18
|
+
export function snapshotHeading(id, alias, contract) {
|
|
19
|
+
const contractId = contract === undefined ? undefined : associatedContractId(contract);
|
|
20
|
+
return [identity(id, alias), SNAPSHOT_RULER, ...(contractId === undefined ? [] : [`└─ ${contractId}`])];
|
|
21
|
+
}
|
|
22
|
+
function answeredHeading(id, alias) {
|
|
23
|
+
return [`✓ came back ${identity(id, alias)}`, SNAPSHOT_RULER].join("\n");
|
|
24
|
+
}
|
|
25
|
+
function contractFacts(contract) {
|
|
26
|
+
return contract.kind === "failed" ? [`! contract failed ${safeText(contract.diagnostic)}`] : [];
|
|
27
|
+
}
|
|
28
|
+
function unobservedText(id, diagnostic) {
|
|
29
|
+
return `! ${id} unobserved: ${safeText(diagnostic)}`;
|
|
30
|
+
}
|
|
31
|
+
function lifeLabel(life) {
|
|
32
|
+
if (life === "running")
|
|
33
|
+
return "● STILL RUNNING";
|
|
34
|
+
if (life === "asleep")
|
|
35
|
+
return "✓ came back";
|
|
36
|
+
if (life === "killed")
|
|
37
|
+
return "× killed";
|
|
38
|
+
return `? ${life}`;
|
|
39
|
+
}
|
|
40
|
+
function pendingWithoutLiveBody(status) {
|
|
41
|
+
if (status.life === "running")
|
|
42
|
+
return 0;
|
|
43
|
+
return status.timeline.entries.reduce((count, entry) => count + (entry.kind === "row" && entry.row.kind === "tell" && entry.row.state === "pending" ? 1 : 0), 0);
|
|
44
|
+
}
|
|
45
|
+
function clock(at) {
|
|
46
|
+
const date = new Date(at);
|
|
47
|
+
return Number.isFinite(date.getTime())
|
|
48
|
+
? `${String(date.getHours()).padStart(2, "0")}:${String(date.getMinutes()).padStart(2, "0")}`
|
|
49
|
+
: "unknown";
|
|
50
|
+
}
|
|
51
|
+
function label(row) {
|
|
52
|
+
if (row.kind === "said")
|
|
53
|
+
return "say";
|
|
54
|
+
if (row.kind === "thought")
|
|
55
|
+
return "think";
|
|
56
|
+
if (row.kind === "note")
|
|
57
|
+
return "note";
|
|
58
|
+
if (row.kind === "call")
|
|
59
|
+
return "call";
|
|
60
|
+
if (row.kind === "tell")
|
|
61
|
+
return "tell";
|
|
62
|
+
if (row.kind === "outcome")
|
|
63
|
+
return row.outcome.kind === "answered" ? "say" : "error";
|
|
64
|
+
if (row.kind === "turn")
|
|
65
|
+
return "call";
|
|
66
|
+
return toolRepr(row).label;
|
|
67
|
+
}
|
|
68
|
+
function mark(row) {
|
|
69
|
+
if (row.kind === "outcome")
|
|
70
|
+
return row.outcome.kind === "answered" ? "✓" : "!";
|
|
71
|
+
if (row.kind === "tell" && row.state === "pending")
|
|
72
|
+
return "⧗";
|
|
73
|
+
if (row.kind === "tool") {
|
|
74
|
+
if (row.state === "active")
|
|
75
|
+
return "⧖";
|
|
76
|
+
if (row.state === "unsettled")
|
|
77
|
+
return "?";
|
|
78
|
+
return row.state.status === "ok" ? "✓" : "!";
|
|
79
|
+
}
|
|
80
|
+
return "│";
|
|
81
|
+
}
|
|
82
|
+
function rowText(row) {
|
|
83
|
+
if (row.kind === "said" ||
|
|
84
|
+
row.kind === "thought" ||
|
|
85
|
+
row.kind === "note" ||
|
|
86
|
+
row.kind === "call" ||
|
|
87
|
+
row.kind === "tell") {
|
|
88
|
+
return {
|
|
89
|
+
text: row.text,
|
|
90
|
+
lines: row.kind === "said" || row.kind === "thought" ? 2 : row.kind === "tell" || row.kind === "call" ? 1 : 2,
|
|
91
|
+
};
|
|
92
|
+
}
|
|
93
|
+
if (row.kind === "outcome")
|
|
94
|
+
return row.outcome.kind === "answered"
|
|
95
|
+
? { text: row.outcome.answer, lines: 3 }
|
|
96
|
+
: { text: row.outcome.diagnostic, lines: 2 };
|
|
97
|
+
if (row.kind === "turn")
|
|
98
|
+
return { text: "", lines: 1 };
|
|
99
|
+
const repr = toolRepr(row);
|
|
100
|
+
return {
|
|
101
|
+
text: repr.text,
|
|
102
|
+
lines: 2,
|
|
103
|
+
...(repr.overflow === "middle-ellipsis" ? { middle: true } : {}),
|
|
104
|
+
...(repr.suffix === undefined ? {} : { suffix: repr.suffix }),
|
|
105
|
+
};
|
|
106
|
+
}
|
|
107
|
+
function eventPrefix(glyph, verb, time) {
|
|
108
|
+
const gutter = time === undefined ? " ".repeat(TIME_WIDTH) : time.padEnd(TIME_WIDTH);
|
|
109
|
+
return `${gutter} ${glyph} ${verb.padEnd(VERB_WIDTH)} `;
|
|
110
|
+
}
|
|
111
|
+
function continuationPrefix() {
|
|
112
|
+
return eventPrefix("│", "");
|
|
113
|
+
}
|
|
114
|
+
function quotedBody(row) {
|
|
115
|
+
return (row.kind === "said" ||
|
|
116
|
+
row.kind === "thought" ||
|
|
117
|
+
row.kind === "tell" ||
|
|
118
|
+
(row.kind === "outcome" && row.outcome.kind === "answered"));
|
|
119
|
+
}
|
|
120
|
+
function quoteLines(lines, prefix) {
|
|
121
|
+
const prefixWidth = prefix.length;
|
|
122
|
+
return lines.map((line) => {
|
|
123
|
+
const body = line.slice(prefixWidth);
|
|
124
|
+
if (body.length === 0)
|
|
125
|
+
return line;
|
|
126
|
+
return `${line.slice(0, prefixWidth)}“${body}”`;
|
|
127
|
+
});
|
|
128
|
+
}
|
|
129
|
+
function renderMiddleEllipsis(first, text, suffix, columns) {
|
|
130
|
+
const prefixWidth = displayColumns(first);
|
|
131
|
+
const remaining = columns - prefixWidth;
|
|
132
|
+
const suffixWidth = displayColumns(suffix);
|
|
133
|
+
const withSuffix = remaining - suffixWidth;
|
|
134
|
+
// `$ ` + one head char + ellipsis + tail; cue and ellipsis alone are not a subject.
|
|
135
|
+
const showSuffix = suffix.length > 0 && withSuffix >= 6;
|
|
136
|
+
return `${first}${truncateMiddleDisplayText(text, Math.max(0, showSuffix ? withSuffix : remaining))}${showSuffix ? suffix : ""}`;
|
|
137
|
+
}
|
|
138
|
+
function renderRow(row, context, history, first) {
|
|
139
|
+
const value = rowText(row);
|
|
140
|
+
const quoted = quotedBody(row);
|
|
141
|
+
const quoteWidth = quoted ? 2 : 0;
|
|
142
|
+
if (value.middle === true) {
|
|
143
|
+
return [renderMiddleEllipsis(first, value.text, value.suffix ?? "", context.columns - quoteWidth)];
|
|
144
|
+
}
|
|
145
|
+
const lines = renderBoundedTextBlock(value.text, {
|
|
146
|
+
first,
|
|
147
|
+
continuation: continuationPrefix(),
|
|
148
|
+
columns: context.columns - quoteWidth,
|
|
149
|
+
lines: history ? Number.MAX_SAFE_INTEGER : value.lines,
|
|
150
|
+
...("truncated" in row && row.truncated === true ? { truncated: true } : {}),
|
|
151
|
+
});
|
|
152
|
+
return quoted ? quoteLines(lines, first) : lines;
|
|
153
|
+
}
|
|
154
|
+
function groupedEntries(entries, context, history = false) {
|
|
155
|
+
const lines = [];
|
|
156
|
+
let previousClock;
|
|
157
|
+
for (const entry of entries) {
|
|
158
|
+
if (entry.kind === "gap") {
|
|
159
|
+
lines.push(`${" ".repeat(TIME_WIDTH)} ⋮ ${entry.count} omitted`);
|
|
160
|
+
continue;
|
|
161
|
+
}
|
|
162
|
+
const row = entry.row;
|
|
163
|
+
const at = clock(row.at);
|
|
164
|
+
const changed = previousClock === undefined || at !== previousClock;
|
|
165
|
+
lines.push(...renderRow(row, context, history, eventPrefix(mark(row), label(row), changed ? at : undefined)));
|
|
166
|
+
previousClock = at;
|
|
167
|
+
}
|
|
168
|
+
return lines;
|
|
169
|
+
}
|
|
170
|
+
function groupedRows(rows, context, history = false) {
|
|
171
|
+
return groupedEntries(rows.filter((row) => row.kind !== "turn").map((row) => ({ kind: "row", row })), context, history);
|
|
172
|
+
}
|
|
173
|
+
function taskDispositionMark(disposition) {
|
|
174
|
+
if (disposition === "done")
|
|
175
|
+
return "✓";
|
|
176
|
+
if (disposition === "drop")
|
|
177
|
+
return "×";
|
|
178
|
+
if (disposition === "on_hold")
|
|
179
|
+
return "⧗";
|
|
180
|
+
if (disposition === "in_progress")
|
|
181
|
+
return "●";
|
|
182
|
+
return disposition === "blocked" ? "‖" : "○";
|
|
183
|
+
}
|
|
184
|
+
function changeStat(group) {
|
|
185
|
+
let added = 0;
|
|
186
|
+
let removed = 0;
|
|
187
|
+
for (const change of group) {
|
|
188
|
+
if (change.diffstat === undefined)
|
|
189
|
+
return "+? -?";
|
|
190
|
+
added += change.diffstat.added;
|
|
191
|
+
removed += change.diffstat.removed;
|
|
192
|
+
}
|
|
193
|
+
return `+${added} -${removed}`;
|
|
194
|
+
}
|
|
195
|
+
function groupedChangeRows(changes) {
|
|
196
|
+
const groups = [];
|
|
197
|
+
const seen = new Map();
|
|
198
|
+
for (const change of changes) {
|
|
199
|
+
const index = seen.get(change.path);
|
|
200
|
+
if (index === undefined) {
|
|
201
|
+
seen.set(change.path, groups.length);
|
|
202
|
+
groups.push([change]);
|
|
203
|
+
}
|
|
204
|
+
else
|
|
205
|
+
groups[index].push(change);
|
|
206
|
+
}
|
|
207
|
+
return groups.map((group) => ({ path: group[0].path, stat: changeStat(group) }));
|
|
208
|
+
}
|
|
209
|
+
function renderReportedChangeLines(snapshot) {
|
|
210
|
+
const rows = groupedChangeRows(snapshot.reportedChanges);
|
|
211
|
+
const width = rows.reduce((max, row) => Math.max(max, row.stat.length), 0);
|
|
212
|
+
return [
|
|
213
|
+
`changes ${snapshot.reportedChanges.length + snapshot.reportedChangesOmitted}`,
|
|
214
|
+
...rows.map((row) => ` ${row.stat.padEnd(width)} ${safeText(row.path)}`),
|
|
215
|
+
...(snapshot.reportedChangesOmitted > 0 ? [` ⋮ ${snapshot.reportedChangesOmitted} earlier changes`] : []),
|
|
216
|
+
];
|
|
217
|
+
}
|
|
218
|
+
function renderTaskRow(row, columns) {
|
|
219
|
+
const prefix = ` ${taskDispositionMark(row.disposition)} ${row.id} · `;
|
|
220
|
+
const body = `${safeText(row.title)} · ${row.disposition} · P${row.priority}`;
|
|
221
|
+
const inline = `${prefix}${body}`;
|
|
222
|
+
if (displayColumns(inline) <= columns)
|
|
223
|
+
return [inline];
|
|
224
|
+
return renderBoundedTextBlock(body, {
|
|
225
|
+
first: prefix,
|
|
226
|
+
continuation: " ",
|
|
227
|
+
columns,
|
|
228
|
+
lines: Number.MAX_SAFE_INTEGER,
|
|
229
|
+
});
|
|
230
|
+
}
|
|
231
|
+
function renderTaskContextLines(created, columns) {
|
|
232
|
+
if (created === undefined)
|
|
233
|
+
return [];
|
|
234
|
+
if (created.kind === "failed")
|
|
235
|
+
return [`! tasks failed ${safeText(created.diagnostic)}`];
|
|
236
|
+
return [`tasks ${created.rows.length}`, ...created.rows.flatMap((row) => renderTaskRow(row, columns))];
|
|
237
|
+
}
|
|
238
|
+
function answerContextLines(observation, columns) {
|
|
239
|
+
return [
|
|
240
|
+
...renderTaskContextLines(observation.createdTasks, columns),
|
|
241
|
+
...renderReportedChangeLines(observation.status.timeline),
|
|
242
|
+
];
|
|
243
|
+
}
|
|
244
|
+
function answeredBlock(observation, answer, alias, columns) {
|
|
245
|
+
const context = answerContextLines(observation, columns).join("\n");
|
|
246
|
+
if (context.length === 0)
|
|
247
|
+
return `${answeredHeading(observation.status.id, alias)}\n${answer}`;
|
|
248
|
+
const separator = answer.endsWith("\n") ? "\n" : "\n\n";
|
|
249
|
+
return `${answeredHeading(observation.status.id, alias)}\n${answer}${separator}${context}`;
|
|
250
|
+
}
|
|
251
|
+
function snapshotCore(view, context, options) {
|
|
252
|
+
const snapshot = view.status.timeline;
|
|
253
|
+
const activity = snapshot.kind === "idle" && snapshot.outcome !== undefined
|
|
254
|
+
? groupedRows([
|
|
255
|
+
...snapshot.entries.filter((entry) => entry.kind === "row").map((entry) => entry.row),
|
|
256
|
+
snapshot.outcome,
|
|
257
|
+
].sort((left, right) => left.sequence - right.sequence), context)
|
|
258
|
+
: groupedEntries(snapshot.entries, context);
|
|
259
|
+
const facts = [
|
|
260
|
+
...(view.status.readonly?.enforcement === "none" ? [`! ${safeText(view.status.readonly.diagnostic)}`] : []),
|
|
261
|
+
...contractFacts(view.contract),
|
|
262
|
+
...(options.facts ?? []),
|
|
263
|
+
];
|
|
264
|
+
const pending = pendingWithoutLiveBody(view.status);
|
|
265
|
+
return {
|
|
266
|
+
activity,
|
|
267
|
+
lines: [
|
|
268
|
+
...snapshotHeading(view.status.id, options.alias, view.contract),
|
|
269
|
+
...facts,
|
|
270
|
+
...activity,
|
|
271
|
+
...(pending === 0 ? [] : [`pending ${pending} tells · no live body`]),
|
|
272
|
+
],
|
|
273
|
+
};
|
|
274
|
+
}
|
|
275
|
+
export function snapshotText(view, context, options = {}) {
|
|
276
|
+
const core = snapshotCore(view, context, options);
|
|
277
|
+
const taskContext = renderTaskContextLines(view.createdTasks, context.columns);
|
|
278
|
+
return [
|
|
279
|
+
...core.lines,
|
|
280
|
+
...(core.activity.length > 0 && taskContext.length > 0 ? [""] : []),
|
|
281
|
+
...taskContext,
|
|
282
|
+
...renderReportedChangeLines(view.status.timeline),
|
|
283
|
+
"",
|
|
284
|
+
lifeLabel(view.status.life),
|
|
285
|
+
].join("\n");
|
|
286
|
+
}
|
|
287
|
+
function mutationSnapshotText(view, context, options = {}) {
|
|
288
|
+
const core = snapshotCore(view, context, options);
|
|
289
|
+
return [...core.lines, ...(options.showLife === false ? [] : ["", lifeLabel(view.status.life)])].join("\n");
|
|
290
|
+
}
|
|
291
|
+
export function mutationObservationStageText(id, observation, context, options = {}) {
|
|
292
|
+
if (observation.kind === "unobserved")
|
|
293
|
+
return unobservedText(id, observation.diagnostic);
|
|
294
|
+
return mutationSnapshotText(observation, context, options);
|
|
295
|
+
}
|
|
296
|
+
export function statusAnswer(view) {
|
|
297
|
+
if (!defaultWaitComplete(view.status))
|
|
298
|
+
return undefined;
|
|
299
|
+
if (view.status.life !== "asleep")
|
|
300
|
+
return undefined;
|
|
301
|
+
if (view.status.readonly?.enforcement === "none")
|
|
302
|
+
return undefined;
|
|
303
|
+
const timeline = view.status.timeline;
|
|
304
|
+
if (timeline.kind !== "idle" || timeline.outcome?.outcome.kind !== "answered")
|
|
305
|
+
return undefined;
|
|
306
|
+
return timeline.outcome.outcome.answer;
|
|
307
|
+
}
|
|
308
|
+
function answerCallFailed(result) {
|
|
309
|
+
return result.dispatch.kind === "failed" || result.alias.kind === "failed" || result.readonly?.enforcement === "none";
|
|
310
|
+
}
|
|
311
|
+
export function akumaRawAnswer(result) {
|
|
312
|
+
if (result.action === "call") {
|
|
313
|
+
if (answerCallFailed(result.result) || result.result.observation.kind !== "observed")
|
|
314
|
+
return undefined;
|
|
315
|
+
return statusAnswer({ status: result.result.observation.status });
|
|
316
|
+
}
|
|
317
|
+
if (result.action === "wait" && result.result.observations.length === 1)
|
|
318
|
+
return statusAnswer(result.result.observations[0]);
|
|
319
|
+
return undefined;
|
|
320
|
+
}
|
|
321
|
+
export function waitText(result, context) {
|
|
322
|
+
const alias = result.alias;
|
|
323
|
+
const total = result.result.observations.length + result.result.unobserved.length;
|
|
324
|
+
const done = result.result.observations.filter((observation) => defaultWaitComplete(observation.status)).length;
|
|
325
|
+
const blocks = [
|
|
326
|
+
...result.result.observations.map((observation) => {
|
|
327
|
+
const answer = statusAnswer(observation);
|
|
328
|
+
if (answer !== undefined)
|
|
329
|
+
return answeredBlock(observation, answer, alias, context.columns);
|
|
330
|
+
return snapshotText(observation, context, { ...(alias === undefined ? {} : { alias }) });
|
|
331
|
+
}),
|
|
332
|
+
...result.result.unobserved.map((member) => unobservedText(member.id, member.diagnostic)),
|
|
333
|
+
];
|
|
334
|
+
if (total <= 1)
|
|
335
|
+
return blocks.join("\n\n");
|
|
336
|
+
return [...blocks, `${done} of ${total} done`].join("\n\n");
|
|
337
|
+
}
|
|
338
|
+
export function historyText(command, result, context) {
|
|
339
|
+
if (command.last)
|
|
340
|
+
return result.mode === "last" ? result.answer : "no answer retained";
|
|
341
|
+
if (result.mode !== "page")
|
|
342
|
+
throw new Error("history result lacks page");
|
|
343
|
+
return [
|
|
344
|
+
...snapshotHeading(result.akuma, result.alias, result.historyResult.contract),
|
|
345
|
+
...groupedRows(result.history.rows, context, true),
|
|
346
|
+
].join("\n");
|
|
347
|
+
}
|
|
348
|
+
export function tellText(result, context) {
|
|
349
|
+
const wake = result.result.tell.wake;
|
|
350
|
+
const facts = wake.kind === "failed"
|
|
351
|
+
? [
|
|
352
|
+
`! wake failed · ${safeText(wake.diagnostic)}${wake.child === undefined ? "" : ` · log ${wake.child.log.path} ${wake.child.log.from}..${wake.child.log.to}`}`,
|
|
353
|
+
]
|
|
354
|
+
: wake.kind === "pursuing"
|
|
355
|
+
? []
|
|
356
|
+
: [`wake ${wake.kind}`];
|
|
357
|
+
return mutationObservationStageText(result.result.akuma, result.result.observation, context, {
|
|
358
|
+
...(result.alias === undefined ? {} : { alias: result.alias }),
|
|
359
|
+
facts,
|
|
360
|
+
showLife: false,
|
|
361
|
+
});
|
|
362
|
+
}
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import type { AkumaInvocationResult } from "../commands/akuma-invoke.js";
|
|
2
2
|
import type { ParsedCommand } from "../parse.js";
|
|
3
3
|
import { type TextRenderContext } from "./terminal.js";
|
|
4
|
-
export
|
|
4
|
+
export { akumaRawAnswer } from "./akuma-activity.js";
|
|
5
5
|
export declare function renderAkumaText(command: ParsedCommand, result: AkumaInvocationResult, context?: TextRenderContext): string;
|
|
6
6
|
export declare function akumaExitCode(result: AkumaInvocationResult): number;
|
|
7
7
|
export declare function akumaJsonValue(result: AkumaInvocationResult): unknown;
|