@astrosheep/keiyaku 4.5.11 → 4.5.13

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 (272) hide show
  1. package/build/integrations/marketplace/plugins/keiyaku/skills/keiyaku-workflow/SKILL.md +135 -377
  2. package/build/integrations/pi/keiyaku.ts +11 -116
  3. package/build/src/akuma/akuma-errors.d.ts +1 -0
  4. package/build/src/akuma/akuma-errors.js +1 -0
  5. package/build/src/akuma/akuma-handle.d.ts +1 -0
  6. package/build/src/akuma/akuma-handle.js +6 -7
  7. package/build/src/akuma/akuma-product.d.ts +6 -8
  8. package/build/src/akuma/akuma-product.js +141 -57
  9. package/build/src/akuma/akuma.d.ts +5 -7
  10. package/build/src/akuma/akuma.js +1 -22
  11. package/build/src/akuma/archetype.d.ts +6 -5
  12. package/build/src/akuma/archetype.js +137 -38
  13. package/build/src/akuma/body.d.ts +5 -5
  14. package/build/src/akuma/body.js +223 -63
  15. package/build/src/akuma/call-input.d.ts +4 -0
  16. package/build/src/akuma/call-input.js +21 -0
  17. package/build/src/akuma/call-request.d.ts +5 -8
  18. package/build/src/akuma/call-request.js +28 -73
  19. package/build/src/akuma/fleet-execution.d.ts +26 -0
  20. package/build/src/akuma/fleet-execution.js +109 -0
  21. package/build/src/{library/fleet-result.d.ts → akuma/fleet-observation.d.ts} +13 -57
  22. package/build/src/{library/fleet-result.js → akuma/fleet-observation.js} +4 -25
  23. package/build/src/akuma/fleet-request.d.ts +122 -0
  24. package/build/src/akuma/fleet-request.js +168 -0
  25. package/build/src/akuma/heart/facts.d.ts +23 -0
  26. package/build/src/akuma/heart/facts.js +7 -0
  27. package/build/src/akuma/heart/index.d.ts +43 -4
  28. package/build/src/akuma/heart/index.js +107 -7
  29. package/build/src/akuma/heart/request-authority.d.ts +2 -0
  30. package/build/src/akuma/heart/request-authority.js +28 -3
  31. package/build/src/akuma/heart/request-rows.d.ts +2 -0
  32. package/build/src/akuma/heart/request-rows.js +24 -5
  33. package/build/src/akuma/heart/rows.d.ts +4 -1
  34. package/build/src/akuma/heart/rows.js +46 -19
  35. package/build/src/akuma/heart/schema.d.ts +1 -1
  36. package/build/src/akuma/heart/schema.js +28 -6
  37. package/build/src/akuma/heart/storage.d.ts +1 -1
  38. package/build/src/akuma/heart/tells.d.ts +29 -0
  39. package/build/src/akuma/heart/tells.js +136 -5
  40. package/build/src/akuma/heart/timeline.js +9 -3
  41. package/build/src/akuma/projection-read.js +36 -3
  42. package/build/src/akuma/projection.js +3 -4
  43. package/build/src/akuma/provider.d.ts +4 -0
  44. package/build/src/akuma/provider.js +56 -3
  45. package/build/src/akuma/providers/claude/index.js +3 -0
  46. package/build/src/akuma/providers/codex-app-server/index.js +14 -12
  47. package/build/src/akuma/providers/pi/index.js +7 -1
  48. package/build/src/akuma/publication.js +65 -49
  49. package/build/src/akuma/request-lifecycle.d.ts +5 -4
  50. package/build/src/akuma/request-lifecycle.js +29 -16
  51. package/build/src/akuma/request-rendezvous.d.ts +15 -12
  52. package/build/src/akuma/request-rendezvous.js +80 -51
  53. package/build/src/akuma/request-serve.d.ts +1 -2
  54. package/build/src/akuma/request-serve.js +94 -39
  55. package/build/src/akuma/request-wire.d.ts +57 -26
  56. package/build/src/akuma/request-wire.js +64 -25
  57. package/build/src/akuma/turn-drive.d.ts +5 -4
  58. package/build/src/akuma/turn-drive.js +27 -11
  59. package/build/src/akuma-body.d.ts +4 -6
  60. package/build/src/akuma-body.js +19 -17
  61. package/build/src/bounded-list.d.ts +8 -0
  62. package/build/src/bounded-list.js +14 -0
  63. package/build/src/cli/accepted.d.ts +1 -1
  64. package/build/src/cli/accepted.js +1 -0
  65. package/build/src/cli/commands/akuma-invoke.js +1 -25
  66. package/build/src/cli/commands/contract-help.d.ts +5 -2
  67. package/build/src/cli/commands/contract-help.js +62 -12
  68. package/build/src/cli/commands/contract-invoke.d.ts +5 -0
  69. package/build/src/cli/commands/contract-invoke.js +26 -1
  70. package/build/src/cli/commands/contract.js +50 -18
  71. package/build/src/cli/commands/task-invoke.js +10 -2
  72. package/build/src/cli/commands/task-query.js +3 -4
  73. package/build/src/cli/coordinates.d.ts +13 -0
  74. package/build/src/cli/coordinates.js +38 -24
  75. package/build/src/cli/draft.d.ts +1 -1
  76. package/build/src/cli/invoke.js +14 -1
  77. package/build/src/cli/render/akuma-activity.d.ts +0 -1
  78. package/build/src/cli/render/akuma-activity.js +5 -38
  79. package/build/src/cli/render/akuma.js +4 -1
  80. package/build/src/cli/render/audit.js +4 -1
  81. package/build/src/cli/render/catalog.js +13 -39
  82. package/build/src/cli/render/contract.js +4 -14
  83. package/build/src/cli/render/kanshi-akuma.js +3 -8
  84. package/build/src/cli/render/kanshi.js +8 -34
  85. package/build/src/cli/render/nuke.js +7 -3
  86. package/build/src/cli/render/receipt.d.ts +1 -0
  87. package/build/src/cli/render/receipt.js +8 -0
  88. package/build/src/cli/render/refusal.js +4 -3
  89. package/build/src/cli/render/task.js +4 -37
  90. package/build/src/cli/render/terminal.d.ts +2 -6
  91. package/build/src/cli/render/terminal.js +4 -9
  92. package/build/src/cli/result.d.ts +2 -1
  93. package/build/src/contract-guidance.js +13 -3
  94. package/build/src/contract-worktree.d.ts +1 -0
  95. package/build/src/contract-worktree.js +26 -2
  96. package/build/src/core/facts/codec.d.ts +2 -1
  97. package/build/src/core/facts/codec.js +3 -0
  98. package/build/src/core/facts/gate.d.ts +2 -1
  99. package/build/src/core/facts/gate.js +63 -0
  100. package/build/src/core/verbs/abandon.d.ts +2 -1
  101. package/build/src/core/verbs/abandon.js +16 -0
  102. package/build/src/core/verbs/amend.d.ts +2 -1
  103. package/build/src/core/verbs/amend.js +20 -0
  104. package/build/src/core/verbs/arc.d.ts +2 -1
  105. package/build/src/core/verbs/arc.js +16 -0
  106. package/build/src/core/verbs/attestation.d.ts +2 -1
  107. package/build/src/core/verbs/attestation.js +16 -0
  108. package/build/src/core/verbs/bind.d.ts +2 -1
  109. package/build/src/core/verbs/bind.js +16 -0
  110. package/build/src/core/verbs/deliver.d.ts +2 -1
  111. package/build/src/core/verbs/deliver.js +16 -0
  112. package/build/src/core/verbs/placement.d.ts +2 -1
  113. package/build/src/core/verbs/placement.js +58 -1
  114. package/build/src/dispatch/association.d.ts +13 -0
  115. package/build/src/dispatch/association.js +20 -0
  116. package/build/src/dispatch/index.d.ts +6 -0
  117. package/build/src/dispatch/index.js +27 -3
  118. package/build/src/git/hooks.d.ts +9 -1
  119. package/build/src/git/hooks.js +79 -1
  120. package/build/src/git/nuke.d.ts +2 -1
  121. package/build/src/git/nuke.js +6 -11
  122. package/build/src/git/private-state-seat.d.ts +26 -1
  123. package/build/src/git/private-state-seat.js +78 -13
  124. package/build/src/git/process.d.ts +2 -0
  125. package/build/src/git/reconcile.d.ts +1 -2
  126. package/build/src/git/reconcile.js +0 -6
  127. package/build/src/git/repository.d.ts +0 -2
  128. package/build/src/git/repository.js +27 -25
  129. package/build/src/git/result-codec.d.ts +29 -0
  130. package/build/src/git/result-codec.js +306 -0
  131. package/build/src/git/target-placement.js +2 -8
  132. package/build/src/git/workspace.d.ts +2 -0
  133. package/build/src/git/workspace.js +4 -0
  134. package/build/src/index.d.ts +1 -0
  135. package/build/src/kanshi/fleet.js +1 -1
  136. package/build/src/kanshi/read.js +55 -28
  137. package/build/src/kanshi/report.d.ts +4 -3
  138. package/build/src/kanshi/select.js +14 -1
  139. package/build/src/library/address.d.ts +1 -0
  140. package/build/src/library/address.js +4 -7
  141. package/build/src/library/akuma-creation.d.ts +5 -3
  142. package/build/src/library/akuma-creation.js +90 -33
  143. package/build/src/library/audit.js +2 -1
  144. package/build/src/library/catalog.d.ts +10 -2
  145. package/build/src/library/catalog.js +65 -25
  146. package/build/src/library/composition.d.ts +1 -0
  147. package/build/src/library/composition.js +1 -0
  148. package/build/src/library/configuration.d.ts +4 -21
  149. package/build/src/library/configuration.js +2 -158
  150. package/build/src/library/continuation.d.ts +20 -1
  151. package/build/src/library/continuation.js +36 -0
  152. package/build/src/library/contract-bind.d.ts +1 -0
  153. package/build/src/library/contract-bind.js +16 -5
  154. package/build/src/library/contract-forwarding.d.ts +2 -2
  155. package/build/src/library/contract-forwarding.js +18 -4
  156. package/build/src/library/contract-handle.js +4 -3
  157. package/build/src/library/contract-operations.d.ts +5 -9
  158. package/build/src/library/contract-operations.js +34 -47
  159. package/build/src/library/contract.d.ts +11 -2
  160. package/build/src/library/contract.js +21 -1
  161. package/build/src/library/delivery.d.ts +4 -1
  162. package/build/src/library/delivery.js +47 -0
  163. package/build/src/library/fleet.d.ts +4 -136
  164. package/build/src/library/fleet.js +31 -315
  165. package/build/src/library/keiyaku.d.ts +7 -3
  166. package/build/src/library/keiyaku.js +1 -0
  167. package/build/src/library/mutation.d.ts +431 -3
  168. package/build/src/library/mutation.js +192 -19
  169. package/build/src/library/nuke.d.ts +7 -1
  170. package/build/src/library/nuke.js +18 -4
  171. package/build/src/library/reconcile.d.ts +14 -0
  172. package/build/src/library/reconcile.js +22 -8
  173. package/build/src/library/refusal.d.ts +9 -1
  174. package/build/src/library/refusal.js +76 -0
  175. package/build/src/library/region.d.ts +0 -8
  176. package/build/src/library/region.js +1 -29
  177. package/build/src/library/result-codec.d.ts +2 -0
  178. package/build/src/library/result-codec.js +12 -0
  179. package/build/src/markdown/lex.js +9 -2
  180. package/build/src/markdown/types.d.ts +2 -1
  181. package/build/src/markdown/types.js +3 -1
  182. package/build/src/plugin/akuma-signals.d.ts +12 -0
  183. package/build/src/plugin/akuma-signals.js +14 -0
  184. package/build/src/plugin/public.d.ts +51 -0
  185. package/build/src/plugin/public.js +1 -0
  186. package/build/src/plugin/runtime.d.ts +14 -0
  187. package/build/src/plugin/runtime.js +357 -0
  188. package/build/src/protocol/amend.js +3 -3
  189. package/build/src/protocol/attempt.d.ts +2 -1
  190. package/build/src/protocol/attempt.js +1 -1
  191. package/build/src/protocol/audit.d.ts +1 -1
  192. package/build/src/protocol/audit.js +1 -1
  193. package/build/src/protocol/bind.d.ts +3 -1
  194. package/build/src/protocol/bind.js +26 -0
  195. package/build/src/protocol/completion.js +0 -1
  196. package/build/src/protocol/deliver.d.ts +1 -1
  197. package/build/src/protocol/deliver.js +136 -57
  198. package/build/src/protocol/intent.d.ts +0 -1
  199. package/build/src/protocol/intent.js +0 -1
  200. package/build/src/protocol/operations.d.ts +9 -2
  201. package/build/src/protocol/operations.js +16 -1
  202. package/build/src/protocol/outcome.d.ts +3 -0
  203. package/build/src/protocol/outcome.js +17 -1
  204. package/build/src/protocol/placement.js +10 -3
  205. package/build/src/protocol/read/status.d.ts +8 -0
  206. package/build/src/protocol/read/status.js +49 -0
  207. package/build/src/protocol/reintegrate.js +10 -3
  208. package/build/src/protocol/result-codec.d.ts +22 -0
  209. package/build/src/protocol/result-codec.js +442 -0
  210. package/build/src/protocol/review.d.ts +1 -1
  211. package/build/src/protocol/review.js +81 -46
  212. package/build/src/protocol/run.d.ts +22 -2
  213. package/build/src/protocol/run.js +119 -29
  214. package/build/src/runtime/proc/lifecycle.js +1 -1
  215. package/build/src/runtime/proc/line-rpc.d.ts +7 -1
  216. package/build/src/runtime/proc/line-rpc.js +49 -9
  217. package/build/src/runtime/proc/run.d.ts +0 -1
  218. package/build/src/runtime/proc/run.js +1 -11
  219. package/build/src/runtime/proc/termination.d.ts +1 -0
  220. package/build/src/runtime/proc/termination.js +63 -18
  221. package/build/src/runtime/proc/windows-launch.exe +0 -0
  222. package/build/src/settings.d.ts +10 -0
  223. package/build/src/settings.js +88 -2
  224. package/build/src/settlement/result-codec.d.ts +2 -0
  225. package/build/src/settlement/result-codec.js +56 -0
  226. package/build/src/settlement/settle.d.ts +5 -12
  227. package/build/src/settlement/settle.js +60 -55
  228. package/build/src/task/board.d.ts +3 -0
  229. package/build/src/task/board.js +22 -4
  230. package/build/src/task/catalog.d.ts +13 -2
  231. package/build/src/task/catalog.js +59 -5
  232. package/build/src/task/compose.d.ts +4 -2
  233. package/build/src/task/compose.js +56 -30
  234. package/build/src/task/created-observation.d.ts +39 -0
  235. package/build/src/task/created-observation.js +26 -0
  236. package/build/src/task/identity.d.ts +6 -0
  237. package/build/src/task/identity.js +48 -7
  238. package/build/src/task/index.d.ts +4 -3
  239. package/build/src/task/index.js +19 -16
  240. package/build/src/task/input.d.ts +3 -1
  241. package/build/src/task/input.js +7 -4
  242. package/build/src/task/lifecycle-operations.d.ts +6 -0
  243. package/build/src/task/lifecycle-operations.js +116 -0
  244. package/build/src/task/mutation-result.d.ts +762 -20
  245. package/build/src/task/mutation-result.js +34 -9
  246. package/build/src/task/mutation.d.ts +4 -9
  247. package/build/src/task/mutation.js +17 -52
  248. package/build/src/task/operation-types.d.ts +114 -0
  249. package/build/src/task/operation-types.js +1 -0
  250. package/build/src/task/operations.d.ts +6 -113
  251. package/build/src/task/operations.js +75 -117
  252. package/build/src/task/query.d.ts +8 -11
  253. package/build/src/task/query.js +7 -14
  254. package/build/src/task/store.d.ts +11 -1
  255. package/build/src/task/store.js +169 -55
  256. package/build/src/task/view.d.ts +4 -0
  257. package/build/src/task/view.js +7 -0
  258. package/build/src/verification/declaration.d.ts +1 -0
  259. package/build/src/verification/declaration.js +18 -1
  260. package/build/src/verification/execution.d.ts +0 -1
  261. package/build/src/verification/execution.js +5 -5
  262. package/build/src/world.d.ts +0 -2
  263. package/build/src/world.js +3 -7
  264. package/package.json +17 -5
  265. package/build/src/cli/square-edge.d.ts +0 -7
  266. package/build/src/cli/square-edge.js +0 -159
  267. package/build/src/git/ref-migration.d.ts +0 -22
  268. package/build/src/git/ref-migration.js +0 -55
  269. package/build/src/library/contract-forwarding-reconciliation-result.d.ts +0 -102
  270. package/build/src/library/contract-forwarding-reconciliation-result.js +0 -92
  271. package/build/src/library/contract-forwarding-result.d.ts +0 -4270
  272. package/build/src/library/contract-forwarding-result.js +0 -521
@@ -1,91 +1,14 @@
1
1
  import { existsSync } from "node:fs";
2
2
  import { dirname, resolve } from "node:path";
3
3
  import { fileURLToPath } from "node:url";
4
- import type { ExtensionAPI, ExtensionCommandContext, ExtensionContext, Theme } from "@earendil-works/pi-coding-agent";
5
-
6
- type Section = Readonly<{ kind?: string; value?: Readonly<{ rows?: readonly Record<string, unknown>[] }> }>;
7
- type Report = Readonly<{
8
- contracts?: Section;
9
- tasks?: Section;
10
- akuma?: Section;
11
- }>;
4
+ import type { ExtensionAPI, Theme } from "@earendil-works/pi-coding-agent";
12
5
 
13
6
  type ExecResult = Readonly<{ stdout: string; stderr: string; code: number }>;
14
7
 
15
- const REFRESH_TIMEOUT_MS = 10_000;
16
-
17
- function rows(section: Section | undefined): readonly Record<string, unknown>[] {
18
- return section?.kind === "present" && Array.isArray(section.value?.rows) ? section.value.rows : [];
19
- }
20
-
21
- function failed(section: Section | undefined): boolean {
22
- return section?.kind === "failed";
23
- }
24
-
25
- function contractNeedsAttention(row: Record<string, unknown>): boolean {
26
- const gates = row.gates;
27
- if (typeof gates === "object" && gates !== null && "reports" in gates && Array.isArray(gates.reports)) {
28
- for (const report of gates.reports) {
29
- if (
30
- typeof report === "object" &&
31
- report !== null &&
32
- "current" in report &&
33
- typeof report.current === "object" &&
34
- report.current !== null &&
35
- "kind" in report.current &&
36
- report.current.kind === "attested" &&
37
- "verdict" in report.current &&
38
- report.current.verdict === "unsatisfied"
39
- )
40
- return true;
41
- }
42
- }
43
- return row.targetLag !== undefined &&
44
- typeof row.targetLag === "object" &&
45
- row.targetLag !== null &&
46
- "kind" in row.targetLag
47
- ? row.targetLag.kind === "unknown"
48
- : false;
49
- }
50
-
51
- function taskNeedsAttention(row: Record<string, unknown>): boolean {
52
- return row.disposition === "blocked";
53
- }
54
-
55
- function akumaNeedsAttention(row: Record<string, unknown>): boolean {
56
- return row.life === "stillborn" || row.life === "hung" || row.life === "untidy";
57
- }
58
-
59
- function summary(report: Report): string {
60
- const contractRows = rows(report.contracts);
61
- const taskRows = rows(report.tasks);
62
- const akumaRows = rows(report.akuma);
63
- const attention =
64
- Number(failed(report.contracts)) +
65
- Number(failed(report.tasks)) +
66
- Number(failed(report.akuma)) +
67
- contractRows.filter(contractNeedsAttention).length +
68
- taskRows.filter(taskNeedsAttention).length +
69
- akumaRows.filter(akumaNeedsAttention).length;
70
- const parts = [`Keiyaku · ${contractRows.length} contracts`, `${akumaRows.length} fleet`, `${taskRows.length} tasks`];
71
- if (attention > 0) parts.push(`! ${attention}`);
72
- return parts.join(" · ");
73
- }
74
-
75
- function parseReport(result: ExecResult): Report | undefined {
76
- if (result.code !== 0 || result.stdout.trim().length === 0) return undefined;
77
- try {
78
- const value: unknown = JSON.parse(result.stdout);
79
- return typeof value === "object" && value !== null ? (value as Report) : undefined;
80
- } catch {
81
- return undefined;
82
- }
83
- }
84
-
85
- async function run(pi: ExtensionAPI, args: readonly string[], timeout = REFRESH_TIMEOUT_MS): Promise<ExecResult> {
8
+ async function runStatus(pi: ExtensionAPI): Promise<ExecResult> {
86
9
  const bundledCli = resolve(dirname(fileURLToPath(import.meta.url)), "../../src/cli/index.js");
87
- if (existsSync(bundledCli)) return await pi.exec(process.execPath, [bundledCli, ...args], { timeout });
88
- return await pi.exec("keiyaku", [...args], { timeout });
10
+ if (existsSync(bundledCli)) return await pi.exec(process.execPath, [bundledCli, "status"]);
11
+ return await pi.exec("keiyaku", ["status"]);
89
12
  }
90
13
 
91
14
  function lineForWidth(value: string, width: number): string {
@@ -119,49 +42,21 @@ function overlay(lines: readonly string[], theme: Theme, done: () => void) {
119
42
  }
120
43
 
121
44
  export default function keiyakuExtension(pi: ExtensionAPI): void {
122
- let refreshInFlight: Promise<void> | undefined;
123
-
124
- const refresh = async (ctx: Pick<ExtensionContext, "hasUI" | "ui">): Promise<void> => {
125
- if (!ctx.hasUI) return;
126
- refreshInFlight ??= (async () => {
127
- try {
128
- const result = await run(pi, ["status", "--json"]);
129
- const report = parseReport(result);
130
- if (report === undefined) {
131
- const diagnostic = result.stderr.trim() || "status unavailable";
132
- ctx.ui.setWidget("keiyaku", [`Keiyaku · ${lineForWidth(diagnostic, 100)}`]);
133
- } else ctx.ui.setWidget("keiyaku", [summary(report)]);
134
- } catch {
135
- ctx.ui.setWidget("keiyaku", ["Keiyaku · status unavailable"]);
136
- }
137
- })().finally(() => {
138
- refreshInFlight = undefined;
139
- });
140
- await refreshInFlight;
141
- };
142
-
143
- const scheduleRefresh = (ctx: Pick<ExtensionContext, "hasUI" | "ui">): void => {
144
- void refresh(ctx).catch(() => undefined);
145
- };
146
-
147
- pi.on("session_start", (_event, ctx) => {
148
- scheduleRefresh(ctx);
149
- });
150
- pi.on("agent_end", (_event, ctx) => {
151
- scheduleRefresh(ctx);
152
- });
153
45
  pi.registerCommand("keiyaku", {
154
46
  description: "Open the Kanshi world status",
155
47
  handler: async (_args, ctx) => {
156
48
  if (!ctx.hasUI) return;
157
- const result = await run(pi, ["status"]);
158
- const body =
159
- result.code === 0 ? result.stdout.trimEnd().split("\n") : [result.stderr.trim() || "status unavailable"];
49
+ let body: string[];
50
+ try {
51
+ const result = await runStatus(pi);
52
+ body = result.code === 0 ? result.stdout.trimEnd().split("\n") : [result.stderr.trim() || "status unavailable"];
53
+ } catch {
54
+ body = ["status unavailable"];
55
+ }
160
56
  await ctx.ui.custom<void>((_tui, theme, _keybindings, done) => overlay(body, theme, done), {
161
57
  overlay: true,
162
58
  overlayOptions: { anchor: "center", width: 100, maxHeight: 40 },
163
59
  });
164
- await refresh(ctx);
165
60
  },
166
61
  });
167
62
  }
@@ -1,4 +1,5 @@
1
1
  import type { AkuId } from "./identity.js";
2
+ export { AkumaBusyError } from "./heart/facts.js";
2
3
  export declare class AkumaNotBornError extends Error {
3
4
  readonly id: AkuId;
4
5
  readonly kind = "akuma-not-born";
@@ -1,3 +1,4 @@
1
+ export { AkumaBusyError } from "./heart/facts.js";
1
2
  export class AkumaNotBornError extends Error {
2
3
  id;
3
4
  kind = "akuma-not-born";
@@ -11,6 +11,7 @@ export declare class AkumaHandle {
11
11
  readonly [CALL_EXECUTION]?: AkumaCallExecution;
12
12
  constructor(id: AkuId, worldPath: WorldRoot, execution?: AkumaCallExecution);
13
13
  private get paths();
14
+ private projectHistoryTurns;
14
15
  private exactHistory;
15
16
  status(): Promise<AkumaStatus>;
16
17
  history(input?: Readonly<{
@@ -83,6 +83,10 @@ export class AkumaHandle {
83
83
  get paths() {
84
84
  return pathsForAkuId(this.worldPath, this.id);
85
85
  }
86
+ async projectHistoryTurns() {
87
+ const slice = await activitySlice(this.paths);
88
+ return projectTurns(slice.rows, { lowestRetained: slice.lowestRetained, highest: slice.highest });
89
+ }
86
90
  async exactHistory(input) {
87
91
  if (typeof input.id !== "string" || input.id.trim() === "")
88
92
  throw new TypeError("Akuma history id must be a nonblank string");
@@ -90,8 +94,7 @@ export class AkumaHandle {
90
94
  throw new TypeError("Akuma history id cannot be combined with before, since, or limit");
91
95
  if (parsePublicHistoryId(input.id) === null)
92
96
  throw new TypeError("Akuma history id must match turn/<positive safe integer>");
93
- const slice = await activitySlice(this.paths);
94
- return selectExactHistory(projectTurns(slice.rows, { lowestRetained: slice.lowestRetained, highest: slice.highest }).rows, input.id);
97
+ return selectExactHistory((await this.projectHistoryTurns()).rows, input.id);
95
98
  }
96
99
  async status() {
97
100
  return (await bornStatus(this.paths, this.id, { aperture: "monitoring" })).status;
@@ -114,11 +117,7 @@ export class AkumaHandle {
114
117
  if (!Number.isSafeInteger(limit) || limit <= 0 || limit > 5_000) {
115
118
  throw new TypeError("Akuma history limit must be a positive safe integer no greater than 5000");
116
119
  }
117
- const slice = await activitySlice(this.paths);
118
- return selectHistory(projectTurns(slice.rows, {
119
- lowestRetained: slice.lowestRetained,
120
- highest: slice.highest,
121
- }), {
120
+ return selectHistory(await this.projectHistoryTurns(), {
122
121
  ...(input.before === undefined ? {} : { before: input.before }),
123
122
  ...(input.since === undefined ? {} : { since: input.since }),
124
123
  limit,
@@ -1,5 +1,5 @@
1
1
  import { AkumaHandle } from "./akuma-handle.js";
2
- import type { AkumaCallContext, AkumaCallInput, AkumaConfiguration, AkumaList, AkumaListInput } from "./akuma.js";
2
+ import type { AkumaCallContext, AkumaCallInput, AkumaCompleteList, AkumaConfiguration, AkumaList, AkumaListInput } from "./akuma.js";
3
3
  import { CALL_WITH_CONTEXT } from "./akuma-product-symbols.js";
4
4
  import type { WorldRoot } from "../world.js";
5
5
  import type { BodyLaunch } from "./body.js";
@@ -31,6 +31,8 @@ export type RequestedAkumaCall = Readonly<{
31
31
  }>;
32
32
  export type AkumaBornCall = BornAkumaCall | RequestedAkumaCall;
33
33
  type AkumaCallLaunchInput = AkumaCallInput;
34
+ export declare const PAGE_POOL_SIZE = 16;
35
+ export declare function boundedMap<Value, Result>(values: readonly Value[], mapper: (value: Value) => Promise<Result>): Promise<readonly Result[]>;
34
36
  export declare class Akuma {
35
37
  private readonly path;
36
38
  private readonly configuration;
@@ -43,16 +45,12 @@ export declare class Akuma {
43
45
  call(input: AkumaCallLaunchInput): Promise<AkumaHandle>;
44
46
  beginCall(input: AkumaCallLaunchInput, context: AkumaCallContext): Promise<AkumaBornCall>;
45
47
  finishCall(born: AkumaBornCall, completion?: Readonly<{
46
- participantName?: string;
47
48
  contractId?: string;
48
49
  }>): Promise<AkumaHandle>;
49
50
  [CALL_WITH_CONTEXT](input: AkumaCallLaunchInput, context: AkumaCallContext): Promise<AkumaHandle>;
51
+ listComplete(input?: Readonly<{
52
+ archetype?: string;
53
+ }>): Promise<AkumaCompleteList>;
50
54
  list(input?: AkumaListInput): Promise<AkumaList>;
51
55
  }
52
- export declare function callAkumaWithContext(akuma: Akuma, input: AkumaCallLaunchInput, context: AkumaCallContext): Promise<AkumaHandle>;
53
- export declare function beginAkumaCall(akuma: Akuma, input: AkumaCallLaunchInput, context: AkumaCallContext): Promise<AkumaBornCall>;
54
- export declare function finishAkumaCall(akuma: Akuma, born: AkumaBornCall, completion?: Readonly<{
55
- participantName?: string;
56
- contractId?: string;
57
- }>): Promise<AkumaHandle>;
58
56
  export {};
@@ -1,8 +1,9 @@
1
1
  import { randomUUID } from "node:crypto";
2
- import { readdir, realpath, stat } from "node:fs/promises";
3
- import { resolve } from "node:path";
2
+ import { readdir, stat } from "node:fs/promises";
3
+ import { boundedListLimit, projectBoundedList } from "../bounded-list.js";
4
4
  import { AkumaHandle } from "./akuma-handle.js";
5
5
  import { CALL_WITH_CONTEXT } from "./akuma-product-symbols.js";
6
+ import { callReadonly, canonicalBirthCwd } from "./call-input.js";
6
7
  import { fleetListRow, readAkumaBirthCwd } from "./akuma-observe.js";
7
8
  import { akuIdFromDirectoryName, akumaPaths, akumaRunRoot, archetypeName, parseAkuId } from "./identity.js";
8
9
  import { loadArchetype, listArchetypes as readArchetypes } from "./archetype.js";
@@ -12,24 +13,98 @@ import { requestForwardedAkumaCall } from "./call-request.js";
12
13
  import { executionChannel } from "./requests.js";
13
14
  import { decodeAllowedActions, unionAllowedActions } from "./allowed.js";
14
15
  import { settings as readSettings } from "../settings.js";
15
- function callReadonly(value) {
16
- if (value === undefined)
17
- return {};
18
- if (value !== true)
19
- throw new TypeError("Akuma call readonly must be true");
20
- return { readonly: true };
16
+ export const PAGE_POOL_SIZE = 16;
17
+ function activityAt(row) {
18
+ if (!("lifeAt" in row))
19
+ return null;
20
+ if (row.lifeAt === null)
21
+ return row.lastActivityAt;
22
+ if (row.lastActivityAt === null)
23
+ return row.lifeAt;
24
+ return row.lifeAt > row.lastActivityAt ? row.lifeAt : row.lastActivityAt;
21
25
  }
22
- async function canonicalBirthCwd(input) {
23
- const selected = resolve(input);
26
+ function compareActivity(left, right) {
27
+ if (left === right)
28
+ return 0;
29
+ if (left === null)
30
+ return 1;
31
+ if (right === null)
32
+ return -1;
33
+ return left > right ? -1 : 1;
34
+ }
35
+ function compareRows(left, right) {
36
+ const activity = compareActivity(activityAt(left), activityAt(right));
37
+ if (activity !== 0)
38
+ return activity;
39
+ return left.id < right.id ? -1 : left.id > right.id ? 1 : 0;
40
+ }
41
+ export async function boundedMap(values, mapper) {
42
+ const results = [];
43
+ let index = 0;
44
+ const worker = async () => {
45
+ for (;;) {
46
+ const selected = index++;
47
+ if (selected >= values.length)
48
+ return;
49
+ results[selected] = await mapper(values[selected]);
50
+ }
51
+ };
52
+ await Promise.all(Array.from({ length: Math.min(PAGE_POOL_SIZE, values.length) }, worker));
53
+ return results;
54
+ }
55
+ async function mtimeBound(paths) {
56
+ const read = async (path) => {
57
+ try {
58
+ return (await stat(path)).mtimeMs;
59
+ }
60
+ catch (error) {
61
+ return error.code === "ENOENT" ? 0 : Number.POSITIVE_INFINITY;
62
+ }
63
+ };
64
+ return Math.max(await read(paths.heart), await read(`${paths.heart}-wal`));
65
+ }
66
+ async function knownAkuma(path, selected) {
67
+ const runRoot = akumaRunRoot(path);
68
+ let names;
24
69
  try {
25
- const canonical = await realpath(selected);
26
- if (!(await stat(canonical)).isDirectory())
27
- throw new Error("not a directory");
28
- return canonical;
70
+ names = (await readdir(runRoot, { withFileTypes: true }))
71
+ .filter((entry) => entry.isDirectory())
72
+ .map((entry) => entry.name)
73
+ .sort();
29
74
  }
30
- catch {
31
- throw new Error(`cwd is not an existing directory: ${input}`);
75
+ catch (error) {
76
+ if (error.code === "ENOENT")
77
+ return { runRoot, rows: [] };
78
+ throw error;
32
79
  }
80
+ const rows = [];
81
+ for (const name of names) {
82
+ let physical;
83
+ try {
84
+ physical = akuIdFromDirectoryName(name);
85
+ }
86
+ catch {
87
+ continue;
88
+ }
89
+ if (selected !== undefined && physical.archetype !== selected)
90
+ continue;
91
+ rows.push({
92
+ id: physical.id,
93
+ paths: akumaPaths({ runRoot, archetype: physical.archetype, suffix: physical.suffix }),
94
+ });
95
+ }
96
+ return { runRoot, rows };
97
+ }
98
+ async function readableRows(rows) {
99
+ const loaded = await boundedMap(rows, async ({ id, paths }) => {
100
+ try {
101
+ return await fleetListRow(paths, id);
102
+ }
103
+ catch {
104
+ return null;
105
+ }
106
+ });
107
+ return [...loaded].filter((row) => row !== null);
33
108
  }
34
109
  export class Akuma {
35
110
  path;
@@ -47,7 +122,10 @@ export class Akuma {
47
122
  return new AkumaHandle(parseAkuId(input.id).id, this.path);
48
123
  }
49
124
  async listArchetypes() {
50
- return readArchetypes(this.configuration.home === undefined ? {} : { home: this.configuration.home });
125
+ return readArchetypes({
126
+ project: this.path,
127
+ ...(this.configuration.home === undefined ? {} : { home: this.configuration.home }),
128
+ });
51
129
  }
52
130
  async call(input) {
53
131
  return await this[CALL_WITH_CONTEXT](input, { initiatorCwd: process.cwd() });
@@ -57,7 +135,7 @@ export class Akuma {
57
135
  const name = archetypeName(input.archetype);
58
136
  const home = this.configuration.home === undefined ? {} : { home: this.configuration.home };
59
137
  const settings = this.configuration.settings ?? (await readSettings({ root: this.path, ...home }));
60
- const archetype = await loadArchetype({ name, ...home, settings, ...readonly });
138
+ const archetype = await loadArchetype({ name, project: this.path, ...home, settings, ...readonly });
61
139
  const allowed = input.allowed === undefined
62
140
  ? archetype.allowed
63
141
  : unionAllowedActions(archetype.allowed, decodeAllowedActions(input.allowed, "Akuma call allowed"));
@@ -134,53 +212,59 @@ export class Akuma {
134
212
  async [CALL_WITH_CONTEXT](input, context) {
135
213
  return await this.finishCall(await this.beginCall(input, context));
136
214
  }
215
+ async listComplete(input = {}) {
216
+ if (typeof input !== "object" || input === null || Array.isArray(input))
217
+ throw new TypeError("Akuma complete list input must be an object");
218
+ const unknown = Object.keys(input).find((key) => key !== "archetype");
219
+ if (unknown !== undefined)
220
+ throw new TypeError(`Akuma complete list input has unknown field: ${unknown}`);
221
+ const selected = input.archetype === undefined ? undefined : archetypeName(input.archetype);
222
+ const known = await knownAkuma(this.path, selected);
223
+ return {
224
+ observedAt: new Date().toISOString(),
225
+ rows: [...(await readableRows(known.rows))].sort(compareRows),
226
+ searched: [known.runRoot],
227
+ };
228
+ }
137
229
  async list(input = {}) {
138
230
  if (typeof input !== "object" || input === null || Array.isArray(input))
139
231
  throw new TypeError("Akuma list input must be an object");
140
- const unknown = Object.keys(input).find((key) => key !== "archetype");
232
+ const unknown = Object.keys(input).find((key) => key !== "archetype" && key !== "limit");
141
233
  if (unknown !== undefined)
142
234
  throw new TypeError(`Akuma list input has unknown field: ${unknown}`);
143
235
  const selected = input.archetype === undefined ? undefined : archetypeName(input.archetype);
236
+ const limit = boundedListLimit(input.limit);
144
237
  const observedAt = new Date().toISOString();
145
- const runRoot = akumaRunRoot(this.path);
146
- let names;
147
- try {
148
- names = (await readdir(runRoot, { withFileTypes: true }))
149
- .filter((entry) => entry.isDirectory())
150
- .map((entry) => entry.name)
151
- .sort();
152
- }
153
- catch (error) {
154
- if (error.code === "ENOENT")
155
- return { observedAt, rows: [], searched: [runRoot] };
156
- throw error;
157
- }
158
- const rows = [];
159
- for (const name of names) {
160
- let physical;
161
- try {
162
- physical = akuIdFromDirectoryName(name);
163
- }
164
- catch {
165
- continue;
238
+ const known = await knownAkuma(this.path, selected);
239
+ const candidatesWithBounds = await boundedMap(known.rows, async (row) => ({
240
+ ...row,
241
+ bound: await mtimeBound(row.paths),
242
+ }));
243
+ const candidates = [...candidatesWithBounds].sort((left, right) => right.bound - left.bound);
244
+ const readable = [];
245
+ let cursor = 0;
246
+ while (cursor < candidates.length) {
247
+ const batch = candidates.slice(cursor, cursor + PAGE_POOL_SIZE);
248
+ readable.push(...(await readableRows(batch)));
249
+ cursor += batch.length;
250
+ const ranked = readable.sort(compareRows);
251
+ const lookahead = ranked[limit];
252
+ const activity = lookahead === undefined ? null : activityAt(lookahead);
253
+ const unreadBound = candidates[cursor]?.bound;
254
+ if (lookahead !== undefined &&
255
+ activity !== null &&
256
+ unreadBound !== undefined &&
257
+ Number.isFinite(unreadBound) &&
258
+ Number.isFinite(Date.parse(activity)) &&
259
+ unreadBound < Date.parse(activity)) {
260
+ break;
166
261
  }
167
- if (selected !== undefined && physical.archetype !== selected)
168
- continue;
169
- const paths = akumaPaths({ runRoot, archetype: physical.archetype, suffix: physical.suffix });
170
- try {
171
- rows.push(await fleetListRow(paths, physical.id));
172
- }
173
- catch { }
174
262
  }
175
- return { observedAt, rows, searched: [runRoot] };
263
+ const ranked = readable.sort(compareRows);
264
+ return {
265
+ observedAt,
266
+ searched: [known.runRoot],
267
+ ...projectBoundedList(ranked, limit),
268
+ };
176
269
  }
177
270
  }
178
- export async function callAkumaWithContext(akuma, input, context) {
179
- return await akuma[CALL_WITH_CONTEXT](input, context);
180
- }
181
- export async function beginAkumaCall(akuma, input, context) {
182
- return await akuma.beginCall(input, context);
183
- }
184
- export async function finishAkumaCall(akuma, born, completion = {}) {
185
- return await akuma.finishCall(born, completion);
186
- }
@@ -6,11 +6,8 @@ import type { WorldRoot } from "../world.js";
6
6
  import type { AllowedAction } from "./allowed.js";
7
7
  import type { ExecutionContext } from "./requests.js";
8
8
  import { z } from "zod";
9
+ import type { BoundedList } from "../bounded-list.js";
9
10
  export declare const POLL_MS = 100;
10
- export declare function canonicalBirthCwd(input: string): Promise<string>;
11
- export declare function callReadonly(value: unknown): Readonly<{
12
- readonly?: true;
13
- }>;
14
11
  export type AkumaListRow = Readonly<{
15
12
  id: AkuId;
16
13
  archetype: string;
@@ -419,13 +416,14 @@ export type UnbornAkumaListRow = Readonly<{
419
416
  at: string;
420
417
  }>;
421
418
  }>;
422
- export type AkumaList = Readonly<{
419
+ export type AkumaList = BoundedList<AkumaListRow | UnbornAkumaListRow> & Readonly<{
423
420
  observedAt: string;
424
- rows: readonly (AkumaListRow | UnbornAkumaListRow)[];
425
421
  searched: readonly string[];
426
422
  }>;
423
+ export type AkumaCompleteList = Omit<AkumaList, "hasMore">;
427
424
  export type AkumaListInput = Readonly<{
428
425
  archetype?: string;
426
+ limit?: number;
429
427
  }>;
430
428
  export type AkumaCallExecution = Readonly<{
431
429
  cwd: string;
@@ -493,4 +491,4 @@ export type AkumaConfiguration = Readonly<{
493
491
  execution?: ExecutionContext;
494
492
  }>;
495
493
  export { AkumaHandle, akumaCallExecution, type LastAnswer } from "./akuma-handle.js";
496
- export { Akuma, callAkumaWithContext } from "./akuma-product.js";
494
+ export { Akuma } from "./akuma-product.js";
@@ -1,31 +1,10 @@
1
1
  import { randomUUID } from "node:crypto";
2
- import { realpath, stat } from "node:fs/promises";
3
- import { resolve } from "node:path";
4
2
  import { CONTROL_RESPONSE_MS, handoffPendingTells, wakeRecordedTell, } from "./body.js";
5
3
  import { HeldAkumaLeash, readHeart, readKill, recordTell, requestStop, } from "./heart/index.js";
6
4
  import { parseAkuId, pathsForAkuId } from "./identity.js";
7
5
  import { activitySnapshotSchema } from "./projection.js";
8
6
  import { z } from "zod";
9
7
  export const POLL_MS = 100;
10
- export async function canonicalBirthCwd(input) {
11
- const selected = resolve(input);
12
- try {
13
- const canonical = await realpath(selected);
14
- if (!(await stat(canonical)).isDirectory())
15
- throw new Error("not a directory");
16
- return canonical;
17
- }
18
- catch {
19
- throw new Error(`cwd is not an existing directory: ${input}`);
20
- }
21
- }
22
- export function callReadonly(value) {
23
- if (value === undefined)
24
- return {};
25
- if (value !== true)
26
- throw new TypeError("Akuma call readonly must be true");
27
- return { readonly: true };
28
- }
29
8
  export const akumaIdSchema = z.string().transform((value, context) => {
30
9
  try {
31
10
  const id = parseAkuId(value).id;
@@ -127,4 +106,4 @@ export async function killAkumaWithRecovery(paths, recover = handoffPendingTells
127
106
  }
128
107
  export { bornStatus, fleetListRow, readAkumaBirthCwd, readBudgetedStatus, } from "./akuma-observe.js";
129
108
  export { AkumaHandle, akumaCallExecution } from "./akuma-handle.js";
130
- export { Akuma, callAkumaWithContext } from "./akuma-product.js";
109
+ export { Akuma } from "./akuma-product.js";
@@ -30,14 +30,15 @@ export declare class AkumaArchetypeError extends Error {
30
30
  readonly kind = "akuma-archetype";
31
31
  constructor(archetype: string, searched: readonly string[], reason: string, guidance?: string);
32
32
  }
33
- export declare function listArchetypes(input?: Readonly<{
33
+ type ArchetypeCoordinates = Readonly<{
34
+ project?: string;
34
35
  home?: string;
35
- }>): Promise<readonly string[]>;
36
- export declare function listArchetypeDefinitions(input?: Readonly<{
37
- home?: string;
38
- }>): Promise<readonly ArchetypeCatalogRow[]>;
36
+ }>;
37
+ export declare function listArchetypes(input?: ArchetypeCoordinates): Promise<readonly string[]>;
38
+ export declare function listArchetypeDefinitions(input?: ArchetypeCoordinates): Promise<readonly ArchetypeCatalogRow[]>;
39
39
  export declare function loadArchetype(input: Readonly<{
40
40
  name: string;
41
+ project?: string;
41
42
  home?: string;
42
43
  settings: Settings;
43
44
  readonly?: true;