opencode-swarm 7.184.11 → 7.184.13

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 (109) hide show
  1. package/.opencode/skills/swarm-ci-monitor/SKILL.md +1 -0
  2. package/.opencode/skills/swarm-pr-feedback/SKILL.md +1 -0
  3. package/.opencode/skills/swarm-pr-review/SKILL.md +21 -21
  4. package/.opencode/skills/swarm-pr-review/references/lane-output-recoverability.md +11 -0
  5. package/.opencode/skills/swarm-pr-review/references/parallel-work-example.md +20 -0
  6. package/.opencode/skills/swarm-pr-subscribe/SKILL.md +1 -0
  7. package/binaries/win32-arm64/swarm-sandbox-runner.exe +0 -0
  8. package/binaries/win32-x64/swarm-sandbox-runner.exe +0 -0
  9. package/dist/background/delegation-lifecycle.d.ts +6 -2
  10. package/dist/background/pending-delegations.d.ts +23 -1
  11. package/dist/cli/auto-checkpoint-a2zbyh20.js +183 -0
  12. package/dist/cli/checkpoint-s444mhx5.js +51 -0
  13. package/dist/cli/{coder-settlement-6mhw6b36.js → coder-settlement-hnwwt3f0.js} +13 -11
  14. package/dist/cli/{config-doctor-tbaswv40.js → config-doctor-2qhay8ft.js} +2 -2
  15. package/dist/cli/{core-4389fcpc.js → core-567f7gbz.js} +3 -3
  16. package/dist/cli/{curation-policy-xe77n9zj.js → curation-policy-1rzheh1x.js} +6 -5
  17. package/dist/cli/{curator-asqb0cm7.js → curator-antxp3dg.js} +48 -42
  18. package/dist/cli/{curator-drift-nnge4dkk.js → curator-drift-ja4d9gq2.js} +9 -9
  19. package/dist/cli/curator-llm-factory-0a3svbcn.js +81 -0
  20. package/dist/cli/{dispatch-cv89p2y1.js → dispatch-0ejzwb45.js} +2 -1
  21. package/dist/cli/{evidence-summary-service-cdrwfdyp.js → evidence-summary-service-6ch3b0t5.js} +24 -19
  22. package/dist/cli/{gate-evidence-xbn83jeh.js → gate-evidence-zh4fpbnv.js} +8 -6
  23. package/dist/cli/guardrail-explain-pqyp8mx5.js +82 -0
  24. package/dist/cli/{guardrail-log-b6ef00v7.js → guardrail-log-99zsmp0p.js} +7 -7
  25. package/dist/cli/{guardrail-reset-77r7jfn2.js → guardrail-reset-mzs00j16.js} +48 -42
  26. package/dist/cli/hive-promoter-f3enyhkn.js +96 -0
  27. package/dist/cli/index-09wnx2ke.js +954 -0
  28. package/dist/cli/{index-kaay5kmx.js → index-0p0qwdyy.js} +2 -2
  29. package/dist/cli/{index-zz5rryxj.js → index-258a0p26.js} +7 -7
  30. package/dist/cli/{index-908zxswj.js → index-2n1ms79t.js} +44 -16
  31. package/dist/cli/{index-ejpxkrp6.js → index-2p3nzm9b.js} +1 -1
  32. package/dist/cli/index-2y9b45jj.js +24 -0
  33. package/dist/cli/{index-hk0eh9qb.js → index-4g4sr4ah.js} +1 -1
  34. package/dist/cli/{index-35cdceqf.js → index-6cjpmvpa.js} +2 -20
  35. package/dist/cli/{index-5cawm255.js → index-79y6dxa9.js} +8 -6
  36. package/dist/cli/index-7bxssqaf.js +2676 -0
  37. package/dist/cli/index-7pmbnjvj.js +11 -0
  38. package/dist/cli/{index-j4efx148.js → index-7r3pkgma.js} +5 -5
  39. package/dist/cli/{index-kg2mb1m5.js → index-7rzgdcvc.js} +2 -2
  40. package/dist/cli/{index-8gs8qkjs.js → index-7vb5rx5w.js} +1 -1
  41. package/dist/cli/{index-hamqvzqn.js → index-9nyqt3kb.js} +10 -2682
  42. package/dist/cli/{index-5njp971j.js → index-9rm7tw6y.js} +8 -8
  43. package/dist/cli/{index-c0dahh34.js → index-9y5b3nsk.js} +1 -1
  44. package/dist/cli/{index-xpcvwzdq.js → index-a96rm6zk.js} +3 -3
  45. package/dist/cli/{index-jhk62dp4.js → index-a9rfvc8j.js} +4 -4
  46. package/dist/cli/{index-xw8xdcrf.js → index-awkb5370.js} +5 -5
  47. package/dist/cli/{index-0mj7hc2z.js → index-bntwj2tp.js} +5 -5
  48. package/dist/cli/{index-aae38s4x.js → index-cftva3pa.js} +2 -11
  49. package/dist/cli/{index-hwjr3xyg.js → index-ck2hzp5b.js} +10 -38
  50. package/dist/cli/{index-e0rrj9kk.js → index-d2xdwz22.js} +9 -9
  51. package/dist/cli/{index-gnryhmak.js → index-dftztz4y.js} +1 -1
  52. package/dist/cli/{index-7k4ksbqd.js → index-djct2zsr.js} +1 -1
  53. package/dist/cli/{index-gqj1k2y6.js → index-e593fgq0.js} +2 -2
  54. package/dist/cli/{index-0ajyhjht.js → index-fxf3zhtq.js} +1 -1
  55. package/dist/cli/index-g4fs6z4e.js +37 -0
  56. package/dist/cli/{index-89nqyqxs.js → index-gz771bj7.js} +3 -3
  57. package/dist/cli/{index-tamg9rve.js → index-gzh04gxh.js} +2 -2
  58. package/dist/cli/index-hg1hqfy3.js +963 -0
  59. package/dist/cli/{index-nh5fxxc9.js → index-jtg0zsnx.js} +4 -2
  60. package/dist/cli/{index-3w96sd7p.js → index-kxm9wm5e.js} +3044 -3842
  61. package/dist/cli/{index-07fqfseh.js → index-m1pqenq9.js} +2 -2
  62. package/dist/cli/{index-be5h97z8.js → index-mfqge140.js} +1 -1
  63. package/dist/cli/{index-6z53ry5f.js → index-nt8g5952.js} +2 -2
  64. package/dist/cli/{index-cn67bp4y.js → index-pq35rmz5.js} +52 -46
  65. package/dist/cli/{index-5m1b70z3.js → index-pwcbtbhd.js} +4 -4
  66. package/dist/cli/{index-w0jbrttk.js → index-ra0z5wza.js} +3 -3
  67. package/dist/cli/{index-vx0wjfc1.js → index-sa061g64.js} +7 -7
  68. package/dist/cli/{index-7kmxy1rb.js → index-t7ck2nvk.js} +4 -4
  69. package/dist/cli/{index-ygdj73xq.js → index-tswsm3sw.js} +3 -3
  70. package/dist/cli/{index-q7q00ppx.js → index-tyzmjw2s.js} +7 -7
  71. package/dist/cli/{index-645ckyxy.js → index-v4ra6hz8.js} +55 -971
  72. package/dist/cli/{index-pbwfwc0v.js → index-vd1sd1m5.js} +48 -42
  73. package/dist/cli/{index-x1gyqgze.js → index-w6w6ny06.js} +2 -2
  74. package/dist/cli/{index-9xfxsx8y.js → index-wksedk4d.js} +1 -1
  75. package/dist/cli/{index-x119gdak.js → index-yx5z4200.js} +2 -2
  76. package/dist/cli/{index-dqpq8m9w.js → index-yydv4jfy.js} +2 -2
  77. package/dist/cli/index.js +49 -43
  78. package/dist/cli/{knowledge-escalator-8cc6jcw0.js → knowledge-escalator-6c813zv8.js} +13 -12
  79. package/dist/cli/{knowledge-events-b5g6ky4h.js → knowledge-events-9hbwedqb.js} +11 -10
  80. package/dist/cli/{knowledge-link-8tetyje0.js → knowledge-link-vvyfhhmr.js} +5 -4
  81. package/dist/cli/{knowledge-store-1w60j5xm.js → knowledge-store-8t9wy3ab.js} +6 -5
  82. package/dist/cli/{knowledge-validator-sd88rjcy.js → knowledge-validator-g61010aw.js} +8 -7
  83. package/dist/cli/{mcp-3052r65q.js → mcp-tyzj9cwy.js} +1 -1
  84. package/dist/cli/{model-preflight-m0nwfsx8.js → model-preflight-q8pnsjay.js} +48 -42
  85. package/dist/cli/{pending-delegations-5nx22evh.js → pending-delegations-8nah16wj.js} +16 -12
  86. package/dist/cli/{pr-subscriptions-kas44ty9.js → pr-subscriptions-rd3gnc3r.js} +14 -12
  87. package/dist/cli/{pr-workflow-gate-pmggnw1j.js → pr-workflow-gate-1y8by6bt.js} +56 -42
  88. package/dist/cli/{runner-ff0bj9w0.js → runner-cwmgb9rw.js} +6 -5
  89. package/dist/cli/{scan-cursor-yw12hvtc.js → scan-cursor-4s30je6f.js} +7 -6
  90. package/dist/cli/{schema-1g1y1d5q.js → schema-r3v5tb9r.js} +1 -1
  91. package/dist/cli/{scope-persistence-de472fxy.js → scope-persistence-vtz91b5n.js} +17 -15
  92. package/dist/cli/{server-1n00bga4.js → server-j1ybvgn4.js} +62 -54
  93. package/dist/cli/{skill-generator-c5wbbjqx.js → skill-generator-h27wtea0.js} +15 -14
  94. package/dist/cli/{snapshot-coordination-init-gwj594hc.js → snapshot-coordination-init-yntqf46w.js} +48 -42
  95. package/dist/cli/{speckit-checkoff-hc479v4r.js → speckit-checkoff-qxt03eh3.js} +4 -4
  96. package/dist/cli/{telemetry-39zmx4fp.js → telemetry-n6y2sjhg.js} +1 -1
  97. package/dist/cli/{workspace-snapshot-nrbvjh1c.js → workspace-snapshot-gnwak61t.js} +3 -3
  98. package/dist/cli/{worktree-collision-ownership-jvgxxnf9.js → worktree-collision-ownership-0a7jtwwe.js} +18 -16
  99. package/dist/cli/{worktree-isolation-r9hr23r8.js → worktree-isolation-4re648a3.js} +48 -42
  100. package/dist/hooks/pr-workflow-gate.d.ts +59 -1
  101. package/dist/index.js +543 -543
  102. package/dist/plan/auto-checkpoint.d.ts +56 -0
  103. package/dist/plan/manager.d.ts +8 -12
  104. package/dist/tools/dispatch-lanes.d.ts +7 -0
  105. package/dist/tools/submit-pr-review-result.d.ts +17 -0
  106. package/package.json +1 -1
  107. package/dist/cli/curator-llm-factory-1gjzcafw.js +0 -75
  108. package/dist/cli/guardrail-explain-xtyxjmyz.js +0 -76
  109. package/dist/cli/hive-promoter-dsrj2ar2.js +0 -90
@@ -0,0 +1,963 @@
1
+ // @bun
2
+ import {
3
+ MAX_TRANSIENT_RETRIES,
4
+ buildTaskCompletionDescriptor,
5
+ ensureTaskCheckpointReceipt,
6
+ isTransientSpawnError,
7
+ loadPlan,
8
+ transientBackoff,
9
+ updateTaskCheckpointReceipt
10
+ } from "./index-v4ra6hz8.js";
11
+ import {
12
+ tool
13
+ } from "./index-9nyqt3kb.js";
14
+ import {
15
+ GitBinaryMissingError,
16
+ GitSpawnCwdError,
17
+ isGitBinaryMissing,
18
+ isSpawnCwdMissing,
19
+ isSpawnCwdUnreadable
20
+ } from "./index-g4fs6z4e.js";
21
+ import {
22
+ readLedgerEvents
23
+ } from "./index-d2xdwz22.js";
24
+ import {
25
+ appendCoreEventSync
26
+ } from "./index-7rzgdcvc.js";
27
+ import {
28
+ derivePlanId,
29
+ derivePlanIdentityHash
30
+ } from "./index-7pmbnjvj.js";
31
+ import {
32
+ assertProjectRoot,
33
+ hasExplicitProjectBoundary,
34
+ isStrictPathDescendant
35
+ } from "./index-t7hmqgjy.js";
36
+ import {
37
+ tryAcquireLock
38
+ } from "./index-ejhxgyvm.js";
39
+ import {
40
+ loadPluginConfigWithMeta
41
+ } from "./index-djct2zsr.js";
42
+ import {
43
+ init_git_executable,
44
+ resolveGitExecutable
45
+ } from "./index-zxdawwc7.js";
46
+ import {
47
+ exports_external,
48
+ init_zod
49
+ } from "./index-gknz3bv1.js";
50
+
51
+ // src/tools/checkpoint.ts
52
+ init_zod();
53
+ import * as child_process from "child_process";
54
+ import * as fs2 from "fs";
55
+ import * as path2 from "path";
56
+ init_git_executable();
57
+
58
+ // src/tools/create-tool.ts
59
+ init_zod();
60
+
61
+ // src/tools/resolve-working-directory.ts
62
+ import * as fs from "fs";
63
+ import * as path from "path";
64
+ function resolveWorkingDirectory(workingDirectory, fallbackDirectory) {
65
+ if (workingDirectory == null || workingDirectory === "") {
66
+ if (typeof fallbackDirectory !== "string" || fallbackDirectory === "") {
67
+ return {
68
+ success: false,
69
+ message: "Invalid working_directory: no explicit working_directory was provided and fallbackDirectory is missing or not a string"
70
+ };
71
+ }
72
+ try {
73
+ assertProjectRoot(fallbackDirectory);
74
+ } catch (error) {
75
+ return {
76
+ success: false,
77
+ message: error instanceof Error ? error.message : String(error)
78
+ };
79
+ }
80
+ return { success: true, directory: fallbackDirectory };
81
+ }
82
+ if (typeof workingDirectory !== "string") {
83
+ return {
84
+ success: false,
85
+ message: "Invalid working_directory: path must be a string"
86
+ };
87
+ }
88
+ if (workingDirectory.includes("\x00")) {
89
+ return {
90
+ success: false,
91
+ message: "Invalid working_directory: null bytes are not allowed"
92
+ };
93
+ }
94
+ const reservedDevicePattern = /^(NUL|CON|AUX|COM[1-9]|LPT[1-9])(\..*)?$/i;
95
+ if (workingDirectory.startsWith("\\\\") || process.platform === "win32" && reservedDevicePattern.test(workingDirectory)) {
96
+ return {
97
+ success: false,
98
+ message: "Invalid working_directory: Windows device paths are not allowed"
99
+ };
100
+ }
101
+ const rawPathParts = workingDirectory.split(/[\\/]/);
102
+ if (rawPathParts.includes("..")) {
103
+ return {
104
+ success: false,
105
+ message: "Invalid working_directory: path traversal sequences (..) are not allowed"
106
+ };
107
+ }
108
+ const normalizedDir = path.normalize(workingDirectory);
109
+ const resolvedDir = path.resolve(normalizedDir);
110
+ let statResult;
111
+ try {
112
+ statResult = fs.statSync(resolvedDir);
113
+ } catch {
114
+ return {
115
+ success: false,
116
+ message: `Invalid working_directory: path "${resolvedDir}" does not exist or is inaccessible`
117
+ };
118
+ }
119
+ if (!statResult.isDirectory()) {
120
+ return {
121
+ success: false,
122
+ message: `Invalid working_directory: path "${resolvedDir}" is not a directory`
123
+ };
124
+ }
125
+ try {
126
+ assertProjectRoot(resolvedDir);
127
+ } catch (error) {
128
+ return {
129
+ success: false,
130
+ message: error instanceof Error ? error.message : String(error)
131
+ };
132
+ }
133
+ if (typeof fallbackDirectory !== "string" || fallbackDirectory === "") {
134
+ return { success: true, directory: resolvedDir };
135
+ }
136
+ const resolvedFallback = path.resolve(fallbackDirectory);
137
+ let fallbackExists = false;
138
+ try {
139
+ fs.statSync(resolvedFallback);
140
+ fallbackExists = true;
141
+ } catch {
142
+ fallbackExists = false;
143
+ }
144
+ if (fallbackExists) {
145
+ const isSubdirectory = isStrictPathDescendant(resolvedDir, resolvedFallback);
146
+ if (isSubdirectory) {
147
+ if (hasExplicitProjectBoundary(resolvedDir)) {
148
+ return { success: true, directory: resolvedDir };
149
+ }
150
+ return {
151
+ success: false,
152
+ message: `Invalid working_directory: "${workingDirectory}" resolves to "${resolvedDir}" ` + `which is a subdirectory of fallback "${resolvedFallback}". ` + `Pass the project root path or omit working_directory entirely.`
153
+ };
154
+ }
155
+ }
156
+ return { success: true, directory: resolvedDir };
157
+ }
158
+
159
+ // src/tools/create-tool.ts
160
+ function classifyToolError(error) {
161
+ const msg = (error instanceof Error ? error.message ?? "" : String(error)).toLowerCase();
162
+ if (msg.includes("not registered") || msg.includes("unknown tool"))
163
+ return "not_registered";
164
+ if (msg.includes("not whitelisted") || msg.includes("not allowed"))
165
+ return "not_whitelisted";
166
+ if (msg.includes("enoent") || msg.includes("command not found") || msg.includes("binary not found") || msg.includes("no such file or directory"))
167
+ return "binary_missing";
168
+ return "execution_error";
169
+ }
170
+ function createSwarmTool(opts) {
171
+ const ownsWorkingDirectory = Object.hasOwn(opts.args, "working_directory");
172
+ const allowsWorkingDirectoryOverride = opts.allowWorkingDirectoryOverride === true && !ownsWorkingDirectory;
173
+ const toolArgs = allowsWorkingDirectoryOverride ? {
174
+ ...opts.args,
175
+ working_directory: exports_external.string().optional().describe("Project or linked-worktree root for this tool invocation")
176
+ } : opts.args;
177
+ return tool({
178
+ description: opts.description,
179
+ args: toolArgs,
180
+ execute: async (args, ctx) => {
181
+ const fallbackDirectory = ctx?.directory ?? process.cwd();
182
+ try {
183
+ let effectiveArgs = args;
184
+ let effectiveDirectory = fallbackDirectory;
185
+ let effectiveContext = ctx;
186
+ if (!ownsWorkingDirectory && typeof args === "object" && args !== null) {
187
+ if (!allowsWorkingDirectoryOverride && Object.hasOwn(args, "working_directory")) {
188
+ throw new Error("Invalid working_directory: this tool does not allow working_directory overrides");
189
+ }
190
+ if (allowsWorkingDirectoryOverride && Object.hasOwn(args, "working_directory")) {
191
+ const { working_directory: workingDirectory, ...executorArgs } = args;
192
+ effectiveArgs = executorArgs;
193
+ if (workingDirectory !== undefined) {
194
+ const resolved = resolveWorkingDirectory(workingDirectory, fallbackDirectory);
195
+ if (!resolved.success)
196
+ throw new Error(resolved.message);
197
+ effectiveDirectory = resolved.directory;
198
+ effectiveContext = {
199
+ ...ctx,
200
+ directory: resolved.directory,
201
+ worktree: resolved.directory
202
+ };
203
+ }
204
+ }
205
+ }
206
+ const result = await opts.execute(effectiveArgs, effectiveDirectory, effectiveContext);
207
+ return result;
208
+ } catch (error) {
209
+ const message = error instanceof Error ? error.message : String(error);
210
+ return JSON.stringify({
211
+ success: false,
212
+ failure_class: classifyToolError(error),
213
+ message: "Tool execution failed",
214
+ errors: [message]
215
+ }, null, 2);
216
+ }
217
+ }
218
+ });
219
+ }
220
+
221
+ // src/tools/checkpoint.ts
222
+ var CHECKPOINT_LOG_PATH = ".swarm/checkpoints.json";
223
+ var PLAN_LOCK_PATH = "plan.json";
224
+ var CHECKPOINT_AGENT_NAME = "checkpoint";
225
+ var MAX_LABEL_LENGTH = 100;
226
+ var GIT_TIMEOUT_MS = 30000;
227
+ var GIT_MAX_BUFFER_BYTES = 5 * 1024 * 1024;
228
+ var CHECKPOINT_LOCK_MAX_ATTEMPTS = 20;
229
+ var CHECKPOINT_LOCK_DELAY_MS = 250;
230
+ var CHECKPOINT_LOCK_DEADLINE_MS = 45000;
231
+ var CHECKPOINT_RECEIPT_LOOKUP_LIMIT = 25;
232
+ var NON_SWARM_COMMIT_PATHSPEC = [
233
+ ".",
234
+ ":(exclude,top).swarm",
235
+ ":(exclude,top).swarm/**"
236
+ ];
237
+ var SHELL_METACHARACTERS = /[;|&$`(){}<>!'"]/;
238
+ var SAFE_LABEL_PATTERN = /^[a-zA-Z0-9_ -]+$/;
239
+ var CONTROL_CHAR_PATTERN = /[\x00-\x08\x0b\x0c\x0e-\x1f\x7f]/;
240
+ var NON_ASCII_PATTERN = /[^\x20-\x7E]/;
241
+ var _internals = {
242
+ tryAcquireLock,
243
+ loadPlan,
244
+ gitExec,
245
+ sleep: (ms) => new Promise((resolve2) => setTimeout(resolve2, ms)),
246
+ findCommitByExactSubject,
247
+ stageAllExcludingSwarm,
248
+ resolveGitExecutable
249
+ };
250
+ function serializeResult(action, payload) {
251
+ return JSON.stringify({
252
+ action,
253
+ ...payload
254
+ }, null, 2);
255
+ }
256
+ function containsNonAsciiChars(label) {
257
+ for (let i = 0;i < label.length; i++) {
258
+ const charCode = label.charCodeAt(i);
259
+ if (charCode < 32 || charCode > 126) {
260
+ return true;
261
+ }
262
+ }
263
+ return false;
264
+ }
265
+ function validateLabel(label) {
266
+ if (!label || label.length === 0) {
267
+ return "label is required";
268
+ }
269
+ if (label.length > MAX_LABEL_LENGTH) {
270
+ return `label exceeds maximum length of ${MAX_LABEL_LENGTH}`;
271
+ }
272
+ if (label.startsWith("--")) {
273
+ return 'label cannot start with "--" (git flag pattern)';
274
+ }
275
+ if (CONTROL_CHAR_PATTERN.test(label)) {
276
+ return "label contains control characters";
277
+ }
278
+ if (NON_ASCII_PATTERN.test(label)) {
279
+ return "label contains non-ASCII or invalid characters";
280
+ }
281
+ if (containsNonAsciiChars(label)) {
282
+ return "label contains non-ASCII characters (must be printable ASCII only)";
283
+ }
284
+ if (SHELL_METACHARACTERS.test(label)) {
285
+ return "label contains shell metacharacters";
286
+ }
287
+ if (!SAFE_LABEL_PATTERN.test(label)) {
288
+ return "label contains invalid characters (use alphanumeric, hyphen, underscore, space)";
289
+ }
290
+ if (!/[a-zA-Z0-9_]/.test(label)) {
291
+ return "label cannot be whitespace-only";
292
+ }
293
+ if (label.includes("..") || label.includes("/") || label.includes("\\")) {
294
+ return "label contains path traversal sequence";
295
+ }
296
+ return null;
297
+ }
298
+ function getCheckpointLogPath(directory) {
299
+ return path2.join(directory, CHECKPOINT_LOG_PATH);
300
+ }
301
+ function readCheckpointLog(directory) {
302
+ const logPath = getCheckpointLogPath(directory);
303
+ try {
304
+ if (fs2.existsSync(logPath)) {
305
+ const content = fs2.readFileSync(logPath, "utf-8");
306
+ const parsed = JSON.parse(content);
307
+ if (!parsed.checkpoints || !Array.isArray(parsed.checkpoints)) {
308
+ return { version: 1, checkpoints: [] };
309
+ }
310
+ return parsed;
311
+ }
312
+ } catch {}
313
+ return { version: 1, checkpoints: [] };
314
+ }
315
+ function writeCheckpointLog(log, directory) {
316
+ const logPath = getCheckpointLogPath(directory);
317
+ const dir = path2.dirname(logPath);
318
+ if (!fs2.existsSync(dir)) {
319
+ fs2.mkdirSync(dir, { recursive: true });
320
+ }
321
+ const tempPath = `${logPath}.tmp`;
322
+ fs2.writeFileSync(tempPath, JSON.stringify(log, null, 2), "utf-8");
323
+ fs2.renameSync(tempPath, logPath);
324
+ }
325
+ function appendRetentionEvent(directory, event) {
326
+ try {
327
+ appendCoreEventSync(directory, {
328
+ ...event,
329
+ timestamp: new Date().toISOString()
330
+ });
331
+ } catch {}
332
+ }
333
+ function loadCheckpointConfig(directory) {
334
+ let maxCheckpoints = 20;
335
+ let allowEmptyCommits = false;
336
+ try {
337
+ const { config } = loadPluginConfigWithMeta(directory);
338
+ maxCheckpoints = config.checkpoint?.max_retention ?? maxCheckpoints;
339
+ allowEmptyCommits = config.checkpoint?.allow_empty_commits === true;
340
+ } catch {}
341
+ return { maxCheckpoints, allowEmptyCommits };
342
+ }
343
+ function appendCheckpointEntry(directory, log, entry, maxCheckpoints) {
344
+ const existing = log.checkpoints.find((checkpoint) => checkpoint.label === entry.label);
345
+ if (existing) {
346
+ if (existing.sha !== entry.sha) {
347
+ throw new Error(`checkpoint log conflict: label "${entry.label}" already exists with a different SHA`);
348
+ }
349
+ return;
350
+ }
351
+ log.checkpoints.push(entry);
352
+ if (log.checkpoints.length > maxCheckpoints) {
353
+ const evicted = log.checkpoints.splice(0, log.checkpoints.length - maxCheckpoints);
354
+ appendRetentionEvent(directory, {
355
+ event: "checkpoint_retention_applied",
356
+ evicted_labels: evicted.map((checkpoint) => checkpoint.label),
357
+ evicted_count: evicted.length,
358
+ remaining_count: log.checkpoints.length
359
+ });
360
+ }
361
+ }
362
+ function upsertTaskCompletionCheckpointEntry(directory, log, entry, maxCheckpoints) {
363
+ const existingIndex = log.checkpoints.findIndex((checkpoint) => checkpoint.label === entry.label);
364
+ if (existingIndex >= 0) {
365
+ const existing = log.checkpoints[existingIndex];
366
+ if (existing.sha === entry.sha) {
367
+ return;
368
+ }
369
+ log.checkpoints[existingIndex] = entry;
370
+ return;
371
+ }
372
+ log.checkpoints.push(entry);
373
+ if (log.checkpoints.length > maxCheckpoints) {
374
+ const evicted = log.checkpoints.splice(0, log.checkpoints.length - maxCheckpoints);
375
+ appendRetentionEvent(directory, {
376
+ event: "checkpoint_retention_applied",
377
+ evicted_labels: evicted.map((checkpoint) => checkpoint.label),
378
+ evicted_count: evicted.length,
379
+ remaining_count: log.checkpoints.length
380
+ });
381
+ }
382
+ }
383
+ function stageAllExcludingSwarm(directory) {
384
+ _internals.gitExec(["add", "--all", "--", ...NON_SWARM_COMMIT_PATHSPEC], directory);
385
+ }
386
+ function gitExec(args, cwd) {
387
+ for (let attempt = 0;attempt < MAX_TRANSIENT_RETRIES; attempt++) {
388
+ const result = child_process.spawnSync(_internals.resolveGitExecutable(), args, {
389
+ cwd,
390
+ encoding: "utf-8",
391
+ timeout: GIT_TIMEOUT_MS,
392
+ stdio: ["ignore", "pipe", "pipe"],
393
+ windowsHide: true,
394
+ maxBuffer: GIT_MAX_BUFFER_BYTES
395
+ });
396
+ if (result.error) {
397
+ const code = result.error.code;
398
+ const message = result.error.message ?? "";
399
+ const isTransient = isTransientSpawnError(result.error) || /ETIMEDOUT|timed out/i.test(message);
400
+ if (!isTransient || attempt >= MAX_TRANSIENT_RETRIES - 1) {
401
+ if (isSpawnCwdMissing(result.error, cwd)) {
402
+ throw new GitSpawnCwdError(cwd, "missing");
403
+ }
404
+ if (isSpawnCwdUnreadable(result.error, cwd)) {
405
+ throw new GitSpawnCwdError(cwd, "unreadable");
406
+ }
407
+ if (isGitBinaryMissing(result.error, cwd)) {
408
+ throw new GitBinaryMissingError(`git executable is not available on PATH (working directory ${cwd} exists)`, { cause: result.error });
409
+ }
410
+ throw new Error(`git failed to start: ${code ?? "unknown"} - ${message}`);
411
+ }
412
+ transientBackoff(attempt);
413
+ continue;
414
+ }
415
+ if (result.status === 0) {
416
+ return result.stdout ?? "";
417
+ }
418
+ throw new Error(result.stderr?.trim() || `git exited with code ${result.status}`);
419
+ }
420
+ throw new Error("git command failed after transient retries");
421
+ }
422
+ function getCurrentSha(directory) {
423
+ return gitExec(["rev-parse", "HEAD"], directory).trim();
424
+ }
425
+ function isGitRepo(directory) {
426
+ try {
427
+ gitExec(["rev-parse", "--git-dir"], directory);
428
+ return { isRepo: true };
429
+ } catch (e) {
430
+ const message = e instanceof Error ? e.message : String(e);
431
+ const isTransient = /ETIMEDOUT|timed out/i.test(message) && !/not a git repository/i.test(message);
432
+ if (isTransient) {
433
+ return {
434
+ isRepo: false,
435
+ warning: "git probe failed after retry exhaustion - treating as not a git repository"
436
+ };
437
+ }
438
+ return {
439
+ isRepo: false,
440
+ warning: "git probe failed - directory may not be a git repository"
441
+ };
442
+ }
443
+ }
444
+ function findCommitByExactSubject(directory, subject) {
445
+ const output = _internals.gitExec([
446
+ "log",
447
+ "HEAD",
448
+ "--format=%H%x00%s",
449
+ "--fixed-strings",
450
+ `--grep=${subject}`,
451
+ "-n",
452
+ String(CHECKPOINT_RECEIPT_LOOKUP_LIMIT)
453
+ ], directory);
454
+ for (const line of output.split(/\r?\n/)) {
455
+ if (!line)
456
+ continue;
457
+ const nulIndex = line.indexOf("\x00");
458
+ if (nulIndex <= 0)
459
+ continue;
460
+ const sha = line.slice(0, nulIndex).trim();
461
+ const candidateSubject = line.slice(nulIndex + 1);
462
+ if (sha.length === 40 && /^[a-f0-9]{40}$/i.test(sha) && candidateSubject === subject) {
463
+ return sha;
464
+ }
465
+ }
466
+ return null;
467
+ }
468
+ async function withTaskCompletionLocksResult(input) {
469
+ const deadline = Date.now() + CHECKPOINT_LOCK_DEADLINE_MS;
470
+ let attempts = 0;
471
+ while (attempts < CHECKPOINT_LOCK_MAX_ATTEMPTS && Date.now() <= deadline) {
472
+ attempts++;
473
+ let planLockResult;
474
+ try {
475
+ planLockResult = await _internals.tryAcquireLock(input.directory, PLAN_LOCK_PATH, CHECKPOINT_AGENT_NAME, `save-task-completion-plan-${input.taskId}`);
476
+ } catch (error) {
477
+ if (input.onFailure) {
478
+ return input.onFailure(error);
479
+ }
480
+ return serializeResult("save_task_completion", {
481
+ success: false,
482
+ error: `save_task_completion failed: ${error instanceof Error ? error.message : String(error)}`
483
+ });
484
+ }
485
+ if (!planLockResult.acquired) {
486
+ if (attempts >= CHECKPOINT_LOCK_MAX_ATTEMPTS || Date.now() + CHECKPOINT_LOCK_DELAY_MS > deadline) {
487
+ break;
488
+ }
489
+ await _internals.sleep(CHECKPOINT_LOCK_DELAY_MS);
490
+ continue;
491
+ }
492
+ try {
493
+ let checkpointLockResult;
494
+ try {
495
+ checkpointLockResult = await _internals.tryAcquireLock(input.directory, CHECKPOINT_LOG_PATH, CHECKPOINT_AGENT_NAME, `save-task-completion-checkpoint-${input.taskId}`);
496
+ } catch (error) {
497
+ if (input.onFailure) {
498
+ return input.onFailure(error);
499
+ }
500
+ return serializeResult("save_task_completion", {
501
+ success: false,
502
+ error: `save_task_completion failed: ${error instanceof Error ? error.message : String(error)}`
503
+ });
504
+ }
505
+ if (!checkpointLockResult.acquired) {
506
+ if (attempts >= CHECKPOINT_LOCK_MAX_ATTEMPTS || Date.now() + CHECKPOINT_LOCK_DELAY_MS > deadline) {
507
+ break;
508
+ }
509
+ await _internals.sleep(CHECKPOINT_LOCK_DELAY_MS);
510
+ continue;
511
+ }
512
+ try {
513
+ return await input.run();
514
+ } catch (error) {
515
+ if (input.onFailure) {
516
+ return input.onFailure(error);
517
+ }
518
+ return serializeResult("save_task_completion", {
519
+ success: false,
520
+ error: `save_task_completion failed: ${error instanceof Error ? error.message : String(error)}`
521
+ });
522
+ } finally {
523
+ if (checkpointLockResult.lock._release) {
524
+ try {
525
+ await checkpointLockResult.lock._release();
526
+ } catch {}
527
+ }
528
+ }
529
+ } finally {
530
+ if (planLockResult.lock._release) {
531
+ try {
532
+ await planLockResult.lock._release();
533
+ } catch {}
534
+ }
535
+ }
536
+ }
537
+ if (input.onBusy) {
538
+ return input.onBusy(attempts);
539
+ }
540
+ return serializeResult("save_task_completion", {
541
+ success: false,
542
+ status: "checkpoint_busy",
543
+ error: "checkpoint_busy: checkpoint or plan state is locked by another operation; retry after the current mutation completes",
544
+ retryable: true,
545
+ attempts
546
+ });
547
+ }
548
+ async function withCheckpointMutationLock(input) {
549
+ const result = await withCheckpointMutationLockResult({
550
+ action: input.action,
551
+ directory: input.directory,
552
+ operationKey: input.operationKey,
553
+ run: input.run,
554
+ onContendedAttempt: input.onContendedAttempt
555
+ });
556
+ return result;
557
+ }
558
+ async function withCheckpointMutationLockResult(input) {
559
+ const deadline = Date.now() + CHECKPOINT_LOCK_DEADLINE_MS;
560
+ let attempts = 0;
561
+ while (attempts < CHECKPOINT_LOCK_MAX_ATTEMPTS && Date.now() <= deadline) {
562
+ attempts++;
563
+ let lockResult;
564
+ try {
565
+ lockResult = await _internals.tryAcquireLock(input.directory, CHECKPOINT_LOG_PATH, CHECKPOINT_AGENT_NAME, input.operationKey);
566
+ } catch (error) {
567
+ if (input.onFailure) {
568
+ return input.onFailure(error);
569
+ }
570
+ return serializeResult(input.action, {
571
+ success: false,
572
+ error: `${input.action} failed: ${error instanceof Error ? error.message : String(error)}`
573
+ });
574
+ }
575
+ if (lockResult.acquired) {
576
+ try {
577
+ try {
578
+ return await input.run();
579
+ } catch (error) {
580
+ if (input.onFailure) {
581
+ return input.onFailure(error);
582
+ }
583
+ return serializeResult(input.action, {
584
+ success: false,
585
+ error: `${input.action} failed: ${error instanceof Error ? error.message : String(error)}`
586
+ });
587
+ }
588
+ } finally {
589
+ if (lockResult.lock._release) {
590
+ try {
591
+ await lockResult.lock._release();
592
+ } catch {}
593
+ }
594
+ }
595
+ }
596
+ const contended = input.onContendedAttempt?.();
597
+ if (contended)
598
+ return contended;
599
+ if (attempts >= CHECKPOINT_LOCK_MAX_ATTEMPTS || Date.now() + CHECKPOINT_LOCK_DELAY_MS > deadline) {
600
+ break;
601
+ }
602
+ await _internals.sleep(CHECKPOINT_LOCK_DELAY_MS);
603
+ }
604
+ if (input.onBusy) {
605
+ return input.onBusy(attempts);
606
+ }
607
+ return serializeResult(input.action, {
608
+ success: false,
609
+ status: "checkpoint_busy",
610
+ error: "checkpoint_busy: checkpoint state is locked by another operation; retry after the current mutation completes",
611
+ retryable: true,
612
+ attempts
613
+ });
614
+ }
615
+ function handleSave(label, directory) {
616
+ try {
617
+ const { maxCheckpoints, allowEmptyCommits } = loadCheckpointConfig(directory);
618
+ const log = readCheckpointLog(directory);
619
+ const existingCheckpoint = log.checkpoints.find((checkpoint) => checkpoint.label === label);
620
+ if (existingCheckpoint) {
621
+ return serializeResult("save", {
622
+ success: false,
623
+ error: `duplicate label: "${label}" already exists. Use a different label or delete the existing checkpoint first.`
624
+ });
625
+ }
626
+ const timestamp = new Date().toISOString();
627
+ _internals.stageAllExcludingSwarm(directory);
628
+ const hasStagedChanges = (() => {
629
+ try {
630
+ gitExec(["diff", "--cached", "--quiet"], directory);
631
+ return false;
632
+ } catch {
633
+ return true;
634
+ }
635
+ })();
636
+ if (hasStagedChanges) {
637
+ gitExec(["commit", "-m", `checkpoint: ${label}`], directory);
638
+ } else if (allowEmptyCommits) {
639
+ gitExec(["commit", "--allow-empty", "-m", `checkpoint: ${label}`], directory);
640
+ }
641
+ const newSha = getCurrentSha(directory);
642
+ appendCheckpointEntry(directory, log, {
643
+ label,
644
+ sha: newSha,
645
+ timestamp
646
+ }, maxCheckpoints);
647
+ writeCheckpointLog(log, directory);
648
+ return serializeResult("save", {
649
+ success: true,
650
+ label,
651
+ sha: newSha,
652
+ message: `Checkpoint saved: "${label}"`
653
+ });
654
+ } catch (e) {
655
+ return serializeResult("save", {
656
+ success: false,
657
+ error: e instanceof Error ? `save failed: ${e.message}` : "save failed: unknown error"
658
+ });
659
+ }
660
+ }
661
+ async function saveCheckpointRecord(label, directory) {
662
+ const labelError = validateLabel(label);
663
+ if (labelError) {
664
+ return { success: false, error: labelError };
665
+ }
666
+ return withCheckpointMutationLockResult({
667
+ action: "save",
668
+ directory,
669
+ operationKey: `save-record-${label}`,
670
+ run: async () => {
671
+ const log = readCheckpointLog(directory);
672
+ if (log.checkpoints.find((checkpoint) => checkpoint.label === label)) {
673
+ return { success: false, error: `duplicate label: "${label}"` };
674
+ }
675
+ let sha = "";
676
+ const repoProbe = isGitRepo(directory);
677
+ if (repoProbe.isRepo) {
678
+ try {
679
+ sha = getCurrentSha(directory);
680
+ } catch {
681
+ sha = "";
682
+ }
683
+ }
684
+ appendCheckpointEntry(directory, log, {
685
+ label,
686
+ sha,
687
+ timestamp: new Date().toISOString()
688
+ }, loadCheckpointConfig(directory).maxCheckpoints);
689
+ writeCheckpointLog(log, directory);
690
+ const result = { success: true, sha };
691
+ if (!sha) {
692
+ result.warning = "no git restore target - checkpoint recorded without a SHA (directory may not be a git repository or HEAD is unavailable)";
693
+ }
694
+ return result;
695
+ },
696
+ onBusy: () => ({
697
+ success: false,
698
+ error: "checkpoint_busy: checkpoint state is locked by another operation; retry after the current mutation completes"
699
+ }),
700
+ onFailure: (error) => ({
701
+ success: false,
702
+ error: error instanceof Error ? error.message : "unknown error"
703
+ })
704
+ });
705
+ }
706
+ function handleRestore(label, directory) {
707
+ try {
708
+ const log = readCheckpointLog(directory);
709
+ const checkpoint = log.checkpoints.find((candidate) => candidate.label === label);
710
+ if (!checkpoint) {
711
+ return serializeResult("restore", {
712
+ success: false,
713
+ error: `checkpoint not found: "${label}"`
714
+ });
715
+ }
716
+ const logBeforeReset = log;
717
+ gitExec(["reset", "--hard", checkpoint.sha], directory);
718
+ writeCheckpointLog(logBeforeReset, directory);
719
+ return serializeResult("restore", {
720
+ success: true,
721
+ label,
722
+ sha: checkpoint.sha,
723
+ message: `Restored to checkpoint: "${label}" (hard reset)`
724
+ });
725
+ } catch (e) {
726
+ return serializeResult("restore", {
727
+ success: false,
728
+ error: e instanceof Error ? `restore failed: ${e.message}` : "restore failed: unknown error"
729
+ });
730
+ }
731
+ }
732
+ function handleList(directory) {
733
+ const log = readCheckpointLog(directory);
734
+ const sorted = [...log.checkpoints].sort((a, b) => b.timestamp.localeCompare(a.timestamp));
735
+ return serializeResult("list", {
736
+ success: true,
737
+ count: sorted.length,
738
+ checkpoints: sorted
739
+ });
740
+ }
741
+ function handleDelete(label, directory) {
742
+ try {
743
+ const log = readCheckpointLog(directory);
744
+ const initialLength = log.checkpoints.length;
745
+ log.checkpoints = log.checkpoints.filter((checkpoint) => checkpoint.label !== label);
746
+ if (log.checkpoints.length === initialLength) {
747
+ return serializeResult("delete", {
748
+ success: false,
749
+ error: `checkpoint not found: "${label}"`
750
+ });
751
+ }
752
+ writeCheckpointLog(log, directory);
753
+ return serializeResult("delete", {
754
+ success: true,
755
+ label,
756
+ message: `Checkpoint deleted: "${label}"`
757
+ });
758
+ } catch (e) {
759
+ return serializeResult("delete", {
760
+ success: false,
761
+ error: e instanceof Error ? `delete failed: ${e.message}` : "delete failed: unknown error"
762
+ });
763
+ }
764
+ }
765
+ async function handleSaveTaskCompletion(taskId, directory) {
766
+ return withTaskCompletionLocksResult({
767
+ directory,
768
+ taskId,
769
+ run: async () => {
770
+ const plan = await _internals.loadPlan(directory);
771
+ if (!plan) {
772
+ return serializeResult("save_task_completion", {
773
+ success: false,
774
+ error: "save_task_completion failed: no durable plan is available under .swarm/plan.json"
775
+ });
776
+ }
777
+ const task = plan.phases.flatMap((phase) => phase.tasks).find((candidate) => candidate.id === taskId);
778
+ if (!task) {
779
+ return serializeResult("save_task_completion", {
780
+ success: false,
781
+ error: `save_task_completion failed: task "${taskId}" was not found in the current plan`
782
+ });
783
+ }
784
+ if (task.status !== "completed") {
785
+ return serializeResult("save_task_completion", {
786
+ success: false,
787
+ error: `save_task_completion failed: task "${taskId}" is ${task.status}, not completed`
788
+ });
789
+ }
790
+ await _internals.afterTaskEligibilityRead?.(directory, task.id);
791
+ const planIdentityHash = derivePlanIdentityHash(plan);
792
+ const planId = derivePlanId(plan);
793
+ const ledgerEvents = await readLedgerEvents(directory);
794
+ const planEvents = ledgerEvents.filter((event) => event.plan_id === planId);
795
+ const anchor = planEvents.find((event) => event.event_type === "plan_created");
796
+ const anchorPlan = anchor?.payload?.plan;
797
+ if (anchorPlan && typeof anchorPlan === "object") {
798
+ const rawIdentity = anchorPlan;
799
+ if (typeof rawIdentity.swarm !== "string" || typeof rawIdentity.title !== "string" || derivePlanIdentityHash({
800
+ swarm: rawIdentity.swarm,
801
+ title: rawIdentity.title
802
+ }) !== planIdentityHash) {
803
+ throw new Error(`save_task_completion failed: ledger identity does not match the current plan for task "${task.id}"`);
804
+ }
805
+ }
806
+ const latestTaskStatusEvent = [...planEvents].reverse().find((event) => event.event_type === "task_status_changed" && event.task_id === task.id);
807
+ if (latestTaskStatusEvent && latestTaskStatusEvent.to_status !== "completed") {
808
+ throw new Error(`save_task_completion failed: the authoritative ledger does not record task "${task.id}" as completed`);
809
+ }
810
+ const completionLedgerSeq = latestTaskStatusEvent?.seq ?? anchor?.seq ?? 0;
811
+ const receipt = ensureTaskCheckpointReceipt(directory, planIdentityHash, task.id, completionLedgerSeq);
812
+ const descriptor = buildTaskCompletionDescriptor(planIdentityHash, task.id, receipt.generation);
813
+ if (receipt.label !== descriptor.label) {
814
+ throw new Error(`Task checkpoint receipt label conflict for ${task.id}: expected ${descriptor.label}, found ${receipt.label}`);
815
+ }
816
+ if (receipt.state === "logged") {
817
+ return serializeResult("save_task_completion", {
818
+ success: true,
819
+ idempotent: true,
820
+ label: receipt.label,
821
+ sha: receipt.sha,
822
+ receipt_state: receipt.state,
823
+ message: `Checkpoint task completion already logged for "${descriptor.taskId}"`
824
+ });
825
+ }
826
+ const recoveredSha = _internals.findCommitByExactSubject(directory, descriptor.subject);
827
+ let committedSha = null;
828
+ committedSha = recoveredSha ?? receipt.sha;
829
+ if (!committedSha) {
830
+ _internals.stageAllExcludingSwarm(directory);
831
+ _internals.gitExec([
832
+ "commit",
833
+ "--allow-empty",
834
+ "-m",
835
+ descriptor.subject,
836
+ "--",
837
+ ...NON_SWARM_COMMIT_PATHSPEC
838
+ ], directory);
839
+ committedSha = getCurrentSha(directory);
840
+ }
841
+ if (!committedSha) {
842
+ throw new Error(`save_task_completion failed: could not resolve commit SHA for task "${descriptor.taskId}"`);
843
+ }
844
+ updateTaskCheckpointReceipt(directory, descriptor, "committed", committedSha);
845
+ const log = readCheckpointLog(directory);
846
+ upsertTaskCompletionCheckpointEntry(directory, log, {
847
+ label: descriptor.label,
848
+ sha: committedSha,
849
+ timestamp: new Date().toISOString()
850
+ }, loadCheckpointConfig(directory).maxCheckpoints);
851
+ writeCheckpointLog(log, directory);
852
+ const logged = updateTaskCheckpointReceipt(directory, descriptor, "logged", committedSha);
853
+ return serializeResult("save_task_completion", {
854
+ success: true,
855
+ label: logged.label,
856
+ sha: committedSha,
857
+ receipt_state: logged.state,
858
+ message: `Checkpoint task completion saved for "${descriptor.taskId}"`
859
+ });
860
+ }
861
+ });
862
+ }
863
+ var checkpoint = createSwarmTool({
864
+ allowWorkingDirectoryOverride: true,
865
+ description: "Save, restore, list, and delete git checkpoints. " + "Use save to create a named snapshot, restore to return tracked files to a checkpoint, " + "list to see all checkpoints, delete to remove a checkpoint from the log, and save_task_completion to record a retry-safe task completion checkpoint. " + "Git commits are preserved on delete.",
866
+ args: {
867
+ action: exports_external.string().describe("Action to perform: save, restore, list, delete, or save_task_completion"),
868
+ label: exports_external.string().optional().describe("Checkpoint label (required for save, restore, delete)"),
869
+ task_id: exports_external.string().optional().describe('Plan task ID (required for save_task_completion), e.g. "1.1"')
870
+ },
871
+ execute: async (args, directory) => {
872
+ const repoProbe = isGitRepo(directory);
873
+ if (!repoProbe.isRepo) {
874
+ return serializeResult("unknown", {
875
+ success: false,
876
+ error: `${repoProbe.warning ?? "not a git repository"} - checkpoint tools require a git repository`
877
+ });
878
+ }
879
+ let action;
880
+ let label;
881
+ let taskId;
882
+ try {
883
+ action = String(args.action);
884
+ label = args.label !== undefined && args.label !== null ? String(args.label) : undefined;
885
+ taskId = args.task_id !== undefined && args.task_id !== null ? String(args.task_id) : undefined;
886
+ } catch {
887
+ return serializeResult("unknown", {
888
+ success: false,
889
+ error: "invalid arguments"
890
+ });
891
+ }
892
+ const validActions = [
893
+ "save",
894
+ "restore",
895
+ "list",
896
+ "delete",
897
+ "save_task_completion"
898
+ ];
899
+ if (!validActions.includes(action)) {
900
+ return serializeResult("unknown", {
901
+ success: false,
902
+ error: `invalid action: "${action}". Valid actions: ${validActions.join(", ")}`
903
+ });
904
+ }
905
+ if (["save", "restore", "delete"].includes(action)) {
906
+ if (!label) {
907
+ return serializeResult(action, {
908
+ success: false,
909
+ error: `label is required for ${action} action`
910
+ });
911
+ }
912
+ const labelError = validateLabel(label);
913
+ if (labelError) {
914
+ return serializeResult(action, {
915
+ success: false,
916
+ error: `invalid label: ${labelError}`
917
+ });
918
+ }
919
+ }
920
+ if (action === "save_task_completion") {
921
+ if (!taskId || taskId.trim().length === 0) {
922
+ return serializeResult("save_task_completion", {
923
+ success: false,
924
+ error: "task_id is required for save_task_completion action"
925
+ });
926
+ }
927
+ }
928
+ switch (action) {
929
+ case "save":
930
+ return withCheckpointMutationLock({
931
+ action: "save",
932
+ directory,
933
+ operationKey: `save-${label}`,
934
+ run: async () => handleSave(label, directory)
935
+ });
936
+ case "restore":
937
+ return withCheckpointMutationLock({
938
+ action: "restore",
939
+ directory,
940
+ operationKey: `restore-${label}`,
941
+ run: async () => handleRestore(label, directory)
942
+ });
943
+ case "list":
944
+ return handleList(directory);
945
+ case "delete":
946
+ return withCheckpointMutationLock({
947
+ action: "delete",
948
+ directory,
949
+ operationKey: `delete-${label}`,
950
+ run: async () => handleDelete(label, directory)
951
+ });
952
+ case "save_task_completion":
953
+ return handleSaveTaskCompletion(taskId, directory);
954
+ default:
955
+ return serializeResult("unknown", {
956
+ success: false,
957
+ error: "unreachable"
958
+ });
959
+ }
960
+ }
961
+ });
962
+
963
+ export { resolveWorkingDirectory, createSwarmTool, _internals, saveCheckpointRecord, checkpoint };