@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.
Files changed (32) hide show
  1. package/build/integrations/marketplace/plugins/keiyaku/skills/keiyaku-akuma/SKILL.md +18 -0
  2. package/build/integrations/marketplace/plugins/keiyaku/skills/keiyaku-akuma/references/automation.md +33 -14
  3. package/build/src/akuma/akuma-handle.js +5 -2
  4. package/build/src/akuma/akuma-instance.d.ts +15 -3
  5. package/build/src/akuma/akuma-instance.js +27 -13
  6. package/build/src/akuma/akuma.d.ts +1 -0
  7. package/build/src/akuma/akuma.js +1 -0
  8. package/build/src/akuma/fleet-execution.d.ts +2 -0
  9. package/build/src/akuma/fleet-execution.js +22 -5
  10. package/build/src/akuma/heart/index.d.ts +3 -0
  11. package/build/src/akuma/heart/index.js +12 -2
  12. package/build/src/akuma/index.d.ts +2 -2
  13. package/build/src/akuma/projection-read.d.ts +2 -0
  14. package/build/src/akuma/projection-read.js +4 -0
  15. package/build/src/akuma/projection.d.ts +1 -1
  16. package/build/src/akuma/projection.js +1 -1
  17. package/build/src/akuma/schema.d.ts +40 -0
  18. package/build/src/akuma/schema.js +114 -0
  19. package/build/src/cli/commands/akuma-invoke.d.ts +5 -0
  20. package/build/src/cli/commands/akuma-invoke.js +19 -3
  21. package/build/src/cli/render/akuma-activity.d.ts +6 -3
  22. package/build/src/cli/render/akuma-activity.js +168 -51
  23. package/build/src/cli/runtime.js +2 -2
  24. package/build/src/index.d.ts +2 -1
  25. package/build/src/index.js +1 -0
  26. package/build/src/kanshi/read.js +32 -6
  27. package/build/src/library/address.d.ts +3 -0
  28. package/build/src/library/address.js +2 -1
  29. package/build/src/library/fleet.js +54 -11
  30. package/build/src/protocol/read/status.d.ts +5 -0
  31. package/build/src/protocol/read/status.js +11 -0
  32. 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) => stream.select(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(), stream === undefined ? undefined : waitObserver(stream));
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. Each Akuma's stream opens with its identity frame
76
- * — the identity and Contract association the observed facts carry — before any
77
- * row; an already settled Akuma prints that frame and never replays backlog
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 = `✓ came back ${identity(id, alias)}`;
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", waited: true };
439
+ return { mark: "●", verb: "still running" };
440
440
  if (answered)
441
- return { mark: "✓", verb: "answered", waited: false };
441
+ return { mark: "✓", verb: "answered" };
442
442
  if (status.life === "asleep")
443
- return { mark: "!", verb: "failed", waited: false };
443
+ return { mark: "!", verb: "failed" };
444
444
  if (status.life === "killed")
445
- return { mark: "×", verb: "killed", waited: false };
445
+ return { mark: "×", verb: "killed" };
446
446
  if (status.life === "hung")
447
- return { mark: "?", verb: "hung", waited: false };
447
+ return { mark: "?", verb: "hung" };
448
448
  if (status.life === "untidy")
449
- return { mark: "!", verb: "untidy", waited: false };
450
- return { mark: "!", verb: "stranded", waited: false };
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
- opened: false,
480
+ plural: undefined,
481
+ headerEmitted: false,
481
482
  observed: false,
482
483
  };
483
484
  }
484
- function registerWaitSource(state, id, alias) {
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
- state.sourceWidth = Math.max(state.sourceWidth, displayColumns(label));
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
- for (const { status, alias, contract } of round) {
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 === undefined) {
500
- if (state.opened)
501
- lines.push("");
502
- lines.push(...snapshotHeading(status.id, alias, contract));
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.sources.size > 1
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 total = result.observations.length + result.unobserved.length;
534
- const multi = total > 1;
535
- const conclusions = result.observations.map((observation) => {
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(status.id) ?? end) : end;
540
- const durationMs = Math.max(0, at - startedAt);
541
- const { mark, verb, waited } = conclusionMarkVerb(status, answered);
542
- const target = multi ? ` ${padToDisplay(state.sources.get(status.id) ?? status.id, state.sourceWidth)}` : "";
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. Each Akuma's stream opens with its identity frame
558
- * — the identity and Contract association the observed facts carry — before any
559
- * row; an already settled Akuma prints that frame and never replays backlog
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 at = waitComplete(status) ? (settleMoment(status) ?? end) : end;
620
- const durationMs = Math.max(0, at - startedAt);
621
- const { mark, verb, waited } = conclusionMarkVerb(status, answered);
622
- lines.push(`${clockFromMs(at)} ${mark} ${verb} — ${waited ? "waited " : ""}${durationText(durationMs)}`);
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 total = result.result.observations.length + result.result.unobserved.length;
787
- const done = result.result.observations.filter((observation) => waitComplete(observation.status)).length;
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
- ...result.result.observations.map((observation) => {
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, alias, context.columns);
793
- return snapshotText(observation, context, { ...(alias === undefined ? {} : { alias }) });
892
+ return [answeredBlock(observation, answer, undefined, context.columns)];
893
+ return [snapshotText(observation, context)];
794
894
  }),
795
- ...result.result.unobserved.map((member) => unobservedText(member.id, member.diagnostic)),
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
- if (total <= 1)
798
- return blocks.join("\n\n");
799
- return [...blocks, `${done} of ${total} done`].join("\n\n");
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") {
@@ -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("../index.js");
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("../index.js");
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);
@@ -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";
@@ -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";
@@ -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
- async function joinAkuma(path, observeContract, dispatches, aliases) {
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
- return snapshot === undefined ? row : { ...row, snapshot };
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
  }