@desplega.ai/agent-swarm 1.90.0 → 1.91.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.
- package/openapi.json +74 -1
- package/package.json +5 -5
- package/src/artifact-sdk/server.ts +2 -1
- package/src/be/memory/providers/sqlite-store.ts +6 -1
- package/src/be/memory/types.ts +1 -0
- package/src/be/scripts/typecheck.ts +132 -1
- package/src/be/seed-scripts/catalog/compound-insights.ts +188 -0
- package/src/be/seed-scripts/catalog/schedule-health.ts +73 -0
- package/src/be/seed-scripts/catalog/smart-recall.ts +65 -0
- package/src/be/seed-scripts/catalog/tool-usage.ts +56 -0
- package/src/be/seed-scripts/index.ts +36 -0
- package/src/commands/artifact.ts +3 -2
- package/src/commands/profile-sync.ts +310 -0
- package/src/commands/runner.ts +91 -1
- package/src/hooks/hook.ts +32 -9
- package/src/http/index.ts +47 -0
- package/src/http/integrations.ts +6 -1
- package/src/http/mcp-bridge.ts +117 -0
- package/src/http/mcp-oauth.ts +97 -39
- package/src/http/memory.ts +5 -2
- package/src/http/openapi.ts +2 -2
- package/src/http/pages-public.ts +10 -11
- package/src/http/pages.ts +7 -11
- package/src/http/scripts.ts +24 -1
- package/src/http/utils.ts +11 -4
- package/src/jira/app.ts +2 -3
- package/src/jira/webhook-lifecycle.ts +2 -1
- package/src/linear/app.ts +2 -3
- package/src/providers/claude-adapter.ts +26 -0
- package/src/scripts-runtime/executors/native.ts +1 -0
- package/src/scripts-runtime/sdk-allowlist.ts +121 -0
- package/src/scripts-runtime/swarm-sdk.ts +198 -3
- package/src/scripts-runtime/types/stdlib.d.ts +227 -0
- package/src/scripts-runtime/types/swarm-sdk.d.ts +227 -0
- package/src/tests/claude-adapter-otel.test.ts +85 -1
- package/src/tests/hook-registration-nudge.test.ts +69 -0
- package/src/tests/mcp-oauth-manual-client.test.ts +213 -0
- package/src/tests/pages-public-html.test.ts +41 -0
- package/src/tests/pages-public-json-redirect.test.ts +37 -2
- package/src/tests/profile-sync.test.ts +282 -0
- package/src/tests/scripts-runtime.test.ts +33 -0
- package/src/tests/seed-scripts.test.ts +2 -2
- package/src/tools/create-metric.ts +2 -3
- package/src/tools/create-page.ts +3 -6
- package/src/tools/memory-rate.ts +2 -1
- package/src/tools/memory-search.ts +1 -0
- package/src/tools/register-kapso-number.ts +2 -4
- package/src/tools/request-human-input.ts +2 -1
- package/src/tools/script-common.ts +2 -4
- package/src/tools/script-run.ts +7 -0
- package/src/utils/constants.ts +58 -8
- package/templates/skills/swarm-scripts/content.md +46 -7
|
@@ -5,6 +5,7 @@ import { assertSelectOnlyQuery } from "@/http/db-query";
|
|
|
5
5
|
import { snapshotMetric } from "@/metrics/version";
|
|
6
6
|
import { createToolRegistrar } from "@/tools/utils";
|
|
7
7
|
import { MetricDefinitionSchema } from "@/types";
|
|
8
|
+
import { getAppUrl } from "@/utils/constants";
|
|
8
9
|
|
|
9
10
|
function slugify(input: string): string {
|
|
10
11
|
const slug = input
|
|
@@ -16,9 +17,7 @@ function slugify(input: string): string {
|
|
|
16
17
|
}
|
|
17
18
|
|
|
18
19
|
function getAppBaseUrl(): string {
|
|
19
|
-
|
|
20
|
-
if (env) return env.replace(/\/+$/, "");
|
|
21
|
-
return "http://localhost:5274";
|
|
20
|
+
return getAppUrl();
|
|
22
21
|
}
|
|
23
22
|
|
|
24
23
|
function metricEditCounter(metricId: string): number {
|
package/src/tools/create-page.ts
CHANGED
|
@@ -23,6 +23,7 @@ import { createPage, getPage, getPageBySlug, getPageVersions, updatePage } from
|
|
|
23
23
|
import { snapshotPage } from "@/pages/version";
|
|
24
24
|
import { createToolRegistrar } from "@/tools/utils";
|
|
25
25
|
import { PageAuthModeSchema, PageContentTypeSchema } from "@/types";
|
|
26
|
+
import { getAppUrl, getPublicMcpBaseUrl } from "@/utils/constants";
|
|
26
27
|
|
|
27
28
|
/** Same slugifier used by the HTTP createPage handler. */
|
|
28
29
|
function slugify(input: string): string {
|
|
@@ -35,15 +36,11 @@ function slugify(input: string): string {
|
|
|
35
36
|
}
|
|
36
37
|
|
|
37
38
|
function getApiBaseUrl(): string {
|
|
38
|
-
|
|
39
|
-
if (env) return env.replace(/\/+$/, "");
|
|
40
|
-
return `http://localhost:${process.env.PORT || "3013"}`;
|
|
39
|
+
return getPublicMcpBaseUrl();
|
|
41
40
|
}
|
|
42
41
|
|
|
43
42
|
function getAppBaseUrl(): string {
|
|
44
|
-
|
|
45
|
-
if (env) return env.replace(/\/+$/, "");
|
|
46
|
-
return "http://localhost:5274";
|
|
43
|
+
return getAppUrl();
|
|
47
44
|
}
|
|
48
45
|
|
|
49
46
|
/**
|
package/src/tools/memory-rate.ts
CHANGED
|
@@ -3,6 +3,7 @@ import * as z from "zod";
|
|
|
3
3
|
import { REFERENCES_SOURCE_MAX_LENGTH, sanitizeReferencesSource } from "@/be/memory/raters/types";
|
|
4
4
|
import { createToolRegistrar } from "@/tools/utils";
|
|
5
5
|
import { getApiKey } from "@/utils/api-key";
|
|
6
|
+
import { getMcpBaseUrl } from "@/utils/constants";
|
|
6
7
|
|
|
7
8
|
/**
|
|
8
9
|
* Plan: thoughts/taras/plans/2026-05-05-memory-rater-v1.5/step-5.md §1
|
|
@@ -92,7 +93,7 @@ export const registerMemoryRateTool = (server: McpServer) => {
|
|
|
92
93
|
cleanedReferencesSource = cleaned;
|
|
93
94
|
}
|
|
94
95
|
|
|
95
|
-
const apiUrl =
|
|
96
|
+
const apiUrl = getMcpBaseUrl();
|
|
96
97
|
const apiKey = getApiKey();
|
|
97
98
|
|
|
98
99
|
const event = {
|
|
@@ -10,13 +10,11 @@ import {
|
|
|
10
10
|
putKapsoNumberMapping,
|
|
11
11
|
} from "@/integrations/kapso/config";
|
|
12
12
|
import { createToolRegistrar } from "@/tools/utils";
|
|
13
|
+
import { getPublicMcpBaseUrl } from "@/utils/constants";
|
|
13
14
|
|
|
14
15
|
/** Build the native inbound webhook URL the swarm exposes for Kapso deliveries. */
|
|
15
16
|
function nativeWebhookUrl(): string {
|
|
16
|
-
|
|
17
|
-
process.env.MCP_BASE_URL || `http://localhost:${process.env.PORT || "3013"}`
|
|
18
|
-
).replace(/\/+$/, "");
|
|
19
|
-
return `${base}/api/integrations/kapso/webhook`;
|
|
17
|
+
return `${getPublicMcpBaseUrl()}/api/integrations/kapso/webhook`;
|
|
20
18
|
}
|
|
21
19
|
|
|
22
20
|
export const registerRegisterKapsoNumberTool = (server: McpServer) => {
|
|
@@ -2,6 +2,7 @@ import type { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
|
|
|
2
2
|
import * as z from "zod";
|
|
3
3
|
import { createApprovalRequest, getAgentCurrentTask } from "@/be/db";
|
|
4
4
|
import { createToolRegistrar } from "@/tools/utils";
|
|
5
|
+
import { getAppUrl } from "@/utils/constants";
|
|
5
6
|
|
|
6
7
|
const QuestionSchema = z.object({
|
|
7
8
|
id: z.string().describe("Unique ID for the question (used as key in responses)"),
|
|
@@ -94,7 +95,7 @@ export const registerRequestHumanInputTool = (server: McpServer) => {
|
|
|
94
95
|
timeoutSeconds,
|
|
95
96
|
});
|
|
96
97
|
|
|
97
|
-
const appUrl =
|
|
98
|
+
const appUrl = getAppUrl();
|
|
98
99
|
const url = `${appUrl}/approval-requests/${request.id}`;
|
|
99
100
|
|
|
100
101
|
return {
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import type { CallToolResult } from "@modelcontextprotocol/sdk/types.js";
|
|
2
2
|
import * as z from "zod";
|
|
3
3
|
import { getApiKey } from "@/utils/api-key";
|
|
4
|
+
import { getMcpBaseUrl } from "@/utils/constants";
|
|
4
5
|
import type { RequestInfo } from "./utils";
|
|
5
6
|
|
|
6
7
|
export const SCRIPT_TRANSPORT_ERROR =
|
|
@@ -20,10 +21,7 @@ export const scriptToolOutputSchema = z.object({
|
|
|
20
21
|
export type ScriptToolStructuredContent = z.infer<typeof scriptToolOutputSchema>;
|
|
21
22
|
|
|
22
23
|
function apiBaseUrl(): string {
|
|
23
|
-
return (
|
|
24
|
-
/\/+$/,
|
|
25
|
-
"",
|
|
26
|
-
);
|
|
24
|
+
return getMcpBaseUrl();
|
|
27
25
|
}
|
|
28
26
|
|
|
29
27
|
function toolError(message: string, status = 400): CallToolResult {
|
package/src/tools/script-run.ts
CHANGED
|
@@ -28,6 +28,13 @@ export const registerScriptRunTool = (server: McpServer) => {
|
|
|
28
28
|
fsMode: scriptFsModeSchema
|
|
29
29
|
.default("none")
|
|
30
30
|
.describe("Filesystem mode. v1 supports none only."),
|
|
31
|
+
idempotencyKey: z
|
|
32
|
+
.string()
|
|
33
|
+
.max(200)
|
|
34
|
+
.optional()
|
|
35
|
+
.describe(
|
|
36
|
+
"When set, output is auto-persisted to kv under script:executions/{key}. Re-running with the same key overwrites. Queryable via kv-get.",
|
|
37
|
+
),
|
|
31
38
|
}),
|
|
32
39
|
outputSchema: scriptToolOutputSchema,
|
|
33
40
|
},
|
package/src/utils/constants.ts
CHANGED
|
@@ -3,19 +3,69 @@
|
|
|
3
3
|
*/
|
|
4
4
|
|
|
5
5
|
/**
|
|
6
|
-
* Default dashboard URL used when `APP_URL`
|
|
7
|
-
*
|
|
8
|
-
*
|
|
6
|
+
* Default dashboard URL used when neither `APP_URL` nor the deprecated
|
|
7
|
+
* `DASHBOARD_URL` is set. Points at the public production dashboard so links
|
|
8
|
+
* (Slack messages, approval URLs, page share links, post-OAuth redirects)
|
|
9
|
+
* stay renderable even when an operator forgets to configure it. Local dev
|
|
10
|
+
* should set `APP_URL` (e.g. in `.env`) to point at the local dashboard.
|
|
9
11
|
*/
|
|
10
12
|
export const DEFAULT_APP_URL = "https://app.agent-swarm.dev";
|
|
11
13
|
|
|
12
14
|
/**
|
|
13
|
-
* Resolve
|
|
14
|
-
*
|
|
15
|
+
* Resolve every explicitly configured app/dashboard URL. Each env var may be
|
|
16
|
+
* a comma-separated origin list; entries are returned in precedence order with
|
|
17
|
+
* trailing slashes stripped.
|
|
18
|
+
*
|
|
19
|
+
* Precedence: `APP_URL` entries → `DASHBOARD_URL` entries (deprecated alias,
|
|
20
|
+
* kept for back-compat).
|
|
21
|
+
*/
|
|
22
|
+
export function getConfiguredAppUrls(): string[] {
|
|
23
|
+
return [process.env.APP_URL, process.env.DASHBOARD_URL]
|
|
24
|
+
.flatMap((value) => (value ?? "").split(","))
|
|
25
|
+
.map((value) => value.trim().replace(/\/+$/, ""))
|
|
26
|
+
.filter(Boolean);
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
/**
|
|
30
|
+
* Resolve the effective app/dashboard URL — the public origin the user's
|
|
31
|
+
* browser is sent to (post-login redirects, Slack/approval links, page
|
|
32
|
+
* `app_url` share links). Trailing slashes are stripped.
|
|
33
|
+
*
|
|
34
|
+
* Precedence: first configured `APP_URL` entry → first configured
|
|
35
|
+
* `DASHBOARD_URL` entry (deprecated alias, kept for back-compat) → fallback.
|
|
36
|
+
* This is the single source of truth; call sites must not re-read
|
|
37
|
+
* `APP_URL`/`DASHBOARD_URL` directly.
|
|
38
|
+
*/
|
|
39
|
+
export function getAppUrl(fallback = DEFAULT_APP_URL): string {
|
|
40
|
+
return (getConfiguredAppUrls()[0] || fallback).replace(/\/+$/, "");
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
/**
|
|
44
|
+
* Internal API/MCP base URL — how workers/agents and in-process callers reach
|
|
45
|
+
* the API server. May be a private/cluster address (e.g. the Helm ClusterIP
|
|
46
|
+
* `http://<release>-api:3013`). Do NOT use for browser-facing or
|
|
47
|
+
* externally-registered URLs (OAuth redirect URIs, webhook URLs): those must
|
|
48
|
+
* resolve to a host the browser / third party can reach — use
|
|
49
|
+
* {@link getPublicMcpBaseUrl} (no request context) or `deriveApiBaseUrl(req)`
|
|
50
|
+
* (request-scoped) instead. Trailing slashes are stripped.
|
|
51
|
+
*/
|
|
52
|
+
export function getMcpBaseUrl(): string {
|
|
53
|
+
const raw = process.env.MCP_BASE_URL?.trim();
|
|
54
|
+
return (raw || `http://localhost:${process.env.PORT || "3013"}`).replace(/\/+$/, "");
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
/**
|
|
58
|
+
* Public, browser-/externally-reachable origin of the API server — where
|
|
59
|
+
* `/api/mcp-oauth/callback`, OAuth redirect URIs, and registered webhook URLs
|
|
60
|
+
* resolve. Falls back to {@link getMcpBaseUrl} when the public and internal
|
|
61
|
+
* hosts are the same (local dev, single-box, or an ngrok/tunnel set as
|
|
62
|
+
* `MCP_BASE_URL`). In split deployments (Helm), set `PUBLIC_MCP_BASE_URL` to
|
|
63
|
+
* the public ingress URL while `MCP_BASE_URL` stays the internal service
|
|
64
|
+
* address. Trailing slashes are stripped.
|
|
15
65
|
*/
|
|
16
|
-
export function
|
|
17
|
-
const raw = process.env.
|
|
18
|
-
return
|
|
66
|
+
export function getPublicMcpBaseUrl(): string {
|
|
67
|
+
const raw = process.env.PUBLIC_MCP_BASE_URL?.trim();
|
|
68
|
+
return raw ? raw.replace(/\/+$/, "") : getMcpBaseUrl();
|
|
19
69
|
}
|
|
20
70
|
|
|
21
71
|
/**
|
|
@@ -32,16 +32,18 @@ Use `script-query-types` before non-trivial work so the script matches the live
|
|
|
32
32
|
Use `script-run` with inline source for one-off work:
|
|
33
33
|
|
|
34
34
|
```typescript
|
|
35
|
-
export default async function main(args:
|
|
35
|
+
export default async function main(args: any, ctx: any) {
|
|
36
36
|
const { swarm, logger } = ctx;
|
|
37
|
-
|
|
38
|
-
|
|
37
|
+
// All SDK methods return Promise<unknown> — unwrap defensively.
|
|
38
|
+
const res: any = await swarm.task_list({ status: args?.status, limit: args?.limit ?? 50 });
|
|
39
|
+
const tasks: any[] = res?.data?.tasks ?? res?.tasks ?? [];
|
|
40
|
+
logger.info(`Fetched ${tasks.length} tasks`);
|
|
39
41
|
return {
|
|
40
|
-
total:
|
|
41
|
-
tasks:
|
|
42
|
+
total: tasks.length,
|
|
43
|
+
tasks: tasks.map((task: any) => ({
|
|
42
44
|
id: task.id,
|
|
43
45
|
status: task.status,
|
|
44
|
-
title: task.task
|
|
46
|
+
title: task.task?.slice(0, 120),
|
|
45
47
|
})),
|
|
46
48
|
};
|
|
47
49
|
}
|
|
@@ -60,8 +62,45 @@ Good named scripts:
|
|
|
60
62
|
- Fan out over many swarm tasks, memories, repos, or schedules.
|
|
61
63
|
- Convert noisy JSON or HTML into a compact summary.
|
|
62
64
|
|
|
65
|
+
## Using `db_query` For Aggregation
|
|
66
|
+
|
|
67
|
+
For scripts that aggregate over tasks, sessions, or memory, `ctx.swarm.db_query` with direct SQL is far more efficient than fetching lists client-side.
|
|
68
|
+
|
|
69
|
+
**The parameter is `sql`, not `query`:**
|
|
70
|
+
|
|
71
|
+
```typescript
|
|
72
|
+
// CORRECT
|
|
73
|
+
const res = await ctx.swarm.db_query({ sql: "SELECT status, count(*) as cnt FROM agent_tasks GROUP BY status" });
|
|
74
|
+
|
|
75
|
+
// WRONG — silently returns no data
|
|
76
|
+
const res = await ctx.swarm.db_query({ query: "SELECT ..." });
|
|
77
|
+
```
|
|
78
|
+
|
|
79
|
+
**`db_query` returns positional rows, not objects.** The response shape is `{ rows: unknown[][], columns: string[] }`. Zip them into objects:
|
|
80
|
+
|
|
81
|
+
```typescript
|
|
82
|
+
function rowsToObjects(res: any): any[] {
|
|
83
|
+
const p = res?.data ?? res;
|
|
84
|
+
const cols: string[] = p?.columns ?? [];
|
|
85
|
+
return (p?.rows ?? []).map((r: any) =>
|
|
86
|
+
Array.isArray(r) ? Object.fromEntries(cols.map((c, i) => [c, r[i]])) : r,
|
|
87
|
+
);
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
const rows = rowsToObjects(await ctx.swarm.db_query({
|
|
91
|
+
sql: `SELECT status, count(*) as cnt FROM agent_tasks WHERE createdAt > datetime('now','-3 days') GROUP BY status`,
|
|
92
|
+
}));
|
|
93
|
+
// rows = [{ status: "completed", cnt: 42 }, ...]
|
|
94
|
+
```
|
|
95
|
+
|
|
96
|
+
**Common tables:** `agent_tasks` (tasks), `session_logs` (tool call logs), `agent_memory` (memories), `scheduled_tasks` (schedules), `agents` (agent registry).
|
|
97
|
+
|
|
98
|
+
**`session_logs` has no `tool_name` column.** Tool names are embedded in the `content` JSON column. Extract them SQL-side with `instr`/`substr` or parse JSON in JS after fetching.
|
|
99
|
+
|
|
63
100
|
## SDK And Context Gotchas
|
|
64
101
|
|
|
102
|
+
- **`args` can be undefined.** When a script is called without arguments, `args` is `undefined`. Always guard: `argsSchema.safeParse(args || {})` or use optional chaining (`args?.field`).
|
|
103
|
+
- **All SDK methods return `Promise<unknown>`.** Never assume a specific return shape without defensive unwrapping (`res?.data?.tasks ?? res?.tasks ?? []`). Run `script-query-types` to see live type signatures — return types are `unknown` and actual shapes vary by endpoint.
|
|
65
104
|
- `agentId` is propagated to scripts via the `X-Agent-ID` header, so SDK calls run as the invoking agent.
|
|
66
105
|
- `taskId` is not ambient. If a script needs to call `ctx.swarm.task_storeProgress`, pass `taskId` explicitly in `args`.
|
|
67
106
|
- Scripts invoked from a workflow script node may run with a workflow identity rather than a human or worker agent identity.
|
|
@@ -73,7 +112,7 @@ Good named scripts:
|
|
|
73
112
|
Thread task identity explicitly:
|
|
74
113
|
|
|
75
114
|
```typescript
|
|
76
|
-
export default async function main(args: { taskId: string; items: string[] }, ctx) {
|
|
115
|
+
export default async function main(args: { taskId: string; items: string[] }, ctx: any) {
|
|
77
116
|
const { swarm } = ctx;
|
|
78
117
|
await swarm.task_storeProgress({
|
|
79
118
|
taskId: args.taskId,
|