@astrosheep/keiyaku 4.0.3 → 4.0.4

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 (141) hide show
  1. package/build/integrations/marketplace/plugins/keiyaku/.claude-plugin/plugin.json +1 -1
  2. package/build/integrations/marketplace/plugins/keiyaku/.codex-plugin/plugin.json +1 -1
  3. package/build/integrations/marketplace/plugins/keiyaku/package.json +1 -1
  4. package/build/integrations/marketplace/plugins/keiyaku/skills/keiyaku/SKILL.md +1 -1
  5. package/build/integrations/marketplace/plugins/keiyaku/skills/keiyaku-akuma/SKILL.md +1 -1
  6. package/build/integrations/marketplace/plugins/keiyaku/skills/keiyaku-bind/SKILL.md +15 -1
  7. package/build/integrations/marketplace/plugins/keiyaku/skills/keiyaku-task/SKILL.md +3 -1
  8. package/build/integrations/marketplace/plugins/keiyaku/skills/keiyaku-workflow/SKILL.md +20 -13
  9. package/build/src/akuma/akuma.d.ts +34 -11
  10. package/build/src/akuma/akuma.js +121 -61
  11. package/build/src/akuma/allowed.d.ts +7 -0
  12. package/build/src/akuma/allowed.js +40 -0
  13. package/build/src/akuma/archetype.d.ts +7 -4
  14. package/build/src/akuma/archetype.js +15 -16
  15. package/build/src/akuma/body.d.ts +3 -1
  16. package/build/src/akuma/body.js +9 -6
  17. package/build/src/akuma/heart/facts.d.ts +49 -7
  18. package/build/src/akuma/heart/index.d.ts +4 -8
  19. package/build/src/akuma/heart/index.js +76 -15
  20. package/build/src/akuma/heart/rows.d.ts +7 -5
  21. package/build/src/akuma/heart/rows.js +98 -11
  22. package/build/src/akuma/heart/schema.d.ts +1 -1
  23. package/build/src/akuma/heart/schema.js +15 -10
  24. package/build/src/akuma/heart/soul.js +3 -1
  25. package/build/src/akuma/heart/storage.d.ts +1 -0
  26. package/build/src/akuma/heart/storage.js +15 -0
  27. package/build/src/akuma/heart/timeline.d.ts +1 -5
  28. package/build/src/akuma/heart/timeline.js +8 -17
  29. package/build/src/akuma/index.d.ts +4 -1
  30. package/build/src/akuma/index.js +1 -0
  31. package/build/src/akuma/projection.d.ts +17 -3
  32. package/build/src/akuma/projection.js +40 -3
  33. package/build/src/akuma/provider.d.ts +1 -1
  34. package/build/src/akuma/provider.js +2 -1
  35. package/build/src/akuma/providers/acp/core.d.ts +3 -1
  36. package/build/src/akuma/providers/acp/core.js +4 -4
  37. package/build/src/akuma/providers/acp/events.d.ts +11 -3
  38. package/build/src/akuma/providers/acp/events.js +78 -4
  39. package/build/src/akuma/providers/claude/events.js +62 -3
  40. package/build/src/akuma/providers/codex-app-server/events.js +1 -13
  41. package/build/src/akuma/providers/grok-build/index.d.ts +2 -1
  42. package/build/src/akuma/providers/grok-build/index.js +72 -2
  43. package/build/src/akuma/providers/opencode-sdk/events.js +71 -6
  44. package/build/src/akuma/providers/pi/events.js +17 -3
  45. package/build/src/akuma/providers/unified-patch.d.ts +4 -0
  46. package/build/src/akuma/providers/unified-patch.js +14 -0
  47. package/build/src/akuma/publication.d.ts +1 -1
  48. package/build/src/akuma/publication.js +1 -1
  49. package/build/src/akuma/requests.d.ts +70 -4
  50. package/build/src/akuma/requests.js +288 -77
  51. package/build/src/akuma-body.d.ts +1 -0
  52. package/build/src/akuma-body.js +31 -0
  53. package/build/src/cli/accepted.d.ts +4 -1
  54. package/build/src/cli/accepted.js +26 -4
  55. package/build/src/cli/commands/akuma-invoke.d.ts +8 -7
  56. package/build/src/cli/commands/akuma-invoke.js +18 -10
  57. package/build/src/cli/commands/akuma.d.ts +11 -2
  58. package/build/src/cli/commands/akuma.js +65 -26
  59. package/build/src/cli/commands/amend.d.ts +1 -1
  60. package/build/src/cli/commands/amend.js +1 -1
  61. package/build/src/cli/commands/contract.d.ts +3 -3
  62. package/build/src/cli/commands/contract.js +1 -1
  63. package/build/src/cli/commands/install.js +2 -2
  64. package/build/src/cli/commands/task-query.d.ts +1 -1
  65. package/build/src/cli/commands/task-query.js +11 -5
  66. package/build/src/cli/coordinates.d.ts +1 -0
  67. package/build/src/cli/coordinates.js +5 -1
  68. package/build/src/cli/invoke.d.ts +1 -0
  69. package/build/src/cli/invoke.js +90 -73
  70. package/build/src/cli/main.d.ts +2 -0
  71. package/build/src/cli/main.js +29 -4
  72. package/build/src/cli/parse.d.ts +1 -0
  73. package/build/src/cli/parse.js +4 -0
  74. package/build/src/cli/render/akuma.d.ts +2 -2
  75. package/build/src/cli/render/akuma.js +84 -13
  76. package/build/src/cli/render/audit.js +16 -2
  77. package/build/src/cli/render/contract.d.ts +2 -0
  78. package/build/src/cli/render/contract.js +203 -36
  79. package/build/src/cli/render/kanshi.js +64 -29
  80. package/build/src/cli/render/receipt.d.ts +2 -1
  81. package/build/src/cli/render/receipt.js +9 -1
  82. package/build/src/cli/render/text.js +3 -1
  83. package/build/src/cli/result.d.ts +38 -3
  84. package/build/src/cli/selectors.d.ts +1 -0
  85. package/build/src/cli/selectors.js +9 -6
  86. package/build/src/contract-worktree.js +3 -0
  87. package/build/src/dispatch/index.js +50 -39
  88. package/build/src/git/hooks.d.ts +0 -4
  89. package/build/src/git/hooks.js +0 -36
  90. package/build/src/git/integration.d.ts +2 -6
  91. package/build/src/git/integration.js +8 -8
  92. package/build/src/git/reconcile.d.ts +6 -1
  93. package/build/src/git/reconcile.js +55 -9
  94. package/build/src/git/tender.d.ts +19 -4
  95. package/build/src/git/tender.js +44 -13
  96. package/build/src/git/terminal-seal.d.ts +14 -0
  97. package/build/src/git/terminal-seal.js +15 -2
  98. package/build/src/git/workspace.d.ts +1 -0
  99. package/build/src/git/workspace.js +12 -4
  100. package/build/src/index.d.ts +1 -1
  101. package/build/src/kanshi/index.d.ts +1 -1
  102. package/build/src/kanshi/index.js +1 -1
  103. package/build/src/kanshi/read.d.ts +9 -1
  104. package/build/src/kanshi/read.js +91 -37
  105. package/build/src/kanshi/report.d.ts +1 -0
  106. package/build/src/library/address.d.ts +5 -14
  107. package/build/src/library/address.js +20 -22
  108. package/build/src/library/akuma-creation.d.ts +4 -2
  109. package/build/src/library/akuma-creation.js +43 -26
  110. package/build/src/library/bind.js +3 -3
  111. package/build/src/library/catalog.d.ts +1 -2
  112. package/build/src/library/catalog.js +11 -6
  113. package/build/src/library/configuration.d.ts +4 -0
  114. package/build/src/library/configuration.js +19 -0
  115. package/build/src/library/contract.d.ts +15 -1
  116. package/build/src/library/contract.js +51 -4
  117. package/build/src/library/fleet.d.ts +47 -9
  118. package/build/src/library/fleet.js +184 -44
  119. package/build/src/library/input.js +1 -0
  120. package/build/src/library/keiyaku.d.ts +4 -4
  121. package/build/src/library/keiyaku.js +1 -2
  122. package/build/src/protocol/bind.d.ts +1 -2
  123. package/build/src/protocol/bind.js +2 -16
  124. package/build/src/protocol/operations.d.ts +4 -1
  125. package/build/src/protocol/operations.js +33 -27
  126. package/build/src/protocol/read/status.d.ts +3 -1
  127. package/build/src/protocol/read/status.js +24 -14
  128. package/build/src/runtime/proc/run.d.ts +1 -0
  129. package/build/src/runtime/proc/run.js +9 -2
  130. package/build/src/settlement/settle.d.ts +1 -0
  131. package/build/src/settlement/settle.js +4 -1
  132. package/build/src/task/board.d.ts +18 -3
  133. package/build/src/task/board.js +73 -26
  134. package/build/src/task/index.d.ts +3 -3
  135. package/build/src/task/index.js +7 -5
  136. package/build/src/task/operations.d.ts +2 -19
  137. package/build/src/task/operations.js +29 -7
  138. package/build/src/task/query.d.ts +9 -2
  139. package/build/src/task/query.js +32 -64
  140. package/build/src/verification/execution.js +2 -1
  141. package/package.json +3 -1
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "keiyaku",
3
- "version": "0.1.0+codex.20260815181541",
3
+ "version": "0.1.0+codex.20260818034655",
4
4
  "description": "Use the Keiyaku contract, task, and Akuma CLI.",
5
5
  "author": { "name": "Keiyaku" },
6
6
  "skills": "./skills/"
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "keiyaku",
3
- "version": "0.1.0+codex.20260815181541",
3
+ "version": "0.1.0+codex.20260818034655",
4
4
  "description": "Use the Keiyaku contract, task, and Akuma CLI.",
5
5
  "author": {
6
6
  "name": "Keiyaku"
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "keiyaku-harness",
3
- "version": "0.1.0+codex.20260815181541",
3
+ "version": "0.1.0+codex.20260818034655",
4
4
  "type": "module",
5
5
  "main": "./opencode.js",
6
6
  "keywords": ["pi-package"],
@@ -31,7 +31,7 @@ keiyaku -C <repo> review [<contract>|@<contract>] --satisfied
31
31
  ```
32
32
 
33
33
  ```bash
34
- keiyaku -C <cwd> call <akuma-name> [--contract <kei/...>] [--alias @name] [--wait <duration> | -d | --detach] [--json] (<prompt> | -)
34
+ keiyaku -C <cwd> call <akuma-name> [--contract <kei/...>] [--alias @name] [--allowed <product.action>]... [--wait <duration> | -d | --detach] [--json] (<prompt> | -)
35
35
  keiyaku -C <repo> wait <akuma-selector>... [--any | --all]
36
36
  keiyaku -C <repo> tell <aku/...|@alias> (<prompt> | -)
37
37
  ```
@@ -13,7 +13,7 @@ is accepted; the identity underneath never changes.
13
13
  ## Start One
14
14
 
15
15
  ```bash
16
- keiyaku -C <cwd> call <akuma-name> [--alias @name] [--wait <duration> | -d | --detach] [--json] (<prompt> | -)
16
+ keiyaku -C <cwd> call <akuma-name> [--alias @name] [--allowed <product.action>]... [--wait <duration> | -d | --detach] [--json] (<prompt> | -)
17
17
  ```
18
18
 
19
19
  Give the worker's initial prompt as one argument (quote it when it contains
@@ -31,6 +31,14 @@ Bind is the last step of an investigation, never the first step of an idea.
31
31
  now. Cannot draft means unresolved design — "the worker will sort out the
32
32
  docs" is a design gap in disguise.
33
33
 
34
+ Split complex work wherever independently acceptable delivery boundaries
35
+ exist. When a complex Keiyaku cannot be split without breaking one acceptance
36
+ boundary, bind one Contract and plan its fulfillment as explicit arcs. Treat
37
+ each arc as a chapter as in a work of literature, not as a task sequence.
38
+ Commission one current chapter at a time; never hand the whole undifferentiated
39
+ Contract to one Deliverer and trust it to finish everything in one pass.
40
+ Continue with `keiyaku-workflow` for the arc document and command.
41
+
34
42
  Two tests close the gate:
35
43
 
36
44
  - **Substitution.** Two workers who never met each deliver test-green from
@@ -59,7 +67,8 @@ Design read the same without a sentence here, delete it.>
59
67
 
60
68
  ## Objective
61
69
  <One observable end-state, judged done/not-done without reading Design.
62
- If you need "and", bind two Contracts.>
70
+ If "and" joins independently acceptable outcomes, bind two Contracts. A
71
+ single outcome may still require several arcs during fulfillment.>
63
72
 
64
73
  ## Design
65
74
  <The closed decisions. A statement belongs here exactly when a test-green
@@ -93,6 +102,11 @@ Decidable without consulting you.>
93
102
  KEIYAKU
94
103
  ~~~~
95
104
 
105
+ Use separate fences for checks that need separate timeouts or results. Fences
106
+ run top-to-bottom, and later fences may use earlier outputs. Put setup/build
107
+ before its consumers; use `&&` in one fence only when the consumer must stop
108
+ if setup fails.
109
+
96
110
  Each declaration may set an individual timeout in its fence info string, using
97
111
  an explicit duration unit such as `bash timeout=5m`. Omit it for an unbounded
98
112
  declaration; there is no Verification-wide timeout.
@@ -1,6 +1,8 @@
1
1
  ---
2
2
  name: keiyaku-task
3
- description: "Use when planning work in a Keiyaku v4 repo: add tasks, wire dependencies, inspect readiness, and track lifecycle."
3
+ description: >-
4
+ Use when recording, decomposing, or tracking a complex task, typically
5
+ involving three or more steps.
4
6
  ---
5
7
 
6
8
  # Keiyaku Task
@@ -121,11 +121,17 @@ the test. Give each resulting Contract coherent terms. Connect them with
121
121
  `--after` only when one must proceed from another's settled result or their
122
122
  intended work is unsafe to run concurrently.
123
123
 
124
- When one acceptance boundary still spans several coherent chapters,
125
- organize them as arcs. An arc is a chapter of the delivery's
126
- story, not a task list: its title names the chapter, Objective is the
127
- chapter's aim, Brief opens the next chapter. When an Arc is active, stay
128
- within that current chapter. `.keiyaku/KEIYAKU.md` renders the current Arc.
124
+ When a complex Keiyaku cannot be split without breaking one acceptance
125
+ boundary, organize its fulfillment into arcs. Do not hand the whole
126
+ undifferentiated Contract to one Deliverer and trust one pass to finish it.
127
+ Record and work one current chapter at a time.
128
+
129
+ An arc is a chapter as in a work of literature: one named part of the
130
+ delivery's story, not a task list, progress slice, or claim that the work is
131
+ mechanically sequential. Its title names the chapter, Objective states that
132
+ chapter's aim, and Brief commissions work for that chapter. When an Arc is
133
+ active, stay within that current chapter. `.keiyaku/KEIYAKU.md` renders the
134
+ current Arc.
129
135
  Record the next chapter before entering it:
130
136
 
131
137
  ```bash
@@ -159,19 +165,20 @@ old Contract onto a different delivery.
159
165
 
160
166
  ## Audit Before Delivery
161
167
 
162
- Audit is how you see a delivery before it exists:
168
+ Audit is an evidence window for a prospective delivery:
163
169
 
164
170
  ```bash
165
171
  keiyaku audit <contract> --diff
166
172
  ```
167
173
 
168
- Audit answers three already-adjudicated questions: candidate, Verification,
169
- and target. It uses the same candidate preparation as deliver, shows the
170
- prospective identity and optional requested diff, and runs declared
171
- Verification against that candidate. A terminal run records ordinary
172
- subject-bound `verified` testimony; it does not record a delivery or request
173
- placement. Read those three answers instead of trusting a worker's completion
174
- report.
174
+ It aggregates facts from the same candidate preparation used by `deliver`:
175
+ the prospective candidate and integration identities, the requested diff, the
176
+ declared Verification commands and their observed results, and the target
177
+ placement observation. A terminal run may record subject-bound `verified`
178
+ testimony for those observed commands; it does not deliver, request placement,
179
+ satisfy a review gate, or decide whether the candidate should land. The
180
+ coordinator judges the returned facts; a worker's completion report is not a
181
+ substitute for them.
175
182
 
176
183
  ## Deliver
177
184
 
@@ -3,6 +3,7 @@ import { type AkuId } from "./identity.js";
3
3
  import { type ActivityHistory, type ActivitySnapshot } from "./projection.js";
4
4
  import { type Settings } from "../settings.js";
5
5
  import type { WorldRoot } from "../world.js";
6
+ import { type AllowedAction } from "./allowed.js";
6
7
  export type AkumaListRow = Readonly<{
7
8
  id: AkuId;
8
9
  archetype: string;
@@ -36,6 +37,22 @@ export type AkumaList = Readonly<{
36
37
  export type AkumaListInput = Readonly<{
37
38
  archetype?: string;
38
39
  }>;
40
+ export type AkumaCallExecution = Readonly<{
41
+ cwd: string;
42
+ source: "input" | "caller" | "process" | "world";
43
+ }>;
44
+ declare const CALL_EXECUTION: unique symbol;
45
+ declare const CALL_WITH_CONTEXT: unique symbol;
46
+ export type AkumaCallInput = Readonly<{
47
+ archetype: string;
48
+ body: string;
49
+ cwd?: string;
50
+ allowed?: readonly AllowedAction[];
51
+ }>;
52
+ type AkumaCallContext = Readonly<{
53
+ initiatorCwd?: string;
54
+ cwdCanonical?: true;
55
+ }>;
39
56
  export type TellResult = Readonly<{
40
57
  admission: Readonly<{
41
58
  tellId: string;
@@ -76,8 +93,7 @@ export declare class AkumaNotBornError extends Error {
76
93
  readonly kind = "akuma-not-born";
77
94
  constructor(id: AkuId);
78
95
  }
79
- /** Package-internal action observation; it uses the same snapshot selector as status. */
80
- export declare function readActionFeedbackStatus(worldPath: WorldRoot, id: AkuId): Promise<AkumaStatus>;
96
+ export declare function tellAkumaWithId(worldPath: WorldRoot, id: AkuId, body: string, tellId: string, recordedAt?: string): Promise<TellResult>;
81
97
  export type BudgetedStatusObservation = Readonly<{
82
98
  status: AkumaStatus;
83
99
  ordinarySelected: number;
@@ -86,10 +102,13 @@ export type BudgetedStatusObservation = Readonly<{
86
102
  export declare function readBudgetedStatus(worldPath: WorldRoot, id: AkuId, input: Readonly<{
87
103
  ordinaryBudget: number;
88
104
  }>): Promise<BudgetedStatusObservation>;
105
+ /** Package-internal birth projection used by the composed Library call result. */
106
+ export declare function readAkumaBirthCwd(worldPath: WorldRoot, id: AkuId): Promise<string>;
89
107
  export declare class AkumaHandle {
90
108
  readonly id: AkuId;
91
109
  private readonly worldPath;
92
- constructor(id: AkuId, worldPath: WorldRoot);
110
+ readonly [CALL_EXECUTION]?: AkumaCallExecution;
111
+ constructor(id: AkuId, worldPath: WorldRoot, execution?: AkumaCallExecution);
93
112
  private get paths();
94
113
  status(): Promise<AkumaStatus>;
95
114
  history(input?: Readonly<{
@@ -108,26 +127,30 @@ export declare class AkumaHandle {
108
127
  kill(): Promise<KillEvidence>;
109
128
  lastAnswer(): Promise<LastAnswer>;
110
129
  }
130
+ /** Package-internal provenance retained only by the handle returned from call. */
131
+ export declare function akumaCallExecution(handle: AkumaHandle): AkumaCallExecution | undefined;
111
132
  export type LastAnswer = Readonly<{
112
133
  kind: "answer";
113
134
  answer: string;
114
135
  }> | Readonly<{
115
136
  kind: "no-answer";
116
137
  }>;
138
+ type AkumaConfiguration = Readonly<{
139
+ home?: string;
140
+ settings?: Settings;
141
+ }>;
117
142
  export declare class Akuma {
118
143
  private readonly path;
119
- private readonly configuredSettings?;
144
+ private readonly configuration;
120
145
  private constructor();
121
- static of(root: WorldRoot, settings?: Settings): Akuma;
122
- private settings;
146
+ static of(root: WorldRoot, input?: AkumaConfiguration): Akuma;
123
147
  of(input: Readonly<{
124
148
  id: string;
125
149
  }>): AkumaHandle;
126
150
  listArchetypes(): Promise<readonly string[]>;
127
- call(input: Readonly<{
128
- archetype: string;
129
- body: string;
130
- cwd?: string;
131
- }>): Promise<AkumaHandle>;
151
+ call(input: AkumaCallInput): Promise<AkumaHandle>;
152
+ [CALL_WITH_CONTEXT](input: AkumaCallInput, context: AkumaCallContext): Promise<AkumaHandle>;
132
153
  list(input?: AkumaListInput): Promise<AkumaList>;
133
154
  }
155
+ /** Package-internal call path for a composition owner that already canonicalized cwd. */
156
+ export declare function callAkumaWithContext(akuma: Akuma, input: AkumaCallInput, context: AkumaCallContext): Promise<AkumaHandle>;
@@ -1,8 +1,8 @@
1
1
  import { randomUUID } from "node:crypto";
2
- import { readdir } from "node:fs/promises";
2
+ import { readdir, realpath, stat } from "node:fs/promises";
3
3
  import { resolve } from "node:path";
4
4
  import { CONTROL_RESPONSE_MS, spawnAkumaBody } from "./body.js";
5
- import { HeldAkumaLeash, activitySlice, life, lifeAt, probeLeash, readHeart, readLastAnsweredTurn, readForkPoint, readKill, readSeal, readSoul, recordTell, requestPause, requestStop, } from "./heart/index.js";
5
+ import { HeldAkumaLeash, activitySlice, isHeartAbsent, life, lifeAt, probeLeash, readHeart, readLastAnsweredTurn, readForkPoint, readKill, readSeal, readSoul, recordTell, requestPause, requestStop, } from "./heart/index.js";
6
6
  import { akuIdFromDirectoryName, akumaPaths, akumaRunRoot, parseAkuId, pathsForAkuId, archetypeName, } from "./identity.js";
7
7
  import { ordinarySelectedCount, ordinarySnapshotBudget, projectTurns, selectActivitySnapshot, selectHistory, selectSnapshot, } from "./projection.js";
8
8
  import { listArchetypes as readArchetypes, loadArchetype } from "./archetype.js";
@@ -10,7 +10,22 @@ import { publishAkuma } from "./publication.js";
10
10
  import { resolveProviderExecution } from "./providers/index.js";
11
11
  import { injectedBodyRequests, requestBodyCall } from "./requests.js";
12
12
  import { settings as readSettings } from "../settings.js";
13
+ import { decodeAllowedActions, } from "./allowed.js";
13
14
  const POLL_MS = 25;
15
+ async function canonicalBirthCwd(input) {
16
+ const selected = resolve(input);
17
+ try {
18
+ const canonical = await realpath(selected);
19
+ if (!(await stat(canonical)).isDirectory())
20
+ throw new Error("not a directory");
21
+ return canonical;
22
+ }
23
+ catch {
24
+ throw new Error(`cwd is not an existing directory: ${input}`);
25
+ }
26
+ }
27
+ const CALL_EXECUTION = Symbol("akuma-call-execution");
28
+ const CALL_WITH_CONTEXT = Symbol("akuma-call-with-context");
14
29
  export class AkumaNotBornError extends Error {
15
30
  id;
16
31
  kind = "akuma-not-born";
@@ -33,9 +48,8 @@ async function takeLeashUntil(paths, deadline) {
33
48
  await wait(POLL_MS);
34
49
  }
35
50
  }
36
- async function recordTellBody(paths, akuma, body) {
37
- const id = randomUUID();
38
- const admitted = await recordTell(paths, { kind: "tell", id, body, recordedAt: new Date().toISOString() });
51
+ async function recordTellBody(paths, akuma, body, id = randomUUID(), recordedAt = new Date().toISOString()) {
52
+ const admitted = await recordTell(paths, { kind: "tell", id, body, recordedAt });
39
53
  if (admitted.kind === "not-born")
40
54
  throw new AkumaNotBornError(akuma);
41
55
  return { kind: "recorded", tellId: admitted.tell.id };
@@ -48,10 +62,34 @@ async function wakeTell(paths, tellId) {
48
62
  catch (error) {
49
63
  return {
50
64
  admission: { tellId, fact: "recorded" },
51
- wake: { kind: "failed", diagnostic: error instanceof Error ? error.message : String(error) },
65
+ wake: { kind: "failed", diagnostic: diagnostic(error) },
52
66
  };
53
67
  }
54
68
  }
69
+ export async function tellAkumaWithId(worldPath, id, body, tellId, recordedAt = new Date().toISOString()) {
70
+ const paths = pathsForAkuId(worldPath, id);
71
+ const recorded = await recordTellBody(paths, id, body, tellId, recordedAt);
72
+ return await wakeTell(paths, recorded.tellId);
73
+ }
74
+ function diagnostic(error) {
75
+ return error instanceof Error ? error.message : String(error);
76
+ }
77
+ async function fleetListRow(paths, expected) {
78
+ const snapshot = await readHeart(paths);
79
+ if (snapshot.soul !== null)
80
+ return await bornListRow(paths, expected, snapshot);
81
+ try {
82
+ if (await probeLeash(paths) === "held")
83
+ return { id: expected, life: "unborn" };
84
+ const seal = await readSeal(paths);
85
+ return seal === null ? { id: expected, life: "unborn" } : { id: expected, life: "stillborn", seal };
86
+ }
87
+ catch (error) {
88
+ if (isHeartAbsent(error))
89
+ return { id: expected, life: "unborn" };
90
+ throw error;
91
+ }
92
+ }
55
93
  async function bornListRow(paths, expected, snapshot) {
56
94
  snapshot ??= await readHeart(paths);
57
95
  if (snapshot.soul === null)
@@ -81,7 +119,7 @@ async function bornStatus(paths, expected, budget) {
81
119
  const resumeUnsupported = current.life === "stranded"
82
120
  && snapshot.latestSession?.provider === snapshot.soul.provider.name
83
121
  && resolveProviderExecution(snapshot.soul.provider).adapter.resume === undefined;
84
- const slice = await activitySlice(paths, { limit: Number.MAX_SAFE_INTEGER });
122
+ const slice = await activitySlice(paths);
85
123
  return {
86
124
  id: current.id,
87
125
  life: current.life,
@@ -92,10 +130,6 @@ async function bornStatus(paths, expected, budget) {
92
130
  : selectSnapshot(projectTurns(slice.rows), budget),
93
131
  };
94
132
  }
95
- /** Package-internal action observation; it uses the same snapshot selector as status. */
96
- export async function readActionFeedbackStatus(worldPath, id) {
97
- return await bornStatus(pathsForAkuId(worldPath, id), id);
98
- }
99
133
  /** Package-internal budgeted observation; Fleet allocates, Akuma still selects. */
100
134
  export async function readBudgetedStatus(worldPath, id, input) {
101
135
  if (!Number.isSafeInteger(input.ordinaryBudget) || input.ordinaryBudget < 0) {
@@ -104,15 +138,22 @@ export async function readBudgetedStatus(worldPath, id, input) {
104
138
  const status = await bornStatus(pathsForAkuId(worldPath, id), id, ordinarySnapshotBudget(input.ordinaryBudget));
105
139
  return { status, ordinarySelected: ordinarySelectedCount(status.timeline) };
106
140
  }
107
- function diagnostic(error) {
108
- return error instanceof Error ? error.message : String(error);
141
+ /** Package-internal birth projection used by the composed Library call result. */
142
+ export async function readAkumaBirthCwd(worldPath, id) {
143
+ const soul = await readSoul(pathsForAkuId(worldPath, id));
144
+ if (soul === null)
145
+ throw new AkumaNotBornError(id);
146
+ return soul.cwd;
109
147
  }
110
148
  export class AkumaHandle {
111
149
  id;
112
150
  worldPath;
113
- constructor(id, worldPath) {
151
+ [CALL_EXECUTION];
152
+ constructor(id, worldPath, execution) {
114
153
  this.id = id;
115
154
  this.worldPath = worldPath;
155
+ if (execution !== undefined)
156
+ this[CALL_EXECUTION] = execution;
116
157
  }
117
158
  get paths() {
118
159
  return pathsForAkuId(this.worldPath, this.id);
@@ -133,11 +174,7 @@ export class AkumaHandle {
133
174
  if (!Number.isSafeInteger(limit) || limit <= 0 || limit > 5_000) {
134
175
  throw new TypeError("Akuma history limit must be a positive safe integer no greater than 5000");
135
176
  }
136
- const slice = await activitySlice(this.paths, {
137
- ...(input.before === undefined ? {} : { before: input.before }),
138
- ...(input.since === undefined ? {} : { since: input.since }),
139
- limit: 5_000,
140
- });
177
+ const slice = await activitySlice(this.paths);
141
178
  return selectHistory(projectTurns(slice.rows, {
142
179
  lowestRetained: slice.lowestRetained,
143
180
  highest: slice.highest,
@@ -254,6 +291,7 @@ export class AkumaHandle {
254
291
  provider: source.provider,
255
292
  options: source.options,
256
293
  ...(source.readonly === undefined ? {} : { readonly: source.readonly }),
294
+ allowed: source.allowed,
257
295
  cwd: source.cwd,
258
296
  origin: { kind: "fork", parent: this.id, at: input.at },
259
297
  confinement: source.confinement,
@@ -264,7 +302,11 @@ export class AkumaHandle {
264
302
  return { kind: "forked", child: child.id };
265
303
  }
266
304
  catch (error) {
267
- return { kind: "upstream-forked", childSession, diagnostic: diagnostic(error) };
305
+ return {
306
+ kind: "upstream-forked",
307
+ childSession,
308
+ diagnostic: diagnostic(error),
309
+ };
268
310
  }
269
311
  }
270
312
  async kill() {
@@ -309,52 +351,75 @@ export class AkumaHandle {
309
351
  : { kind: "no-answer" };
310
352
  }
311
353
  }
354
+ /** Package-internal provenance retained only by the handle returned from call. */
355
+ export function akumaCallExecution(handle) {
356
+ return handle[CALL_EXECUTION];
357
+ }
312
358
  export class Akuma {
313
359
  path;
314
- configuredSettings;
315
- constructor(path, configuredSettings) {
360
+ configuration;
361
+ constructor(path, configuration) {
316
362
  this.path = path;
317
- this.configuredSettings = configuredSettings;
363
+ this.configuration = configuration;
318
364
  }
319
- static of(root, settings) {
365
+ static of(root, input = {}) {
320
366
  if (typeof root !== "string")
321
367
  throw new TypeError("Akuma.of root must be a WorldRoot");
322
- return new Akuma(root, settings);
323
- }
324
- async settings() {
325
- return this.configuredSettings ?? await readSettings({ root: this.path });
368
+ return new Akuma(root, input);
326
369
  }
327
370
  of(input) {
328
371
  return new AkumaHandle(parseAkuId(input.id).id, this.path);
329
372
  }
330
373
  async listArchetypes() {
331
- return readArchetypes({ settings: await this.settings() });
374
+ return readArchetypes(this.configuration.home === undefined ? {} : { home: this.configuration.home });
332
375
  }
333
376
  async call(input) {
377
+ return await this[CALL_WITH_CONTEXT](input, { initiatorCwd: process.cwd() });
378
+ }
379
+ async [CALL_WITH_CONTEXT](input, context) {
334
380
  const name = archetypeName(input.archetype);
335
- const archetype = await loadArchetype({ name, settings: await this.settings() });
336
- const provider = archetype.adapter;
337
- const cwd = resolve(input.cwd ?? this.path);
338
- const recipe = Object.freeze({
381
+ const home = this.configuration.home === undefined ? {} : { home: this.configuration.home };
382
+ const settings = this.configuration.settings ?? await readSettings({ root: this.path, ...home });
383
+ const archetype = await loadArchetype({ name, ...home, settings });
384
+ const allowed = input.allowed === undefined
385
+ ? archetype.allowed
386
+ : decodeAllowedActions(input.allowed, "Akuma call allowed");
387
+ const requests = injectedBodyRequests();
388
+ const requestRecipe = Object.freeze({
339
389
  ...(archetype.description === undefined ? {} : { description: archetype.description }),
340
390
  provider: archetype.provider,
341
391
  options: archetype.options,
342
392
  ...(archetype.readonly === undefined ? {} : { readonly: archetype.readonly }),
343
- confinement: provider.confinement({ cwd, options: archetype.options }),
393
+ allowed,
344
394
  });
345
- const requests = injectedBodyRequests();
346
395
  if (requests !== null) {
396
+ const cwd = input.cwd === undefined
397
+ ? undefined
398
+ : context?.cwdCanonical === true ? input.cwd : await canonicalBirthCwd(input.cwd);
347
399
  const child = await requestBodyCall({
348
400
  directory: requests,
349
401
  id: randomUUID(),
350
402
  world: this.path,
351
403
  archetype: name,
352
404
  body: input.body,
353
- cwd,
354
- recipe,
405
+ ...(cwd === undefined ? {} : { cwd }),
406
+ recipe: requestRecipe,
407
+ });
408
+ const bornCwd = await readAkumaBirthCwd(this.path, child);
409
+ return new AkumaHandle(child, this.path, {
410
+ cwd: bornCwd,
411
+ source: cwd === undefined ? "caller" : "input",
355
412
  });
356
- return new AkumaHandle(child, this.path);
357
413
  }
414
+ const initiatorCwd = context.initiatorCwd;
415
+ const selectedCwd = input.cwd ?? initiatorCwd ?? this.path;
416
+ const cwd = input.cwd !== undefined && context?.cwdCanonical === true
417
+ ? input.cwd
418
+ : await canonicalBirthCwd(selectedCwd);
419
+ const recipe = Object.freeze({
420
+ ...requestRecipe,
421
+ confinement: archetype.adapter.confinement({ cwd, options: archetype.options }),
422
+ });
358
423
  const published = await publishAkuma({
359
424
  worldPath: this.path,
360
425
  archetype: archetype.name,
@@ -363,18 +428,17 @@ export class Akuma {
363
428
  seed: {
364
429
  id: allocated.id,
365
430
  archetype: allocated.archetype,
366
- ...(archetype.description === undefined ? {} : { description: archetype.description }),
367
- provider: archetype.provider,
368
- options: archetype.options,
369
- ...(archetype.readonly === undefined ? {} : { readonly: archetype.readonly }),
431
+ ...recipe,
370
432
  cwd,
371
433
  origin: { kind: "direct" },
372
- confinement: recipe.confinement,
373
434
  },
374
435
  initialBody: input.body,
375
436
  }),
376
437
  });
377
- return new AkumaHandle(published.id, this.path);
438
+ return new AkumaHandle(published.id, this.path, {
439
+ cwd,
440
+ source: input.cwd !== undefined ? "input" : initiatorCwd === undefined ? "world" : "process",
441
+ });
378
442
  }
379
443
  async list(input = {}) {
380
444
  if (typeof input !== "object" || input === null || Array.isArray(input)) {
@@ -399,28 +463,20 @@ export class Akuma {
399
463
  }
400
464
  const rows = [];
401
465
  for (const name of names) {
466
+ let physical;
402
467
  try {
403
- const physical = akuIdFromDirectoryName(name);
404
- if (selected !== undefined && physical.archetype !== selected)
405
- continue;
406
- const paths = akumaPaths({ runRoot, archetype: physical.archetype, suffix: physical.suffix });
407
- const snapshot = await readHeart(paths);
408
- if (snapshot.soul !== null) {
409
- rows.push(await bornListRow(paths, physical.id, snapshot));
410
- continue;
411
- }
412
- if (await probeLeash(paths) === "held") {
413
- rows.push({ id: physical.id, life: "unborn" });
414
- continue;
415
- }
416
- const seal = await readSeal(paths);
417
- rows.push(seal === null
418
- ? { id: physical.id, life: "unborn" }
419
- : { id: physical.id, life: "stillborn", seal });
468
+ physical = akuIdFromDirectoryName(name);
420
469
  }
421
470
  catch {
422
471
  continue;
423
472
  }
473
+ if (selected !== undefined && physical.archetype !== selected)
474
+ continue;
475
+ const paths = akumaPaths({ runRoot, archetype: physical.archetype, suffix: physical.suffix });
476
+ try {
477
+ rows.push(await fleetListRow(paths, physical.id));
478
+ }
479
+ catch { }
424
480
  }
425
481
  return {
426
482
  rows,
@@ -428,3 +484,7 @@ export class Akuma {
428
484
  };
429
485
  }
430
486
  }
487
+ /** Package-internal call path for a composition owner that already canonicalized cwd. */
488
+ export async function callAkumaWithContext(akuma, input, context) {
489
+ return await akuma[CALL_WITH_CONTEXT](input, context);
490
+ }
@@ -0,0 +1,7 @@
1
+ export declare const ALLOWED_ACTIONS: readonly ["akuma.call", "akuma.kill", "akuma.tell", "contract.deliver", "task.add", "task.addDocument", "task.compose", "task.done", "task.drop", "task.hold", "task.resume", "task.start", "task.stop", "task.update"];
2
+ export type AllowedAction = (typeof ALLOWED_ACTIONS)[number];
3
+ export type AllowedActions = readonly AllowedAction[];
4
+ export declare function isAllowedAction(value: unknown): value is AllowedAction;
5
+ export declare function decodeAllowedActions(value: unknown, label?: string): AllowedActions;
6
+ export declare function effectiveAllowedActions(value: unknown): AllowedActions;
7
+ export declare function clipAllowedActions(requested: AllowedActions, parent: AllowedActions): AllowedActions;
@@ -0,0 +1,40 @@
1
+ export const ALLOWED_ACTIONS = Object.freeze([
2
+ "akuma.call",
3
+ "akuma.kill",
4
+ "akuma.tell",
5
+ "contract.deliver",
6
+ "task.add",
7
+ "task.addDocument",
8
+ "task.compose",
9
+ "task.done",
10
+ "task.drop",
11
+ "task.hold",
12
+ "task.resume",
13
+ "task.start",
14
+ "task.stop",
15
+ "task.update",
16
+ ]);
17
+ const ALLOWED_ACTION_SET = new Set(ALLOWED_ACTIONS);
18
+ export function isAllowedAction(value) {
19
+ return typeof value === "string" && ALLOWED_ACTION_SET.has(value);
20
+ }
21
+ export function decodeAllowedActions(value, label = "allowed") {
22
+ if (!Array.isArray(value))
23
+ throw new TypeError(`${label} must be an array`);
24
+ const seen = new Set();
25
+ for (const action of value) {
26
+ if (!isAllowedAction(action))
27
+ throw new TypeError(`${label} contains an unknown action: ${String(action)}`);
28
+ if (seen.has(action))
29
+ throw new TypeError(`${label} contains a duplicate action: ${action}`);
30
+ seen.add(action);
31
+ }
32
+ return Object.freeze([...seen].sort());
33
+ }
34
+ export function effectiveAllowedActions(value) {
35
+ return value === undefined ? ALLOWED_ACTIONS : decodeAllowedActions(value);
36
+ }
37
+ export function clipAllowedActions(requested, parent) {
38
+ const ceiling = new Set(parent);
39
+ return Object.freeze(requested.filter((action) => ceiling.has(action)));
40
+ }
@@ -1,12 +1,14 @@
1
1
  import type { Settings } from "../settings.js";
2
2
  import type { ProviderAdapter } from "./provider.js";
3
3
  import { type ProviderExecution, type ProviderOptions, type ReadonlyRestraint } from "./provider-recipe.js";
4
+ import { type AllowedActions } from "./allowed.js";
4
5
  type DecodedArchetype = Readonly<{
5
6
  name: string;
6
7
  path: string;
7
8
  provider: string;
8
9
  description?: string;
9
10
  options: ProviderOptions;
11
+ allowed: AllowedActions;
10
12
  }>;
11
13
  export type ArchetypeCatalogRow = Readonly<{
12
14
  name: string;
@@ -27,14 +29,15 @@ export declare class AkumaArchetypeError extends Error {
27
29
  readonly kind = "akuma-archetype";
28
30
  constructor(archetype: string, searched: readonly string[], reason: string, guidance?: string);
29
31
  }
30
- export declare function listArchetypes(input: Readonly<{
31
- settings: Settings;
32
+ export declare function listArchetypes(input?: Readonly<{
33
+ home?: string;
32
34
  }>): Promise<readonly string[]>;
33
- export declare function listArchetypeDefinitions(input: Readonly<{
34
- settings: Settings;
35
+ export declare function listArchetypeDefinitions(input?: Readonly<{
36
+ home?: string;
35
37
  }>): Promise<readonly ArchetypeCatalogRow[]>;
36
38
  export declare function loadArchetype(input: Readonly<{
37
39
  name: string;
40
+ home?: string;
38
41
  settings: Settings;
39
42
  }>): Promise<AdmittedArchetype>;
40
43
  export {};