@desplega.ai/agent-swarm 1.99.0 → 1.99.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.
package/openapi.json CHANGED
@@ -2,7 +2,7 @@
2
2
  "openapi": "3.1.0",
3
3
  "info": {
4
4
  "title": "Agent Swarm API",
5
- "version": "1.99.0",
5
+ "version": "1.99.1",
6
6
  "description": "Multi-agent orchestration API for Claude Code, Codex, and Gemini CLI. Enables task distribution, agent communication, and service discovery.\n\nMCP tools are documented separately in [MCP.md](./MCP.md)."
7
7
  },
8
8
  "servers": [
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@desplega.ai/agent-swarm",
3
- "version": "1.99.0",
3
+ "version": "1.99.1",
4
4
  "description": "Multi-agent orchestration for Claude Code, Codex, Gemini CLI, and other AI coding assistants",
5
5
  "license": "MIT",
6
6
  "author": "desplega.sh <contact@desplega.sh>",
@@ -5,13 +5,24 @@
5
5
  *
6
6
  * Idempotent: already-scrubbed rows are no-ops (scrubSecrets is idempotent).
7
7
  * Uses seed_state to avoid re-scanning on subsequent boots.
8
+ *
9
+ * Restart-safe: progress is persisted as a cursor in seed_state after each
10
+ * batch, so a restart (e.g. K8s probe SIGKILL) resumes from the last
11
+ * committed batch instead of re-scanning from zero.
12
+ *
13
+ * Non-blocking: yields to the event loop between batches so /health and
14
+ * startup/liveness probes stay responsive.
8
15
  */
9
16
 
10
17
  import { scrubSecrets } from "../utils/secret-scrubber";
11
18
  import { getDb } from "./db";
12
19
 
13
20
  const SCRUB_KEY = "boot-scrub-logs-v2";
14
- const BATCH_SIZE = 500;
21
+ const CURSOR_KEY = "boot-scrub-logs-v2-cursor";
22
+ const BATCH_SIZE = 200;
23
+
24
+ /** Yield to the event loop so probes can respond. */
25
+ const yieldTick = () => new Promise<void>((r) => setTimeout(r, 5));
15
26
 
16
27
  export async function runBootScrubLogs(): Promise<void> {
17
28
  const db = getDb();
@@ -24,46 +35,94 @@ export async function runBootScrubLogs(): Promise<void> {
24
35
 
25
36
  if (done) return;
26
37
 
27
- // ESCAPE '!' makes ! the escape character so !_ matches a literal underscore
28
- // instead of the LIKE single-char wildcard. Without this, '%npm_%' matches
29
- // any row containing "npm" + any char (e.g. "npm install"), drowning real
30
- // token rows when a LIMIT is applied.
31
- const rows = db
32
- .prepare<{ id: string; content: string }, []>(
33
- `SELECT id, content FROM session_logs
34
- WHERE content LIKE '%lin!_oauth!_%' ESCAPE '!'
35
- OR content LIKE '%lin!_api!_%' ESCAPE '!'
36
- OR content LIKE '%npm!_%' ESCAPE '!'
37
- OR content LIKE '%ATATT%'`,
38
- )
39
- .all();
38
+ // Resume from last cursor if a previous run was interrupted
39
+ const savedCursor =
40
+ db
41
+ .prepare<{ seededHash: string }, [string, string]>(
42
+ "SELECT seededHash FROM seed_state WHERE kind = ? AND key = ?",
43
+ )
44
+ .get("maintenance", CURSOR_KEY)?.seededHash ?? "";
40
45
 
41
- if (rows.length === 0) {
46
+ const lastProcessedId = savedCursor || "";
47
+
48
+ // Count total work remaining (for logging only)
49
+ const totalRemaining =
50
+ db
51
+ .prepare<{ count: number }, [string]>(
52
+ `SELECT COUNT(*) as count FROM session_logs
53
+ WHERE id > ?
54
+ AND (content LIKE '%lin!_oauth!_%' ESCAPE '!'
55
+ OR content LIKE '%lin!_api!_%' ESCAPE '!'
56
+ OR content LIKE '%npm!_%' ESCAPE '!'
57
+ OR content LIKE '%ATATT%')`,
58
+ )
59
+ .get(lastProcessedId)?.count ?? 0;
60
+
61
+ if (totalRemaining === 0) {
42
62
  markDone(db);
43
63
  return;
44
64
  }
45
65
 
46
- console.log(`[boot-scrub-logs] starting: ${rows.length} candidate rows`);
66
+ console.log(
67
+ `[boot-scrub-logs] starting: ${totalRemaining} candidate rows remaining` +
68
+ (lastProcessedId ? ` (resuming from cursor ${lastProcessedId.slice(0, 8)}…)` : ""),
69
+ );
47
70
 
71
+ const selectBatch = db.prepare<{ id: string; content: string }, [string]>(
72
+ `SELECT id, content FROM session_logs
73
+ WHERE id > ?
74
+ AND (content LIKE '%lin!_oauth!_%' ESCAPE '!'
75
+ OR content LIKE '%lin!_api!_%' ESCAPE '!'
76
+ OR content LIKE '%npm!_%' ESCAPE '!'
77
+ OR content LIKE '%ATATT%')
78
+ ORDER BY id ASC
79
+ LIMIT ${BATCH_SIZE}`,
80
+ );
48
81
  const update = db.prepare("UPDATE session_logs SET content = ? WHERE id = ?");
82
+ const saveCursor = db.prepare(
83
+ `INSERT INTO seed_state (kind, key, seededHash, seededAt)
84
+ VALUES ('maintenance', '${CURSOR_KEY}', ?, datetime('now'))
85
+ ON CONFLICT (kind, key) DO UPDATE SET seededHash = ?, seededAt = datetime('now')`,
86
+ );
87
+
49
88
  let scrubbed = 0;
89
+ let scanned = 0;
90
+ let cursor = lastProcessedId;
91
+
92
+ // Paginated cursor loop — each iteration fetches the next BATCH_SIZE rows
93
+ // ordered by id, processes them in a transaction, saves the cursor, and
94
+ // yields to the event loop.
95
+ for (;;) {
96
+ const rows = selectBatch.all(cursor);
97
+ if (rows.length === 0) break;
98
+
99
+ const batchLastId = rows[rows.length - 1]!.id;
50
100
 
51
- for (let i = 0; i < rows.length; i += BATCH_SIZE) {
52
- const batch = rows.slice(i, i + BATCH_SIZE);
53
101
  const tx = db.transaction(() => {
54
- for (const row of batch) {
102
+ for (const row of rows) {
55
103
  const cleaned = scrubSecrets(row.content);
56
104
  if (cleaned !== row.content) {
57
105
  update.run(cleaned, row.id);
58
106
  scrubbed++;
59
107
  }
60
108
  }
109
+ // Persist cursor inside the same transaction so it's atomic with the scrub
110
+ saveCursor.run(batchLastId, batchLastId);
61
111
  });
62
112
  tx();
113
+
114
+ scanned += rows.length;
115
+ cursor = batchLastId;
116
+
117
+ // Yield to the event loop between batches
118
+ await yieldTick();
63
119
  }
64
120
 
65
121
  markDone(db);
66
- console.log(`[boot-scrub-logs] complete: scanned=${rows.length} scrubbed=${scrubbed}`);
122
+ // Clean up the cursor key now that we're fully done
123
+ db.run("DELETE FROM seed_state WHERE kind = 'maintenance' AND key = ?", [CURSOR_KEY]);
124
+
125
+ console.log(`[boot-scrub-logs] complete: scanned=${scanned} scrubbed=${scrubbed}`);
67
126
  }
68
127
 
69
128
  function markDone(db: ReturnType<typeof getDb>) {