@desplega.ai/agent-swarm 1.91.0 → 1.92.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +3 -2
- package/openapi.json +1005 -152
- package/package.json +6 -6
- package/plugin/skills/pages/SKILL.md +5 -2
- package/src/be/db.ts +662 -19
- package/src/be/memory/constants.ts +2 -1
- package/src/be/memory/providers/openai-embedding.ts +2 -5
- package/src/be/memory/providers/sqlite-store.ts +293 -76
- package/src/be/memory/types.ts +35 -0
- package/src/be/migrations/083_script_workflows.sql +51 -0
- package/src/be/migrations/084_script_run_journal_duration.sql +5 -0
- package/src/be/migrations/085_script_runs_kind.sql +9 -0
- package/src/be/migrations/086_pages_default_authed.sql +64 -0
- package/src/be/migrations/087_skill_files.sql +19 -0
- package/src/be/modelsdev-cache.json +42310 -38617
- package/src/be/scripts/typecheck.ts +49 -0
- package/src/be/seed-scripts/catalog/boot-triage.ts +221 -0
- package/src/be/seed-scripts/catalog/catalog-report.ts +457 -0
- package/src/be/seed-scripts/catalog/compound-insights.ts +310 -6
- package/src/be/seed-scripts/catalog/gh-pr-snapshot.ts +1 -1
- package/src/be/seed-scripts/catalog/memory-eval.ts +1059 -0
- package/src/be/seed-scripts/catalog/ops-catalog-audit.ts +506 -0
- package/src/be/seed-scripts/catalog/schedule-health.ts +78 -2
- package/src/be/seed-scripts/catalog/task-context-gathering.ts +92 -0
- package/src/be/seed-scripts/catalog/task-failure-audit.ts +48 -1
- package/src/be/seed-scripts/catalog/tool-usage.ts +6 -3
- package/src/be/seed-scripts/index.ts +51 -5
- package/src/be/seed-skills/index.ts +3 -3
- package/src/be/skill-sync.ts +91 -7
- package/src/be/swarm-config-guard.ts +17 -0
- package/src/commands/runner.ts +49 -4
- package/src/heartbeat/templates.ts +20 -16
- package/src/http/db-query.ts +20 -5
- package/src/http/index.ts +51 -7
- package/src/http/mcp-user.ts +23 -0
- package/src/http/mcp.ts +58 -0
- package/src/http/memory.ts +58 -0
- package/src/http/pages.ts +1 -1
- package/src/http/script-runs.ts +557 -0
- package/src/http/scripts.ts +39 -2
- package/src/http/skills.ts +225 -0
- package/src/prompts/session-templates.ts +24 -4
- package/src/providers/claude-adapter.ts +107 -28
- 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 +209 -0
- package/src/scripts-runtime/sdk-allowlist.ts +4 -0
- package/src/scripts-runtime/swarm-sdk.ts +13 -0
- package/src/scripts-runtime/types/stdlib.d.ts +61 -0
- package/src/scripts-runtime/types/swarm-sdk.d.ts +61 -0
- package/src/server.ts +4 -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 +271 -74
- package/src/tests/create-page-tool.test.ts +19 -2
- 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/heartbeat-checklist.test.ts +36 -0
- package/src/tests/mcp-tools.test.ts +6 -0
- package/src/tests/mcp-transport-gc.test.ts +58 -0
- package/src/tests/memory-health-endpoint.test.ts +78 -0
- package/src/tests/memory-store.test.ts +221 -1
- package/src/tests/pages-http.test.ts +20 -2
- package/src/tests/pages-storage.test.ts +26 -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 +102 -2
- package/src/tests/seed-scripts.test.ts +468 -3
- package/src/tests/skill-files-http.test.ts +171 -0
- package/src/tests/skill-files.test.ts +162 -0
- package/src/tests/skill-get-file-tool.test.ts +110 -0
- package/src/tests/skill-sync.test.ts +125 -6
- package/src/tests/slack-message-text.test.ts +250 -0
- package/src/tests/system-default-skills.test.ts +40 -0
- package/src/tools/create-page.ts +2 -2
- package/src/tools/db-query.ts +16 -6
- package/src/tools/script-runs.ts +123 -0
- package/src/tools/skills/index.ts +1 -0
- package/src/tools/skills/skill-get-file.ts +80 -0
- package/src/tools/slack-read.ts +12 -3
- package/src/tools/tool-config.ts +6 -2
- package/src/types.ts +72 -0
- 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/schedules/daily-blocker-digest/content.md +68 -54
- package/templates/schedules/daily-compounding-reflection/content.md +4 -4
- package/templates/schedules/daily-hn-briefing/content.md +5 -5
- package/templates/schedules/daily-workflow-health-audit/content.md +6 -6
- package/templates/schedules/gtm-weekly-review/content.md +9 -9
- package/templates/schedules/weekly-dependabot-triage/content.md +24 -20
- package/templates/skills/agentmail-sending/content.md +6 -7
- package/templates/skills/desloppify/content.md +8 -9
- package/templates/skills/jira-interaction/content.md +25 -33
- package/templates/skills/kapso-whatsapp/content.md +29 -30
- package/templates/skills/linear-interaction/content.md +8 -9
- package/templates/skills/pages/content.md +205 -55
- package/templates/skills/profile-corruption-escalation/content.md +44 -85
- package/templates/skills/script-workflows/config.json +14 -0
- package/templates/skills/script-workflows/content.md +68 -0
- package/templates/skills/sprite-cli/content.md +4 -5
- package/templates/skills/swarm-scripts/content.md +2 -3
- package/templates/skills/turso-interaction/content.md +14 -17
- package/templates/skills/workflow-iterate/content.md +38 -391
- package/templates/skills/x-api-interactions/content.md +4 -6
- package/templates/skills/scheduled-task-resilience/config.json +0 -14
- package/templates/skills/scheduled-task-resilience/content.md +0 -95
|
@@ -37,8 +37,10 @@ describe("system-default skills", () => {
|
|
|
37
37
|
const names = skills.map((skill) => skill.name);
|
|
38
38
|
|
|
39
39
|
expect(names).toContain("attio-interaction");
|
|
40
|
+
expect(names).toContain("script-workflows");
|
|
40
41
|
expect(names).toContain("swarm-scripts");
|
|
41
42
|
expect(skills.find((skill) => skill.name === "attio-interaction")?.systemDefault).toBe(true);
|
|
43
|
+
expect(skills.find((skill) => skill.name === "script-workflows")?.systemDefault).toBe(true);
|
|
42
44
|
expect(skills.find((skill) => skill.name === "swarm-scripts")?.systemDefault).toBe(true);
|
|
43
45
|
expect(skills.find((skill) => skill.name === "kv-storage")?.systemDefault).toBe(true);
|
|
44
46
|
expect(skills.find((skill) => skill.name === "pages")?.systemDefault).toBe(true);
|
|
@@ -48,6 +50,7 @@ describe("system-default skills", () => {
|
|
|
48
50
|
|
|
49
51
|
const defaults = getSystemDefaultSkills().map((skill) => skill.name);
|
|
50
52
|
expect(defaults).toContain("attio-interaction");
|
|
53
|
+
expect(defaults).toContain("script-workflows");
|
|
51
54
|
expect(defaults).toContain("swarm-scripts");
|
|
52
55
|
expect(defaults).toContain("kv-storage");
|
|
53
56
|
expect(defaults).toContain("pages");
|
|
@@ -78,6 +81,43 @@ describe("system-default skills", () => {
|
|
|
78
81
|
expect(skills.find((skill) => skill.id === manualDefault.id)?.isActive).toBe(true);
|
|
79
82
|
});
|
|
80
83
|
|
|
84
|
+
test("existing agents see swarm-scope skills without explicit install rows", () => {
|
|
85
|
+
const existingAgent = createAgent({
|
|
86
|
+
name: "Existing Swarm Skill Worker",
|
|
87
|
+
description: "Created before a swarm-scope skill",
|
|
88
|
+
role: "worker",
|
|
89
|
+
isLead: false,
|
|
90
|
+
status: "idle",
|
|
91
|
+
maxTasks: 1,
|
|
92
|
+
capabilities: [],
|
|
93
|
+
});
|
|
94
|
+
|
|
95
|
+
const swarmSkill = createSkill({
|
|
96
|
+
name: "manual-swarm-scope-skill",
|
|
97
|
+
description: "Manual swarm scope skill",
|
|
98
|
+
content:
|
|
99
|
+
"---\nname: manual-swarm-scope-skill\ndescription: Manual swarm scope skill\n---\nBody.",
|
|
100
|
+
type: "personal",
|
|
101
|
+
scope: "swarm",
|
|
102
|
+
systemDefault: false,
|
|
103
|
+
});
|
|
104
|
+
|
|
105
|
+
const installRow = getDb()
|
|
106
|
+
.prepare<{ count: number }, [string, string]>(
|
|
107
|
+
`SELECT COUNT(*) AS count
|
|
108
|
+
FROM agent_skills
|
|
109
|
+
WHERE agentId = ?
|
|
110
|
+
AND skillId = ?`,
|
|
111
|
+
)
|
|
112
|
+
.get(existingAgent.id, swarmSkill.id);
|
|
113
|
+
|
|
114
|
+
expect(installRow?.count ?? 0).toBe(0);
|
|
115
|
+
|
|
116
|
+
const skills = getAgentSkills(existingAgent.id);
|
|
117
|
+
expect(skills.map((skill) => skill.name)).toContain("manual-swarm-scope-skill");
|
|
118
|
+
expect(skills.find((skill) => skill.id === swarmSkill.id)?.isActive).toBe(true);
|
|
119
|
+
});
|
|
120
|
+
|
|
81
121
|
test("new agents get concrete agent_skills rows for system defaults", () => {
|
|
82
122
|
const beforeAgent = createAgent({
|
|
83
123
|
name: "Concrete Install Worker",
|
package/src/tools/create-page.ts
CHANGED
|
@@ -82,8 +82,8 @@ export const registerCreatePageTool = (server: McpServer) => {
|
|
|
82
82
|
contentType: PageContentTypeSchema.describe(
|
|
83
83
|
"'text/html' renders directly at /p/:id; 'application/json' is rendered by the SPA.",
|
|
84
84
|
),
|
|
85
|
-
authMode: PageAuthModeSchema.default("
|
|
86
|
-
"'
|
|
85
|
+
authMode: PageAuthModeSchema.default("authed").describe(
|
|
86
|
+
"'authed' — requires page-session cookie (default); 'public' — no gate and must be explicit; 'password' — requires key.",
|
|
87
87
|
),
|
|
88
88
|
password: z
|
|
89
89
|
.string()
|
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
|
|
|
@@ -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
|
+
};
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
export { registerSkillCreateTool } from "./skill-create";
|
|
2
2
|
export { registerSkillDeleteTool } from "./skill-delete";
|
|
3
3
|
export { registerSkillGetTool } from "./skill-get";
|
|
4
|
+
export { registerSkillGetFileTool } from "./skill-get-file";
|
|
4
5
|
export { registerSkillInstallTool } from "./skill-install";
|
|
5
6
|
export { registerSkillInstallRemoteTool } from "./skill-install-remote";
|
|
6
7
|
export { registerSkillListTool } from "./skill-list";
|
|
@@ -0,0 +1,80 @@
|
|
|
1
|
+
import type { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
|
|
2
|
+
import * as z from "zod";
|
|
3
|
+
import { getSkillById, getSkillFile } from "@/be/db";
|
|
4
|
+
import { createToolRegistrar } from "@/tools/utils";
|
|
5
|
+
|
|
6
|
+
export const registerSkillGetFileTool = (server: McpServer) => {
|
|
7
|
+
createToolRegistrar(server)(
|
|
8
|
+
"skill-get-file",
|
|
9
|
+
{
|
|
10
|
+
title: "Get Skill File",
|
|
11
|
+
annotations: { destructiveHint: false },
|
|
12
|
+
description:
|
|
13
|
+
"Fetch a bundled reference file from a complex skill by skillId and relative path. Use this when the file is not available on disk.",
|
|
14
|
+
inputSchema: z.object({
|
|
15
|
+
skillId: z.string().describe("Skill ID"),
|
|
16
|
+
path: z.string().describe("Relative path, e.g. references/animations.md"),
|
|
17
|
+
}),
|
|
18
|
+
outputSchema: z.object({
|
|
19
|
+
yourAgentId: z.string().uuid().optional(),
|
|
20
|
+
success: z.boolean(),
|
|
21
|
+
message: z.string(),
|
|
22
|
+
file: z.any().optional(),
|
|
23
|
+
}),
|
|
24
|
+
},
|
|
25
|
+
async (args, requestInfo, _meta) => {
|
|
26
|
+
const skill = getSkillById(args.skillId);
|
|
27
|
+
if (!skill) {
|
|
28
|
+
return {
|
|
29
|
+
content: [{ type: "text", text: "Skill not found." }],
|
|
30
|
+
structuredContent: {
|
|
31
|
+
yourAgentId: requestInfo.agentId,
|
|
32
|
+
success: false,
|
|
33
|
+
message: "Skill not found.",
|
|
34
|
+
},
|
|
35
|
+
};
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
let file = null;
|
|
39
|
+
try {
|
|
40
|
+
file = getSkillFile(args.skillId, args.path);
|
|
41
|
+
} catch (err) {
|
|
42
|
+
const message = err instanceof Error ? err.message : "Invalid file path.";
|
|
43
|
+
return {
|
|
44
|
+
content: [{ type: "text", text: message }],
|
|
45
|
+
structuredContent: {
|
|
46
|
+
yourAgentId: requestInfo.agentId,
|
|
47
|
+
success: false,
|
|
48
|
+
message,
|
|
49
|
+
},
|
|
50
|
+
};
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
if (!file) {
|
|
54
|
+
return {
|
|
55
|
+
content: [{ type: "text", text: "Skill file not found." }],
|
|
56
|
+
structuredContent: {
|
|
57
|
+
yourAgentId: requestInfo.agentId,
|
|
58
|
+
success: false,
|
|
59
|
+
message: "Skill file not found.",
|
|
60
|
+
},
|
|
61
|
+
};
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
return {
|
|
65
|
+
content: [
|
|
66
|
+
{
|
|
67
|
+
type: "text",
|
|
68
|
+
text: `Skill file "${skill.name}/${file.path}" (${file.mimeType}):\n\n${file.content}`,
|
|
69
|
+
},
|
|
70
|
+
],
|
|
71
|
+
structuredContent: {
|
|
72
|
+
yourAgentId: requestInfo.agentId,
|
|
73
|
+
success: true,
|
|
74
|
+
message: `Found skill file "${file.path}".`,
|
|
75
|
+
file,
|
|
76
|
+
},
|
|
77
|
+
};
|
|
78
|
+
},
|
|
79
|
+
);
|
|
80
|
+
};
|
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
|
@@ -128,11 +128,12 @@ export const DEFERRED_TOOLS = new Set([
|
|
|
128
128
|
// Approval Requests (1)
|
|
129
129
|
"request-human-input",
|
|
130
130
|
|
|
131
|
-
// Skills (
|
|
131
|
+
// Skills (12)
|
|
132
132
|
"skill-create",
|
|
133
133
|
"skill-update",
|
|
134
134
|
"skill-delete",
|
|
135
135
|
"skill-get",
|
|
136
|
+
"skill-get-file",
|
|
136
137
|
"skill-list",
|
|
137
138
|
"skill-search",
|
|
138
139
|
"skill-install",
|
|
@@ -164,12 +165,15 @@ export const DEFERRED_TOOLS = new Set([
|
|
|
164
165
|
"kv-incr",
|
|
165
166
|
"kv-list",
|
|
166
167
|
|
|
167
|
-
// Reusable scripts (
|
|
168
|
+
// Reusable scripts (8)
|
|
168
169
|
"script-search",
|
|
169
170
|
"script-run",
|
|
170
171
|
"script-upsert",
|
|
171
172
|
"script-delete",
|
|
172
173
|
"script-query-types",
|
|
174
|
+
"launch-script-run",
|
|
175
|
+
"get-script-run",
|
|
176
|
+
"list-script-runs",
|
|
173
177
|
|
|
174
178
|
// External command routes (1)
|
|
175
179
|
"swarm_x",
|
package/src/types.ts
CHANGED
|
@@ -1535,6 +1535,65 @@ 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
|
+
// `workflow` = durable background run launched via /api/script-runs (has a journal).
|
|
1559
|
+
// `inline` = synchronous one-off run via /api/scripts/run (no journal).
|
|
1560
|
+
export const ScriptRunKindSchema = z.enum(["workflow", "inline"]);
|
|
1561
|
+
export type ScriptRunKind = z.infer<typeof ScriptRunKindSchema>;
|
|
1562
|
+
|
|
1563
|
+
export const ScriptRunSchema = z.object({
|
|
1564
|
+
id: z.string().uuid(),
|
|
1565
|
+
agentId: z.string(),
|
|
1566
|
+
scriptName: z.string().optional(),
|
|
1567
|
+
source: z.string(),
|
|
1568
|
+
args: z.unknown(),
|
|
1569
|
+
kind: ScriptRunKindSchema,
|
|
1570
|
+
status: ScriptRunStatusSchema,
|
|
1571
|
+
pid: z.number().int().optional(),
|
|
1572
|
+
startedAt: z.string(),
|
|
1573
|
+
finishedAt: z.string().optional(),
|
|
1574
|
+
output: z.unknown().optional(),
|
|
1575
|
+
error: z.string().optional(),
|
|
1576
|
+
lastHeartbeatAt: z.string().optional(),
|
|
1577
|
+
idempotencyKey: z.string().optional(),
|
|
1578
|
+
requestedByUserId: z.string().optional(),
|
|
1579
|
+
});
|
|
1580
|
+
export type ScriptRun = z.infer<typeof ScriptRunSchema>;
|
|
1581
|
+
|
|
1582
|
+
export const ScriptRunJournalEntrySchema = z.object({
|
|
1583
|
+
id: z.string().uuid(),
|
|
1584
|
+
runId: z.string().uuid(),
|
|
1585
|
+
stepKey: z.string(),
|
|
1586
|
+
stepType: z.string(),
|
|
1587
|
+
config: z.record(z.string(), z.unknown()),
|
|
1588
|
+
status: z.enum(["completed", "failed"]),
|
|
1589
|
+
result: z.unknown().optional(),
|
|
1590
|
+
error: z.string().optional(),
|
|
1591
|
+
startedAt: z.string(),
|
|
1592
|
+
completedAt: z.string().optional(),
|
|
1593
|
+
durationMs: z.number().optional(),
|
|
1594
|
+
});
|
|
1595
|
+
export type ScriptRunJournalEntry = z.infer<typeof ScriptRunJournalEntrySchema>;
|
|
1596
|
+
|
|
1538
1597
|
// --- Workflow Run Step ---
|
|
1539
1598
|
|
|
1540
1599
|
export const WorkflowRunStepStatusSchema = z.enum([
|
|
@@ -1742,6 +1801,19 @@ export const SkillWithInstallInfoSchema = SkillSchema.extend({
|
|
|
1742
1801
|
});
|
|
1743
1802
|
export type SkillWithInstallInfo = z.infer<typeof SkillWithInstallInfoSchema>;
|
|
1744
1803
|
|
|
1804
|
+
export const SkillFileSchema = z.object({
|
|
1805
|
+
id: z.string(),
|
|
1806
|
+
skillId: z.string(),
|
|
1807
|
+
path: z.string(),
|
|
1808
|
+
content: z.string(),
|
|
1809
|
+
mimeType: z.string(),
|
|
1810
|
+
isBinary: z.boolean(),
|
|
1811
|
+
size: z.number().nullable(),
|
|
1812
|
+
createdAt: z.string(),
|
|
1813
|
+
lastUpdatedAt: z.string(),
|
|
1814
|
+
});
|
|
1815
|
+
export type SkillFile = z.infer<typeof SkillFileSchema>;
|
|
1816
|
+
|
|
1745
1817
|
// ── MCP Servers ──────────────────────────────────────────────────────────
|
|
1746
1818
|
|
|
1747
1819
|
export const McpServerTransportSchema = z.enum(["stdio", "http", "sse"]);
|
|
@@ -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, an absolute path,
|
|
89
|
+
// or a whitespace-separated command string.
|
|
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));
|