@astrosheep/keiyaku 4.5.24 → 4.5.26

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 (43) hide show
  1. package/build/src/akuma/akuma-handle.d.ts +10 -0
  2. package/build/src/akuma/akuma-handle.js +18 -18
  3. package/build/src/akuma/akuma-instance.d.ts +5 -10
  4. package/build/src/akuma/akuma-instance.js +55 -40
  5. package/build/src/akuma/akuma-observe.d.ts +40 -7
  6. package/build/src/akuma/akuma-observe.js +51 -16
  7. package/build/src/akuma/akuma-product.d.ts +1 -2
  8. package/build/src/akuma/akuma-product.js +8 -5
  9. package/build/src/akuma/akuma.d.ts +2 -0
  10. package/build/src/akuma/body.d.ts +1 -1
  11. package/build/src/akuma/body.js +48 -30
  12. package/build/src/akuma/call-request.d.ts +0 -1
  13. package/build/src/akuma/call-request.js +1 -2
  14. package/build/src/akuma/fleet-execution.d.ts +3 -1
  15. package/build/src/akuma/fleet-execution.js +30 -71
  16. package/build/src/akuma/fleet-observation.d.ts +13 -2
  17. package/build/src/akuma/fleet-observation.js +2 -1
  18. package/build/src/akuma/fleet-request.js +3 -2
  19. package/build/src/akuma/heart/timeline.d.ts +1 -1
  20. package/build/src/akuma/heart/timeline.js +5 -5
  21. package/build/src/akuma/projection-read.js +37 -7
  22. package/build/src/akuma/projection.d.ts +1 -0
  23. package/build/src/akuma/projection.js +24 -9
  24. package/build/src/akuma/providers/pi/index.d.ts +2 -1
  25. package/build/src/akuma/providers/pi/index.js +2 -1
  26. package/build/src/akuma/publication.js +7 -4
  27. package/build/src/akuma-body.js +1 -0
  28. package/build/src/cli/commands/akuma-invoke.d.ts +1 -0
  29. package/build/src/cli/commands/akuma-invoke.js +16 -3
  30. package/build/src/cli/invoke.js +1 -0
  31. package/build/src/cli/render/akuma-activity.d.ts +13 -5
  32. package/build/src/cli/render/akuma-activity.js +118 -112
  33. package/build/src/cli/render/akuma-tool.js +5 -3
  34. package/build/src/library/address.d.ts +1 -1
  35. package/build/src/library/address.js +10 -8
  36. package/build/src/library/akuma-creation.d.ts +3 -0
  37. package/build/src/library/akuma-creation.js +111 -46
  38. package/build/src/library/composition.d.ts +3 -1
  39. package/build/src/library/fleet.d.ts +7 -1
  40. package/build/src/library/fleet.js +35 -9
  41. package/build/src/library/keiyaku.d.ts +3 -1
  42. package/build/src/runtime/proc/windows-launch.exe +0 -0
  43. package/package.json +3 -3
@@ -1,7 +1,7 @@
1
1
  import { appendFile, stat } from "node:fs/promises";
2
2
  import { basename, delimiter, join } from "node:path";
3
3
  import { fileURLToPath } from "node:url";
4
- import { abortableDelay } from "./abort.js";
4
+ import { abortable, abortableDelay } from "./abort.js";
5
5
  import { BodySupervisor } from "./body-supervisor.js";
6
6
  import { driveTurn, turnRecipe } from "./turn-drive.js";
7
7
  import { HeldAkumaLeash, breakBody, decidePendingTellDisposition, drainPendingTells, endTurn, failOpenBoundTurns, finishBodyIfIdle, heartExists, isHeartAbsent, probeLeash, projectTell, readHeart, readOpenBoundTurns, readOpenPendingTellDisposition, readTell, readTurn, readNonterminalRequests, resolvePendingTellDisposition, } from "./heart/index.js";
@@ -587,50 +587,68 @@ function failedChild(exit) {
587
587
  child: { code: exit.code, signal: exit.signal, log: exit.log },
588
588
  };
589
589
  }
590
- async function awaitWake(paths, tellId, after, child, schedule) {
591
- for (;;) {
592
- const settled = await settledWake(paths, tellId, after);
593
- if (settled !== null) {
594
- child.release();
595
- return settled;
596
- }
597
- const timerController = new AbortController();
598
- const timer = schedule(WAKE_REREAD_MS, timerController.signal).then(() => ({ kind: "timer" }));
599
- let winner;
600
- try {
601
- winner = await Promise.race([child.exited.then((exit) => ({ kind: "exited", exit })), timer]);
602
- }
603
- catch (error) {
590
+ async function awaitWake(paths, input) {
591
+ const { tellId, after, child, schedule, signal } = input;
592
+ try {
593
+ for (;;) {
594
+ signal?.throwIfAborted();
595
+ const settled = await settledWake(paths, tellId, after);
596
+ if (settled !== null) {
597
+ child.release();
598
+ return settled;
599
+ }
600
+ const timerController = new AbortController();
601
+ const timer = schedule(WAKE_REREAD_MS, timerController.signal).then(() => ({ kind: "timer" }));
602
+ let winner;
603
+ try {
604
+ const waited = Promise.race([child.exited.then((exit) => ({ kind: "exited", exit })), timer]);
605
+ winner = signal === undefined ? await waited : await abortable(waited, signal);
606
+ }
607
+ catch (error) {
608
+ timerController.abort();
609
+ await timer.catch(() => undefined);
610
+ throw error;
611
+ }
612
+ if (winner.kind === "timer")
613
+ continue;
604
614
  timerController.abort();
605
615
  await timer.catch(() => undefined);
606
- throw error;
607
- }
608
- if (winner.kind === "timer")
609
- continue;
610
- timerController.abort();
611
- await timer.catch(() => undefined);
612
- const final = await settledWake(paths, tellId, after);
613
- if (final !== null) {
614
- child.release();
615
- return final;
616
+ const final = await settledWake(paths, tellId, after);
617
+ if (final !== null) {
618
+ child.release();
619
+ return final;
620
+ }
621
+ return winner.exit.code === LEASH_HELD_EXIT ? { kind: "held" } : failedChild(winner.exit);
616
622
  }
617
- return winner.exit.code === LEASH_HELD_EXIT ? { kind: "held" } : failedChild(winner.exit);
623
+ }
624
+ catch (error) {
625
+ child.release();
626
+ throw error;
618
627
  }
619
628
  }
620
- async function wakePendingTells(paths, tellId, runtime) {
629
+ async function wakePendingTells(paths, tellId, runtime, signal) {
621
630
  try {
631
+ signal?.throwIfAborted();
622
632
  const beforeHeart = await readHeart(paths);
623
633
  if (tellId === undefined && beforeHeart.pending.length === 0)
624
634
  return null;
625
635
  const before = beforeHeart.latestBody?.sequence ?? 0;
626
- return await awaitWake(paths, tellId, before, await runtime.spawn(paths), runtime.schedule ?? abortableDelay);
636
+ return await awaitWake(paths, {
637
+ ...(tellId === undefined ? {} : { tellId }),
638
+ after: before,
639
+ child: await runtime.spawn(paths),
640
+ schedule: runtime.schedule ?? abortableDelay,
641
+ ...(signal === undefined ? {} : { signal }),
642
+ });
627
643
  }
628
644
  catch (error) {
645
+ if (signal?.aborted)
646
+ throw error;
629
647
  return { kind: "failed", diagnostic: diagnostic(error) };
630
648
  }
631
649
  }
632
- export async function wakeRecordedTell(paths, tellId, runtime = DIRECT_TELL_WAKE) {
633
- const wake = (await wakePendingTells(paths, tellId, runtime));
650
+ export async function wakeRecordedTell(paths, tellId, runtime = DIRECT_TELL_WAKE, signal) {
651
+ const wake = (await wakePendingTells(paths, tellId, runtime, signal));
634
652
  const tell = await readTell(paths, tellId);
635
653
  if (tell === null)
636
654
  throw new Error(`recorded Tell ${tellId} is missing from Heart`);
@@ -30,7 +30,6 @@ declare const akumaCallPayloadSchema: z.ZodObject<{
30
30
  archetype: z.ZodPipe<z.ZodString, z.ZodTransform<string, string>>;
31
31
  body: z.ZodOptional<z.ZodString>;
32
32
  initiator: z.ZodOptional<z.ZodString>;
33
- awaitAsleep: z.ZodOptional<z.ZodLiteral<true>>;
34
33
  cwd: z.ZodOptional<z.ZodString>;
35
34
  recipe: z.ZodObject<{
36
35
  description: z.ZodOptional<z.ZodString>;
@@ -68,7 +68,6 @@ const akumaCallPayloadSchema = z
68
68
  archetype: archetypeSchema,
69
69
  body: z.string().optional(),
70
70
  initiator: z.string().min(1).optional(),
71
- awaitAsleep: z.literal(true).optional(),
72
71
  cwd: absolutePathSchema.optional(),
73
72
  recipe: akumaCallRecipeSchema,
74
73
  })
@@ -105,7 +104,6 @@ async function executeAkumaCall(request, facts, capabilities) {
105
104
  worldPath: world,
106
105
  archetype: request.archetype,
107
106
  signal: facts.signal,
108
- ...(request.awaitAsleep === undefined ? {} : { awaitAsleep: request.awaitAsleep }),
109
107
  launch: async (allocated) => {
110
108
  if (!facts.admissionOpen())
111
109
  throw new Error("body closed request admission");
@@ -129,6 +127,7 @@ async function executeAkumaCall(request, facts, capabilities) {
129
127
  export function akumaCallRequestProtocol() {
130
128
  return {
131
129
  action: "akuma.call",
130
+ supportsCancellation: true,
132
131
  encodeRequest: callPayload,
133
132
  decodeRequest: decodeAkumaCallRequest,
134
133
  encodeResult: (result) => result,
@@ -1,4 +1,5 @@
1
1
  import { type AkumaStatus } from "./akuma.js";
2
+ import type { ActivityRow } from "./projection.js";
2
3
  import { type DispatchAssociation } from "./dispatch-association.js";
3
4
  import type { AkumaAlias } from "../identity/selector.js";
4
5
  import type { WorldRoot } from "../world.js";
@@ -12,6 +13,7 @@ export type WaitIdentityFacts = Readonly<{
12
13
  /** One observed Akuma as a live wait viewer sees it: its status plus its identity facts. */
13
14
  export type WaitObservedAkuma = Readonly<{
14
15
  status: AkumaStatus;
16
+ rows: readonly ActivityRow[];
15
17
  }> & WaitIdentityFacts;
16
18
  /** One selected Akuma's frozen identity, resolved before the first observation round. */
17
19
  export type WaitSelectedAkuma = Readonly<{
@@ -34,7 +36,7 @@ export type WaitExecutionInput = Readonly<{
34
36
  selectionOrder?: readonly AkumaStatus["id"][];
35
37
  /** Reports the frozen selected set before the first round, so a viewer can fix its layout. */
36
38
  onSelected?: (selected: readonly WaitSelectedAkuma[]) => void;
37
- /** Reports every observation round to a live viewer; absent keeps the cheap completion probe. */
39
+ /** Reports every observation round to a live viewer. */
38
40
  observe?: (observed: readonly WaitObservedAkuma[]) => void;
39
41
  }>;
40
42
  export declare function executeWaitAkuma(input: WaitExecutionInput): Promise<AkumaWaitResult>;
@@ -1,6 +1,6 @@
1
1
  import { AkumaNotBornError, defaultWaitComplete } from "./akuma.js";
2
2
  import { createAkumaProduct } from "./akuma-product.js";
3
- import { readBudgetedStatus, readWaitComplete } from "./akuma-observe.js";
3
+ import { readLiveStatus, waitForObservation } from "./akuma-observe.js";
4
4
  import { NO_DISPATCH_ASSOCIATION } from "./dispatch-association.js";
5
5
  import { EMPTY_CREATED_TASK_OBSERVATION } from "../task/created-observation.js";
6
6
  import { fleetResultSchemas, parseAkumaObservation, } from "./fleet-observation.js";
@@ -11,7 +11,6 @@ function observationDiagnostic(error) {
11
11
  return error instanceof Error ? error.message : String(error);
12
12
  }
13
13
  const SHARED_ORDINARY_BUDGET = 30;
14
- const POLL_MS = 100;
15
14
  function akumaOnlyObservation(status) {
16
15
  return parseAkumaObservation({
17
16
  status,
@@ -22,19 +21,17 @@ function akumaOnlyObservation(status) {
22
21
  async function observeWaitRound(path, ids, signal) {
23
22
  signal?.throwIfAborted();
24
23
  if (ids.length <= 1) {
25
- return {
26
- statuses: await Promise.all(ids.map(async (id) => await source(path).selectHandle({ id }).status())),
27
- unobserved: [],
28
- };
24
+ const observations = await Promise.all(ids.map(async (id) => await readLiveStatus(path, id, { aperture: "monitoring" })));
25
+ return { observations, unobserved: [] };
29
26
  }
30
27
  let remaining = SHARED_ORDINARY_BUDGET;
31
- const statuses = [];
28
+ const observations = [];
32
29
  const unobserved = [];
33
30
  for (const id of ids) {
34
31
  signal?.throwIfAborted();
35
32
  try {
36
- const observed = await readBudgetedStatus(path, id, { aperture: "monitoring", ordinaryBudget: remaining });
37
- statuses.push(observed.status);
33
+ const observed = await readLiveStatus(path, id, { aperture: "monitoring", ordinaryBudget: remaining });
34
+ observations.push(observed);
38
35
  remaining -= observed.ordinarySelected;
39
36
  }
40
37
  catch (error) {
@@ -43,50 +40,13 @@ async function observeWaitRound(path, ids, signal) {
43
40
  unobserved.push({ id, diagnostic: observationDiagnostic(error) });
44
41
  }
45
42
  }
46
- return { statuses, unobserved };
47
- }
48
- async function probeWaitRound(input) {
49
- let observed = 0;
50
- let complete = 0;
51
- for (const id of input.ids) {
52
- input.signal?.throwIfAborted();
53
- try {
54
- if (await readWaitComplete(input.path, id))
55
- complete += 1;
56
- observed += 1;
57
- }
58
- catch (error) {
59
- if (input.ids.length <= 1 || error instanceof AkumaNotBornError)
60
- throw error;
61
- // Plural wait retries unreadable members; final rendering owns diagnostics.
62
- }
63
- }
64
- return observed > 0 && (input.completion === "any" ? complete > 0 : complete === input.ids.length);
43
+ return { observations, unobserved };
65
44
  }
66
45
  function roundComplete(round, completion) {
67
- const settled = round.statuses.map(defaultWaitComplete);
46
+ const settled = round.observations.map((observation) => defaultWaitComplete(observation.status));
68
47
  return (settled.length > 0 &&
69
48
  (completion === "any" ? settled.some(Boolean) : round.unobserved.length === 0 && settled.every(Boolean)));
70
49
  }
71
- function delay(milliseconds, signal) {
72
- return new Promise((resolve, reject) => {
73
- let timer;
74
- const done = () => {
75
- signal?.removeEventListener("abort", abort);
76
- resolve();
77
- };
78
- const abort = () => {
79
- clearTimeout(timer);
80
- signal?.removeEventListener("abort", abort);
81
- reject(signal?.reason ?? new Error("upstream request aborted"));
82
- };
83
- timer = setTimeout(done, milliseconds);
84
- if (signal?.aborted)
85
- abort();
86
- else
87
- signal?.addEventListener("abort", abort, { once: true });
88
- });
89
- }
90
50
  /** Canonical ids in the caller's selection order, appending any id the caller never named. */
91
51
  function selectionOrderedIds(ids, order) {
92
52
  const present = new Set(ids);
@@ -107,7 +67,6 @@ function selectionOrderedIds(ids, order) {
107
67
  return ordered;
108
68
  }
109
69
  export async function executeWaitAkuma(input) {
110
- const deadline = input.timeoutMs === undefined ? undefined : performance.now() + input.timeoutMs;
111
70
  // Identity facts are read once per observed Akuma, not once per 100ms round.
112
71
  const facts = new Map();
113
72
  if (input.onSelected !== undefined) {
@@ -119,47 +78,47 @@ export async function executeWaitAkuma(input) {
119
78
  const ordered = input.selectionOrder === undefined ? input.ids : selectionOrderedIds(input.ids, input.selectionOrder);
120
79
  input.onSelected(ordered.map((id) => ({ id, ...facts.get(id) })));
121
80
  }
122
- const observeRound = async (statuses) => {
81
+ const observeRound = async (round) => {
123
82
  if (input.observe === undefined)
124
83
  return;
125
84
  const observed = [];
126
- for (const status of statuses) {
85
+ for (const observation of round.observations) {
127
86
  input.signal?.throwIfAborted();
87
+ const { status, rows } = observation;
128
88
  let known = facts.get(status.id);
129
89
  if (known === undefined && input.identity !== undefined) {
130
90
  known = await input.identity(status.id);
131
91
  facts.set(status.id, known);
132
92
  }
133
93
  if (known === undefined) {
134
- observed.push({ status, contract: NO_DISPATCH_ASSOCIATION });
94
+ observed.push({ status, rows, contract: NO_DISPATCH_ASSOCIATION });
135
95
  continue;
136
96
  }
137
- observed.push({ status, ...known });
97
+ observed.push({ status, rows, ...known });
138
98
  }
139
99
  input.observe(observed);
140
100
  };
141
- for (;;) {
142
- const expired = deadline !== undefined && performance.now() >= deadline;
143
- if (expired || input.observe !== undefined || (await probeWaitRound(input))) {
144
- const round = await observeWaitRound(input.path, input.ids, input.signal);
145
- input.signal?.throwIfAborted();
146
- await observeRound(round.statuses);
147
- // The probe is not a completion receipt. Judge the actual returned values.
148
- if (roundComplete(round, input.completion) || (deadline !== undefined && performance.now() >= deadline)) {
149
- return fleetResultSchemas.wait.parse({
150
- completion: input.completion,
151
- observations: round.statuses.map(akumaOnlyObservation),
152
- unobserved: round.unobserved,
153
- });
154
- }
155
- }
156
- await delay(deadline === undefined ? POLL_MS : Math.min(POLL_MS, Math.max(0, deadline - performance.now())), input.signal);
157
- }
101
+ const waited = await waitForObservation({
102
+ ...(input.timeoutMs === undefined ? {} : { timeoutMs: input.timeoutMs }),
103
+ ...(input.signal === undefined ? {} : { signal: input.signal }),
104
+ observe: async () => await observeWaitRound(input.path, input.ids, input.signal),
105
+ complete: (round) => roundComplete(round, input.completion),
106
+ onObserve: async (round) => await observeRound(round),
107
+ });
108
+ return fleetResultSchemas.wait.parse({
109
+ mode: input.completion,
110
+ reason: waited.reason,
111
+ observations: waited.value.observations.map((observation) => akumaOnlyObservation(observation.status)),
112
+ unobserved: waited.value.unobserved,
113
+ });
158
114
  }
159
115
  export async function executeTellAkuma(input) {
160
116
  input.signal?.throwIfAborted();
161
117
  const handle = source(input.path).selectHandle({ id: input.id });
162
- const tell = await handle.tell(input.body, input.tellId, input.recordedAt, undefined, input.initiator === undefined ? {} : { initiator: input.initiator });
118
+ const tell = await handle.tell(input.body, input.tellId, input.recordedAt, undefined, {
119
+ ...(input.initiator === undefined ? {} : { initiator: input.initiator }),
120
+ ...(input.signal === undefined ? {} : { signal: input.signal }),
121
+ });
163
122
  input.signal?.throwIfAborted();
164
123
  return fleetResultSchemas.tell.parse({
165
124
  akuma: input.id,
@@ -245,6 +245,7 @@ declare const akumaObservationSchema: z.ZodObject<{
245
245
  }, z.core.$strict>]>;
246
246
  }, z.core.$strict>]>>>;
247
247
  omitted: z.ZodNumber;
248
+ openingSequence: z.ZodOptional<z.ZodNumber>;
248
249
  }, z.core.$strict>, z.ZodObject<{
249
250
  reportedChanges: z.ZodReadonly<z.ZodArray<z.ZodObject<{
250
251
  sequence: z.ZodNumber;
@@ -449,10 +450,14 @@ declare const akumaUnobservedSchema: z.ZodObject<{
449
450
  diagnostic: z.ZodString;
450
451
  }, z.core.$strict>;
451
452
  declare const akumaWaitResultSchema: z.ZodObject<{
452
- completion: z.ZodEnum<{
453
+ mode: z.ZodEnum<{
453
454
  any: "any";
454
455
  all: "all";
455
456
  }>;
457
+ reason: z.ZodEnum<{
458
+ deadline: "deadline";
459
+ completed: "completed";
460
+ }>;
456
461
  observations: z.ZodArray<z.ZodObject<{
457
462
  status: z.ZodObject<{
458
463
  id: z.ZodPipe<z.ZodString, z.ZodTransform<import("./identity.js").AkuId, string>>;
@@ -699,6 +704,7 @@ declare const akumaWaitResultSchema: z.ZodObject<{
699
704
  }, z.core.$strict>]>;
700
705
  }, z.core.$strict>]>>>;
701
706
  omitted: z.ZodNumber;
707
+ openingSequence: z.ZodOptional<z.ZodNumber>;
702
708
  }, z.core.$strict>, z.ZodObject<{
703
709
  reportedChanges: z.ZodReadonly<z.ZodArray<z.ZodObject<{
704
710
  sequence: z.ZodNumber;
@@ -1022,10 +1028,14 @@ export declare function isKillResult(value: unknown): value is AkumaKillResult;
1022
1028
  export declare function isTellResult(value: unknown): value is AkumaTellResult;
1023
1029
  export declare const fleetResultSchemas: {
1024
1030
  wait: z.ZodObject<{
1025
- completion: z.ZodEnum<{
1031
+ mode: z.ZodEnum<{
1026
1032
  any: "any";
1027
1033
  all: "all";
1028
1034
  }>;
1035
+ reason: z.ZodEnum<{
1036
+ deadline: "deadline";
1037
+ completed: "completed";
1038
+ }>;
1029
1039
  observations: z.ZodArray<z.ZodObject<{
1030
1040
  status: z.ZodObject<{
1031
1041
  id: z.ZodPipe<z.ZodString, z.ZodTransform<import("./identity.js").AkuId, string>>;
@@ -1272,6 +1282,7 @@ export declare const fleetResultSchemas: {
1272
1282
  }, z.core.$strict>]>;
1273
1283
  }, z.core.$strict>]>>>;
1274
1284
  omitted: z.ZodNumber;
1285
+ openingSequence: z.ZodOptional<z.ZodNumber>;
1275
1286
  }, z.core.$strict>, z.ZodObject<{
1276
1287
  reportedChanges: z.ZodReadonly<z.ZodArray<z.ZodObject<{
1277
1288
  sequence: z.ZodNumber;
@@ -50,7 +50,8 @@ const akumaUnobservedSchema = z.object({ id: akumaIdSchema, diagnostic: z.string
50
50
  const akumaKillResultItemSchema = z.object({ id: akumaIdSchema, evidence: killEvidenceSchema }).strict();
51
51
  const akumaWaitResultSchema = z
52
52
  .object({
53
- completion: z.enum(["any", "all"]),
53
+ mode: z.enum(["any", "all"]),
54
+ reason: z.enum(["completed", "deadline"]),
54
55
  observations: z.array(akumaObservationSchema),
55
56
  unobserved: z.array(akumaUnobservedSchema),
56
57
  })
@@ -10,8 +10,8 @@ const fleetTargetsSchema = z
10
10
  .array(akumaIdSchema)
11
11
  .min(1)
12
12
  .superRefine((ids, context) => {
13
- if (ids.some((id, index) => index > 0 && ids[index - 1] >= id))
14
- context.addIssue({ code: "custom", message: "expected a strictly ordered target set" });
13
+ if (new Set(ids).size !== ids.length)
14
+ context.addIssue({ code: "custom", message: "expected a deduplicated target set" });
15
15
  });
16
16
  const waitRequestSchema = z
17
17
  .object({
@@ -92,6 +92,7 @@ function decodedFleetResult(action, value) {
92
92
  export function fleetRequestProtocol(action) {
93
93
  return {
94
94
  action,
95
+ supportsCancellation: true,
95
96
  encodeRequest: (request) => {
96
97
  const { action: _action, ...payload } = request;
97
98
  return payload;
@@ -13,6 +13,6 @@ export type StatusFactInput = Readonly<{
13
13
  aperture: "monitoring" | "receipt";
14
14
  admittedTellId?: string;
15
15
  }>;
16
- /** Select the frontier and Tell pins, not an arbitrary tail of raw events. */
16
+ /** Select the frontier and its delivered Tells, plus aperture-required Tell pins. */
17
17
  export declare function statusFacts(database: DatabaseSync, input: StatusFactInput): readonly TimelineFact[];
18
18
  export declare function activityFactSlice(database: DatabaseSync): ActivityFactSlice;
@@ -88,7 +88,7 @@ function decodeTimelineRows(database, rows) {
88
88
  return fact;
89
89
  });
90
90
  }
91
- /** Select the frontier and Tell pins, not an arbitrary tail of raw events. */
91
+ /** Select the frontier and its delivered Tells, plus aperture-required Tell pins. */
92
92
  export function statusFacts(database, input) {
93
93
  const frontier = database.prepare("SELECT sequence FROM turns ORDER BY sequence DESC LIMIT 1").get();
94
94
  const rows = database
@@ -97,13 +97,13 @@ export function statusFacts(database, input) {
97
97
  UNION SELECT end_sequence FROM turns WHERE sequence = ? AND end_sequence IS NOT NULL
98
98
  UNION SELECT sequence FROM calls WHERE turn_sequence = ?
99
99
  UNION SELECT sequence FROM activity WHERE turn_sequence = ?
100
- UNION SELECT MAX(end_sequence) FROM turns
100
+ UNION SELECT tells.sequence FROM tells
101
+ JOIN tell_deliveries ON tell_deliveries.tell_id = tells.id
102
+ WHERE tell_deliveries.turn_sequence = ?
101
103
  UNION SELECT sequence FROM tells WHERE id = ?
102
104
  UNION SELECT sequence FROM tells WHERE ? AND ${tellStateSql} = 'pending'
103
- UNION SELECT MAX(sequence) FROM tells WHERE ? AND ${tellStateSql} = 'told'
104
- UNION SELECT MAX(sequence) FROM tells
105
105
  ) SELECT timeline.sequence, timeline.kind FROM timeline JOIN selected USING(sequence) ORDER BY sequence`)
106
- .all(frontier?.sequence ?? null, frontier?.sequence ?? null, frontier?.sequence ?? null, frontier?.sequence ?? null, input.admittedTellId ?? null, input.aperture === "monitoring" || input.admittedTellId === undefined ? 1 : 0, input.aperture === "monitoring" ? 1 : 0);
106
+ .all(frontier?.sequence ?? null, frontier?.sequence ?? null, frontier?.sequence ?? null, frontier?.sequence ?? null, frontier?.sequence ?? null, input.admittedTellId ?? null, input.aperture === "monitoring" || input.admittedTellId === undefined ? 1 : 0);
107
107
  return decodeTimelineRows(database, rows);
108
108
  }
109
109
  export function activityFactSlice(database) {
@@ -101,18 +101,41 @@ function frontierReportedChanges(ledger) {
101
101
  function isPendingTell(row) {
102
102
  return row.kind === "tell" && row.state === "pending";
103
103
  }
104
- function isToldTell(row) {
105
- return row.kind === "tell" && row.state === "told";
106
- }
107
104
  function isActiveTool(row) {
108
105
  return row.kind === "tool" && row.state === "active";
109
106
  }
107
+ function isProtectedCurrentTurnActivity(row) {
108
+ return (row.kind === "said" ||
109
+ (row.kind === "tool" && row.call.kind === "fileChange" && row.state !== "active" && row.state !== "unsettled"));
110
+ }
111
+ function isSettledTellDeliveredTo(row, turnSequence) {
112
+ return (row.kind === "tell" &&
113
+ row.state === "told" &&
114
+ row.deliveries.some((delivery) => delivery.turnSequence === turnSequence));
115
+ }
116
+ function currentTurnWindow(ledger) {
117
+ const turn = ledger.openTurn;
118
+ if (turn === undefined)
119
+ return [];
120
+ const turnRows = new Set(turn.rows);
121
+ return ledger.rows.filter((row) => turnRows.has(row) || isSettledTellDeliveredTo(row, turn.turn.turnSequence));
122
+ }
123
+ function openingInput(ledger) {
124
+ const turn = ledger.openTurn;
125
+ if (turn === undefined)
126
+ return undefined;
127
+ const call = turn.rows.find((row) => row.kind === "call");
128
+ if (call !== undefined)
129
+ return call;
130
+ return ledger.rows.find((row) => row.kind === "tell" &&
131
+ row.state === "told" &&
132
+ row.deliveries.some((delivery) => delivery.route === "launch" && delivery.turnSequence === turn.turn.turnSequence));
133
+ }
110
134
  export function selectSnapshot(ledger, input) {
111
135
  const budget = input.budget ?? { tail: DEFAULT_TAIL, voice: DEFAULT_VOICE };
112
136
  if (ledger.turns.length === 0 && !ledger.rows.some((row) => row.kind === "tell"))
113
137
  return { snapshot: { kind: "unborn", entries: [], omitted: 0, ...EMPTY_REPORTED }, ordinaryCount: 0 };
114
138
  const pending = ledger.rows.filter(isPendingTell);
115
- const latestTold = input.aperture === "monitoring" ? ledger.rows.findLast(isToldTell) : undefined;
116
139
  const admittedTell = input.aperture === "receipt" && input.admittedTellId !== undefined
117
140
  ? ledger.rows.find((row) => row.kind === "tell" && row.tellId === input.admittedTellId)
118
141
  : undefined;
@@ -120,11 +143,17 @@ export function selectSnapshot(ledger, input) {
120
143
  ? admittedTell === undefined
121
144
  ? []
122
145
  : [admittedTell]
123
- : [...pending, ...(latestTold === undefined ? [] : [latestTold])];
146
+ : pending;
124
147
  const reported = frontierReportedChanges(ledger);
125
148
  if (ledger.openTurn !== undefined) {
126
- const window = ledger.openTurn.rows;
127
- const pinSet = new Set([...window.filter(isActiveTool), ...pins]);
149
+ const window = currentTurnWindow(ledger);
150
+ const opening = openingInput(ledger);
151
+ const pinSet = new Set([
152
+ ...window.filter(isActiveTool),
153
+ ...window.filter(isProtectedCurrentTurnActivity),
154
+ ...pins,
155
+ ...(opening === undefined ? [] : [opening]),
156
+ ]);
128
157
  const ordinary = window.filter((row) => !pinSet.has(row));
129
158
  const tailCount = Math.max(0, budget.tail);
130
159
  const tail = tailCount === 0 ? [] : ordinary.slice(-tailCount);
@@ -140,6 +169,7 @@ export function selectSnapshot(ledger, input) {
140
169
  turn: ledger.openTurn.turn,
141
170
  entries: assembleEntries(ledger, window, selected),
142
171
  omitted,
172
+ ...(opening === undefined ? {} : { openingSequence: opening.sequence }),
143
173
  ...reported,
144
174
  },
145
175
  ordinaryCount: tail.length + voice.length,
@@ -733,6 +733,7 @@ export declare const activitySnapshotSchema: z.ZodUnion<readonly [z.ZodObject<{
733
733
  }, z.core.$strict>]>;
734
734
  }, z.core.$strict>]>>>;
735
735
  omitted: z.ZodNumber;
736
+ openingSequence: z.ZodOptional<z.ZodNumber>;
736
737
  }, z.core.$strict>, z.ZodObject<{
737
738
  reportedChanges: z.ZodReadonly<z.ZodArray<z.ZodObject<{
738
739
  sequence: z.ZodNumber;
@@ -3,6 +3,7 @@ import { projectTell } from "./heart/facts.js";
3
3
  import { z } from "zod";
4
4
  const nonblankTextSchema = z.string().refine((value) => value.trim() !== "");
5
5
  const countSchema = z.number().int().nonnegative();
6
+ const sequenceSchema = z.number().int().positive();
6
7
  const timestampSchema = z.string().refine((value) => Number.isFinite(Date.parse(value)), "expected timestamp");
7
8
  const diffstatSchema = z.object({ added: countSchema, removed: countSchema }).strict();
8
9
  const fileChangeSchema = z
@@ -162,6 +163,28 @@ const reportedChangeFields = {
162
163
  reportedChanges: z.array(reportedFileChangeSchema).readonly(),
163
164
  reportedChangesOmitted: countSchema,
164
165
  };
166
+ const openActivitySnapshotSchema = z
167
+ .object({
168
+ kind: z.literal("open"),
169
+ turn: turnRowSchema,
170
+ entries: z.array(snapshotEntrySchema(openSnapshotRowSchema)).readonly(),
171
+ omitted: countSchema,
172
+ openingSequence: sequenceSchema.optional(),
173
+ ...reportedChangeFields,
174
+ })
175
+ .strict()
176
+ .superRefine((snapshot, context) => {
177
+ if (snapshot.openingSequence === undefined)
178
+ return;
179
+ const matches = snapshot.entries.filter((entry) => entry.kind === "row" && entry.row.sequence === snapshot.openingSequence);
180
+ if (matches.length !== 1) {
181
+ context.addIssue({
182
+ code: "custom",
183
+ path: ["openingSequence"],
184
+ message: "openingSequence must name exactly one snapshot entry",
185
+ });
186
+ }
187
+ });
165
188
  export const activitySnapshotSchema = z.union([
166
189
  z
167
190
  .object({
@@ -171,15 +194,7 @@ export const activitySnapshotSchema = z.union([
171
194
  ...reportedChangeFields,
172
195
  })
173
196
  .strict(),
174
- z
175
- .object({
176
- kind: z.literal("open"),
177
- turn: turnRowSchema,
178
- entries: z.array(snapshotEntrySchema(openSnapshotRowSchema)).readonly(),
179
- omitted: countSchema,
180
- ...reportedChangeFields,
181
- })
182
- .strict(),
197
+ openActivitySnapshotSchema,
183
198
  z
184
199
  .object({
185
200
  kind: z.literal("idle"),
@@ -1,8 +1,9 @@
1
- import type { createAgentSession, DefaultResourceLoader, getAgentDir, ModelRuntime, SessionManager, CreateAgentSessionOptions } from "@earendil-works/pi-coding-agent";
1
+ import type { createAgentSession, createBashToolDefinition, DefaultResourceLoader, getAgentDir, ModelRuntime, SessionManager, CreateAgentSessionOptions } from "@earendil-works/pi-coding-agent";
2
2
  import type { ProviderExecution } from "../../provider-recipe.js";
3
3
  import { type ProviderAdapter } from "../../provider.js";
4
4
  export type PiSdk = Readonly<{
5
5
  createAgentSession(options?: CreateAgentSessionOptions): ReturnType<typeof createAgentSession>;
6
+ createBashToolDefinition: typeof createBashToolDefinition;
6
7
  DefaultResourceLoader: typeof DefaultResourceLoader;
7
8
  getAgentDir: typeof getAgentDir;
8
9
  ModelRuntime: typeof ModelRuntime;
@@ -54,7 +54,7 @@ async function piCreateOptions(sdk, input) {
54
54
  const customTools = input.requests === undefined || input.options.readonly === true
55
55
  ? undefined
56
56
  : [
57
- (await import("@earendil-works/pi-coding-agent")).createBashToolDefinition(input.cwd, {
57
+ sdk.createBashToolDefinition(input.cwd, {
58
58
  spawnHook: (context) => ({
59
59
  ...context,
60
60
  env: akumaExecutionEnvironment(context.env, {}, input.requests.dir, ["PI_SESSION_ID", "PI_SESSION_FILE"]),
@@ -228,6 +228,7 @@ async function loadPiSdk() {
228
228
  const sdk = await import("@earendil-works/pi-coding-agent");
229
229
  return {
230
230
  createAgentSession: sdk.createAgentSession,
231
+ createBashToolDefinition: sdk.createBashToolDefinition,
231
232
  DefaultResourceLoader: sdk.DefaultResourceLoader,
232
233
  getAgentDir: sdk.getAgentDir,
233
234
  ModelRuntime: sdk.ModelRuntime,