@astrosheep/keiyaku 4.0.5 → 4.0.6

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 (153) 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 +15 -7
  6. package/build/integrations/marketplace/plugins/keiyaku/skills/keiyaku-bind/SKILL.md +5 -3
  7. package/build/integrations/marketplace/plugins/keiyaku/skills/keiyaku-workflow/SKILL.md +10 -4
  8. package/build/src/akuma/akuma.d.ts +2 -0
  9. package/build/src/akuma/akuma.js +26 -3
  10. package/build/src/akuma/archetype.d.ts +3 -1
  11. package/build/src/akuma/archetype.js +11 -6
  12. package/build/src/akuma/body-turn.d.ts +64 -0
  13. package/build/src/akuma/body-turn.js +449 -0
  14. package/build/src/akuma/body.d.ts +3 -7
  15. package/build/src/akuma/body.js +9 -420
  16. package/build/src/akuma/heart/facts.d.ts +2 -0
  17. package/build/src/akuma/heart/facts.js +3 -1
  18. package/build/src/akuma/heart/index.js +12 -2
  19. package/build/src/akuma/heart/rows.js +4 -1
  20. package/build/src/akuma/heart/schema.d.ts +1 -1
  21. package/build/src/akuma/heart/schema.js +3 -2
  22. package/build/src/akuma/heart/timeline.d.ts +1 -0
  23. package/build/src/akuma/heart/timeline.js +19 -0
  24. package/build/src/akuma/nuke.d.ts +2 -0
  25. package/build/src/akuma/nuke.js +152 -0
  26. package/build/src/akuma/provider.d.ts +3 -1
  27. package/build/src/akuma/providers/acp/config.d.ts +8 -0
  28. package/build/src/akuma/providers/acp/config.js +37 -0
  29. package/build/src/akuma/providers/acp/core.js +6 -0
  30. package/build/src/akuma/providers/acp/index.d.ts +1 -8
  31. package/build/src/akuma/providers/acp/index.js +2 -37
  32. package/build/src/akuma/providers/claude/index.js +5 -0
  33. package/build/src/akuma/providers/codex-app-server/index.js +5 -0
  34. package/build/src/akuma/providers/grok-build/index.js +1 -1
  35. package/build/src/akuma/providers/index.d.ts +3 -2
  36. package/build/src/akuma/providers/index.js +36 -36
  37. package/build/src/akuma/providers/opencode-sdk/index.js +6 -0
  38. package/build/src/akuma/providers/opencode-sdk/session.d.ts +1 -1
  39. package/build/src/akuma/providers/opencode-sdk/session.js +1 -1
  40. package/build/src/akuma/providers/pi/index.d.ts +1 -1
  41. package/build/src/akuma/providers/pi/index.js +27 -11
  42. package/build/src/akuma/request-serve.d.ts +1 -0
  43. package/build/src/akuma/request-serve.js +31 -31
  44. package/build/src/akuma/request-wire.d.ts +3 -2
  45. package/build/src/akuma/request-wire.js +5 -2
  46. package/build/src/akuma-body.js +15 -1
  47. package/build/src/alias/index.d.ts +1 -0
  48. package/build/src/alias/index.js +34 -1
  49. package/build/src/cli/accepted.js +4 -0
  50. package/build/src/cli/commands/akuma-invoke.js +1 -0
  51. package/build/src/cli/commands/akuma.d.ts +1 -0
  52. package/build/src/cli/commands/akuma.js +11 -8
  53. package/build/src/cli/commands/contract.d.ts +25 -14
  54. package/build/src/cli/commands/contract.js +23 -17
  55. package/build/src/cli/commands/install.d.ts +2 -2
  56. package/build/src/cli/commands/install.js +6 -6
  57. package/build/src/cli/commands/nuke.d.ts +10 -0
  58. package/build/src/cli/commands/nuke.js +20 -0
  59. package/build/src/cli/commands/task-invoke.d.ts +15 -0
  60. package/build/src/cli/commands/task-invoke.js +57 -26
  61. package/build/src/cli/commands/task-query.d.ts +2 -2
  62. package/build/src/cli/commands/task-query.js +1 -2
  63. package/build/src/cli/commands/task.js +22 -22
  64. package/build/src/cli/coordinates.d.ts +5 -2
  65. package/build/src/cli/coordinates.js +5 -52
  66. package/build/src/cli/invoke.d.ts +5 -4
  67. package/build/src/cli/invoke.js +89 -59
  68. package/build/src/cli/main.d.ts +0 -2
  69. package/build/src/cli/main.js +4 -140
  70. package/build/src/cli/parse.d.ts +15 -2
  71. package/build/src/cli/parse.js +73 -8
  72. package/build/src/cli/render/akuma.js +55 -40
  73. package/build/src/cli/render/contract.js +49 -48
  74. package/build/src/cli/render/kanshi.js +15 -5
  75. package/build/src/cli/render/nuke.d.ts +3 -0
  76. package/build/src/cli/render/nuke.js +8 -0
  77. package/build/src/cli/render/refusal.d.ts +2 -1
  78. package/build/src/cli/render/refusal.js +31 -0
  79. package/build/src/cli/render/text.js +6 -1
  80. package/build/src/cli/result.d.ts +21 -2
  81. package/build/src/cli/runtime.d.ts +2 -0
  82. package/build/src/cli/runtime.js +140 -0
  83. package/build/src/cli/usage.d.ts +0 -2
  84. package/build/src/cli/usage.js +0 -4
  85. package/build/src/contract-worktree.d.ts +28 -12
  86. package/build/src/contract-worktree.js +129 -63
  87. package/build/src/git/hooks.d.ts +2 -0
  88. package/build/src/git/hooks.js +34 -1
  89. package/build/src/git/integration.d.ts +4 -0
  90. package/build/src/git/integration.js +38 -0
  91. package/build/src/git/nuke.d.ts +2 -0
  92. package/build/src/git/nuke.js +97 -0
  93. package/build/src/git/process.d.ts +2 -0
  94. package/build/src/git/reconcile.d.ts +2 -0
  95. package/build/src/git/reconcile.js +33 -2
  96. package/build/src/git/repository.js +4 -1
  97. package/build/src/git/target-placement.d.ts +2 -1
  98. package/build/src/git/target-placement.js +6 -7
  99. package/build/src/git/tender.d.ts +1 -0
  100. package/build/src/git/tender.js +2 -2
  101. package/build/src/git/workspace.d.ts +3 -0
  102. package/build/src/kanshi/read.js +5 -1
  103. package/build/src/library/akuma-creation.d.ts +1 -0
  104. package/build/src/library/akuma-creation.js +8 -1
  105. package/build/src/library/audit.d.ts +1 -0
  106. package/build/src/library/audit.js +1 -0
  107. package/build/src/library/contract.d.ts +6 -4
  108. package/build/src/library/contract.js +56 -34
  109. package/build/src/library/delivery.d.ts +3 -1
  110. package/build/src/library/keiyaku.d.ts +5 -1
  111. package/build/src/library/keiyaku.js +2 -0
  112. package/build/src/library/mutation.d.ts +1 -0
  113. package/build/src/library/mutation.js +3 -0
  114. package/build/src/library/nuke.d.ts +14 -0
  115. package/build/src/library/nuke.js +54 -0
  116. package/build/src/library/reconcile.js +5 -2
  117. package/build/src/library/refusal.d.ts +10 -1
  118. package/build/src/library/repo.d.ts +1 -0
  119. package/build/src/library/repo.js +2 -0
  120. package/build/src/protocol/audit.js +12 -5
  121. package/build/src/protocol/completion.d.ts +11 -1
  122. package/build/src/protocol/completion.js +68 -15
  123. package/build/src/protocol/deliver.d.ts +14 -2
  124. package/build/src/protocol/deliver.js +107 -6
  125. package/build/src/protocol/intent.d.ts +1 -0
  126. package/build/src/protocol/intent.js +7 -1
  127. package/build/src/protocol/operations.d.ts +30 -3
  128. package/build/src/protocol/operations.js +2 -2
  129. package/build/src/protocol/placement.d.ts +9 -1
  130. package/build/src/protocol/placement.js +5 -4
  131. package/build/src/protocol/read/status.d.ts +16 -4
  132. package/build/src/protocol/read/status.js +54 -12
  133. package/build/src/protocol/review.d.ts +1 -0
  134. package/build/src/protocol/review.js +8 -0
  135. package/build/src/runtime/proc/run.js +9 -5
  136. package/build/src/settlement/fence.d.ts +2 -0
  137. package/build/src/settlement/fence.js +24 -0
  138. package/build/src/settlement/settle.js +1 -2
  139. package/build/src/task/compose.d.ts +1 -1
  140. package/build/src/task/compose.js +2 -7
  141. package/build/src/task/context.d.ts +14 -2
  142. package/build/src/task/context.js +30 -7
  143. package/build/src/task/index.d.ts +6 -5
  144. package/build/src/task/index.js +15 -18
  145. package/build/src/task/mutation.d.ts +7 -2
  146. package/build/src/task/mutation.js +14 -6
  147. package/build/src/task/operations.d.ts +12 -6
  148. package/build/src/task/operations.js +22 -46
  149. package/build/src/task/store.d.ts +1 -0
  150. package/build/src/task/store.js +91 -1
  151. package/build/src/workspace-place.d.ts +2 -0
  152. package/build/src/workspace-place.js +27 -1
  153. package/package.json +1 -1
@@ -0,0 +1,152 @@
1
+ import { lstat, readdir, rm } from "node:fs/promises";
2
+ import { nukeAliases } from "../alias/index.js";
3
+ import { CONTROL_RESPONSE_MS } from "./body.js";
4
+ import { HeldAkumaLeash, readHeart, readKill, requestStop, } from "./heart/index.js";
5
+ import { akuIdFromDirectoryName, akumaPaths, akumaRunRoot, } from "./identity.js";
6
+ const POLL_MS = 25;
7
+ async function hasAkumaCustody(paths) {
8
+ try {
9
+ const value = await lstat(paths.heart);
10
+ return value.isFile() && !value.isSymbolicLink();
11
+ }
12
+ catch (error) {
13
+ if (error.code !== "ENOENT")
14
+ throw error;
15
+ return false;
16
+ }
17
+ }
18
+ function wait(milliseconds) {
19
+ return new Promise((resolve) => setTimeout(resolve, milliseconds));
20
+ }
21
+ async function takeLeashUntil(paths, deadline) {
22
+ for (;;) {
23
+ const leash = await HeldAkumaLeash.try(paths);
24
+ if (leash !== null)
25
+ return leash;
26
+ if (performance.now() >= deadline)
27
+ return null;
28
+ await wait(POLL_MS);
29
+ }
30
+ }
31
+ async function stopRunningAkuma(entry) {
32
+ const request = await requestStop(entry.paths, new Date().toISOString());
33
+ const leash = await takeLeashUntil(entry.paths, performance.now() + CONTROL_RESPONSE_MS);
34
+ if (leash === null)
35
+ throw new Error(`Akuma ${entry.id} could not be stopped: unavailable`);
36
+ try {
37
+ if (request.kind === "already-killed" || request.kind === "already-stopped")
38
+ return leash;
39
+ if (await readKill(entry.paths, request.body.sequence) !== null)
40
+ return leash;
41
+ const current = await readHeart(entry.paths);
42
+ if (current.latestBody?.sequence !== request.body.sequence) {
43
+ if (await readKill(entry.paths, request.body.sequence) !== null)
44
+ return leash;
45
+ throw new Error(`Akuma ${entry.id} could not be stopped: unavailable`);
46
+ }
47
+ if (current.latestBody.end !== "put-down") {
48
+ await leash.clearStop(entry.paths);
49
+ throw new Error(`Akuma ${entry.id} could not be stopped: untidy`);
50
+ }
51
+ if (await leash.settleStop(entry.paths, request.body.sequence) === null) {
52
+ throw new Error(`Akuma ${entry.id} could not be stopped: unavailable`);
53
+ }
54
+ return leash;
55
+ }
56
+ catch (error) {
57
+ leash.release();
58
+ throw error;
59
+ }
60
+ }
61
+ async function removeRegularFile(path) {
62
+ try {
63
+ const value = await lstat(path);
64
+ if (value.isFile() && !value.isSymbolicLink())
65
+ await rm(path, { force: true });
66
+ }
67
+ catch (error) {
68
+ if (error.code !== "ENOENT")
69
+ throw error;
70
+ }
71
+ }
72
+ async function removeAkumaEntry(entry) {
73
+ for (const path of [entry.paths.heart, entry.paths.log,
74
+ `${entry.paths.heart}-wal`, `${entry.paths.heart}-shm`]) {
75
+ await removeRegularFile(path);
76
+ }
77
+ }
78
+ async function nukeAkumaEntries(world) {
79
+ const runRoot = akumaRunRoot(world);
80
+ let names;
81
+ try {
82
+ names = (await readdir(runRoot, { withFileTypes: true }))
83
+ .filter((entry) => entry.isDirectory())
84
+ .map((entry) => entry.name)
85
+ .sort();
86
+ }
87
+ catch (error) {
88
+ if (error.code === "ENOENT")
89
+ names = [];
90
+ else
91
+ throw error;
92
+ }
93
+ const entries = [];
94
+ for (const name of names) {
95
+ let physical;
96
+ try {
97
+ physical = akuIdFromDirectoryName(name);
98
+ }
99
+ catch {
100
+ // The run root can contain bytes not produced by Keiyaku. Preserve them.
101
+ continue;
102
+ }
103
+ const paths = akumaPaths({ runRoot, archetype: physical.archetype, suffix: physical.suffix });
104
+ if (!(await hasAkumaCustody(paths)))
105
+ continue;
106
+ entries.push({ id: physical.id, paths });
107
+ }
108
+ return entries;
109
+ }
110
+ export async function stopAkuma(world) {
111
+ const entries = await nukeAkumaEntries(world);
112
+ const held = [];
113
+ try {
114
+ for (const entry of entries) {
115
+ const snapshot = await readHeart(entry.paths);
116
+ const leash = snapshot.soul !== null && snapshot.latestBody?.end === undefined
117
+ ? await stopRunningAkuma(entry)
118
+ : await takeLeashUntil(entry.paths, performance.now() + CONTROL_RESPONSE_MS);
119
+ if (leash === null)
120
+ throw new Error(`Akuma ${entry.id} could not be verified stopped`);
121
+ try {
122
+ const after = await readHeart(entry.paths);
123
+ if (after.soul === null || after.latestBody?.end !== undefined) {
124
+ held.push(leash);
125
+ continue;
126
+ }
127
+ throw new Error(`Akuma ${entry.id} stopped without a durable end`);
128
+ }
129
+ catch (error) {
130
+ leash.release();
131
+ throw error;
132
+ }
133
+ }
134
+ return async () => {
135
+ try {
136
+ await nukeAliases(world);
137
+ for (const entry of entries)
138
+ await removeAkumaEntry(entry);
139
+ }
140
+ finally {
141
+ for (const leash of held.reverse())
142
+ leash.release();
143
+ }
144
+ };
145
+ }
146
+ finally {
147
+ if (held.length !== entries.length) {
148
+ for (const leash of held.reverse())
149
+ leash.release();
150
+ }
151
+ }
152
+ }
@@ -121,8 +121,10 @@ export type Session = Readonly<{
121
121
  events: AsyncIterable<AgentEvent>;
122
122
  receipts?: AsyncIterable<TellReceipt>;
123
123
  completion: Promise<TurnResult>;
124
- /** Fulfills after every adapter-owned OS child or native session is disposed. */
124
+ /** Requests graceful adapter-owned cancellation. */
125
125
  abort(): Promise<void>;
126
+ /** Fulfills only after forced adapter-owned disposal is proved. */
127
+ forceDispose(): Promise<void>;
126
128
  tell?(tell: Readonly<{
127
129
  id: string;
128
130
  text: string;
@@ -0,0 +1,8 @@
1
+ export type AcpExecutionConfig = Readonly<{
2
+ argvBefore: readonly string[];
3
+ argvAfter: readonly string[];
4
+ modelArg?: string;
5
+ effortArg?: string;
6
+ systemPromptArg?: string;
7
+ }>;
8
+ export declare function decodeAcpConfig(value: unknown): AcpExecutionConfig;
@@ -0,0 +1,37 @@
1
+ function argumentName(value, key) {
2
+ const selected = value[key];
3
+ if (selected === undefined)
4
+ return undefined;
5
+ if (typeof selected !== "string" || selected.trim().length === 0) {
6
+ throw new TypeError(`ACP provider config ${key} must be a nonblank string`);
7
+ }
8
+ return selected;
9
+ }
10
+ export function decodeAcpConfig(value) {
11
+ if (value === null || typeof value !== "object" || Array.isArray(value)) {
12
+ throw new TypeError("ACP provider config must be an object");
13
+ }
14
+ const config = value;
15
+ const unknown = Object.keys(config)
16
+ .find((key) => !["argvBefore", "argvAfter", "effortArg", "modelArg", "systemPromptArg"].includes(key));
17
+ if (unknown !== undefined)
18
+ throw new TypeError(`ACP provider config has unknown field ${unknown}`);
19
+ if (!Array.isArray(config.argvBefore)
20
+ || config.argvBefore.some((arg) => typeof arg !== "string" || arg.trim().length === 0)) {
21
+ throw new TypeError("ACP provider config argvBefore must be an array of nonblank strings");
22
+ }
23
+ if (!Array.isArray(config.argvAfter)
24
+ || config.argvAfter.some((arg) => typeof arg !== "string" || arg.trim().length === 0)) {
25
+ throw new TypeError("ACP provider config argvAfter must be an array of nonblank strings");
26
+ }
27
+ const modelArg = argumentName(config, "modelArg");
28
+ const effortArg = argumentName(config, "effortArg");
29
+ const systemPromptArg = argumentName(config, "systemPromptArg");
30
+ return Object.freeze({
31
+ argvBefore: Object.freeze([...config.argvBefore]),
32
+ argvAfter: Object.freeze([...config.argvAfter]),
33
+ ...(modelArg === undefined ? {} : { modelArg }),
34
+ ...(effortArg === undefined ? {} : { effortArg }),
35
+ ...(systemPromptArg === undefined ? {} : { systemPromptArg }),
36
+ });
37
+ }
@@ -122,6 +122,12 @@ function beginAcpPrompt(agent, child, turn, sessionId, input) {
122
122
  else
123
123
  await turn.finish(result, () => child.close(true));
124
124
  },
125
+ forceDispose: async () => {
126
+ if (!turn.open())
127
+ await child.close(true);
128
+ else
129
+ await turn.finish({ kind: "failed", diagnostic: "ACP turn force-disposed" }, () => child.close(true));
130
+ },
125
131
  };
126
132
  }
127
133
  export async function startAcpSession(launch, input, dependencies = {}) {
@@ -1,12 +1,5 @@
1
1
  import type { ProviderAdapter } from "../../provider.js";
2
2
  import type { ProviderExecution } from "../../provider-recipe.js";
3
3
  import { type AcpDependencies } from "./core.js";
4
- export type AcpExecutionConfig = Readonly<{
5
- argvBefore: readonly string[];
6
- argvAfter: readonly string[];
7
- modelArg?: string;
8
- effortArg?: string;
9
- systemPromptArg?: string;
10
- }>;
11
- export declare function decodeAcpConfig(value: unknown): AcpExecutionConfig;
4
+ export { decodeAcpConfig, type AcpExecutionConfig } from "./config.js";
12
5
  export declare function createAcpProvider(execution: ProviderExecution, dependencies?: AcpDependencies): ProviderAdapter;
@@ -1,41 +1,6 @@
1
+ import { decodeAcpConfig } from "./config.js";
1
2
  import { startAcpSession } from "./core.js";
2
- function argumentName(value, key) {
3
- const selected = value[key];
4
- if (selected === undefined)
5
- return undefined;
6
- if (typeof selected !== "string" || selected.trim().length === 0) {
7
- throw new TypeError(`ACP provider config ${key} must be a nonblank string`);
8
- }
9
- return selected;
10
- }
11
- export function decodeAcpConfig(value) {
12
- if (value === null || typeof value !== "object" || Array.isArray(value)) {
13
- throw new TypeError("ACP provider config must be an object");
14
- }
15
- const config = value;
16
- const unknown = Object.keys(config)
17
- .find((key) => !["argvBefore", "argvAfter", "effortArg", "modelArg", "systemPromptArg"].includes(key));
18
- if (unknown !== undefined)
19
- throw new TypeError(`ACP provider config has unknown field ${unknown}`);
20
- if (!Array.isArray(config.argvBefore)
21
- || config.argvBefore.some((arg) => typeof arg !== "string" || arg.trim().length === 0)) {
22
- throw new TypeError("ACP provider config argvBefore must be an array of nonblank strings");
23
- }
24
- if (!Array.isArray(config.argvAfter)
25
- || config.argvAfter.some((arg) => typeof arg !== "string" || arg.trim().length === 0)) {
26
- throw new TypeError("ACP provider config argvAfter must be an array of nonblank strings");
27
- }
28
- const modelArg = argumentName(config, "modelArg");
29
- const effortArg = argumentName(config, "effortArg");
30
- const systemPromptArg = argumentName(config, "systemPromptArg");
31
- return Object.freeze({
32
- argvBefore: Object.freeze([...config.argvBefore]),
33
- argvAfter: Object.freeze([...config.argvAfter]),
34
- ...(modelArg === undefined ? {} : { modelArg }),
35
- ...(effortArg === undefined ? {} : { effortArg }),
36
- ...(systemPromptArg === undefined ? {} : { systemPromptArg }),
37
- });
38
- }
3
+ export { decodeAcpConfig } from "./config.js";
39
4
  function optionAdmission(options, config) {
40
5
  if (options.network !== undefined) {
41
6
  return { kind: "refused", diagnostic: "ACP provider does not support the network option" };
@@ -269,6 +269,11 @@ async function driveClaude(load, execution, drive) {
269
269
  },
270
270
  async abort() {
271
271
  shutDown(new Error("Claude query aborted"));
272
+ await observed.completion;
273
+ },
274
+ async forceDispose() {
275
+ shutDown(new Error("Claude query force-disposed"));
276
+ await observed.completion;
272
277
  },
273
278
  };
274
279
  }
@@ -182,6 +182,11 @@ async function startCodex(execution, input) {
182
182
  events,
183
183
  completion,
184
184
  abort: () => abortTurn(server, state, finish),
185
+ forceDispose: async () => {
186
+ if (!state.settled)
187
+ settle({ kind: "failed", diagnostic: "codex app-server force-disposed" });
188
+ await server.close(true);
189
+ },
185
190
  tell: (tell) => steerTurn(server, state, tell),
186
191
  };
187
192
  }
@@ -10,7 +10,7 @@ function object(value) {
10
10
  }
11
11
  function nativeName(update) {
12
12
  const wire = update;
13
- return nonblank(update.name) ?? nonblank(wire.toolName);
13
+ return nonblank(update.name) ?? nonblank(update.title) ?? nonblank(wire.toolName);
14
14
  }
15
15
  function readCall(name, input) {
16
16
  if (name !== "read_file" && name !== "hashline_read")
@@ -1,6 +1,7 @@
1
1
  import type { ProviderAdapter } from "../provider.js";
2
2
  import { type ProviderExecution } from "../provider-recipe.js";
3
- export declare function resolveProviderExecution(input: unknown): Readonly<{
3
+ export declare function decodeProviderExecution(input: unknown): ProviderExecution;
4
+ export declare function resolveProviderExecution(input: unknown): Promise<Readonly<{
4
5
  execution: ProviderExecution;
5
6
  adapter: ProviderAdapter;
6
- }>;
7
+ }>>;
@@ -1,50 +1,50 @@
1
1
  import { decodeProviderRecipe } from "../provider-recipe.js";
2
- import { createAcpProvider, decodeAcpConfig } from "./acp/index.js";
3
- import { claudeProvider, createClaudeProvider } from "./claude/index.js";
4
- import { createCodexAppServerProvider } from "./codex-app-server/index.js";
5
- import { createGrokBuildProvider } from "./grok-build/index.js";
6
- import { createOpencodeProvider } from "./opencode-sdk/index.js";
7
- import { createPiProvider } from "./pi/index.js";
8
- const PROVIDERS = {
9
- claude: claudeProvider,
10
- };
11
- function providerConfig(execution) {
12
- const { config, kind } = execution;
13
- if (kind === "claude-agent-sdk" && config !== undefined)
14
- throw new TypeError("provider execution config is unsupported by claude-agent-sdk");
15
- if (kind === "opencode-sdk" && config !== undefined)
16
- throw new TypeError("provider execution config is unsupported by opencode-sdk");
17
- if (kind === "acp")
18
- return decodeAcpConfig(config);
19
- return config;
2
+ import { decodeAcpConfig } from "./acp/config.js";
3
+ export function decodeProviderExecution(input) {
4
+ const decoded = decodeProviderRecipe(input);
5
+ if ((decoded.kind === "claude-agent-sdk" || decoded.kind === "opencode-sdk") && decoded.config !== undefined) {
6
+ throw new TypeError(`provider execution config is unsupported by ${decoded.kind}`);
7
+ }
8
+ if (decoded.kind === "pi") {
9
+ if (decoded.executable !== undefined || decoded.config !== undefined) {
10
+ throw new TypeError("Pi provider does not support executable or config");
11
+ }
12
+ if (decoded.env !== undefined && Object.keys(decoded.env).length > 0) {
13
+ throw new TypeError("env injection not supported for provider pi");
14
+ }
15
+ }
16
+ if (decoded.kind === "grok-build" && decoded.config !== undefined) {
17
+ throw new TypeError("Grok Build does not support execution config");
18
+ }
19
+ const config = decoded.kind === "acp" ? decodeAcpConfig(decoded.config) : decoded.config;
20
+ return Object.freeze({
21
+ name: decoded.name,
22
+ kind: decoded.kind,
23
+ ...(decoded.executable === undefined ? {} : { executable: decoded.executable }),
24
+ ...(config === undefined ? {} : { config }),
25
+ ...(decoded.env === undefined ? {} : { env: decoded.env }),
26
+ });
20
27
  }
21
- function adapterFor(execution) {
28
+ async function adapterFor(execution) {
22
29
  if (execution.kind === "acp")
23
- return createAcpProvider(execution);
30
+ return (await import("./acp/index.js")).createAcpProvider(execution);
24
31
  if (execution.kind === "claude-agent-sdk") {
32
+ const { claudeProvider, createClaudeProvider } = await import("./claude/index.js");
25
33
  return execution.executable === undefined && execution.env === undefined
26
- ? PROVIDERS.claude
34
+ ? claudeProvider
27
35
  : createClaudeProvider(async () => await import("@anthropic-ai/claude-agent-sdk"), execution);
28
36
  }
29
37
  if (execution.kind === "codex-app-server")
30
- return createCodexAppServerProvider(execution);
38
+ return (await import("./codex-app-server/index.js")).createCodexAppServerProvider(execution);
31
39
  if (execution.kind === "grok-build")
32
- return createGrokBuildProvider(execution);
40
+ return (await import("./grok-build/index.js")).createGrokBuildProvider(execution);
33
41
  if (execution.kind === "opencode-sdk")
34
- return createOpencodeProvider(execution);
42
+ return (await import("./opencode-sdk/index.js")).createOpencodeProvider(execution);
35
43
  if (execution.kind === "pi")
36
- return createPiProvider(execution);
44
+ return (await import("./pi/index.js")).createPiProvider(execution);
37
45
  throw new TypeError(`unknown Akuma provider kind ${execution.kind}`);
38
46
  }
39
- export function resolveProviderExecution(input) {
40
- const decoded = decodeProviderRecipe(input);
41
- const config = providerConfig(decoded);
42
- const execution = Object.freeze({
43
- name: decoded.name,
44
- kind: decoded.kind,
45
- ...(decoded.executable === undefined ? {} : { executable: decoded.executable }),
46
- ...(config === undefined ? {} : { config }),
47
- ...(decoded.env === undefined ? {} : { env: decoded.env }),
48
- });
49
- return Object.freeze({ execution, adapter: adapterFor(execution) });
47
+ export async function resolveProviderExecution(input) {
48
+ const execution = decodeProviderExecution(input);
49
+ return Object.freeze({ execution, adapter: await adapterFor(execution) });
50
50
  }
@@ -195,6 +195,11 @@ function terminalSettlement(events, close) {
195
195
  });
196
196
  return { completion, finish };
197
197
  }
198
+ async function forceDisposeOpencode(abortController, close, finish) {
199
+ abortController.abort();
200
+ await close();
201
+ await finish({ kind: "failed", diagnostic: "OpenCode session force-disposed" });
202
+ }
198
203
  async function drive(execution, input, loader) {
199
204
  admit(input.options);
200
205
  const signal = input.signal ?? new AbortController().signal;
@@ -256,6 +261,7 @@ async function drive(execution, input, loader) {
256
261
  void session.abort({ path: { id: sessionId }, query: { directory: input.cwd }, throwOnError: true }).catch(() => undefined);
257
262
  await finish({ kind: "failed", diagnostic: "OpenCode session interrupted" });
258
263
  },
264
+ forceDispose: () => forceDisposeOpencode(abortController, closeOnce, finish),
259
265
  };
260
266
  }
261
267
  catch (error) {
@@ -1,4 +1,4 @@
1
- import { type OpencodeClient } from "@opencode-ai/sdk";
1
+ import type { OpencodeClient } from "@opencode-ai/sdk";
2
2
  import type { ResumeCoordinate } from "../../heart/index.js";
3
3
  import type { ProviderExecution } from "../../provider-recipe.js";
4
4
  export type OpencodeSdkSession = Pick<OpencodeClient["session"], "create" | "get" | "fork" | "abort" | "promptAsync" | "messages">;
@@ -1,7 +1,6 @@
1
1
  import net from "node:net";
2
2
  import { abortable } from "../../abort.js";
3
3
  import { spawnDetachedProcess } from "../../../runtime/proc/run.js";
4
- import { createOpencodeClient } from "@opencode-ai/sdk";
5
4
  export const OPENCODE_SDK_PROVIDER = "opencode-sdk";
6
5
  export function parseModel(model) {
7
6
  const slash = model.indexOf("/");
@@ -50,6 +49,7 @@ export async function loadOpencode(execution, cwd, signal, loader) {
50
49
  env: { ...process.env, ...execution.env },
51
50
  log: `${cwd}/.opencode.log`,
52
51
  });
52
+ const { createOpencodeClient } = await import("@opencode-ai/sdk");
53
53
  const client = createOpencodeClient({ baseUrl: `http://127.0.0.1:${port}`, directory: cwd });
54
54
  try {
55
55
  await waitReady(client, cwd, signal);
@@ -1,4 +1,4 @@
1
- import { createAgentSession, DefaultResourceLoader, getAgentDir, ModelRuntime, SessionManager, type CreateAgentSessionOptions } from "@earendil-works/pi-coding-agent";
1
+ import type { createAgentSession, 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<{
@@ -1,4 +1,3 @@
1
- import { createAgentSession, createBashToolDefinition, DefaultResourceLoader, getAgentDir, ModelRuntime, SessionManager, } from "@earendil-works/pi-coding-agent";
2
1
  import { abortable } from "../../abort.js";
3
2
  import { AKUMA_REQUESTS_ENV, AgentEventChannel, } from "../../provider.js";
4
3
  import { piTerminalFailure, translatePiEvent } from "./events.js";
@@ -41,6 +40,14 @@ async function piCreateOptions(sdk, input) {
41
40
  : "sessionFile" in input.session.coordinate
42
41
  ? sdk.SessionManager.open(input.session.coordinate.sessionFile)
43
42
  : (() => { throw new Error("Pi resume requires sessionFile"); })();
43
+ const customTools = input.requests === undefined || input.options.readonly === true
44
+ ? undefined
45
+ : [(await import("@earendil-works/pi-coding-agent")).createBashToolDefinition(input.cwd, {
46
+ spawnHook: (context) => ({
47
+ ...context,
48
+ env: { ...context.env, [AKUMA_REQUESTS_ENV]: input.requests.dir },
49
+ }),
50
+ })];
44
51
  return {
45
52
  cwd: input.cwd,
46
53
  sessionManager,
@@ -48,20 +55,19 @@ async function piCreateOptions(sdk, input) {
48
55
  ...(resourceLoader === undefined ? {} : { resourceLoader }),
49
56
  ...(input.options.effort === undefined ? {} : { thinkingLevel: input.options.effort }),
50
57
  ...(input.options.readonly === undefined ? {} : { tools: ["read", "grep", "find", "ls"] }),
51
- ...(input.requests === undefined || input.options.readonly === true
52
- ? {}
53
- : { customTools: [createBashToolDefinition(input.cwd, {
54
- spawnHook: (context) => ({
55
- ...context,
56
- env: { ...context.env, [AKUMA_REQUESTS_ENV]: input.requests.dir },
57
- }),
58
- })] }),
58
+ ...(customTools === undefined ? {} : { customTools }),
59
59
  };
60
60
  }
61
61
  async function createPiSession(sdk, input, signal) {
62
62
  const setup = piCreateOptions(sdk, input).then(async (options) => await sdk.createAgentSession(options));
63
63
  return await abortable(setup, signal, (created) => created.session.dispose());
64
64
  }
65
+ function forceDisposePi(dispose, settle, setAborting) {
66
+ setAborting();
67
+ dispose();
68
+ settle({ kind: "failed", diagnostic: "Pi session force-disposed" });
69
+ return Promise.resolve();
70
+ }
65
71
  async function drivePi(sdk, input, signal) {
66
72
  const created = await createPiSession(sdk, input, signal);
67
73
  const native = created.session;
@@ -155,6 +161,7 @@ async function drivePi(sdk, input, signal) {
155
161
  })();
156
162
  return abortRequest;
157
163
  },
164
+ forceDispose: () => forceDisposePi(dispose, settle, () => { aborting = true; }),
158
165
  };
159
166
  }
160
167
  function piSessionFile(coordinate) {
@@ -170,8 +177,17 @@ async function forkPi(sdk, input) {
170
177
  throw new Error("Pi fork did not create a distinct child session file");
171
178
  return { session: { sessionFile: child } };
172
179
  }
173
- const defaultSdk = { createAgentSession, DefaultResourceLoader, getAgentDir, ModelRuntime, SessionManager };
174
- export function createPiProvider(execution = { name: "pi", kind: "pi" }, load = async () => defaultSdk) {
180
+ async function loadPiSdk() {
181
+ const sdk = await import("@earendil-works/pi-coding-agent");
182
+ return {
183
+ createAgentSession: sdk.createAgentSession,
184
+ DefaultResourceLoader: sdk.DefaultResourceLoader,
185
+ getAgentDir: sdk.getAgentDir,
186
+ ModelRuntime: sdk.ModelRuntime,
187
+ SessionManager: sdk.SessionManager,
188
+ };
189
+ }
190
+ export function createPiProvider(execution = { name: "pi", kind: "pi" }, load = loadPiSdk) {
175
191
  if (execution.executable !== undefined || execution.config !== undefined) {
176
192
  throw new TypeError("Pi provider does not support executable or config");
177
193
  }
@@ -41,6 +41,7 @@ export type UpstreamExecutionPort = Readonly<{
41
41
  deliver(input: ContractCall & Readonly<{
42
42
  message?: string;
43
43
  includeDirty: boolean;
44
+ materializeConflict: boolean;
44
45
  }>): Promise<Readonly<{
45
46
  result: unknown;
46
47
  deliveryFactId?: string;
@@ -222,6 +222,7 @@ async function executeDeliver(input, request) {
222
222
  contractId: request.contractId,
223
223
  ...(request.message === undefined ? {} : { message: request.message }),
224
224
  includeDirty: request.includeDirty,
225
+ materializeConflict: request.materializeConflict,
225
226
  requester: request.requester,
226
227
  signal: input.signal,
227
228
  });
@@ -303,12 +304,8 @@ async function serveUpstream(input) {
303
304
  try {
304
305
  const served = await execute(input, request);
305
306
  outcome = { kind: "returned", result: served.result };
306
- if (served.service === undefined) {
307
- fact = await voidRequest(input.paths, request.id, "body completed without a durable Contract fact reference");
308
- await projectReceipt(input.directory, fact);
309
- return;
310
- }
311
- service = served.service;
307
+ if (served.service !== undefined)
308
+ service = served.service;
312
309
  }
313
310
  catch (error) {
314
311
  outcome = { kind: "failed", failure: failure(error) };
@@ -368,37 +365,40 @@ export class BodyRequestPump {
368
365
  return new BodyRequestPump(input);
369
366
  }
370
367
  async run() {
371
- try {
372
- while (!this.closeSignal.signal.aborted) {
373
- for (const name of await requestFiles(this.directory)) {
374
- if (this.closeSignal.signal.aborted)
375
- return;
376
- const id = name.slice(0, -".request.json".length);
377
- if (this.handled.has(id))
378
- continue;
379
- const claim = decodeClaim(await readFile(join(this.directory, name), "utf8"), id);
380
- if (claim !== null && !this.admissionClosed) {
381
- await serve({
382
- directory: this.directory,
383
- claim,
384
- ...this.input,
385
- signal: this.executionSignal.signal,
386
- admissionOpen: () => !this.admissionClosed,
387
- });
388
- }
368
+ while (!this.closeSignal.signal.aborted) {
369
+ for (const name of await requestFiles(this.directory)) {
370
+ if (this.closeSignal.signal.aborted)
371
+ return;
372
+ const id = name.slice(0, -".request.json".length);
373
+ if (this.handled.has(id))
374
+ continue;
375
+ const path = join(this.directory, name);
376
+ const claim = decodeClaim(await readFile(path, "utf8"), id);
377
+ if (claim === null) {
378
+ await rm(path, { force: true });
389
379
  this.handled.add(id);
380
+ continue;
390
381
  }
391
- try {
392
- await abortableDelay(POLL_MS, this.closeSignal.signal);
382
+ if (!this.admissionClosed) {
383
+ await serve({
384
+ directory: this.directory,
385
+ claim,
386
+ ...this.input,
387
+ signal: this.executionSignal.signal,
388
+ admissionOpen: () => !this.admissionClosed,
389
+ });
393
390
  }
394
- catch {
391
+ this.handled.add(id);
392
+ }
393
+ try {
394
+ await abortableDelay(POLL_MS, this.closeSignal.signal);
395
+ }
396
+ catch (error) {
397
+ if (this.closeSignal.signal.aborted)
395
398
  return;
396
- }
399
+ throw error;
397
400
  }
398
401
  }
399
- catch {
400
- // Request transport loss is recovered from Heart on the next Body.
401
- }
402
402
  }
403
403
  async close() {
404
404
  this.stopAdmission();