@astrosheep/keiyaku 4.0.6 → 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 (65) hide show
  1. package/build/src/akuma/archetype.js +8 -1
  2. package/build/src/akuma/provider-recipe.d.ts +2 -0
  3. package/build/src/akuma/provider-recipe.js +10 -0
  4. package/build/src/akuma/providers/acp/config.d.ts +2 -0
  5. package/build/src/akuma/providers/acp/config.js +9 -1
  6. package/build/src/akuma/providers/acp/core.d.ts +5 -2
  7. package/build/src/akuma/providers/acp/core.js +29 -16
  8. package/build/src/akuma/providers/acp/index.js +8 -2
  9. package/build/src/akuma/providers/claude/index.d.ts +1 -1
  10. package/build/src/akuma/providers/claude/index.js +9 -5
  11. package/build/src/akuma/providers/codex-app-server/index.js +4 -1
  12. package/build/src/akuma/providers/grok-build/index.js +15 -1
  13. package/build/src/akuma/providers/index.js +1 -1
  14. package/build/src/akuma/providers/opencode-sdk/index.js +3 -0
  15. package/build/src/akuma/providers/pi/index.js +3 -1
  16. package/build/src/body/amend.js +0 -9
  17. package/build/src/body/region.js +2 -2
  18. package/build/src/cli/accepted.js +2 -0
  19. package/build/src/cli/commands/contract.d.ts +5 -3
  20. package/build/src/cli/commands/contract.js +23 -13
  21. package/build/src/cli/commands/task-invoke.d.ts +4 -1
  22. package/build/src/cli/commands/task-invoke.js +6 -3
  23. package/build/src/cli/commands/task.js +10 -2
  24. package/build/src/cli/parse.js +2 -2
  25. package/build/src/cli/render/akuma.js +42 -11
  26. package/build/src/cli/render/catalog.js +5 -1
  27. package/build/src/cli/render/contract.js +25 -3
  28. package/build/src/cli/render/kanshi.js +142 -94
  29. package/build/src/cli/render/receipt.d.ts +1 -0
  30. package/build/src/cli/render/receipt.js +18 -14
  31. package/build/src/cli/render/refusal.js +6 -0
  32. package/build/src/cli/render/task.js +63 -11
  33. package/build/src/cli/result.d.ts +8 -1
  34. package/build/src/contract-worktree.d.ts +0 -2
  35. package/build/src/contract-worktree.js +13 -24
  36. package/build/src/git/nuke.js +1 -6
  37. package/build/src/git/reconcile.d.ts +0 -2
  38. package/build/src/git/reconcile.js +2 -33
  39. package/build/src/git/tender.d.ts +13 -2
  40. package/build/src/git/tender.js +43 -4
  41. package/build/src/kanshi/read.js +32 -12
  42. package/build/src/kanshi/report.d.ts +2 -1
  43. package/build/src/library/continuation.d.ts +26 -0
  44. package/build/src/library/continuation.js +78 -0
  45. package/build/src/library/contract.d.ts +9 -5
  46. package/build/src/library/contract.js +27 -2
  47. package/build/src/library/delivery.d.ts +18 -13
  48. package/build/src/library/keiyaku.d.ts +1 -1
  49. package/build/src/library/mutation.js +11 -4
  50. package/build/src/protocol/deliver.d.ts +12 -2
  51. package/build/src/protocol/deliver.js +52 -19
  52. package/build/src/protocol/operations.d.ts +7 -2
  53. package/build/src/settlement/fence.d.ts +0 -2
  54. package/build/src/settlement/fence.js +0 -24
  55. package/build/src/task/board.d.ts +21 -2
  56. package/build/src/task/board.js +25 -12
  57. package/build/src/task/identity.js +14 -4
  58. package/build/src/task/index.d.ts +1 -0
  59. package/build/src/task/index.js +7 -1
  60. package/build/src/task/operations.d.ts +2 -1
  61. package/build/src/task/operations.js +15 -4
  62. package/build/src/task/query.js +2 -2
  63. package/build/src/task/store.d.ts +2 -0
  64. package/build/src/task/store.js +12 -83
  65. package/package.json +1 -1
@@ -96,7 +96,11 @@ function decodeArchetype(name, path, markdown) {
96
96
  const network = archetypeEnum(values, "network", ["disabled", "enabled"]);
97
97
  const description = archetypeField(values, "description");
98
98
  const allowed = effectiveAllowedActions(values.allowed);
99
+ const systemPromptMode = archetypeEnum(values, "systemPromptMode", ["append", "replace"]);
99
100
  const systemPrompt = lines.slice(closing + 1).join("\n");
101
+ if (systemPromptMode !== undefined && systemPrompt.length === 0) {
102
+ throw new TypeError("Akuma systemPromptMode requires a nonempty Markdown body");
103
+ }
100
104
  return Object.freeze({
101
105
  name,
102
106
  path,
@@ -106,7 +110,10 @@ function decodeArchetype(name, path, markdown) {
106
110
  ...(model === undefined ? {} : { model }),
107
111
  ...(effort === undefined ? {} : { effort }),
108
112
  ...(network === undefined ? {} : { network }),
109
- ...(systemPrompt.length === 0 ? {} : { systemPrompt }),
113
+ ...(systemPrompt.length === 0 ? {} : {
114
+ systemPrompt,
115
+ systemPromptMode: systemPromptMode ?? "append",
116
+ }),
110
117
  }),
111
118
  ...(readonly === undefined ? {} : { readonly }),
112
119
  allowed,
@@ -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) {
@@ -1,8 +1,10 @@
1
+ import type { SystemPromptMode } from "../../provider-recipe.js";
1
2
  export type AcpExecutionConfig = Readonly<{
2
3
  argvBefore: readonly string[];
3
4
  argvAfter: readonly string[];
4
5
  modelArg?: string;
5
6
  effortArg?: string;
6
7
  systemPromptArg?: string;
8
+ systemPromptMode?: SystemPromptMode;
7
9
  }>;
8
10
  export declare function decodeAcpConfig(value: unknown): AcpExecutionConfig;
@@ -13,7 +13,7 @@ export function decodeAcpConfig(value) {
13
13
  }
14
14
  const config = value;
15
15
  const unknown = Object.keys(config)
16
- .find((key) => !["argvBefore", "argvAfter", "effortArg", "modelArg", "systemPromptArg"].includes(key));
16
+ .find((key) => !["argvBefore", "argvAfter", "effortArg", "modelArg", "systemPromptArg", "systemPromptMode"].includes(key));
17
17
  if (unknown !== undefined)
18
18
  throw new TypeError(`ACP provider config has unknown field ${unknown}`);
19
19
  if (!Array.isArray(config.argvBefore)
@@ -27,11 +27,19 @@ export function decodeAcpConfig(value) {
27
27
  const modelArg = argumentName(config, "modelArg");
28
28
  const effortArg = argumentName(config, "effortArg");
29
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
+ }
30
37
  return Object.freeze({
31
38
  argvBefore: Object.freeze([...config.argvBefore]),
32
39
  argvAfter: Object.freeze([...config.argvAfter]),
33
40
  ...(modelArg === undefined ? {} : { modelArg }),
34
41
  ...(effortArg === undefined ? {} : { effortArg }),
35
42
  ...(systemPromptArg === undefined ? {} : { systemPromptArg }),
43
+ ...(systemPromptMode === undefined ? {} : { systemPromptMode }),
36
44
  });
37
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,7 +127,7 @@ 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);
@@ -131,6 +143,7 @@ function beginAcpPrompt(agent, child, turn, sessionId, input) {
131
143
  };
132
144
  }
133
145
  export async function startAcpSession(launch, input, dependencies = {}) {
146
+ const acp = await import("@agentclientprotocol/sdk");
134
147
  const signal = input.signal ?? new AbortController().signal;
135
148
  signal.throwIfAborted();
136
149
  const child = (dependencies.spawnProcess ?? spawnStdioProcess)({
@@ -144,15 +157,15 @@ export async function startAcpSession(launch, input, dependencies = {}) {
144
157
  });
145
158
  let sessionId;
146
159
  let turn;
147
- const connection = createAcpClient((notification) => {
160
+ const connection = createAcpClient(acp, (notification) => {
148
161
  if (notification.sessionId === sessionId)
149
162
  turn.update(notification.update);
150
163
  }).connect(acp.ndJsonStream(Writable.toWeb(child.input), Readable.toWeb(child.output)));
151
- turn = createAcpTurn(connection, dependencies.interpretTool);
164
+ turn = createAcpTurn(acp, connection, dependencies.interpretTool);
152
165
  try {
153
- sessionId = await abortable(establishSession(connection.agent, input), signal);
166
+ sessionId = await abortable(establishSession(acp, connection.agent, input, dependencies), signal);
154
167
  turn.events.emit({ type: "session", coordinate: { sessionId } });
155
- const session = beginAcpPrompt(connection.agent, child, turn, sessionId, input);
168
+ const session = beginAcpPrompt({ acp, agent: connection.agent }, child, turn, sessionId, input);
156
169
  return { session, agent: connection.agent, sessionId, open: turn.open };
157
170
  }
158
171
  catch (error) {
@@ -161,7 +174,7 @@ export async function startAcpSession(launch, input, dependencies = {}) {
161
174
  await child.close(true);
162
175
  }
163
176
  catch (cleanup) {
164
- 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 });
165
178
  }
166
179
  turn.events.end();
167
180
  throw error;
@@ -11,8 +11,14 @@ function optionAdmission(options, config) {
11
11
  if (options.effort !== undefined && config.effortArg === undefined) {
12
12
  return { kind: "refused", diagnostic: "ACP provider has no effort argument mapping" };
13
13
  }
14
- if (options.systemPrompt !== undefined && options.systemPrompt.length > 0 && config.systemPromptArg === undefined) {
15
- 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
+ }
16
22
  }
17
23
  return {
18
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
  };
@@ -277,12 +279,14 @@ async function driveClaude(load, execution, drive) {
277
279
  },
278
280
  };
279
281
  }
280
- 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;
281
285
  return {
282
286
  admitOptions: admitClaudeOptions,
283
- fork: (input) => forkClaude(load, execution, input),
284
- start: (input) => driveClaude(load, execution, input),
285
- 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),
286
290
  };
287
291
  }
288
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));
@@ -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 {
@@ -32,7 +32,7 @@ async function adapterFor(execution) {
32
32
  const { claudeProvider, createClaudeProvider } = await import("./claude/index.js");
33
33
  return execution.executable === undefined && execution.env === undefined
34
34
  ? claudeProvider
35
- : createClaudeProvider(async () => await import("@anthropic-ai/claude-agent-sdk"), execution);
35
+ : createClaudeProvider(execution);
36
36
  }
37
37
  if (execution.kind === "codex-app-server")
38
38
  return (await import("./codex-app-server/index.js")).createCodexAppServerProvider(execution);
@@ -24,6 +24,9 @@ function opencodeSessionId(coordinateValue) {
24
24
  function admit(options) {
25
25
  if (options.network !== undefined)
26
26
  throw new Error("OpenCode does not support explicit network");
27
+ if (options.systemPromptMode === "replace") {
28
+ throw new Error("OpenCode V1 does not support replacing the native system prompt");
29
+ }
27
30
  if (options.model !== undefined)
28
31
  parseModel(options.model);
29
32
  }
@@ -32,7 +32,9 @@ async function piCreateOptions(sdk, input) {
32
32
  const resourceLoader = input.options.systemPrompt === undefined ? undefined : new sdk.DefaultResourceLoader({
33
33
  cwd: input.cwd,
34
34
  agentDir: sdk.getAgentDir(),
35
- systemPromptOverride: () => input.options.systemPrompt,
35
+ ...(input.options.systemPromptMode === "append"
36
+ ? { appendSystemPromptOverride: (base) => [...base, input.options.systemPrompt] }
37
+ : { systemPromptOverride: () => input.options.systemPrompt }),
36
38
  });
37
39
  await resourceLoader?.reload();
38
40
  const sessionManager = input.session.kind === "fresh"
@@ -151,15 +151,6 @@ function applyRemove(body, operation, document) {
151
151
  body.extensionIndexes.delete(normalizeTitle(operation.target));
152
152
  }
153
153
  function applyUpdate(body, operation, document) {
154
- if (operation.target.startsWith("Criterion ")) {
155
- const title = operation.target.slice("Criterion ".length);
156
- const index = criterionIndex(body, title);
157
- if (index < 0)
158
- refusal(`unknown criterion '${title}'`);
159
- const criterion = body.criteria[index];
160
- body.criteria[index] = { ...criterion, body: prose(document, operation.section, "criterion") };
161
- return;
162
- }
163
154
  const index = extensionIndex(body, operation.target);
164
155
  if (index < 0)
165
156
  refusal(`unknown extension '${operation.target}'`);
@@ -175,8 +175,8 @@ function patternsOverlap(left, right) {
175
175
  }
176
176
  export function decodeRegion(document, section) {
177
177
  const blocks = directChildren(section, "code_block");
178
- if (blocks.length !== 1 || !blocks[0].closed || blocks[0].info.length > 0) {
179
- refusal("Region must contain one closed fence without an info string");
178
+ if (blocks.length !== 1 || !blocks[0].closed || (blocks[0].info !== "" && blocks[0].info !== "txt")) {
179
+ refusal("Region must contain one closed fence with no info string or the exact 'txt' info string");
180
180
  }
181
181
  const other = section.children.filter((node) => node !== blocks[0] && nonblank(document, node));
182
182
  if (other.length > 0)
@@ -57,6 +57,7 @@ export function acceptedDeliver(result, coordinate) {
57
57
  ...(value.verificationReuse === undefined ? {} : { verificationReuse: value.verificationReuse }),
58
58
  ...(value.verificationSummary === undefined ? {} : { verificationSummary: value.verificationSummary }),
59
59
  ...(value.placement === undefined ? {} : { placement: value.placement }),
60
+ ...(value.continuation === undefined ? {} : { continuation: value.continuation }),
60
61
  ...(value.cleanup === undefined ? {} : { cleanup: value.cleanup }),
61
62
  ...(value.leak === undefined ? {} : { leak: value.leak }),
62
63
  };
@@ -78,6 +79,7 @@ export function acceptedReview(result, coordinate) {
78
79
  ...(value.verificationReuse === undefined ? {} : { verificationReuse: value.verificationReuse }),
79
80
  ...(value.verificationSummary === undefined ? {} : { verificationSummary: value.verificationSummary }),
80
81
  ...(value.placement === undefined ? {} : { placement: value.placement }),
82
+ ...(value.continuation === undefined ? {} : { continuation: value.continuation }),
81
83
  ...(value.workspace === undefined ? {} : { workspace: value.workspace }),
82
84
  ...(value.cleanup === undefined ? {} : { cleanup: value.cleanup }),
83
85
  ...(value.leak === undefined ? {} : { leak: value.leak }),
@@ -5,8 +5,8 @@ export type ContractCommandSpec = Readonly<{
5
5
  flags: Readonly<Record<string, ContractFlagKind>>;
6
6
  usage: string;
7
7
  purpose: string;
8
+ help?: string;
8
9
  }>;
9
- export declare const AMEND_OPERATIONS_HELP: string;
10
10
  export declare const CONTRACT_COMMAND_SPECS: {
11
11
  readonly bind: {
12
12
  readonly positional: "none";
@@ -35,6 +35,7 @@ export declare const CONTRACT_COMMAND_SPECS: {
35
35
  };
36
36
  readonly usage: "amend [<contract>|@<contract>] [--after <kei/...> ... | --clear-after] [--gates <name,...>] [--actor <actor>] [-]";
37
37
  readonly purpose: "Amend one Contract's document operations or structured terms.";
38
+ readonly help: string;
38
39
  };
39
40
  readonly deliver: {
40
41
  readonly positional: "optional";
@@ -47,7 +48,8 @@ export declare const CONTRACT_COMMAND_SPECS: {
47
48
  readonly json: "boolean";
48
49
  };
49
50
  readonly usage: "deliver [<contract>|@<contract>] [--message <text>] [--include-dirty] [--materialize-conflict] [--actor <actor>]";
50
- readonly purpose: "Deliver one Contract candidate.";
51
+ readonly purpose: "Deliver one Contract candidate from the appointed worktree.";
52
+ readonly help: string;
51
53
  };
52
54
  readonly review: {
53
55
  readonly positional: "optional";
@@ -139,7 +141,7 @@ export declare const CONTRACT_COMMAND_SPECS: {
139
141
  readonly confirm: "value";
140
142
  readonly json: "boolean";
141
143
  };
142
- readonly usage: "nuke [--confirm <WorldRoot>]";
144
+ readonly usage: "nuke [--confirm <WorldRoot>] [--json]";
143
145
  readonly purpose: "Remove Keiyaku-owned data from one confirmed World.";
144
146
  };
145
147
  readonly settings: {
@@ -1,17 +1,27 @@
1
- export const AMEND_OPERATIONS_HELP = [
2
- "stdin operations (H2 sections only, no H1):",
3
- " ## Replace: Context|Objective|Design|Region|Criteria|Verification|<extension>",
4
- " ## Append: Context|Objective|Design|Criteria|<extension>",
5
- " ## Add: Criteria|<new-extension-title>",
6
- " ## Update: Criterion <existing-title>|<existing-extension-title>",
7
- " ## Remove: Criterion <existing-title>|<existing-extension-title>",
8
- "",
9
- "full operation grammar: docs/document.md, Amend Operations",
10
- ].join("\n");
11
1
  export const CONTRACT_COMMAND_SPECS = {
12
2
  bind: { positional: "none", stdin: "required", flags: { actor: "value", task: "value", target: "value", here: "boolean", after: "repeat-value", gates: "raw-value", json: "boolean" }, usage: "bind [--task <task/...>] [--target <ref>] [--here] [--after <kei/...> ...] [--gates <name,...>] [--actor <actor>] -", purpose: "Create one Contract from stdin Markdown." },
13
- amend: { positional: "optional", stdin: "optional", flags: { actor: "value", after: "repeat-value", "clear-after": "boolean", gates: "raw-value", json: "boolean" }, usage: "amend [<contract>|@<contract>] [--after <kei/...> ... | --clear-after] [--gates <name,...>] [--actor <actor>] [-]", purpose: "Amend one Contract's document operations or structured terms." },
14
- deliver: { positional: "optional", stdin: "none", flags: { actor: "value", message: "value", "include-dirty": "boolean", "materialize-conflict": "boolean", json: "boolean" }, usage: "deliver [<contract>|@<contract>] [--message <text>] [--include-dirty] [--materialize-conflict] [--actor <actor>]", purpose: "Deliver one Contract candidate." },
3
+ amend: {
4
+ positional: "optional", stdin: "optional", flags: { actor: "value", after: "repeat-value", "clear-after": "boolean", gates: "raw-value", json: "boolean" }, usage: "amend [<contract>|@<contract>] [--after <kei/...> ... | --clear-after] [--gates <name,...>] [--actor <actor>] [-]", purpose: "Amend one Contract's document operations or structured terms.",
5
+ help: [
6
+ " ## Replace: Context|Objective|Design|Region|Criteria|Verification|<extension>",
7
+ " ## Append: Context|Objective|Design|Criteria|<extension>",
8
+ " ## Add: Criteria|<new-extension-title>",
9
+ " ## Update: <existing-extension-title>",
10
+ " ## Remove: Criterion <existing-title>",
11
+ " ## Remove: <existing-extension-title>",
12
+ ].join("\n"),
13
+ },
14
+ deliver: {
15
+ positional: "optional", stdin: "none", flags: { actor: "value", message: "value", "include-dirty": "boolean", "materialize-conflict": "boolean", json: "boolean" }, usage: "deliver [<contract>|@<contract>] [--message <text>] [--include-dirty] [--materialize-conflict] [--actor <actor>]", purpose: "Deliver one Contract candidate from the appointed worktree.",
16
+ help: [
17
+ " --include-dirty Capture the complete non-ignored worktree tree as the",
18
+ " candidate; stages nothing, commits nothing. Refused",
19
+ " while unmerged paths exist.",
20
+ " --materialize-conflict After a conflict result, project the judged targetHead",
21
+ " into the worktree as an uncommitted merge. Not a",
22
+ " delivery: resolve, stage, deliver again.",
23
+ ].join("\n"),
24
+ },
15
25
  review: { positional: "optional", stdin: "optional", flags: { actor: "value", satisfied: "boolean", unsatisfied: "boolean", summary: "value", json: "boolean" }, usage: "review [<contract>|@<contract>] (--satisfied | --unsatisfied) (--summary <text> | -) [--actor <actor>]", purpose: "Record one review verdict." },
16
26
  arc: { positional: "optional", stdin: "required", flags: { actor: "value", json: "boolean" }, usage: "arc [<contract>|@<contract>] [--actor <actor>] -", purpose: "Record stdin arc Markdown for one Contract." },
17
27
  abandon: { positional: "optional", stdin: "none", flags: { actor: "value", note: "value", json: "boolean" }, usage: "abandon [<contract>|@<contract>] [--note <text>] [--actor <actor>]", purpose: "Abandon one Contract with an optional note." },
@@ -20,7 +30,7 @@ export const CONTRACT_COMMAND_SPECS = {
20
30
  ls: { positional: "optional", stdin: "none", flags: { json: "boolean" }, usage: "ls task[/]\n keiyaku ls kei[/]\n keiyaku ls aku[/]\n keiyaku ls aku/<akuma>[/]\n keiyaku ls \"aku/*/*\"", purpose: "List one identity directory." },
21
31
  audit: { positional: "optional", stdin: "none", flags: { "include-dirty": "boolean", diff: "boolean", actor: "value", json: "boolean" }, usage: "audit [<contract>|@<contract>] [--include-dirty] [--diff] [--actor <actor>]", purpose: "Ask what candidate preparation, Verification, and target placement would do." },
22
32
  reconcile: { positional: "optional", stdin: "none", flags: { "retry-hooks": "boolean", json: "boolean" }, usage: "reconcile [<contract>|@<contract>] [--retry-hooks]", purpose: "Reconcile one Contract or the invocation world." },
23
- nuke: { positional: "none", stdin: "none", flags: { confirm: "value", json: "boolean" }, usage: "nuke [--confirm <WorldRoot>]", purpose: "Remove Keiyaku-owned data from one confirmed World." },
33
+ nuke: { positional: "none", stdin: "none", flags: { confirm: "value", json: "boolean" }, usage: "nuke [--confirm <WorldRoot>] [--json]", purpose: "Remove Keiyaku-owned data from one confirmed World." },
24
34
  settings: { positional: "none", stdin: "none", flags: { json: "boolean" }, usage: "settings", purpose: "Read user and project Settings resources." },
25
35
  region: { positional: "optional", stdin: "none", flags: { overlap: "boolean", path: "value", json: "boolean" }, usage: "region [<contract>] [--overlap]\n region --path <repo-relative-path>", purpose: "Read active declared Contract Regions." },
26
36
  };
@@ -1,7 +1,10 @@
1
1
  import { type BlockedTaskList, type TaskBatchResult, type TaskCompositionResult, type TaskDecompositionTree, type TaskDetail, type TaskDoctorReport, type TaskList, type TaskMutationResult, type TaskNamespaceResult, type TaskQueryResult, type TaskUpdateResult } from "../../task/index.js";
2
2
  import type { ParsedTaskCommand } from "./task.js";
3
3
  import type { WorldRoot } from "../../world.js";
4
- export type TaskInvocationResult = TaskMutationResult | TaskUpdateResult | TaskBatchResult | TaskCompositionResult | TaskDetail | TaskList | BlockedTaskList | TaskQueryResult | TaskDecompositionTree | TaskDoctorReport | TaskNamespaceResult | TaskWorldObservation;
4
+ export type TaskShowResult = TaskDetail | readonly TaskDetail[] | Extract<TaskMutationResult, {
5
+ kind: "refused";
6
+ }>;
7
+ export type TaskInvocationResult = TaskMutationResult | TaskUpdateResult | TaskBatchResult | TaskCompositionResult | TaskShowResult | TaskList | BlockedTaskList | TaskQueryResult | TaskDecompositionTree | TaskDoctorReport | TaskNamespaceResult | TaskWorldObservation;
5
8
  type TaskWorldRead = TaskList | BlockedTaskList | TaskQueryResult | TaskDoctorReport;
6
9
  export type TaskWorldObservation = Readonly<{
7
10
  kind: "present";
@@ -1,4 +1,4 @@
1
- import { Tasks, } from "../../task/index.js";
1
+ import { Tasks, observeTaskDetails, } from "../../task/index.js";
2
2
  import { injectedBodyRequests, requestBodyTask } from "../../akuma/requests.js";
3
3
  import { decodeTaskMutationRequest } from "../../task/mutation.js";
4
4
  import { resolveTaskNamespaceContext, writeTaskNamespaceContext } from "../../task/context.js";
@@ -157,8 +157,11 @@ async function invokeRead(tasks, command, current) {
157
157
  const id = command.positionals[0];
158
158
  switch (command.action) {
159
159
  case "show": {
160
- const detail = await tasks.task({ id }).read();
161
- return detail ?? { kind: "refused", refusal: { kind: "task-missing", taskId: id } };
160
+ const ids = command.positionals.map((taskId) => tasks.task({ id: taskId }).id);
161
+ const observed = await observeTaskDetails(tasks.root, ids);
162
+ if (observed.kind !== "accepted")
163
+ return observed;
164
+ return command.output === "json" || observed.value.length > 1 ? observed.value : observed.value[0];
162
165
  }
163
166
  case "ls": return tasks.list({ selection: command.flags.all === true ? "all" : command.flags.closed === true ? "closed" : "active", ...readScope(command, current), ...(limit(command) === undefined ? {} : { limit: limit(command) }) });
164
167
  case "ready": return tasks.ready({ ...readScope(command, current), ...(value(command, "parent") === undefined ? {} : { parent: value(command, "parent") }), ...(limit(command) === undefined ? {} : { limit: limit(command) }) });
@@ -13,7 +13,7 @@ const TASK_COMMAND_SPECS = {
13
13
  task add [--namespace <ns>] [--actor <actor>] -`,
14
14
  purpose: "Create one Task from flags or a canonical stdin document.",
15
15
  },
16
- show: { arity: [1, 1], flags: COMMON, usage: "task show <TaskId>", purpose: "Read one Task and its relationships." },
16
+ show: { arity: [1, Number.POSITIVE_INFINITY], flags: COMMON, usage: "task show <TaskId>...", purpose: "Read one or more Tasks and their relationships." },
17
17
  ls: { arity: [0, 0], flags: { ...COMMON, closed: "boolean", all: "boolean", world: "boolean", limit: "value" }, usage: "task ls [--closed | --all] [--world] [--limit <n>]", purpose: "List Tasks in the selected scope." },
18
18
  ready: { arity: [0, 0], flags: { ...COMMON, world: "boolean", parent: "value", limit: "value" }, usage: "task ready [--world] [--parent <TaskId>] [--limit <n>]", purpose: "List open Tasks whose every need is terminal." },
19
19
  blocked: { arity: [0, 0], flags: { ...COMMON, world: "boolean", parent: "value", limit: "value" }, usage: "task blocked [--world] [--parent <TaskId>] [--limit <n>]", purpose: "List Tasks blocked by dependencies." },
@@ -46,7 +46,15 @@ export function isTaskAction(value) {
46
46
  export function renderTaskHelp(action) {
47
47
  if (action !== undefined) {
48
48
  const spec = TASK_COMMAND_SPECS[action];
49
- return `${spec.purpose}\n\n${usageLine(spec.usage)}`;
49
+ const queryGuide = action === "query" ? [
50
+ "",
51
+ "fields: state priority title id parent under needs blocks ready blocked created updated",
52
+ "operators: = != < > <= >= ~ and or not ( )",
53
+ "examples:",
54
+ " keiyaku task query --where 'priority <= 1 and ready' --world",
55
+ " keiyaku task query --where 'updated < 2026-08-06T00:00:00.000Z' --world",
56
+ ].join("\n") : "";
57
+ return `${spec.purpose}\n\n${usageLine(spec.usage)}${queryGuide}`;
50
58
  }
51
59
  return [
52
60
  "usage: keiyaku task <command> ...",
@@ -1,7 +1,7 @@
1
1
  import { isTaskAction, parseTaskCommand, renderTaskHelp, renderTaskUsage, } from "./commands/task.js";
2
2
  import { isAkumaAction, parseAkumaCatalogPath, parseAkumaCommand, renderAkumaHelp, renderAkumaRootRows, renderAkumaUsage, } from "./commands/akuma.js";
3
3
  import { INSTALL_USAGE, parseInstallCommand, renderInstallHelp } from "./commands/install.js";
4
- import { AMEND_OPERATIONS_HELP, CONTRACT_COMMAND_SPECS, } from "./commands/contract.js";
4
+ import { CONTRACT_COMMAND_SPECS, } from "./commands/contract.js";
5
5
  import { CliUsageError, isBlankInput, usageLine } from "./usage.js";
6
6
  export { CliUsageError } from "./usage.js";
7
7
  const ROOT_USAGE = "usage: keiyaku [-C <path>] [--repo <path>] <command> [<contract>|@<contract>] [--flag ...] [-]";
@@ -25,7 +25,7 @@ export function renderRootHelp() {
25
25
  export function renderContractHelp(command) {
26
26
  const spec = CONTRACT_COMMAND_SPECS[command];
27
27
  const help = `${spec.purpose}\n\n${usageLine(spec.usage)}`;
28
- return command === "amend" ? `${help}\n\n${AMEND_OPERATIONS_HELP}` : help;
28
+ return spec.help === undefined ? help : `${help}\n\n${spec.help}`;
29
29
  }
30
30
  function contractUsage(command) {
31
31
  return usageLine(CONTRACT_COMMAND_SPECS[command].usage);