@desplega.ai/agent-swarm 1.145.0 → 1.147.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/dist/{acp-adapter-580t41c0.js → acp-adapter-9n319wqc.js} +61 -6
- package/dist/{actions-m662tjng.js → actions-2vxqvpr9.js} +9 -8
- package/dist/{app-zb3rndm8.js → app-w066xfy0.js} +6 -5
- package/dist/assistant-kt31dj2h.js +236 -0
- package/dist/{boot-reembed-072x00vn.js → boot-reembed-az5rassp.js} +7 -6
- package/dist/{boot-reembed-65zz2smt.js → boot-reembed-j3mm3rfz.js} +8 -7
- package/dist/{boot-scrub-logs-yz0ams04.js → boot-scrub-logs-b6h54817.js} +4 -4
- package/dist/{claude-adapter-paj270y2.js → claude-adapter-w1z04ann.js} +8 -6
- package/dist/{claude-managed-setup-w619jncn.js → claude-managed-setup-f5pmsyrp.js} +24 -3
- package/dist/{claude-sdk-session-swt80j9z.js → claude-sdk-session-f0cc1kec.js} +8 -6
- package/dist/{cli-dt553ktb.js → cli-0b6y7kxm.js} +1 -1
- package/dist/{cli-bxdhw615.js → cli-12fz972k.js} +4 -4
- package/dist/{cli-s2f20r5w.js → cli-1th1728g.js} +2 -2
- package/dist/{cli-rs6ec3b9.js → cli-3tgwnf43.js} +3 -3
- package/dist/{cli-gxnzc6wh.js → cli-4nr988h6.js} +1 -1
- package/dist/{cli-axs8e6d4.js → cli-53s590z8.js} +1 -1
- package/dist/{cli-ygbjp3z8.js → cli-54wwve05.js} +5 -4
- package/dist/{cli-v0asr5yv.js → cli-719k9j2c.js} +2 -2
- package/dist/{cli-anktweqr.js → cli-89xfd7f9.js} +21 -47
- package/dist/{cli-q34a0e10.js → cli-8v0g1yc8.js} +1 -1
- package/dist/cli-9d8ntetj.js +42 -0
- package/dist/{cli-z9fprcrf.js → cli-atcve9y8.js} +881 -738
- package/dist/{cli-jmb7g47h.js → cli-b5bq9crk.js} +14 -31
- package/dist/cli-d8brzxjb.js +190 -0
- package/dist/{cli-gzky0gkr.js → cli-de14znh8.js} +2 -2
- package/dist/{cli-xytwff7f.js → cli-dttph1bw.js} +2 -2
- package/dist/cli-ep19k3mb.js +19 -0
- package/dist/{cli-m1xa8be7.js → cli-f146mn5s.js} +7 -50
- package/dist/{cli-1fj0chdk.js → cli-f7dsy61y.js} +5 -3
- package/dist/{cli-f567d5c1.js → cli-ftht3zzj.js} +2782 -2589
- package/dist/{cli-a0g1gbt5.js → cli-hbv0kq6w.js} +2 -2
- package/dist/{cli-fhsz3twr.js → cli-jsp14b6c.js} +171 -1
- package/dist/{cli-7fnkj1hj.js → cli-kpv03zhj.js} +21 -17
- package/dist/{cli-m4j6trn4.js → cli-m8yrsg97.js} +2 -2
- package/dist/cli-m9sxbkhm.js +779 -0
- package/dist/cli-mmemxxdc.js +823 -0
- package/dist/{cli-tqtvj5pt.js → cli-n8508nre.js} +1 -1
- package/dist/{cli-z96t0thj.js → cli-p1d5b073.js} +37 -2
- package/dist/{cli-gh8y8f36.js → cli-rw51bq3j.js} +13 -10
- package/dist/{cli-59ej1c07.js → cli-smby5m17.js} +7 -6
- package/dist/{cli-65vqzz3v.js → cli-tzvk9haz.js} +94 -372
- package/dist/{cli-dc9k2ba9.js → cli-w9kb3bk1.js} +1 -1
- package/dist/{cli-kpev657q.js → cli-y6b6fb76.js} +25 -3
- package/dist/{cli-123q2get.js → cli-yz3djrm0.js} +9 -9
- package/dist/{cli-wvp6bvy4.js → cli-zhjmqxzh.js} +1 -1
- package/dist/cli.js +13 -13
- package/dist/{codex-adapter-k98vaw2r.js → codex-adapter-zw4dwsx7.js} +2 -2
- package/dist/{codex-hook-9pf19cks.js → codex-hook-bw8p5nh4.js} +2 -2
- package/dist/{codex-session-runner-tngtphx7.js → codex-session-runner-k981s65v.js} +2 -2
- package/dist/{commands-y8nbr3fv.js → commands-sykrxx7e.js} +4 -4
- package/dist/{db-30p4h2t1.js → db-sfbsc8wt.js} +6 -4
- package/dist/handlers-0aspcejm.js +603 -0
- package/dist/{hook-dcw4jfbj.js → hook-4nvqj2sb.js} +7 -5
- package/dist/{hook-21jexvfx.js → hook-hxccs7m5.js} +24 -49
- package/dist/{http-r4kq5g8d.js → http-y06z517d.js} +230 -94
- package/dist/{index-q3qja6ta.js → index-hssww7kj.js} +15 -14
- package/dist/{index-gay66b9w.js → index-k8znfx0z.js} +13 -12
- package/dist/{index-53zyht4z.js → index-tce1rss4.js} +371 -110
- package/dist/{index-nqvf26za.js → index-x5j894zf.js} +14 -13
- package/dist/{keepalive-xs8fa55y.js → keepalive-w733ax66.js} +6 -6
- package/dist/{lead-d9m2t4s0.js → lead-75bjqa9d.js} +29 -27
- package/dist/{maintenance-3zh82g6z.js → maintenance-w4f1zjck.js} +8 -7
- package/dist/{oauth-refresh-sweep-03snsye5.js → oauth-refresh-sweep-wdetky26.js} +5 -5
- package/dist/{onboard-0cg1925x.js → onboard-bb5net8s.js} +2 -2
- package/dist/{otel-impl-0ab95z27.js → otel-impl-0w7c14ap.js} +1 -1
- package/dist/{pi-mono-adapter-dtkz4e7j.js → pi-mono-adapter-2has4gnp.js} +12 -2
- package/dist/{pricing-refresh-e5jhzz4j.js → pricing-refresh-9nzsrgcq.js} +6 -6
- package/dist/{rbac-roles-p1mf40z3.js → rbac-roles-r3hqjp09.js} +5 -5
- package/dist/{rbac-roles-2w3cmrkc.js → rbac-roles-z1jrffbt.js} +4 -4
- package/dist/{registry-9kdck5ye.js → registry-v5wytg0s.js} +1 -2
- package/dist/{render-v2-xhawry3k.js → render-v2-vbr89fnb.js} +6 -5
- package/dist/{seed-pricing-t223sw91.js → seed-pricing-g62hy1pk.js} +7 -5
- package/dist/{setup-zcs0rce7.js → setup-gnfqnptp.js} +2 -2
- package/dist/{worker-qnp2ka62.js → worker-kznxp4qm.js} +29 -27
- package/openapi.json +263 -12
- package/package.json +1 -1
- package/plugin/commands/start-worker.md +4 -9
- package/plugin/pi-skills/start-worker/SKILL.md +4 -9
- package/src/be/db/agents.ts +556 -0
- package/src/be/db/context-versions.ts +117 -0
- package/src/be/db/runtime.ts +406 -0
- package/src/be/db/tasks/read.ts +1167 -0
- package/src/be/db/tasks/write.ts +953 -0
- package/src/be/db.ts +584 -3429
- package/src/be/migrations/147_scheduled_tasks_parent_task.sql +7 -0
- package/src/be/migrations/148_pages_svg.sql +123 -0
- package/src/be/migrations/149_session_tokens.sql +24 -0
- package/src/be/scripts/typecheck.ts +1 -0
- package/src/be/seed-pricing.ts +13 -8
- package/src/be/seed-scripts/catalog/ops-catalog-audit.inline.ts +1 -1
- package/src/be/seed-scripts/catalog/ops-catalog-audit.ts +1 -1
- package/src/be/seed-scripts/catalog/star-history-refresh.ts +101 -0
- package/src/be/seed-scripts/catalog/star-history-renderer.ts +119 -0
- package/src/be/seed-scripts/index.ts +17 -0
- package/src/be/seed-skills/index.ts +14 -0
- package/src/be/swarm-config-guard.ts +5 -2
- package/src/be/task-attachment-store.ts +70 -0
- package/src/be/users.ts +77 -0
- package/src/commands/attachments-section.ts +37 -0
- package/src/commands/claude-managed-setup.ts +45 -3
- package/src/commands/claude-md-session.ts +408 -0
- package/src/commands/context-preamble.ts +15 -3
- package/src/commands/profile-sync.ts +205 -5
- package/src/commands/provider-credentials.ts +60 -27
- package/src/commands/runner.ts +63 -58
- package/src/commands/templates.ts +25 -2
- package/src/hooks/hook.ts +26 -67
- package/src/http/agents.ts +40 -1
- package/src/http/auth.ts +13 -1
- package/src/http/fs.ts +21 -51
- package/src/http/index.ts +2 -0
- package/src/http/mcp.ts +32 -0
- package/src/http/pages-public.ts +20 -4
- package/src/http/pages.ts +11 -3
- package/src/http/poll.ts +4 -0
- package/src/http/repos.ts +3 -3
- package/src/http/sessions.ts +82 -0
- package/src/http/tasks.ts +10 -2
- package/src/http/utils.ts +79 -1
- package/src/prompts/session-templates.ts +12 -8
- package/src/providers/acp-adapter.ts +62 -8
- package/src/providers/pi-mono-adapter.ts +23 -6
- package/src/providers/pricing-sources.md +11 -0
- package/src/scheduler/scheduler.test.ts +49 -2
- package/src/scheduler/scheduler.ts +4 -0
- package/src/scripts-runtime/sdk-allowlist.ts +1 -0
- package/src/scripts-runtime/types/stdlib.d.ts +8 -0
- package/src/scripts-runtime/types/swarm-sdk.d.ts +8 -0
- package/src/server.ts +2 -0
- package/src/slack/assistant.ts +87 -46
- package/src/slack/handlers.test.ts +1 -3
- package/src/slack/handlers.ts +102 -87
- package/src/slack/inbound-files.ts +466 -0
- package/src/slack/templates.ts +14 -0
- package/src/tasks/task-terminal-effects.ts +158 -0
- package/src/tests/acp-adapter.test.ts +369 -117
- package/src/tests/budget-claim-gate.test.ts +1 -0
- package/src/tests/build-pi-skills.test.ts +5 -7
- package/src/tests/claude-managed-setup.test.ts +52 -4
- package/src/tests/claude-md-session.test.ts +603 -0
- package/src/tests/codex-skill-resolver.test.ts +19 -0
- package/src/tests/context-versioning.test.ts +107 -0
- package/src/tests/cors-allowlist.test.ts +309 -0
- package/src/tests/credential-check.test.ts +157 -14
- package/src/tests/db-agents.test.ts +91 -0
- package/src/tests/db-context-versions.test.ts +37 -0
- package/src/tests/db-extraction-contract.test.ts +45 -0
- package/src/tests/db-runtime.test.ts +70 -0
- package/src/tests/db-task-read-contract.test.ts +624 -0
- package/src/tests/db-task-write-contract.test.ts +653 -0
- package/src/tests/db-tasks-read.test.ts +183 -0
- package/src/tests/db-tasks-write.test.ts +230 -0
- package/src/tests/defer-task.test.ts +389 -0
- package/src/tests/docs-index-footprint.test.js +2 -2
- package/src/tests/e2e-visuals-stage.test.ts +151 -0
- package/src/tests/file-lock.test.ts +144 -0
- package/src/tests/fixtures/file-lock-holder.ts +13 -0
- package/src/tests/fixtures/hold-file-lock.ts +25 -0
- package/src/tests/heartbeat-expiry.test.ts +188 -0
- package/src/tests/http-api-integration.test.ts +78 -1
- package/src/tests/pages-public-html.test.ts +43 -0
- package/src/tests/pages-svg-migration.test.ts +42 -0
- package/src/tests/preload.ts +4 -1
- package/src/tests/profile-sync-warning.test.ts +16 -0
- package/src/tests/profile-sync.test.ts +126 -0
- package/src/tests/prompt-template-session.test.ts +20 -4
- package/src/tests/provider-registration.test.ts +93 -0
- package/src/tests/rbac-e2e-helpers.test.ts +53 -0
- package/src/tests/rbac-e2e-helpers.ts +19 -2
- package/src/tests/reload-config.test.ts +26 -0
- package/src/tests/routing-affinity-inventory.test.ts +1 -1
- package/src/tests/runner-attachments-section.test.ts +22 -2
- package/src/tests/runner-context-preamble.test.ts +30 -0
- package/src/tests/runner-deployment-resume.test.ts +107 -0
- package/src/tests/runner-repo-autostash.test.ts +12 -0
- package/src/tests/script-connections.test.ts +4 -0
- package/src/tests/seed-scripts.test.ts +2 -2
- package/src/tests/session-tokens.test.ts +279 -0
- package/src/tests/slack-file-tools-attachments.test.ts +408 -0
- package/src/tests/slack-inbound-files.test.ts +847 -0
- package/src/tests/star-history-refresh.test.ts +102 -0
- package/src/tests/swarm-config-reserved-keys.test.ts +21 -0
- package/src/tests/swarm-repos.test.ts +20 -0
- package/src/tests/tool-annotations.test.ts +4 -3
- package/src/tests/ui-logs-parser.test.ts +176 -0
- package/src/tools/create-page.ts +4 -2
- package/src/tools/defer-task.ts +239 -0
- package/src/tools/repos/update-repo.ts +2 -2
- package/src/tools/schedules/create-schedule.ts +1 -0
- package/src/tools/schedules/list-schedules.ts +1 -0
- package/src/tools/schedules/patch-schedule.ts +1 -0
- package/src/tools/schedules/update-schedule.ts +1 -0
- package/src/tools/slack-download-file.ts +60 -29
- package/src/tools/slack-read.ts +73 -32
- package/src/tools/store-progress.ts +16 -146
- package/src/tools/tool-config.ts +1 -0
- package/src/tools/utils.ts +21 -0
- package/src/types.ts +37 -4
- package/src/utils/acp-session-token.ts +56 -0
- package/src/utils/file-lock.ts +111 -0
- package/src/utils/heartbeat-expiry.ts +78 -0
- package/src/utils/request-auth-context.ts +2 -0
- package/src/utils/task-attachment-links.ts +22 -0
- package/templates/skills/code-quality/content.md +8 -5
- package/templates/skills/download-task-attachment/content.md +2 -1
- package/{plugin/pi-skills → templates/skills}/review-offered-task/SKILL.md +5 -5
- package/templates/skills/review-offered-task/config.json +14 -0
- package/{plugin/commands/review-offered-task.md → templates/skills/review-offered-task/content.md} +3 -8
- package/templates/skills/scheduling/content.md +4 -0
- package/templates/skills/slack-interaction/content.md +2 -2
- package/templates/skills/work-on-task/SKILL.md +16 -0
- package/templates/skills/work-on-task/config.json +14 -0
- package/templates/skills/work-on-task/content.md +11 -0
- package/dist/assistant-0qt42nzg.js +0 -212
- package/dist/cli-fs1kkqgr.js +0 -317
- package/dist/cli-jnf5zghe.js +0 -269
- package/dist/cli-n1q8q14d.js +0 -177
- package/dist/handlers-yfp1qn81.js +0 -592
- package/plugin/commands/work-on-task.md +0 -39
- package/plugin/pi-skills/work-on-task/SKILL.md +0 -39
|
@@ -0,0 +1,102 @@
|
|
|
1
|
+
import { afterAll, beforeAll, expect, test } from "bun:test";
|
|
2
|
+
import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
|
|
3
|
+
import type { ScriptContext } from "swarm-sdk";
|
|
4
|
+
import { closeDb, getPageBySlug, initDb } from "../be/db";
|
|
5
|
+
import refresh from "../be/seed-scripts/catalog/star-history-refresh";
|
|
6
|
+
import { renderChart } from "../be/seed-scripts/catalog/star-history-renderer";
|
|
7
|
+
import { registerCreatePageTool } from "../tools/create-page";
|
|
8
|
+
|
|
9
|
+
const agentId = crypto.randomUUID();
|
|
10
|
+
const server = new McpServer({ name: "star-history-test", version: "1" });
|
|
11
|
+
registerCreatePageTool(server);
|
|
12
|
+
const tool = (server as any)._registeredTools.create_page;
|
|
13
|
+
beforeAll(() => initDb(":memory:"));
|
|
14
|
+
afterAll(() => closeDb());
|
|
15
|
+
|
|
16
|
+
function fixture() {
|
|
17
|
+
let cache: unknown;
|
|
18
|
+
let fail = false;
|
|
19
|
+
let calls = 0;
|
|
20
|
+
const ctx = {
|
|
21
|
+
stdlib: {
|
|
22
|
+
fetch: async (url: string, init: RequestInit) => {
|
|
23
|
+
calls++;
|
|
24
|
+
if (fail) return new Response("", { status: 503 });
|
|
25
|
+
const page = Number(new URL(url).searchParams.get("page"));
|
|
26
|
+
if ((init.headers as Record<string, string>)["If-None-Match"]) {
|
|
27
|
+
return new Response(null, { status: 304 });
|
|
28
|
+
}
|
|
29
|
+
return Response.json(
|
|
30
|
+
Array.from({ length: page === 1 ? 100 : 2 }, (_, i) => ({
|
|
31
|
+
starred_at: new Date(Date.UTC(2026, 0, 1) + (page * 100 + i) * 86400000).toISOString(),
|
|
32
|
+
})),
|
|
33
|
+
{ headers: { ETag: `"page-${page}"` } },
|
|
34
|
+
);
|
|
35
|
+
},
|
|
36
|
+
},
|
|
37
|
+
swarm: {
|
|
38
|
+
kv_getOrNull: async () => (cache ? { value: cache } : null),
|
|
39
|
+
kv_set: async ({ value }: { value: unknown }) => {
|
|
40
|
+
cache = value;
|
|
41
|
+
},
|
|
42
|
+
page_create: async (args: unknown) => {
|
|
43
|
+
const res = await tool.handler(args, {
|
|
44
|
+
sessionId: "stars",
|
|
45
|
+
requestInfo: { headers: { "x-agent-id": agentId } },
|
|
46
|
+
});
|
|
47
|
+
return res.structuredContent;
|
|
48
|
+
},
|
|
49
|
+
},
|
|
50
|
+
} as unknown as ScriptContext;
|
|
51
|
+
return {
|
|
52
|
+
ctx,
|
|
53
|
+
fail: () => {
|
|
54
|
+
fail = true;
|
|
55
|
+
},
|
|
56
|
+
calls: () => calls,
|
|
57
|
+
};
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
test("paginated refresh, ETags, stable MCP page IDs/URLs, and cached fallback", async () => {
|
|
61
|
+
const f = fixture();
|
|
62
|
+
const first = await refresh({}, f.ctx);
|
|
63
|
+
expect(first.stars).toBe(102);
|
|
64
|
+
expect(f.calls()).toBe(2);
|
|
65
|
+
expect(first.stale).toBe(false);
|
|
66
|
+
const second = await refresh({}, f.ctx);
|
|
67
|
+
expect(second.notModified).toBe(2);
|
|
68
|
+
expect(second.pages).toEqual(first.pages);
|
|
69
|
+
for (const page of second.pages) {
|
|
70
|
+
const stored = await getPageBySlug(agentId, page.slug);
|
|
71
|
+
expect(stored?.contentType).toBe("image/svg+xml");
|
|
72
|
+
expect(stored?.authMode).toBe("public");
|
|
73
|
+
expect(stored?.body).toContain("102 stars");
|
|
74
|
+
expect(stored?.body).not.toContain("NaN");
|
|
75
|
+
}
|
|
76
|
+
f.fail();
|
|
77
|
+
const fallback = await refresh({}, f.ctx);
|
|
78
|
+
expect(fallback.stale).toBe(true);
|
|
79
|
+
expect(fallback.stars).toBe(102);
|
|
80
|
+
expect(fallback.pages).toEqual(first.pages);
|
|
81
|
+
expect(fallback.fetchedAt).toBe(second.fetchedAt);
|
|
82
|
+
});
|
|
83
|
+
|
|
84
|
+
test("a failed first fetch refuses to publish an empty chart", async () => {
|
|
85
|
+
const f = fixture();
|
|
86
|
+
f.fail();
|
|
87
|
+
await expect(refresh({}, f.ctx)).rejects.toThrow("no cached series");
|
|
88
|
+
});
|
|
89
|
+
|
|
90
|
+
test("dry run renders both themes without publishing", async () => {
|
|
91
|
+
const f = fixture();
|
|
92
|
+
const result = await refresh({ dryRun: true, authenticated: false }, f.ctx);
|
|
93
|
+
expect(result.pages).toHaveLength(2);
|
|
94
|
+
expect(result.pages.every((page) => "bytes" in page && page.bytes > 1000)).toBe(true);
|
|
95
|
+
});
|
|
96
|
+
|
|
97
|
+
test("renderer escapes repository text and handles one star", async () => {
|
|
98
|
+
const svg = renderChart("owner/<repo>", [Date.UTC(2026, 0, 1)], "dark");
|
|
99
|
+
expect(svg).toContain("owner/<repo>");
|
|
100
|
+
expect(svg).toContain("1 stars");
|
|
101
|
+
expect(svg).not.toContain("NaN");
|
|
102
|
+
});
|
|
@@ -134,6 +134,27 @@ describe("swarm-config reserved keys guard", () => {
|
|
|
134
134
|
await unlink(`${TEST_DB_PATH}-shm`).catch(() => {});
|
|
135
135
|
});
|
|
136
136
|
|
|
137
|
+
for (const key of ["CORS_ALLOW_ANY_ORIGIN", "cors_allow_any_origin"]) {
|
|
138
|
+
test(`rejects deployment-only ${key} through DB, HTTP, and MCP`, async () => {
|
|
139
|
+
const config = { scope: "global" as const, key, value: "true" };
|
|
140
|
+
await expect(upsertSwarmConfig(config)).rejects.toThrow(EXPECTED_MESSAGE(key));
|
|
141
|
+
const res = await fetch(`${baseUrl}/api/config`, {
|
|
142
|
+
method: "PUT",
|
|
143
|
+
headers: { "Content-Type": "application/json" },
|
|
144
|
+
body: JSON.stringify(config),
|
|
145
|
+
});
|
|
146
|
+
expect(res.status).toBe(400);
|
|
147
|
+
expect(await res.json()).toMatchObject({ error: EXPECTED_MESSAGE(key) });
|
|
148
|
+
const handler = mcpServer.handlers.get("set-config")!;
|
|
149
|
+
const result = (await handler(config, makeRequestInfo())) as {
|
|
150
|
+
structuredContent: { success: boolean; message: string };
|
|
151
|
+
};
|
|
152
|
+
expect(result.structuredContent.success).toBe(false);
|
|
153
|
+
expect(result.structuredContent.message).toBe(EXPECTED_MESSAGE(key));
|
|
154
|
+
expect(await getSwarmConfigs({ key })).toHaveLength(0);
|
|
155
|
+
});
|
|
156
|
+
}
|
|
157
|
+
|
|
137
158
|
// ─── Helper predicate ─────────────────────────────────────────────────────
|
|
138
159
|
describe("isReservedConfigKey helper", () => {
|
|
139
160
|
test("recognizes API_KEY", () => {
|
|
@@ -11,6 +11,11 @@ import {
|
|
|
11
11
|
initDb,
|
|
12
12
|
updateSwarmRepo,
|
|
13
13
|
} from "../be/db";
|
|
14
|
+
import {
|
|
15
|
+
REPO_GUIDELINES_MAX_ENTRIES,
|
|
16
|
+
RepoGuidelinesInputSchema,
|
|
17
|
+
RepoGuidelinesSchema,
|
|
18
|
+
} from "../types";
|
|
14
19
|
|
|
15
20
|
const TEST_DB_PATH = "./test-swarm-repos.sqlite";
|
|
16
21
|
|
|
@@ -255,6 +260,21 @@ describe("Swarm Repos", () => {
|
|
|
255
260
|
expect(updated?.guidelines).toBeNull();
|
|
256
261
|
});
|
|
257
262
|
|
|
263
|
+
test("write schema caps each guideline list; read schema stays unbounded", () => {
|
|
264
|
+
const atCap = Array.from({ length: REPO_GUIDELINES_MAX_ENTRIES }, (_, i) => `check ${i}`);
|
|
265
|
+
const overCap = [...atCap, "one more"];
|
|
266
|
+
const base = { prChecks: [], mergeChecks: [], allowMerge: false, review: [] };
|
|
267
|
+
|
|
268
|
+
expect(RepoGuidelinesInputSchema.safeParse({ ...base, prChecks: atCap }).success).toBe(true);
|
|
269
|
+
for (const key of ["prChecks", "mergeChecks", "review"] as const) {
|
|
270
|
+
expect(RepoGuidelinesInputSchema.safeParse({ ...base, [key]: overCap }).success).toBe(
|
|
271
|
+
false,
|
|
272
|
+
);
|
|
273
|
+
// A stored row above the cap must still read back.
|
|
274
|
+
expect(RepoGuidelinesSchema.safeParse({ ...base, [key]: overCap }).success).toBe(true);
|
|
275
|
+
}
|
|
276
|
+
});
|
|
277
|
+
|
|
258
278
|
test("should round-trip null vs configured distinction", async () => {
|
|
259
279
|
const withGuidelines = await getSwarmRepoByName("guidelines-repo");
|
|
260
280
|
const withoutGuidelines = await getSwarmRepoByName("no-guidelines-repo");
|
|
@@ -199,8 +199,8 @@ describe("Tool Annotations & Classification", () => {
|
|
|
199
199
|
expect(overlap).toEqual([]);
|
|
200
200
|
});
|
|
201
201
|
|
|
202
|
-
test("CORE_TOOLS contains exactly
|
|
203
|
-
expect(CORE_TOOLS.size).toBe(
|
|
202
|
+
test("CORE_TOOLS contains exactly 26 tools", () => {
|
|
203
|
+
expect(CORE_TOOLS.size).toBe(26);
|
|
204
204
|
});
|
|
205
205
|
|
|
206
206
|
test("ALL_TOOLS equals CORE_TOOLS union DEFERRED_TOOLS", () => {
|
|
@@ -251,6 +251,7 @@ describe("Tool Annotations & Classification", () => {
|
|
|
251
251
|
"task-action",
|
|
252
252
|
"send-task",
|
|
253
253
|
"get-tasks",
|
|
254
|
+
"defer-task",
|
|
254
255
|
];
|
|
255
256
|
for (const tool of lifecycleTools) {
|
|
256
257
|
expect(CORE_TOOLS.has(tool)).toBe(true);
|
|
@@ -377,7 +378,7 @@ describe("Tool Annotations & Classification", () => {
|
|
|
377
378
|
// Includes 11 skill tools, 7 MCP server tools, reusable script tools, and the
|
|
378
379
|
// native Kapso/WhatsApp tools (register/unregister number + send/reply message).
|
|
379
380
|
expect(count).toBeGreaterThanOrEqual(45);
|
|
380
|
-
expect(count).toBeLessThanOrEqual(
|
|
381
|
+
expect(count).toBeLessThanOrEqual(141);
|
|
381
382
|
});
|
|
382
383
|
|
|
383
384
|
test("core tools are fewer than deferred tools", () => {
|
|
@@ -786,6 +786,182 @@ describe("ui logs parser", () => {
|
|
|
786
786
|
]);
|
|
787
787
|
});
|
|
788
788
|
|
|
789
|
+
test("surfaces originalType and a value summary for codex item.type unknown", () => {
|
|
790
|
+
const messages = parseSessionLogs([
|
|
791
|
+
log("sleep", "codex", 1, {
|
|
792
|
+
type: "item.completed",
|
|
793
|
+
item: {
|
|
794
|
+
id: "call-1",
|
|
795
|
+
type: "unknown",
|
|
796
|
+
originalType: "sleep",
|
|
797
|
+
value: { durationMs: 30000 },
|
|
798
|
+
},
|
|
799
|
+
}),
|
|
800
|
+
log("image", "codex", 2, {
|
|
801
|
+
type: "item.completed",
|
|
802
|
+
item: {
|
|
803
|
+
id: "exec-1",
|
|
804
|
+
type: "unknown",
|
|
805
|
+
originalType: "imageView",
|
|
806
|
+
value: { path: "/workspace/personal/meme/spongebob.jpg" },
|
|
807
|
+
},
|
|
808
|
+
}),
|
|
809
|
+
log("subagent", "codex", 3, {
|
|
810
|
+
type: "item.completed",
|
|
811
|
+
item: {
|
|
812
|
+
id: "subagent-1",
|
|
813
|
+
type: "unknown",
|
|
814
|
+
originalType: "subAgentActivity",
|
|
815
|
+
value: { kind: "completed", agentPath: "/root/standards_review" },
|
|
816
|
+
},
|
|
817
|
+
}),
|
|
818
|
+
log("collab", "codex", 4, {
|
|
819
|
+
type: "item.completed",
|
|
820
|
+
item: {
|
|
821
|
+
id: "collab-1",
|
|
822
|
+
type: "unknown",
|
|
823
|
+
originalType: "collabAgentToolCall",
|
|
824
|
+
value: { tool: "wait", status: "completed" },
|
|
825
|
+
},
|
|
826
|
+
}),
|
|
827
|
+
]);
|
|
828
|
+
|
|
829
|
+
expect(messages.map((message) => message.content[0])).toEqual([
|
|
830
|
+
expect.objectContaining({
|
|
831
|
+
kind: "unknown",
|
|
832
|
+
data: expect.objectContaining({
|
|
833
|
+
type: "item.completed · sleep",
|
|
834
|
+
itemType: "sleep",
|
|
835
|
+
detail: "sleep 30s",
|
|
836
|
+
}),
|
|
837
|
+
}),
|
|
838
|
+
expect.objectContaining({
|
|
839
|
+
kind: "unknown",
|
|
840
|
+
data: expect.objectContaining({
|
|
841
|
+
type: "item.completed · imageView",
|
|
842
|
+
itemType: "imageView",
|
|
843
|
+
detail: "spongebob.jpg",
|
|
844
|
+
}),
|
|
845
|
+
}),
|
|
846
|
+
expect.objectContaining({
|
|
847
|
+
kind: "unknown",
|
|
848
|
+
data: expect.objectContaining({
|
|
849
|
+
type: "item.completed · subAgentActivity",
|
|
850
|
+
itemType: "subAgentActivity",
|
|
851
|
+
detail: "completed · /root/standards_review",
|
|
852
|
+
}),
|
|
853
|
+
}),
|
|
854
|
+
expect.objectContaining({
|
|
855
|
+
kind: "unknown",
|
|
856
|
+
data: expect.objectContaining({
|
|
857
|
+
type: "item.completed · collabAgentToolCall",
|
|
858
|
+
itemType: "collabAgentToolCall",
|
|
859
|
+
detail: "wait · completed",
|
|
860
|
+
}),
|
|
861
|
+
}),
|
|
862
|
+
]);
|
|
863
|
+
});
|
|
864
|
+
|
|
865
|
+
test("groups a codex unknown item's started and completed events into one row", () => {
|
|
866
|
+
const items = normalizeSessionLogs([
|
|
867
|
+
log(
|
|
868
|
+
"sleep-start",
|
|
869
|
+
"codex",
|
|
870
|
+
1,
|
|
871
|
+
{
|
|
872
|
+
type: "item.started",
|
|
873
|
+
item: {
|
|
874
|
+
id: "sleep-1",
|
|
875
|
+
type: "unknown",
|
|
876
|
+
originalType: "sleep",
|
|
877
|
+
value: { durationMs: 45000 },
|
|
878
|
+
},
|
|
879
|
+
},
|
|
880
|
+
"2026-06-01T09:32:48.000Z",
|
|
881
|
+
),
|
|
882
|
+
log(
|
|
883
|
+
"sleep-done",
|
|
884
|
+
"codex",
|
|
885
|
+
2,
|
|
886
|
+
{
|
|
887
|
+
type: "item.completed",
|
|
888
|
+
item: {
|
|
889
|
+
id: "sleep-1",
|
|
890
|
+
type: "unknown",
|
|
891
|
+
originalType: "sleep",
|
|
892
|
+
value: { durationMs: 45000 },
|
|
893
|
+
},
|
|
894
|
+
},
|
|
895
|
+
"2026-06-01T09:33:33.000Z",
|
|
896
|
+
),
|
|
897
|
+
]).items;
|
|
898
|
+
|
|
899
|
+
expect(items.filter((item) => item.kind === "unknown")).toHaveLength(1);
|
|
900
|
+
expect(items[0]).toEqual(
|
|
901
|
+
expect.objectContaining({ kind: "unknown", status: "completed", durationMs: 45000 }),
|
|
902
|
+
);
|
|
903
|
+
});
|
|
904
|
+
|
|
905
|
+
test("shows a codex unknown item as running before its terminal event arrives", () => {
|
|
906
|
+
const items = normalizeSessionLogs([
|
|
907
|
+
log("collab-start", "codex", 1, {
|
|
908
|
+
type: "item.started",
|
|
909
|
+
item: {
|
|
910
|
+
id: "collab-1",
|
|
911
|
+
type: "unknown",
|
|
912
|
+
originalType: "collabAgentToolCall",
|
|
913
|
+
value: { tool: "wait", status: "pending" },
|
|
914
|
+
},
|
|
915
|
+
}),
|
|
916
|
+
]).items;
|
|
917
|
+
|
|
918
|
+
expect(items).toHaveLength(1);
|
|
919
|
+
expect(items[0]).toEqual(expect.objectContaining({ kind: "unknown", status: "running" }));
|
|
920
|
+
expect(items[0].durationMs).toBeUndefined();
|
|
921
|
+
});
|
|
922
|
+
|
|
923
|
+
test("marks a codex unknown item failed when its completed event carries a failure signal", () => {
|
|
924
|
+
const items = normalizeSessionLogs([
|
|
925
|
+
log("collab-start", "codex", 1, {
|
|
926
|
+
type: "item.started",
|
|
927
|
+
item: {
|
|
928
|
+
id: "collab-1",
|
|
929
|
+
type: "unknown",
|
|
930
|
+
originalType: "collabAgentToolCall",
|
|
931
|
+
value: { tool: "wait", status: "pending" },
|
|
932
|
+
},
|
|
933
|
+
}),
|
|
934
|
+
log("collab-done", "codex", 2, {
|
|
935
|
+
type: "item.completed",
|
|
936
|
+
item: {
|
|
937
|
+
id: "collab-1",
|
|
938
|
+
type: "unknown",
|
|
939
|
+
originalType: "collabAgentToolCall",
|
|
940
|
+
status: "failed",
|
|
941
|
+
value: { tool: "wait", status: "failed" },
|
|
942
|
+
},
|
|
943
|
+
}),
|
|
944
|
+
]).items;
|
|
945
|
+
|
|
946
|
+
expect(items).toHaveLength(1);
|
|
947
|
+
expect(items[0]).toEqual(expect.objectContaining({ kind: "unknown", status: "failed" }));
|
|
948
|
+
});
|
|
949
|
+
|
|
950
|
+
test("keeps id-less codex unknown items as separate one-off rows", () => {
|
|
951
|
+
const items = normalizeSessionLogs([
|
|
952
|
+
log("no-id-start", "codex", 1, {
|
|
953
|
+
type: "item.started",
|
|
954
|
+
item: { type: "unknown", originalType: "sleep", value: { durationMs: 1000 } },
|
|
955
|
+
}),
|
|
956
|
+
log("no-id-done", "codex", 2, {
|
|
957
|
+
type: "item.completed",
|
|
958
|
+
item: { type: "unknown", originalType: "sleep", value: { durationMs: 1000 } },
|
|
959
|
+
}),
|
|
960
|
+
]).items;
|
|
961
|
+
|
|
962
|
+
expect(items.filter((item) => item.kind === "unknown")).toHaveLength(2);
|
|
963
|
+
});
|
|
964
|
+
|
|
789
965
|
test("normalizes claude-managed raw SSE events without unknown noise", () => {
|
|
790
966
|
const result = normalizeSessionLogs([
|
|
791
967
|
log("status", "claude-managed", 1, {
|
package/src/tools/create-page.ts
CHANGED
|
@@ -83,9 +83,11 @@ export const registerCreatePageTool = (server: McpServer) => {
|
|
|
83
83
|
body: z
|
|
84
84
|
.string()
|
|
85
85
|
.min(1)
|
|
86
|
-
.describe(
|
|
86
|
+
.describe(
|
|
87
|
+
"Full page body (HTML document, SVG image, or JSON-render spec, per contentType).",
|
|
88
|
+
),
|
|
87
89
|
contentType: PageContentTypeSchema.describe(
|
|
88
|
-
"'text/html'
|
|
90
|
+
"'text/html' and 'image/svg+xml' render directly at /p/:id; 'application/json' is rendered by the SPA.",
|
|
89
91
|
),
|
|
90
92
|
authMode: PageAuthModeSchema.default("authed").describe(
|
|
91
93
|
"'authed' — requires page-session cookie (default); 'public' — no gate and must be explicit; 'password' — requires key.",
|
|
@@ -0,0 +1,239 @@
|
|
|
1
|
+
import { ensure } from "@desplega.ai/business-use";
|
|
2
|
+
import type { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
|
|
3
|
+
import * as z from "zod";
|
|
4
|
+
import { resolveTaskAuditUserId } from "@/be/audit-user";
|
|
5
|
+
import {
|
|
6
|
+
completeTask,
|
|
7
|
+
createScheduledTask,
|
|
8
|
+
getAgentById,
|
|
9
|
+
getDbClient,
|
|
10
|
+
getTaskById,
|
|
11
|
+
updateAgentStatusFromCapacity,
|
|
12
|
+
} from "@/be/db";
|
|
13
|
+
import { runTaskTerminalEffects } from "@/tasks/task-terminal-effects";
|
|
14
|
+
import { getTaskOutputValidationError } from "@/tasks/terminal-result-guard";
|
|
15
|
+
import { assertOwnsTask, ownerCtx } from "@/tools/task-tool-ctx";
|
|
16
|
+
import { createToolRegistrar, swarmToolOutputSchema, toolErr, toolOk } from "@/tools/utils";
|
|
17
|
+
import { isTerminalTaskStatus } from "@/types";
|
|
18
|
+
|
|
19
|
+
/** Thrown inside the transaction to abort and roll back the schedule INSERT. */
|
|
20
|
+
class DeferAbortedError extends Error {}
|
|
21
|
+
|
|
22
|
+
/** Render `note` + optional `checks` as the plain-text tail both texts share. */
|
|
23
|
+
function renderChecks(checks?: string[]): string {
|
|
24
|
+
if (!checks || checks.length === 0) return "";
|
|
25
|
+
return `\n\nChecks:\n${checks.map((c) => `- ${c}`).join("\n")}`;
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
export const registerDeferTaskTool = (server: McpServer) => {
|
|
29
|
+
createToolRegistrar(server)(
|
|
30
|
+
"defer-task",
|
|
31
|
+
{
|
|
32
|
+
title: "Defer Task",
|
|
33
|
+
annotations: { destructiveHint: false, idempotentHint: false },
|
|
34
|
+
description:
|
|
35
|
+
"Completes this task now with status `completed` and books a wake-up for you. Use when the result needs time: a build, a deploy, a reply. The task reaches its final state on this call; the lead sees your summary as its output. A one-off schedule wakes you up later with a child task that carries this task as its parent. Provide delayMs or runAt, a summary of what you did, and a note that says what is pending and what to check.",
|
|
36
|
+
inputSchema: z.object({
|
|
37
|
+
taskId: z.string().describe("The ID of the task you are working on."),
|
|
38
|
+
delayMs: z
|
|
39
|
+
.number()
|
|
40
|
+
.int()
|
|
41
|
+
.positive()
|
|
42
|
+
.optional()
|
|
43
|
+
.describe("Wake up after this many milliseconds (e.g. 1800000 for 30 min)."),
|
|
44
|
+
runAt: z
|
|
45
|
+
.string()
|
|
46
|
+
.datetime()
|
|
47
|
+
.optional()
|
|
48
|
+
.describe("Wake up at this ISO datetime (e.g. '2026-03-06T15:00:00Z'). Must be future."),
|
|
49
|
+
summary: z
|
|
50
|
+
.string()
|
|
51
|
+
.min(1)
|
|
52
|
+
.max(4000)
|
|
53
|
+
.describe(
|
|
54
|
+
"What you did so far and where things stand. This becomes the task's output; the lead and your wake-up run both read it.",
|
|
55
|
+
),
|
|
56
|
+
note: z
|
|
57
|
+
.string()
|
|
58
|
+
.min(1)
|
|
59
|
+
.max(2000)
|
|
60
|
+
.describe("What is pending, and what to check on wake-up."),
|
|
61
|
+
checks: z
|
|
62
|
+
.array(z.string().min(1))
|
|
63
|
+
.max(20)
|
|
64
|
+
.optional()
|
|
65
|
+
.describe("Concrete things to verify on wake-up, one per entry."),
|
|
66
|
+
}),
|
|
67
|
+
outputSchema: swarmToolOutputSchema({
|
|
68
|
+
yourAgentId: z.string().optional(),
|
|
69
|
+
taskId: z.string().optional(),
|
|
70
|
+
scheduleId: z.string().optional(),
|
|
71
|
+
nextRunAt: z.string().optional(),
|
|
72
|
+
}),
|
|
73
|
+
},
|
|
74
|
+
async ({ taskId, delayMs, runAt, summary, note, checks }, requestInfo, _meta) => {
|
|
75
|
+
if (!requestInfo.agentId) {
|
|
76
|
+
return toolErr('Agent ID not found. Set the "X-Agent-ID" header.');
|
|
77
|
+
}
|
|
78
|
+
const agent = await getAgentById(requestInfo.agentId);
|
|
79
|
+
if (!agent) {
|
|
80
|
+
return toolErr(`Agent not found: ${requestInfo.agentId}`);
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
const task = await getTaskById(taskId);
|
|
84
|
+
if (!task) {
|
|
85
|
+
return toolErr(`Task with ID "${taskId}" not found.`);
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
const forbidden = assertOwnsTask(ownerCtx(requestInfo), task);
|
|
89
|
+
if (forbidden) return forbidden;
|
|
90
|
+
|
|
91
|
+
// A deferral completes the task and books a wake-up for the CALLER, so
|
|
92
|
+
// only the assignee may defer. `assertOwnsTask` above is the RBAC
|
|
93
|
+
// chokepoint; it does not (today) constrain agent-to-agent access.
|
|
94
|
+
if (task.agentId !== requestInfo.agentId) {
|
|
95
|
+
return toolErr(`Task "${taskId}" is not assigned to you.`);
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
if (isTerminalTaskStatus(task.status)) {
|
|
99
|
+
return toolErr(`Task ${taskId} is already ${task.status}; nothing to defer.`);
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
// A workflow step's completion drives `src/workflows/resume.ts` to advance
|
|
103
|
+
// `next` nodes immediately using this call's output. The scheduled wake-up
|
|
104
|
+
// task runs outside that workflow run and has no way to feed its eventual
|
|
105
|
+
// result back into the step, so the workflow would advance on a deferral
|
|
106
|
+
// note instead of the real result. Not supported: fail the step normally
|
|
107
|
+
// (or use a workflow-native wait) instead of deferring it.
|
|
108
|
+
if (task.workflowRunId) {
|
|
109
|
+
return toolErr(
|
|
110
|
+
`Task ${taskId} is owned by workflow run ${task.workflowRunId}; workflow-owned tasks cannot be deferred.`,
|
|
111
|
+
);
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
if (!delayMs && !runAt) {
|
|
115
|
+
return toolErr("Provide either delayMs or runAt.");
|
|
116
|
+
}
|
|
117
|
+
if (delayMs && runAt) {
|
|
118
|
+
return toolErr("Provide either delayMs or runAt, not both.");
|
|
119
|
+
}
|
|
120
|
+
if (runAt && new Date(runAt).getTime() <= Date.now()) {
|
|
121
|
+
return toolErr("runAt must be in the future.");
|
|
122
|
+
}
|
|
123
|
+
const nextRunAt = delayMs ? new Date(Date.now() + delayMs).toISOString() : runAt!;
|
|
124
|
+
|
|
125
|
+
const checksBlock = renderChecks(checks);
|
|
126
|
+
const taskTemplate = `Resume task ${taskId}: ${note}${checksBlock}`;
|
|
127
|
+
const createdBy =
|
|
128
|
+
(await resolveTaskAuditUserId(requestInfo.sourceTaskId, requestInfo.agentId)) ?? undefined;
|
|
129
|
+
|
|
130
|
+
// The deferral note is written as the task's FINAL output — a task with
|
|
131
|
+
// an outputSchema must satisfy it on completion (store-progress enforces
|
|
132
|
+
// the same rule). Validate before creating anything: the real schedule
|
|
133
|
+
// id isn't known yet, but it never changes whether this prose is valid
|
|
134
|
+
// JSON against the schema, so a placeholder stands in for it here.
|
|
135
|
+
const previewOutput = `${summary}\n\nDeferred until ${nextRunAt} (schedule pending). Pending: ${note}${checksBlock}`;
|
|
136
|
+
const outputValidationError = getTaskOutputValidationError(task.outputSchema, previewOutput);
|
|
137
|
+
if (outputValidationError) {
|
|
138
|
+
return toolErr(
|
|
139
|
+
`Task ${taskId} has an outputSchema; its terminal output must satisfy it, but a deferral note cannot. ${outputValidationError}`,
|
|
140
|
+
);
|
|
141
|
+
}
|
|
142
|
+
|
|
143
|
+
try {
|
|
144
|
+
const committed = await getDbClient().transaction(async () => {
|
|
145
|
+
const schedule = await createScheduledTask({
|
|
146
|
+
// Unique name (`getScheduledTaskByName` is a unique lookup). The UUID
|
|
147
|
+
// prevents concurrent deferrals of the same task from colliding.
|
|
148
|
+
name: `deferred-${taskId.slice(0, 8)}-${Date.now()}-${crypto.randomUUID()}`,
|
|
149
|
+
description: note,
|
|
150
|
+
taskTemplate,
|
|
151
|
+
targetType: "agent-task",
|
|
152
|
+
scheduleType: "one_time",
|
|
153
|
+
nextRunAt,
|
|
154
|
+
targetAgentId: requestInfo.agentId,
|
|
155
|
+
createdByAgentId: requestInfo.agentId,
|
|
156
|
+
taskType: "deferred",
|
|
157
|
+
tags: ["deferred"],
|
|
158
|
+
priority: task.priority,
|
|
159
|
+
// No `model`: a concrete provider model pinned now can be
|
|
160
|
+
// incompatible with the assignee/provider at wake-up, especially
|
|
161
|
+
// after a delay. Only the portable modelTier travels — mirrors
|
|
162
|
+
// the non-inheriting continuation path in `src/be/db.ts`.
|
|
163
|
+
modelTier: task.modelTier,
|
|
164
|
+
parentTaskId: taskId,
|
|
165
|
+
createdBy,
|
|
166
|
+
});
|
|
167
|
+
|
|
168
|
+
const output = `${summary}\n\nDeferred until ${nextRunAt} (schedule ${schedule.id}). Pending: ${note}${checksBlock}`;
|
|
169
|
+
|
|
170
|
+
// Deliberately NOT running `getTaskOutputValidationError` again here:
|
|
171
|
+
// already validated above against a placeholder id; re-run would be
|
|
172
|
+
// redundant since the id never affects JSON-shape validity.
|
|
173
|
+
const completed = await completeTask(taskId, output);
|
|
174
|
+
if (!completed) {
|
|
175
|
+
// Another writer terminally completed/failed/cancelled this task
|
|
176
|
+
// between our early check and this transaction's write. Abort:
|
|
177
|
+
// rolling back here discards the schedule INSERT so no orphan
|
|
178
|
+
// wake-up is committed, and no terminal effects fire for a
|
|
179
|
+
// completion that didn't happen on this call.
|
|
180
|
+
throw new DeferAbortedError(
|
|
181
|
+
`Task ${taskId} reached a terminal state before this deferral committed.`,
|
|
182
|
+
);
|
|
183
|
+
}
|
|
184
|
+
|
|
185
|
+
// afterCommit: the transaction can still roll back; business-use must
|
|
186
|
+
// not be told the task completed for a write that never landed.
|
|
187
|
+
getDbClient().afterCommit(() => {
|
|
188
|
+
ensure({
|
|
189
|
+
id: "completed",
|
|
190
|
+
flow: "task",
|
|
191
|
+
runId: taskId,
|
|
192
|
+
depIds: task.wasPaused ? ["started", "resumed"] : ["started"],
|
|
193
|
+
data: {
|
|
194
|
+
taskId,
|
|
195
|
+
agentId: task.agentId,
|
|
196
|
+
previousStatus: task.status,
|
|
197
|
+
hasOutput: true,
|
|
198
|
+
},
|
|
199
|
+
validator: (data) => data.previousStatus === "in_progress",
|
|
200
|
+
// biome-ignore lint/correctness/noEmptyPattern: data unused, ctx needed
|
|
201
|
+
filter: ({}, ctx) => ctx.deps.length > 0,
|
|
202
|
+
conditions: [{ timeout_ms: 3_600_000 }], // 1 hour
|
|
203
|
+
});
|
|
204
|
+
});
|
|
205
|
+
|
|
206
|
+
if (task.agentId) {
|
|
207
|
+
await updateAgentStatusFromCapacity(task.agentId);
|
|
208
|
+
}
|
|
209
|
+
|
|
210
|
+
return { scheduleId: schedule.id, output, completed };
|
|
211
|
+
});
|
|
212
|
+
|
|
213
|
+
await runTaskTerminalEffects({
|
|
214
|
+
task: committed.completed,
|
|
215
|
+
status: "completed",
|
|
216
|
+
output: committed.output,
|
|
217
|
+
agentId: requestInfo.agentId,
|
|
218
|
+
});
|
|
219
|
+
|
|
220
|
+
return toolOk(
|
|
221
|
+
`Task ${taskId} completed and deferred. Wake-up at ${nextRunAt} (schedule ${committed.scheduleId}). This task is final; the wake-up task continues the work.`,
|
|
222
|
+
{
|
|
223
|
+
data: {
|
|
224
|
+
yourAgentId: requestInfo.agentId,
|
|
225
|
+
taskId,
|
|
226
|
+
scheduleId: committed.scheduleId,
|
|
227
|
+
nextRunAt,
|
|
228
|
+
},
|
|
229
|
+
},
|
|
230
|
+
);
|
|
231
|
+
} catch (error) {
|
|
232
|
+
const message = error instanceof Error ? error.message : "Unknown error";
|
|
233
|
+
return toolErr(`Failed to defer task: ${message}`, {
|
|
234
|
+
data: { yourAgentId: requestInfo.agentId, taskId },
|
|
235
|
+
});
|
|
236
|
+
}
|
|
237
|
+
},
|
|
238
|
+
);
|
|
239
|
+
};
|
|
@@ -2,7 +2,7 @@ import type { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
|
|
|
2
2
|
import * as z from "zod";
|
|
3
3
|
import { updateSwarmRepo } from "@/be/db";
|
|
4
4
|
import { createToolRegistrar, swarmToolOutputSchema, toolErr, toolOk } from "@/tools/utils";
|
|
5
|
-
import {
|
|
5
|
+
import { RepoGuidelinesInputSchema, RepoHooksSchema } from "@/types";
|
|
6
6
|
|
|
7
7
|
const swarmRepoOutputShape = z.looseObject({
|
|
8
8
|
id: z.string().optional(),
|
|
@@ -46,7 +46,7 @@ export const registerUpdateRepoTool = (server: McpServer) => {
|
|
|
46
46
|
.describe(
|
|
47
47
|
"Repository hook install config. Set { enabled: true } to opt into best-effort worker hook installation, or null to disable.",
|
|
48
48
|
),
|
|
49
|
-
guidelines:
|
|
49
|
+
guidelines: RepoGuidelinesInputSchema.nullable()
|
|
50
50
|
.optional()
|
|
51
51
|
.describe(
|
|
52
52
|
"Repository guidelines: prChecks (commands before PR), mergeChecks (conditions before merge), allowMerge (default false), review (guidance for reviewers). Pass null to clear.",
|
|
@@ -120,6 +120,7 @@ const scheduleDataShape = {
|
|
|
120
120
|
lastRunAt: z.string().optional(),
|
|
121
121
|
nextRunAt: z.string().optional(),
|
|
122
122
|
createdByAgentId: z.string().optional(),
|
|
123
|
+
parentTaskId: z.string().optional(),
|
|
123
124
|
timezone: z.string().optional(),
|
|
124
125
|
model: z.string().optional(),
|
|
125
126
|
modelTier: ModelTierSchema.optional(),
|
|
@@ -22,6 +22,7 @@ const scheduleRowShape = {
|
|
|
22
22
|
lastRunAt: z.string().optional(),
|
|
23
23
|
nextRunAt: z.string().optional(),
|
|
24
24
|
createdByAgentId: z.string().optional(),
|
|
25
|
+
parentTaskId: z.string().optional(),
|
|
25
26
|
timezone: z.string().optional(),
|
|
26
27
|
consecutiveErrors: z.number().optional(),
|
|
27
28
|
lastErrorAt: z.string().optional(),
|
|
@@ -89,6 +89,7 @@ const scheduleDataShape = {
|
|
|
89
89
|
lastRunAt: z.string().optional(),
|
|
90
90
|
nextRunAt: z.string().optional(),
|
|
91
91
|
createdByAgentId: z.string().optional(),
|
|
92
|
+
parentTaskId: z.string().optional(),
|
|
92
93
|
timezone: z.string().optional(),
|
|
93
94
|
model: z.string().optional(),
|
|
94
95
|
modelTier: ModelTierSchema.optional(),
|