@astrosheep/keiyaku 2.9.3 → 2.9.5

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 (282) hide show
  1. package/build/.tsbuildinfo +1 -1
  2. package/build/agents/call-terms.js +14 -94
  3. package/build/agents/harness/activity-values.js +16 -0
  4. package/build/agents/harness/event-persistence.js +218 -0
  5. package/build/agents/harness/execution-handle.js +26 -222
  6. package/build/agents/harness/index.js +3 -1
  7. package/build/agents/harness/outcome.js +68 -5
  8. package/build/agents/harness/projection.js +179 -124
  9. package/build/agents/harness/provider-adapter.js +58 -0
  10. package/build/agents/harness/router.js +11 -187
  11. package/build/agents/harness/runtime.js +17 -4
  12. package/build/agents/harness/session.js +15 -0
  13. package/build/agents/launch-snapshot/model.js +15 -236
  14. package/build/agents/launch-snapshot/parse-helpers.js +114 -0
  15. package/build/agents/launch-snapshot/resolve.js +17 -43
  16. package/build/agents/opencode-sdk.js +45 -12
  17. package/build/agents/providers/claude-agent-sdk/adapter.js +6 -17
  18. package/build/agents/providers/claude-agent-sdk/registration.js +214 -0
  19. package/build/agents/providers/claude-agent-sdk/session-schema.js +2 -0
  20. package/build/agents/providers/codex-app-server/adapter.js +82 -53
  21. package/build/agents/providers/codex-app-server/events.js +90 -11
  22. package/build/agents/providers/codex-app-server/identity.js +1 -0
  23. package/build/agents/providers/codex-app-server/policy.js +29 -0
  24. package/build/agents/providers/codex-app-server/registration.js +248 -0
  25. package/build/agents/providers/codex-app-server/session-schema.js +6 -0
  26. package/build/agents/providers/codex-app-server/session.js +23 -17
  27. package/build/agents/providers/codex-app-server/terms.js +1 -0
  28. package/build/agents/providers/opencode-sdk/adapter.js +94 -50
  29. package/build/agents/providers/opencode-sdk/events.js +8 -4
  30. package/build/agents/providers/opencode-sdk/registration.js +135 -0
  31. package/build/agents/providers/opencode-sdk/session-schema.js +2 -0
  32. package/build/agents/providers/opencode-sdk/session.js +21 -14
  33. package/build/agents/providers/opencode-sdk/terms.js +1 -0
  34. package/build/agents/providers/pi/adapter.js +36 -30
  35. package/build/agents/providers/pi/events.js +4 -3
  36. package/build/agents/providers/pi/registration.js +145 -0
  37. package/build/agents/providers/pi/session-schema.js +6 -0
  38. package/build/agents/providers/pi/terms.js +1 -0
  39. package/build/agents/providers/registration.js +7 -0
  40. package/build/agents/providers/registry.js +76 -0
  41. package/build/agents/selector.js +2 -29
  42. package/build/agents/types.js +10 -0
  43. package/build/artifact-directories.js +22 -0
  44. package/build/cli/commands/akuma/akuma/handler.js +1 -1
  45. package/build/cli/commands/akuma/akuma/meta.js +1 -1
  46. package/build/cli/commands/akuma/list/meta-ls.js +1 -1
  47. package/build/cli/commands/akuma/view/handler.js +5 -4
  48. package/build/cli/commands/akuma/view/meta.js +1 -1
  49. package/build/cli/commands/akuma.js +6 -0
  50. package/build/cli/commands/contract/amend/handler.js +8 -2
  51. package/build/cli/commands/contract/amend/meta.js +4 -4
  52. package/build/cli/commands/contract/arc/handler.js +8 -2
  53. package/build/cli/commands/contract/arc/meta.js +4 -4
  54. package/build/cli/commands/contract/audit/handler.js +16 -0
  55. package/build/cli/commands/contract/audit/meta.js +16 -0
  56. package/build/cli/commands/contract/bind/handler.js +4 -3
  57. package/build/cli/commands/contract/bind/meta.js +44 -8
  58. package/build/cli/commands/contract/catalog.js +2 -0
  59. package/build/cli/commands/contract/forfeit/handler.js +4 -4
  60. package/build/cli/commands/contract/forfeit/meta.js +1 -1
  61. package/build/cli/commands/contract/log/handler.js +3 -3
  62. package/build/cli/commands/contract/log/meta.js +1 -1
  63. package/build/cli/commands/contract/petition/handler.js +5 -8
  64. package/build/cli/commands/contract/petition/meta.js +6 -6
  65. package/build/cli/commands/contract/renew/handler.js +20 -6
  66. package/build/cli/commands/contract/renew/meta.js +6 -5
  67. package/build/cli/commands/metadata.js +59 -0
  68. package/build/cli/commands/projection/call/handler.js +6 -5
  69. package/build/cli/commands/projection/call/meta.js +4 -1
  70. package/build/cli/commands/projection/kill/handler.js +16 -32
  71. package/build/cli/commands/projection/kill/meta.js +1 -1
  72. package/build/cli/commands/projection/revive/meta.js +4 -1
  73. package/build/cli/commands/projection/shared.js +28 -0
  74. package/build/cli/commands/projection/status/handler.js +1 -1
  75. package/build/cli/commands/projection/status/meta.js +1 -1
  76. package/build/cli/commands/projection/tell/handler.js +14 -30
  77. package/build/cli/commands/projection/tell/meta.js +4 -1
  78. package/build/cli/commands/projection/wait/handler.js +45 -22
  79. package/build/cli/commands/projection/wait/meta.js +8 -3
  80. package/build/cli/commands/registry.js +2 -54
  81. package/build/cli/commands/shared.js +10 -7
  82. package/build/cli/commands/skills/install/handler.js +2 -5
  83. package/build/cli/commands/skills/install/meta.js +1 -1
  84. package/build/cli/commands/skills/skills/handler.js +1 -1
  85. package/build/cli/commands/skills/skills/meta.js +1 -1
  86. package/build/cli/commands/system/completion/meta.js +1 -1
  87. package/build/cli/commands/system/dump-env/meta.js +1 -1
  88. package/build/cli/commands/system/guide/meta.js +1 -1
  89. package/build/cli/commands/task/add/handler.js +7 -15
  90. package/build/cli/commands/task/add/meta.js +4 -1
  91. package/build/cli/commands/task/catalog.js +2 -2
  92. package/build/cli/commands/task/doctor/meta.js +1 -1
  93. package/build/cli/commands/task/done/handler.js +4 -8
  94. package/build/cli/commands/task/done/meta.js +1 -1
  95. package/build/cli/commands/task/drop/handler.js +3 -6
  96. package/build/cli/commands/task/drop/meta.js +1 -1
  97. package/build/cli/commands/task/ls/handler.js +12 -8
  98. package/build/cli/commands/task/ls/meta.js +3 -3
  99. package/build/cli/commands/task/shared.js +14 -4
  100. package/build/cli/commands/task/{view → show}/handler.js +7 -8
  101. package/build/cli/commands/task/show/meta.js +8 -0
  102. package/build/cli/commands/task/start/handler.js +3 -6
  103. package/build/cli/commands/task/start/meta.js +1 -1
  104. package/build/cli/commands/task/stop/handler.js +4 -8
  105. package/build/cli/commands/task/stop/meta.js +1 -1
  106. package/build/cli/commands/task/task/handler.js +1 -1
  107. package/build/cli/commands/task/task/meta.js +1 -1
  108. package/build/cli/commands/task/update/handler.js +6 -11
  109. package/build/cli/commands/task/update/meta.js +4 -1
  110. package/build/cli/completion.js +1 -1
  111. package/build/cli/flags.js +37 -6
  112. package/build/cli/help.js +7 -14
  113. package/build/cli/index.js +112 -154
  114. package/build/cli/missing-subcommand.js +7 -0
  115. package/build/cli/output-stream-error-policy.js +19 -5
  116. package/build/cli/parse-flags.js +33 -7
  117. package/build/cli/parse-metadata.js +3 -9
  118. package/build/cli/parse-selectors.js +2 -1
  119. package/build/cli/parse.js +35 -5
  120. package/build/cli/projection-address.js +12 -12
  121. package/build/cli/render/address.js +41 -37
  122. package/build/cli/render/arc.js +24 -12
  123. package/build/cli/render/audit.js +121 -0
  124. package/build/cli/render/call.js +11 -5
  125. package/build/cli/render/errors.js +36 -1
  126. package/build/cli/render/format.js +4 -0
  127. package/build/cli/render/kanshi.js +6 -12
  128. package/build/cli/render/kill.js +9 -0
  129. package/build/cli/render/misc.js +58 -13
  130. package/build/cli/render/petition.js +20 -15
  131. package/build/cli/render/projection-activity.js +15 -6
  132. package/build/cli/render/shared.js +60 -21
  133. package/build/cli/render/skills.js +10 -0
  134. package/build/cli/render/status.js +56 -23
  135. package/build/cli/render/success-response.js +78 -0
  136. package/build/cli/render/task.js +66 -57
  137. package/build/cli/render/tell.js +5 -0
  138. package/build/cli/render/terminal-failure.js +63 -0
  139. package/build/cli/render/types.js +1 -0
  140. package/build/cli/render/wait.js +68 -38
  141. package/build/cli/types.js +1 -1
  142. package/build/config/akuma-loader.js +0 -5
  143. package/build/config/env-keys.js +3 -1
  144. package/build/config/env.js +3 -0
  145. package/build/config/provider-policy-ownership.js +3 -32
  146. package/build/config/settings/disease.js +8 -1
  147. package/build/config/settings/loader.js +11 -3
  148. package/build/config/settings/provider-instance-fields.js +51 -0
  149. package/build/config/settings/schema.js +6 -176
  150. package/build/core/addressing.js +66 -16
  151. package/build/core/amend.js +61 -18
  152. package/build/core/arc.js +33 -73
  153. package/build/core/audit.js +650 -0
  154. package/build/core/bind.js +82 -35
  155. package/build/core/call/call.js +15 -9
  156. package/build/core/call/context.js +6 -51
  157. package/build/core/call/execution.js +22 -22
  158. package/build/core/call/prompt.js +14 -1
  159. package/build/core/command-markdown.js +45 -0
  160. package/build/core/context.js +13 -7
  161. package/build/core/contract-view.js +22 -6
  162. package/build/core/draft.js +139 -19
  163. package/build/core/entry.js +3 -4
  164. package/build/core/forfeit.js +5 -2
  165. package/build/core/hints.js +1 -75
  166. package/build/core/identifier-slug.js +66 -0
  167. package/build/core/ids.js +25 -11
  168. package/build/core/ledger.js +20 -68
  169. package/build/core/markdown/lex.js +2 -1
  170. package/build/core/markdown/parser.js +0 -68
  171. package/build/core/markdown/render.js +68 -0
  172. package/build/core/markdown/sections.js +2 -1
  173. package/build/core/outcome-base.js +4 -0
  174. package/build/core/projection/generation/database.js +41 -52
  175. package/build/core/projection/generation/model.js +63 -2
  176. package/build/core/{projection-generation-continuation.js → projection/generation/projection-generation-continuation.js} +3 -3
  177. package/build/core/{projection-generation-execution.js → projection/generation/projection-generation-execution.js} +36 -53
  178. package/build/core/{projection-generation-identity.js → projection/generation/projection-generation-identity.js} +2 -2
  179. package/build/core/{projection-generation-launcher.js → projection/generation/projection-generation-launcher.js} +9 -7
  180. package/build/core/{projection-generation-process.js → projection/generation/projection-generation-process.js} +2 -1
  181. package/build/core/{projection-generation-runner.js → projection/generation/projection-generation-runner.js} +19 -3
  182. package/build/core/projection/generation/store.js +3 -3
  183. package/build/core/projection/generation/transitions.js +19 -3
  184. package/build/core/projection/index.js +27 -0
  185. package/build/core/{projection-activity.js → projection/projection-activity.js} +12 -0
  186. package/build/core/{projection-alias.js → projection/projection-alias.js} +13 -13
  187. package/build/core/projection/projection-coordinate.js +61 -0
  188. package/build/core/{projection-core.js → projection/projection-core.js} +9 -9
  189. package/build/core/{projection-execution-observer.js → projection/projection-execution-observer.js} +37 -78
  190. package/build/core/{projection-identity.js → projection/projection-identity.js} +1 -1
  191. package/build/core/{projection-kill.js → projection/projection-kill.js} +1 -1
  192. package/build/core/{projection-life-observer.js → projection/projection-life-observer.js} +16 -2
  193. package/build/core/{projection-mint.js → projection/projection-mint.js} +10 -10
  194. package/build/core/{projection-status.js → projection/projection-status.js} +163 -45
  195. package/build/core/projection/projection-terminal-failure.js +106 -0
  196. package/build/core/projection/projection-wait.js +275 -0
  197. package/build/core/projection/projection-wake.js +227 -0
  198. package/build/core/provider-loader-ports.js +4 -0
  199. package/build/core/ref-log.js +65 -40
  200. package/build/core/registry.js +63 -10
  201. package/build/core/renew-session.js +128 -0
  202. package/build/core/renew.js +448 -102
  203. package/build/core/scope.js +272 -68
  204. package/build/core/seal.js +165 -40
  205. package/build/core/{claim-delivery.js → settlement/claim-delivery.js} +8 -8
  206. package/build/core/{claim.js → settlement/claim.js} +32 -8
  207. package/build/core/settlement/index.js +19 -0
  208. package/build/core/{petition-claim-gates.js → settlement/petition-claim-gates.js} +24 -49
  209. package/build/core/{petition-forfeit.js → settlement/petition-forfeit.js} +2 -2
  210. package/build/core/{petition-head-guard.js → settlement/petition-head-guard.js} +2 -2
  211. package/build/core/settlement/petition-io.js +1 -0
  212. package/build/core/{petition-preview.js → settlement/petition-preview.js} +2 -2
  213. package/build/core/{petition.js → settlement/petition.js} +21 -12
  214. package/build/core/settlement/queue-read-model.js +143 -0
  215. package/build/core/settlement/queue-seat-allocation.js +156 -0
  216. package/build/core/settlement/queue.js +6 -0
  217. package/build/core/settlement/settlement.js +288 -0
  218. package/build/core/{verdict.js → settlement/verdict.js} +9 -37
  219. package/build/core/settlement/verification.js +59 -0
  220. package/build/core/status/board.js +62 -41
  221. package/build/core/status/ledger-batch.js +158 -0
  222. package/build/core/status/reconciliation.js +10 -4
  223. package/build/core/stored-agent-event.js +6 -2
  224. package/build/core/task/board.js +41 -4
  225. package/build/core/task/commands.js +45 -28
  226. package/build/core/task/document.js +5 -0
  227. package/build/core/task/index.js +12 -0
  228. package/build/core/task/model.js +8 -1
  229. package/build/core/task/query.js +172 -0
  230. package/build/core/task/settlement-git.js +265 -74
  231. package/build/core/task/settlement-policy.js +67 -29
  232. package/build/core/task/source-board.js +1 -0
  233. package/build/core/task/task-contract.js +444 -0
  234. package/build/core/{task-doctor.js → task/task-doctor.js} +1 -1
  235. package/build/core/{task-git-actor.js → task/task-git-actor.js} +5 -10
  236. package/build/core/{task-git-runtime.js → task/task-git-runtime.js} +3 -3
  237. package/build/core/{task-git-store.js → task/task-git-store.js} +90 -19
  238. package/build/core/task/task-repository.js +1 -0
  239. package/build/core/{task-store-repository.js → task/task-store-repository.js} +6 -4
  240. package/build/core/task/task-store.js +1 -0
  241. package/build/core/{task-worktree.js → task/task-worktree.js} +2 -1
  242. package/build/core/task/task.js +7 -0
  243. package/build/core/task/tree.js +37 -0
  244. package/build/core/task/validation.js +5 -2
  245. package/build/core/transcripts.js +15 -3
  246. package/build/core/worktree-path.js +43 -28
  247. package/build/flow-error.js +83 -39
  248. package/build/fs/path-coordinate.js +16 -0
  249. package/build/generated/version.js +3 -2
  250. package/build/git/branches.js +2 -1
  251. package/build/git/core.js +106 -2
  252. package/build/git/diff/parsers.js +23 -13
  253. package/build/git/diff/structured.js +127 -0
  254. package/build/git/path-coordinate.js +17 -0
  255. package/build/git/refs.js +57 -0
  256. package/build/git/streaming-batch.js +301 -0
  257. package/build/git/worktree.js +65 -0
  258. package/build/index.js +9 -9
  259. package/build/keiyaku.js +2 -36
  260. package/build/telemetry/logger.js +4 -2
  261. package/package.json +4 -10
  262. package/skills/keiyaku/SKILL.md +5 -4
  263. package/skills/keiyaku-akuma/SKILL.md +25 -11
  264. package/skills/keiyaku-task/SKILL.md +17 -8
  265. package/skills/keiyaku-workflow/SKILL.md +58 -9
  266. package/build/agents/providers/codex-sdk.js +0 -329
  267. package/build/cli/commands/task/view/meta.js +0 -8
  268. package/build/core/command-io.js +0 -174
  269. package/build/core/path-coordinate.js +0 -27
  270. package/build/core/petition-claim.js +0 -117
  271. package/build/core/petition-run.js +0 -83
  272. package/build/core/projection-coordinate.js +0 -63
  273. package/build/core/projection-wait.js +0 -165
  274. package/build/core/projection-wake.js +0 -136
  275. package/build/core/queue.js +0 -343
  276. package/build/core/task-contract.js +0 -237
  277. package/build/core/task.js +0 -4
  278. /package/build/{core/task-repository.js → agents/launch-snapshot/types.js} +0 -0
  279. /package/build/{core/task-store.js → agents/providers/claude-agent-sdk/terms.js} +0 -0
  280. /package/build/core/{projection-generation-runtime.js → projection/generation/projection-generation-runtime.js} +0 -0
  281. /package/build/core/{projection-life-protocol.js → projection/projection-life-protocol.js} +0 -0
  282. /package/build/core/{projection-runner-lock.js → projection/projection-runner-lock.js} +0 -0
@@ -0,0 +1,158 @@
1
+ import { wrapGitError, runGitProcess } from "../../git/core.js";
2
+ import { GitStreamingBatchFramingError, GitStreamingBatchResponseEndedError, openGitStreamingBatch, } from "../../git/streaming-batch.js";
3
+ import { parseLedgerEntryCommitMessage } from "../entry.js";
4
+ import { ACTIVE_LEDGER_REF_PREFIX, } from "../ledger.js";
5
+ const MAX_LEDGER_COMMIT_BYTES = 1024 * 1024;
6
+ async function runStatusGit(cwd, args) {
7
+ const result = await runGitProcess(cwd, args);
8
+ if (result.error || result.status !== 0) {
9
+ throw wrapGitError(args.join(" "), {
10
+ message: result.error?.message
11
+ ?? `git exited with status ${result.status ?? "unknown"}`,
12
+ stderr: result.stderr,
13
+ stdout: result.stdout,
14
+ }, cwd);
15
+ }
16
+ return Buffer.from(result.stdout, "utf8");
17
+ }
18
+ async function queryBatchObject(batch, expression) {
19
+ const record = await batch.request(expression, {
20
+ maxContentBytes: MAX_LEDGER_COMMIT_BYTES,
21
+ });
22
+ if (record.kind === "missing" || record.kind === "ambiguous") {
23
+ throw new Error(`[git cat-file ${expression}] object is ${record.kind === "missing" ? "missing" : "ambiguous"}`);
24
+ }
25
+ return {
26
+ oid: record.record.oid,
27
+ type: record.record.type,
28
+ content: record.record.content,
29
+ };
30
+ }
31
+ function parseCommitObject(object) {
32
+ if (object.type !== "commit") {
33
+ throw new Error(`git cat-file expected commit ${object.oid}, received ${object.type}`);
34
+ }
35
+ const content = object.content.toString("utf8");
36
+ const messageStart = content.indexOf("\n\n");
37
+ if (messageStart < 0)
38
+ throw new Error(`git commit ${object.oid} has no message separator`);
39
+ const parents = [];
40
+ for (const line of content.slice(0, messageStart).split("\n")) {
41
+ if (!line.startsWith("parent "))
42
+ continue;
43
+ const parent = line.slice("parent ".length);
44
+ if (!/^[0-9a-f]{40,64}$/.test(parent)) {
45
+ throw new Error(`git commit ${object.oid} has an invalid parent header`);
46
+ }
47
+ parents.push(parent);
48
+ }
49
+ return { oid: object.oid, parents, message: content.slice(messageStart + 2) };
50
+ }
51
+ function parseLedgerRefs(output) {
52
+ return output
53
+ .toString("utf8")
54
+ .split(/\r?\n/)
55
+ .filter((ref) => ref.startsWith(ACTIVE_LEDGER_REF_PREFIX))
56
+ .map((ref) => ({ contractId: ref.slice(ACTIVE_LEDGER_REF_PREFIX.length), ref }));
57
+ }
58
+ async function readLedgerFromBatch(batch, ref, commits) {
59
+ const headObject = await queryBatchObject(batch, `${ref.ref}^{commit}`);
60
+ const head = parseCommitObject(headObject);
61
+ commits.set(head.oid, Promise.resolve(head));
62
+ const ordered = [];
63
+ const visited = new Set();
64
+ const visiting = new Set();
65
+ const visit = async (oid) => {
66
+ if (visited.has(oid))
67
+ return;
68
+ if (visiting.has(oid))
69
+ throw new Error(`git ledger ancestry contains a cycle at ${oid}`);
70
+ visiting.add(oid);
71
+ let pending = commits.get(oid);
72
+ if (!pending) {
73
+ pending = queryBatchObject(batch, oid).then(parseCommitObject);
74
+ commits.set(oid, pending);
75
+ }
76
+ const commit = await pending;
77
+ for (const parent of commit.parents)
78
+ await visit(parent);
79
+ visiting.delete(oid);
80
+ visited.add(oid);
81
+ ordered.push(commit);
82
+ };
83
+ await visit(head.oid);
84
+ return {
85
+ contractId: ref.contractId,
86
+ ref: ref.ref,
87
+ head: head.oid,
88
+ entries: ordered.map((commit) => parseLedgerEntryCommitMessage(commit.message)),
89
+ };
90
+ }
91
+ /**
92
+ * Read the aggregate status ledger snapshot with at most two Git processes:
93
+ * ref-name discovery and one framed, streaming object session.
94
+ */
95
+ export async function readStatusLedgers(cwd) {
96
+ const refOutput = await runStatusGit(cwd, [
97
+ "for-each-ref",
98
+ "--sort=refname",
99
+ "--format=%(refname)",
100
+ ACTIVE_LEDGER_REF_PREFIX,
101
+ ]);
102
+ const refs = parseLedgerRefs(refOutput);
103
+ if (refs.length === 0)
104
+ return [];
105
+ const batch = openGitStreamingBatch(cwd);
106
+ const commits = new Map();
107
+ const results = [];
108
+ let readError;
109
+ try {
110
+ for (let index = 0; index < refs.length; index += 1) {
111
+ const ref = refs[index];
112
+ try {
113
+ results.push(await readLedgerFromBatch(batch, ref, commits));
114
+ }
115
+ catch (error) {
116
+ const diagnostic = error instanceof Error ? error : new Error(String(error));
117
+ results.push({ id: ref.contractId, error: diagnostic });
118
+ // Framing and mid-frame EOF both destroy the batch channel; isolate remaining refs.
119
+ if (error instanceof GitStreamingBatchFramingError
120
+ || error instanceof GitStreamingBatchResponseEndedError) {
121
+ const isolationReason = error instanceof GitStreamingBatchResponseEndedError
122
+ ? "response ended"
123
+ : "malformed frame";
124
+ for (const pending of refs.slice(index + 1)) {
125
+ results.push({
126
+ id: pending.contractId,
127
+ error: new Error(`git cat-file batch terminated after ${isolationReason}: ${diagnostic.message}`),
128
+ });
129
+ }
130
+ break;
131
+ }
132
+ }
133
+ }
134
+ }
135
+ catch (error) {
136
+ readError = error;
137
+ }
138
+ try {
139
+ const close = await batch.close();
140
+ if (!close.aborted && (close.timedOut || close.signal || close.code !== 0)) {
141
+ throw wrapGitError("cat-file --batch", {
142
+ message: close.timedOut
143
+ ? "git timed out"
144
+ : close.signal
145
+ ? `git exited from signal ${close.signal}`
146
+ : `git exited with status ${close.code ?? "unknown"}`,
147
+ stderr: close.stderr.toString("utf8"),
148
+ }, cwd);
149
+ }
150
+ }
151
+ catch (error) {
152
+ if (!readError)
153
+ readError = error;
154
+ }
155
+ if (readError)
156
+ throw readError;
157
+ return results;
158
+ }
@@ -1,6 +1,6 @@
1
1
  import * as fs from "node:fs";
2
2
  import * as path from "node:path";
3
- import { createGit } from "../../git/core.js";
3
+ import { createGit, parseRevListOutput, wrapGitError } from "../../git/core.js";
4
4
  import { stableRepoRoot } from "../worktree-path.js";
5
5
  function isClaimReceipt(value) {
6
6
  if (!value || typeof value !== "object")
@@ -51,7 +51,13 @@ export async function computeClaimReconciliation(cwd, defaultBranch) {
51
51
  if (receiptLines.length === 0)
52
52
  return null;
53
53
  const git = createGit(cwd);
54
- const firstParentHistory = (await git.raw(["rev-list", "--first-parent", defaultBranch.head])).trim().split("\n").filter(Boolean);
54
+ let firstParentHistory;
55
+ try {
56
+ firstParentHistory = parseRevListOutput(await git.raw(["rev-list", "--first-parent", defaultBranch.head]));
57
+ }
58
+ catch (error) {
59
+ throw wrapGitError(`rev-list --first-parent ${defaultBranch.head}`, error, cwd);
60
+ }
55
61
  const firstParentSet = new Set(firstParentHistory);
56
62
  let baselineIndex = -1;
57
63
  for (let index = receiptLines.length - 1; index >= 0; index -= 1) {
@@ -70,10 +76,10 @@ export async function computeClaimReconciliation(cwd, defaultBranch) {
70
76
  return line.record?.kind === "baseline" ? [{ line: line.line, reason: "non-chain" }] : [];
71
77
  });
72
78
  // Only records after the latest valid baseline belong to this fence window.
79
+ // Intentional degrade: rev-list failure yields empty segments plus non-chain receipt issues.
73
80
  let revList;
74
81
  try {
75
- const output = await git.raw(["rev-list", "--first-parent", "--reverse", `${baseline.head}..${defaultBranch.head}`]);
76
- revList = output.trim().split("\n").filter((h) => h.length > 0);
82
+ revList = parseRevListOutput(await git.raw(["rev-list", "--first-parent", "--reverse", `${baseline.head}..${defaultBranch.head}`]));
77
83
  }
78
84
  catch {
79
85
  return {
@@ -1,17 +1,21 @@
1
1
  import { activityEventBodySchema, AGENT_EVENT_TEXT_MAX_CHARS, AGENT_UNKNOWN_PAYLOAD_MAX_CHARS, EXEC_EVENT_BUFFER_HARD_CAP, } from "../agents/harness/index.js";
2
2
  import { isSubagentProvider } from "../keiyaku.js";
3
- const FAIL_CODES = new Set(["SUBAGENT_TIMEOUT", "SUBAGENT_NON_ZERO_EXIT", "SUBAGENT_SPAWN_ERROR", "SUBAGENT_EXEC_ERROR", "SUBAGENT_PROVIDER_UNAVAILABLE", "PROJECTION_STATE_ERROR"]);
3
+ const FAIL_CODES = new Set(["SUBAGENT_TIMEOUT", "SUBAGENT_TRANSPORT_ERROR", "SUBAGENT_POLICY_BUDGET", "SUBAGENT_NON_ZERO_EXIT", "SUBAGENT_SPAWN_ERROR", "SUBAGENT_EXEC_ERROR", "SUBAGENT_PROVIDER_UNAVAILABLE", "PROJECTION_STATE_ERROR"]);
4
4
  const KNOWN = new Set(["message", "unknown", "activity", "thought", "plan", "turn", "usage", "warning", "diagnostic", "truncation", "done", "fail"]);
5
5
  const record = (value) => value !== null && typeof value === "object" && !Array.isArray(value);
6
6
  const finite = (value) => typeof value === "number" && Number.isFinite(value);
7
+ const nonNegativeFinite = (value) => finite(value) && value >= 0;
7
8
  const optionalString = (value, key) => value[key] === undefined || typeof value[key] === "string";
8
9
  const nonNegativeSafeInteger = (value) => typeof value === "number" && Number.isSafeInteger(value) && value >= 0;
9
10
  const safeIntegerOrNull = (value) => value === null || (typeof value === "number" && Number.isSafeInteger(value));
10
11
  function isValidStoredWarning(warning) {
12
+ if (warning.reason === "rate-limit") {
13
+ return warning.state === "exhausted" && Object.keys(warning).length === 2;
14
+ }
11
15
  if (warning.reason === "retry") {
12
16
  return nonNegativeSafeInteger(warning.attempt)
13
17
  && nonNegativeSafeInteger(warning.maxRetries)
14
- && nonNegativeSafeInteger(warning.retryDelayMs)
18
+ && nonNegativeFinite(warning.retryDelayMs)
15
19
  && safeIntegerOrNull(warning.errorStatus);
16
20
  }
17
21
  if (warning.reason === "refusal" || warning.reason === "overload") {
@@ -1,6 +1,5 @@
1
1
  import { FlowError } from "../../flow-error.js";
2
- import { TASK_ID } from "./validation.js";
3
- const TASK_READ_ID = /^k-[0-9a-f]{7,12}$/;
2
+ import { isTaskId } from "./validation.js";
4
3
  function compareText(left, right) {
5
4
  return left < right ? -1 : left > right ? 1 : 0;
6
5
  }
@@ -60,6 +59,30 @@ function cycleComponents(tasks) {
60
59
  visit(id);
61
60
  return cycles;
62
61
  }
62
+ function parentCycleComponents(tasks) {
63
+ const cycles = [];
64
+ const visited = new Set();
65
+ for (const start of [...tasks.keys()].sort()) {
66
+ if (visited.has(start))
67
+ continue;
68
+ const path = [];
69
+ const positions = new Map();
70
+ let current = start;
71
+ while (current !== null && tasks.has(current) && !visited.has(current)) {
72
+ const prior = positions.get(current);
73
+ if (prior !== undefined) {
74
+ cycles.push(new Set(path.slice(prior)));
75
+ break;
76
+ }
77
+ positions.set(current, path.length);
78
+ path.push(current);
79
+ current = tasks.get(current).parent;
80
+ }
81
+ for (const id of path)
82
+ visited.add(id);
83
+ }
84
+ return cycles;
85
+ }
63
86
  /** Derive the complete storage-neutral board from domain records. */
64
87
  export function buildTaskBoardFromDocuments(documents) {
65
88
  const diseases = [];
@@ -91,6 +114,17 @@ export function buildTaskBoardFromDocuments(documents) {
91
114
  candidates.delete(id);
92
115
  }
93
116
  }
117
+ for (const component of parentCycleComponents(candidates)) {
118
+ const relatedIds = [...component].sort();
119
+ for (const id of relatedIds) {
120
+ diseases.push({
121
+ code: "parent_cycle",
122
+ message: `task ${id} participates in a parent cycle`,
123
+ id,
124
+ relatedIds,
125
+ });
126
+ }
127
+ }
94
128
  let changed = true;
95
129
  while (changed) {
96
130
  changed = false;
@@ -129,6 +163,7 @@ export function buildTaskBoardFromDocuments(documents) {
129
163
  parent: task.parent,
130
164
  from: [...task.from],
131
165
  createdAt: task.createdAt,
166
+ ...(task.updatedAt === undefined ? {} : { updatedAt: task.updatedAt }),
132
167
  creator: task.creator,
133
168
  ...(task.startedAt === undefined ? {} : { startedAt: task.startedAt }),
134
169
  ...(task.contractId === undefined ? {} : { contractId: task.contractId }),
@@ -148,9 +183,11 @@ export function resolveTaskReadId(board, input) {
148
183
  return resolveTaskId(board, input);
149
184
  }
150
185
  export function resolveTaskId(board, input) {
151
- if (!TASK_READ_ID.test(input)) {
186
+ if (!isTaskId(input)) {
152
187
  throw new FlowError("INVALID_TARGET", `invalid task id '${input}'`);
153
188
  }
189
+ if (board.byId.has(input))
190
+ return input;
154
191
  const matches = board.tasks
155
192
  .map((task) => task.id)
156
193
  .filter((id) => id.startsWith(input))
@@ -163,7 +200,7 @@ export function resolveTaskId(board, input) {
163
200
  return matches[0];
164
201
  }
165
202
  export function requireCanonicalTaskId(input) {
166
- if (!TASK_ID.test(input)) {
203
+ if (!isTaskId(input)) {
167
204
  throw new FlowError("INVALID_TARGET", "task mutations require a canonical full task id");
168
205
  }
169
206
  return input;
@@ -1,6 +1,8 @@
1
1
  import { FlowError } from "../../flow-error.js";
2
+ import { IdentifierSlugError, normalizeIdentifierSlug, SUFFIXABLE_IDENTIFIER_SLUG_MAX_UTF8_BYTES, } from "../identifier-slug.js";
2
3
  import { requireCanonicalTaskId, requireHealthy, resolveTaskId, } from "./board.js";
3
4
  import { dateTimestamp, invalid, nonblank, } from "./validation.js";
5
+ import { advancedTaskUpdatedAt } from "./model.js";
4
6
  export async function readTaskBoard(repository) {
5
7
  return repository.read();
6
8
  }
@@ -14,15 +16,17 @@ function documentFromView(view) {
14
16
  parent: view.parent,
15
17
  from: [...view.from],
16
18
  createdAt: view.createdAt,
19
+ ...(view.updatedAt === undefined ? {} : { updatedAt: view.updatedAt }),
17
20
  creator: view.creator,
18
21
  ...(view.startedAt === undefined ? {} : { startedAt: view.startedAt }),
19
22
  ...(view.contractId === undefined ? {} : { contractId: view.contractId }),
20
23
  body: view.body,
21
24
  };
22
25
  }
23
- function replaceDocument(document) {
26
+ function replaceDocument(document, subject) {
24
27
  return {
25
28
  replacements: [document],
29
+ subject,
26
30
  select(resulting) {
27
31
  const value = resulting.byId.get(document.id);
28
32
  if (!value)
@@ -43,6 +47,16 @@ function resolveMutationIds(board, ids) {
43
47
  export async function createTask(repository, input, deps) {
44
48
  nonblank("title", input.title);
45
49
  nonblank("creator", input.creator);
50
+ let baseId;
51
+ try {
52
+ baseId = normalizeIdentifierSlug(input.title, SUFFIXABLE_IDENTIFIER_SLUG_MAX_UTF8_BYTES);
53
+ }
54
+ catch (error) {
55
+ if (error instanceof IdentifierSlugError) {
56
+ throw new FlowError("INVALID_TARGET", `task title ${error.message}`, { cause: error });
57
+ }
58
+ throw error;
59
+ }
46
60
  const pri = input.pri ?? 2;
47
61
  if (!Number.isInteger(pri) || pri < 0 || pri > 3)
48
62
  invalid("task priority must be an integer from 0 through 3");
@@ -55,19 +69,14 @@ export async function createTask(repository, input, deps) {
55
69
  const from = resolveMutationIds(board, input.from ?? []);
56
70
  if (new Set(from).size !== from.length)
57
71
  invalid("task from edges must be unique");
58
- let id;
59
- for (let attempt = 0; attempt < 32; attempt += 1) {
60
- const bytes = deps.randomBytes(6);
61
- if (bytes.byteLength !== 6)
62
- throw new FlowError("INTERNAL_STATE", "randomBytes(6) must return exactly 6 bytes");
63
- const candidate = `k-${Buffer.from(bytes).toString("hex")}`;
64
- if (!board.byId.has(candidate)) {
65
- id = candidate;
66
- break;
72
+ let id = baseId;
73
+ for (let ordinal = 2; board.byId.has(id); ordinal += 1) {
74
+ if (!Number.isSafeInteger(ordinal)) {
75
+ throw new FlowError("INTERNAL_STATE", `task id collision ordinal exceeded the safe integer range for ${baseId}`);
67
76
  }
77
+ id = `${baseId}-${ordinal}`;
68
78
  }
69
- if (!id)
70
- throw new FlowError("CONCURRENT_MODIFICATION", "could not allocate a unique task id after 32 attempts");
79
+ const now = deps.now();
71
80
  const document = {
72
81
  id,
73
82
  title: input.title,
@@ -76,14 +85,15 @@ export async function createTask(repository, input, deps) {
76
85
  needs,
77
86
  parent,
78
87
  from,
79
- createdAt: dateTimestamp("createdAt", deps.now()),
88
+ createdAt: dateTimestamp("createdAt", now),
89
+ updatedAt: dateTimestamp("updatedAt", now),
80
90
  creator: input.creator,
81
91
  body: input.body ?? "",
82
92
  };
83
- return replaceDocument(document);
93
+ return replaceDocument(document, `Add task ${id}`);
84
94
  });
85
95
  }
86
- export async function updateTask(repository, fullIdValue, patch) {
96
+ export async function updateTask(repository, fullIdValue, patch, now = new Date()) {
87
97
  const hasChange = Object.values(patch).some((value) => value !== undefined);
88
98
  if (!hasChange)
89
99
  throw new FlowError("EMPTY_PARAM", "task update requires at least one change");
@@ -112,18 +122,21 @@ export async function updateTask(repository, fullIdValue, patch) {
112
122
  if (JSON.stringify(document) === JSON.stringify(documentFromView(current))) {
113
123
  throw new FlowError("EMPTY_PARAM", "task update requires at least one change");
114
124
  }
115
- return replaceDocument(document);
125
+ document.updatedAt = advancedTaskUpdatedAt(current, now);
126
+ return replaceDocument(document, `Update task ${id}`);
116
127
  });
117
128
  }
118
- async function transition(repository, fullIdValue, apply) {
129
+ async function transition(repository, fullIdValue, apply, subject, now) {
119
130
  return repository.mutate((board) => {
120
131
  requireHealthy(board);
121
132
  const id = fullIdValue === undefined
122
133
  ? selectSoleInProgress(board).id
123
134
  : resolveTaskId(board, fullIdValue);
124
- const document = documentFromView(requireExisting(board, id));
135
+ const current = requireExisting(board, id);
136
+ const document = documentFromView(current);
125
137
  apply(document);
126
- return replaceDocument(document);
138
+ document.updatedAt = advancedTaskUpdatedAt(current, now);
139
+ return replaceDocument(document, subject(id));
127
140
  });
128
141
  }
129
142
  function rejectBound(document, action = "state") {
@@ -141,18 +154,18 @@ export async function startTask(repository, fullId, now) {
141
154
  invalid(`task ${document.id} is ${document.state}, not open`);
142
155
  document.state = "in_progress";
143
156
  document.startedAt = dateTimestamp("startedAt", now);
144
- });
157
+ }, (id) => `Start task ${id}`, now);
145
158
  }
146
- export async function stopTask(repository, fullId) {
159
+ export async function stopTask(repository, fullId, now = new Date()) {
147
160
  return transition(repository, fullId, (document) => {
148
161
  rejectBound(document);
149
162
  if (document.state !== "in_progress")
150
163
  invalid(`task ${document.id} is ${document.state}, not in_progress`);
151
164
  document.state = "open";
152
165
  delete document.startedAt;
153
- });
166
+ }, (id) => `Stop task ${id}`, now);
154
167
  }
155
- export async function doneTask(repository, fullId) {
168
+ export async function doneTask(repository, fullId, now = new Date()) {
156
169
  return transition(repository, fullId, (document) => {
157
170
  rejectBound(document);
158
171
  if (document.state !== "open" && document.state !== "in_progress") {
@@ -160,9 +173,9 @@ export async function doneTask(repository, fullId) {
160
173
  }
161
174
  document.state = "done";
162
175
  delete document.startedAt;
163
- });
176
+ }, (id) => `Done task ${id}`, now);
164
177
  }
165
- export async function dropTask(repository, fullId) {
178
+ export async function dropTask(repository, fullId, now = new Date()) {
166
179
  return transition(repository, fullId, (document) => {
167
180
  rejectBound(document, "drop");
168
181
  if (document.state !== "open" && document.state !== "in_progress") {
@@ -170,7 +183,7 @@ export async function dropTask(repository, fullId) {
170
183
  }
171
184
  document.state = "drop";
172
185
  delete document.startedAt;
173
- });
186
+ }, (id) => `Drop task ${id}`, now);
174
187
  }
175
188
  export function selectSoleInProgress(board) {
176
189
  const candidates = board.tasks.filter((task) => task.state === "in_progress");
@@ -184,7 +197,7 @@ export function selectSoleInProgress(board) {
184
197
  }
185
198
  /** Reopen task pointers whose contract authority has disappeared. The caller
186
199
  * supplies the ledger fact; this operation itself remains storage-neutral. */
187
- export async function rollbackOrphanTaskPointers(repository, contractIds) {
200
+ export async function rollbackOrphanTaskPointers(repository, contractIds, now = new Date()) {
188
201
  const before = await repository.read();
189
202
  const orphans = before.tasks
190
203
  .filter((task) => task.contractId !== undefined && !contractIds.has(task.contractId))
@@ -199,13 +212,17 @@ export async function rollbackOrphanTaskPointers(repository, contractIds) {
199
212
  return [];
200
213
  const document = documentFromView(current);
201
214
  document.state = "open";
215
+ document.updatedAt = advancedTaskUpdatedAt(current, now);
202
216
  delete document.startedAt;
203
217
  delete document.contractId;
204
218
  return [document];
205
219
  });
206
220
  if (replacements.length === 0)
207
221
  throw new FlowError("CONCURRENT_MODIFICATION", "task pointers changed while doctor was reconciling them");
208
- return { replacements, select: () => undefined };
222
+ const subject = replacements.length === 1
223
+ ? `Clear orphan contract pointer from task ${replacements[0].id}`
224
+ : `Clear orphan contract pointers from ${replacements.length} tasks`;
225
+ return { replacements, subject, select: () => undefined };
209
226
  });
210
227
  return orphans;
211
228
  }
@@ -9,6 +9,7 @@ const FIELD_ORDER = [
9
9
  "parent",
10
10
  "from",
11
11
  "createdAt",
12
+ "updatedAt",
12
13
  "creator",
13
14
  "startedAt",
14
15
  "contractId",
@@ -134,6 +135,8 @@ export function parseTaskDocument(source) {
134
135
  if (from.includes(id))
135
136
  invalid("task field 'from' must not reference itself");
136
137
  const createdAt = canonicalTimestamp("createdAt", values.get("createdAt"));
138
+ const updatedValue = values.get("updatedAt");
139
+ const updatedAt = updatedValue === undefined ? undefined : canonicalTimestamp("updatedAt", updatedValue);
137
140
  const creator = nonblank("creator", values.get("creator"));
138
141
  const startedValue = values.get("startedAt");
139
142
  const contractValue = values.get("contractId");
@@ -154,6 +157,7 @@ export function parseTaskDocument(source) {
154
157
  parent,
155
158
  from,
156
159
  createdAt,
160
+ ...(updatedAt === undefined ? {} : { updatedAt }),
157
161
  creator,
158
162
  ...(startedAt === undefined ? {} : { startedAt }),
159
163
  ...(contractId === undefined ? {} : { contractId }),
@@ -174,6 +178,7 @@ function serializeUnchecked(document) {
174
178
  parent: document.parent,
175
179
  from: document.from,
176
180
  createdAt: document.createdAt,
181
+ ...(document.updatedAt === undefined ? {} : { updatedAt: document.updatedAt }),
177
182
  creator: document.creator,
178
183
  };
179
184
  if (document.startedAt !== undefined)
@@ -0,0 +1,12 @@
1
+ /**
2
+ * Public surface for the task package.
3
+ * Only symbols required by current external consumers are re-exported.
4
+ * Package-internal modules must import each other directly, not through this file.
5
+ */
6
+ export { reconcileTaskForfeit, settleTaskBoardForBind, settleTaskBoardForLanding, } from "./task-contract.js";
7
+ export { resolveTaskCreator } from "./task-git-actor.js";
8
+ export { openTaskCommandRepository, readTaskDoctor, taskCommandContractId, } from "./task-git-runtime.js";
9
+ export { openTaskStore } from "./task-git-store.js";
10
+ export { taskRepositoryFromStore } from "./task-store-repository.js";
11
+ export { isTaskId } from "./validation.js";
12
+ export { buildTaskTree, createTask, doneTask, dropTask, parseTaskDocument, readTaskBoard, resolveTaskReadId, serializeTaskDocument, startTask, stopTask, updateTask, deriveTaskDisplay, selectTaskList, taskUpdatedAt, advancedTaskUpdatedAt, } from "./task.js";
@@ -1 +1,8 @@
1
- export {};
1
+ export function taskUpdatedAt(task) {
2
+ return task.updatedAt ?? task.createdAt;
3
+ }
4
+ export function advancedTaskUpdatedAt(task, observed) {
5
+ const observedMs = observed instanceof Date ? observed.getTime() : Date.parse(observed);
6
+ const priorMs = Date.parse(taskUpdatedAt(task));
7
+ return new Date(Math.max(observedMs, priorMs + 1)).toISOString();
8
+ }