@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,15 +1,50 @@
1
- import { formatSettingsDiseases } from "../../config/settings.js";
2
- import { formatAgeMs, projectionGlyph, renderKanshiBoard, shortCommitHash } from "../../core/render.js";
1
+ import { formatSettingsDiseases } from "../../config/settings/disease.js";
2
+ import { shortCommitHash } from "../../git/commits.js";
3
+ import { formatAgeMs } from "./format.js";
4
+ import { renderKanshiBoard } from "./kanshi.js";
3
5
  import { renderProjectionActivitySummary, } from "./projection-activity.js";
4
6
  export const STATUS_DISEASES_SECTION_TITLE = "Diseases";
5
7
  export const STATUS_PROJECTION_SECTION_TITLE = "akuma";
6
8
  export const STATUS_CLAIM_RECEIPTS_SECTION_TITLE = "claim receipts";
7
- export function renderSettingsDiseasesSection(diseases) {
8
- if (diseases.length === 0)
9
+ function projectionGlyph(state) {
10
+ switch (state) {
11
+ case "out":
12
+ return "●";
13
+ case "lost":
14
+ return "○";
15
+ case "done":
16
+ return "◎";
17
+ case "dismissed":
18
+ return "×";
19
+ case "dead":
20
+ return "✕";
21
+ case "minting":
22
+ return "…";
23
+ case "startup-timeout":
24
+ return "!";
25
+ case "unknown":
26
+ return "?";
27
+ }
28
+ }
29
+ export function renderSettingsDiseasesSection(diseases, taskDiseases = []) {
30
+ if (diseases.length === 0 && taskDiseases.length === 0)
9
31
  return "";
10
32
  const lines = formatSettingsDiseases(diseases).map((line) => `- ${line}`);
33
+ lines.push(...taskDiseases.map((line) => `- ${line}`));
11
34
  return `${STATUS_DISEASES_SECTION_TITLE}\n${lines.join("\n")}\n`;
12
35
  }
36
+ function renderTaskStatusSection(tasks) {
37
+ if (!tasks || tasks.inProgress + tasks.ready + tasks.blocked === 0)
38
+ return "";
39
+ const segments = [
40
+ tasks.inProgress > 0 ? `${tasks.inProgress} in_progress` : undefined,
41
+ tasks.ready > 0 ? `${tasks.ready} ready` : undefined,
42
+ tasks.blocked > 0 ? `${tasks.blocked} blocked` : undefined,
43
+ ].filter((segment) => segment !== undefined);
44
+ const lines = [`tasks · ${segments.join(" / ")}`];
45
+ lines.push(...tasks.topTasks.map((task) => ` ${task.id.slice(0, 9)} · P${task.pri} · ${task.state === "in_progress" ? "in_progress" : "ready"} · ${task.title}`));
46
+ return `${lines.join("\n")}\n`;
47
+ }
13
48
  function renderProjectionDiagnostics(row) {
14
49
  if (row.diagnostics.length === 0)
15
50
  return "";
@@ -18,7 +53,7 @@ function renderProjectionDiagnostics(row) {
18
53
  function renderProjectionOmission(board) {
19
54
  if (!board.omitted)
20
55
  return undefined;
21
- const counts = ["failed", "dead", "done", "dismissed"]
56
+ const counts = ["failed", "dead", "done", "killed", "dismissed"]
22
57
  .flatMap((state) => {
23
58
  const count = board.omitted?.byState[state] ?? 0;
24
59
  return count > 0 ? [`${count} ${state}`] : [];
@@ -28,6 +63,8 @@ function renderProjectionOmission(board) {
28
63
  function renderProjectionAge(row) {
29
64
  if (row.state === "done")
30
65
  return "done";
66
+ if (row.state === "killed")
67
+ return "killed";
31
68
  if (row.state === "dismissed")
32
69
  return "dismissed";
33
70
  if (row.state === "failed")
@@ -50,6 +87,7 @@ function projectionActivityState(row) {
50
87
  case "failed":
51
88
  case "dead": return "dead";
52
89
  case "lost": return "lost";
90
+ case "killed":
53
91
  case "dismissed": return "dismissed";
54
92
  case "minting":
55
93
  case "out": return "running";
@@ -68,11 +106,13 @@ function renderProjectionActivityLine(row) {
68
106
  });
69
107
  }
70
108
  function renderProjectionRow(row) {
71
- const glyph = projectionGlyph(row.state === "failed"
72
- ? "lost"
73
- : row.state === "startup-timeout"
74
- ? "minting"
75
- : row.state);
109
+ const glyph = row.state === "killed"
110
+ ? "×"
111
+ : projectionGlyph(row.state === "failed"
112
+ ? "lost"
113
+ : row.state === "startup-timeout"
114
+ ? "minting"
115
+ : row.state);
76
116
  const identity = row.alias ? `${row.id} (${row.alias})` : row.id;
77
117
  let heading;
78
118
  if (row.state === "startup-timeout" || row.state === "unknown") {
@@ -90,6 +130,25 @@ function renderProjectionRow(row) {
90
130
  const activity = renderProjectionActivityLine(row);
91
131
  return activity ? [heading, activity] : [heading];
92
132
  }
133
+ function isTerminalProjectionStatus(row) {
134
+ return row.state === "done"
135
+ || row.state === "killed"
136
+ || row.state === "dismissed"
137
+ || row.state === "failed"
138
+ || row.state === "dead";
139
+ }
140
+ export function renderProjectionFilteredStatus(board, projectionId) {
141
+ const head = board.defaultHead ? shortCommitHash(board.defaultHead) : "(no HEAD)";
142
+ const heading = `現世 ${head} · projection ${projectionId}`;
143
+ const row = board.projections?.rows.find((candidate) => candidate.id === projectionId);
144
+ if (!row)
145
+ return `${heading}\nno projections on projection ${projectionId}\n`;
146
+ const lines = [heading, "", ...renderProjectionRow(row), ""];
147
+ if (!isTerminalProjectionStatus(row))
148
+ lines.push(`» keiyaku tell ${projectionId} "..."`);
149
+ lines.push(`» keiyaku wait ${projectionId}`);
150
+ return `${lines.join("\n")}\n`;
151
+ }
93
152
  export function renderProjectionStatusSection(board) {
94
153
  if (!board || board.rows.length === 0)
95
154
  return "";
@@ -116,17 +175,24 @@ export function renderStatusBoard(board) {
116
175
  const boardText = renderKanshiBoard(board);
117
176
  const projectionText = renderProjectionStatusSection(board.projections);
118
177
  const receiptsText = renderClaimReconciliationSection(board.claimReconciliation);
119
- const diseasesText = renderSettingsDiseasesSection(board.diseases);
120
- if (!projectionText && !receiptsText && board.diseases.length === 0)
178
+ const taskText = renderTaskStatusSection(board.tasks);
179
+ const taskDiseaseText = [
180
+ ...(board.tasks?.diseases ?? []).map((disease) => disease.message),
181
+ ...(board.tasks?.diagnostic ? [board.tasks.diagnostic] : []),
182
+ ];
183
+ const diseasesText = renderSettingsDiseasesSection(board.diseases, taskDiseaseText);
184
+ if (!projectionText && !receiptsText && !taskText && diseasesText === "")
121
185
  return boardText;
122
186
  const sections = [boardText.trimEnd()];
123
- if (board.targetFilter !== null) {
187
+ if (board.targetFilter !== null && (projectionText || receiptsText || diseasesText)) {
124
188
  sections.push("repository-wide — not filtered by target");
125
189
  }
126
190
  if (projectionText)
127
191
  sections.push(projectionText.trimEnd());
128
192
  if (receiptsText)
129
193
  sections.push(receiptsText.trimEnd());
194
+ if (taskText)
195
+ sections.push(taskText.trimEnd());
130
196
  if (diseasesText)
131
197
  sections.push(diseasesText.trimEnd());
132
198
  return `${sections.join("\n\n")}\n`;
@@ -0,0 +1,113 @@
1
+ export function shortTaskId(id) {
2
+ return id.slice(0, 9);
3
+ }
4
+ export function shortContractId(contractId) {
5
+ const separator = contractId.indexOf(".");
6
+ return separator > 0 ? contractId.slice(0, separator) : contractId;
7
+ }
8
+ export function deriveTaskDisplay(task) {
9
+ if (task.state === "done")
10
+ return "done";
11
+ if (task.state === "drop")
12
+ return "dropped";
13
+ if (task.contractId !== undefined)
14
+ return `bound(${shortContractId(task.contractId)})`;
15
+ if (task.state === "in_progress")
16
+ return "in_progress";
17
+ return task.blocked ? "blocked" : "ready";
18
+ }
19
+ function renderTaskRowWithId(task, id) {
20
+ return `${id} · P${task.pri} · ${deriveTaskDisplay(task)} · ${task.title}`;
21
+ }
22
+ export function renderTaskListRow(task) {
23
+ return renderTaskRowWithId(task, shortTaskId(task.id));
24
+ }
25
+ export function renderTaskMutationRow(task) {
26
+ return renderTaskRowWithId(task, task.id);
27
+ }
28
+ export function renderTaskRow(task, completeId = false) {
29
+ return completeId ? renderTaskMutationRow(task) : renderTaskListRow(task);
30
+ }
31
+ function displayOrder(display) {
32
+ if (display === "in_progress")
33
+ return 0;
34
+ if (display === "ready")
35
+ return 1;
36
+ if (display === "blocked")
37
+ return 2;
38
+ if (display.startsWith("bound("))
39
+ return 3;
40
+ return display === "done" ? 4 : 5;
41
+ }
42
+ function compareText(left, right) {
43
+ return left < right ? -1 : left > right ? 1 : 0;
44
+ }
45
+ function compareTasks(left, right) {
46
+ const displayDifference = displayOrder(deriveTaskDisplay(left)) - displayOrder(deriveTaskDisplay(right));
47
+ if (displayDifference !== 0)
48
+ return displayDifference;
49
+ if (left.pri !== right.pri)
50
+ return left.pri - right.pri;
51
+ const createdDifference = compareText(left.createdAt, right.createdAt);
52
+ return createdDifference || compareText(left.id, right.id);
53
+ }
54
+ function selected(task, selection) {
55
+ const display = deriveTaskDisplay(task);
56
+ const terminal = display === "done" || display === "dropped";
57
+ if (selection === "done")
58
+ return terminal;
59
+ if (selection === "default")
60
+ return !terminal;
61
+ return true;
62
+ }
63
+ export function groupAndSortTasks(tasks, selection = "default") {
64
+ return tasks.filter((task) => selected(task, selection)).sort(compareTasks);
65
+ }
66
+ export function renderTaskList(tasks, options = {}) {
67
+ return groupAndSortTasks(tasks, options.selection).map(renderTaskListRow).join("\n");
68
+ }
69
+ function renderIds(ids) {
70
+ return ids.length === 0 ? "(none)" : ids.map(shortTaskId).join(", ");
71
+ }
72
+ export function renderTaskShow(task) {
73
+ const released = new Set(task.releasedNeeds);
74
+ const needs = task.needs.length === 0
75
+ ? "(none)"
76
+ : task.needs
77
+ .map((id) => `${shortTaskId(id)}${released.has(id) ? " (dropped)" : ""}`)
78
+ .join(", ");
79
+ return [
80
+ `${shortTaskId(task.id)} · ${task.title}`,
81
+ `state: ${deriveTaskDisplay(task)}`,
82
+ `priority: ${task.pri}`,
83
+ `needs: ${needs}`,
84
+ `blocks: ${renderIds(task.blocks)}`,
85
+ `parent: ${task.parent === null ? "(none)" : shortTaskId(task.parent)}`,
86
+ `from: ${renderIds(task.from)}`,
87
+ `created: ${task.createdAt} by ${task.creator}`,
88
+ "",
89
+ task.body,
90
+ ].join("\n");
91
+ }
92
+ export function taskJson(task) {
93
+ return {
94
+ id: task.id,
95
+ title: task.title,
96
+ state: task.state,
97
+ display: deriveTaskDisplay(task),
98
+ priority: task.pri,
99
+ needs: [...task.needs],
100
+ blocks: [...task.blocks],
101
+ parent: task.parent,
102
+ from: [...task.from],
103
+ contractId: task.contractId ?? null,
104
+ createdAt: task.createdAt,
105
+ creator: task.creator,
106
+ };
107
+ }
108
+ export function renderTaskShowJson(task) {
109
+ return `${JSON.stringify({ ...taskJson(task), body: task.body })}\n`;
110
+ }
111
+ export function renderTaskListJson(tasks, selection = "default") {
112
+ return `${JSON.stringify(groupAndSortTasks(tasks, selection).map(taskJson))}\n`;
113
+ }
@@ -10,6 +10,7 @@ function phaseState(phase) {
10
10
  case "done": return "returned";
11
11
  case "failed": return "dead";
12
12
  case "lost": return "lost";
13
+ case "killed":
13
14
  case "dismissed": return "dismissed";
14
15
  case "startup-timeout": return "stalled";
15
16
  case "unknown": return "unknown";
@@ -35,6 +36,8 @@ function waitStateText(snapshot) {
35
36
  return `returned · lived ${formatTimelineDuration(snapshot.durationMs)}`;
36
37
  case "failed":
37
38
  return `dead · lived ${formatTimelineDuration(snapshot.durationMs)}`;
39
+ case "killed":
40
+ return "killed";
38
41
  case "dismissed":
39
42
  return "dismissed";
40
43
  case "lost":
@@ -68,7 +71,7 @@ function terminalDiagnostic(result) {
68
71
  }
69
72
  function ledgerHint(projectionId, snapshot) {
70
73
  const file = snapshot.eventLedgerFile ?? "events.jsonl";
71
- return `⟶ ledger: .keiyaku/projection/${projectionId}/${file}`;
74
+ return `· ledger: .keiyaku/projection/${projectionId}/${file}`;
72
75
  }
73
76
  function recordReviveLines(artifactId) {
74
77
  return [
@@ -98,6 +101,17 @@ function nextActionLines(projectionId, result, state) {
98
101
  : ["The akuma dropped dead mid-turn.", "no record survived — there is nothing to revive."];
99
102
  }
100
103
  if (result.state === "terminal" && result.verdict.state === "failed") {
104
+ if (snapshot.failureCode === "SUBAGENT_PROVIDER_UNAVAILABLE") {
105
+ return snapshot.sourceArtifactId
106
+ ? [
107
+ "× provider unavailable — no turn accepted",
108
+ `» nothing was consumed · retry from the same artifact: keiyaku revive ${snapshot.sourceArtifactId}`,
109
+ ]
110
+ : [
111
+ "× provider unavailable — no turn accepted",
112
+ "» nothing was consumed · safe to call again",
113
+ ];
114
+ }
101
115
  return [
102
116
  "The akuma failed and stopped.",
103
117
  ...(snapshot.artifactId ? [`record: ${snapshot.artifactId}`] : []),
@@ -117,7 +131,7 @@ function nextActionLines(projectionId, result, state) {
117
131
  ];
118
132
  }
119
133
  if (state === "stalled" || state === "unknown") {
120
- return [ledgerHint(projectionId, snapshot), "⟶ keiyaku status"];
134
+ return [ledgerHint(projectionId, snapshot), "» keiyaku status"];
121
135
  }
122
136
  return [];
123
137
  }
@@ -186,6 +200,12 @@ export function buildWaitResponse(projectionId, result, options = {}) {
186
200
  }
187
201
  const snapshot = result.snapshot;
188
202
  const maxColumns = options.maxColumns ?? resolveLineColumns();
203
+ if (result.state === "terminal" && result.verdict.state === "killed") {
204
+ return {
205
+ ...textResponse(`× killed ${projectionId}`),
206
+ exitCode: 1,
207
+ };
208
+ }
189
209
  if (snapshot.phase === "startup-timeout") {
190
210
  return startupTimeoutResponse(projectionId, result.pact.akuma);
191
211
  }
@@ -216,7 +236,7 @@ export function buildWaitResponse(projectionId, result, options = {}) {
216
236
  lines.push(" not a word out of it yet — pulse is steady");
217
237
  }
218
238
  const nextActions = nextActionLines(options.alias ?? projectionId, result, state);
219
- if (rendered.hasMutationFiles && nextActions.length > 0)
239
+ if (nextActions.length > 0)
220
240
  lines.push("");
221
241
  lines.push(...nextActions);
222
242
  const response = textResponse(lines.join("\n"));
@@ -1 +1 @@
1
- export {};
1
+ export const TASK_SUBCOMMANDS = ["add", "view", "ls", "start", "stop", "done", "drop", "update", "doctor"];
@@ -5,7 +5,7 @@ import { parseToAST } from "../core/markdown/parser.js";
5
5
  import { isGitRepo } from "../git/branches.js";
6
6
  import { BUILTIN_AGENT_PROFILES } from "../keiyaku.js";
7
7
  import { getKeiyakuHome } from "./env.js";
8
- import { agentNameSchema, providerInstanceNameSchema } from "./settings.js";
8
+ import { agentNameSchema, providerInstanceNameSchema } from "./settings/schema.js";
9
9
  const nonblankString = z.string().refine((value) => value.trim().length > 0, { message: "must not be blank" });
10
10
  const effortOptionsSchema = z.array(nonblankString).superRefine((values, ctx) => {
11
11
  if (new Set(values).size !== values.length) {
@@ -187,7 +187,7 @@ function foldLayer(selected, shadows, allCandidates, source, layer) {
187
187
  selected[candidate.name] = candidate;
188
188
  }
189
189
  }
190
- export async function loadAkumaCatalogV2(cwd) {
190
+ export async function loadAkumaCatalog(cwd) {
191
191
  const candidates = [];
192
192
  const selected = {};
193
193
  const shadows = [];
@@ -4,12 +4,14 @@ import * as path from "node:path";
4
4
  import { z } from "zod";
5
5
  import { isErrnoException } from "../errno.js";
6
6
  import { ENV_KEYS, getEnvDefaultValue } from "./env-keys.js";
7
- function readEnvHome() {
8
- const value = process.env.KEIYAKU_HOME?.trim();
7
+ import { configureResponseArtifactCatalog } from "../core/response-artifact-catalog.js";
8
+ import { createSqliteResponseArtifactCatalog } from "../core/response-artifact-catalog-sqlite.js";
9
+ function readEnvHome(env = process.env) {
10
+ const value = env.KEIYAKU_HOME?.trim();
9
11
  return value && value.length > 0 ? value : undefined;
10
12
  }
11
- export function getKeiyakuHome() {
12
- return readEnvHome() ?? path.join(os.homedir(), ".keiyaku");
13
+ export function getKeiyakuHome(env = process.env) {
14
+ return readEnvHome(env) ?? path.join(os.homedir(), ".keiyaku");
13
15
  }
14
16
  const DOTENV_FILE = ".env";
15
17
  function decodeQuotedValue(raw) {
@@ -260,6 +262,8 @@ export function initializeConfig(env = process.env, cwd = process.cwd()) {
260
262
  runtimeEnv: snapshotEnv({ ...dotenvEnv, ...env }),
261
263
  });
262
264
  activeConfig = runtimeConfig;
265
+ const homeEnv = { ...process.env, ...env };
266
+ configureResponseArtifactCatalog(createSqliteResponseArtifactCatalog({ keiyakuHome: getKeiyakuHome(homeEnv) }));
263
267
  return runtimeConfig;
264
268
  }
265
269
  export function getConfig() {
@@ -270,4 +274,5 @@ export function getConfig() {
270
274
  }
271
275
  export function resetConfigForTests() {
272
276
  activeConfig = undefined;
277
+ configureResponseArtifactCatalog(undefined);
273
278
  }
@@ -0,0 +1,26 @@
1
+ import { FlowError } from "../../flow-error.js";
2
+ import { SETTINGS_FILE } from "../../keiyaku.js";
3
+ export function findSettingsDiseasesForKnob(diseases, knob) {
4
+ return diseases.filter((disease) => disease.knob === knob);
5
+ }
6
+ export function formatSettingsDiseases(diseases) {
7
+ return diseases.map((disease) => {
8
+ const where = disease.knob ? `${disease.source}:${disease.knob}` : `${disease.source}:(root)`;
9
+ return `${where}: ${disease.reason}`;
10
+ });
11
+ }
12
+ /**
13
+ * Reject when a named knob is diseased and was not admitted into knobs.
14
+ * A healthy value from another source still wins; only failed-and-unset knobs reject.
15
+ */
16
+ export function assertSettingsKnobUsable(loaded, knob) {
17
+ const knobs = loaded.knobs;
18
+ if (knobs !== null && knobs[knob] !== undefined)
19
+ return;
20
+ const hits = findSettingsDiseasesForKnob(loaded.diseases, knob);
21
+ if (hits.length === 0)
22
+ return;
23
+ throw new FlowError("INVALID_SETTINGS", `Invalid settings in ${SETTINGS_FILE} (diseased knob '${knob}').`, {
24
+ hints: formatSettingsDiseases(hits),
25
+ });
26
+ }
@@ -0,0 +1,8 @@
1
+ export const KNOWN_SETTINGS_KNOBS = ["default", "reviewer", "gate", "defaultBranch", "worktreeBootstrap"];
2
+ export const KNOWN_SETTINGS_ROOT_KEYS = [...KNOWN_SETTINGS_KNOBS, "providers"];
3
+ export function resolveDefaultAgentName(settings) {
4
+ return settings?.default ?? "akuma-codex";
5
+ }
6
+ export function resolveReviewerAgentName(settings) {
7
+ return settings?.reviewer ?? resolveDefaultAgentName(settings);
8
+ }
@@ -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
+ }