opencode-swarm-plugin 0.18.0 → 0.19.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -15,6 +15,12 @@
15
15
 
16
16
  import { checkSwarmHealth } from "./streams/swarm-mail";
17
17
 
18
+ /** Default timeout for URL reachability checks in milliseconds */
19
+ const DEFAULT_URL_TIMEOUT_MS = 2000;
20
+
21
+ /** Timeout for bunx commands (semantic-memory check) in milliseconds */
22
+ const BUNX_TIMEOUT_MS = 10000;
23
+
18
24
  export type ToolName =
19
25
  | "semantic-memory"
20
26
  | "cass"
@@ -55,11 +61,13 @@ async function commandExists(cmd: string): Promise<boolean> {
55
61
  }
56
62
 
57
63
  /**
58
- * Check if a URL is reachable
64
+ * Check if a URL is reachable.
65
+ * Uses GET instead of HEAD because some servers don't support HEAD.
66
+ * We only check response.ok status, not body content, so GET has minimal overhead vs HEAD.
59
67
  */
60
68
  async function urlReachable(
61
69
  url: string,
62
- timeoutMs: number = 2000,
70
+ timeoutMs: number = DEFAULT_URL_TIMEOUT_MS,
63
71
  ): Promise<boolean> {
64
72
  try {
65
73
  const controller = new AbortController();
@@ -109,7 +117,7 @@ const toolCheckers: Record<ToolName, () => Promise<ToolStatus>> = {
109
117
  stderr: "pipe",
110
118
  });
111
119
 
112
- const timeout = setTimeout(() => proc.kill(), 10000);
120
+ const timeout = setTimeout(() => proc.kill(), BUNX_TIMEOUT_MS);
113
121
  const exitCode = await proc.exited;
114
122
  clearTimeout(timeout);
115
123
 
@@ -242,7 +250,8 @@ const toolCheckers: Record<ToolName, () => Promise<ToolStatus>> = {
242
250
  };
243
251
 
244
252
  /**
245
- * Fallback behavior descriptions for each tool
253
+ * Human-readable descriptions of graceful degradation behavior when tools are unavailable.
254
+ * Shown to users in warnings and tool status output.
246
255
  */
247
256
  const fallbackBehaviors: Record<ToolName, string> = {
248
257
  "semantic-memory":
@@ -330,7 +339,9 @@ export async function checkAllTools(): Promise<
330
339
  }
331
340
 
332
341
  /**
333
- * Log a warning about a missing tool (once per tool per session)
342
+ * Log a warning when a tool is missing.
343
+ * Uses Set to deduplicate - logs once per tool per session to prevent spam
344
+ * when tool availability is checked repeatedly.
334
345
  */
335
346
  export function warnMissingTool(tool: ToolName): void {
336
347
  if (warningsLogged.has(tool)) {
@@ -397,7 +408,19 @@ export async function ifToolAvailable<T>(
397
408
  }
398
409
 
399
410
  /**
400
- * Reset tool cache (for testing)
411
+ * Reset the tool availability cache.
412
+ * Use in tests to ensure fresh checks, or when tool availability may have
413
+ * changed mid-session (e.g., after installing a tool via `bunx`).
414
+ *
415
+ * @example
416
+ * // In tests
417
+ * beforeEach(() => resetToolCache());
418
+ *
419
+ * @example
420
+ * // After installing a tool
421
+ * await installTool('semantic-memory');
422
+ * resetToolCache();
423
+ * const available = await isToolAvailable('semantic-memory');
401
424
  */
402
425
  export function resetToolCache(): void {
403
426
  toolCache.clear();
@@ -0,0 +1,86 @@
1
+ /**
2
+ * Quick test to verify bug fixes in store.ts
3
+ *
4
+ * Bug 1 (xcavl.5): Transaction rollback error propagation
5
+ * Bug 2 (xcavl.6): File reservation idempotency
6
+ */
7
+
8
+ import { getDatabase, resetDatabase } from "./src/streams/index";
9
+ import { reserveFiles } from "./src/streams/store";
10
+
11
+ async function testBug1_RollbackErrorPropagation() {
12
+ console.log("\n=== Testing Bug 1: Rollback Error Propagation ===");
13
+ console.log(
14
+ "Note: This is hard to test in isolation without forcing connection loss.",
15
+ );
16
+ console.log(
17
+ "The fix ensures composite errors are thrown when both transaction AND rollback fail.",
18
+ );
19
+ console.log(
20
+ "✅ Code review confirms fix is in place (see lines 141-166 in store.ts)",
21
+ );
22
+ }
23
+
24
+ async function testBug2_ReservationIdempotency() {
25
+ console.log("\n=== Testing Bug 2: File Reservation Idempotency ===");
26
+
27
+ await resetDatabase(); // Clean slate
28
+ const db = await getDatabase();
29
+
30
+ const projectKey = "test-project";
31
+ const agentName = "TestWorker";
32
+ const paths = ["src/file.ts", "src/other.ts"];
33
+
34
+ // First reservation
35
+ await reserveFiles(projectKey, agentName, paths, {
36
+ reason: "Initial reservation",
37
+ exclusive: true,
38
+ ttlSeconds: 3600,
39
+ });
40
+
41
+ // Query reservations
42
+ const result1 = await db.query<{ count: string }>(
43
+ `SELECT COUNT(*) as count FROM reservations
44
+ WHERE project_key = $1 AND agent_name = $2 AND released_at IS NULL`,
45
+ [projectKey, agentName],
46
+ );
47
+ const count1 = parseInt(result1.rows[0]?.count || "0");
48
+ console.log(`After first reservation: ${count1} active reservations`);
49
+
50
+ // RETRY the same reservation (simulating network timeout + retry)
51
+ await reserveFiles(projectKey, agentName, paths, {
52
+ reason: "Retry after timeout",
53
+ exclusive: true,
54
+ ttlSeconds: 3600,
55
+ });
56
+
57
+ // Query again - should still be 2 (idempotent)
58
+ const result2 = await db.query<{ count: string }>(
59
+ `SELECT COUNT(*) as count FROM reservations
60
+ WHERE project_key = $1 AND agent_name = $2 AND released_at IS NULL`,
61
+ [projectKey, agentName],
62
+ );
63
+ const count2 = parseInt(result2.rows[0]?.count || "0");
64
+ console.log(`After retry: ${count2} active reservations`);
65
+
66
+ if (count1 === count2 && count1 === 2) {
67
+ console.log("✅ PASS: Reservation is idempotent (no duplicates created)");
68
+ } else {
69
+ console.log(
70
+ `❌ FAIL: Expected 2 reservations both times, got ${count1} then ${count2}`,
71
+ );
72
+ }
73
+ }
74
+
75
+ async function main() {
76
+ try {
77
+ await testBug1_RollbackErrorPropagation();
78
+ await testBug2_ReservationIdempotency();
79
+ console.log("\n=== All tests complete ===\n");
80
+ } catch (error) {
81
+ console.error("Test failed:", error);
82
+ process.exit(1);
83
+ }
84
+ }
85
+
86
+ main();