@desplega.ai/agent-swarm 1.90.0 → 1.92.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/README.md +2 -1
- package/openapi.json +803 -150
- package/package.json +5 -5
- package/src/artifact-sdk/server.ts +2 -1
- package/src/be/db.ts +337 -1
- package/src/be/memory/providers/sqlite-store.ts +6 -1
- package/src/be/memory/types.ts +1 -0
- package/src/be/migrations/083_script_workflows.sql +51 -0
- package/src/be/modelsdev-cache.json +42352 -38595
- package/src/be/scripts/typecheck.ts +181 -1
- package/src/be/seed-scripts/catalog/compound-insights.ts +398 -0
- package/src/be/seed-scripts/catalog/ops-catalog-audit.ts +911 -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/task-context-gathering.ts +92 -0
- package/src/be/seed-scripts/catalog/tool-usage.ts +59 -0
- package/src/be/seed-scripts/index.ts +54 -0
- package/src/be/seed-skills/index.ts +7 -0
- package/src/be/swarm-config-guard.ts +17 -0
- package/src/commands/artifact.ts +3 -2
- package/src/commands/profile-sync.ts +310 -0
- package/src/commands/runner.ts +134 -3
- package/src/hooks/hook.ts +32 -9
- package/src/http/db-query.ts +20 -5
- package/src/http/index.ts +57 -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/script-runs.ts +555 -0
- 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/prompts/session-templates.ts +24 -4
- package/src/providers/claude-adapter.ts +86 -13
- package/src/script-workflows/executor.ts +110 -0
- package/src/script-workflows/harness.ts +73 -0
- package/src/script-workflows/label-lint.ts +51 -0
- package/src/script-workflows/limits.ts +22 -0
- package/src/script-workflows/supervisor.ts +139 -0
- package/src/script-workflows/workflow-ctx.ts +205 -0
- package/src/scripts-runtime/executors/native.ts +1 -0
- package/src/scripts-runtime/sdk-allowlist.ts +124 -0
- package/src/scripts-runtime/swarm-sdk.ts +198 -3
- package/src/scripts-runtime/types/stdlib.d.ts +287 -0
- package/src/scripts-runtime/types/swarm-sdk.d.ts +287 -0
- package/src/server.ts +2 -0
- package/src/slack/handlers.ts +11 -4
- package/src/slack/message-text.ts +98 -0
- package/src/slack/thread-buffer.ts +5 -3
- package/src/tests/claude-adapter-binary.test.ts +147 -4
- package/src/tests/claude-adapter-otel.test.ts +85 -1
- package/src/tests/db-query.test.ts +28 -0
- package/src/tests/error-tracker.test.ts +121 -0
- package/src/tests/harness-provider-resolution.test.ts +33 -0
- package/src/tests/hook-registration-nudge.test.ts +69 -0
- package/src/tests/mcp-oauth-manual-client.test.ts +213 -0
- package/src/tests/mcp-tools.test.ts +6 -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/prompt-template-session.test.ts +34 -5
- package/src/tests/script-runs-http.test.ts +278 -0
- package/src/tests/script-workflows-label-lint.test.ts +43 -0
- package/src/tests/script-workflows-runtime-e2e.test.ts +170 -0
- package/src/tests/scripts-mcp-e2e.test.ts +49 -2
- package/src/tests/scripts-runtime.test.ts +33 -0
- package/src/tests/seed-scripts.test.ts +347 -2
- package/src/tests/slack-message-text.test.ts +250 -0
- package/src/tests/system-default-skills.test.ts +40 -0
- package/src/tools/create-metric.ts +2 -3
- package/src/tools/create-page.ts +3 -6
- package/src/tools/db-query.ts +16 -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/tools/script-runs.ts +123 -0
- package/src/tools/slack-read.ts +12 -3
- package/src/tools/tool-config.ts +4 -1
- package/src/types.ts +52 -0
- package/src/utils/constants.ts +58 -8
- package/src/utils/error-tracker.ts +40 -1
- package/src/utils/internal-ai/complete-structured.ts +10 -4
- package/src/workflows/executors/raw-llm.ts +76 -59
- package/templates/skills/pages/content.md +205 -55
- package/templates/skills/script-workflows/config.json +14 -0
- package/templates/skills/script-workflows/content.md +68 -0
- package/templates/skills/swarm-scripts/content.md +45 -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/db-query.ts
CHANGED
|
@@ -1,10 +1,21 @@
|
|
|
1
1
|
import type { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
|
|
2
2
|
import * as z from "zod";
|
|
3
|
-
import { executeReadOnlyQuery } from "@/http/db-query";
|
|
3
|
+
import { DbQueryInputShape, executeReadOnlyQuery, resolveDbQuerySql } from "@/http/db-query";
|
|
4
4
|
import { createToolRegistrar } from "@/tools/utils";
|
|
5
5
|
|
|
6
6
|
const MCP_MAX_ROWS = 100;
|
|
7
7
|
|
|
8
|
+
const DbQueryToolInputSchema = z
|
|
9
|
+
.object({
|
|
10
|
+
...DbQueryInputShape,
|
|
11
|
+
sql: z.string().optional().describe("SQL query (read-only only — writes are rejected)"),
|
|
12
|
+
query: z.string().optional().describe("Deprecated runtime alias for sql."),
|
|
13
|
+
params: z.array(z.any()).optional().default([]).describe("Query parameters"),
|
|
14
|
+
})
|
|
15
|
+
.refine((body) => body.sql !== undefined || body.query !== undefined, {
|
|
16
|
+
message: "Either sql or query is required",
|
|
17
|
+
});
|
|
18
|
+
|
|
8
19
|
export const registerDbQueryTool = (server: McpServer) => {
|
|
9
20
|
createToolRegistrar(server)(
|
|
10
21
|
"db-query",
|
|
@@ -13,10 +24,7 @@ export const registerDbQueryTool = (server: McpServer) => {
|
|
|
13
24
|
description:
|
|
14
25
|
"Execute a read-only SQL query against the swarm database. Available to all authenticated agents — be aware results may include secrets (oauth_tokens, configs). Results capped at 100 rows.",
|
|
15
26
|
annotations: { readOnlyHint: true },
|
|
16
|
-
inputSchema:
|
|
17
|
-
sql: z.string().describe("SQL query (read-only only — writes are rejected)"),
|
|
18
|
-
params: z.array(z.any()).optional().default([]).describe("Query parameters"),
|
|
19
|
-
}),
|
|
27
|
+
inputSchema: DbQueryToolInputSchema,
|
|
20
28
|
outputSchema: z.object({
|
|
21
29
|
success: z.boolean(),
|
|
22
30
|
columns: z.array(z.string()),
|
|
@@ -26,8 +34,10 @@ export const registerDbQueryTool = (server: McpServer) => {
|
|
|
26
34
|
truncated: z.boolean(),
|
|
27
35
|
}),
|
|
28
36
|
},
|
|
29
|
-
async (
|
|
37
|
+
async (input, _requestInfo, _meta) => {
|
|
30
38
|
try {
|
|
39
|
+
const sql = resolveDbQuerySql(input);
|
|
40
|
+
const params = input.params ?? [];
|
|
31
41
|
const result = executeReadOnlyQuery(sql, params, MCP_MAX_ROWS);
|
|
32
42
|
const truncated = result.total > MCP_MAX_ROWS;
|
|
33
43
|
|
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
|
},
|
|
@@ -0,0 +1,123 @@
|
|
|
1
|
+
import type { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
|
|
2
|
+
import * as z from "zod";
|
|
3
|
+
import { createToolRegistrar } from "@/tools/utils";
|
|
4
|
+
import { ScriptRunStatusSchema } from "@/types";
|
|
5
|
+
import { proxyScriptsApi, scriptNameSchema, scriptToolOutputSchema } from "./script-common";
|
|
6
|
+
|
|
7
|
+
export const LAUNCH_SCRIPT_RUN_DESCRIPTION =
|
|
8
|
+
"Launch a durable one-off script workflow run. The run executes in the background and can be inspected with get-script-run for terminal status and journal entries.";
|
|
9
|
+
|
|
10
|
+
export const GET_SCRIPT_RUN_DESCRIPTION =
|
|
11
|
+
"Get a durable script workflow run by ID, including its journal entries for swarm-script, raw-llm, and agent-task steps.";
|
|
12
|
+
|
|
13
|
+
export const LIST_SCRIPT_RUNS_DESCRIPTION =
|
|
14
|
+
"List durable script workflow runs, optionally filtered by status or agent ID.";
|
|
15
|
+
|
|
16
|
+
export const registerScriptRunsTools = (server: McpServer) => {
|
|
17
|
+
const register = createToolRegistrar(server);
|
|
18
|
+
|
|
19
|
+
register(
|
|
20
|
+
"launch-script-run",
|
|
21
|
+
{
|
|
22
|
+
title: "Launch Script Run",
|
|
23
|
+
description: LAUNCH_SCRIPT_RUN_DESCRIPTION,
|
|
24
|
+
annotations: { openWorldHint: true },
|
|
25
|
+
inputSchema: z.object({
|
|
26
|
+
source: z.string().min(1).describe("TypeScript script workflow source."),
|
|
27
|
+
args: z.unknown().optional().describe("JSON-serializable workflow arguments."),
|
|
28
|
+
idempotencyKey: z
|
|
29
|
+
.string()
|
|
30
|
+
.min(1)
|
|
31
|
+
.max(200)
|
|
32
|
+
.optional()
|
|
33
|
+
.describe("Optional key that returns the existing run instead of launching a duplicate."),
|
|
34
|
+
scriptName: scriptNameSchema
|
|
35
|
+
.optional()
|
|
36
|
+
.describe("Optional human-readable script/workflow name for the run."),
|
|
37
|
+
requestedByUserId: z
|
|
38
|
+
.string()
|
|
39
|
+
.optional()
|
|
40
|
+
.describe("Optional canonical user ID to attribute the run to."),
|
|
41
|
+
}),
|
|
42
|
+
outputSchema: scriptToolOutputSchema,
|
|
43
|
+
},
|
|
44
|
+
async (args, requestInfo) =>
|
|
45
|
+
proxyScriptsApi({
|
|
46
|
+
method: "POST",
|
|
47
|
+
path: "/api/script-runs",
|
|
48
|
+
body: { ...args, background: true },
|
|
49
|
+
requestInfo,
|
|
50
|
+
successMessage: (data) => {
|
|
51
|
+
const id =
|
|
52
|
+
typeof data === "object" && data !== null && "id" in data
|
|
53
|
+
? String((data as { id: unknown }).id)
|
|
54
|
+
: "unknown";
|
|
55
|
+
return `Script run launched: ${id}.`;
|
|
56
|
+
},
|
|
57
|
+
}),
|
|
58
|
+
);
|
|
59
|
+
|
|
60
|
+
register(
|
|
61
|
+
"get-script-run",
|
|
62
|
+
{
|
|
63
|
+
title: "Get Script Run",
|
|
64
|
+
description: GET_SCRIPT_RUN_DESCRIPTION,
|
|
65
|
+
annotations: { readOnlyHint: true, openWorldHint: false },
|
|
66
|
+
inputSchema: z.object({
|
|
67
|
+
id: z.string().uuid().describe("Script run ID."),
|
|
68
|
+
}),
|
|
69
|
+
outputSchema: scriptToolOutputSchema,
|
|
70
|
+
},
|
|
71
|
+
async ({ id }, requestInfo) =>
|
|
72
|
+
proxyScriptsApi({
|
|
73
|
+
method: "GET",
|
|
74
|
+
path: `/api/script-runs/${encodeURIComponent(id)}`,
|
|
75
|
+
requestInfo,
|
|
76
|
+
successMessage: (data) => {
|
|
77
|
+
const status =
|
|
78
|
+
typeof data === "object" &&
|
|
79
|
+
data !== null &&
|
|
80
|
+
"run" in data &&
|
|
81
|
+
typeof (data as { run?: { status?: unknown } }).run?.status === "string"
|
|
82
|
+
? (data as { run: { status: string } }).run.status
|
|
83
|
+
: "unknown";
|
|
84
|
+
return `Script run ${id} status: ${status}.`;
|
|
85
|
+
},
|
|
86
|
+
}),
|
|
87
|
+
);
|
|
88
|
+
|
|
89
|
+
register(
|
|
90
|
+
"list-script-runs",
|
|
91
|
+
{
|
|
92
|
+
title: "List Script Runs",
|
|
93
|
+
description: LIST_SCRIPT_RUNS_DESCRIPTION,
|
|
94
|
+
annotations: { readOnlyHint: true, openWorldHint: false },
|
|
95
|
+
inputSchema: z.object({
|
|
96
|
+
status: ScriptRunStatusSchema.optional().describe("Optional script run status filter."),
|
|
97
|
+
agentId: z.string().optional().describe("Optional agent ID filter."),
|
|
98
|
+
limit: z.number().int().min(1).max(500).default(50).describe("Maximum runs to return."),
|
|
99
|
+
offset: z.number().int().min(0).default(0).describe("Pagination offset."),
|
|
100
|
+
}),
|
|
101
|
+
outputSchema: scriptToolOutputSchema,
|
|
102
|
+
},
|
|
103
|
+
async ({ status, agentId, limit, offset }, requestInfo) => {
|
|
104
|
+
const params = new URLSearchParams();
|
|
105
|
+
if (status) params.set("status", status);
|
|
106
|
+
if (agentId) params.set("agentId", agentId);
|
|
107
|
+
params.set("limit", String(limit));
|
|
108
|
+
params.set("offset", String(offset));
|
|
109
|
+
return proxyScriptsApi({
|
|
110
|
+
method: "GET",
|
|
111
|
+
path: `/api/script-runs?${params.toString()}`,
|
|
112
|
+
requestInfo,
|
|
113
|
+
successMessage: (data) => {
|
|
114
|
+
const total =
|
|
115
|
+
typeof data === "object" && data !== null && "total" in data
|
|
116
|
+
? Number((data as { total: unknown }).total)
|
|
117
|
+
: 0;
|
|
118
|
+
return `Found ${Number.isFinite(total) ? total : 0} script run(s).`;
|
|
119
|
+
},
|
|
120
|
+
});
|
|
121
|
+
},
|
|
122
|
+
);
|
|
123
|
+
};
|
package/src/tools/slack-read.ts
CHANGED
|
@@ -3,6 +3,7 @@ import * as z from "zod";
|
|
|
3
3
|
import { getAgentById, getInboxMessageById, getTaskById } from "@/be/db";
|
|
4
4
|
import { getSlackApp } from "@/slack/app";
|
|
5
5
|
import { downloadFile } from "@/slack/files";
|
|
6
|
+
import { extractSlackMessageText } from "@/slack/message-text";
|
|
6
7
|
import { createToolRegistrar } from "@/tools/utils";
|
|
7
8
|
|
|
8
9
|
/**
|
|
@@ -203,6 +204,13 @@ export const registerSlackReadTool = (server: McpServer) => {
|
|
|
203
204
|
text?: string;
|
|
204
205
|
ts: string;
|
|
205
206
|
files?: RawFile[];
|
|
207
|
+
attachments?: Array<{
|
|
208
|
+
fallback?: string;
|
|
209
|
+
text?: string;
|
|
210
|
+
title?: string;
|
|
211
|
+
pretext?: string;
|
|
212
|
+
}>;
|
|
213
|
+
blocks?: unknown[];
|
|
206
214
|
};
|
|
207
215
|
|
|
208
216
|
let rawMessages: RawMessage[] = [];
|
|
@@ -267,8 +275,9 @@ export const registerSlackReadTool = (server: McpServer) => {
|
|
|
267
275
|
}> = [];
|
|
268
276
|
|
|
269
277
|
for (const m of rawMessages) {
|
|
270
|
-
// Include messages with text
|
|
271
|
-
|
|
278
|
+
// Include messages with text, attachments, blocks, or files
|
|
279
|
+
const extractedText = extractSlackMessageText(m);
|
|
280
|
+
if (!extractedText && (!m.files || m.files.length === 0)) continue;
|
|
272
281
|
|
|
273
282
|
const isBot =
|
|
274
283
|
m.user === botUserId || m.bot_id !== undefined || m.subtype === "bot_message";
|
|
@@ -330,7 +339,7 @@ export const registerSlackReadTool = (server: McpServer) => {
|
|
|
330
339
|
user: m.user,
|
|
331
340
|
username,
|
|
332
341
|
isBot,
|
|
333
|
-
text:
|
|
342
|
+
text: extractedText,
|
|
334
343
|
ts: m.ts,
|
|
335
344
|
files,
|
|
336
345
|
});
|
package/src/tools/tool-config.ts
CHANGED
|
@@ -164,12 +164,15 @@ export const DEFERRED_TOOLS = new Set([
|
|
|
164
164
|
"kv-incr",
|
|
165
165
|
"kv-list",
|
|
166
166
|
|
|
167
|
-
// Reusable scripts (
|
|
167
|
+
// Reusable scripts (8)
|
|
168
168
|
"script-search",
|
|
169
169
|
"script-run",
|
|
170
170
|
"script-upsert",
|
|
171
171
|
"script-delete",
|
|
172
172
|
"script-query-types",
|
|
173
|
+
"launch-script-run",
|
|
174
|
+
"get-script-run",
|
|
175
|
+
"list-script-runs",
|
|
173
176
|
|
|
174
177
|
// External command routes (1)
|
|
175
178
|
"swarm_x",
|
package/src/types.ts
CHANGED
|
@@ -1535,6 +1535,58 @@ export const WorkflowRunSchema = z.object({
|
|
|
1535
1535
|
});
|
|
1536
1536
|
export type WorkflowRun = z.infer<typeof WorkflowRunSchema>;
|
|
1537
1537
|
|
|
1538
|
+
// --- Script Workflow Runs ---
|
|
1539
|
+
|
|
1540
|
+
export const ScriptRunStatusSchema = z.enum([
|
|
1541
|
+
"running",
|
|
1542
|
+
"paused",
|
|
1543
|
+
"completed",
|
|
1544
|
+
"failed",
|
|
1545
|
+
"cancelled",
|
|
1546
|
+
"aborted_limit",
|
|
1547
|
+
]);
|
|
1548
|
+
export type ScriptRunStatus = z.infer<typeof ScriptRunStatusSchema>;
|
|
1549
|
+
|
|
1550
|
+
export const TERMINAL_SCRIPT_RUN_STATUSES = [
|
|
1551
|
+
"completed",
|
|
1552
|
+
"failed",
|
|
1553
|
+
"cancelled",
|
|
1554
|
+
"aborted_limit",
|
|
1555
|
+
] as const;
|
|
1556
|
+
export type TerminalScriptRunStatus = (typeof TERMINAL_SCRIPT_RUN_STATUSES)[number];
|
|
1557
|
+
|
|
1558
|
+
export const ScriptRunSchema = z.object({
|
|
1559
|
+
id: z.string().uuid(),
|
|
1560
|
+
agentId: z.string(),
|
|
1561
|
+
scriptName: z.string().optional(),
|
|
1562
|
+
source: z.string(),
|
|
1563
|
+
args: z.unknown(),
|
|
1564
|
+
status: ScriptRunStatusSchema,
|
|
1565
|
+
pid: z.number().int().optional(),
|
|
1566
|
+
startedAt: z.string(),
|
|
1567
|
+
finishedAt: z.string().optional(),
|
|
1568
|
+
output: z.unknown().optional(),
|
|
1569
|
+
error: z.string().optional(),
|
|
1570
|
+
lastHeartbeatAt: z.string().optional(),
|
|
1571
|
+
idempotencyKey: z.string().optional(),
|
|
1572
|
+
requestedByUserId: z.string().optional(),
|
|
1573
|
+
});
|
|
1574
|
+
export type ScriptRun = z.infer<typeof ScriptRunSchema>;
|
|
1575
|
+
|
|
1576
|
+
export const ScriptRunJournalEntrySchema = z.object({
|
|
1577
|
+
id: z.string().uuid(),
|
|
1578
|
+
runId: z.string().uuid(),
|
|
1579
|
+
stepKey: z.string(),
|
|
1580
|
+
stepType: z.string(),
|
|
1581
|
+
config: z.record(z.string(), z.unknown()),
|
|
1582
|
+
status: z.enum(["completed", "failed"]),
|
|
1583
|
+
result: z.unknown().optional(),
|
|
1584
|
+
error: z.string().optional(),
|
|
1585
|
+
startedAt: z.string(),
|
|
1586
|
+
completedAt: z.string().optional(),
|
|
1587
|
+
});
|
|
1588
|
+
export type ScriptRunJournalEntry = z.infer<typeof ScriptRunJournalEntrySchema>;
|
|
1589
|
+
|
|
1538
1590
|
// --- Workflow Run Step ---
|
|
1539
1591
|
|
|
1540
1592
|
export const WorkflowRunStepStatusSchema = z.enum([
|
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
|
/**
|
|
@@ -28,7 +28,46 @@ export const MAX_RATE_LIMIT_RESET_MS = 7 * 24 * 60 * 60 * 1000;
|
|
|
28
28
|
* "429 Too Many Requests"; does not match "No conversation found with session ID".
|
|
29
29
|
*/
|
|
30
30
|
export function isRateLimitMessage(s: string): boolean {
|
|
31
|
-
return
|
|
31
|
+
return (
|
|
32
|
+
/rate.?limit|hit your[\w\s-]*limit|usage[ _-]?limit|too many requests|\b429\b/i.test(s) ||
|
|
33
|
+
isCodexCreditsExhaustedMessage(s)
|
|
34
|
+
);
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
/**
|
|
38
|
+
* Detects Codex's workspace-credit-exhausted error, which surfaces as:
|
|
39
|
+
* "Your workspace is out of credits. Ask your workspace owner to refill in order to continue."
|
|
40
|
+
* This wording does not match the standard rate-limit patterns, so it needs its own predicate.
|
|
41
|
+
* Kept specific to avoid false positives — "refill" alone is intentionally excluded.
|
|
42
|
+
*/
|
|
43
|
+
export function isCodexCreditsExhaustedMessage(s: string): boolean {
|
|
44
|
+
return /out of credits|refill in order to continue|workspace owner to refill/i.test(s);
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
/** Default cooldown applied when a Codex OAuth slot returns a credits-exhausted error.
|
|
48
|
+
* The workspace credit cap is weekly, so a 2-hour cooldown is conservative but avoids
|
|
49
|
+
* the sawtooth of the 5-minute tier-3 fallback re-handing the dead slot every 5 minutes.
|
|
50
|
+
*/
|
|
51
|
+
export const CODEX_CREDITS_EXHAUSTED_COOLDOWN_MS = 2 * 60 * 60 * 1000; // 2h
|
|
52
|
+
|
|
53
|
+
/** Floor for the operator-tunable Codex credits cooldown — never shorter than the tier-3 fallback. */
|
|
54
|
+
export const MIN_CODEX_CREDITS_EXHAUSTED_COOLDOWN_MS = 5 * 60 * 1000; // 5m
|
|
55
|
+
|
|
56
|
+
/**
|
|
57
|
+
* Resolve the effective Codex credits-exhausted cooldown (ms) from a raw config
|
|
58
|
+
* value (string | number | undefined). Falls back to the default constant on
|
|
59
|
+
* absent / empty / non-finite / non-positive input, then clamps to
|
|
60
|
+
* [MIN_CODEX_CREDITS_EXHAUSTED_COOLDOWN_MS, MAX_RATE_LIMIT_RESET_MS].
|
|
61
|
+
* Pure + side-effect free so it's unit-testable and cheap to call.
|
|
62
|
+
*/
|
|
63
|
+
export function resolveCodexCreditsExhaustedCooldownMs(
|
|
64
|
+
raw: string | number | undefined | null,
|
|
65
|
+
): number {
|
|
66
|
+
if (raw === undefined || raw === null || raw === "") return CODEX_CREDITS_EXHAUSTED_COOLDOWN_MS;
|
|
67
|
+
const n =
|
|
68
|
+
typeof raw === "number" ? raw : /^\d+$/.test(raw.trim()) ? Number(raw.trim()) : Number.NaN;
|
|
69
|
+
if (!Number.isFinite(n) || n <= 0) return CODEX_CREDITS_EXHAUSTED_COOLDOWN_MS;
|
|
70
|
+
return Math.min(Math.max(n, MIN_CODEX_CREDITS_EXHAUSTED_COOLDOWN_MS), MAX_RATE_LIMIT_RESET_MS);
|
|
32
71
|
}
|
|
33
72
|
|
|
34
73
|
/**
|
|
@@ -84,10 +84,16 @@ async function defaultSpawnClaudeCli(
|
|
|
84
84
|
signal?: AbortSignal,
|
|
85
85
|
jsonSchema?: object,
|
|
86
86
|
): Promise<string> {
|
|
87
|
-
//
|
|
88
|
-
//
|
|
89
|
-
//
|
|
90
|
-
const
|
|
87
|
+
// SWARM_USE_CLAUDE_BRIDGE mirrors the main claude adapter's subscription-pool
|
|
88
|
+
// routing. Otherwise CLAUDE_BINARY may be a single binary ("claude", "shannon")
|
|
89
|
+
// or a whitespace-separated command string ("bunx @dexh/shannon").
|
|
90
|
+
const useClaudeBridge = ["true", "1"].includes(
|
|
91
|
+
(process.env.SWARM_USE_CLAUDE_BRIDGE ?? "").trim().toLowerCase(),
|
|
92
|
+
);
|
|
93
|
+
const claudeBinaryRaw = useClaudeBridge
|
|
94
|
+
? "claude-bridge"
|
|
95
|
+
: (process.env.CLAUDE_BINARY ?? "claude").trim();
|
|
96
|
+
const claudeBinaryArgv = (claudeBinaryRaw || "claude").split(/\s+/);
|
|
91
97
|
const cmd = [...claudeBinaryArgv, "-p", "--model", model, "--output-format", "json"];
|
|
92
98
|
if (jsonSchema) {
|
|
93
99
|
cmd.push("--json-schema", JSON.stringify(jsonSchema));
|