@desplega.ai/agent-swarm 1.94.0 → 1.96.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.
Files changed (38) hide show
  1. package/README.md +3 -3
  2. package/openapi.json +46 -1
  3. package/package.json +4 -3
  4. package/src/be/boot-scrub-logs.ts +76 -0
  5. package/src/be/db.ts +22 -10
  6. package/src/be/migrations/094_mcp_extra_authorize_params.sql +4 -0
  7. package/src/be/modelsdev-cache.json +89422 -85636
  8. package/src/be/skill-sync.ts +4 -4
  9. package/src/be/swarm-config-guard.ts +8 -0
  10. package/src/commands/provider-credentials.ts +37 -9
  11. package/src/commands/runner.ts +28 -0
  12. package/src/http/agents.ts +1 -0
  13. package/src/http/config.ts +24 -4
  14. package/src/http/index.ts +9 -0
  15. package/src/http/mcp-oauth.ts +14 -0
  16. package/src/oauth/mcp-wrapper.ts +14 -0
  17. package/src/prompts/session-templates.ts +21 -0
  18. package/src/providers/codex-skill-resolver.ts +22 -8
  19. package/src/providers/opencode-adapter.ts +20 -2
  20. package/src/providers/pi-mono-adapter.ts +160 -21
  21. package/src/providers/types.ts +33 -0
  22. package/src/tests/bedrock-model-groups.test.ts +135 -0
  23. package/src/tests/credential-check.test.ts +538 -50
  24. package/src/tests/harness-provider-resolution.test.ts +23 -0
  25. package/src/tests/mcp-oauth-queries.test.ts +71 -1
  26. package/src/tests/mcp-oauth-wrapper.test.ts +109 -0
  27. package/src/tests/opencode-adapter.test.ts +29 -1
  28. package/src/tests/provider-command-format.test.ts +12 -0
  29. package/src/tests/secret-scrubber.test.ts +73 -1
  30. package/src/tests/skill-fs-writer.test.ts +7 -1
  31. package/src/tests/skill-sync.test.ts +15 -3
  32. package/src/tools/mcp-servers/mcp-server-create.ts +7 -0
  33. package/src/tools/mcp-servers/mcp-server-update.ts +8 -0
  34. package/src/tools/swarm-config/get-config.ts +9 -1
  35. package/src/tools/swarm-config/list-config.ts +8 -0
  36. package/src/types.ts +22 -0
  37. package/src/utils/secret-scrubber.ts +33 -12
  38. package/src/utils/skill-fs-writer.ts +11 -3
package/README.md CHANGED
@@ -127,7 +127,7 @@ Check [our templates](https://templates.agent-swarm.dev) for a quick start.
127
127
  - **Workflow engine with Human-in-the-Loop** — DAG-based automation with approval gates, retries, and structured I/O. [Workflows →](https://docs.agent-swarm.dev/docs/concepts/workflows)
128
128
  - **Scheduled & recurring tasks** — cron-based automation for standing work. [Scheduling →](https://docs.agent-swarm.dev/docs/concepts/scheduling)
129
129
  - **Durable script workflows** — launch background script runs, inspect their journals, and track them from the dashboard when a one-shot `script-run` is too small. [Guide →](https://docs.agent-swarm.dev/docs/guides/script-workflow-runs)
130
- - **Harness & LLM agnostic** — run with Claude Code, Claude Bridge, OpenAI Codex, pi-mono, Devin, Claude Managed Agents, raw LLMs, or opencode. Tasks, schedules, and workflow agent-task nodes can use portable `modelTier` intent (`smol`, `regular`, `smart`, `ultra`) and resolve it per worker/provider at run time. [Harness config →](https://docs.agent-swarm.dev/docs/guides/harness-configuration) · [Add a new provider →](https://docs.agent-swarm.dev/docs/guides/harness-providers)
130
+ - **Harness & LLM agnostic** — run with Claude Code, Claude Bridge, OpenAI Codex, pi-mono (Anthropic, OpenRouter, or Amazon Bedrock), Devin, Claude Managed Agents, raw LLMs, or opencode. Tasks, schedules, and workflow agent-task nodes can use portable `modelTier` intent (`smol`, `regular`, `smart`, `ultra`) and resolve it per worker/provider at run time. [Harness config →](https://docs.agent-swarm.dev/docs/guides/harness-configuration) · [Add a new provider →](https://docs.agent-swarm.dev/docs/guides/harness-providers)
131
131
  - **Follow-up continuity across all harnesses** — child tasks inherit a bounded prior-task context preamble built from the task chain, so continuity survives restarts and works the same across every provider. [Task lifecycle →](https://docs.agent-swarm.dev/docs/concepts/task-lifecycle)
132
132
  - **Skills & MCP servers** — reusable procedural knowledge, bundled skill reference files, and per-agent MCP servers with scope cascade. [MCP tools →](https://docs.agent-swarm.dev/docs/reference/mcp-tools)
133
133
  - **External tool-router access** — the `x` command and `swarm_x` MCP tool let humans and agents execute approved third-party routes such as Composio without baking bespoke MCP servers first. [CLI →](https://docs.agent-swarm.dev/docs/reference/cli) · [Composio →](https://docs.agent-swarm.dev/docs/integrations/composio)
@@ -140,7 +140,7 @@ Check [our templates](https://templates.agent-swarm.dev) for a quick start.
140
140
 
141
141
  Need help? Contact us at [contact@desplega.sh](mailto:contact@desplega.sh).
142
142
 
143
- **Prerequisites:** [Docker](https://docker.com) and a [Claude Code](https://docs.anthropic.com/en/docs/claude-code) OAuth token (`claude setup-token`).
143
+ **Prerequisites:** [Docker](https://docker.com) and at least one supported harness credential. The default quick start assumes a [Claude Code](https://docs.anthropic.com/en/docs/claude-code) OAuth token (`claude setup-token`), but pi-mono / Bedrock, Codex, Devin, and other provider setups are also supported.
144
144
 
145
145
  The fastest way is the onboarding wizard — it collects credentials, picks presets, and generates a working `docker-compose.yml`:
146
146
 
@@ -154,7 +154,7 @@ Prefer manual setup? Clone and run with Docker Compose:
154
154
  git clone https://github.com/desplega-ai/agent-swarm.git
155
155
  cd agent-swarm
156
156
  cp .env.docker.example .env
157
- # edit .env — set API_KEY and CLAUDE_CODE_OAUTH_TOKEN
157
+ # edit .env — set API_KEY plus the credential for your chosen harness (for example CLAUDE_CODE_OAUTH_TOKEN)
158
158
  docker compose -f docker-compose.example.yml --env-file .env up -d
159
159
  ```
160
160
 
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.94.0",
5
+ "version": "1.96.0",
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": [
@@ -981,6 +981,51 @@
981
981
  "post_task"
982
982
  ],
983
983
  "default": "boot"
984
+ },
985
+ "bedrock": {
986
+ "type": [
987
+ "object",
988
+ "null"
989
+ ],
990
+ "properties": {
991
+ "region": {
992
+ "type": "string"
993
+ },
994
+ "probedAt": {
995
+ "type": "number"
996
+ },
997
+ "ready": {
998
+ "type": "boolean"
999
+ },
1000
+ "models": {
1001
+ "type": "array",
1002
+ "items": {
1003
+ "type": "object",
1004
+ "properties": {
1005
+ "id": {
1006
+ "type": "string"
1007
+ },
1008
+ "name": {
1009
+ "type": "string"
1010
+ }
1011
+ },
1012
+ "required": [
1013
+ "id",
1014
+ "name"
1015
+ ]
1016
+ },
1017
+ "default": []
1018
+ },
1019
+ "error": {
1020
+ "type": "string"
1021
+ }
1022
+ },
1023
+ "default": null,
1024
+ "required": [
1025
+ "region",
1026
+ "probedAt",
1027
+ "ready"
1028
+ ]
984
1029
  }
985
1030
  },
986
1031
  "required": [
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@desplega.ai/agent-swarm",
3
- "version": "1.94.0",
3
+ "version": "1.96.0",
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>",
@@ -108,13 +108,14 @@
108
108
  "@ai-sdk/openai": "^3.0.41",
109
109
  "@anthropic-ai/sdk": "^0.93.0",
110
110
  "@asteasolutions/zod-to-openapi": "^8.0.0",
111
+ "@aws-sdk/client-bedrock": "3.1048.0",
111
112
  "@desplega.ai/business-use": "^0.4.2",
112
113
  "@desplega.ai/localtunnel": "^2.2.0",
113
- "@inkjs/ui": "^2.0.0",
114
- "@linear/sdk": "^77.0.0",
115
114
  "@earendil-works/pi-agent-core": "^0.79.1",
116
115
  "@earendil-works/pi-ai": "^0.79.1",
117
116
  "@earendil-works/pi-coding-agent": "^0.79.1",
117
+ "@inkjs/ui": "^2.0.0",
118
+ "@linear/sdk": "^77.0.0",
118
119
  "@modelcontextprotocol/sdk": "^1.25.1",
119
120
  "@openai/codex-sdk": "^0.139.0",
120
121
  "@opencode-ai/sdk": "^1.16.2",
@@ -0,0 +1,76 @@
1
+ /**
2
+ * One-time boot-scrub: retroactively sanitize session_logs rows that contain
3
+ * sensitive patterns (structural regex matches) which pre-date the defense-in-
4
+ * depth scrub added to createSessionLogs / task persistence paths.
5
+ *
6
+ * Idempotent: already-scrubbed rows are no-ops (scrubSecrets is idempotent).
7
+ * Uses seed_state to avoid re-scanning on subsequent boots.
8
+ */
9
+
10
+ import { scrubSecrets } from "../utils/secret-scrubber";
11
+ import { getDb } from "./db";
12
+
13
+ const SCRUB_KEY = "boot-scrub-logs-v2";
14
+ const BATCH_SIZE = 500;
15
+
16
+ export async function runBootScrubLogs(): Promise<void> {
17
+ const db = getDb();
18
+
19
+ const done = db
20
+ .prepare<{ key: string }, [string, string]>(
21
+ "SELECT key FROM seed_state WHERE kind = ? AND key = ?",
22
+ )
23
+ .get("maintenance", SCRUB_KEY);
24
+
25
+ if (done) return;
26
+
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();
40
+
41
+ if (rows.length === 0) {
42
+ markDone(db);
43
+ return;
44
+ }
45
+
46
+ console.log(`[boot-scrub-logs] starting: ${rows.length} candidate rows`);
47
+
48
+ const update = db.prepare("UPDATE session_logs SET content = ? WHERE id = ?");
49
+ let scrubbed = 0;
50
+
51
+ for (let i = 0; i < rows.length; i += BATCH_SIZE) {
52
+ const batch = rows.slice(i, i + BATCH_SIZE);
53
+ const tx = db.transaction(() => {
54
+ for (const row of batch) {
55
+ const cleaned = scrubSecrets(row.content);
56
+ if (cleaned !== row.content) {
57
+ update.run(cleaned, row.id);
58
+ scrubbed++;
59
+ }
60
+ }
61
+ });
62
+ tx();
63
+ }
64
+
65
+ markDone(db);
66
+ console.log(`[boot-scrub-logs] complete: scanned=${rows.length} scrubbed=${scrubbed}`);
67
+ }
68
+
69
+ function markDone(db: ReturnType<typeof getDb>) {
70
+ db.run(
71
+ `INSERT INTO seed_state (kind, key, seededHash, seededAt)
72
+ VALUES ('maintenance', ?, 'done', datetime('now'))
73
+ ON CONFLICT (kind, key) DO UPDATE SET seededHash = 'done', seededAt = datetime('now')`,
74
+ [SCRUB_KEY],
75
+ );
76
+ }
package/src/be/db.ts CHANGED
@@ -2100,7 +2100,7 @@ export function completeTask(id: string, output?: string): AgentTask | null {
2100
2100
  if (!row) return null;
2101
2101
 
2102
2102
  if (output) {
2103
- row = taskQueries.setOutput().get(output, id);
2103
+ row = taskQueries.setOutput().get(scrubSecrets(output), id);
2104
2104
  }
2105
2105
 
2106
2106
  if (row && oldTask) {
@@ -2141,7 +2141,8 @@ export function failTask(id: string, reason: string): AgentTask | null {
2141
2141
  }
2142
2142
 
2143
2143
  const finishedAt = new Date().toISOString();
2144
- const row = taskQueries.setFailure().get(reason, finishedAt, id);
2144
+ const scrubbedReason = scrubSecrets(reason);
2145
+ const row = taskQueries.setFailure().get(scrubbedReason, finishedAt, id);
2145
2146
  if (row && oldTask) {
2146
2147
  try {
2147
2148
  createLogEntry({
@@ -2150,7 +2151,7 @@ export function failTask(id: string, reason: string): AgentTask | null {
2150
2151
  agentId: row.agentId ?? undefined,
2151
2152
  oldValue: oldTask.status,
2152
2153
  newValue: "failed",
2153
- metadata: { reason },
2154
+ metadata: { reason: scrubbedReason },
2154
2155
  });
2155
2156
  } catch {}
2156
2157
  try {
@@ -2496,21 +2497,22 @@ export function deleteTask(id: string): boolean {
2496
2497
  }
2497
2498
 
2498
2499
  export function updateTaskProgress(id: string, progress: string): AgentTask | null {
2499
- const row = taskQueries.setProgress().get(progress, id);
2500
+ const scrubbedProgress = scrubSecrets(progress);
2501
+ const row = taskQueries.setProgress().get(scrubbedProgress, id);
2500
2502
  if (row) {
2501
2503
  try {
2502
2504
  createLogEntry({
2503
2505
  eventType: "task_progress",
2504
2506
  taskId: id,
2505
2507
  agentId: row.agentId ?? undefined,
2506
- newValue: progress,
2508
+ newValue: scrubbedProgress,
2507
2509
  });
2508
2510
  } catch {}
2509
2511
  try {
2510
2512
  import("../workflows/event-bus").then(({ workflowEventBus }) => {
2511
2513
  workflowEventBus.emit("task.progress", {
2512
2514
  taskId: id,
2513
- progress,
2515
+ progress: scrubbedProgress,
2514
2516
  agentId: row.agentId,
2515
2517
  });
2516
2518
  });
@@ -2791,6 +2793,7 @@ export function createLogEntry(entry: {
2791
2793
  metadata?: Record<string, unknown>;
2792
2794
  }): AgentLog {
2793
2795
  const id = crypto.randomUUID();
2796
+ const metaJson = entry.metadata ? JSON.stringify(entry.metadata) : null;
2794
2797
  const row = logQueries
2795
2798
  .insert()
2796
2799
  .get(
@@ -2799,8 +2802,8 @@ export function createLogEntry(entry: {
2799
2802
  entry.agentId ?? null,
2800
2803
  entry.taskId ?? null,
2801
2804
  entry.oldValue ?? null,
2802
- entry.newValue ?? null,
2803
- entry.metadata ? JSON.stringify(entry.metadata) : null,
2805
+ entry.newValue ? scrubSecrets(entry.newValue) : null,
2806
+ metaJson ? scrubSecrets(metaJson) : null,
2804
2807
  );
2805
2808
  if (!row) throw new Error("Failed to create log entry");
2806
2809
  return rowToAgentLog(row);
@@ -9437,6 +9440,7 @@ type McpServerRow = {
9437
9440
  headers: string | null;
9438
9441
  envConfigKeys: string | null;
9439
9442
  headerConfigKeys: string | null;
9443
+ extraAuthorizeParams: string | null;
9440
9444
  authMethod: string | null;
9441
9445
  isEnabled: number;
9442
9446
  version: number;
@@ -9468,6 +9472,7 @@ function rowToMcpServer(row: McpServerRow): McpServer {
9468
9472
  headers: row.headers,
9469
9473
  envConfigKeys: row.envConfigKeys,
9470
9474
  headerConfigKeys: row.headerConfigKeys,
9475
+ extraAuthorizeParams: row.extraAuthorizeParams,
9471
9476
  authMethod: (row.authMethod as McpServer["authMethod"]) ?? "static",
9472
9477
  isEnabled: row.isEnabled === 1,
9473
9478
  version: row.version,
@@ -9506,6 +9511,7 @@ export interface McpServerInsert {
9506
9511
  headers?: string;
9507
9512
  envConfigKeys?: string;
9508
9513
  headerConfigKeys?: string;
9514
+ extraAuthorizeParams?: string;
9509
9515
  }
9510
9516
 
9511
9517
  export function createMcpServer(data: McpServerInsert): McpServer {
@@ -9517,9 +9523,9 @@ export function createMcpServer(data: McpServerInsert): McpServer {
9517
9523
  `INSERT INTO mcp_servers (
9518
9524
  id, name, description, scope, ownerAgentId, transport,
9519
9525
  command, args, url, headers,
9520
- envConfigKeys, headerConfigKeys,
9526
+ envConfigKeys, headerConfigKeys, extraAuthorizeParams,
9521
9527
  isEnabled, version, createdAt, lastUpdatedAt
9522
- ) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, 1, 1, ?, ?) RETURNING *`,
9528
+ ) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, 1, 1, ?, ?) RETURNING *`,
9523
9529
  )
9524
9530
  .get(
9525
9531
  id,
@@ -9534,6 +9540,7 @@ export function createMcpServer(data: McpServerInsert): McpServer {
9534
9540
  data.headers ?? null,
9535
9541
  data.envConfigKeys ?? null,
9536
9542
  data.headerConfigKeys ?? null,
9543
+ data.extraAuthorizeParams ?? null,
9537
9544
  now,
9538
9545
  now,
9539
9546
  );
@@ -9596,6 +9603,10 @@ export function updateMcpServer(
9596
9603
  sets.push("headerConfigKeys = ?");
9597
9604
  params.push(updates.headerConfigKeys ?? null);
9598
9605
  }
9606
+ if (updates.extraAuthorizeParams !== undefined) {
9607
+ sets.push("extraAuthorizeParams = ?");
9608
+ params.push(updates.extraAuthorizeParams ?? null);
9609
+ }
9599
9610
  if (updates.isEnabled !== undefined) {
9600
9611
  sets.push("isEnabled = ?");
9601
9612
  params.push(updates.isEnabled ? 1 : 0);
@@ -9617,6 +9628,7 @@ export function updateMcpServer(
9617
9628
  "headers",
9618
9629
  "envConfigKeys",
9619
9630
  "headerConfigKeys",
9631
+ "extraAuthorizeParams",
9620
9632
  "transport",
9621
9633
  ];
9622
9634
  if (configFields.some((f) => (updates as Record<string, unknown>)[f] !== undefined)) {
@@ -0,0 +1,4 @@
1
+ -- Extra OAuth authorize-request params, applied at authorize time only.
2
+ -- JSON object string of flat string->string pairs, e.g. {"access_type":"offline","prompt":"consent"}.
3
+ -- NULL (default) => authorize URL is unchanged from today. Provider-agnostic.
4
+ ALTER TABLE mcp_servers ADD COLUMN extraAuthorizeParams TEXT;