opencode-swarm 7.179.1 → 7.180.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (115) hide show
  1. package/binaries/win32-arm64/swarm-sandbox-runner.exe +0 -0
  2. package/binaries/win32-x64/swarm-sandbox-runner.exe +0 -0
  3. package/dist/agents/architect.d.ts +67 -0
  4. package/dist/agents/council-prompts.d.ts +3 -3
  5. package/dist/agents/index.d.ts +1 -1
  6. package/dist/background/pr-event-subscribers.d.ts +2 -0
  7. package/dist/background/pr-feedback-event-queue.d.ts +7 -0
  8. package/dist/background/pr-feedback-loop.d.ts +162 -0
  9. package/dist/cli/{coder-settlement-f1nmezzv.js → coder-settlement-b57917a3.js} +14 -14
  10. package/dist/cli/{config-doctor-ena2803f.js → config-doctor-ebat06eq.js} +2 -2
  11. package/dist/cli/{core-fcfzme3m.js → core-8k41qh0d.js} +4 -5
  12. package/dist/cli/{curation-policy-c7mdsf7m.js → curation-policy-m3x9mkj9.js} +7 -6
  13. package/dist/cli/{curator-q9s5q9v4.js → curator-8yb3qkxs.js} +45 -43
  14. package/dist/cli/{curator-drift-d93pwt8h.js → curator-drift-3av9hffm.js} +22 -12
  15. package/dist/cli/curator-llm-factory-htgqafq0.js +74 -0
  16. package/dist/cli/{evidence-summary-service-wtj8mkm6.js → evidence-summary-service-s258ea5t.js} +23 -21
  17. package/dist/cli/{gate-evidence-h83yb8nd.js → gate-evidence-fqxpf3xz.js} +9 -9
  18. package/dist/cli/guardrail-explain-r0qxa4zy.js +75 -0
  19. package/dist/cli/{guardrail-log-zf1htf1d.js → guardrail-log-j2wdmvmy.js} +7 -7
  20. package/dist/cli/{guardrail-reset-f2p198j3.js → guardrail-reset-wb2eqbvf.js} +45 -43
  21. package/dist/cli/hive-promoter-kxpc3r17.js +89 -0
  22. package/dist/cli/{index-wrt875kh.js → index-0725jpp7.js} +1 -1
  23. package/dist/cli/{index-34bwqcea.js → index-12ewkms0.js} +1 -1
  24. package/dist/cli/{index-epfjc1c1.js → index-2q5m4m2r.js} +11 -11
  25. package/dist/cli/{index-h59vsbch.js → index-38cfx8g8.js} +2 -2
  26. package/dist/cli/{index-xy7k7f3k.js → index-3bbjpbmn.js} +2 -2
  27. package/dist/cli/{index-vp9pn7sv.js → index-3zvp7t5d.js} +5 -5
  28. package/dist/cli/{index-dz12bshh.js → index-50v672wd.js} +8 -8
  29. package/dist/cli/{index-yfpswnjt.js → index-516j9vsk.js} +4 -4
  30. package/dist/cli/{index-w4c2repz.js → index-600aqfvm.js} +47 -43
  31. package/dist/cli/{index-0h3cr7pn.js → index-6gcxgx84.js} +1 -1
  32. package/dist/cli/{index-qt0793qm.js → index-6mwha20t.js} +3 -3
  33. package/dist/cli/{index-93nx95me.js → index-855qxtdm.js} +8 -7
  34. package/dist/cli/{index-cvg402ha.js → index-91dhg3wr.js} +2 -2
  35. package/dist/cli/{index-3pzwsesm.js → index-92m05mew.js} +2 -2
  36. package/dist/cli/{index-ne9zamcs.js → index-989v83pb.js} +1 -1
  37. package/dist/cli/{index-9fb01jk5.js → index-as4xaams.js} +1 -1
  38. package/dist/cli/{index-2b0ktpbf.js → index-bdpgfep1.js} +5 -5
  39. package/dist/cli/{index-q375xdcw.js → index-bhmrgbyv.js} +9 -1
  40. package/dist/cli/{index-nzekzzs2.js → index-bv4b8tpa.js} +5 -5
  41. package/dist/cli/{index-rb9jmj30.js → index-cj64vp08.js} +7 -7
  42. package/dist/cli/{index-549wxbe1.js → index-cjapb4hy.js} +1 -1
  43. package/dist/cli/{index-5d224akk.js → index-dzm5nfh7.js} +4 -4
  44. package/dist/cli/{index-f2v90gfc.js → index-ea9fk6v0.js} +25 -166
  45. package/dist/cli/index-ejhxgyvm.js +147 -0
  46. package/dist/cli/{index-n1tcj1q7.js → index-g062wnw1.js} +187 -47
  47. package/dist/cli/{index-s4bkey0q.js → index-gc6h1fmf.js} +4 -4
  48. package/dist/cli/{index-pn023bf9.js → index-gw5egd8j.js} +49 -47
  49. package/dist/cli/index-gx2vp8gx.js +100 -0
  50. package/dist/cli/{index-yp25bc9n.js → index-gy3tcmk4.js} +7 -7
  51. package/dist/cli/{index-n2wjrgcs.js → index-k5vwnnym.js} +7 -9
  52. package/dist/cli/{index-ajm7ebyd.js → index-k6f0nryj.js} +2 -2
  53. package/dist/cli/{index-zbhvr9st.js → index-n5carmk0.js} +8 -10
  54. package/dist/cli/{index-h4nh6s8b.js → index-nhxykfyy.js} +1 -1
  55. package/dist/cli/{index-pskba939.js → index-rejpbgxp.js} +1 -1
  56. package/dist/cli/{index-9s1xzvnn.js → index-rp4akjrm.js} +13 -3
  57. package/dist/cli/{index-g6nyk713.js → index-s0zgz4p1.js} +1 -1
  58. package/dist/cli/{index-aqpwm350.js → index-tnhk72rs.js} +3 -3
  59. package/dist/cli/{index-sj29n50e.js → index-v0par978.js} +7 -7
  60. package/dist/cli/index-v7fx8q3z.js +339 -0
  61. package/dist/cli/{index-a11ztzs5.js → index-vmthakz0.js} +1 -1
  62. package/dist/cli/{index-b567t0cr.js → index-wx3wz7ya.js} +10 -8
  63. package/dist/cli/{index-y98fvmb8.js → index-xd0e4amf.js} +33 -18
  64. package/dist/cli/{index-m1w40kfw.js → index-xj5z92qz.js} +4 -103
  65. package/dist/cli/{index-6r92fw88.js → index-y30y1rkg.js} +9 -9
  66. package/dist/cli/{index-pfg3jwe9.js → index-yf7tdhpz.js} +3 -3
  67. package/dist/cli/{index-y94vwx2q.js → index-z2xf153n.js} +13 -15
  68. package/dist/cli/{index-ky262b73.js → index-z2zrcecq.js} +1721 -902
  69. package/dist/cli/{index-zz7gzw57.js → index-zxdawwc7.js} +88 -4
  70. package/dist/cli/index.js +46 -44
  71. package/dist/cli/{knowledge-escalator-z6tkgrjq.js → knowledge-escalator-n30h6ran.js} +17 -16
  72. package/dist/cli/{knowledge-events-2bgefatz.js → knowledge-events-k2qqase2.js} +15 -14
  73. package/dist/cli/{knowledge-link-gfz47ves.js → knowledge-link-s2qs178z.js} +6 -5
  74. package/dist/cli/{knowledge-store-1fphg9sa.js → knowledge-store-zy226v5a.js} +7 -6
  75. package/dist/cli/{knowledge-validator-723h2vck.js → knowledge-validator-qgyxdpc9.js} +9 -8
  76. package/dist/cli/{mcp-xyswgya5.js → mcp-pj7acze3.js} +1 -1
  77. package/dist/cli/{pending-delegations-5zm19yvv.js → pending-delegations-sgnnyw55.js} +14 -13
  78. package/dist/cli/{pr-subscriptions-5hm6hzv6.js → pr-subscriptions-7fb7xm3q.js} +14 -13
  79. package/dist/cli/{pr-workflow-gate-48rzb2bh.js → pr-workflow-gate-5dqcrkz6.js} +47 -43
  80. package/dist/cli/{runner-16b691ba.js → runner-p9hstpxn.js} +7 -6
  81. package/dist/cli/{scan-cursor-m28tddrg.js → scan-cursor-jc7wwb7e.js} +9 -9
  82. package/dist/cli/{schema-jt9axyj6.js → schema-5k68crjb.js} +3 -1
  83. package/dist/cli/{scope-persistence-d0358pw7.js → scope-persistence-xeg4khwk.js} +19 -18
  84. package/dist/cli/{server-d5jxcbxz.js → server-ej6tzd7t.js} +53 -51
  85. package/dist/cli/{skill-generator-qanka67x.js → skill-generator-nspt9b3n.js} +19 -18
  86. package/dist/cli/{snapshot-coordination-init-mhrq11p8.js → snapshot-coordination-init-3rs3zpaa.js} +45 -43
  87. package/dist/cli/speckit-checkoff-dqf636ya.js +31 -0
  88. package/dist/cli/{telemetry-qxav5cdk.js → telemetry-zhcgfe70.js} +1 -1
  89. package/dist/cli/{workspace-snapshot-rfnn80dg.js → workspace-snapshot-4m2ex1ey.js} +4 -5
  90. package/dist/cli/{worktree-collision-ownership-ygdbpexh.js → worktree-collision-ownership-rbzgbvj4.js} +17 -17
  91. package/dist/cli/{worktree-isolation-4855f2az.js → worktree-isolation-6zpnzr70.js} +45 -43
  92. package/dist/commands/pr-feedback-loop.d.ts +1 -0
  93. package/dist/commands/registry.d.ts +10 -2
  94. package/dist/config/schema.d.ts +27 -0
  95. package/dist/council/disagreement-detector.d.ts +23 -1
  96. package/dist/council/general-council-service.d.ts +40 -3
  97. package/dist/failures/invocation-failure.d.ts +11 -0
  98. package/dist/hooks/delegation-tracker.d.ts +6 -1
  99. package/dist/hooks/pr-workflow-gate.d.ts +1 -1
  100. package/dist/hooks/system-enhancer.d.ts +4 -2
  101. package/dist/hooks/system-render-boundary.d.ts +68 -0
  102. package/dist/index.js +634 -606
  103. package/dist/sdd/effective-spec.d.ts +35 -22
  104. package/dist/sdd/speckit-checkoff.d.ts +113 -0
  105. package/dist/session/hydration-ownership.d.ts +83 -0
  106. package/dist/session/snapshot-coordination-init.d.ts +2 -1
  107. package/dist/session/snapshot-reader.d.ts +25 -7
  108. package/dist/state.d.ts +32 -2
  109. package/dist/utils/provider-error-classification.d.ts +11 -0
  110. package/opencode-swarm.schema.json +40 -0
  111. package/package.json +1 -1
  112. package/dist/cli/curator-llm-factory-dds2fnhj.js +0 -72
  113. package/dist/cli/guardrail-explain-qm5wej7v.js +0 -73
  114. package/dist/cli/hive-promoter-1mhx2jh0.js +0 -87
  115. package/dist/cli/index-4ddpvd5c.js +0 -94
@@ -1,164 +1,23 @@
1
1
  // @bun
2
- import {
3
- require_proper_lockfile
4
- } from "./index-7g4c7s5r.js";
5
2
  import {
6
3
  emit
7
- } from "./index-qt0793qm.js";
4
+ } from "./index-6mwha20t.js";
5
+ import {
6
+ tryAcquireLock
7
+ } from "./index-ejhxgyvm.js";
8
8
  import {
9
9
  atomicWriteFileAnyRoot
10
- } from "./index-ne9zamcs.js";
10
+ } from "./index-989v83pb.js";
11
11
  import {
12
12
  init_logger,
13
13
  warn
14
14
  } from "./index-zgwm4ryv.js";
15
- import {
16
- __toESM
17
- } from "./index-a76rekgs.js";
18
-
19
- // src/parallel/file-locks.ts
20
- var import_proper_lockfile = __toESM(require_proper_lockfile(), 1);
21
- import * as crypto from "crypto";
22
- import * as fs from "fs";
23
- import * as path from "path";
24
- var LOCKS_DIR = ".swarm/locks";
25
- var LOCK_TIMEOUT_MS = 5 * 60 * 1000;
26
- var _internals = {
27
- tryAcquireLock,
28
- writeFile: fs.promises.writeFile
29
- };
30
- function getLockFilePath(directory, filePath) {
31
- const normalized = path.resolve(directory, filePath);
32
- const baseDir = path.resolve(directory) + path.sep;
33
- const pathOk = process.platform === "win32" ? normalized.toLowerCase().startsWith(baseDir.toLowerCase()) : normalized.startsWith(baseDir);
34
- if (!pathOk) {
35
- throw new Error("Invalid file path: path traversal not allowed");
36
- }
37
- const pathForHash = process.platform === "win32" ? normalized.toLowerCase() : normalized;
38
- const hash = crypto.createHash("sha256").update(pathForHash).digest("hex");
39
- const lockPath = path.join(directory, LOCKS_DIR, `${hash}.lock`);
40
- const legacyPaths = [pathForHash];
41
- if (process.platform === "win32" && normalized !== pathForHash) {
42
- legacyPaths.push(normalized);
43
- }
44
- for (const legacyPath of legacyPaths) {
45
- const oldHash = Buffer.from(legacyPath).toString("base64").replace(/[/+=]/g, "_");
46
- const oldLockPath = path.join(directory, LOCKS_DIR, `${oldHash}.lock`);
47
- if (fs.existsSync(oldLockPath)) {
48
- return oldLockPath;
49
- }
50
- }
51
- return lockPath;
52
- }
53
- function getMetaPath(lockPath) {
54
- return `${lockPath.replace(/\.lock$/, "")}.meta`;
55
- }
56
- function readMetaFile(metaPath) {
57
- try {
58
- const content = fs.readFileSync(metaPath, "utf-8");
59
- return JSON.parse(content);
60
- } catch {
61
- return null;
62
- }
63
- }
64
- async function tryAcquireLock(directory, filePath, agent, taskId) {
65
- const lockPath = getLockFilePath(directory, filePath);
66
- const locksDir = path.dirname(lockPath);
67
- if (!fs.existsSync(locksDir)) {
68
- fs.mkdirSync(locksDir, { recursive: true });
69
- }
70
- if (!fs.existsSync(lockPath)) {
71
- fs.writeFileSync(lockPath, "", "utf-8");
72
- }
73
- let release;
74
- try {
75
- release = await import_proper_lockfile.default.lock(lockPath, {
76
- stale: LOCK_TIMEOUT_MS,
77
- retries: {
78
- retries: 5,
79
- minTimeout: 10,
80
- maxTimeout: 500,
81
- factor: 2
82
- },
83
- realpath: false
84
- });
85
- } catch (err) {
86
- const code = err.code;
87
- if (code === "ELOCKED" || code === "EEXIST") {
88
- return { acquired: false };
89
- }
90
- throw err;
91
- }
92
- const lock = {
93
- filePath,
94
- agent,
95
- taskId,
96
- timestamp: new Date().toISOString(),
97
- expiresAt: Date.now() + LOCK_TIMEOUT_MS,
98
- _release: release
99
- };
100
- return { acquired: true, lock };
101
- }
102
- function listActiveLocks(directory) {
103
- const locksDir = path.join(directory, LOCKS_DIR);
104
- const locks = [];
105
- if (!fs.existsSync(locksDir)) {
106
- return locks;
107
- }
108
- const files = fs.readdirSync(locksDir);
109
- const now = Date.now();
110
- for (const file of files) {
111
- if (!file.endsWith(".lock"))
112
- continue;
113
- const lockPath = path.join(locksDir, file);
114
- try {
115
- const stat = fs.statSync(lockPath);
116
- if (stat.isFile()) {
117
- const plLockDir = `${lockPath}.lock`;
118
- if (fs.existsSync(plLockDir)) {
119
- const metaPath = getMetaPath(lockPath);
120
- const meta = readMetaFile(metaPath);
121
- if (meta) {
122
- if (meta.expiresAt < now) {
123
- continue;
124
- }
125
- locks.push({
126
- filePath: meta.originalPath,
127
- agent: meta.agent,
128
- taskId: meta.taskId,
129
- timestamp: meta.acquiredAt,
130
- expiresAt: meta.expiresAt,
131
- laneId: meta.laneId
132
- });
133
- } else {
134
- let acquiredAt = Date.now();
135
- try {
136
- acquiredAt = fs.statSync(plLockDir).mtimeMs;
137
- } catch {}
138
- const expiresAt = acquiredAt + LOCK_TIMEOUT_MS;
139
- if (expiresAt < now) {
140
- continue;
141
- }
142
- locks.push({
143
- filePath: lockPath,
144
- agent: "unknown",
145
- taskId: "unknown",
146
- timestamp: new Date(acquiredAt).toISOString(),
147
- expiresAt
148
- });
149
- }
150
- }
151
- }
152
- } catch {}
153
- }
154
- return locks;
155
- }
156
15
 
157
16
  // src/utils/project-boundary.ts
158
17
  init_logger();
159
- import * as fs2 from "fs";
18
+ import * as fs from "fs";
160
19
  import * as os from "os";
161
- import * as path2 from "path";
20
+ import * as path from "path";
162
21
  var MAX_PROJECT_ROOT_DEPTH = 20;
163
22
  var PROJECT_ROOT_INDICATORS = [
164
23
  "package.json",
@@ -181,14 +40,14 @@ var PROJECT_BOUNDARY_MARKERS = [
181
40
  { name: ".opencode", accepts: (stat) => stat.isDirectory() }
182
41
  ];
183
42
  function isAbsoluteDirectoryInput(value) {
184
- return typeof value === "string" && value.trim().length > 0 && path2.isAbsolute(value);
43
+ return typeof value === "string" && value.trim().length > 0 && path.isAbsolute(value);
185
44
  }
186
45
  function hasExplicitProjectBoundary(directory) {
187
46
  if (!isAbsoluteDirectoryInput(directory))
188
47
  return false;
189
48
  for (const marker of PROJECT_BOUNDARY_MARKERS) {
190
49
  try {
191
- const stat = _internals2.lstatSync(path2.join(directory, marker.name));
50
+ const stat = _internals.lstatSync(path.join(directory, marker.name));
192
51
  if (!stat.isSymbolicLink() && marker.accepts(stat))
193
52
  return true;
194
53
  } catch {}
@@ -199,8 +58,8 @@ function isStrictPathDescendant(candidate, root) {
199
58
  if (!isAbsoluteDirectoryInput(candidate) || !isAbsoluteDirectoryInput(root)) {
200
59
  return false;
201
60
  }
202
- const relative2 = path2.relative(root, candidate);
203
- return relative2 !== "" && relative2 !== ".." && !relative2.startsWith(`..${path2.sep}`) && !path2.isAbsolute(relative2);
61
+ const relative2 = path.relative(root, candidate);
62
+ return relative2 !== "" && relative2 !== ".." && !relative2.startsWith(`..${path.sep}`) && !path.isAbsolute(relative2);
204
63
  }
205
64
  function isMissingPathError(error) {
206
65
  const code = error?.code;
@@ -211,8 +70,8 @@ function isWeakConfigContainerRoot(directory, dependencies) {
211
70
  const resolved = dependencies.realpathSync(directory);
212
71
  return resolved === dependencies.realpathSync(os.tmpdir()) || resolved === dependencies.realpathSync(os.homedir());
213
72
  } catch {
214
- const resolved = path2.resolve(directory);
215
- return resolved === path2.resolve(os.tmpdir()) || resolved === path2.resolve(os.homedir());
73
+ const resolved = path.resolve(directory);
74
+ return resolved === path.resolve(os.tmpdir()) || resolved === path.resolve(os.homedir());
216
75
  }
217
76
  }
218
77
  function projectIndicatorState(directory, dependencies, options = {}) {
@@ -221,7 +80,7 @@ function projectIndicatorState(directory, dependencies, options = {}) {
221
80
  if (!allowConfigOnly && indicator === ".opencode")
222
81
  continue;
223
82
  try {
224
- const indicatorStat = dependencies.statSync(path2.join(directory, indicator));
83
+ const indicatorStat = dependencies.statSync(path.join(directory, indicator));
225
84
  if (indicatorStat.isFile() || indicatorStat.isDirectory())
226
85
  return "present";
227
86
  } catch (error) {
@@ -231,7 +90,7 @@ function projectIndicatorState(directory, dependencies, options = {}) {
231
90
  }
232
91
  return "absent";
233
92
  }
234
- function assertProjectRoot(directory, dependencies = fs2, artifactLabel = "runtime state") {
93
+ function assertProjectRoot(directory, dependencies = fs, artifactLabel = "runtime state") {
235
94
  let resolved;
236
95
  try {
237
96
  resolved = dependencies.realpathSync(directory);
@@ -249,14 +108,14 @@ function assertProjectRoot(directory, dependencies = fs2, artifactLabel = "runti
249
108
  throw new Error(`Cannot verify project root for "${resolved}" \u2014 ancestor search exceeded ${MAX_PROJECT_ROOT_DEPTH} levels`);
250
109
  }
251
110
  depth++;
252
- const parent = path2.dirname(current);
111
+ const parent = path.dirname(current);
253
112
  if (parent === current)
254
113
  break;
255
- if (path2.dirname(parent) === parent) {
114
+ if (path.dirname(parent) === parent) {
256
115
  current = parent;
257
116
  continue;
258
117
  }
259
- const parentSwarm = path2.join(parent, ".swarm");
118
+ const parentSwarm = path.join(parent, ".swarm");
260
119
  let parentSwarmStat;
261
120
  try {
262
121
  parentSwarmStat = dependencies.statSync(parentSwarm);
@@ -284,8 +143,8 @@ function assertProjectRoot(directory, dependencies = fs2, artifactLabel = "runti
284
143
  current = parent;
285
144
  }
286
145
  }
287
- var _internals2 = {
288
- lstatSync: fs2.lstatSync
146
+ var _internals = {
147
+ lstatSync: fs.lstatSync
289
148
  };
290
149
 
291
150
  // src/evidence/lock.ts
@@ -357,19 +216,19 @@ async function withEvidenceLock(directory, evidencePath, agent, taskId, fn, time
357
216
  });
358
217
  const delay = Math.min(backoffMs(attempt), deadline - Date.now());
359
218
  if (delay > 0) {
360
- await new Promise((resolve3) => setTimeout(resolve3, delay));
219
+ await new Promise((resolve2) => setTimeout(resolve2, delay));
361
220
  }
362
221
  attempt++;
363
222
  }
364
223
  }
365
224
 
366
225
  // src/evidence/task-file.ts
367
- import * as path3 from "path";
226
+ import * as path2 from "path";
368
227
  function taskEvidenceRelPath(taskId) {
369
- return path3.join("evidence", `${taskId}.json`);
228
+ return path2.join("evidence", `${taskId}.json`);
370
229
  }
371
230
  function taskEvidencePath(directory, taskId) {
372
- return path3.join(directory, ".swarm", taskEvidenceRelPath(taskId));
231
+ return path2.join(directory, ".swarm", taskEvidenceRelPath(taskId));
373
232
  }
374
233
  async function atomicWriteFile(targetPath, content) {
375
234
  await atomicWriteFileAnyRoot(targetPath, content);
@@ -378,4 +237,4 @@ function withTaskEvidenceLock(directory, taskId, agent, fn) {
378
237
  return withEvidenceLock(directory, taskEvidenceRelPath(taskId), agent, taskId, fn);
379
238
  }
380
239
 
381
- export { tryAcquireLock, listActiveLocks, hasExplicitProjectBoundary, isStrictPathDescendant, assertProjectRoot, EvidenceLockTimeoutError, withEvidenceLock, taskEvidencePath, atomicWriteFile, withTaskEvidenceLock };
240
+ export { hasExplicitProjectBoundary, isStrictPathDescendant, assertProjectRoot, EvidenceLockTimeoutError, withEvidenceLock, taskEvidencePath, atomicWriteFile, withTaskEvidenceLock };
@@ -0,0 +1,147 @@
1
+ // @bun
2
+ import {
3
+ require_proper_lockfile
4
+ } from "./index-7g4c7s5r.js";
5
+ import {
6
+ __toESM
7
+ } from "./index-a76rekgs.js";
8
+
9
+ // src/parallel/file-locks.ts
10
+ var import_proper_lockfile = __toESM(require_proper_lockfile(), 1);
11
+ import * as crypto from "crypto";
12
+ import * as fs from "fs";
13
+ import * as path from "path";
14
+ var LOCKS_DIR = ".swarm/locks";
15
+ var LOCK_TIMEOUT_MS = 5 * 60 * 1000;
16
+ var _internals = {
17
+ tryAcquireLock,
18
+ writeFile: fs.promises.writeFile
19
+ };
20
+ function getLockFilePath(directory, filePath) {
21
+ const normalized = path.resolve(directory, filePath);
22
+ const baseDir = path.resolve(directory) + path.sep;
23
+ const pathOk = process.platform === "win32" ? normalized.toLowerCase().startsWith(baseDir.toLowerCase()) : normalized.startsWith(baseDir);
24
+ if (!pathOk) {
25
+ throw new Error("Invalid file path: path traversal not allowed");
26
+ }
27
+ const pathForHash = process.platform === "win32" ? normalized.toLowerCase() : normalized;
28
+ const hash = crypto.createHash("sha256").update(pathForHash).digest("hex");
29
+ const lockPath = path.join(directory, LOCKS_DIR, `${hash}.lock`);
30
+ const legacyPaths = [pathForHash];
31
+ if (process.platform === "win32" && normalized !== pathForHash) {
32
+ legacyPaths.push(normalized);
33
+ }
34
+ for (const legacyPath of legacyPaths) {
35
+ const oldHash = Buffer.from(legacyPath).toString("base64").replace(/[/+=]/g, "_");
36
+ const oldLockPath = path.join(directory, LOCKS_DIR, `${oldHash}.lock`);
37
+ if (fs.existsSync(oldLockPath)) {
38
+ return oldLockPath;
39
+ }
40
+ }
41
+ return lockPath;
42
+ }
43
+ function getMetaPath(lockPath) {
44
+ return `${lockPath.replace(/\.lock$/, "")}.meta`;
45
+ }
46
+ function readMetaFile(metaPath) {
47
+ try {
48
+ const content = fs.readFileSync(metaPath, "utf-8");
49
+ return JSON.parse(content);
50
+ } catch {
51
+ return null;
52
+ }
53
+ }
54
+ async function tryAcquireLock(directory, filePath, agent, taskId) {
55
+ const lockPath = getLockFilePath(directory, filePath);
56
+ const locksDir = path.dirname(lockPath);
57
+ if (!fs.existsSync(locksDir)) {
58
+ fs.mkdirSync(locksDir, { recursive: true });
59
+ }
60
+ if (!fs.existsSync(lockPath)) {
61
+ fs.writeFileSync(lockPath, "", "utf-8");
62
+ }
63
+ let release;
64
+ try {
65
+ release = await import_proper_lockfile.default.lock(lockPath, {
66
+ stale: LOCK_TIMEOUT_MS,
67
+ retries: {
68
+ retries: 5,
69
+ minTimeout: 10,
70
+ maxTimeout: 500,
71
+ factor: 2
72
+ },
73
+ realpath: false
74
+ });
75
+ } catch (err) {
76
+ const code = err.code;
77
+ if (code === "ELOCKED" || code === "EEXIST") {
78
+ return { acquired: false };
79
+ }
80
+ throw err;
81
+ }
82
+ const lock = {
83
+ filePath,
84
+ agent,
85
+ taskId,
86
+ timestamp: new Date().toISOString(),
87
+ expiresAt: Date.now() + LOCK_TIMEOUT_MS,
88
+ _release: release
89
+ };
90
+ return { acquired: true, lock };
91
+ }
92
+ function listActiveLocks(directory) {
93
+ const locksDir = path.join(directory, LOCKS_DIR);
94
+ const locks = [];
95
+ if (!fs.existsSync(locksDir)) {
96
+ return locks;
97
+ }
98
+ const files = fs.readdirSync(locksDir);
99
+ const now = Date.now();
100
+ for (const file of files) {
101
+ if (!file.endsWith(".lock"))
102
+ continue;
103
+ const lockPath = path.join(locksDir, file);
104
+ try {
105
+ const stat = fs.statSync(lockPath);
106
+ if (stat.isFile()) {
107
+ const plLockDir = `${lockPath}.lock`;
108
+ if (fs.existsSync(plLockDir)) {
109
+ const metaPath = getMetaPath(lockPath);
110
+ const meta = readMetaFile(metaPath);
111
+ if (meta) {
112
+ if (meta.expiresAt < now) {
113
+ continue;
114
+ }
115
+ locks.push({
116
+ filePath: meta.originalPath,
117
+ agent: meta.agent,
118
+ taskId: meta.taskId,
119
+ timestamp: meta.acquiredAt,
120
+ expiresAt: meta.expiresAt,
121
+ laneId: meta.laneId
122
+ });
123
+ } else {
124
+ let acquiredAt = Date.now();
125
+ try {
126
+ acquiredAt = fs.statSync(plLockDir).mtimeMs;
127
+ } catch {}
128
+ const expiresAt = acquiredAt + LOCK_TIMEOUT_MS;
129
+ if (expiresAt < now) {
130
+ continue;
131
+ }
132
+ locks.push({
133
+ filePath: lockPath,
134
+ agent: "unknown",
135
+ taskId: "unknown",
136
+ timestamp: new Date(acquiredAt).toISOString(),
137
+ expiresAt
138
+ });
139
+ }
140
+ }
141
+ }
142
+ } catch {}
143
+ }
144
+ return locks;
145
+ }
146
+
147
+ export { tryAcquireLock, listActiveLocks };