@astrosheep/keiyaku 2.8.5 → 2.9.1

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 (238) hide show
  1. package/README.md +35 -15
  2. package/build/.tsbuildinfo +1 -1
  3. package/build/agents/{call-terms_v2.js → call-terms.js} +12 -8
  4. package/build/agents/harness/control-types.js +1 -0
  5. package/build/agents/harness/execution-handle.js +23 -3
  6. package/build/agents/harness/index.js +6 -607
  7. package/build/agents/harness/outcome.js +141 -0
  8. package/build/agents/harness/projection.js +310 -0
  9. package/build/agents/harness/router.js +201 -0
  10. package/build/agents/harness/runtime.js +2 -1
  11. package/build/agents/{launch-snapshot_v2.js → launch-snapshot/model.js} +69 -74
  12. package/build/agents/launch-snapshot/resolve.js +72 -0
  13. package/build/agents/opencode-sdk.js +24 -3
  14. package/build/agents/providers/{claude-agent-sdk.js → claude-agent-sdk/adapter.js} +46 -368
  15. package/build/agents/providers/claude-agent-sdk/events.js +218 -0
  16. package/build/agents/providers/claude-agent-sdk/session.js +158 -0
  17. package/build/agents/providers/codex-app-server/adapter.js +347 -0
  18. package/build/agents/providers/codex-app-server/events.js +352 -0
  19. package/build/agents/providers/codex-app-server/session.js +266 -0
  20. package/build/agents/providers/codex-sdk.js +23 -5
  21. package/build/agents/providers/opencode-sdk/adapter.js +344 -0
  22. package/build/agents/providers/opencode-sdk/events.js +354 -0
  23. package/build/agents/providers/opencode-sdk/session.js +180 -0
  24. package/build/agents/providers/pi/adapter.js +269 -0
  25. package/build/agents/providers/pi/checkpoint.js +41 -0
  26. package/build/agents/providers/pi/events.js +282 -0
  27. package/build/agents/{selector_v2.js → selector.js} +8 -6
  28. package/build/cli/commands/akuma/akuma/handler.js +4 -0
  29. package/build/cli/commands/akuma/akuma/meta.js +16 -0
  30. package/build/cli/commands/akuma/catalog.js +8 -0
  31. package/build/cli/commands/akuma/list/handler.js +15 -0
  32. package/build/cli/commands/akuma/list/meta-ls.js +9 -0
  33. package/build/cli/commands/akuma/view/handler.js +34 -0
  34. package/build/cli/commands/akuma/view/meta.js +8 -0
  35. package/build/cli/commands/akuma.js +1 -1
  36. package/build/cli/commands/contract/amend/handler.js +10 -0
  37. package/build/cli/commands/contract/amend/meta.js +12 -0
  38. package/build/cli/commands/contract/arc/handler.js +9 -0
  39. package/build/cli/commands/contract/arc/meta.js +26 -0
  40. package/build/cli/commands/contract/bind/handler.js +23 -0
  41. package/build/cli/commands/contract/bind/meta.js +30 -0
  42. package/build/cli/commands/contract/catalog.js +16 -0
  43. package/build/cli/commands/contract/forfeit/handler.js +13 -0
  44. package/build/cli/commands/contract/forfeit/meta.js +12 -0
  45. package/build/cli/commands/contract/log/handler.js +12 -0
  46. package/build/cli/commands/contract/log/meta.js +12 -0
  47. package/build/cli/commands/contract/petition/handler.js +26 -0
  48. package/build/cli/commands/contract/petition/meta.js +23 -0
  49. package/build/cli/commands/contract/renew/handler.js +10 -0
  50. package/build/cli/commands/contract/renew/meta.js +17 -0
  51. package/build/cli/commands/projection/call/handler.js +42 -0
  52. package/build/cli/commands/projection/call/meta.js +9 -0
  53. package/build/cli/commands/projection/catalog.js +14 -0
  54. package/build/cli/commands/projection/kill/handler.js +37 -0
  55. package/build/cli/commands/projection/kill/meta.js +11 -0
  56. package/build/cli/commands/projection/revive/handler.js +56 -0
  57. package/build/cli/commands/projection/revive/meta.js +9 -0
  58. package/build/cli/commands/projection/status/handler.js +25 -0
  59. package/build/cli/commands/projection/status/meta.js +9 -0
  60. package/build/cli/commands/projection/tell/handler.js +36 -0
  61. package/build/cli/commands/projection/tell/meta.js +9 -0
  62. package/build/cli/commands/projection/wait/handler.js +23 -0
  63. package/build/cli/commands/projection/wait/meta.js +11 -0
  64. package/build/cli/commands/registry.js +100 -0
  65. package/build/cli/commands/shared.js +71 -0
  66. package/build/cli/commands/skills/catalog.js +6 -0
  67. package/build/cli/commands/skills/install/handler.js +10 -0
  68. package/build/cli/commands/skills/install/meta.js +9 -0
  69. package/build/cli/commands/skills/skills/handler.js +4 -0
  70. package/build/cli/commands/skills/skills/meta.js +10 -0
  71. package/build/cli/commands/system/catalog.js +8 -0
  72. package/build/cli/commands/system/completion/handler.js +10 -0
  73. package/build/cli/commands/system/completion/meta.js +9 -0
  74. package/build/cli/commands/system/dump-env/handler.js +3 -0
  75. package/build/cli/commands/system/dump-env/meta.js +10 -0
  76. package/build/cli/commands/system/guide/handler.js +4 -0
  77. package/build/cli/commands/system/guide/meta.js +9 -0
  78. package/build/cli/commands/task/add/handler.js +21 -0
  79. package/build/cli/commands/task/add/meta.js +8 -0
  80. package/build/cli/commands/task/catalog.js +22 -0
  81. package/build/cli/commands/task/doctor/handler.js +4 -0
  82. package/build/cli/commands/task/doctor/meta.js +8 -0
  83. package/build/cli/commands/task/done/handler.js +17 -0
  84. package/build/cli/commands/task/done/meta.js +8 -0
  85. package/build/cli/commands/task/drop/handler.js +6 -0
  86. package/build/cli/commands/task/drop/meta.js +8 -0
  87. package/build/cli/commands/task/ls/handler.js +13 -0
  88. package/build/cli/commands/task/ls/meta.js +8 -0
  89. package/build/cli/commands/task/shared.js +131 -0
  90. package/build/cli/commands/task/start/handler.js +6 -0
  91. package/build/cli/commands/task/start/meta.js +8 -0
  92. package/build/cli/commands/task/stop/handler.js +17 -0
  93. package/build/cli/commands/task/stop/meta.js +8 -0
  94. package/build/cli/commands/task/task/handler.js +5 -0
  95. package/build/cli/commands/task/task/meta.js +11 -0
  96. package/build/cli/commands/task/update/handler.js +18 -0
  97. package/build/cli/commands/task/update/meta.js +8 -0
  98. package/build/cli/commands/task/view/handler.js +14 -0
  99. package/build/cli/commands/task/view/meta.js +8 -0
  100. package/build/cli/completion.js +26 -27
  101. package/build/cli/flags.js +79 -2
  102. package/build/cli/help.js +27 -49
  103. package/build/cli/index.js +121 -374
  104. package/build/cli/output-stream-error-policy.js +17 -0
  105. package/build/cli/parse-flags.js +197 -0
  106. package/build/cli/parse-metadata.js +129 -0
  107. package/build/cli/parse-selectors.js +65 -0
  108. package/build/cli/parse.js +46 -313
  109. package/build/cli/projection-address.js +5 -0
  110. package/build/cli/render/arc.js +1 -1
  111. package/build/cli/render/errors.js +4 -3
  112. package/build/cli/render/format.js +14 -0
  113. package/build/cli/render/kanshi.js +106 -0
  114. package/build/cli/render/petition.js +24 -1
  115. package/build/cli/render/projection-activity.js +5 -1
  116. package/build/cli/render/shared.js +2 -2
  117. package/build/cli/render/status.js +79 -13
  118. package/build/cli/render/task.js +113 -0
  119. package/build/cli/render/wait.js +23 -3
  120. package/build/cli/types.js +1 -1
  121. package/build/config/{akuma-loader_v2.js → akuma-loader.js} +7 -4
  122. package/build/config/env.js +9 -4
  123. package/build/config/settings/disease.js +26 -0
  124. package/build/config/settings/knobs.js +8 -0
  125. package/build/config/settings/loader.js +244 -0
  126. package/build/config/settings/schema.js +276 -0
  127. package/build/core/addressing.js +1 -1
  128. package/build/core/amend.js +5 -8
  129. package/build/core/arc.js +7 -8
  130. package/build/core/bind-workspace.js +123 -0
  131. package/build/core/bind.js +75 -155
  132. package/build/core/call/call.js +229 -0
  133. package/build/core/call/context.js +251 -0
  134. package/build/core/call/execution.js +217 -0
  135. package/build/core/call/prompt.js +42 -0
  136. package/build/core/call/reference.js +24 -0
  137. package/build/core/call-persist.js +10 -3
  138. package/build/core/claim-delivery.js +247 -0
  139. package/build/core/claim.js +39 -277
  140. package/build/core/context.js +2 -1
  141. package/build/core/{render.js → contract-view.js} +12 -140
  142. package/build/core/draft.js +107 -9
  143. package/build/core/entry.js +9 -1
  144. package/build/core/execution-coordinate.js +4 -20
  145. package/build/core/execution-pact.js +16 -31
  146. package/build/core/forfeit.js +35 -22
  147. package/build/core/hints.js +2 -2
  148. package/build/core/log.js +2 -2
  149. package/build/core/path-coordinate.js +27 -0
  150. package/build/core/petition-claim-gates.js +8 -6
  151. package/build/core/petition-preview.js +97 -0
  152. package/build/core/petition-run.js +5 -7
  153. package/build/core/petition.js +39 -26
  154. package/build/core/projection/generation/database.js +306 -0
  155. package/build/core/projection/generation/identity.js +84 -0
  156. package/build/core/projection/generation/model.js +201 -0
  157. package/build/core/projection/generation/store.js +73 -0
  158. package/build/core/projection/generation/transitions.js +258 -0
  159. package/build/core/projection/heart.js +0 -307
  160. package/build/core/projection/tell/database.js +109 -0
  161. package/build/core/projection/tell/model.js +97 -0
  162. package/build/core/{projection-tells.js → projection/tell/store.js} +3 -158
  163. package/build/core/projection-activity.js +23 -2
  164. package/build/core/projection-coordinate.js +14 -2
  165. package/build/core/projection-core.js +2 -1
  166. package/build/core/projection-execution-observer.js +259 -0
  167. package/build/core/projection-generation-continuation.js +1 -1
  168. package/build/core/projection-generation-execution.js +90 -20
  169. package/build/core/projection-generation-launcher.js +1 -1
  170. package/build/core/projection-generation-runner.js +198 -4
  171. package/build/core/projection-identity.js +1 -1
  172. package/build/core/projection-kill.js +50 -17
  173. package/build/core/projection-life-observer.js +1 -1
  174. package/build/core/projection-life-protocol.js +10 -0
  175. package/build/core/projection-mint.js +1 -1
  176. package/build/core/projection-status.js +45 -32
  177. package/build/core/projection-wait.js +12 -2
  178. package/build/core/projection-wake.js +5 -2
  179. package/build/core/{queue_v2.js → queue.js} +2 -1
  180. package/build/core/registry.js +1 -1
  181. package/build/core/renew.js +9 -11
  182. package/build/core/response-artifact-catalog-sqlite.js +112 -0
  183. package/build/core/response-artifact-catalog.js +7 -0
  184. package/build/core/seal.js +44 -16
  185. package/build/core/status/board.js +289 -0
  186. package/build/core/status/drift.js +99 -0
  187. package/build/core/status/lifecycle.js +84 -0
  188. package/build/core/status/reconciliation.js +131 -0
  189. package/build/core/stored-agent-event.js +28 -4
  190. package/build/core/target-ref.js +10 -0
  191. package/build/core/task/board.js +175 -0
  192. package/build/core/task/commands.js +211 -0
  193. package/build/core/task/document.js +189 -0
  194. package/build/core/task/model.js +1 -0
  195. package/build/core/task/settlement-git.js +207 -0
  196. package/build/core/task/settlement-policy.js +82 -0
  197. package/build/core/task/source-board.js +84 -0
  198. package/build/core/task/source-model.js +1 -0
  199. package/build/core/task/validation.js +44 -0
  200. package/build/core/task-contract.js +237 -0
  201. package/build/core/task-doctor.js +14 -0
  202. package/build/core/task-git-actor.js +38 -0
  203. package/build/core/task-git-runtime.js +37 -0
  204. package/build/core/task-git-store.js +385 -0
  205. package/build/core/task-repository.js +1 -0
  206. package/build/core/task-store-repository.js +86 -0
  207. package/build/core/task-store.js +1 -0
  208. package/build/core/task-worktree.js +51 -0
  209. package/build/core/task.js +4 -0
  210. package/build/core/transcripts.js +138 -56
  211. package/build/core/validation.js +17 -0
  212. package/build/core/verdict.js +2 -2
  213. package/build/core/worktree-path.js +33 -8
  214. package/build/flow-error.js +5 -2
  215. package/build/generated/version.js +1 -1
  216. package/build/git/branches.js +33 -9
  217. package/build/git/commits.js +3 -0
  218. package/build/git/core.js +9 -0
  219. package/build/git/diff/parsers.js +0 -94
  220. package/build/index.js +2 -0
  221. package/build/keiyaku-constants.js +2 -0
  222. package/build/keiyaku.js +4 -3
  223. package/package.json +3 -2
  224. package/skills/keiyaku/SKILL.md +30 -13
  225. package/skills/keiyaku-akuma/SKILL.md +30 -57
  226. package/skills/keiyaku-task/SKILL.md +51 -0
  227. package/skills/keiyaku-workflow/SKILL.md +71 -0
  228. package/build/agents/harness/pump.js +0 -158
  229. package/build/agents/providers/codex-app-server.js +0 -864
  230. package/build/agents/providers/opencode-sdk.js +0 -363
  231. package/build/agents/providers/pi.js +0 -557
  232. package/build/config/settings.js +0 -533
  233. package/build/core/call.js +0 -686
  234. package/build/core/projection/generation.js +0 -412
  235. package/build/core/projection/identity.js +0 -81
  236. package/build/core/projection/mint.js +0 -174
  237. package/build/core/projection-generation-store.js +0 -707
  238. package/build/core/status.js +0 -539
@@ -0,0 +1,244 @@
1
+ import * as fs from "node:fs/promises";
2
+ import * as path from "node:path";
3
+ import { isErrnoException } from "../../errno.js";
4
+ import { SETTINGS_FILE } from "../../keiyaku.js";
5
+ import { getKeiyakuHome } from "../env.js";
6
+ import { KNOWN_SETTINGS_KNOBS, KNOWN_SETTINGS_ROOT_KEYS } from "./knobs.js";
7
+ import { BUILTIN_PROVIDER_INSTANCE_CANDIDATES, knobSchemas, providerInstanceNameSchema, providerInstanceSchema, } from "./schema.js";
8
+ function isPlainObject(value) {
9
+ return typeof value === "object" && value !== null && !Array.isArray(value);
10
+ }
11
+ function deepMerge(base, override) {
12
+ if (!isPlainObject(base) || !isPlainObject(override)) {
13
+ return override;
14
+ }
15
+ const merged = { ...base };
16
+ for (const [key, value] of Object.entries(override)) {
17
+ merged[key] = key in merged ? deepMerge(merged[key], value) : value;
18
+ }
19
+ return merged;
20
+ }
21
+ function formatZodIssues(error) {
22
+ return error.issues
23
+ .map((issue) => `${issue.path.join(".") || "(root)"}: ${issue.message}`)
24
+ .join("; ");
25
+ }
26
+ async function readSettingsSource(filePath) {
27
+ try {
28
+ const text = await fs.readFile(filePath, "utf-8");
29
+ try {
30
+ return { found: true, raw: JSON.parse(text) };
31
+ }
32
+ catch (error) {
33
+ const reason = error instanceof SyntaxError
34
+ ? `invalid JSON: ${error.message}`
35
+ : error instanceof Error
36
+ ? error.message
37
+ : String(error);
38
+ return { found: true, raw: undefined, parseError: reason };
39
+ }
40
+ }
41
+ catch (error) {
42
+ if (isErrnoException(error) && error.code === "ENOENT") {
43
+ return { found: false };
44
+ }
45
+ const reason = error instanceof Error ? error.message : String(error);
46
+ return { found: true, raw: undefined, parseError: reason };
47
+ }
48
+ }
49
+ function builtinProviderInstanceCatalog() {
50
+ return { selected: { ...BUILTIN_PROVIDER_INSTANCE_CANDIDATES }, shadows: [] };
51
+ }
52
+ function formatProviderInstanceIssues(name, error) {
53
+ return error.issues
54
+ .map((issue) => {
55
+ const suffix = issue.path.length === 0 ? "" : `.${issue.path.join(".")}`;
56
+ return `providers.${name}${suffix}: ${issue.message}`;
57
+ })
58
+ .join("; ");
59
+ }
60
+ function collectProviderCandidates(source, rawProviders) {
61
+ if (!isPlainObject(rawProviders)) {
62
+ return {
63
+ candidates: [],
64
+ disease: { source: source === "project" ? "local" : "global", knob: "providers", reason: "providers: must be a JSON object" },
65
+ };
66
+ }
67
+ const candidates = [];
68
+ for (const [name, rawInstance] of Object.entries(rawProviders)) {
69
+ const nameResult = providerInstanceNameSchema.safeParse(name);
70
+ if (!nameResult.success) {
71
+ candidates.push({
72
+ name,
73
+ source,
74
+ reason: formatProviderInstanceIssues(name, nameResult.error),
75
+ });
76
+ continue;
77
+ }
78
+ const instanceResult = providerInstanceSchema.safeParse(rawInstance);
79
+ if (!instanceResult.success) {
80
+ candidates.push({
81
+ name,
82
+ source,
83
+ reason: formatProviderInstanceIssues(name, instanceResult.error),
84
+ });
85
+ continue;
86
+ }
87
+ const instance = instanceResult.data;
88
+ if (instance.kind === "codex-app-server" && instance.writableRoots !== undefined && source === "global") {
89
+ let relativeIndex;
90
+ for (let i = 0; i < instance.writableRoots.length; i++) {
91
+ if (!path.posix.isAbsolute(instance.writableRoots[i])) {
92
+ relativeIndex = i;
93
+ break;
94
+ }
95
+ }
96
+ if (relativeIndex !== undefined) {
97
+ candidates.push({
98
+ name,
99
+ source,
100
+ reason: `providers.${name}.writableRoots.${relativeIndex}: writableRoots item must be an absolute path for global provider instances`,
101
+ });
102
+ continue;
103
+ }
104
+ }
105
+ candidates.push({ name, source, instance: instanceResult.data });
106
+ }
107
+ return { candidates };
108
+ }
109
+ function mergeProviderCandidates(catalog, candidates) {
110
+ const selected = { ...catalog.selected };
111
+ const shadows = [...catalog.shadows];
112
+ for (const candidate of candidates) {
113
+ const prior = selected[candidate.name];
114
+ if (prior !== undefined) {
115
+ shadows.push({ name: candidate.name, source: prior.source, shadowedBy: candidate.source });
116
+ }
117
+ selected[candidate.name] = candidate;
118
+ }
119
+ return { selected, shadows };
120
+ }
121
+ function collectSourceDiseases(source, providerSource, read) {
122
+ if (!read.found) {
123
+ return { partial: {}, diseases: [], providerCandidates: [] };
124
+ }
125
+ if (read.parseError !== undefined) {
126
+ return {
127
+ partial: {},
128
+ diseases: [{ source, reason: `settings root is unreadable (${read.parseError})` }],
129
+ providerCandidates: [],
130
+ };
131
+ }
132
+ if (!isPlainObject(read.raw)) {
133
+ return {
134
+ partial: {},
135
+ diseases: [{ source, reason: "settings root must be a JSON object" }],
136
+ providerCandidates: [],
137
+ };
138
+ }
139
+ const diseases = [];
140
+ const partial = {};
141
+ let providerCandidates = [];
142
+ const raw = read.raw;
143
+ for (const key of Object.keys(raw)) {
144
+ if (!KNOWN_SETTINGS_ROOT_KEYS.includes(key)) {
145
+ diseases.push({
146
+ source,
147
+ knob: key,
148
+ reason: key === "agents"
149
+ ? 'settings key "agents" has been removed; define profiles in .keiyaku/akuma/<name>.md'
150
+ : `unknown settings key "${key}"`,
151
+ });
152
+ }
153
+ }
154
+ if ("providers" in raw) {
155
+ const collectedProviders = collectProviderCandidates(providerSource, raw.providers);
156
+ providerCandidates = collectedProviders.candidates;
157
+ if (collectedProviders.disease !== undefined)
158
+ diseases.push(collectedProviders.disease);
159
+ }
160
+ for (const knob of KNOWN_SETTINGS_KNOBS) {
161
+ if (!(knob in raw))
162
+ continue;
163
+ // worktreeBootstrap is project-layer only. Global/user presence is a named
164
+ // disease and never contributes execution authority.
165
+ if (knob === "worktreeBootstrap" && source === "global") {
166
+ diseases.push({
167
+ source,
168
+ knob,
169
+ reason: "worktreeBootstrap is project-only and never supplies a value from global settings",
170
+ });
171
+ continue;
172
+ }
173
+ const schema = knobSchemas[knob];
174
+ const result = schema.safeParse(raw[knob]);
175
+ if (!result.success) {
176
+ diseases.push({
177
+ source,
178
+ knob,
179
+ reason: formatZodIssues(result.error),
180
+ });
181
+ continue;
182
+ }
183
+ if (result.data === undefined)
184
+ continue;
185
+ partial[knob] = result.data;
186
+ }
187
+ return { partial, diseases, providerCandidates };
188
+ }
189
+ function mergeSettingsKnobs(base, override) {
190
+ // worktreeBootstrap is whole-value project authority; never field-merge it.
191
+ const { worktreeBootstrap: baseBootstrap, ...baseRest } = base;
192
+ const { worktreeBootstrap: overrideBootstrap, ...overrideRest } = override;
193
+ const mergedRaw = deepMerge(baseRest, overrideRest);
194
+ if (!isPlainObject(mergedRaw)) {
195
+ return overrideBootstrap !== undefined
196
+ ? { ...override, worktreeBootstrap: overrideBootstrap }
197
+ : { ...override };
198
+ }
199
+ const merged = {};
200
+ for (const knob of KNOWN_SETTINGS_KNOBS) {
201
+ if (knob === "worktreeBootstrap")
202
+ continue;
203
+ if (knob in mergedRaw) {
204
+ merged[knob] = mergedRaw[knob];
205
+ }
206
+ }
207
+ const bootstrap = overrideBootstrap ?? baseBootstrap;
208
+ if (bootstrap !== undefined)
209
+ merged.worktreeBootstrap = bootstrap;
210
+ return merged;
211
+ }
212
+ export async function loadKeiyakuSettings(cwd) {
213
+ const localSettingsPath = path.join(cwd, SETTINGS_FILE);
214
+ const globalSettingsPath = path.join(getKeiyakuHome(), path.basename(SETTINGS_FILE));
215
+ let globalRead;
216
+ let localRead;
217
+ try {
218
+ [globalRead, localRead] = await Promise.all([
219
+ readSettingsSource(globalSettingsPath),
220
+ readSettingsSource(localSettingsPath),
221
+ ]);
222
+ }
223
+ catch (error) {
224
+ // loadKeiyakuSettings never throws; unexpected I/O becomes a local root disease.
225
+ const reason = error instanceof Error ? error.message : String(error);
226
+ return {
227
+ knobs: null,
228
+ diseases: [{ source: "local", reason: `settings root is unreadable (${reason})` }],
229
+ providerInstances: builtinProviderInstanceCatalog(),
230
+ };
231
+ }
232
+ const globalCollected = collectSourceDiseases("global", "global", globalRead);
233
+ const localCollected = collectSourceDiseases("local", "project", localRead);
234
+ const diseases = [...globalCollected.diseases, ...localCollected.diseases];
235
+ let providerInstances = builtinProviderInstanceCatalog();
236
+ providerInstances = mergeProviderCandidates(providerInstances, globalCollected.providerCandidates);
237
+ providerInstances = mergeProviderCandidates(providerInstances, localCollected.providerCandidates);
238
+ let knobs = null;
239
+ if (globalRead.found || localRead.found) {
240
+ knobs = mergeSettingsKnobs(globalCollected.partial, localCollected.partial);
241
+ }
242
+ // Empty object still means "settings files were present"; callers treat null as absent files.
243
+ return { knobs, diseases, providerInstances };
244
+ }
@@ -0,0 +1,276 @@
1
+ import { z } from "zod";
2
+ import { CLAUDE_SETTING_SOURCES, } from "../../keiyaku.js";
3
+ const tomlValueSchema = z.lazy(() => z.union([
4
+ z.string(),
5
+ z.number(),
6
+ z.boolean(),
7
+ z.array(tomlValueSchema),
8
+ z.record(z.string(), tomlValueSchema),
9
+ ]));
10
+ const structuredTomlValueSchema = z.record(z.string(), tomlValueSchema);
11
+ const executableSchema = z.string().superRefine((value, ctx) => {
12
+ if (value.length === 0 || /\s/.test(value) || value.includes("\"") || value.includes("'")) {
13
+ ctx.addIssue({
14
+ code: z.ZodIssueCode.custom,
15
+ message: "must be a single program name token with no spaces or quotes.",
16
+ });
17
+ }
18
+ });
19
+ const nonblankOptionalString = z
20
+ .string()
21
+ .refine((value) => value.trim().length > 0, { message: "must not be blank." })
22
+ .optional();
23
+ const providerCommonFields = {
24
+ description: nonblankOptionalString,
25
+ };
26
+ const providerEnvSchema = z.record(z.string(), z.string()).optional();
27
+ function refinePiEnvInjection(data, ctx) {
28
+ if (data.env === undefined)
29
+ return;
30
+ if (Object.keys(data.env).length === 0)
31
+ return;
32
+ ctx.addIssue({
33
+ code: z.ZodIssueCode.custom,
34
+ path: ["env"],
35
+ message: "env injection not supported for provider pi",
36
+ });
37
+ }
38
+ const CODEX_SDK_FORBIDDEN_THREAD_OPTIONS_KEYS = [
39
+ "model",
40
+ "modelReasoningEffort",
41
+ "sandboxMode",
42
+ "networkAccessEnabled",
43
+ "webSearchMode",
44
+ "approvalPolicy",
45
+ "workingDirectory",
46
+ "skipGitRepoCheck",
47
+ ];
48
+ const FORBIDDEN_CONFIG_KEYS = [
49
+ "model",
50
+ "model_reasoning_effort",
51
+ ];
52
+ function isConfigKeyForbidden(key) {
53
+ return FORBIDDEN_CONFIG_KEYS.includes(key);
54
+ }
55
+ const claudeSettingSourceSchema = z.enum(CLAUDE_SETTING_SOURCES);
56
+ const writableRootItemSchema = z.string().superRefine((value, ctx) => {
57
+ if (value.length === 0) {
58
+ ctx.addIssue({ code: z.ZodIssueCode.custom, message: "must not be empty." });
59
+ return;
60
+ }
61
+ if (value.startsWith("~")) {
62
+ ctx.addIssue({ code: z.ZodIssueCode.custom, message: "must not begin with tilde." });
63
+ return;
64
+ }
65
+ if (value.includes("\0")) {
66
+ ctx.addIssue({ code: z.ZodIssueCode.custom, message: "must not contain NUL byte." });
67
+ return;
68
+ }
69
+ if (/\$[A-Za-z_]/.test(value) || /\$\{/.test(value)) {
70
+ ctx.addIssue({ code: z.ZodIssueCode.custom, message: "must not contain environment variable references." });
71
+ return;
72
+ }
73
+ });
74
+ export const providerInstanceSchema = z.discriminatedUnion("kind", [
75
+ z
76
+ .object({
77
+ kind: z.literal("codex-sdk"),
78
+ ...providerCommonFields,
79
+ executable: executableSchema.optional(),
80
+ profile: z.string().optional(),
81
+ config: structuredTomlValueSchema.optional(),
82
+ threadOptions: structuredTomlValueSchema.optional(),
83
+ })
84
+ .strict()
85
+ .superRefine((data, ctx) => {
86
+ if (data.threadOptions) {
87
+ for (const key of CODEX_SDK_FORBIDDEN_THREAD_OPTIONS_KEYS) {
88
+ if (key in data.threadOptions) {
89
+ ctx.addIssue({
90
+ code: z.ZodIssueCode.custom,
91
+ path: ["threadOptions", key],
92
+ message: `"${key}" is owned by the Akuma or Keiyaku execution, not this provider instance`,
93
+ });
94
+ }
95
+ }
96
+ }
97
+ if (data.config) {
98
+ for (const key of Object.keys(data.config)) {
99
+ if (isConfigKeyForbidden(key)) {
100
+ ctx.addIssue({
101
+ code: z.ZodIssueCode.custom,
102
+ path: ["config", key],
103
+ message: `"${key}" is owned by the Akuma or Keiyaku execution, not this provider instance`,
104
+ });
105
+ }
106
+ }
107
+ }
108
+ }),
109
+ z
110
+ .object({
111
+ kind: z.literal("codex-app-server"),
112
+ ...providerCommonFields,
113
+ executable: executableSchema.optional(),
114
+ profile: z.string().optional(),
115
+ config: structuredTomlValueSchema.optional(),
116
+ writableRoots: z.array(writableRootItemSchema).optional(),
117
+ env: providerEnvSchema,
118
+ })
119
+ .strict()
120
+ .superRefine((data, ctx) => {
121
+ if (data.config) {
122
+ for (const key of Object.keys(data.config)) {
123
+ if (isConfigKeyForbidden(key)) {
124
+ ctx.addIssue({
125
+ code: z.ZodIssueCode.custom,
126
+ path: ["config", key],
127
+ message: `"${key}" is owned by the Akuma or Keiyaku execution, not this provider instance`,
128
+ });
129
+ }
130
+ }
131
+ }
132
+ }),
133
+ z
134
+ .object({
135
+ kind: z.literal("claude-agent-sdk"),
136
+ ...providerCommonFields,
137
+ executable: executableSchema.optional(),
138
+ settingSources: z.array(claudeSettingSourceSchema).optional(),
139
+ env: providerEnvSchema,
140
+ })
141
+ .strict(),
142
+ z
143
+ .object({
144
+ kind: z.literal("opencode-sdk"),
145
+ ...providerCommonFields,
146
+ executable: executableSchema.optional(),
147
+ env: providerEnvSchema,
148
+ })
149
+ .strict(),
150
+ z
151
+ .object({
152
+ kind: z.literal("pi"),
153
+ ...providerCommonFields,
154
+ env: providerEnvSchema,
155
+ })
156
+ .strict()
157
+ .superRefine(refinePiEnvInjection),
158
+ ]);
159
+ const PROVIDER_INSTANCE_NAME_PATTERN = /^[a-z0-9][a-z0-9-]*$/;
160
+ export const providerInstanceNameSchema = z
161
+ .string()
162
+ .max(64, "must contain at most 64 characters.")
163
+ .regex(PROVIDER_INSTANCE_NAME_PATTERN, "must match ^[a-z0-9][a-z0-9-]*$.");
164
+ export const BUILTIN_PROVIDER_INSTANCE_CANDIDATES = {
165
+ "codex-sdk": { name: "codex-sdk", source: "builtin", instance: { kind: "codex-sdk", executable: "codex" } },
166
+ "codex-app-server": {
167
+ name: "codex-app-server",
168
+ source: "builtin",
169
+ instance: { kind: "codex-app-server", executable: "codex" },
170
+ },
171
+ "claude-agent-sdk": {
172
+ name: "claude-agent-sdk",
173
+ source: "builtin",
174
+ instance: { kind: "claude-agent-sdk", executable: "claude", settingSources: ["project"] },
175
+ },
176
+ "opencode-sdk": {
177
+ name: "opencode-sdk",
178
+ source: "builtin",
179
+ instance: { kind: "opencode-sdk", executable: "opencode" },
180
+ },
181
+ pi: { name: "pi", source: "builtin", instance: { kind: "pi" } },
182
+ };
183
+ const AGENT_NAME_PATTERN = /^[a-zA-Z0-9]+(?:[.-][a-zA-Z0-9]+)*$/;
184
+ export const agentNameSchema = z
185
+ .string()
186
+ .min(1, "must not be empty.")
187
+ .regex(AGENT_NAME_PATTERN, "must contain only alphanumeric characters, dots, and hyphens, and must not start or end with a separator.");
188
+ function optionalTrimmedString() {
189
+ return z.string().transform((value) => {
190
+ const trimmed = value.trim();
191
+ return trimmed.length > 0 ? trimmed : undefined;
192
+ }).optional();
193
+ }
194
+ function requiredTrimmedString(message) {
195
+ return z.string().transform((value, ctx) => {
196
+ const trimmed = value.trim();
197
+ if (trimmed.length === 0) {
198
+ ctx.addIssue({
199
+ code: z.ZodIssueCode.custom,
200
+ message,
201
+ });
202
+ return z.NEVER;
203
+ }
204
+ return trimmed;
205
+ });
206
+ }
207
+ const gateCommandSchema = z
208
+ .object({
209
+ label: requiredTrimmedString("must not be empty."),
210
+ command: requiredTrimmedString("must not be empty.").superRefine((value, ctx) => {
211
+ if (/\s/.test(value) || value.includes("\"") || value.includes("'")) {
212
+ ctx.addIssue({
213
+ code: z.ZodIssueCode.custom,
214
+ message: "must be a single program name token with no spaces or quotes.",
215
+ });
216
+ }
217
+ }),
218
+ args: z.array(z.string()).optional(),
219
+ })
220
+ .strict()
221
+ .transform((value) => ({
222
+ ...value,
223
+ args: value.args ?? [],
224
+ }));
225
+ const gateSchema = z
226
+ .object({
227
+ commands: z.array(gateCommandSchema).optional(),
228
+ })
229
+ .strict();
230
+ const WORKTREE_BOOTSTRAP_DEFAULT_TIMEOUT_MS = 300_000;
231
+ const WORKTREE_BOOTSTRAP_MIN_TIMEOUT_MS = 1;
232
+ const WORKTREE_BOOTSTRAP_MAX_TIMEOUT_MS = 3_600_000;
233
+ export const worktreeBootstrapSchema = z
234
+ .object({
235
+ command: z
236
+ .array(z.string())
237
+ .min(1, "command must be a nonempty argv array.")
238
+ .superRefine((items, ctx) => {
239
+ items.forEach((item, index) => {
240
+ if (item.trim().length === 0) {
241
+ ctx.addIssue({
242
+ code: z.ZodIssueCode.custom,
243
+ path: [index],
244
+ message: "command items must be nonempty after trimming.",
245
+ });
246
+ }
247
+ });
248
+ }),
249
+ timeoutMs: z
250
+ .number()
251
+ .int("timeoutMs must be an integer.")
252
+ .min(WORKTREE_BOOTSTRAP_MIN_TIMEOUT_MS, `timeoutMs must be >= ${WORKTREE_BOOTSTRAP_MIN_TIMEOUT_MS}.`)
253
+ .max(WORKTREE_BOOTSTRAP_MAX_TIMEOUT_MS, `timeoutMs must be <= ${WORKTREE_BOOTSTRAP_MAX_TIMEOUT_MS}.`)
254
+ .optional(),
255
+ })
256
+ .strict()
257
+ .transform((value) => ({
258
+ command: value.command,
259
+ timeoutMs: value.timeoutMs ?? WORKTREE_BOOTSTRAP_DEFAULT_TIMEOUT_MS,
260
+ }));
261
+ const settingsSchema = z
262
+ .object({
263
+ default: agentNameSchema.optional(),
264
+ reviewer: agentNameSchema.optional(),
265
+ gate: gateSchema.optional(),
266
+ defaultBranch: optionalTrimmedString(),
267
+ worktreeBootstrap: worktreeBootstrapSchema.optional(),
268
+ })
269
+ .strict();
270
+ export const knobSchemas = {
271
+ default: agentNameSchema,
272
+ reviewer: agentNameSchema,
273
+ gate: gateSchema,
274
+ defaultBranch: optionalTrimmedString(),
275
+ worktreeBootstrap: worktreeBootstrapSchema,
276
+ };
@@ -3,7 +3,7 @@ import { FlowError } from "../flow-error.js";
3
3
  import { listContractIds, readLedger } from "./ledger.js";
4
4
  import { isResponseArtifactId } from "./response-artifact-id.js";
5
5
  import { stableRepoRoot } from "./worktree-path.js";
6
- import { deriveContractState, isTerminalState, } from "./status.js";
6
+ import { deriveContractState, isTerminalState, } from "./status/lifecycle.js";
7
7
  function normalizeAddressQuery(raw) {
8
8
  const value = raw.startsWith("@") ? raw.slice(1) : raw;
9
9
  if (!value.trim()) {
@@ -1,13 +1,13 @@
1
1
  import * as fs from "node:fs/promises";
2
2
  import * as path from "node:path";
3
3
  import { FlowError } from "../flow-error.js";
4
- import { getCurrentBranch, isGitRepo } from "../git/branches.js";
4
+ import { getCurrentBranch, requireGitRepository } from "../git/branches.js";
5
5
  import { KEIYAKU_FILE } from "../keiyaku.js";
6
6
  import { activeLedgerRef, appendWithRetry, readLedger } from "./ledger.js";
7
- import { renderContractView } from "./render.js";
8
- import { deriveContractState, isTerminalState } from "./status.js";
7
+ import { renderContractView } from "./contract-view.js";
8
+ import { deriveContractState, isTerminalState } from "./status/lifecycle.js";
9
9
  import { resolveContractAddress, withResolvedAddress } from "./addressing.js";
10
- import { resolveDefaultBranch } from "./status.js";
10
+ import { resolveDefaultBranch } from "./status/drift.js";
11
11
  import { assertCleanWorkingTree } from "./arc.js";
12
12
  import { normalizeScopePattern } from "./scope.js";
13
13
  function requireAmendment(value) {
@@ -71,10 +71,7 @@ export async function amendContract(input) {
71
71
  return { contractId: input.contractId, amendmentNumber, ledgerHead: result.head };
72
72
  }
73
73
  export async function amendKeiyaku(input) {
74
- const isRepo = await isGitRepo(input.cwd);
75
- if (!isRepo) {
76
- throw new FlowError("NOT_GIT_REPO", `${input.cwd} is not a git repository`);
77
- }
74
+ await requireGitRepository(input.cwd, "amend");
78
75
  const address = await resolveContractAddress(input.cwd, input.contractId, input.contractAddressSource);
79
76
  return await withResolvedAddress(address, async () => {
80
77
  const contractId = address.binding.commissionId;
package/build/core/arc.js CHANGED
@@ -1,7 +1,7 @@
1
1
  import * as fs from "node:fs/promises";
2
2
  import * as path from "node:path";
3
3
  import { requireText, FlowError } from "../flow-error.js";
4
- import { getCurrentBranch, isGitRepo } from "../git/branches.js";
4
+ import { getCurrentBranch, requireGitRepository } from "../git/branches.js";
5
5
  import { DIRTY_FILE_CATEGORY, getDirtyFiles, renderDirtyFileStatusLine } from "../git/worktree.js";
6
6
  import { KEIYAKU_FILE, ACTOR_IDENTITY } from "../keiyaku.js";
7
7
  import { buildDirtyWorktreeArtifactHint } from "./hints.js";
@@ -9,9 +9,10 @@ import { normalizeInlineTitle } from "./markdown/titles.js";
9
9
  import { parseCommandMarkdown, requireMarkdownTextSection, requireMarkdownTitle, } from "./command-io.js";
10
10
  import { resolveContractAddress, withResolvedAddress } from "./addressing.js";
11
11
  import { activeLedgerRef, appendWithRetry, readLedger } from "./ledger.js";
12
- import { renderContractView } from "./render.js";
13
- import { findOpenArc, getMaxArc, sealOpenArc } from "./seal.js";
14
- import { deriveContractState, isTerminalState, resolveDefaultBranch } from "./status.js";
12
+ import { renderContractView } from "./contract-view.js";
13
+ import { findOpenArc, getMaxArc, sealCurrentArc } from "./seal.js";
14
+ import { deriveContractState, isTerminalState } from "./status/lifecycle.js";
15
+ import { resolveDefaultBranch } from "./status/drift.js";
15
16
  import { persistResponseHistory } from "./transcripts.js";
16
17
  const DIRTY_WORKTREE_LIST_LIMIT = 10;
17
18
  const KEIYAKU_ARTIFACT_DIR_PREFIX = ".keiyaku/";
@@ -111,7 +112,7 @@ export async function openArc(input) {
111
112
  throw new FlowError("INTERNAL_STATE", `contract ${input.contractId} not found or not active`);
112
113
  }
113
114
  assertArcOpenable(input.contractId, ledger.entries);
114
- const sealed = await sealOpenArc({
115
+ const sealed = await sealCurrentArc({
115
116
  cwd: input.cwd,
116
117
  renderCwd: input.cwd,
117
118
  contractId: input.contractId,
@@ -160,9 +161,7 @@ async function readBranchLabels(cwd) {
160
161
  return { currentBranch, baseBranch };
161
162
  }
162
163
  export async function arcKeiyaku(input) {
163
- if (!(await isGitRepo(input.cwd))) {
164
- throw new FlowError("NOT_GIT_REPO", `${input.cwd} is not a git repository`);
165
- }
164
+ await requireGitRepository(input.cwd, "arc");
166
165
  const address = await resolveContractAddress(input.cwd, input.contractId, input.contractAddressSource);
167
166
  return await withResolvedAddress(address, async () => {
168
167
  await assertCleanWorkingTree(input.cwd, []);