@astrosheep/keiyaku 4.0.5 → 4.1.0

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 (166) 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 +19 -7
  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-recipe.d.ts +2 -0
  27. package/build/src/akuma/provider-recipe.js +10 -0
  28. package/build/src/akuma/provider.d.ts +3 -1
  29. package/build/src/akuma/providers/acp/config.d.ts +10 -0
  30. package/build/src/akuma/providers/acp/config.js +45 -0
  31. package/build/src/akuma/providers/acp/core.d.ts +5 -2
  32. package/build/src/akuma/providers/acp/core.js +35 -16
  33. package/build/src/akuma/providers/acp/index.d.ts +1 -8
  34. package/build/src/akuma/providers/acp/index.js +10 -39
  35. package/build/src/akuma/providers/claude/index.d.ts +1 -1
  36. package/build/src/akuma/providers/claude/index.js +14 -5
  37. package/build/src/akuma/providers/codex-app-server/index.js +9 -1
  38. package/build/src/akuma/providers/grok-build/index.js +16 -2
  39. package/build/src/akuma/providers/index.d.ts +3 -2
  40. package/build/src/akuma/providers/index.js +37 -37
  41. package/build/src/akuma/providers/opencode-sdk/index.js +9 -0
  42. package/build/src/akuma/providers/opencode-sdk/session.d.ts +1 -1
  43. package/build/src/akuma/providers/opencode-sdk/session.js +1 -1
  44. package/build/src/akuma/providers/pi/index.d.ts +1 -1
  45. package/build/src/akuma/providers/pi/index.js +30 -12
  46. package/build/src/akuma/request-serve.d.ts +1 -0
  47. package/build/src/akuma/request-serve.js +31 -31
  48. package/build/src/akuma/request-wire.d.ts +3 -2
  49. package/build/src/akuma/request-wire.js +5 -2
  50. package/build/src/akuma-body.js +15 -1
  51. package/build/src/alias/index.d.ts +1 -0
  52. package/build/src/alias/index.js +34 -1
  53. package/build/src/body/amend.js +0 -9
  54. package/build/src/body/region.js +2 -2
  55. package/build/src/cli/accepted.js +6 -0
  56. package/build/src/cli/commands/akuma-invoke.js +1 -0
  57. package/build/src/cli/commands/akuma.d.ts +1 -0
  58. package/build/src/cli/commands/akuma.js +11 -8
  59. package/build/src/cli/commands/contract.d.ts +28 -15
  60. package/build/src/cli/commands/contract.js +34 -18
  61. package/build/src/cli/commands/install.d.ts +2 -2
  62. package/build/src/cli/commands/install.js +6 -6
  63. package/build/src/cli/commands/nuke.d.ts +10 -0
  64. package/build/src/cli/commands/nuke.js +20 -0
  65. package/build/src/cli/commands/task-invoke.d.ts +19 -1
  66. package/build/src/cli/commands/task-invoke.js +63 -29
  67. package/build/src/cli/commands/task-query.d.ts +2 -2
  68. package/build/src/cli/commands/task-query.js +1 -2
  69. package/build/src/cli/commands/task.js +30 -22
  70. package/build/src/cli/coordinates.d.ts +5 -2
  71. package/build/src/cli/coordinates.js +5 -52
  72. package/build/src/cli/invoke.d.ts +5 -4
  73. package/build/src/cli/invoke.js +89 -59
  74. package/build/src/cli/main.d.ts +0 -2
  75. package/build/src/cli/main.js +4 -140
  76. package/build/src/cli/parse.d.ts +15 -2
  77. package/build/src/cli/parse.js +73 -8
  78. package/build/src/cli/render/akuma.js +95 -49
  79. package/build/src/cli/render/catalog.js +5 -1
  80. package/build/src/cli/render/contract.js +72 -49
  81. package/build/src/cli/render/kanshi.js +157 -99
  82. package/build/src/cli/render/nuke.d.ts +3 -0
  83. package/build/src/cli/render/nuke.js +8 -0
  84. package/build/src/cli/render/receipt.d.ts +1 -0
  85. package/build/src/cli/render/receipt.js +18 -14
  86. package/build/src/cli/render/refusal.d.ts +2 -1
  87. package/build/src/cli/render/refusal.js +37 -0
  88. package/build/src/cli/render/task.js +63 -11
  89. package/build/src/cli/render/text.js +6 -1
  90. package/build/src/cli/result.d.ts +28 -2
  91. package/build/src/cli/runtime.d.ts +2 -0
  92. package/build/src/cli/runtime.js +140 -0
  93. package/build/src/cli/usage.d.ts +0 -2
  94. package/build/src/cli/usage.js +0 -4
  95. package/build/src/contract-worktree.d.ts +26 -12
  96. package/build/src/contract-worktree.js +119 -64
  97. package/build/src/git/hooks.d.ts +2 -0
  98. package/build/src/git/hooks.js +34 -1
  99. package/build/src/git/integration.d.ts +4 -0
  100. package/build/src/git/integration.js +38 -0
  101. package/build/src/git/nuke.d.ts +2 -0
  102. package/build/src/git/nuke.js +92 -0
  103. package/build/src/git/process.d.ts +2 -0
  104. package/build/src/git/repository.js +4 -1
  105. package/build/src/git/target-placement.d.ts +2 -1
  106. package/build/src/git/target-placement.js +6 -7
  107. package/build/src/git/tender.d.ts +14 -2
  108. package/build/src/git/tender.js +45 -6
  109. package/build/src/git/workspace.d.ts +3 -0
  110. package/build/src/kanshi/read.js +37 -13
  111. package/build/src/kanshi/report.d.ts +2 -1
  112. package/build/src/library/akuma-creation.d.ts +1 -0
  113. package/build/src/library/akuma-creation.js +8 -1
  114. package/build/src/library/audit.d.ts +1 -0
  115. package/build/src/library/audit.js +1 -0
  116. package/build/src/library/continuation.d.ts +26 -0
  117. package/build/src/library/continuation.js +78 -0
  118. package/build/src/library/contract.d.ts +14 -8
  119. package/build/src/library/contract.js +82 -35
  120. package/build/src/library/delivery.d.ts +18 -11
  121. package/build/src/library/keiyaku.d.ts +5 -1
  122. package/build/src/library/keiyaku.js +2 -0
  123. package/build/src/library/mutation.d.ts +1 -0
  124. package/build/src/library/mutation.js +14 -4
  125. package/build/src/library/nuke.d.ts +14 -0
  126. package/build/src/library/nuke.js +54 -0
  127. package/build/src/library/reconcile.js +5 -2
  128. package/build/src/library/refusal.d.ts +10 -1
  129. package/build/src/library/repo.d.ts +1 -0
  130. package/build/src/library/repo.js +2 -0
  131. package/build/src/protocol/audit.js +12 -5
  132. package/build/src/protocol/completion.d.ts +11 -1
  133. package/build/src/protocol/completion.js +68 -15
  134. package/build/src/protocol/deliver.d.ts +25 -3
  135. package/build/src/protocol/deliver.js +148 -14
  136. package/build/src/protocol/intent.d.ts +1 -0
  137. package/build/src/protocol/intent.js +7 -1
  138. package/build/src/protocol/operations.d.ts +36 -4
  139. package/build/src/protocol/operations.js +2 -2
  140. package/build/src/protocol/placement.d.ts +9 -1
  141. package/build/src/protocol/placement.js +5 -4
  142. package/build/src/protocol/read/status.d.ts +16 -4
  143. package/build/src/protocol/read/status.js +54 -12
  144. package/build/src/protocol/review.d.ts +1 -0
  145. package/build/src/protocol/review.js +8 -0
  146. package/build/src/runtime/proc/run.js +9 -5
  147. package/build/src/settlement/settle.js +1 -2
  148. package/build/src/task/board.d.ts +21 -2
  149. package/build/src/task/board.js +25 -12
  150. package/build/src/task/compose.d.ts +1 -1
  151. package/build/src/task/compose.js +2 -7
  152. package/build/src/task/context.d.ts +14 -2
  153. package/build/src/task/context.js +30 -7
  154. package/build/src/task/identity.js +14 -4
  155. package/build/src/task/index.d.ts +7 -5
  156. package/build/src/task/index.js +21 -18
  157. package/build/src/task/mutation.d.ts +7 -2
  158. package/build/src/task/mutation.js +14 -6
  159. package/build/src/task/operations.d.ts +14 -7
  160. package/build/src/task/operations.js +37 -50
  161. package/build/src/task/query.js +2 -2
  162. package/build/src/task/store.d.ts +3 -0
  163. package/build/src/task/store.js +21 -2
  164. package/build/src/workspace-place.d.ts +2 -0
  165. package/build/src/workspace-place.js +27 -1
  166. 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
+ }
@@ -1,9 +1,11 @@
1
+ export type SystemPromptMode = "append" | "replace";
1
2
  export type ProviderOptions = Readonly<{
2
3
  model?: string;
3
4
  effort?: string;
4
5
  readonly?: true;
5
6
  network?: "disabled" | "enabled";
6
7
  systemPrompt?: string;
8
+ systemPromptMode?: SystemPromptMode;
7
9
  }>;
8
10
  export type ReadonlyRestraint = Readonly<{
9
11
  enforcement: "native";
@@ -34,12 +34,22 @@ export function decodeProviderOptions(value) {
34
34
  throw new TypeError("provider option network must be disabled, enabled");
35
35
  }
36
36
  const systemPrompt = optionText(options, "systemPrompt", "allow");
37
+ const systemPromptMode = options.systemPromptMode;
38
+ if (systemPromptMode !== undefined) {
39
+ if (systemPromptMode !== "append" && systemPromptMode !== "replace") {
40
+ throw new TypeError("provider option systemPromptMode must be append, replace");
41
+ }
42
+ if (systemPrompt === undefined) {
43
+ throw new TypeError("provider option systemPromptMode requires systemPrompt");
44
+ }
45
+ }
37
46
  return Object.freeze({
38
47
  ...(model === undefined ? {} : { model }),
39
48
  ...(effort === undefined ? {} : { effort }),
40
49
  ...(options.readonly === undefined ? {} : { readonly: true }),
41
50
  ...(network === undefined ? {} : { network }),
42
51
  ...(systemPrompt === undefined ? {} : { systemPrompt }),
52
+ ...(systemPromptMode === undefined ? {} : { systemPromptMode }),
43
53
  });
44
54
  }
45
55
  export function decodeReadonlyRestraint(value) {
@@ -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,10 @@
1
+ import type { SystemPromptMode } from "../../provider-recipe.js";
2
+ export type AcpExecutionConfig = Readonly<{
3
+ argvBefore: readonly string[];
4
+ argvAfter: readonly string[];
5
+ modelArg?: string;
6
+ effortArg?: string;
7
+ systemPromptArg?: string;
8
+ systemPromptMode?: SystemPromptMode;
9
+ }>;
10
+ export declare function decodeAcpConfig(value: unknown): AcpExecutionConfig;
@@ -0,0 +1,45 @@
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", "systemPromptMode"].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
+ const systemPromptMode = config.systemPromptMode;
31
+ if (systemPromptMode !== undefined && systemPromptMode !== "append" && systemPromptMode !== "replace") {
32
+ throw new TypeError("ACP provider config systemPromptMode must be append, replace");
33
+ }
34
+ if (systemPromptMode !== undefined && systemPromptArg === undefined) {
35
+ throw new TypeError("ACP provider config systemPromptMode requires systemPromptArg");
36
+ }
37
+ return Object.freeze({
38
+ argvBefore: Object.freeze([...config.argvBefore]),
39
+ argvAfter: Object.freeze([...config.argvAfter]),
40
+ ...(modelArg === undefined ? {} : { modelArg }),
41
+ ...(effortArg === undefined ? {} : { effortArg }),
42
+ ...(systemPromptArg === undefined ? {} : { systemPromptArg }),
43
+ ...(systemPromptMode === undefined ? {} : { systemPromptMode }),
44
+ });
45
+ }
@@ -1,16 +1,19 @@
1
- import * as acp from "@agentclientprotocol/sdk";
1
+ import type * as AcpSdk from "@agentclientprotocol/sdk";
2
2
  import { spawnStdioProcess } from "../../../runtime/proc/stdio.js";
3
3
  import { type ProviderAdapter, type Session } from "../../provider.js";
4
4
  import { type AcpToolInterpreter, type AcpToolUpdate } from "./events.js";
5
5
  export type { AcpToolInterpreter, AcpToolUpdate };
6
6
  export type AcpStartInput = Parameters<ProviderAdapter["start"]>[0] | Parameters<NonNullable<ProviderAdapter["resume"]>>[0];
7
+ export type AcpSessionMeta = Readonly<Record<string, unknown>>;
7
8
  export type AcpDependencies = Readonly<{
8
9
  spawnProcess?: typeof spawnStdioProcess;
9
10
  interpretTool?: AcpToolInterpreter;
11
+ freshSessionMeta?: AcpSessionMeta;
12
+ loadSessionMeta?: AcpSessionMeta;
10
13
  }>;
11
14
  export type AcpLiveSession = Readonly<{
12
15
  session: Session;
13
- agent: acp.ClientContext;
16
+ agent: AcpSdk.ClientContext;
14
17
  sessionId: string;
15
18
  open(): boolean;
16
19
  }>;
@@ -1,10 +1,9 @@
1
- import * as acp from "@agentclientprotocol/sdk";
2
1
  import { Readable, Writable } from "node:stream";
3
2
  import { spawnStdioProcess } from "../../../runtime/proc/stdio.js";
4
3
  import { abortable } from "../../abort.js";
5
4
  import { AKUMA_REQUESTS_ENV, AgentEventChannel, } from "../../provider.js";
6
5
  import { EMPTY_ACP_EVENT_STATE, flushAcpEvents, mapAcpUpdate, } from "./events.js";
7
- function diagnostic(error) {
6
+ function diagnostic(acp, error) {
8
7
  if (!(error instanceof Error))
9
8
  return String(error);
10
9
  if (!(error instanceof acp.RequestError) || error.data === undefined)
@@ -18,7 +17,7 @@ function diagnostic(error) {
18
17
  }
19
18
  return data.length === 0 ? `${error.message} [${error.code}]` : `${error.message} [${error.code}]: ${data}`;
20
19
  }
21
- function createAcpClient(onUpdate) {
20
+ function createAcpClient(acp, onUpdate) {
22
21
  return acp.client({ name: "keiyaku" })
23
22
  .onNotification(acp.methods.client.session.update, ({ params }) => onUpdate(params));
24
23
  }
@@ -29,7 +28,10 @@ function promptResult(response) {
29
28
  }
30
29
  return { kind: "failed", diagnostic: `ACP prompt ended ${response.stopReason}` };
31
30
  }
32
- async function establishSession(agent, input) {
31
+ function requestMeta(meta) {
32
+ return meta === undefined || Object.keys(meta).length === 0 ? {} : { _meta: meta };
33
+ }
34
+ async function establishSession(acp, agent, input, dependencies) {
33
35
  const initialized = await agent.request(acp.methods.agent.initialize, {
34
36
  protocolVersion: acp.PROTOCOL_VERSION,
35
37
  clientInfo: { name: "keiyaku", version: "4" },
@@ -38,15 +40,24 @@ async function establishSession(agent, input) {
38
40
  throw new Error("ACP agent does not advertise session/load");
39
41
  }
40
42
  if (input.session.kind === "fresh") {
41
- return (await agent.request(acp.methods.agent.session.new, { cwd: input.cwd, mcpServers: [] })).sessionId;
43
+ return (await agent.request(acp.methods.agent.session.new, {
44
+ cwd: input.cwd,
45
+ mcpServers: [],
46
+ ...requestMeta(dependencies.freshSessionMeta),
47
+ })).sessionId;
42
48
  }
43
49
  const sessionId = input.session.coordinate.sessionId;
44
50
  if (sessionId === undefined)
45
51
  throw new Error("ACP resume coordinate has no session id");
46
- await agent.request(acp.methods.agent.session.load, { cwd: input.cwd, mcpServers: [], sessionId });
52
+ await agent.request(acp.methods.agent.session.load, {
53
+ cwd: input.cwd,
54
+ mcpServers: [],
55
+ sessionId,
56
+ ...requestMeta(dependencies.loadSessionMeta),
57
+ });
47
58
  return sessionId;
48
59
  }
49
- function createAcpTurn(connection, interpret) {
60
+ function createAcpTurn(acp, connection, interpret) {
50
61
  const events = new AgentEventChannel();
51
62
  let state = EMPTY_ACP_EVENT_STATE;
52
63
  let terminal = false;
@@ -66,7 +77,7 @@ function createAcpTurn(connection, interpret) {
66
77
  terminal = true;
67
78
  let completionResult = result;
68
79
  const failCleanup = (error) => {
69
- completionResult = { kind: "failed", diagnostic: `ACP cleanup failed: ${diagnostic(error)}` };
80
+ completionResult = { kind: "failed", diagnostic: `ACP cleanup failed: ${diagnostic(acp, error)}` };
70
81
  };
71
82
  try {
72
83
  await cleanup();
@@ -96,11 +107,12 @@ function createAcpTurn(connection, interpret) {
96
107
  open: () => !terminal,
97
108
  };
98
109
  }
99
- function beginAcpPrompt(agent, child, turn, sessionId, input) {
110
+ function beginAcpPrompt(context, child, turn, sessionId, input) {
111
+ const { acp, agent } = context;
100
112
  void agent.request(acp.methods.agent.session.prompt, {
101
113
  sessionId,
102
114
  prompt: [{ type: "text", text: input.body }, ...input.launchTells.map(({ text }) => ({ type: "text", text }))],
103
- }).then((response) => turn.finish(promptResult(response), () => child.endInputAndDrain()), (error) => turn.finish({ kind: "failed", diagnostic: diagnostic(error) }, () => child.endInputAndDrain()));
115
+ }).then((response) => turn.finish(promptResult(response), () => child.endInputAndDrain()), (error) => turn.finish({ kind: "failed", diagnostic: diagnostic(acp, error) }, () => child.endInputAndDrain()));
104
116
  void child.exited.then((exit) => turn.finish({
105
117
  kind: "failed",
106
118
  diagnostic: exit.stderr || `ACP process exited${exit.code === null ? "" : ` with code ${exit.code}`}`,
@@ -115,16 +127,23 @@ function beginAcpPrompt(agent, child, turn, sessionId, input) {
115
127
  await agent.notify(acp.methods.agent.session.cancel, { sessionId });
116
128
  }
117
129
  catch (error) {
118
- result = { kind: "failed", diagnostic: diagnostic(error) };
130
+ result = { kind: "failed", diagnostic: diagnostic(acp, error) };
119
131
  }
120
132
  if (!turn.open())
121
133
  await child.close(true);
122
134
  else
123
135
  await turn.finish(result, () => child.close(true));
124
136
  },
137
+ forceDispose: async () => {
138
+ if (!turn.open())
139
+ await child.close(true);
140
+ else
141
+ await turn.finish({ kind: "failed", diagnostic: "ACP turn force-disposed" }, () => child.close(true));
142
+ },
125
143
  };
126
144
  }
127
145
  export async function startAcpSession(launch, input, dependencies = {}) {
146
+ const acp = await import("@agentclientprotocol/sdk");
128
147
  const signal = input.signal ?? new AbortController().signal;
129
148
  signal.throwIfAborted();
130
149
  const child = (dependencies.spawnProcess ?? spawnStdioProcess)({
@@ -138,15 +157,15 @@ export async function startAcpSession(launch, input, dependencies = {}) {
138
157
  });
139
158
  let sessionId;
140
159
  let turn;
141
- const connection = createAcpClient((notification) => {
160
+ const connection = createAcpClient(acp, (notification) => {
142
161
  if (notification.sessionId === sessionId)
143
162
  turn.update(notification.update);
144
163
  }).connect(acp.ndJsonStream(Writable.toWeb(child.input), Readable.toWeb(child.output)));
145
- turn = createAcpTurn(connection, dependencies.interpretTool);
164
+ turn = createAcpTurn(acp, connection, dependencies.interpretTool);
146
165
  try {
147
- sessionId = await abortable(establishSession(connection.agent, input), signal);
166
+ sessionId = await abortable(establishSession(acp, connection.agent, input, dependencies), signal);
148
167
  turn.events.emit({ type: "session", coordinate: { sessionId } });
149
- const session = beginAcpPrompt(connection.agent, child, turn, sessionId, input);
168
+ const session = beginAcpPrompt({ acp, agent: connection.agent }, child, turn, sessionId, input);
150
169
  return { session, agent: connection.agent, sessionId, open: turn.open };
151
170
  }
152
171
  catch (error) {
@@ -155,7 +174,7 @@ export async function startAcpSession(launch, input, dependencies = {}) {
155
174
  await child.close(true);
156
175
  }
157
176
  catch (cleanup) {
158
- throw new Error(`${diagnostic(error)}; ACP cleanup failed: ${diagnostic(cleanup)}`, { cause: error });
177
+ throw new Error(`${diagnostic(acp, error)}; ACP cleanup failed: ${diagnostic(acp, cleanup)}`, { cause: error });
159
178
  }
160
179
  turn.events.end();
161
180
  throw error;
@@ -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" };
@@ -46,8 +11,14 @@ function optionAdmission(options, config) {
46
11
  if (options.effort !== undefined && config.effortArg === undefined) {
47
12
  return { kind: "refused", diagnostic: "ACP provider has no effort argument mapping" };
48
13
  }
49
- if (options.systemPrompt !== undefined && options.systemPrompt.length > 0 && config.systemPromptArg === undefined) {
50
- return { kind: "refused", diagnostic: "ACP provider has no systemPrompt argument mapping" };
14
+ if (options.systemPrompt !== undefined && options.systemPrompt.length > 0) {
15
+ if (config.systemPromptArg === undefined) {
16
+ return { kind: "refused", diagnostic: "ACP provider has no systemPrompt argument mapping" };
17
+ }
18
+ if (options.systemPromptMode !== undefined
19
+ && options.systemPromptMode !== (config.systemPromptMode ?? "replace")) {
20
+ return { kind: "refused", diagnostic: "ACP provider systemPromptMode does not match the configured argument mode" };
21
+ }
51
22
  }
52
23
  return {
53
24
  kind: "admitted",
@@ -19,7 +19,7 @@ export type ClaudeSdk = Readonly<{
19
19
  sessionId: string;
20
20
  }>>;
21
21
  }>;
22
- export declare function createClaudeProvider(load: () => Promise<ClaudeSdk>, execution?: ClaudeExecution): ProviderAdapter;
22
+ export declare function createClaudeProvider(loadOrExecution?: (() => Promise<ClaudeSdk>) | ClaudeExecution, execution?: ClaudeExecution): ProviderAdapter;
23
23
  export declare const claudeProvider: Readonly<{
24
24
  admitOptions(options: ProviderOptions): import("../../provider.js").ProviderOptionAdmission;
25
25
  fork?(input: Readonly<{
@@ -74,7 +74,9 @@ function claudeQueryOptions(input, execution, abortController) {
74
74
  ...(input.options.model === undefined ? {} : { model: input.options.model }),
75
75
  ...(input.options.effort === undefined ? {} : { effort: input.options.effort }),
76
76
  ...(input.options.systemPrompt === undefined || input.options.systemPrompt.length === 0 ? {} : {
77
- systemPrompt: { type: "preset", preset: "claude_code", append: input.options.systemPrompt },
77
+ systemPrompt: input.options.systemPromptMode === "replace"
78
+ ? input.options.systemPrompt
79
+ : { type: "preset", preset: "claude_code", append: input.options.systemPrompt },
78
80
  }),
79
81
  ...(input.session.kind === "fresh" ? {} : { resume: claudeSessionId(input.session.coordinate) }),
80
82
  };
@@ -269,15 +271,22 @@ async function driveClaude(load, execution, drive) {
269
271
  },
270
272
  async abort() {
271
273
  shutDown(new Error("Claude query aborted"));
274
+ await observed.completion;
275
+ },
276
+ async forceDispose() {
277
+ shutDown(new Error("Claude query force-disposed"));
278
+ await observed.completion;
272
279
  },
273
280
  };
274
281
  }
275
- export function createClaudeProvider(load, execution = {}) {
282
+ export function createClaudeProvider(loadOrExecution = async () => await import("@anthropic-ai/claude-agent-sdk"), execution = {}) {
283
+ const load = typeof loadOrExecution === "function" ? loadOrExecution : async () => await import("@anthropic-ai/claude-agent-sdk");
284
+ const selectedExecution = typeof loadOrExecution === "function" ? execution : loadOrExecution;
276
285
  return {
277
286
  admitOptions: admitClaudeOptions,
278
- fork: (input) => forkClaude(load, execution, input),
279
- start: (input) => driveClaude(load, execution, input),
280
- resume: (input) => driveClaude(load, execution, input),
287
+ fork: (input) => forkClaude(load, selectedExecution, input),
288
+ start: (input) => driveClaude(load, selectedExecution, input),
289
+ resume: (input) => driveClaude(load, selectedExecution, input),
281
290
  };
282
291
  }
283
292
  export const claudeProvider = createClaudeProvider(async () => await import("@anthropic-ai/claude-agent-sdk"));
@@ -66,7 +66,10 @@ async function admitTurn(server, input, state, events, config) {
66
66
  ...(config === undefined ? {} : { config }),
67
67
  ...(input.options.model === undefined ? {} : { model: input.options.model }),
68
68
  ...(input.options.systemPrompt === undefined || input.options.systemPrompt.length === 0
69
- ? {} : { developerInstructions: input.options.systemPrompt }),
69
+ ? {}
70
+ : input.options.systemPromptMode === "replace"
71
+ ? { baseInstructions: input.options.systemPrompt }
72
+ : { developerInstructions: input.options.systemPrompt }),
70
73
  };
71
74
  if (input.session.kind === "fresh") {
72
75
  state.threadId = threadId(await server.request("thread/start", threadParams));
@@ -182,6 +185,11 @@ async function startCodex(execution, input) {
182
185
  events,
183
186
  completion,
184
187
  abort: () => abortTurn(server, state, finish),
188
+ forceDispose: async () => {
189
+ if (!state.settled)
190
+ settle({ kind: "failed", diagnostic: "codex app-server force-disposed" });
191
+ await server.close(true);
192
+ },
185
193
  tell: (tell) => steerTurn(server, state, tell),
186
194
  };
187
195
  }
@@ -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")
@@ -71,7 +71,9 @@ function optionAdmission(options) {
71
71
  if (options.network !== undefined) {
72
72
  return { kind: "refused", diagnostic: "Grok Build does not support the network option" };
73
73
  }
74
- if (options.systemPrompt !== undefined && options.systemPrompt.length > 0) {
74
+ if (options.systemPrompt !== undefined
75
+ && options.systemPrompt.length > 0
76
+ && options.systemPromptMode === undefined) {
75
77
  return { kind: "refused", diagnostic: "Grok Build does not support the systemPrompt option" };
76
78
  }
77
79
  return {
@@ -85,6 +87,17 @@ function optionAdmission(options) {
85
87
  }),
86
88
  };
87
89
  }
90
+ function grokSessionMeta(options) {
91
+ if (options.systemPrompt === undefined || options.systemPrompt.length === 0)
92
+ return {};
93
+ if (options.systemPromptMode === "append")
94
+ return { freshSessionMeta: { rules: options.systemPrompt } };
95
+ if (options.systemPromptMode === "replace") {
96
+ const meta = { systemPromptOverride: options.systemPrompt };
97
+ return { freshSessionMeta: meta, loadSessionMeta: meta };
98
+ }
99
+ return {};
100
+ }
88
101
  function argv(execution, options) {
89
102
  if (execution.executable === undefined)
90
103
  throw new Error("Grok Build provider execution requires executable");
@@ -129,6 +142,7 @@ export function createGrokBuildProvider(execution, dependencies = {}) {
129
142
  return withInterject(await startAcpSession(launch, input, {
130
143
  ...dependencies,
131
144
  interpretTool: interpretGrokTool,
145
+ ...grokSessionMeta(input.options),
132
146
  }));
133
147
  };
134
148
  return {
@@ -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
+ }>>;