@desplega.ai/agent-swarm 1.93.0 → 1.95.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 (85) hide show
  1. package/README.md +2 -2
  2. package/openapi.json +180 -1
  3. package/package.json +4 -3
  4. package/src/be/db.ts +74 -9
  5. package/src/be/migrations/090_model_tiers.sql +2 -0
  6. package/src/be/migrations/091_seed_swarm_operations_metrics.sql +12 -0
  7. package/src/be/migrations/092_metrics_dashboard_combobox_filters.sql +68 -0
  8. package/src/be/migrations/093_slack_message_tracking.sql +6 -0
  9. package/src/be/migrations/094_mcp_extra_authorize_params.sql +4 -0
  10. package/src/be/migrations/runner.ts +52 -0
  11. package/src/be/modelsdev-cache.json +2060 -198
  12. package/src/be/scripts/boot-reembed.ts +74 -0
  13. package/src/be/scripts/db.ts +19 -3
  14. package/src/be/seed/index.ts +1 -1
  15. package/src/be/seed/registry.ts +2 -2
  16. package/src/be/seed/runner.ts +5 -5
  17. package/src/be/seed/types.ts +6 -1
  18. package/src/be/seed-pricing.ts +1 -0
  19. package/src/be/seed-scripts/index.ts +3 -2
  20. package/src/be/skill-sync.ts +4 -4
  21. package/src/be/swarm-config-guard.ts +8 -0
  22. package/src/commands/provider-credentials.ts +14 -8
  23. package/src/commands/runner.ts +84 -13
  24. package/src/http/index.ts +13 -2
  25. package/src/http/mcp-oauth.ts +14 -0
  26. package/src/http/metrics.ts +55 -6
  27. package/src/http/schedules.ts +16 -15
  28. package/src/http/script-runs.ts +7 -1
  29. package/src/http/scripts.ts +147 -1
  30. package/src/http/tasks.ts +7 -0
  31. package/src/model-tiers.ts +140 -0
  32. package/src/oauth/mcp-wrapper.ts +14 -0
  33. package/src/providers/claude-managed-models.ts +9 -0
  34. package/src/providers/codex-skill-resolver.ts +22 -8
  35. package/src/providers/opencode-adapter.ts +21 -2
  36. package/src/providers/pi-mono-adapter.ts +143 -26
  37. package/src/providers/types.ts +12 -0
  38. package/src/scheduler/scheduler.ts +22 -34
  39. package/src/server-user.ts +8 -2
  40. package/src/slack/responses.ts +39 -11
  41. package/src/slack/watcher.ts +121 -8
  42. package/src/tests/agents-list-model-display.test.ts +13 -0
  43. package/src/tests/aws-error-classifier.test.ts +148 -0
  44. package/src/tests/claude-managed-adapter.test.ts +12 -0
  45. package/src/tests/context-window.test.ts +7 -0
  46. package/src/tests/credential-check.test.ts +185 -46
  47. package/src/tests/harness-provider-resolution.test.ts +23 -0
  48. package/src/tests/http-api-integration.test.ts +19 -0
  49. package/src/tests/mcp-oauth-queries.test.ts +71 -1
  50. package/src/tests/mcp-oauth-wrapper.test.ts +109 -0
  51. package/src/tests/metrics-http.test.ts +137 -3
  52. package/src/tests/migration-046-budgets.test.ts +33 -0
  53. package/src/tests/migration-runner-regressions.test.ts +69 -0
  54. package/src/tests/model-control.test.ts +162 -46
  55. package/src/tests/opencode-adapter.test.ts +38 -1
  56. package/src/tests/pi-mono-adapter.test.ts +319 -0
  57. package/src/tests/provider-command-format.test.ts +12 -0
  58. package/src/tests/providers/pi-cost.test.ts +9 -0
  59. package/src/tests/runner-fallback-output.test.ts +50 -0
  60. package/src/tests/scripts-boot-reembed.test.ts +163 -0
  61. package/src/tests/scripts-embeddings.test.ts +90 -0
  62. package/src/tests/seed.test.ts +26 -1
  63. package/src/tests/session-costs-model-key-normalize.test.ts +2 -0
  64. package/src/tests/skill-fs-writer.test.ts +7 -1
  65. package/src/tests/skill-sync.test.ts +15 -3
  66. package/src/tests/slack-watcher.test.ts +66 -0
  67. package/src/tests/workflow-agent-task.test.ts +5 -2
  68. package/src/tests/workflow-validation-port-routing.test.ts +181 -0
  69. package/src/tools/mcp-servers/mcp-server-create.ts +7 -0
  70. package/src/tools/mcp-servers/mcp-server-update.ts +8 -0
  71. package/src/tools/memory-get.ts +11 -0
  72. package/src/tools/memory-search.ts +18 -0
  73. package/src/tools/schedules/create-schedule.ts +71 -70
  74. package/src/tools/schedules/update-schedule.ts +43 -31
  75. package/src/tools/send-task.ts +16 -5
  76. package/src/tools/task-action.ts +11 -3
  77. package/src/types.ts +30 -0
  78. package/src/utils/aws-error-classifier.ts +97 -0
  79. package/src/utils/context-window.ts +2 -0
  80. package/src/utils/credentials.test.ts +68 -0
  81. package/src/utils/credentials.ts +44 -3
  82. package/src/utils/pretty-print.ts +25 -10
  83. package/src/utils/skill-fs-writer.ts +11 -3
  84. package/src/workflows/engine.ts +3 -2
  85. package/src/workflows/executors/agent-task.ts +3 -1
package/README.md CHANGED
@@ -46,7 +46,7 @@
46
46
 
47
47
  ## What it does
48
48
 
49
- Agent Swarm runs a team of AI agents that coordinate autonomously. A **lead agent** receives tasks ( from Slack, GitHub, GitLab, Linear, Jira, email, or the API) breaks them down, and delegates to **worker agents** running in isolated environments (Docker). Workers execute tasks, ship solutions, and write their learnings back to a shared memory so the whole swarm gets smarter every session.
49
+ Agent Swarm runs a team of AI agents that coordinate autonomously. A **lead agent** receives tasks (from Slack, GitHub, GitLab, Linear, Jira, email, or the API), breaks them down, and delegates to **worker agents** running in isolated environments (Docker). Workers execute tasks, ship solutions, and write their learnings back to a shared memory so the whole swarm gets smarter every session.
50
50
 
51
51
  You can run agents for Marketing, Product, UX, Engineering, Support, Operations, HR, Finance, or any role you can think of. A centralized Lead coordinates them, and they share the learnings horizontally. That's the true difference between [*AI First*](https://www.pleasedontdeploy.com/i/197193364/ai-first) and [*AI Native*](https://www.pleasedontdeploy.com/i/197193364/third-the-ai-native-metamorphosis).
52
52
 
@@ -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. [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, 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)
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.93.0",
5
+ "version": "1.95.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": [
@@ -6894,6 +6894,15 @@
6894
6894
  "model": {
6895
6895
  "type": "string"
6896
6896
  },
6897
+ "modelTier": {
6898
+ "type": "string",
6899
+ "enum": [
6900
+ "smol",
6901
+ "regular",
6902
+ "smart",
6903
+ "ultra"
6904
+ ]
6905
+ },
6897
6906
  "scheduleType": {
6898
6907
  "type": "string",
6899
6908
  "enum": [
@@ -7141,6 +7150,19 @@
7141
7150
  "model": {
7142
7151
  "type": "string"
7143
7152
  },
7153
+ "modelTier": {
7154
+ "type": [
7155
+ "string",
7156
+ "null"
7157
+ ],
7158
+ "enum": [
7159
+ "smol",
7160
+ "regular",
7161
+ "smart",
7162
+ "ultra",
7163
+ null
7164
+ ]
7165
+ },
7144
7166
  "nextRunAt": {
7145
7167
  "type": [
7146
7168
  "string",
@@ -7297,6 +7319,14 @@
7297
7319
  "name": "agentId",
7298
7320
  "in": "query"
7299
7321
  },
7322
+ {
7323
+ "schema": {
7324
+ "type": "string"
7325
+ },
7326
+ "required": false,
7327
+ "name": "scriptName",
7328
+ "in": "query"
7329
+ },
7300
7330
  {
7301
7331
  "schema": {
7302
7332
  "type": "integer",
@@ -9249,6 +9279,143 @@
9249
9279
  }
9250
9280
  }
9251
9281
  },
9282
+ "/api/scripts": {
9283
+ "get": {
9284
+ "operationId": "scripts_list",
9285
+ "summary": "List saved scripts",
9286
+ "description": "Dashboard read: lean projection without source. Scratch scripts are excluded unless includeScratch=true.",
9287
+ "tags": [
9288
+ "Scripts"
9289
+ ],
9290
+ "security": [
9291
+ {
9292
+ "bearerAuth": []
9293
+ }
9294
+ ],
9295
+ "parameters": [
9296
+ {
9297
+ "schema": {
9298
+ "type": "string",
9299
+ "enum": [
9300
+ "global",
9301
+ "agent"
9302
+ ]
9303
+ },
9304
+ "required": false,
9305
+ "name": "scope",
9306
+ "in": "query"
9307
+ },
9308
+ {
9309
+ "schema": {
9310
+ "type": "string",
9311
+ "enum": [
9312
+ "true",
9313
+ "false"
9314
+ ]
9315
+ },
9316
+ "required": false,
9317
+ "name": "includeScratch",
9318
+ "in": "query"
9319
+ }
9320
+ ],
9321
+ "responses": {
9322
+ "200": {
9323
+ "description": "Saved scripts"
9324
+ },
9325
+ "400": {
9326
+ "description": "Validation error"
9327
+ }
9328
+ }
9329
+ }
9330
+ },
9331
+ "/api/scripts/type-defs": {
9332
+ "get": {
9333
+ "operationId": "scripts_type_defs",
9334
+ "summary": "Get script SDK and stdlib type definitions",
9335
+ "description": "Static .d.ts blobs for editor integration (e.g. Monaco extraLibs). Cacheable.",
9336
+ "tags": [
9337
+ "Scripts"
9338
+ ],
9339
+ "security": [
9340
+ {
9341
+ "bearerAuth": []
9342
+ }
9343
+ ],
9344
+ "responses": {
9345
+ "200": {
9346
+ "description": "SDK and stdlib type definition blobs"
9347
+ }
9348
+ }
9349
+ }
9350
+ },
9351
+ "/api/scripts/{id}": {
9352
+ "get": {
9353
+ "operationId": "scripts_get",
9354
+ "summary": "Get a saved script by id",
9355
+ "description": "Dashboard read: full record including source and parsed signature.",
9356
+ "tags": [
9357
+ "Scripts"
9358
+ ],
9359
+ "security": [
9360
+ {
9361
+ "bearerAuth": []
9362
+ }
9363
+ ],
9364
+ "parameters": [
9365
+ {
9366
+ "schema": {
9367
+ "type": "string",
9368
+ "format": "uuid"
9369
+ },
9370
+ "required": true,
9371
+ "name": "id",
9372
+ "in": "path"
9373
+ }
9374
+ ],
9375
+ "responses": {
9376
+ "200": {
9377
+ "description": "Script detail"
9378
+ },
9379
+ "404": {
9380
+ "description": "Script not found"
9381
+ }
9382
+ }
9383
+ }
9384
+ },
9385
+ "/api/scripts/{id}/versions": {
9386
+ "get": {
9387
+ "operationId": "scripts_versions",
9388
+ "summary": "List versions of a saved script",
9389
+ "description": "Dashboard read: version history, newest first.",
9390
+ "tags": [
9391
+ "Scripts"
9392
+ ],
9393
+ "security": [
9394
+ {
9395
+ "bearerAuth": []
9396
+ }
9397
+ ],
9398
+ "parameters": [
9399
+ {
9400
+ "schema": {
9401
+ "type": "string",
9402
+ "format": "uuid"
9403
+ },
9404
+ "required": true,
9405
+ "name": "id",
9406
+ "in": "path"
9407
+ }
9408
+ ],
9409
+ "responses": {
9410
+ "200": {
9411
+ "description": "Script versions"
9412
+ },
9413
+ "404": {
9414
+ "description": "Script not found"
9415
+ }
9416
+ }
9417
+ }
9418
+ },
9252
9419
  "/api/mcp-bridge": {
9253
9420
  "post": {
9254
9421
  "summary": "Generic MCP tool proxy for the scripts SDK bridge",
@@ -10745,6 +10912,18 @@
10745
10912
  },
10746
10913
  "requestedByUserId": {
10747
10914
  "type": "string"
10915
+ },
10916
+ "model": {
10917
+ "type": "string"
10918
+ },
10919
+ "modelTier": {
10920
+ "type": "string",
10921
+ "enum": [
10922
+ "smol",
10923
+ "regular",
10924
+ "smart",
10925
+ "ultra"
10926
+ ]
10748
10927
  }
10749
10928
  },
10750
10929
  "required": [
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@desplega.ai/agent-swarm",
3
- "version": "1.93.0",
3
+ "version": "1.95.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",
package/src/be/db.ts CHANGED
@@ -2,6 +2,7 @@ import { Database } from "bun:sqlite";
2
2
  import { parseProviderMeta } from "@/utils/provider-metadata.ts";
3
3
  import pkg from "../../package.json";
4
4
  import { addEyesReactionOnTaskStart } from "../github/task-reactions";
5
+ import { type ModelTier, parseModelTier } from "../model-tiers";
5
6
  import { configureDbResolver } from "../prompts/resolver";
6
7
  import type {
7
8
  ActiveSession,
@@ -1000,6 +1001,8 @@ type AgentTaskRow = {
1000
1001
  slackThreadTs: string | null;
1001
1002
  slackUserId: string | null;
1002
1003
  slackReplySent: number;
1004
+ slackProgressMessageTs: string | null;
1005
+ slackTreeRootMessageTs: string | null;
1003
1006
  vcsProvider: string | null;
1004
1007
  vcsRepo: string | null;
1005
1008
  vcsEventType: string | null;
@@ -1018,6 +1021,7 @@ type AgentTaskRow = {
1018
1021
  parentTaskId: string | null;
1019
1022
  claudeSessionId: string | null;
1020
1023
  model: string | null;
1024
+ modelTier: string | null;
1021
1025
  scheduleId: string | null;
1022
1026
  workflowRunId: string | null;
1023
1027
  workflowRunStepId: string | null;
@@ -1088,6 +1092,8 @@ function rowToAgentTask(row: AgentTaskRow): AgentTask {
1088
1092
  slackThreadTs: row.slackThreadTs ?? undefined,
1089
1093
  slackUserId: row.slackUserId ?? undefined,
1090
1094
  slackReplySent: !!row.slackReplySent,
1095
+ slackProgressMessageTs: row.slackProgressMessageTs ?? undefined,
1096
+ slackTreeRootMessageTs: row.slackTreeRootMessageTs ?? undefined,
1091
1097
  vcsProvider: (row.vcsProvider as "github" | "gitlab" | null) ?? undefined,
1092
1098
  vcsRepo: row.vcsRepo ?? undefined,
1093
1099
  vcsEventType: row.vcsEventType ?? undefined,
@@ -1105,7 +1111,8 @@ function rowToAgentTask(row: AgentTaskRow): AgentTask {
1105
1111
  dir: row.dir ?? undefined,
1106
1112
  parentTaskId: row.parentTaskId ?? undefined,
1107
1113
  claudeSessionId: row.claudeSessionId ?? undefined,
1108
- model: (row.model as "haiku" | "sonnet" | "opus" | "fable" | null) ?? undefined,
1114
+ model: row.model ?? undefined,
1115
+ modelTier: parseModelTier(row.modelTier) ?? undefined,
1109
1116
  scheduleId: row.scheduleId ?? undefined,
1110
1117
  workflowRunId: row.workflowRunId ?? undefined,
1111
1118
  workflowRunStepId: row.workflowRunStepId ?? undefined,
@@ -1161,6 +1168,7 @@ function rowToAgentTaskSummary(row: AgentTaskRow): AgentTaskSummary {
1161
1168
  parentTaskId: t.parentTaskId,
1162
1169
  scheduleId: t.scheduleId,
1163
1170
  model: t.model,
1171
+ modelTier: t.modelTier,
1164
1172
  provider: t.provider,
1165
1173
  requestedByUserId: t.requestedByUserId,
1166
1174
  progress: t.progress,
@@ -1363,6 +1371,30 @@ export function markTaskSlackReplySent(taskId: string): void {
1363
1371
  getDb().run(`UPDATE agent_tasks SET slackReplySent = 1 WHERE id = ?`, [taskId]);
1364
1372
  }
1365
1373
 
1374
+ export function setSlackMessageTracking(
1375
+ taskId: string,
1376
+ fields: {
1377
+ slackProgressMessageTs?: string | null;
1378
+ slackTreeRootMessageTs?: string | null;
1379
+ },
1380
+ ): void {
1381
+ const sets: string[] = [];
1382
+ const args: (string | null)[] = [];
1383
+
1384
+ if (Object.hasOwn(fields, "slackProgressMessageTs")) {
1385
+ sets.push("slackProgressMessageTs = ?");
1386
+ args.push(fields.slackProgressMessageTs ?? null);
1387
+ }
1388
+ if (Object.hasOwn(fields, "slackTreeRootMessageTs")) {
1389
+ sets.push("slackTreeRootMessageTs = ?");
1390
+ args.push(fields.slackTreeRootMessageTs ?? null);
1391
+ }
1392
+ if (sets.length === 0) return;
1393
+
1394
+ args.push(taskId);
1395
+ getDb().run(`UPDATE agent_tasks SET ${sets.join(", ")} WHERE id = ?`, args);
1396
+ }
1397
+
1366
1398
  export function getChildTasks(parentTaskId: string): AgentTask[] {
1367
1399
  return getDb()
1368
1400
  .prepare<AgentTaskRow, [string]>(
@@ -2850,6 +2882,7 @@ export interface CreateTaskOptions {
2850
2882
  dir?: string;
2851
2883
  parentTaskId?: string;
2852
2884
  model?: string;
2885
+ modelTier?: ModelTier;
2853
2886
  scheduleId?: string;
2854
2887
  workflowRunId?: string;
2855
2888
  workflowRunStepId?: string;
@@ -3056,9 +3089,9 @@ export function createTaskExtended(task: string, options?: CreateTaskOptions): A
3056
3089
  vcsProvider, vcsRepo, vcsEventType, vcsNumber, vcsCommentId, vcsAuthor, vcsUrl,
3057
3090
  vcsInstallationId, vcsNodeId,
3058
3091
  agentmailInboxId, agentmailMessageId, agentmailThreadId,
3059
- mentionMessageId, mentionChannelId, dir, parentTaskId, model, scheduleId,
3092
+ mentionMessageId, mentionChannelId, dir, parentTaskId, model, modelTier, scheduleId,
3060
3093
  workflowRunId, workflowRunStepId, outputSchema, followUpConfig, requestedByUserId, contextKey, swarmVersion, createdAt, lastUpdatedAt, created_by, updated_by
3061
- ) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?) RETURNING *`,
3094
+ ) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?) RETURNING *`,
3062
3095
  )
3063
3096
  .get(
3064
3097
  id,
@@ -3093,6 +3126,7 @@ export function createTaskExtended(task: string, options?: CreateTaskOptions): A
3093
3126
  options?.dir ?? null,
3094
3127
  options?.parentTaskId ?? null,
3095
3128
  options?.model ?? null,
3129
+ options?.modelTier ?? null,
3096
3130
  options?.scheduleId ?? null,
3097
3131
  options?.workflowRunId ?? null,
3098
3132
  options?.workflowRunStepId ?? null,
@@ -5266,6 +5300,7 @@ type ScheduledTaskRow = {
5266
5300
  lastErrorAt: string | null;
5267
5301
  lastErrorMessage: string | null;
5268
5302
  model: string | null;
5303
+ modelTier: string | null;
5269
5304
  scheduleType: string;
5270
5305
  createdAt: string;
5271
5306
  lastUpdatedAt: string;
@@ -5306,7 +5341,8 @@ function rowToScheduledTask(row: ScheduledTaskRow): ScheduledTask {
5306
5341
  consecutiveErrors: row.consecutiveErrors ?? 0,
5307
5342
  lastErrorAt: normalizeDate(row.lastErrorAt) ?? undefined,
5308
5343
  lastErrorMessage: row.lastErrorMessage ?? undefined,
5309
- model: (row.model as "haiku" | "sonnet" | "opus" | "fable" | null) ?? undefined,
5344
+ model: row.model ?? undefined,
5345
+ modelTier: parseModelTier(row.modelTier) ?? undefined,
5310
5346
  scheduleType: row.scheduleType as "recurring" | "one_time",
5311
5347
  createdAt: normalizeDateRequired(row.createdAt),
5312
5348
  lastUpdatedAt: normalizeDateRequired(row.lastUpdatedAt),
@@ -5401,6 +5437,7 @@ export interface CreateScheduledTaskData {
5401
5437
  createdByAgentId?: string;
5402
5438
  timezone?: string;
5403
5439
  model?: string;
5440
+ modelTier?: ModelTier;
5404
5441
  scheduleType?: "recurring" | "one_time";
5405
5442
  }
5406
5443
 
@@ -5413,8 +5450,8 @@ export function createScheduledTask(data: CreateScheduledTaskData): ScheduledTas
5413
5450
  `INSERT INTO scheduled_tasks (
5414
5451
  id, name, description, cronExpression, intervalMs, taskTemplate,
5415
5452
  taskType, tags, priority, targetAgentId, enabled, nextRunAt,
5416
- createdByAgentId, timezone, model, scheduleType, createdAt, lastUpdatedAt
5417
- ) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?) RETURNING *`,
5453
+ createdByAgentId, timezone, model, modelTier, scheduleType, createdAt, lastUpdatedAt
5454
+ ) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?) RETURNING *`,
5418
5455
  )
5419
5456
  .get(
5420
5457
  id,
@@ -5432,6 +5469,7 @@ export function createScheduledTask(data: CreateScheduledTaskData): ScheduledTas
5432
5469
  data.createdByAgentId ?? null,
5433
5470
  data.timezone ?? "UTC",
5434
5471
  data.model ?? null,
5472
+ data.modelTier ?? null,
5435
5473
  data.scheduleType ?? "recurring",
5436
5474
  now,
5437
5475
  now,
@@ -5459,6 +5497,7 @@ export interface UpdateScheduledTaskData {
5459
5497
  lastErrorAt?: string | null;
5460
5498
  lastErrorMessage?: string | null;
5461
5499
  model?: string | null;
5500
+ modelTier?: ModelTier | null;
5462
5501
  scheduleType?: "recurring" | "one_time";
5463
5502
  lastUpdatedAt?: string;
5464
5503
  }
@@ -5538,6 +5577,10 @@ export function updateScheduledTask(
5538
5577
  updates.push("model = ?");
5539
5578
  params.push(data.model);
5540
5579
  }
5580
+ if (data.modelTier !== undefined) {
5581
+ updates.push("modelTier = ?");
5582
+ params.push(data.modelTier);
5583
+ }
5541
5584
  if (data.scheduleType !== undefined) {
5542
5585
  updates.push("scheduleType = ?");
5543
5586
  params.push(data.scheduleType);
@@ -9394,6 +9437,7 @@ type McpServerRow = {
9394
9437
  headers: string | null;
9395
9438
  envConfigKeys: string | null;
9396
9439
  headerConfigKeys: string | null;
9440
+ extraAuthorizeParams: string | null;
9397
9441
  authMethod: string | null;
9398
9442
  isEnabled: number;
9399
9443
  version: number;
@@ -9425,6 +9469,7 @@ function rowToMcpServer(row: McpServerRow): McpServer {
9425
9469
  headers: row.headers,
9426
9470
  envConfigKeys: row.envConfigKeys,
9427
9471
  headerConfigKeys: row.headerConfigKeys,
9472
+ extraAuthorizeParams: row.extraAuthorizeParams,
9428
9473
  authMethod: (row.authMethod as McpServer["authMethod"]) ?? "static",
9429
9474
  isEnabled: row.isEnabled === 1,
9430
9475
  version: row.version,
@@ -9463,6 +9508,7 @@ export interface McpServerInsert {
9463
9508
  headers?: string;
9464
9509
  envConfigKeys?: string;
9465
9510
  headerConfigKeys?: string;
9511
+ extraAuthorizeParams?: string;
9466
9512
  }
9467
9513
 
9468
9514
  export function createMcpServer(data: McpServerInsert): McpServer {
@@ -9474,9 +9520,9 @@ export function createMcpServer(data: McpServerInsert): McpServer {
9474
9520
  `INSERT INTO mcp_servers (
9475
9521
  id, name, description, scope, ownerAgentId, transport,
9476
9522
  command, args, url, headers,
9477
- envConfigKeys, headerConfigKeys,
9523
+ envConfigKeys, headerConfigKeys, extraAuthorizeParams,
9478
9524
  isEnabled, version, createdAt, lastUpdatedAt
9479
- ) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, 1, 1, ?, ?) RETURNING *`,
9525
+ ) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, 1, 1, ?, ?) RETURNING *`,
9480
9526
  )
9481
9527
  .get(
9482
9528
  id,
@@ -9491,6 +9537,7 @@ export function createMcpServer(data: McpServerInsert): McpServer {
9491
9537
  data.headers ?? null,
9492
9538
  data.envConfigKeys ?? null,
9493
9539
  data.headerConfigKeys ?? null,
9540
+ data.extraAuthorizeParams ?? null,
9494
9541
  now,
9495
9542
  now,
9496
9543
  );
@@ -9553,6 +9600,10 @@ export function updateMcpServer(
9553
9600
  sets.push("headerConfigKeys = ?");
9554
9601
  params.push(updates.headerConfigKeys ?? null);
9555
9602
  }
9603
+ if (updates.extraAuthorizeParams !== undefined) {
9604
+ sets.push("extraAuthorizeParams = ?");
9605
+ params.push(updates.extraAuthorizeParams ?? null);
9606
+ }
9556
9607
  if (updates.isEnabled !== undefined) {
9557
9608
  sets.push("isEnabled = ?");
9558
9609
  params.push(updates.isEnabled ? 1 : 0);
@@ -9574,6 +9625,7 @@ export function updateMcpServer(
9574
9625
  "headers",
9575
9626
  "envConfigKeys",
9576
9627
  "headerConfigKeys",
9628
+ "extraAuthorizeParams",
9577
9629
  "transport",
9578
9630
  ];
9579
9631
  if (configFields.some((f) => (updates as Record<string, unknown>)[f] !== undefined)) {
@@ -11801,6 +11853,7 @@ export function getScriptRunByIdempotencyKey(idempotencyKey: string): ScriptRun
11801
11853
  export function listScriptRuns(opts?: {
11802
11854
  status?: ScriptRunStatus;
11803
11855
  agentId?: string;
11856
+ scriptName?: string;
11804
11857
  limit?: number;
11805
11858
  offset?: number;
11806
11859
  }): ScriptRunListItem[] {
@@ -11814,6 +11867,10 @@ export function listScriptRuns(opts?: {
11814
11867
  conditions.push("agentId = ?");
11815
11868
  params.push(opts.agentId);
11816
11869
  }
11870
+ if (opts?.scriptName) {
11871
+ conditions.push("scriptName = ?");
11872
+ params.push(opts.scriptName);
11873
+ }
11817
11874
 
11818
11875
  const limit = opts?.limit ?? 50;
11819
11876
  const offset = opts?.offset ?? 0;
@@ -11842,7 +11899,11 @@ export function listScriptRuns(opts?: {
11842
11899
  return rows.map(rowToScriptRunListItem);
11843
11900
  }
11844
11901
 
11845
- export function countScriptRuns(opts?: { status?: ScriptRunStatus; agentId?: string }): number {
11902
+ export function countScriptRuns(opts?: {
11903
+ status?: ScriptRunStatus;
11904
+ agentId?: string;
11905
+ scriptName?: string;
11906
+ }): number {
11846
11907
  const conditions: string[] = [];
11847
11908
  const params: string[] = [];
11848
11909
  if (opts?.status) {
@@ -11853,6 +11914,10 @@ export function countScriptRuns(opts?: { status?: ScriptRunStatus; agentId?: str
11853
11914
  conditions.push("agentId = ?");
11854
11915
  params.push(opts.agentId);
11855
11916
  }
11917
+ if (opts?.scriptName) {
11918
+ conditions.push("scriptName = ?");
11919
+ params.push(opts.scriptName);
11920
+ }
11856
11921
  const where = conditions.length > 0 ? `WHERE ${conditions.join(" AND ")}` : "";
11857
11922
  const row = getDb()
11858
11923
  .prepare<{ count: number }, string[]>(`SELECT COUNT(*) AS count FROM script_runs ${where}`)
@@ -0,0 +1,2 @@
1
+ ALTER TABLE agent_tasks ADD COLUMN modelTier TEXT;
2
+ ALTER TABLE scheduled_tasks ADD COLUMN modelTier TEXT;
@@ -0,0 +1,12 @@
1
+ -- Keep the seeded system dashboard aligned with the production swarm dashboard.
2
+ -- Migration 081 created the starter row; this forward migration updates both
3
+ -- fresh installs and existing databases without rewriting the applied seed.
4
+
5
+ UPDATE metrics
6
+ SET
7
+ title = 'Swarm operations overview',
8
+ description = 'A starter dashboard mixing raw SQL widgets with chart and table visualizations.',
9
+ definition = '{"version":1,"widgets":[{"id":"tasks-created-per-day","title":"Tasks created per day","description":"Daily task volume for the selected time range.","query":{"sql":"SELECT date(createdAt) AS day, COUNT(*) AS tasks FROM agent_tasks WHERE createdAt >= datetime(''now'', ?) GROUP BY day ORDER BY day","params":["{{rangeModifier}}"],"maxRows":100},"viz":{"type":"line","x":"day","y":"tasks","columns":[{"key":"day","label":"Day"},{"key":"tasks","label":"Tasks","format":"integer"}],"format":"integer"}},{"id":"usage-by-user","title":"Usage by user","description":"Tasks requested and session cost by user for the selected time range, requester filter, and agent filter.","query":{"sql":"SELECT COALESCE(u.name, ''Unassigned'') AS user, COUNT(DISTINCT t.id) AS tasks, ROUND(COALESCE(SUM(sc.totalCostUsd), 0), 4) AS cost_usd FROM agent_tasks t LEFT JOIN users u ON u.id = t.requestedByUserId LEFT JOIN session_costs sc ON sc.taskId = t.id WHERE t.createdAt >= datetime(''now'', ?) AND (? = '''' OR COALESCE(u.id, '''') = ? OR COALESCE(u.name, '''') LIKE ''%'' || ? || ''%'') AND (? = '''' OR COALESCE(t.agentId, '''') = ?) GROUP BY COALESCE(u.name, ''Unassigned'') ORDER BY cost_usd DESC, tasks DESC","params":["{{rangeModifier}}","{{userFilter}}","{{userFilter}}","{{userFilter}}","{{agentFilter}}","{{agentFilter}}"],"maxRows":100},"viz":{"type":"bar","x":"user","y":"tasks","columns":[{"key":"user","label":"User"},{"key":"tasks","label":"Tasks","format":"integer"},{"key":"cost_usd","label":"Cost","format":"currency"}],"format":"integer"}},{"id":"usage-by-model","title":"Usage by model","description":"Tasks, sessions, tokens, and total cost by model for the selected time range and agent filter.","query":{"sql":"SELECT model, COUNT(DISTINCT taskId) AS tasks, COUNT(*) AS sessions, SUM(inputTokens + outputTokens) AS tokens, ROUND(SUM(totalCostUsd), 2) AS cost_usd FROM session_costs WHERE createdAt >= datetime(''now'', ?) AND (? = '''' OR agentId = ?) GROUP BY model ORDER BY cost_usd DESC","params":["{{rangeModifier}}","{{agentFilter}}","{{agentFilter}}"],"maxRows":100},"viz":{"type":"bar","x":"model","y":"cost_usd","columns":[{"key":"model","label":"Model"},{"key":"tasks","label":"Tasks","format":"integer"},{"key":"sessions","label":"Sessions","format":"integer"},{"key":"tokens","label":"Tokens","format":"integer"},{"key":"cost_usd","label":"Cost","format":"currency"}],"format":"currency"}},{"id":"avg-cost-per-task-by-model","title":"Avg cost per task by model","description":"Average session cost per task, grouped by model, for the selected time range and agent filter.","query":{"sql":"SELECT model, COUNT(DISTINCT taskId) AS tasks, ROUND(SUM(totalCostUsd) * 1.0 / NULLIF(COUNT(DISTINCT taskId), 0), 4) AS avg_cost_per_task FROM session_costs WHERE createdAt >= datetime(''now'', ?) AND (? = '''' OR agentId = ?) GROUP BY model ORDER BY avg_cost_per_task DESC","params":["{{rangeModifier}}","{{agentFilter}}","{{agentFilter}}"],"maxRows":100},"viz":{"type":"bar","x":"model","y":"avg_cost_per_task","columns":[{"key":"model","label":"Model"},{"key":"tasks","label":"Tasks","format":"integer"},{"key":"avg_cost_per_task","label":"Avg cost / task","format":"currency"}],"format":"currency"}},{"id":"avg-task-time-by-model","title":"Avg task time by model","description":"Average wall-clock task duration (minutes) by model for finished tasks in the selected time range and agent filter.","query":{"sql":"SELECT model, COUNT(*) AS tasks, ROUND(AVG((julianday(finishedAt) - julianday(createdAt)) * 1440), 1) AS avg_minutes FROM agent_tasks WHERE finishedAt IS NOT NULL AND model IS NOT NULL AND createdAt >= datetime(''now'', ?) AND (? = '''' OR agentId = ?) GROUP BY model ORDER BY tasks DESC","params":["{{rangeModifier}}","{{agentFilter}}","{{agentFilter}}"],"maxRows":100},"viz":{"type":"bar","x":"model","y":"avg_minutes","columns":[{"key":"model","label":"Model"},{"key":"tasks","label":"Tasks","format":"integer"},{"key":"avg_minutes","label":"Avg time","format":"duration"}],"format":"duration"}},{"id":"cost-per-minute-by-model","title":"Cost per minute by model","description":"Total session cost divided by active session minutes, grouped by model. Active minutes = sum of session durations.","query":{"sql":"SELECT model, ROUND(SUM(durationMs) / 60000.0, 1) AS active_minutes, ROUND(SUM(totalCostUsd), 2) AS cost_usd, ROUND(SUM(totalCostUsd) / NULLIF(SUM(durationMs) / 60000.0, 0), 4) AS cost_per_minute FROM session_costs WHERE createdAt >= datetime(''now'', ?) AND (? = '''' OR agentId = ?) AND durationMs > 0 GROUP BY model ORDER BY cost_per_minute DESC","params":["{{rangeModifier}}","{{agentFilter}}","{{agentFilter}}"],"maxRows":100},"viz":{"type":"bar","x":"model","y":"cost_per_minute","columns":[{"key":"model","label":"Model"},{"key":"active_minutes","label":"Active minutes","format":"number"},{"key":"cost_usd","label":"Total cost","format":"currency"},{"key":"cost_per_minute","label":"Cost / min","format":"currency"}],"format":"currency"}},{"id":"cost-per-minute-by-agent","title":"Cost per minute by agent","description":"Total session cost divided by active session minutes, grouped by agent.","query":{"sql":"SELECT COALESCE(a.name, sc.agentId, ''unknown'') AS agent, ROUND(SUM(sc.durationMs) / 60000.0, 1) AS active_minutes, ROUND(SUM(sc.totalCostUsd), 2) AS cost_usd, ROUND(SUM(sc.totalCostUsd) / NULLIF(SUM(sc.durationMs) / 60000.0, 0), 4) AS cost_per_minute FROM session_costs sc LEFT JOIN agents a ON a.id = sc.agentId WHERE sc.createdAt >= datetime(''now'', ?) AND (? = '''' OR sc.agentId = ?) AND sc.durationMs > 0 GROUP BY agent ORDER BY cost_per_minute DESC","params":["{{rangeModifier}}","{{agentFilter}}","{{agentFilter}}"],"maxRows":100},"viz":{"type":"bar","x":"agent","y":"cost_per_minute","columns":[{"key":"agent","label":"Agent"},{"key":"active_minutes","label":"Active minutes","format":"number"},{"key":"cost_usd","label":"Total cost","format":"currency"},{"key":"cost_per_minute","label":"Cost / min","format":"currency"}],"format":"currency"}},{"id":"agent-performance","title":"Tasks, avg time & cost by agent","description":"Finished tasks per agent with average wall-clock duration and average cost per task for the selected time range.","query":{"sql":"SELECT COALESCE(a.name, t.agentId, ''unassigned'') AS agent, COUNT(*) AS tasks, ROUND(AVG((julianday(t.finishedAt) - julianday(t.createdAt)) * 1440), 1) AS avg_minutes, ROUND(COALESCE(SUM(sc.cost), 0), 2) AS cost_usd, ROUND(COALESCE(SUM(sc.cost), 0) / COUNT(*), 4) AS avg_cost_per_task, ROUND(COALESCE(SUM(sc.cost), 0) / NULLIF(SUM(sc.dur) / 60000.0, 0), 4) AS cost_per_minute FROM agent_tasks t LEFT JOIN agents a ON a.id = t.agentId LEFT JOIN (SELECT taskId, SUM(totalCostUsd) AS cost, SUM(durationMs) AS dur FROM session_costs GROUP BY taskId) sc ON sc.taskId = t.id WHERE t.finishedAt IS NOT NULL AND t.createdAt >= datetime(''now'', ?) AND (? = '''' OR COALESCE(t.agentId, '''') = ?) GROUP BY agent ORDER BY tasks DESC","params":["{{rangeModifier}}","{{agentFilter}}","{{agentFilter}}"],"maxRows":100},"viz":{"type":"table","columns":[{"key":"agent","label":"Agent"},{"key":"tasks","label":"Tasks","format":"integer"},{"key":"avg_minutes","label":"Avg time","format":"duration"},{"key":"avg_cost_per_task","label":"Avg cost / task","format":"currency"},{"key":"cost_usd","label":"Total cost","format":"currency"},{"key":"cost_per_minute","label":"Cost / min","format":"currency"}]}},{"id":"task-outcomes-by-day","title":"Task outcomes by day","description":"Completed and failed tasks for the selected time range.","query":{"sql":"SELECT date(finishedAt) AS day, SUM(CASE WHEN status = ''completed'' THEN 1 ELSE 0 END) AS completed, SUM(CASE WHEN status = ''failed'' THEN 1 ELSE 0 END) AS failed FROM agent_tasks WHERE finishedAt IS NOT NULL AND finishedAt >= datetime(''now'', ?) GROUP BY day ORDER BY day","params":["{{rangeModifier}}"],"maxRows":100},"viz":{"type":"multi-line","x":"day","series":["completed","failed"],"columns":[{"key":"day","label":"Day"},{"key":"completed","label":"Completed","format":"integer"},{"key":"failed","label":"Failed","format":"integer"}],"format":"integer"}},{"id":"recent-task-outcomes","title":"Recent task outcomes","description":"Task status breakdown for tasks created in the selected time range.","query":{"sql":"SELECT status, COUNT(*) AS tasks FROM agent_tasks WHERE createdAt >= datetime(''now'', ?) GROUP BY status ORDER BY tasks DESC","params":["{{rangeModifier}}"],"maxRows":100},"viz":{"type":"table","columns":[{"key":"status","label":"Status"},{"key":"tasks","label":"Tasks","format":"integer"}]}}],"variables":[{"key":"rangeModifier","label":"Time range","type":"select","defaultValue":"-30 days","options":[{"label":"Last 7 days","value":"-7 days"},{"label":"Last 30 days","value":"-30 days"},{"label":"Last 90 days","value":"-90 days"}]},{"key":"userFilter","label":"Requester user ID or name","type":"text","defaultValue":""},{"key":"agentFilter","label":"Agent ID","type":"text","defaultValue":""}],"layout":{"columns":3},"refreshSeconds":60}',
10
+ updatedAt = CURRENT_TIMESTAMP
11
+ WHERE agentId = 'system'
12
+ AND slug = 'swarm-operations-overview';
@@ -0,0 +1,68 @@
1
+ -- Convert the seeded system dashboard user/agent filters from free-text inputs
2
+ -- to dynamic select variables. The UI renders select variables with resolved
3
+ -- options as searchable comboboxes.
4
+
5
+ UPDATE metrics
6
+ SET
7
+ definition = json_set(
8
+ replace(
9
+ replace(
10
+ replace(
11
+ replace(
12
+ replace(
13
+ definition,
14
+ '? = '''' OR COALESCE(u.id, '''') = ? OR COALESCE(u.name, '''') LIKE ''%'' || ? || ''%''',
15
+ '? = ''all'' OR COALESCE(u.id, '''') = ? OR COALESCE(u.name, '''') LIKE ''%'' || ? || ''%'''
16
+ ),
17
+ '? = '''' OR agentId = ?',
18
+ '? = ''all'' OR agentId = ?'
19
+ ),
20
+ '? = '''' OR sc.agentId = ?',
21
+ '? = ''all'' OR sc.agentId = ?'
22
+ ),
23
+ '? = '''' OR COALESCE(t.agentId, '''') = ?',
24
+ '? = ''all'' OR COALESCE(t.agentId, '''') = ?'
25
+ ),
26
+ '? = '''' OR COALESCE(agentId, '''') = ?',
27
+ '? = ''all'' OR COALESCE(agentId, '''') = ?'
28
+ ),
29
+ '$.variables',
30
+ json('[
31
+ {
32
+ "key": "rangeModifier",
33
+ "label": "Time range",
34
+ "type": "select",
35
+ "defaultValue": "-30 days",
36
+ "options": [
37
+ { "label": "Last 7 days", "value": "-7 days" },
38
+ { "label": "Last 30 days", "value": "-30 days" },
39
+ { "label": "Last 90 days", "value": "-90 days" }
40
+ ]
41
+ },
42
+ {
43
+ "key": "userFilter",
44
+ "label": "Requester",
45
+ "type": "select",
46
+ "defaultValue": "all",
47
+ "optionsQuery": {
48
+ "sql": "SELECT id, label FROM (SELECT ''all'' AS id, ''All requesters'' AS label, 0 AS sort_key UNION ALL SELECT id, COALESCE(NULLIF(name, ''''), email, id) AS label, 1 AS sort_key FROM users WHERE id IS NOT NULL) ORDER BY sort_key, label COLLATE NOCASE",
49
+ "valueKey": "id",
50
+ "labelKey": "label"
51
+ }
52
+ },
53
+ {
54
+ "key": "agentFilter",
55
+ "label": "Agent",
56
+ "type": "select",
57
+ "defaultValue": "all",
58
+ "optionsQuery": {
59
+ "sql": "SELECT id, label FROM (SELECT ''all'' AS id, ''All agents'' AS label, 0 AS sort_key UNION ALL SELECT id, COALESCE(NULLIF(name, ''''), id) AS label, 1 AS sort_key FROM agents WHERE id IS NOT NULL) ORDER BY sort_key, label COLLATE NOCASE",
60
+ "valueKey": "id",
61
+ "labelKey": "label"
62
+ }
63
+ }
64
+ ]')
65
+ ),
66
+ updatedAt = CURRENT_TIMESTAMP
67
+ WHERE agentId = 'system'
68
+ AND slug = 'swarm-operations-overview';
@@ -0,0 +1,6 @@
1
+ -- Persist Slack message timestamps used by the watcher to update progress in place.
2
+ -- Without this, a server restart drops process-local maps and the next watcher
3
+ -- tick posts a duplicate progress/tree message in the same Slack thread.
4
+
5
+ ALTER TABLE agent_tasks ADD COLUMN slackProgressMessageTs TEXT;
6
+ ALTER TABLE agent_tasks ADD COLUMN slackTreeRootMessageTs TEXT;
@@ -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;