@astrosheep/keiyaku 4.5.22 → 4.5.24
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/skills/keiyaku-akuma/SKILL.md +18 -0
- package/build/integrations/marketplace/plugins/keiyaku/skills/keiyaku-akuma/references/automation.md +33 -14
- package/build/src/akuma/akuma-handle.js +5 -2
- package/build/src/akuma/akuma-instance.d.ts +15 -3
- package/build/src/akuma/akuma-instance.js +27 -13
- package/build/src/akuma/akuma.d.ts +1 -0
- package/build/src/akuma/akuma.js +1 -0
- package/build/src/akuma/fleet-execution.d.ts +2 -0
- package/build/src/akuma/fleet-execution.js +22 -5
- package/build/src/akuma/heart/index.d.ts +3 -0
- package/build/src/akuma/heart/index.js +12 -2
- package/build/src/akuma/index.d.ts +2 -2
- package/build/src/akuma/projection-read.d.ts +2 -0
- package/build/src/akuma/projection-read.js +4 -0
- package/build/src/akuma/projection.d.ts +1 -1
- package/build/src/akuma/projection.js +1 -1
- package/build/src/akuma/schema.d.ts +40 -0
- package/build/src/akuma/schema.js +114 -0
- package/build/src/cli/commands/akuma-invoke.d.ts +5 -0
- package/build/src/cli/commands/akuma-invoke.js +19 -3
- package/build/src/cli/render/akuma-activity.d.ts +6 -3
- package/build/src/cli/render/akuma-activity.js +168 -51
- package/build/src/cli/runtime.js +2 -2
- package/build/src/index.d.ts +2 -1
- package/build/src/index.js +1 -0
- package/build/src/kanshi/read.js +32 -6
- package/build/src/library/address.d.ts +3 -0
- package/build/src/library/address.js +2 -1
- package/build/src/library/fleet.js +54 -11
- package/build/src/protocol/read/status.d.ts +5 -0
- package/build/src/protocol/read/status.js +11 -0
- package/package.json +1 -1
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import { type AkuId } from "../../akuma/identity.js";
|
|
2
2
|
import { type ActivityHistory } from "../../akuma/akuma.js";
|
|
3
3
|
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";
|
|
4
|
+
import { type WaitSelectedIdentity } from "../render/akuma-activity.js";
|
|
4
5
|
import type { Settings } from "../../settings.js";
|
|
5
6
|
import type { WorldRoot } from "../../world.js";
|
|
6
7
|
import type { InvokedAkumaCommand } from "./akuma.js";
|
|
@@ -23,6 +24,10 @@ export type AkumaInvocationResult = Readonly<{
|
|
|
23
24
|
result: AkumaWaitResult;
|
|
24
25
|
alias?: string;
|
|
25
26
|
streamed?: boolean;
|
|
27
|
+
/** The moment this invocation began waiting, so a non-streamed scoreboard asserts real waiting. */
|
|
28
|
+
startedAt?: number;
|
|
29
|
+
/** The frozen selected set, so a non-streamed render names each target as the caller selected it. */
|
|
30
|
+
selection?: readonly WaitSelectedIdentity[];
|
|
26
31
|
}> | Readonly<{
|
|
27
32
|
kind: "akuma";
|
|
28
33
|
action: "tell";
|
|
@@ -82,9 +82,12 @@ async function promptBody(command, input) {
|
|
|
82
82
|
* baseline, so an already settled Akuma prints no backlog rows. When the wait
|
|
83
83
|
* ends, `conclude` prints its closing scoreboard on the same channel.
|
|
84
84
|
*/
|
|
85
|
-
function waitObserver(stream) {
|
|
85
|
+
function waitObserver(stream, onSelected) {
|
|
86
86
|
return {
|
|
87
|
-
selected: (selected) =>
|
|
87
|
+
selected: (selected) => {
|
|
88
|
+
onSelected?.(selected);
|
|
89
|
+
stream.select(selected);
|
|
90
|
+
},
|
|
88
91
|
observe: (observed) => {
|
|
89
92
|
const lines = stream.observe(observed);
|
|
90
93
|
if (lines.length > 0)
|
|
@@ -95,13 +98,24 @@ function waitObserver(stream) {
|
|
|
95
98
|
async function invokeWait(command, input) {
|
|
96
99
|
const alias = command.akuma.length === 1 ? inputAlias(command.akuma[0]) : undefined;
|
|
97
100
|
const stream = command.output === "text" ? waitObservationStream(resultContext()) : undefined;
|
|
101
|
+
const startedAt = Date.now();
|
|
102
|
+
let selection;
|
|
103
|
+
const observer = stream === undefined
|
|
104
|
+
? undefined
|
|
105
|
+
: waitObserver(stream, (selected) => {
|
|
106
|
+
selection = selected;
|
|
107
|
+
});
|
|
98
108
|
const result = await waitAkuma({
|
|
99
109
|
path: input.path,
|
|
100
110
|
akuma: command.akuma,
|
|
101
111
|
...(input.repo === undefined ? {} : { repo: input.repo }),
|
|
102
112
|
...(command.completion === undefined ? {} : { completion: command.completion }),
|
|
103
113
|
...(command.timeoutMs === undefined ? {} : { timeoutMs: command.timeoutMs }),
|
|
104
|
-
}, input.execution ?? localExecutionContext(),
|
|
114
|
+
}, input.execution ?? localExecutionContext(), observer);
|
|
115
|
+
const frozen = {
|
|
116
|
+
startedAt,
|
|
117
|
+
...(selection === undefined ? {} : { selection }),
|
|
118
|
+
};
|
|
105
119
|
if (stream !== undefined && stream.streamed()) {
|
|
106
120
|
const closing = stream.conclude(result);
|
|
107
121
|
if (closing.length > 0)
|
|
@@ -112,6 +126,7 @@ async function invokeWait(command, input) {
|
|
|
112
126
|
result,
|
|
113
127
|
streamed: true,
|
|
114
128
|
...(alias === undefined ? {} : { alias }),
|
|
129
|
+
...frozen,
|
|
115
130
|
};
|
|
116
131
|
}
|
|
117
132
|
return {
|
|
@@ -119,6 +134,7 @@ async function invokeWait(command, input) {
|
|
|
119
134
|
action: "wait",
|
|
120
135
|
result,
|
|
121
136
|
...(alias === undefined ? {} : { alias }),
|
|
137
|
+
...frozen,
|
|
122
138
|
};
|
|
123
139
|
}
|
|
124
140
|
async function inputInitiator(input) {
|
|
@@ -60,6 +60,7 @@ export type WaitConclusionResult = Readonly<{
|
|
|
60
60
|
export type WaitSelectedIdentity = Readonly<{
|
|
61
61
|
id: string;
|
|
62
62
|
alias?: string;
|
|
63
|
+
contract?: DispatchAssociation;
|
|
63
64
|
}>;
|
|
64
65
|
export type WaitObservationStream = Readonly<{
|
|
65
66
|
/** Freeze the selected set's identities so the source column is stable before the first row. */
|
|
@@ -72,9 +73,11 @@ export type WaitObservationStream = Readonly<{
|
|
|
72
73
|
}>;
|
|
73
74
|
/**
|
|
74
75
|
* Live view over a wait's successive observation rounds, one append-only
|
|
75
|
-
* stream per selected Akuma.
|
|
76
|
-
*
|
|
77
|
-
*
|
|
76
|
+
* stream per selected Akuma. A plural wait opens one aggregate frame naming
|
|
77
|
+
* its selected set before any activity row and never opens a per-target frame
|
|
78
|
+
* during observation; a single-target wait opens that Akuma's own identity
|
|
79
|
+
* frame — the identity and Contract association the observed facts carry —
|
|
80
|
+
* and an already settled Akuma prints that frame while replaying no backlog
|
|
78
81
|
* rows. `conclude` renders the closing rows once the wait ends: one row per
|
|
79
82
|
* observed Akuma in `<clock> <mark> <verb> — <duration>` grammar, with a target
|
|
80
83
|
* named for a multi-target scoreboard and no activity replay.
|
|
@@ -30,7 +30,7 @@ export function snapshotHeading(id, alias, contract) {
|
|
|
30
30
|
return [...head, frameRule(head)];
|
|
31
31
|
}
|
|
32
32
|
function answeredHeading(id, alias) {
|
|
33
|
-
const heading = `✓
|
|
33
|
+
const heading = `✓ answered ${identity(id, alias)}`;
|
|
34
34
|
return [heading, frameRule([heading])];
|
|
35
35
|
}
|
|
36
36
|
function contractFacts(contract) {
|
|
@@ -436,18 +436,18 @@ export function activityStream(context, layout = plainLayout()) {
|
|
|
436
436
|
}
|
|
437
437
|
function conclusionMarkVerb(status, answered) {
|
|
438
438
|
if (status.life === "running")
|
|
439
|
-
return { mark: "●", verb: "still running"
|
|
439
|
+
return { mark: "●", verb: "still running" };
|
|
440
440
|
if (answered)
|
|
441
|
-
return { mark: "✓", verb: "answered"
|
|
441
|
+
return { mark: "✓", verb: "answered" };
|
|
442
442
|
if (status.life === "asleep")
|
|
443
|
-
return { mark: "!", verb: "failed"
|
|
443
|
+
return { mark: "!", verb: "failed" };
|
|
444
444
|
if (status.life === "killed")
|
|
445
|
-
return { mark: "×", verb: "killed"
|
|
445
|
+
return { mark: "×", verb: "killed" };
|
|
446
446
|
if (status.life === "hung")
|
|
447
|
-
return { mark: "?", verb: "hung"
|
|
447
|
+
return { mark: "?", verb: "hung" };
|
|
448
448
|
if (status.life === "untidy")
|
|
449
|
-
return { mark: "!", verb: "untidy"
|
|
450
|
-
return { mark: "!", verb: "stranded"
|
|
449
|
+
return { mark: "!", verb: "untidy" };
|
|
450
|
+
return { mark: "!", verb: "stranded" };
|
|
451
451
|
}
|
|
452
452
|
/**
|
|
453
453
|
* The durable moment an Akuma settled, read from the outcome row its own timeline
|
|
@@ -473,51 +473,105 @@ function durationText(durationMs) {
|
|
|
473
473
|
function createWaitObservationState() {
|
|
474
474
|
return {
|
|
475
475
|
streams: new Map(),
|
|
476
|
-
attributed: new Map(),
|
|
477
476
|
sources: new Map(),
|
|
477
|
+
contracts: new Map(),
|
|
478
478
|
settledAt: new Map(),
|
|
479
479
|
sourceWidth: 0,
|
|
480
|
-
|
|
480
|
+
plural: undefined,
|
|
481
|
+
headerEmitted: false,
|
|
481
482
|
observed: false,
|
|
482
483
|
};
|
|
483
484
|
}
|
|
484
|
-
|
|
485
|
+
/** Register one selected or newly observed source; the set freezes the column before the first row. */
|
|
486
|
+
function registerWaitSource(state, id, alias, contract) {
|
|
485
487
|
if (state.sources.has(id))
|
|
486
488
|
return;
|
|
487
489
|
const label = alias ?? id;
|
|
488
490
|
state.sources.set(id, label);
|
|
489
|
-
|
|
491
|
+
if (contract !== undefined)
|
|
492
|
+
state.contracts.set(id, contract);
|
|
493
|
+
if (!state.headerEmitted)
|
|
494
|
+
state.sourceWidth = Math.max(state.sourceWidth, displayColumns(label));
|
|
495
|
+
}
|
|
496
|
+
/**
|
|
497
|
+
* The one aggregate head a plural wait prints before any activity row: the
|
|
498
|
+
* selected set read as a list, each target on its own line named by the alias
|
|
499
|
+
* addressing it, otherwise its complete identity, with its `· kei/<contract>`
|
|
500
|
+
* association inline when one exists, all closed by the frame's single rule.
|
|
501
|
+
*/
|
|
502
|
+
function aggregateHeading(state) {
|
|
503
|
+
const head = [...state.sources].map(([id, label]) => {
|
|
504
|
+
const contractId = associatedContractId(state.contracts.get(id) ?? { kind: "none" });
|
|
505
|
+
return contractId === undefined ? label : `${label} · ${contractId}`;
|
|
506
|
+
});
|
|
507
|
+
return [...head, frameRule(head)];
|
|
490
508
|
}
|
|
491
509
|
function observeWaitRound(state, round, context, now) {
|
|
492
510
|
state.observed = true;
|
|
493
511
|
// Establish the whole round's sources before any row so widths stay aligned within it.
|
|
494
512
|
for (const member of round)
|
|
495
|
-
registerWaitSource(state, member.status.id, member.alias);
|
|
513
|
+
registerWaitSource(state, member.status.id, member.alias, member.contract);
|
|
496
514
|
const lines = [];
|
|
497
|
-
|
|
515
|
+
// The observation subject opens once: an aggregate head for a plural set, the observed
|
|
516
|
+
// identity frame for a single target. Every later round only appends attributed rows.
|
|
517
|
+
if (!state.headerEmitted && round.length > 0) {
|
|
518
|
+
state.headerEmitted = true;
|
|
519
|
+
state.plural ??= state.sources.size > 1;
|
|
520
|
+
if (state.plural) {
|
|
521
|
+
lines.push(...aggregateHeading(state));
|
|
522
|
+
}
|
|
523
|
+
else {
|
|
524
|
+
const sole = round[0];
|
|
525
|
+
lines.push(...snapshotHeading(sole.status.id, sole.alias, sole.contract));
|
|
526
|
+
}
|
|
527
|
+
}
|
|
528
|
+
for (const { status } of round) {
|
|
498
529
|
const known = state.streams.get(status.id);
|
|
499
|
-
if (known
|
|
500
|
-
|
|
501
|
-
|
|
502
|
-
|
|
503
|
-
state.opened = true;
|
|
504
|
-
state.attributed.set(status.id, alias);
|
|
530
|
+
if (known !== undefined) {
|
|
531
|
+
lines.push(...known(status.timeline));
|
|
532
|
+
}
|
|
533
|
+
else {
|
|
505
534
|
// Only a plural wait attributes its rows; a single-target stream keeps the plain row grammar.
|
|
506
|
-
const stream = activityStream(context, state.
|
|
535
|
+
const stream = activityStream(context, state.plural === true
|
|
507
536
|
? sourceLayout(state.sources.get(status.id) ?? status.id, () => state.sourceWidth)
|
|
508
537
|
: plainLayout());
|
|
509
538
|
state.streams.set(status.id, stream);
|
|
510
539
|
// A wait starts at the current settled frontier: its backlog is neither evidence nor budget.
|
|
511
540
|
lines.push(...stream.seed(status.timeline));
|
|
512
541
|
}
|
|
513
|
-
else {
|
|
514
|
-
lines.push(...known(status.timeline));
|
|
515
|
-
}
|
|
516
542
|
if (!state.settledAt.has(status.id) && waitComplete(status))
|
|
517
543
|
state.settledAt.set(status.id, settleMoment(status) ?? now());
|
|
518
544
|
}
|
|
519
545
|
return lines;
|
|
520
546
|
}
|
|
547
|
+
/**
|
|
548
|
+
* A conclusion clause asserts real waiting: a target that settled at or after
|
|
549
|
+
* this wait began keeps its duration, a target already settled names no
|
|
550
|
+
* duration, and an unfinished target carries the elapsed wait.
|
|
551
|
+
*/
|
|
552
|
+
function conclusionClause(at, startedAt, complete, end) {
|
|
553
|
+
if (!complete)
|
|
554
|
+
return ` — waited ${durationText(Math.max(0, end - startedAt))}`;
|
|
555
|
+
return at >= startedAt ? ` — ${durationText(Math.max(0, at - startedAt))}` : "";
|
|
556
|
+
}
|
|
557
|
+
/** Present ids in frozen selection order, then any result member the selection never named. */
|
|
558
|
+
function orderedWaitIds(state, result) {
|
|
559
|
+
const ids = [...state.sources.keys()];
|
|
560
|
+
const known = new Set(ids);
|
|
561
|
+
for (const observation of result.observations) {
|
|
562
|
+
if (!known.has(observation.status.id)) {
|
|
563
|
+
known.add(observation.status.id);
|
|
564
|
+
ids.push(observation.status.id);
|
|
565
|
+
}
|
|
566
|
+
}
|
|
567
|
+
for (const member of result.unobserved) {
|
|
568
|
+
if (!known.has(member.id)) {
|
|
569
|
+
known.add(member.id);
|
|
570
|
+
ids.push(member.id);
|
|
571
|
+
}
|
|
572
|
+
}
|
|
573
|
+
return ids;
|
|
574
|
+
}
|
|
521
575
|
function concludeWaitStream(state, result, startedAt, now) {
|
|
522
576
|
const tail = [];
|
|
523
577
|
// The returned observation can be newer than the final callback. Advance every known stream
|
|
@@ -530,19 +584,26 @@ function concludeWaitStream(state, result, startedAt, now) {
|
|
|
530
584
|
for (const stream of state.streams.values())
|
|
531
585
|
tail.push(...stream.flush());
|
|
532
586
|
const end = now();
|
|
533
|
-
const
|
|
534
|
-
const multi =
|
|
535
|
-
const
|
|
587
|
+
const order = orderedWaitIds(state, result);
|
|
588
|
+
const multi = order.length > 1;
|
|
589
|
+
const observationById = new Map(result.observations.map((observation) => [observation.status.id, observation]));
|
|
590
|
+
const unobservedById = new Map(result.unobserved.map((member) => [member.id, member]));
|
|
591
|
+
// A multi-target scoreboard names the frozen source label; a single target keeps the bare diagnostic.
|
|
592
|
+
const label = (id) => (multi ? (state.sources.get(id) ?? id) : id);
|
|
593
|
+
const unobservedLines = order
|
|
594
|
+
.filter((id) => unobservedById.has(id))
|
|
595
|
+
.map((id) => unobservedText(label(id), unobservedById.get(id).diagnostic));
|
|
596
|
+
const conclusions = order.flatMap((id) => {
|
|
597
|
+
const observation = observationById.get(id);
|
|
598
|
+
if (observation === undefined)
|
|
599
|
+
return [];
|
|
536
600
|
const status = observation.status;
|
|
537
|
-
const answered = statusAnswer(observation) !== undefined;
|
|
538
601
|
const complete = waitComplete(status);
|
|
539
|
-
const at = complete ? (state.settledAt.get(
|
|
540
|
-
const
|
|
541
|
-
const
|
|
542
|
-
|
|
543
|
-
return `${clockFromMs(at)}${target} ${mark} ${verb} — ${waited ? "waited " : ""}${durationText(durationMs)}`;
|
|
602
|
+
const at = complete ? (state.settledAt.get(id) ?? end) : end;
|
|
603
|
+
const { mark, verb } = conclusionMarkVerb(status, statusAnswer(observation) !== undefined);
|
|
604
|
+
const target = multi ? ` ${padToDisplay(state.sources.get(id) ?? id, state.sourceWidth)}` : "";
|
|
605
|
+
return [`${clockFromMs(at)}${target} ${mark} ${verb}${conclusionClause(at, startedAt, complete, end)}`];
|
|
544
606
|
});
|
|
545
|
-
const unobservedLines = result.unobserved.map((member) => unobservedText(member.id, member.diagnostic));
|
|
546
607
|
const answeredSingle = !multi && result.observations.length === 1 && statusAnswer(result.observations[0]) !== undefined;
|
|
547
608
|
const blocks = [
|
|
548
609
|
...(unobservedLines.length > 0 ? [unobservedLines.join("\n")] : []),
|
|
@@ -554,9 +615,11 @@ function concludeWaitStream(state, result, startedAt, now) {
|
|
|
554
615
|
}
|
|
555
616
|
/**
|
|
556
617
|
* Live view over a wait's successive observation rounds, one append-only
|
|
557
|
-
* stream per selected Akuma.
|
|
558
|
-
*
|
|
559
|
-
*
|
|
618
|
+
* stream per selected Akuma. A plural wait opens one aggregate frame naming
|
|
619
|
+
* its selected set before any activity row and never opens a per-target frame
|
|
620
|
+
* during observation; a single-target wait opens that Akuma's own identity
|
|
621
|
+
* frame — the identity and Contract association the observed facts carry —
|
|
622
|
+
* and an already settled Akuma prints that frame while replaying no backlog
|
|
560
623
|
* rows. `conclude` renders the closing rows once the wait ends: one row per
|
|
561
624
|
* observed Akuma in `<clock> <mark> <verb> — <duration>` grammar, with a target
|
|
562
625
|
* named for a multi-target scoreboard and no activity replay.
|
|
@@ -567,7 +630,8 @@ export function waitObservationStream(context, options = {}) {
|
|
|
567
630
|
const state = createWaitObservationState();
|
|
568
631
|
const select = (selected) => {
|
|
569
632
|
for (const member of selected)
|
|
570
|
-
registerWaitSource(state, member.id, member.alias);
|
|
633
|
+
registerWaitSource(state, member.id, member.alias, member.contract);
|
|
634
|
+
state.plural ??= selected.length > 1;
|
|
571
635
|
};
|
|
572
636
|
const observe = (round) => observeWaitRound(state, round, context, now);
|
|
573
637
|
const conclude = (result) => concludeWaitStream(state, result, startedAt, now);
|
|
@@ -616,10 +680,10 @@ export function callObservationStream(context, head, options = {}) {
|
|
|
616
680
|
const end = now();
|
|
617
681
|
const status = observation.status;
|
|
618
682
|
const answered = statusAnswer({ status }) !== undefined;
|
|
619
|
-
const
|
|
620
|
-
const
|
|
621
|
-
const { mark, verb
|
|
622
|
-
lines.push(`${clockFromMs(at)} ${mark} ${verb}
|
|
683
|
+
const complete = waitComplete(status);
|
|
684
|
+
const at = complete ? (settleMoment(status) ?? end) : end;
|
|
685
|
+
const { mark, verb } = conclusionMarkVerb(status, answered);
|
|
686
|
+
lines.push(`${clockFromMs(at)} ${mark} ${verb}${conclusionClause(at, startedAt, complete, end)}`);
|
|
623
687
|
const failure = failedOutcomeDiagnostic(status);
|
|
624
688
|
if (failure !== undefined)
|
|
625
689
|
lines.push(`! error ${safeText(failure)}`);
|
|
@@ -783,20 +847,73 @@ export function akumaRawAnswer(result) {
|
|
|
783
847
|
}
|
|
784
848
|
export function waitText(result, context) {
|
|
785
849
|
const alias = result.alias;
|
|
786
|
-
const
|
|
787
|
-
const
|
|
850
|
+
const observations = result.result.observations;
|
|
851
|
+
const unobserved = result.result.unobserved;
|
|
852
|
+
const total = observations.length + unobserved.length;
|
|
853
|
+
if (total <= 1) {
|
|
854
|
+
const single = [
|
|
855
|
+
...observations.map((observation) => {
|
|
856
|
+
const answer = statusAnswer(observation);
|
|
857
|
+
if (answer !== undefined)
|
|
858
|
+
return answeredBlock(observation, answer, alias, context.columns);
|
|
859
|
+
return snapshotText(observation, context, { ...(alias === undefined ? {} : { alias }) });
|
|
860
|
+
}),
|
|
861
|
+
...unobserved.map((member) => unobservedText(member.id, member.diagnostic)),
|
|
862
|
+
];
|
|
863
|
+
return single.join("\n\n");
|
|
864
|
+
}
|
|
865
|
+
const end = Date.now();
|
|
866
|
+
const startedAt = result.startedAt ?? end;
|
|
867
|
+
const order = [];
|
|
868
|
+
const labels = new Map();
|
|
869
|
+
const remember = (id, frozenAlias) => {
|
|
870
|
+
if (labels.has(id))
|
|
871
|
+
return;
|
|
872
|
+
labels.set(id, frozenAlias ?? id);
|
|
873
|
+
order.push(id);
|
|
874
|
+
};
|
|
875
|
+
for (const member of result.selection ?? [])
|
|
876
|
+
remember(member.id, member.alias);
|
|
877
|
+
for (const observation of observations)
|
|
878
|
+
remember(observation.status.id);
|
|
879
|
+
for (const member of unobserved)
|
|
880
|
+
remember(member.id);
|
|
881
|
+
const label = (id) => labels.get(id) ?? id;
|
|
882
|
+
const observationById = new Map(observations.map((observation) => [observation.status.id, observation]));
|
|
883
|
+
const unobservedById = new Map(unobserved.map((member) => [member.id, member]));
|
|
884
|
+
const sourceWidth = order.reduce((width, id) => Math.max(width, displayColumns(label(id))), 0);
|
|
788
885
|
const blocks = [
|
|
789
|
-
...
|
|
886
|
+
...order.flatMap((id) => {
|
|
887
|
+
const observation = observationById.get(id);
|
|
888
|
+
if (observation === undefined)
|
|
889
|
+
return [];
|
|
790
890
|
const answer = statusAnswer(observation);
|
|
791
891
|
if (answer !== undefined)
|
|
792
|
-
return answeredBlock(observation, answer,
|
|
793
|
-
return snapshotText(observation, context
|
|
892
|
+
return [answeredBlock(observation, answer, undefined, context.columns)];
|
|
893
|
+
return [snapshotText(observation, context)];
|
|
794
894
|
}),
|
|
795
|
-
...
|
|
895
|
+
...(unobserved.length > 0
|
|
896
|
+
? [
|
|
897
|
+
order
|
|
898
|
+
.filter((id) => unobservedById.has(id))
|
|
899
|
+
.map((id) => unobservedText(label(id), unobservedById.get(id).diagnostic))
|
|
900
|
+
.join("\n"),
|
|
901
|
+
]
|
|
902
|
+
: []),
|
|
796
903
|
];
|
|
797
|
-
|
|
798
|
-
|
|
799
|
-
|
|
904
|
+
const rows = order.flatMap((id) => {
|
|
905
|
+
const observation = observationById.get(id);
|
|
906
|
+
if (observation === undefined)
|
|
907
|
+
return [];
|
|
908
|
+
const status = observation.status;
|
|
909
|
+
const complete = waitComplete(status);
|
|
910
|
+
const at = complete ? (settleMoment(status) ?? end) : end;
|
|
911
|
+
const { mark, verb } = conclusionMarkVerb(status, statusAnswer(observation) !== undefined);
|
|
912
|
+
return [
|
|
913
|
+
`${clockFromMs(at)} ${padToDisplay(label(id), sourceWidth)} ${mark} ${verb}${conclusionClause(at, startedAt, complete, end)}`,
|
|
914
|
+
];
|
|
915
|
+
});
|
|
916
|
+
return rows.length > 0 ? [...blocks, rows.join("\n")].join("\n\n") : blocks.join("\n\n");
|
|
800
917
|
}
|
|
801
918
|
export function historyText(command, result, context) {
|
|
802
919
|
if (result.mode === "exact") {
|
package/build/src/cli/runtime.js
CHANGED
|
@@ -151,7 +151,7 @@ async function commandFailureText(error, command) {
|
|
|
151
151
|
const diagnostic = error instanceof Error ? error.message : String(error);
|
|
152
152
|
if (command.output === "json" || error instanceof CliUsageError)
|
|
153
153
|
return diagnostic;
|
|
154
|
-
const { KeiyakuRefused } = await import("../
|
|
154
|
+
const { KeiyakuRefused } = await import("../library/refusal.js");
|
|
155
155
|
if (error instanceof KeiyakuRefused) {
|
|
156
156
|
if (error.refusal.kind === "contract-missing") {
|
|
157
157
|
return renderUsageMessage(diagnostic, {
|
|
@@ -177,7 +177,7 @@ export async function runCliCommand(invocation) {
|
|
|
177
177
|
return await writeResult(command, result);
|
|
178
178
|
}
|
|
179
179
|
catch (error) {
|
|
180
|
-
const { executionReceipt } = await import("../
|
|
180
|
+
const { executionReceipt } = await import("../library/execution-result.js");
|
|
181
181
|
const receipt = executionReceipt(error);
|
|
182
182
|
if (receipt !== undefined) {
|
|
183
183
|
const diagnostic = error instanceof Error ? error.message : String(error);
|
package/build/src/index.d.ts
CHANGED
|
@@ -1,7 +1,8 @@
|
|
|
1
1
|
export * from "./library/keiyaku.js";
|
|
2
2
|
export { Akuma, Schema, ALLOWED_ACTIONS, AkumaBusyError, AkumaDecodeError, AkumaNotBornError, AkumaProviderError, } from "./akuma/index.js";
|
|
3
|
-
export type { AkumaBirthInput, AkumaHistoryOptions, AkumaIdleOptions, AkumaSignalOptions, AkumaTellOptions, InterruptReceipt, KillEvidence, JsonSchema, JsonSchemaDocument, AkuId, AkumaStatus, ActivityHistory, ActivityRow, AllowedAction, AllowedActions, } from "./akuma/index.js";
|
|
3
|
+
export type { AkumaBirthInput, AkumaHistoryOptions, AkumaIdleOptions, AkumaIdleResult, AkumaSignalOptions, AkumaTellOptions, InterruptReceipt, KillEvidence, JsonSchema, JsonSchemaDocument, StandardSchemaV1, SchemaLike, AkuId, AkumaStatus, ActivityHistory, ActivityRow, AllowedAction, AllowedActions, } from "./akuma/index.js";
|
|
4
4
|
export { settings } from "./settings.js";
|
|
5
|
+
export { z } from "zod";
|
|
5
6
|
export { World, WorldError } from "./world.js";
|
|
6
7
|
export type { WorldResolution, WorldResolutionInput, WorldRoot } from "./world.js";
|
|
7
8
|
export type { KeiyakuPlugin, PluginContext, PluginHooks, PluginInstance, PluginManifest, PluginBodyEnd, PluginOutcome, PluginSignal, PluginSignalMap, } from "./plugin/public.js";
|
package/build/src/index.js
CHANGED
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
export * from "./library/keiyaku.js";
|
|
2
2
|
export { Akuma, Schema, ALLOWED_ACTIONS, AkumaBusyError, AkumaDecodeError, AkumaNotBornError, AkumaProviderError, } from "./akuma/index.js";
|
|
3
3
|
export { settings } from "./settings.js";
|
|
4
|
+
export { z } from "zod";
|
|
4
5
|
export { World, WorldError } from "./world.js";
|
package/build/src/kanshi/read.js
CHANGED
|
@@ -2,12 +2,12 @@ import { Repo } from "../library/repo.js";
|
|
|
2
2
|
import { scopeForRepo } from "../library/repo.js";
|
|
3
3
|
import { observeTaskBoard } from "../task/operations.js";
|
|
4
4
|
import { contractNamespace } from "../task/identity.js";
|
|
5
|
-
import { readAkumaCatalog, readAkumaTimeline } from "../akuma/akuma.js";
|
|
5
|
+
import { readAkumaCatalog, readAkumaTimeline, withoutReportedChanges } from "../akuma/akuma.js";
|
|
6
6
|
import { readAliases } from "../alias/index.js";
|
|
7
7
|
import { readDispatchesAt } from "../dispatch/index.js";
|
|
8
8
|
import { readTaskHolderProjectionAt } from "../settlement/holder.js";
|
|
9
9
|
import { observeCurrentPhysicalIssue } from "../protocol/read/observation.js";
|
|
10
|
-
import { readContractBoard, readContractCatalogue } from "../protocol/read/status.js";
|
|
10
|
+
import { readContractBoard, readContractCatalogue, readContractPhase } from "../protocol/read/status.js";
|
|
11
11
|
import { withGitDecodeChannel, withGitReadObservation } from "../git/read-observation.js";
|
|
12
12
|
import { decodeContractDocument } from "../body/decode.js";
|
|
13
13
|
import { assertRegionPattern } from "../body/region.js";
|
|
@@ -262,7 +262,27 @@ async function readTasks(path, holders, observeContract) {
|
|
|
262
262
|
value: { root: path, hasMore: recent.hasMore, rows: joinTasks(recent.rows, holders, observeContract) },
|
|
263
263
|
}), (error) => ({ kind: "failed", failure: taskFailure(error) }));
|
|
264
264
|
}
|
|
265
|
-
|
|
265
|
+
/**
|
|
266
|
+
* Placement discharge for the observed fleet snapshots: an associated Contract
|
|
267
|
+
* whose own observation proves it claimed has placed the candidate those
|
|
268
|
+
* reported changes describe, so the composite report no longer presents them
|
|
269
|
+
* as pending. An unproven or failed Contract read keeps the changes.
|
|
270
|
+
*/
|
|
271
|
+
async function claimedAssociatedContracts(observation, dispatches) {
|
|
272
|
+
const ids = [...new Set(dispatches.map((dispatch) => dispatch.contractId))];
|
|
273
|
+
const claimed = new Set();
|
|
274
|
+
await Promise.all(ids.map(async (id) => {
|
|
275
|
+
try {
|
|
276
|
+
if ((await readContractPhase(observation, id)) === "claimed")
|
|
277
|
+
claimed.add(id);
|
|
278
|
+
}
|
|
279
|
+
catch {
|
|
280
|
+
/* unproven placement keeps reported changes */
|
|
281
|
+
}
|
|
282
|
+
}));
|
|
283
|
+
return claimed;
|
|
284
|
+
}
|
|
285
|
+
async function joinAkuma(path, observeContract, isContractClaimed, dispatches, aliases) {
|
|
266
286
|
if (aliases.kind !== "present")
|
|
267
287
|
return aliases;
|
|
268
288
|
try {
|
|
@@ -301,7 +321,10 @@ async function joinAkuma(path, observeContract, dispatches, aliases) {
|
|
|
301
321
|
...source,
|
|
302
322
|
rows: rows.map((row) => {
|
|
303
323
|
const snapshot = snapshots.get(row.id);
|
|
304
|
-
|
|
324
|
+
if (snapshot === undefined)
|
|
325
|
+
return row;
|
|
326
|
+
const placed = row.contract !== undefined && isContractClaimed(row.contract.id);
|
|
327
|
+
return { ...row, snapshot: placed ? withoutReportedChanges(snapshot) : snapshot };
|
|
305
328
|
}),
|
|
306
329
|
},
|
|
307
330
|
};
|
|
@@ -332,7 +355,7 @@ async function observeWithoutRepo(world, region, observedAt, branch) {
|
|
|
332
355
|
const observeContract = contractEndpointObserver(contracts);
|
|
333
356
|
const tasks = world === null ? { kind: "absent" } : await readTasks(world, holders, observeContract);
|
|
334
357
|
const aliases = world === null ? { kind: "absent" } : await readAliasBindings(world);
|
|
335
|
-
const akuma = world === null ? { kind: "absent" } : await joinAkuma(world, observeContract, [], aliases);
|
|
358
|
+
const akuma = world === null ? { kind: "absent" } : await joinAkuma(world, observeContract, () => false, [], aliases);
|
|
336
359
|
return {
|
|
337
360
|
report: {
|
|
338
361
|
root: world,
|
|
@@ -364,11 +387,14 @@ async function observeRepo(input) {
|
|
|
364
387
|
const observeContract = contractEndpointObserver(contracts);
|
|
365
388
|
const aliases = world === null ? { kind: "absent" } : await readAliasBindings(world);
|
|
366
389
|
const tasks = world === null ? { kind: "absent" } : await readTasks(world, holders, observeContract);
|
|
390
|
+
const claimed = dispatches.kind === "present"
|
|
391
|
+
? await claimedAssociatedContracts(observation, dispatches.value)
|
|
392
|
+
: new Set();
|
|
367
393
|
const akuma = world === null
|
|
368
394
|
? { kind: "absent" }
|
|
369
395
|
: dispatches.kind === "failed"
|
|
370
396
|
? dispatches
|
|
371
|
-
: await joinAkuma(world, observeContract, dispatches.value, aliases);
|
|
397
|
+
: await joinAkuma(world, observeContract, (id) => claimed.has(id), dispatches.value, aliases);
|
|
372
398
|
const assembled = {
|
|
373
399
|
root: world,
|
|
374
400
|
observedAt,
|
|
@@ -51,6 +51,9 @@ export declare function addressAkuma(input: UncheckedAkumaAddressInput): Promise
|
|
|
51
51
|
}>>;
|
|
52
52
|
export declare function addressAkumaSet(input: UncheckedAkumaAddressInput): Promise<Readonly<{
|
|
53
53
|
path: WorldRoot;
|
|
54
|
+
/** The complete selected set in canonical order: the durable result order. */
|
|
54
55
|
ids: readonly AkuId[];
|
|
56
|
+
/** The same set in the caller's selector order, for surfaces that name the selection as chosen. */
|
|
57
|
+
orderedIds: readonly AkuId[];
|
|
55
58
|
}>>;
|
|
56
59
|
export {};
|
|
@@ -158,9 +158,10 @@ export async function addressAkumaSet(input) {
|
|
|
158
158
|
const sources = { fleetIds, aliases, dispatches };
|
|
159
159
|
for (const selector of selectors)
|
|
160
160
|
addSelectorIds(selector, sources, selected, contractMembers);
|
|
161
|
+
const orderedIds = [...selected];
|
|
161
162
|
const ids = [...selected].sort((left, right) => Buffer.compare(Buffer.from(left), Buffer.from(right)));
|
|
162
163
|
if (ids.length === 0)
|
|
163
164
|
throw new TypeError("Akuma selector snapshot is empty");
|
|
164
165
|
await refuseForeignContractMembers(path, ids, contractMembers);
|
|
165
|
-
return { path, ids };
|
|
166
|
+
return { path, ids, orderedIds };
|
|
166
167
|
}
|