@astrosheep/keiyaku 0.1.87 → 1.0.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 (158) hide show
  1. package/README.md +12 -22
  2. package/build/.tsbuildinfo +1 -1
  3. package/build/agents/effective-policy.js +32 -0
  4. package/build/agents/harness/event-channel.js +178 -0
  5. package/build/agents/harness/execution-handle.js +117 -0
  6. package/build/agents/harness/index.js +622 -0
  7. package/build/agents/harness/pump.js +152 -0
  8. package/build/agents/harness/runtime.js +68 -0
  9. package/build/agents/index.js +104 -213
  10. package/build/agents/opencode-sdk.js +12 -18
  11. package/build/agents/providers/claude-agent-sdk.js +529 -0
  12. package/build/agents/providers/codex-app-server.js +656 -0
  13. package/build/agents/providers/codex-sdk.js +193 -0
  14. package/build/agents/providers/opencode-sdk.js +257 -0
  15. package/build/agents/providers/pi.js +345 -0
  16. package/build/agents/selector.js +15 -6
  17. package/build/agents/session.js +29 -0
  18. package/build/agents/types.js +5 -11
  19. package/build/cli/commands/akuma.js +79 -0
  20. package/build/cli/completion.js +71 -0
  21. package/build/cli/draft-artifact.js +50 -0
  22. package/build/cli/flags.js +96 -0
  23. package/build/cli/help.js +108 -0
  24. package/build/cli/index.js +204 -912
  25. package/build/cli/parse.js +280 -0
  26. package/build/cli/render/arc.js +63 -0
  27. package/build/{responses/tool-errors.js → cli/render/errors.js} +15 -16
  28. package/build/cli/render/format.js +199 -0
  29. package/build/cli/render/misc.js +40 -0
  30. package/build/cli/render/petition.js +32 -0
  31. package/build/{responses → cli/render}/response-style.js +4 -4
  32. package/build/cli/render/shared.js +122 -0
  33. package/build/cli/render/status.js +69 -0
  34. package/build/cli/render/summon.js +17 -0
  35. package/build/cli/render/tell.js +11 -0
  36. package/build/cli/skills-install.js +237 -0
  37. package/build/cli/subagent-guard.js +6 -3
  38. package/build/config/akuma-loader.js +268 -0
  39. package/build/config/env-keys.js +0 -10
  40. package/build/config/{schema.js → env.js} +117 -23
  41. package/build/config/settings.js +137 -260
  42. package/build/core/amend.js +120 -0
  43. package/build/core/arc.js +225 -0
  44. package/build/core/atomic-publish.js +64 -0
  45. package/build/core/bind.js +281 -0
  46. package/build/core/claim.js +360 -0
  47. package/build/core/command-io.js +174 -0
  48. package/build/core/context.js +108 -0
  49. package/build/core/contract.js +14 -0
  50. package/build/core/draft.js +82 -0
  51. package/build/core/entry.js +203 -0
  52. package/build/core/forfeit.js +60 -0
  53. package/build/core/hints.js +108 -0
  54. package/build/core/ids.js +70 -0
  55. package/build/core/ledger.js +117 -0
  56. package/build/core/log.js +24 -0
  57. package/build/{protocol → core}/markdown/parser.js +68 -0
  58. package/build/{protocol → core}/markdown/sections.js +2 -28
  59. package/build/core/markdown/titles.js +9 -0
  60. package/build/{tools/petition/claim-gates.js → core/petition-claim-gates.js} +43 -29
  61. package/build/core/petition-claim.js +116 -0
  62. package/build/core/petition-forfeit.js +39 -0
  63. package/build/{tools/round/head-guard.js → core/petition-head-guard.js} +2 -2
  64. package/build/core/petition-run.js +81 -0
  65. package/build/core/petition.js +96 -0
  66. package/build/core/places.js +82 -0
  67. package/build/core/projection-core.js +541 -0
  68. package/build/core/projection-mint.js +79 -0
  69. package/build/core/projection-status.js +179 -0
  70. package/build/core/projection-wake.js +252 -0
  71. package/build/core/queue.js +73 -0
  72. package/build/core/ref-log.js +94 -0
  73. package/build/core/registry.js +96 -0
  74. package/build/core/render.js +326 -0
  75. package/build/core/renew.js +195 -0
  76. package/build/core/scope.js +163 -0
  77. package/build/core/seal.js +195 -0
  78. package/build/core/status.js +476 -0
  79. package/build/{tools/summon/persist.js → core/summon-persist.js} +10 -9
  80. package/build/core/summon.js +377 -0
  81. package/build/{protocol/response-history.js → core/transcripts.js} +83 -39
  82. package/build/core/verdict.js +96 -0
  83. package/build/core/worktree-path.js +134 -0
  84. package/build/flow-error.js +80 -4
  85. package/build/generated/version.js +1 -1
  86. package/build/git/branches.js +8 -9
  87. package/build/git/core.js +1 -1
  88. package/build/git/diff/pathspec.js +2 -3
  89. package/build/git/diff/preview.js +7 -2
  90. package/build/git/diff/read.js +4 -3
  91. package/build/git/staging.js +1 -1
  92. package/build/index.js +12 -6
  93. package/build/keiyaku.js +17 -13
  94. package/build/telemetry/debug-log.js +1 -1
  95. package/package.json +16 -9
  96. package/skills/keiyaku/SKILL.md +16 -13
  97. package/build/agents/claude-agent-sdk-exec.js +0 -352
  98. package/build/agents/codex-cli-exec.js +0 -237
  99. package/build/agents/codex-sdk-exec.js +0 -291
  100. package/build/agents/gemini-cli-core-exec.js +0 -499
  101. package/build/agents/opencode-exec.js +0 -297
  102. package/build/agents/process-runner.js +0 -239
  103. package/build/agents/progress-reporter.js +0 -137
  104. package/build/agents/round-runner.js +0 -53
  105. package/build/config/apply-argument-descriptions.js +0 -16
  106. package/build/config/architect-hints.js +0 -374
  107. package/build/config/base-rules.js +0 -23
  108. package/build/config/config.js +0 -50
  109. package/build/config/dotenv.js +0 -61
  110. package/build/config/keiyaku-home.js +0 -9
  111. package/build/config/path-utils.js +0 -11
  112. package/build/config/render-template.js +0 -8
  113. package/build/git/diff/filter.js +0 -67
  114. package/build/git/keiyaku-state.js +0 -58
  115. package/build/git/snapshot.js +0 -41
  116. package/build/git/stash.js +0 -62
  117. package/build/protocol/bind-draft.js +0 -195
  118. package/build/protocol/draft-artifacts.js +0 -99
  119. package/build/protocol/file-guards.js +0 -24
  120. package/build/protocol/keiyaku-draft.js +0 -168
  121. package/build/protocol/ledger.js +0 -374
  122. package/build/protocol/markdown/index.js +0 -14
  123. package/build/protocol/markdown/normalization.js +0 -71
  124. package/build/protocol/markdown/render.js +0 -68
  125. package/build/protocol/oath.js +0 -36
  126. package/build/protocol/render-keiyaku.js +0 -37
  127. package/build/protocol/review.js +0 -1
  128. package/build/protocol/status-previews.js +0 -68
  129. package/build/responses/responses.js +0 -515
  130. package/build/tools/amend/index.js +0 -18
  131. package/build/tools/amend/run.js +0 -44
  132. package/build/tools/bind/index.js +0 -44
  133. package/build/tools/bind/run.js +0 -237
  134. package/build/tools/contracts.js +0 -1
  135. package/build/tools/create-handler.js +0 -31
  136. package/build/tools/help.js +0 -9
  137. package/build/tools/petition/claim.js +0 -174
  138. package/build/tools/petition/forfeit.js +0 -100
  139. package/build/tools/petition/index.js +0 -83
  140. package/build/tools/petition/run.js +0 -66
  141. package/build/tools/round/guidance.js +0 -7
  142. package/build/tools/round/prompt.js +0 -70
  143. package/build/tools/round/report.js +0 -153
  144. package/build/tools/round/worktree.js +0 -55
  145. package/build/tools/round-close/index.js +0 -19
  146. package/build/tools/round-close/run.js +0 -98
  147. package/build/tools/round-open/index.js +0 -20
  148. package/build/tools/round-open/run.js +0 -77
  149. package/build/tools/schema.js +0 -69
  150. package/build/tools/status/index.js +0 -16
  151. package/build/tools/status/read.js +0 -155
  152. package/build/tools/summon/index.js +0 -27
  153. package/build/tools/summon/run.js +0 -80
  154. /package/build/{config/toml.js → agents/harness/events.js} +0 -0
  155. /package/build/{git/diff/contracts.js → cli/render/types.js} +0 -0
  156. /package/build/{protocol/bind-input.js → cli/types.js} +0 -0
  157. /package/build/{protocol → core}/markdown/lex.js +0 -0
  158. /package/build/{protocol → core}/markdown/types.js +0 -0
@@ -0,0 +1,152 @@
1
+ import * as fs from "node:fs";
2
+ import * as path from "node:path";
3
+ import { claimAllInbox, claimFencedTells, listTellIds, markTellSubmitted, markTellConsumed, readTellSubmitted, readTellOriginal, readAdmissionLock, isTellCausallyConsumed, touchHeartbeat as touchHeartbeatFile, } from "../../core/projection-core.js";
4
+ /** Tier-2 boundary snapshot. No tells leaves prompt byte-identical. */
5
+ export function snapshotReplayPrompt(projectionDirectory, prompt, tellFence, executionId) {
6
+ const tellIds = claimFencedTells(projectionDirectory, tellFence, executionId);
7
+ if (tellIds.length === 0)
8
+ return { prompt, tellIds };
9
+ const originals = tellIds.map((tellId) => readTellOriginal(projectionDirectory, "inflight", tellId));
10
+ const frames = originals.map((original, index) => `--- tell ${tellIds[index]} ---\n${original.text}`);
11
+ const effort = originals.reduce((value, original) => original.effort ?? value, undefined);
12
+ return { prompt: `${prompt}\n\n${frames.join("\n\n")}`, tellIds, ...(effort ? { effort } : {}) };
13
+ }
14
+ /** Tier-2 has no steer acknowledgement: actual driver start is its ordinal-zero submission point. */
15
+ export function markReplayStarted(projectionDirectory, executionId, tellIds) {
16
+ for (const tellId of tellIds) {
17
+ if (listTellIds(projectionDirectory, "inflight").includes(tellId)) {
18
+ markTellSubmitted(projectionDirectory, tellId, { executionId, fence: { kind: "ordinal", ordinal: 0 } });
19
+ }
20
+ }
21
+ }
22
+ /** A successful replay terminal is checkpoint ordinal one; failures intentionally do not consume. */
23
+ export function markReplayCompleted(projectionDirectory, executionId) {
24
+ for (const tellId of listTellIds(projectionDirectory, "submitted")) {
25
+ const submitted = readTellSubmitted(projectionDirectory, tellId);
26
+ if (submitted.executionId === executionId && submitted.fence && isTellCausallyConsumed(submitted.fence, { kind: "ordinal", ordinal: 1 })) {
27
+ markTellConsumed(projectionDirectory, tellId);
28
+ }
29
+ }
30
+ }
31
+ /**
32
+ * Tier-2's only submission acknowledgement. A created driver is deliberately
33
+ * insufficient: observe the first provider event, or a successful completion
34
+ * from a provider that emitted no events.
35
+ */
36
+ export function createReplayRunStartGate(projectionDirectory, executionId, tellIds) {
37
+ let started = false;
38
+ const start = () => {
39
+ if (started)
40
+ return;
41
+ markReplayStarted(projectionDirectory, executionId, tellIds);
42
+ started = true;
43
+ };
44
+ return { observeProviderEvent: start, observeSuccessfulCompletion: start };
45
+ }
46
+ /** Heartbeat throttle: no more than one utimes call per 5000ms. */
47
+ export const HEARTBEAT_MIN_INTERVAL_MS = 5_000;
48
+ /**
49
+ * Supervisor pump: iterates handle.events, forwards to publicChannel,
50
+ * touches heartbeat (throttled), checks inbox at checkpoints,
51
+ * and on final sweep publishes verdict.
52
+ */
53
+ export async function pumpSupervisor(handle, projectionDirectory, publicChannel, onEvent) {
54
+ let lastHeartbeatMs = 0;
55
+ let heartbeatDegraded = false;
56
+ const heartbeat = () => {
57
+ const now = Date.now();
58
+ if (now - lastHeartbeatMs < HEARTBEAT_MIN_INTERVAL_MS)
59
+ return;
60
+ lastHeartbeatMs = now;
61
+ try {
62
+ touchHeartbeatFile(projectionDirectory, now);
63
+ heartbeatDegraded = false;
64
+ }
65
+ catch (error) {
66
+ if (!heartbeatDegraded) {
67
+ publicChannel.push({
68
+ type: "diagnostic",
69
+ text: `heartbeat degraded: ${String(error)}`,
70
+ });
71
+ }
72
+ heartbeatDegraded = true;
73
+ }
74
+ };
75
+ const heartbeatTimer = setInterval(heartbeat, HEARTBEAT_MIN_INTERVAL_MS);
76
+ const stopHeartbeatTimer = () => clearInterval(heartbeatTimer);
77
+ try {
78
+ for await (const event of handle.events) {
79
+ publicChannel.push(event);
80
+ onEvent?.(event);
81
+ heartbeat();
82
+ if (event.type === "done" || event.type === "fail")
83
+ stopHeartbeatTimer();
84
+ }
85
+ }
86
+ catch (error) {
87
+ if (error instanceof Error && error.code === "PROJECTION_STATE_ERROR") {
88
+ publicChannel.push({
89
+ type: "fail",
90
+ code: "PROJECTION_STATE_ERROR",
91
+ message: error.message,
92
+ });
93
+ }
94
+ else {
95
+ publicChannel.push({
96
+ type: "diagnostic",
97
+ text: `pump error: ${String(error)}`,
98
+ });
99
+ }
100
+ }
101
+ finally {
102
+ stopHeartbeatTimer();
103
+ }
104
+ }
105
+ function readTellOriginalText(projectionDirPath, tellId, layer = "inflight") {
106
+ const content = JSON.parse(fs.readFileSync(path.join(projectionDirPath, layer, tellId), "utf8"));
107
+ return content.text;
108
+ }
109
+ /**
110
+ * Build an onSyncCheckpoint callback that claims all inbox tells
111
+ * and delivers them via the given SteerControl.
112
+ * Intended for use inside adapter start() calls as the onSyncCheckpoint callback.
113
+ */
114
+ export function buildSyncCheckpointCallback(projectionDirectory) {
115
+ const submitting = new Set();
116
+ return (control, checkpoint) => {
117
+ claimAllInbox(projectionDirectory);
118
+ const executionId = readAdmissionLock(projectionDirectory)?.executionId;
119
+ for (const tellId of listTellIds(projectionDirectory, "inflight")) {
120
+ if (submitting.has(tellId))
121
+ continue;
122
+ submitting.add(tellId);
123
+ const original = readTellOriginal(projectionDirectory, "inflight", tellId);
124
+ const originalText = original.text;
125
+ const tell = {
126
+ id: tellId,
127
+ text: `--- tell ${tellId} ---\n${originalText}`,
128
+ ...(original.effort ? { effort: original.effort } : {}),
129
+ };
130
+ void control.enqueue(tell)
131
+ .then((ack) => {
132
+ if (!executionId)
133
+ throw new Error("projection checkpoint has no execution admission");
134
+ if (!ack)
135
+ throw new Error("provider enqueue resolved without TellSubmitAck");
136
+ markTellSubmitted(projectionDirectory, tellId, { executionId, fence: ack.fence });
137
+ })
138
+ .catch(() => {
139
+ // Leave in inflight for the next checkpoint.
140
+ })
141
+ .finally(() => submitting.delete(tellId));
142
+ }
143
+ if (!executionId)
144
+ return;
145
+ for (const tellId of listTellIds(projectionDirectory, "submitted")) {
146
+ const submitted = readTellSubmitted(projectionDirectory, tellId);
147
+ if (submitted.executionId === executionId && submitted.fence && isTellCausallyConsumed(submitted.fence, checkpoint.fence)) {
148
+ markTellConsumed(projectionDirectory, tellId);
149
+ }
150
+ }
151
+ };
152
+ }
@@ -0,0 +1,68 @@
1
+ import { createHash } from "node:crypto";
2
+ import { getConfig } from "../../config/env.js";
3
+ import { DISABLE_SUBAGENT_SPAWN_ENV_KEY } from "../../config/env-keys.js";
4
+ export function sha256Utf8(value) {
5
+ const bytes = Buffer.from(value, "utf8");
6
+ return { sha256: createHash("sha256").update(bytes).digest("hex"), byteLength: bytes.byteLength };
7
+ }
8
+ /**
9
+ * Observer calls are synchronous at the native boundary. Their return values
10
+ * never gate provider progress; rejected thenables are reduced to diagnostics.
11
+ */
12
+ export function emitProviderEvidence(callback, evidence, diagnostic) {
13
+ if (!callback)
14
+ return;
15
+ const reportFailure = () => {
16
+ try {
17
+ diagnostic?.("provider evidence callback failed");
18
+ }
19
+ catch {
20
+ // Evidence and its diagnostics are observation-only.
21
+ }
22
+ };
23
+ try {
24
+ const returned = callback(evidence);
25
+ if (returned !== null &&
26
+ (typeof returned === "object" || typeof returned === "function") &&
27
+ "then" in returned &&
28
+ typeof returned.then === "function") {
29
+ void Promise.resolve(returned).catch(reportFailure);
30
+ }
31
+ }
32
+ catch {
33
+ reportFailure();
34
+ }
35
+ }
36
+ export function createAbortError(message) {
37
+ const error = new Error(message);
38
+ error.name = "AbortError";
39
+ return error;
40
+ }
41
+ export function renderTruncationSuffix(truncatedChars) {
42
+ if (truncatedChars <= 0)
43
+ return "";
44
+ return `\n...[truncated ${truncatedChars} chars from start]...`;
45
+ }
46
+ export function renderCapturedText(buffer) {
47
+ const snapshot = buffer.snapshot();
48
+ return `${snapshot.text}${renderTruncationSuffix(snapshot.truncatedChars)}`;
49
+ }
50
+ export function linkAbortSignal(signal, controller, onAbort) {
51
+ if (!signal)
52
+ return undefined;
53
+ const handler = () => {
54
+ onAbort();
55
+ controller.abort(signal.reason);
56
+ };
57
+ signal.addEventListener("abort", handler, { once: true });
58
+ if (signal.aborted) {
59
+ handler();
60
+ }
61
+ return () => signal.removeEventListener("abort", handler);
62
+ }
63
+ export function buildSubagentEnv() {
64
+ return {
65
+ ...getConfig().runtimeEnv,
66
+ [DISABLE_SUBAGENT_SPAWN_ENV_KEY]: "1",
67
+ };
68
+ }
@@ -1,223 +1,114 @@
1
- import { z } from "zod";
2
1
  import { FlowError } from "../flow-error.js";
3
- import { getAvailableAgentNames, loadKeiyakuSettings, normalizeAgentProfile, resolveAgentProfile, } from "../config/settings.js";
4
- import { runClaudeAgentSdkExec } from "./claude-agent-sdk-exec.js";
5
- import { runCodexCliExec } from "./codex-cli-exec.js";
6
- import { runCodexSdkExec } from "./codex-sdk-exec.js";
7
- import { runGeminiCliCoreExec } from "./gemini-cli-core-exec.js";
8
- import { runOpencodeExec } from "./opencode-exec.js";
9
- import { SUBAGENT_SESSION_VERSION, subagentSessionEnvelopeSchema, } from "./types.js";
10
- export { SubagentExecError, isSubagentExecError } from "./types.js";
11
- const codexThreadHandleSchema = z.object({ threadId: z.string().trim().min(1) });
12
- const claudeSessionHandleSchema = z.object({ sessionId: z.string().trim().min(1) });
13
- const geminiSessionHandleSchema = z.object({ sessionId: z.string().trim().min(1) });
14
- const opencodeSessionHandleSchema = z.object({ sessionId: z.string().trim().min(1) });
15
- const codexCliAdapter = {
16
- provider: "codex-cli",
17
- handleSchema: codexThreadHandleSchema,
18
- async run(prompt, cwd, config) {
19
- const result = await runCodexCliExec(prompt, cwd, {
20
- model: config.model,
21
- profile: config.profile,
22
- config: config.config,
23
- executable: config.executable,
24
- signal: config.signal,
25
- });
26
- return result;
27
- },
28
- async resume(prompt, cwd, config, handle) {
29
- const result = await runCodexCliExec(prompt, cwd, {
30
- model: config.model,
31
- profile: config.profile,
32
- config: config.config,
33
- executable: config.executable,
34
- signal: config.signal,
35
- threadId: handle.threadId,
36
- });
37
- return result;
38
- },
39
- };
40
- const codexSdkAdapter = {
41
- provider: "codex-sdk",
42
- handleSchema: codexThreadHandleSchema,
43
- async run(prompt, cwd, config) {
44
- const result = await runCodexSdkExec(prompt, cwd, {
45
- model: config.model,
46
- profile: config.profile,
47
- config: config.config,
48
- executable: config.executable,
49
- signal: config.signal,
50
- });
51
- return result;
52
- },
53
- async resume(prompt, cwd, config, handle) {
54
- const result = await runCodexSdkExec(prompt, cwd, {
55
- model: config.model,
56
- profile: config.profile,
57
- config: config.config,
58
- executable: config.executable,
59
- signal: config.signal,
60
- threadId: handle.threadId,
61
- });
62
- return result;
63
- },
64
- };
65
- const claudeAgentSdkAdapter = {
66
- provider: "claude-agent-sdk",
67
- handleSchema: claudeSessionHandleSchema,
68
- async run(prompt, cwd, config) {
69
- const result = await runClaudeAgentSdkExec(prompt, cwd, {
70
- model: config.model,
71
- executable: config.executable,
72
- effort: config.effort,
73
- signal: config.signal,
74
- permissionMode: config.permissionMode,
75
- settingSources: config.settingSources,
76
- systemPromptFile: config.systemPromptFile,
77
- });
78
- return result;
79
- },
80
- async resume(prompt, cwd, config, handle) {
81
- const result = await runClaudeAgentSdkExec(prompt, cwd, {
82
- model: config.model,
83
- executable: config.executable,
84
- effort: config.effort,
85
- signal: config.signal,
86
- permissionMode: config.permissionMode,
87
- settingSources: config.settingSources,
88
- systemPromptFile: config.systemPromptFile,
89
- sessionId: handle.sessionId,
90
- });
91
- return result;
92
- },
93
- };
94
- const geminiCliCoreAdapter = {
95
- provider: "gemini-cli-core",
96
- handleSchema: geminiSessionHandleSchema,
97
- run(prompt, cwd, config) {
98
- return runGeminiCliCoreExec(prompt, cwd, {
99
- model: config.model,
100
- signal: config.signal,
101
- systemPromptFile: config.systemPromptFile,
102
- });
103
- },
104
- resume(prompt, cwd, config, handle) {
105
- return runGeminiCliCoreExec(prompt, cwd, {
106
- model: config.model,
107
- signal: config.signal,
108
- sessionId: handle.sessionId,
109
- systemPromptFile: config.systemPromptFile,
110
- });
111
- },
112
- };
113
- const opencodeSdkAdapter = {
114
- provider: "opencode-sdk",
115
- handleSchema: opencodeSessionHandleSchema,
116
- async run(prompt, cwd, config) {
117
- const result = await runOpencodeExec(prompt, cwd, {
118
- model: config.model,
119
- executable: config.executable,
120
- signal: config.signal,
121
- });
122
- return result;
123
- },
124
- async resume(prompt, cwd, config, handle) {
125
- const result = await runOpencodeExec(prompt, cwd, {
126
- model: config.model,
127
- executable: config.executable,
128
- signal: config.signal,
129
- sessionId: handle.sessionId,
130
- });
131
- return result;
132
- },
133
- };
134
- const SUBAGENT_ADAPTERS = {
135
- "codex-cli": codexCliAdapter,
136
- "codex-sdk": codexSdkAdapter,
137
- "claude-agent-sdk": claudeAgentSdkAdapter,
138
- "gemini-cli-core": geminiCliCoreAdapter,
139
- "opencode-sdk": opencodeSdkAdapter,
140
- };
141
- function getSubagentAdapter(provider) {
142
- return SUBAGENT_ADAPTERS[provider];
2
+ import { EFFORT_LEVELS } from "../keiyaku.js";
3
+ import { resolveEffectiveCodexSdkProfile } from "./effective-policy.js";
4
+ import { getConfig } from "../config/env.js";
5
+ import { selectSubagent } from "./selector.js";
6
+ export { formatAvailableAgentNames } from "./selector.js";
7
+ export function createAbortError(message) {
8
+ const error = new Error(message);
9
+ error.name = "AbortError";
10
+ return error;
143
11
  }
144
- function parseSessionEnvelopeText(sessionText, resumePath) {
145
- let parsedSession;
146
- try {
147
- parsedSession = JSON.parse(sessionText);
148
- }
149
- catch {
150
- throw new FlowError("INVALID_RESPONSE_PATH", `Resume path file '${resumePath}' includes invalid 'session' metadata; this conversation cannot be resumed.`);
151
- }
152
- const sessionResult = subagentSessionEnvelopeSchema.safeParse(parsedSession);
153
- if (!sessionResult.success) {
154
- throw new FlowError("INVALID_RESPONSE_PATH", `Resume path file '${resumePath}' includes invalid 'session' metadata; this conversation cannot be resumed.`);
155
- }
156
- return sessionResult.data;
12
+ export function coerceString(value) {
13
+ return typeof value === "string" && value.trim().length > 0 ? value : undefined;
157
14
  }
158
- export function resolvePersistedSubagentSession(input) {
159
- const sessionText = input.sessionText?.trim();
160
- if (sessionText) {
161
- return parseSessionEnvelopeText(sessionText, input.resumePath);
162
- }
163
- return undefined;
15
+ function codexEffort(config) {
16
+ const value = config?.model_reasoning_effort;
17
+ return typeof value === "string" && EFFORT_LEVELS.includes(value) ? value : undefined;
164
18
  }
165
- function validateResumeSession(adapter, session, resumePath) {
166
- const envelopeResult = subagentSessionEnvelopeSchema.safeParse(session);
167
- if (!envelopeResult.success) {
168
- throw new FlowError("INVALID_RESPONSE_PATH", `Resume path file '${resumePath}' includes an invalid session envelope.`);
169
- }
170
- if (session.provider !== adapter.provider) {
171
- throw new FlowError("INVALID_RESPONSE_PATH", `Resume path file '${resumePath}' was created with provider '${session.provider}', but the selected subagent resolves to '${adapter.provider}'. Cross-provider resume is not supported.`);
172
- }
173
- if (session.version !== SUBAGENT_SESSION_VERSION) {
174
- throw new FlowError("INVALID_RESPONSE_PATH", `Resume path file '${resumePath}' uses unsupported session version '${session.version}' for provider '${session.provider}'.`);
175
- }
176
- const handleResult = adapter.handleSchema.safeParse(session.handle);
177
- if (!handleResult.success) {
178
- throw new FlowError("INVALID_RESPONSE_PATH", `Resume path file '${resumePath}' includes an invalid session handle for provider '${session.provider}'.`);
179
- }
180
- return handleResult.data;
19
+ export async function resolveSubagentConfig(agentName, cwd) {
20
+ return (await selectSubagent(agentName, cwd)).profile;
181
21
  }
182
- function formatAvailableAgentNames(names) {
183
- return names.length > 0 ? names.join(", ") : "(none configured)";
22
+ function maxCaptureCharsForProvider(provider) {
23
+ const config = getConfig();
24
+ switch (provider) {
25
+ case "codex-sdk":
26
+ return config.codex.execMaxCaptureChars;
27
+ case "codex-app-server":
28
+ return config.codex.execMaxCaptureChars;
29
+ case "claude-agent-sdk":
30
+ return config.claude.execMaxCaptureChars;
31
+ case "opencode-sdk":
32
+ return config.opencode.execMaxCaptureChars;
33
+ case "pi":
34
+ return config.codex.execMaxCaptureChars;
35
+ }
184
36
  }
185
- function createSyntheticSession(provider) {
186
- const handle = provider === "codex-cli" || provider === "codex-sdk"
187
- ? { threadId: `fake-${provider}-thread` }
188
- : { sessionId: `fake-${provider}-session` };
189
- return {
190
- provider,
191
- version: SUBAGENT_SESSION_VERSION,
192
- handle,
37
+ export function buildSummonTerms(config, prompt, cwd, options) {
38
+ const runtimeConfig = getConfig();
39
+ const baseTerms = {
40
+ workdir: cwd,
41
+ prompt,
42
+ timeoutMs: options.timeoutMs ?? runtimeConfig.subagent.execTimeoutMs,
43
+ idleTimeoutMs: options.idleTimeoutMs ?? runtimeConfig.subagent.execIdleTimeoutMs,
44
+ maxCaptureChars: maxCaptureCharsForProvider(config.provider),
45
+ signal: options.signal,
46
+ projectionDir: options.projectionDir,
47
+ projectionExecutionId: options.projectionExecutionId,
193
48
  };
194
- }
195
- export function createSyntheticSessionForProvider(provider) {
196
- return createSyntheticSession(provider);
197
- }
198
- export async function resolveSubagentConfig(agentName, cwd) {
199
- const settings = await loadKeiyakuSettings(cwd);
200
- const availableNames = getAvailableAgentNames(settings);
201
- if (!availableNames.includes(agentName)) {
202
- throw new FlowError("UNKNOWN_SUBAGENT", `Unknown agent '${agentName}'. Expected one of: ${formatAvailableAgentNames(availableNames)}`);
203
- }
204
- try {
205
- return normalizeAgentProfile(resolveAgentProfile(settings, agentName));
206
- }
207
- catch (error) {
208
- if (error instanceof FlowError && error.code === "UNKNOWN_SUBAGENT") {
209
- throw new FlowError("UNKNOWN_SUBAGENT", `Unknown agent '${agentName}'. Expected one of: ${formatAvailableAgentNames(availableNames)}`);
210
- }
211
- throw error;
49
+ const body = options.akumaBody;
50
+ const replayPrompt = body ? `[Akuma system prompt]\n${body}\n\n[Operator prompt]\n${prompt}` : prompt;
51
+ switch (config.provider) {
52
+ case "codex-sdk":
53
+ {
54
+ const effective = "effectivePolicy" in config && config.threadOptions
55
+ ? config
56
+ : resolveEffectiveCodexSdkProfile(config, cwd, {});
57
+ return {
58
+ ...baseTerms, prompt: replayPrompt,
59
+ provider: "codex-sdk",
60
+ model: effective.model,
61
+ profile: effective.profile,
62
+ config: effective.config,
63
+ threadOptions: effective.threadOptions,
64
+ effectivePolicy: effective.effectivePolicy,
65
+ executable: effective.executable,
66
+ };
67
+ }
68
+ case "codex-app-server":
69
+ return {
70
+ ...baseTerms,
71
+ provider: "codex-app-server",
72
+ model: config.model,
73
+ effort: codexEffort(config.config),
74
+ profile: config.profile,
75
+ config: config.config,
76
+ executable: config.executable,
77
+ developerInstructions: body || undefined,
78
+ onCheckpoint() { },
79
+ };
80
+ case "claude-agent-sdk":
81
+ return {
82
+ ...baseTerms,
83
+ provider: "claude-agent-sdk",
84
+ model: config.model,
85
+ executable: config.executable,
86
+ effort: config.effort,
87
+ permissionMode: config.permissionMode,
88
+ settingSources: config.settingSources,
89
+ systemPrompt: body ? { type: "preset", preset: "claude_code", append: body } : undefined,
90
+ onCheckpoint() { },
91
+ };
92
+ case "opencode-sdk":
93
+ return {
94
+ ...baseTerms, prompt: replayPrompt,
95
+ provider: "opencode-sdk",
96
+ model: config.model,
97
+ executable: config.executable,
98
+ };
99
+ case "pi":
100
+ return {
101
+ ...baseTerms,
102
+ provider: "pi",
103
+ model: config.model,
104
+ thinkingLevel: config.thinkingLevel,
105
+ appendSystemPromptOverride: body ? () => [body] : undefined,
106
+ onCheckpoint() { },
107
+ };
108
+ default:
109
+ return assertNever(config);
212
110
  }
213
111
  }
214
- export async function runSubagentExec(agentName, prompt, cwd, options = {}) {
215
- const config = await resolveSubagentConfig(agentName, cwd);
216
- const effectiveConfig = options.model ? { ...config, model: options.model } : config;
217
- const adapter = SUBAGENT_ADAPTERS[config.provider];
218
- if (!options.resume) {
219
- return adapter.run(prompt, cwd, { ...effectiveConfig, signal: options.signal });
220
- }
221
- const handle = validateResumeSession(adapter, options.resume.session, options.resume.resumePath);
222
- return adapter.resume(prompt, cwd, { ...effectiveConfig, signal: options.signal }, handle);
112
+ function assertNever(_value) {
113
+ throw new FlowError("INVALID_SETTINGS", "Unsupported subagent provider.");
223
114
  }
@@ -1,5 +1,7 @@
1
1
  import { spawn } from "node:child_process";
2
2
  import net from "node:net";
3
+ import { coerceString } from "./index.js";
4
+ import { createAbortError } from "./harness/runtime.js";
3
5
  const DEFAULT_HOSTNAME = "127.0.0.1";
4
6
  const DEFAULT_PORT = 4096;
5
7
  const DEFAULT_STARTUP_TIMEOUT_MS = 10_000;
@@ -21,22 +23,14 @@ const EVENT_DATA_PREFIX = "data:";
21
23
  function sleep(ms) {
22
24
  return new Promise((resolve) => setTimeout(resolve, ms));
23
25
  }
24
- function coerceString(value) {
25
- return typeof value === "string" && value.trim().length > 0 ? value : undefined;
26
- }
27
26
  function normalizeBaseUrl(baseUrl) {
28
27
  return coerceString(baseUrl)?.replace(/\/+$/, "") ?? `http://${DEFAULT_HOSTNAME}:${DEFAULT_PORT}`;
29
28
  }
30
- function createAbortError(message) {
31
- const error = new Error(message);
32
- error.name = "AbortError";
33
- return error;
34
- }
35
29
  function buildSessionPath(sessionId) {
36
30
  return `${SESSION_PATH}/${encodeURIComponent(sessionId)}`;
37
31
  }
38
- async function createJsonRequest(fetchImpl, baseUrl, requestPath, init = {}) {
39
- const response = await fetchImpl(`${baseUrl}${requestPath}`, {
32
+ async function createJsonRequest(fetcher, baseUrl, requestPath, init = {}) {
33
+ const response = await fetcher(`${baseUrl}${requestPath}`, {
40
34
  ...init,
41
35
  headers: {
42
36
  [RESPONSE_HEADER_ACCEPT]: JSON_CONTENT_TYPE,
@@ -95,40 +89,40 @@ function createSseStream(response) {
95
89
  };
96
90
  }
97
91
  export function createOpencodeClient(options = {}) {
98
- const fetchImpl = options.fetch ?? globalThis.fetch;
99
- if (typeof fetchImpl !== "function") {
92
+ const fetcher = options.fetch ?? globalThis.fetch;
93
+ if (typeof fetcher !== "function") {
100
94
  throw new Error("OpenCode SDK requires global fetch support");
101
95
  }
102
96
  const baseUrl = normalizeBaseUrl(options.baseUrl);
103
97
  return {
104
98
  config: {
105
99
  async get() {
106
- return createJsonRequest(fetchImpl, baseUrl, CONFIG_PATH);
100
+ return createJsonRequest(fetcher, baseUrl, CONFIG_PATH);
107
101
  },
108
102
  },
109
103
  session: {
110
104
  async create(input = {}) {
111
- return createJsonRequest(fetchImpl, baseUrl, SESSION_PATH, {
105
+ return createJsonRequest(fetcher, baseUrl, SESSION_PATH, {
112
106
  method: "POST",
113
107
  body: JSON.stringify(input.body ?? {}),
114
108
  });
115
109
  },
116
110
  async get(input) {
117
- return createJsonRequest(fetchImpl, baseUrl, buildSessionPath(input.path.id));
111
+ return createJsonRequest(fetcher, baseUrl, buildSessionPath(input.path.id));
118
112
  },
119
113
  async prompt(input) {
120
- return createJsonRequest(fetchImpl, baseUrl, `${buildSessionPath(input.path.id)}${SESSION_MESSAGE_PATH_SUFFIX}`, {
114
+ return createJsonRequest(fetcher, baseUrl, `${buildSessionPath(input.path.id)}${SESSION_MESSAGE_PATH_SUFFIX}`, {
121
115
  method: "POST",
122
116
  body: JSON.stringify(input.body),
123
117
  });
124
118
  },
125
119
  async messages(input) {
126
- return createJsonRequest(fetchImpl, baseUrl, `${buildSessionPath(input.path.id)}${SESSION_MESSAGE_PATH_SUFFIX}`);
120
+ return createJsonRequest(fetcher, baseUrl, `${buildSessionPath(input.path.id)}${SESSION_MESSAGE_PATH_SUFFIX}`);
127
121
  },
128
122
  },
129
123
  event: {
130
124
  async subscribe() {
131
- const response = await fetchImpl(`${baseUrl}${EVENT_PATH}`, {
125
+ const response = await fetcher(`${baseUrl}${EVENT_PATH}`, {
132
126
  method: "GET",
133
127
  headers: {
134
128
  [RESPONSE_HEADER_ACCEPT]: EVENT_STREAM_CONTENT_TYPE,