@astrosheep/keiyaku 2.8.4 → 2.9.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 (229) 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 +39 -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 +35 -0
  55. package/build/cli/commands/projection/kill/meta.js +11 -0
  56. package/build/cli/commands/projection/revive/handler.js +53 -0
  57. package/build/cli/commands/projection/revive/meta.js +9 -0
  58. package/build/cli/commands/projection/status/handler.js +23 -0
  59. package/build/cli/commands/projection/status/meta.js +9 -0
  60. package/build/cli/commands/projection/tell/handler.js +34 -0
  61. package/build/cli/commands/projection/tell/meta.js +9 -0
  62. package/build/cli/commands/projection/wait/handler.js +21 -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.js +159 -72
  106. package/build/cli/projection-address.js +5 -0
  107. package/build/cli/render/arc.js +1 -1
  108. package/build/cli/render/errors.js +4 -3
  109. package/build/cli/render/format.js +14 -0
  110. package/build/cli/render/kanshi.js +106 -0
  111. package/build/cli/render/petition.js +24 -1
  112. package/build/cli/render/projection-activity.js +5 -1
  113. package/build/cli/render/shared.js +2 -2
  114. package/build/cli/render/status.js +79 -13
  115. package/build/cli/render/task.js +113 -0
  116. package/build/cli/render/wait.js +23 -3
  117. package/build/cli/types.js +1 -1
  118. package/build/config/{akuma-loader_v2.js → akuma-loader.js} +2 -2
  119. package/build/config/env.js +9 -4
  120. package/build/config/settings/disease.js +26 -0
  121. package/build/config/settings/knobs.js +8 -0
  122. package/build/config/settings/loader.js +244 -0
  123. package/build/config/settings/schema.js +276 -0
  124. package/build/core/addressing.js +1 -1
  125. package/build/core/amend.js +3 -3
  126. package/build/core/arc.js +5 -4
  127. package/build/core/bind-workspace.js +123 -0
  128. package/build/core/bind.js +75 -155
  129. package/build/core/call/call.js +224 -0
  130. package/build/core/call/context.js +238 -0
  131. package/build/core/call/execution.js +217 -0
  132. package/build/core/call/prompt.js +42 -0
  133. package/build/core/call/reference.js +24 -0
  134. package/build/core/call-persist.js +1 -1
  135. package/build/core/claim-delivery.js +247 -0
  136. package/build/core/claim.js +39 -277
  137. package/build/core/context.js +2 -1
  138. package/build/core/{render.js → contract-view.js} +12 -140
  139. package/build/core/draft.js +107 -9
  140. package/build/core/entry.js +9 -1
  141. package/build/core/execution-coordinate.js +4 -20
  142. package/build/core/execution-pact.js +16 -31
  143. package/build/core/forfeit.js +35 -22
  144. package/build/core/hints.js +2 -2
  145. package/build/core/log.js +2 -2
  146. package/build/core/petition-claim-gates.js +8 -6
  147. package/build/core/petition-preview.js +97 -0
  148. package/build/core/petition-run.js +3 -2
  149. package/build/core/petition.js +39 -26
  150. package/build/core/projection/generation/database.js +306 -0
  151. package/build/core/projection/generation/identity.js +84 -0
  152. package/build/core/projection/generation/model.js +201 -0
  153. package/build/core/projection/generation/store.js +73 -0
  154. package/build/core/projection/generation/transitions.js +258 -0
  155. package/build/core/projection/heart.js +0 -307
  156. package/build/core/projection/tell/database.js +109 -0
  157. package/build/core/projection/tell/model.js +97 -0
  158. package/build/core/{projection-tells.js → projection/tell/store.js} +3 -158
  159. package/build/core/projection-activity.js +23 -2
  160. package/build/core/projection-core.js +2 -1
  161. package/build/core/projection-execution-observer.js +259 -0
  162. package/build/core/projection-generation-continuation.js +1 -1
  163. package/build/core/projection-generation-execution.js +90 -20
  164. package/build/core/projection-generation-launcher.js +1 -1
  165. package/build/core/projection-generation-runner.js +198 -4
  166. package/build/core/projection-identity.js +1 -1
  167. package/build/core/projection-kill.js +50 -17
  168. package/build/core/projection-life-observer.js +1 -1
  169. package/build/core/projection-life-protocol.js +10 -0
  170. package/build/core/projection-mint.js +1 -1
  171. package/build/core/projection-status.js +45 -32
  172. package/build/core/projection-wait.js +12 -2
  173. package/build/core/projection-wake.js +5 -2
  174. package/build/core/{queue_v2.js → queue.js} +2 -1
  175. package/build/core/registry.js +1 -1
  176. package/build/core/renew.js +7 -7
  177. package/build/core/response-artifact-catalog-sqlite.js +112 -0
  178. package/build/core/response-artifact-catalog.js +7 -0
  179. package/build/core/seal.js +44 -16
  180. package/build/core/status/board.js +286 -0
  181. package/build/core/status/drift.js +99 -0
  182. package/build/core/status/lifecycle.js +84 -0
  183. package/build/core/status/reconciliation.js +131 -0
  184. package/build/core/stored-agent-event.js +28 -4
  185. package/build/core/target-ref.js +10 -0
  186. package/build/core/task/board.js +175 -0
  187. package/build/core/task/commands.js +211 -0
  188. package/build/core/task/document.js +189 -0
  189. package/build/core/task/model.js +1 -0
  190. package/build/core/task/settlement-git.js +207 -0
  191. package/build/core/task/settlement-policy.js +82 -0
  192. package/build/core/task/source-board.js +84 -0
  193. package/build/core/task/source-model.js +1 -0
  194. package/build/core/task/validation.js +44 -0
  195. package/build/core/task-contract.js +224 -0
  196. package/build/core/task-doctor.js +14 -0
  197. package/build/core/task-git-actor.js +38 -0
  198. package/build/core/task-git-runtime.js +34 -0
  199. package/build/core/task-git-store.js +375 -0
  200. package/build/core/task-repository.js +1 -0
  201. package/build/core/task-store-repository.js +86 -0
  202. package/build/core/task-store.js +1 -0
  203. package/build/core/task-worktree.js +51 -0
  204. package/build/core/task.js +4 -0
  205. package/build/core/transcripts.js +138 -56
  206. package/build/core/validation.js +17 -0
  207. package/build/core/verdict.js +2 -2
  208. package/build/core/worktree-path.js +24 -6
  209. package/build/flow-error.js +3 -2
  210. package/build/generated/version.js +1 -1
  211. package/build/git/commits.js +3 -0
  212. package/build/git/diff/parsers.js +0 -94
  213. package/build/index.js +2 -0
  214. package/build/keiyaku-constants.js +2 -0
  215. package/build/keiyaku.js +4 -3
  216. package/package.json +3 -2
  217. package/skills/keiyaku/SKILL.md +1 -1
  218. package/skills/keiyaku-akuma/SKILL.md +11 -6
  219. package/build/agents/harness/pump.js +0 -158
  220. package/build/agents/providers/codex-app-server.js +0 -864
  221. package/build/agents/providers/opencode-sdk.js +0 -363
  222. package/build/agents/providers/pi.js +0 -557
  223. package/build/config/settings.js +0 -533
  224. package/build/core/call.js +0 -686
  225. package/build/core/projection/generation.js +0 -412
  226. package/build/core/projection/identity.js +0 -81
  227. package/build/core/projection/mint.js +0 -174
  228. package/build/core/projection-generation-store.js +0 -707
  229. package/build/core/status.js +0 -539
@@ -1,533 +0,0 @@
1
- import * as fs from "node:fs/promises";
2
- import * as path from "node:path";
3
- import { z } from "zod";
4
- import { isErrnoException } from "../errno.js";
5
- import { FlowError } from "../flow-error.js";
6
- import { CLAUDE_SETTING_SOURCES, SETTINGS_FILE, } from "../keiyaku.js";
7
- import { getKeiyakuHome } from "./env.js";
8
- const tomlValueSchema = z.lazy(() => z.union([
9
- z.string(),
10
- z.number(),
11
- z.boolean(),
12
- z.array(tomlValueSchema),
13
- z.record(z.string(), tomlValueSchema),
14
- ]));
15
- const structuredTomlValueSchema = z.record(z.string(), tomlValueSchema);
16
- const executableSchema = z.string().superRefine((value, ctx) => {
17
- if (value.length === 0 || /\s/.test(value) || value.includes("\"") || value.includes("'")) {
18
- ctx.addIssue({
19
- code: z.ZodIssueCode.custom,
20
- message: "must be a single program name token with no spaces or quotes.",
21
- });
22
- }
23
- });
24
- const nonblankOptionalString = z
25
- .string()
26
- .refine((value) => value.trim().length > 0, { message: "must not be blank." })
27
- .optional();
28
- const providerCommonFields = {
29
- description: nonblankOptionalString,
30
- };
31
- const CODEX_SDK_FORBIDDEN_THREAD_OPTIONS_KEYS = [
32
- "model",
33
- "modelReasoningEffort",
34
- "sandboxMode",
35
- "networkAccessEnabled",
36
- "webSearchMode",
37
- "approvalPolicy",
38
- "workingDirectory",
39
- "skipGitRepoCheck",
40
- ];
41
- const FORBIDDEN_CONFIG_KEYS = [
42
- "model",
43
- "model_reasoning_effort",
44
- ];
45
- function isConfigKeyForbidden(key) {
46
- return FORBIDDEN_CONFIG_KEYS.includes(key);
47
- }
48
- const claudeSettingSourceSchema = z.enum(CLAUDE_SETTING_SOURCES);
49
- const writableRootItemSchema = z.string().superRefine((value, ctx) => {
50
- if (value.length === 0) {
51
- ctx.addIssue({ code: z.ZodIssueCode.custom, message: "must not be empty." });
52
- return;
53
- }
54
- if (value.startsWith("~")) {
55
- ctx.addIssue({ code: z.ZodIssueCode.custom, message: "must not begin with tilde." });
56
- return;
57
- }
58
- if (value.includes("\0")) {
59
- ctx.addIssue({ code: z.ZodIssueCode.custom, message: "must not contain NUL byte." });
60
- return;
61
- }
62
- if (/\$[A-Za-z_]/.test(value) || /\$\{/.test(value)) {
63
- ctx.addIssue({ code: z.ZodIssueCode.custom, message: "must not contain environment variable references." });
64
- return;
65
- }
66
- });
67
- export const providerInstanceSchema = z.discriminatedUnion("kind", [
68
- z
69
- .object({
70
- kind: z.literal("codex-sdk"),
71
- ...providerCommonFields,
72
- executable: executableSchema.optional(),
73
- profile: z.string().optional(),
74
- config: structuredTomlValueSchema.optional(),
75
- threadOptions: structuredTomlValueSchema.optional(),
76
- })
77
- .strict()
78
- .superRefine((data, ctx) => {
79
- if (data.threadOptions) {
80
- for (const key of CODEX_SDK_FORBIDDEN_THREAD_OPTIONS_KEYS) {
81
- if (key in data.threadOptions) {
82
- ctx.addIssue({
83
- code: z.ZodIssueCode.custom,
84
- path: ["threadOptions", key],
85
- message: `"${key}" is owned by the Akuma or Keiyaku execution, not this provider instance`,
86
- });
87
- }
88
- }
89
- }
90
- if (data.config) {
91
- for (const key of Object.keys(data.config)) {
92
- if (isConfigKeyForbidden(key)) {
93
- ctx.addIssue({
94
- code: z.ZodIssueCode.custom,
95
- path: ["config", key],
96
- message: `"${key}" is owned by the Akuma or Keiyaku execution, not this provider instance`,
97
- });
98
- }
99
- }
100
- }
101
- }),
102
- z
103
- .object({
104
- kind: z.literal("codex-app-server"),
105
- ...providerCommonFields,
106
- executable: executableSchema.optional(),
107
- profile: z.string().optional(),
108
- config: structuredTomlValueSchema.optional(),
109
- writableRoots: z.array(writableRootItemSchema).optional(),
110
- })
111
- .strict()
112
- .superRefine((data, ctx) => {
113
- if (data.config) {
114
- for (const key of Object.keys(data.config)) {
115
- if (isConfigKeyForbidden(key)) {
116
- ctx.addIssue({
117
- code: z.ZodIssueCode.custom,
118
- path: ["config", key],
119
- message: `"${key}" is owned by the Akuma or Keiyaku execution, not this provider instance`,
120
- });
121
- }
122
- }
123
- }
124
- }),
125
- z
126
- .object({
127
- kind: z.literal("claude-agent-sdk"),
128
- ...providerCommonFields,
129
- executable: executableSchema.optional(),
130
- settingSources: z.array(claudeSettingSourceSchema).optional(),
131
- })
132
- .strict(),
133
- z
134
- .object({
135
- kind: z.literal("opencode-sdk"),
136
- ...providerCommonFields,
137
- executable: executableSchema.optional(),
138
- })
139
- .strict(),
140
- z
141
- .object({
142
- kind: z.literal("pi"),
143
- ...providerCommonFields,
144
- })
145
- .strict(),
146
- ]);
147
- const PROVIDER_INSTANCE_NAME_PATTERN = /^[a-z0-9][a-z0-9-]*$/;
148
- export const providerInstanceNameSchema = z
149
- .string()
150
- .max(64, "must contain at most 64 characters.")
151
- .regex(PROVIDER_INSTANCE_NAME_PATTERN, "must match ^[a-z0-9][a-z0-9-]*$.");
152
- export const BUILTIN_PROVIDER_INSTANCE_CANDIDATES = {
153
- "codex-sdk": { name: "codex-sdk", source: "builtin", instance: { kind: "codex-sdk", executable: "codex" } },
154
- "codex-app-server": {
155
- name: "codex-app-server",
156
- source: "builtin",
157
- instance: { kind: "codex-app-server", executable: "codex" },
158
- },
159
- "claude-agent-sdk": {
160
- name: "claude-agent-sdk",
161
- source: "builtin",
162
- instance: { kind: "claude-agent-sdk", executable: "claude", settingSources: ["project"] },
163
- },
164
- "opencode-sdk": {
165
- name: "opencode-sdk",
166
- source: "builtin",
167
- instance: { kind: "opencode-sdk", executable: "opencode" },
168
- },
169
- pi: { name: "pi", source: "builtin", instance: { kind: "pi" } },
170
- };
171
- const AGENT_NAME_PATTERN = /^[a-zA-Z0-9]+(?:[.-][a-zA-Z0-9]+)*$/;
172
- export const agentNameSchema = z
173
- .string()
174
- .min(1, "must not be empty.")
175
- .regex(AGENT_NAME_PATTERN, "must contain only alphanumeric characters, dots, and hyphens, and must not start or end with a separator.");
176
- function optionalTrimmedString() {
177
- return z.string().transform((value) => {
178
- const trimmed = value.trim();
179
- return trimmed.length > 0 ? trimmed : undefined;
180
- }).optional();
181
- }
182
- function requiredTrimmedString(message) {
183
- return z.string().transform((value, ctx) => {
184
- const trimmed = value.trim();
185
- if (trimmed.length === 0) {
186
- ctx.addIssue({
187
- code: z.ZodIssueCode.custom,
188
- message,
189
- });
190
- return z.NEVER;
191
- }
192
- return trimmed;
193
- });
194
- }
195
- const gateCommandSchema = z
196
- .object({
197
- label: requiredTrimmedString("must not be empty."),
198
- command: requiredTrimmedString("must not be empty.").superRefine((value, ctx) => {
199
- if (/\s/.test(value) || value.includes("\"") || value.includes("'")) {
200
- ctx.addIssue({
201
- code: z.ZodIssueCode.custom,
202
- message: "must be a single program name token with no spaces or quotes.",
203
- });
204
- }
205
- }),
206
- args: z.array(z.string()).optional(),
207
- })
208
- .strict()
209
- .transform((value) => ({
210
- ...value,
211
- args: value.args ?? [],
212
- }));
213
- const gateSchema = z
214
- .object({
215
- commands: z.array(gateCommandSchema).optional(),
216
- })
217
- .strict();
218
- const KNOWN_SETTINGS_KNOBS = ["default", "reviewer", "gate", "defaultBranch", "worktreeBootstrap"];
219
- const KNOWN_SETTINGS_ROOT_KEYS = [...KNOWN_SETTINGS_KNOBS, "providers"];
220
- const WORKTREE_BOOTSTRAP_DEFAULT_TIMEOUT_MS = 300_000;
221
- const WORKTREE_BOOTSTRAP_MIN_TIMEOUT_MS = 1;
222
- const WORKTREE_BOOTSTRAP_MAX_TIMEOUT_MS = 3_600_000;
223
- export const worktreeBootstrapSchema = z
224
- .object({
225
- command: z
226
- .array(z.string())
227
- .min(1, "command must be a nonempty argv array.")
228
- .superRefine((items, ctx) => {
229
- items.forEach((item, index) => {
230
- if (item.trim().length === 0) {
231
- ctx.addIssue({
232
- code: z.ZodIssueCode.custom,
233
- path: [index],
234
- message: "command items must be nonempty after trimming.",
235
- });
236
- }
237
- });
238
- }),
239
- timeoutMs: z
240
- .number()
241
- .int("timeoutMs must be an integer.")
242
- .min(WORKTREE_BOOTSTRAP_MIN_TIMEOUT_MS, `timeoutMs must be >= ${WORKTREE_BOOTSTRAP_MIN_TIMEOUT_MS}.`)
243
- .max(WORKTREE_BOOTSTRAP_MAX_TIMEOUT_MS, `timeoutMs must be <= ${WORKTREE_BOOTSTRAP_MAX_TIMEOUT_MS}.`)
244
- .optional(),
245
- })
246
- .strict()
247
- .transform((value) => ({
248
- command: value.command,
249
- timeoutMs: value.timeoutMs ?? WORKTREE_BOOTSTRAP_DEFAULT_TIMEOUT_MS,
250
- }));
251
- const settingsSchema = z
252
- .object({
253
- default: agentNameSchema.optional(),
254
- reviewer: agentNameSchema.optional(),
255
- gate: gateSchema.optional(),
256
- defaultBranch: optionalTrimmedString(),
257
- worktreeBootstrap: worktreeBootstrapSchema.optional(),
258
- })
259
- .strict();
260
- const knobSchemas = {
261
- default: agentNameSchema,
262
- reviewer: agentNameSchema,
263
- gate: gateSchema,
264
- defaultBranch: optionalTrimmedString(),
265
- worktreeBootstrap: worktreeBootstrapSchema,
266
- };
267
- function isPlainObject(value) {
268
- return typeof value === "object" && value !== null && !Array.isArray(value);
269
- }
270
- function deepMerge(base, override) {
271
- if (!isPlainObject(base) || !isPlainObject(override)) {
272
- return override;
273
- }
274
- const merged = { ...base };
275
- for (const [key, value] of Object.entries(override)) {
276
- merged[key] = key in merged ? deepMerge(merged[key], value) : value;
277
- }
278
- return merged;
279
- }
280
- function formatZodIssues(error) {
281
- return error.issues
282
- .map((issue) => `${issue.path.join(".") || "(root)"}: ${issue.message}`)
283
- .join("; ");
284
- }
285
- async function readSettingsSource(filePath) {
286
- try {
287
- const text = await fs.readFile(filePath, "utf-8");
288
- try {
289
- return { found: true, raw: JSON.parse(text) };
290
- }
291
- catch (error) {
292
- const reason = error instanceof SyntaxError
293
- ? `invalid JSON: ${error.message}`
294
- : error instanceof Error
295
- ? error.message
296
- : String(error);
297
- return { found: true, raw: undefined, parseError: reason };
298
- }
299
- }
300
- catch (error) {
301
- if (isErrnoException(error) && error.code === "ENOENT") {
302
- return { found: false };
303
- }
304
- const reason = error instanceof Error ? error.message : String(error);
305
- return { found: true, raw: undefined, parseError: reason };
306
- }
307
- }
308
- function builtinProviderInstanceCatalog() {
309
- return { selected: { ...BUILTIN_PROVIDER_INSTANCE_CANDIDATES }, shadows: [] };
310
- }
311
- function formatProviderInstanceIssues(name, error) {
312
- return error.issues
313
- .map((issue) => {
314
- const suffix = issue.path.length === 0 ? "" : `.${issue.path.join(".")}`;
315
- return `providers.${name}${suffix}: ${issue.message}`;
316
- })
317
- .join("; ");
318
- }
319
- function collectProviderCandidates(source, rawProviders) {
320
- if (!isPlainObject(rawProviders)) {
321
- return {
322
- candidates: [],
323
- disease: { source: source === "project" ? "local" : "global", knob: "providers", reason: "providers: must be a JSON object" },
324
- };
325
- }
326
- const candidates = [];
327
- for (const [name, rawInstance] of Object.entries(rawProviders)) {
328
- const nameResult = providerInstanceNameSchema.safeParse(name);
329
- if (!nameResult.success) {
330
- candidates.push({
331
- name,
332
- source,
333
- reason: formatProviderInstanceIssues(name, nameResult.error),
334
- });
335
- continue;
336
- }
337
- const instanceResult = providerInstanceSchema.safeParse(rawInstance);
338
- if (!instanceResult.success) {
339
- candidates.push({
340
- name,
341
- source,
342
- reason: formatProviderInstanceIssues(name, instanceResult.error),
343
- });
344
- continue;
345
- }
346
- const instance = instanceResult.data;
347
- if (instance.kind === "codex-app-server" && instance.writableRoots !== undefined && source === "global") {
348
- let relativeIndex;
349
- for (let i = 0; i < instance.writableRoots.length; i++) {
350
- if (!path.posix.isAbsolute(instance.writableRoots[i])) {
351
- relativeIndex = i;
352
- break;
353
- }
354
- }
355
- if (relativeIndex !== undefined) {
356
- candidates.push({
357
- name,
358
- source,
359
- reason: `providers.${name}.writableRoots.${relativeIndex}: writableRoots item must be an absolute path for global provider instances`,
360
- });
361
- continue;
362
- }
363
- }
364
- candidates.push({ name, source, instance: instanceResult.data });
365
- }
366
- return { candidates };
367
- }
368
- function mergeProviderCandidates(catalog, candidates) {
369
- const selected = { ...catalog.selected };
370
- const shadows = [...catalog.shadows];
371
- for (const candidate of candidates) {
372
- const prior = selected[candidate.name];
373
- if (prior !== undefined) {
374
- shadows.push({ name: candidate.name, source: prior.source, shadowedBy: candidate.source });
375
- }
376
- selected[candidate.name] = candidate;
377
- }
378
- return { selected, shadows };
379
- }
380
- function collectSourceDiseases(source, providerSource, read) {
381
- if (!read.found) {
382
- return { partial: {}, diseases: [], providerCandidates: [] };
383
- }
384
- if (read.parseError !== undefined) {
385
- return {
386
- partial: {},
387
- diseases: [{ source, reason: `settings root is unreadable (${read.parseError})` }],
388
- providerCandidates: [],
389
- };
390
- }
391
- if (!isPlainObject(read.raw)) {
392
- return {
393
- partial: {},
394
- diseases: [{ source, reason: "settings root must be a JSON object" }],
395
- providerCandidates: [],
396
- };
397
- }
398
- const diseases = [];
399
- const partial = {};
400
- let providerCandidates = [];
401
- const raw = read.raw;
402
- for (const key of Object.keys(raw)) {
403
- if (!KNOWN_SETTINGS_ROOT_KEYS.includes(key)) {
404
- diseases.push({
405
- source,
406
- knob: key,
407
- reason: key === "agents"
408
- ? 'settings key "agents" has been removed; define profiles in .keiyaku/akuma/<name>.md'
409
- : `unknown settings key "${key}"`,
410
- });
411
- }
412
- }
413
- if ("providers" in raw) {
414
- const collectedProviders = collectProviderCandidates(providerSource, raw.providers);
415
- providerCandidates = collectedProviders.candidates;
416
- if (collectedProviders.disease !== undefined)
417
- diseases.push(collectedProviders.disease);
418
- }
419
- for (const knob of KNOWN_SETTINGS_KNOBS) {
420
- if (!(knob in raw))
421
- continue;
422
- // worktreeBootstrap is project-layer only. Global/user presence is a named
423
- // disease and never contributes execution authority.
424
- if (knob === "worktreeBootstrap" && source === "global") {
425
- diseases.push({
426
- source,
427
- knob,
428
- reason: "worktreeBootstrap is project-only and never supplies a value from global settings",
429
- });
430
- continue;
431
- }
432
- const schema = knobSchemas[knob];
433
- const result = schema.safeParse(raw[knob]);
434
- if (!result.success) {
435
- diseases.push({
436
- source,
437
- knob,
438
- reason: formatZodIssues(result.error),
439
- });
440
- continue;
441
- }
442
- if (result.data === undefined)
443
- continue;
444
- partial[knob] = result.data;
445
- }
446
- return { partial, diseases, providerCandidates };
447
- }
448
- function mergeSettingsKnobs(base, override) {
449
- // worktreeBootstrap is whole-value project authority; never field-merge it.
450
- const { worktreeBootstrap: baseBootstrap, ...baseRest } = base;
451
- const { worktreeBootstrap: overrideBootstrap, ...overrideRest } = override;
452
- const mergedRaw = deepMerge(baseRest, overrideRest);
453
- if (!isPlainObject(mergedRaw)) {
454
- return overrideBootstrap !== undefined
455
- ? { ...override, worktreeBootstrap: overrideBootstrap }
456
- : { ...override };
457
- }
458
- const merged = {};
459
- for (const knob of KNOWN_SETTINGS_KNOBS) {
460
- if (knob === "worktreeBootstrap")
461
- continue;
462
- if (knob in mergedRaw) {
463
- merged[knob] = mergedRaw[knob];
464
- }
465
- }
466
- const bootstrap = overrideBootstrap ?? baseBootstrap;
467
- if (bootstrap !== undefined)
468
- merged.worktreeBootstrap = bootstrap;
469
- return merged;
470
- }
471
- export function findSettingsDiseasesForKnob(diseases, knob) {
472
- return diseases.filter((disease) => disease.knob === knob);
473
- }
474
- export function formatSettingsDiseases(diseases) {
475
- return diseases.map((disease) => {
476
- const where = disease.knob ? `${disease.source}:${disease.knob}` : `${disease.source}:(root)`;
477
- return `${where}: ${disease.reason}`;
478
- });
479
- }
480
- /**
481
- * Reject when a named knob is diseased and was not admitted into knobs.
482
- * A healthy value from another source still wins; only failed-and-unset knobs reject.
483
- */
484
- export function assertSettingsKnobUsable(loaded, knob) {
485
- const knobs = loaded.knobs;
486
- if (knobs !== null && knobs[knob] !== undefined)
487
- return;
488
- const hits = findSettingsDiseasesForKnob(loaded.diseases, knob);
489
- if (hits.length === 0)
490
- return;
491
- throw new FlowError("INVALID_SETTINGS", `Invalid settings in ${SETTINGS_FILE} (diseased knob '${knob}').`, {
492
- hints: formatSettingsDiseases(hits),
493
- });
494
- }
495
- export async function loadKeiyakuSettings(cwd) {
496
- const localSettingsPath = path.join(cwd, SETTINGS_FILE);
497
- const globalSettingsPath = path.join(getKeiyakuHome(), path.basename(SETTINGS_FILE));
498
- let globalRead;
499
- let localRead;
500
- try {
501
- [globalRead, localRead] = await Promise.all([
502
- readSettingsSource(globalSettingsPath),
503
- readSettingsSource(localSettingsPath),
504
- ]);
505
- }
506
- catch (error) {
507
- // loadKeiyakuSettings never throws; unexpected I/O becomes a local root disease.
508
- const reason = error instanceof Error ? error.message : String(error);
509
- return {
510
- knobs: null,
511
- diseases: [{ source: "local", reason: `settings root is unreadable (${reason})` }],
512
- providerInstances: builtinProviderInstanceCatalog(),
513
- };
514
- }
515
- const globalCollected = collectSourceDiseases("global", "global", globalRead);
516
- const localCollected = collectSourceDiseases("local", "project", localRead);
517
- const diseases = [...globalCollected.diseases, ...localCollected.diseases];
518
- let providerInstances = builtinProviderInstanceCatalog();
519
- providerInstances = mergeProviderCandidates(providerInstances, globalCollected.providerCandidates);
520
- providerInstances = mergeProviderCandidates(providerInstances, localCollected.providerCandidates);
521
- let knobs = null;
522
- if (globalRead.found || localRead.found) {
523
- knobs = mergeSettingsKnobs(globalCollected.partial, localCollected.partial);
524
- }
525
- // Empty object still means "settings files were present"; callers treat null as absent files.
526
- return { knobs, diseases, providerInstances };
527
- }
528
- export function resolveDefaultAgentName(settings) {
529
- return settings?.default ?? "akuma-codex";
530
- }
531
- export function resolveReviewerAgentName(settings) {
532
- return settings?.reviewer ?? resolveDefaultAgentName(settings);
533
- }