@desplega.ai/agent-swarm 1.129.0 → 1.131.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 -2
- package/dist/{actions-kfv2dawe.js → actions-w53mbns9.js} +11 -11
- package/dist/{anthropic-messages-3xgme8my.js → anthropic-messages-rjmd2e7n.js} +5 -5
- package/dist/{app-q8vjk6e8.js → app-m9gwqsqv.js} +8 -8
- package/dist/{artifact-hg4t1nx7.js → artifact-jysj3wrg.js} +2 -2
- package/dist/{assistant-g037r345.js → assistant-bp65m6f3.js} +13 -13
- package/dist/{azure-openai-responses-xa4sh9f6.js → azure-openai-responses-c0yjmbfd.js} +8 -5
- package/dist/{boot-reembed-18pzsrxa.js → boot-reembed-62p0at2k.js} +6 -6
- package/dist/{boot-reembed-wpks2cj8.js → boot-reembed-xmfp6wdh.js} +7 -7
- package/dist/{boot-scrub-logs-n7szd57r.js → boot-scrub-logs-rr8q2dms.js} +5 -5
- package/dist/{claude-adapter-qz4r0ajt.js → claude-adapter-2y47mrkd.js} +9 -9
- package/dist/{cli-mhjnbgb6.js → cli-0rcyq6d9.js} +450 -186
- package/dist/{cli-ttkmehy0.js → cli-2q72qz2s.js} +274 -25
- package/dist/{cli-gztgc3hz.js → cli-2wadx7p2.js} +2 -2
- package/dist/cli-3fj60etf.js +153 -0
- package/dist/{cli-7ecgm5v0.js → cli-3kf51j15.js} +1 -1
- package/dist/{cli-vgexpfsr.js → cli-57khth04.js} +6 -3
- package/dist/{cli-2mbe7vk0.js → cli-5dsbcjjw.js} +1 -1
- package/dist/{cli-etr62zy6.js → cli-5kx36evn.js} +2 -2
- package/dist/{cli-v2tr6amf.js → cli-5n6e5yfn.js} +30 -8
- package/dist/{cli-hx02s9zq.js → cli-5v4rw6yd.js} +2 -2
- package/dist/{cli-v37yr6mf.js → cli-5ze9rv97.js} +4 -4
- package/dist/{cli-ch7t5v7n.js → cli-6map995v.js} +9 -4
- package/dist/{cli-h0457a67.js → cli-70xqj0z3.js} +3 -3
- package/dist/{cli-kk4f9r17.js → cli-72q8m8gk.js} +8 -6
- package/dist/{cli-7padk69a.js → cli-78xwvkhj.js} +2 -2
- package/dist/{cli-dxtxacy7.js → cli-9dr5qzc0.js} +45 -8
- package/dist/{cli-17zxdgsq.js → cli-a40rnf71.js} +6 -6
- package/dist/{cli-t53vnnxz.js → cli-a4jzdpa3.js} +2514 -1055
- package/dist/{cli-w10fkrf1.js → cli-akdvwc9v.js} +354 -115
- package/dist/{cli-1qkk5er0.js → cli-bmze986d.js} +1 -1
- package/dist/{cli-ga12dc1d.js → cli-bqw0mt1n.js} +3 -3
- package/dist/{cli-0sf92efp.js → cli-cdeg08wx.js} +1 -1
- package/dist/{cli-a8ynhkyj.js → cli-cvgjwnh1.js} +17 -15
- package/dist/{cli-9fxg3v5m.js → cli-d2tfkcga.js} +4 -4
- package/dist/{cli-nhcd7x43.js → cli-d3x3x1c9.js} +2 -2
- package/dist/{cli-6nh2qxvh.js → cli-darcwax3.js} +1 -1
- package/dist/{cli-yjbgmysx.js → cli-e7ckcg4v.js} +12 -12
- package/dist/{cli-4n2gcjpd.js → cli-efxfnq6q.js} +4 -3
- package/dist/{cli-z9xqqhfk.js → cli-er3a4bb0.js} +1 -1
- package/dist/{cli-xg2eezyv.js → cli-f1am9szv.js} +1 -1
- package/dist/{cli-xy4cwsm9.js → cli-fywms69x.js} +1 -1
- package/dist/{cli-33jj76y8.js → cli-g6b8x1h8.js} +1 -1
- package/dist/{cli-epsvdckx.js → cli-gm9cbhbq.js} +20 -10
- package/dist/{cli-rkndnn90.js → cli-k9qkf1hw.js} +6 -4
- package/dist/{cli-zejvt395.js → cli-kh3ba2zf.js} +1 -1
- package/dist/{cli-gb25p1h2.js → cli-nm5gtp1x.js} +2 -2
- package/dist/{cli-akxnpx17.js → cli-pm6xbh84.js} +1829 -1629
- package/dist/{cli-2k0ztbfp.js → cli-psh3c4va.js} +1 -1
- package/dist/{cli-39vgrsep.js → cli-qv61hp37.js} +2 -2
- package/dist/{cli-hv2fndhm.js → cli-r6kqqbj0.js} +6 -4
- package/dist/{cli-1y1cm570.js → cli-rzbpmxdr.js} +1 -1
- package/dist/{cli-mmrk00vp.js → cli-t1xze5qh.js} +1 -1
- package/dist/{cli-g252a9ky.js → cli-t55yw3a1.js} +2 -2
- package/dist/{cli-7bz8dzqa.js → cli-t76vw5db.js} +1184 -1522
- package/dist/{cli-4n4mdv21.js → cli-xpxde7rm.js} +7 -3
- package/dist/{cli-fpaa5k2q.js → cli-yw8vghx2.js} +54 -18
- package/dist/cli-zsspvvm2.js +490 -0
- package/dist/cli.js +27 -25
- package/dist/{codex-adapter-7v6t7ff6.js → codex-adapter-d24q75wk.js} +7 -7
- package/dist/{codex-hook-amndawyb.js → codex-hook-hq54t9ft.js} +2 -2
- package/dist/{codex-session-runner-e7j4khe2.js → codex-session-runner-g50vcsex.js} +7 -7
- package/dist/{commands-ex0k5tbf.js → commands-c3nrc5hg.js} +5 -5
- package/dist/{db-df7jatfc.js → db-nt7fg3tw.js} +5 -5
- package/dist/{google-generative-ai-y42hnw9p.js → google-generative-ai-hysrd8qx.js} +7 -5
- package/dist/{google-vertex-4sg66gb6.js → google-vertex-p5arrga0.js} +7 -5
- package/dist/{handlers-1j2cfqt2.js → handlers-fkr6swat.js} +13 -13
- package/dist/{hook-nx9a0mj3.js → hook-77wpvnjc.js} +7 -7
- package/dist/{hook-w6yaf6sy.js → hook-g019yehy.js} +8 -8
- package/dist/{http-4tz5h8wg.js → http-jewj0syk.js} +2993 -716
- package/dist/{index-0bpkzb09.js → index-070v4eyb.js} +3 -3
- package/dist/{index-0s67qv7h.js → index-6gm5he8h.js} +11 -11
- package/dist/{index-35gtv6gj.js → index-7n2xpbd4.js} +310 -40
- package/dist/{index-ker3gccb.js → index-9znhar3x.js} +4 -4
- package/dist/{index-5n2c8yay.js → index-hase6zbe.js} +2 -2
- package/dist/{index-kcf3tm9h.js → index-hfvggd0z.js} +12 -12
- package/dist/{index-7c0jqna0.js → index-hp1kdbjj.js} +13 -13
- package/dist/{keepalive-3j4a9168.js → keepalive-j0fjvqfd.js} +7 -7
- package/dist/{lead-6kpevymb.js → lead-1n8x30xe.js} +33 -33
- package/dist/{maintenance-m1k82ean.js → maintenance-gvev11sa.js} +7 -7
- package/dist/{mistral-conversations-bjkdfb6z.js → mistral-conversations-81fwav0j.js} +6 -6
- package/dist/{oauth-refresh-sweep-881k8e4q.js → oauth-refresh-sweep-s9ecsvh0.js} +6 -6
- package/dist/{onboard-c0ykffjn.js → onboard-pvm5b38w.js} +2 -2
- package/dist/{openai-codex-responses-xetseqvt.js → openai-codex-responses-5g27pd6f.js} +43 -23
- package/dist/{openai-completions-5r2g5axa.js → openai-completions-3vkmfz2a.js} +55 -11
- package/dist/{openai-responses-ce67hd23.js → openai-responses-dg4t7j7a.js} +8 -5
- package/dist/{openrouter-images-pzfp22jb.js → openrouter-images-g3gm8931.js} +1 -1
- package/dist/{otel-impl-sqagjerv.js → otel-impl-p6jvgd9c.js} +6 -4
- package/dist/{otel-3mp81ppd.js → otel-nqa12wf3.js} +2 -2
- package/dist/{pi-mono-adapter-k584k16g.js → pi-mono-adapter-t091s27g.js} +17117 -7517
- package/dist/{pricing-refresh-y82whbf9.js → pricing-refresh-k49rcyed.js} +7 -7
- package/dist/{rbac-roles-nvkqmzyd.js → rbac-roles-30pspvy7.js} +7 -7
- package/dist/{rbac-roles-4ht8gegc.js → rbac-roles-cr80g96t.js} +6 -6
- package/dist/{render-v2-wvqdgvvp.js → render-v2-xm457ng1.js} +8 -8
- package/dist/{seed-pricing-fy3rxhde.js → seed-pricing-yhpv35fr.js} +6 -6
- package/dist/{setup-ak5cnc6f.js → setup-nytnmwwr.js} +2 -2
- package/dist/{worker-d8769q8a.js → worker-msytr2cw.js} +33 -33
- package/openapi.json +34039 -11036
- package/package.json +8 -6
- package/src/apps/definition.ts +368 -26
- package/src/apps/row-store.ts +66 -10
- package/src/apps/schema-migrate.ts +95 -1
- package/src/apps/store.ts +13 -4
- package/src/apps/sync-run-as.ts +18 -0
- package/src/apps/sync.ts +934 -0
- package/src/apps/version.ts +16 -2
- package/src/assets/key.ts +8 -1
- package/src/be/asset-key-audit.ts +3 -1
- package/src/be/db-queries/mcp-oauth.ts +297 -24
- package/src/be/db.ts +42 -1
- package/src/be/migrations/129_asset_keys_apps_scripts.sql +161 -0
- package/src/be/scripts/db.ts +5 -2
- package/src/be/scripts/typecheck.ts +1 -0
- package/src/be/seed-scripts/catalog/app-sync-run.ts +32 -0
- package/src/be/seed-scripts/catalog/github-issues-pull.ts +102 -0
- package/src/be/seed-scripts/index.ts +18 -0
- package/src/be/seed-skills/index.ts +7 -0
- package/src/http/active-sessions.ts +26 -18
- package/src/http/agents.ts +94 -27
- package/src/http/api-keys.ts +91 -17
- package/src/http/approval-requests.ts +87 -10
- package/src/http/apps.ts +361 -53
- package/src/http/assets.ts +165 -31
- package/src/http/budgets.ts +7 -5
- package/src/http/codex-oauth-keep-warm.ts +21 -3
- package/src/http/config.ts +46 -16
- package/src/http/context.ts +33 -6
- package/src/http/ecosystem.ts +20 -6
- package/src/http/events.ts +24 -9
- package/src/http/favorites.ts +20 -6
- package/src/http/fs.ts +60 -17
- package/src/http/heartbeat.ts +18 -4
- package/src/http/inbox-state.ts +12 -6
- package/src/http/integrations.ts +25 -5
- package/src/http/kv.ts +36 -15
- package/src/http/mcp-bridge.ts +5 -1
- package/src/http/mcp-oauth.ts +332 -17
- package/src/http/mcp-servers.ts +56 -18
- package/src/http/memory.ts +282 -29
- package/src/http/metrics.ts +80 -17
- package/src/http/oauth-callback.ts +10 -4
- package/src/http/oauth-generic.ts +5 -1
- package/src/http/oauth-locks.ts +6 -3
- package/src/http/openapi.ts +57 -9
- package/src/http/page-proxy.ts +25 -5
- package/src/http/pages-public.ts +37 -20
- package/src/http/pages.ts +93 -28
- package/src/http/poll.ts +89 -5
- package/src/http/pricing.ts +9 -6
- package/src/http/prompt-templates.ts +85 -21
- package/src/http/route-def.ts +67 -4
- package/src/http/schedules.ts +63 -14
- package/src/http/script-connection-proxy.ts +30 -4
- package/src/http/script-connections.ts +393 -43
- package/src/http/script-runs.ts +73 -19
- package/src/http/scripts.ts +257 -39
- package/src/http/session-data.ts +87 -14
- package/src/http/sessions.ts +80 -5
- package/src/http/skills.ts +115 -35
- package/src/http/stats.ts +94 -13
- package/src/http/task-templates.ts +6 -4
- package/src/http/tasks.ts +230 -61
- package/src/http/trackers/jira.ts +90 -30
- package/src/http/trackers/linear.ts +46 -12
- package/src/http/users.ts +162 -25
- package/src/http/utils.ts +12 -2
- package/src/http/webhooks.ts +30 -16
- package/src/http/workflow-events.ts +18 -5
- package/src/http/workflows.ts +128 -39
- package/src/http/x.ts +37 -6
- package/src/oauth/ensure-mcp-token.ts +16 -1
- package/src/oauth/mcp-wrapper.ts +281 -26
- package/src/providers/pi-mono-adapter.ts +1 -1
- package/src/scripts-runtime/credential-broker/README.md +1 -1
- package/src/scripts-runtime/credential-broker/fetch-patch.ts +12 -2
- package/src/scripts-runtime/sdk-allowlist.ts +1 -0
- package/src/scripts-runtime/stdlib/fetch.ts +6 -0
- package/src/scripts-runtime/types/stdlib.d.ts +1 -0
- package/src/scripts-runtime/types/swarm-sdk.d.ts +1 -0
- package/src/server.ts +2 -0
- package/src/tests/apps-rbac.test.ts +110 -0
- package/src/tests/apps-spike2.test.ts +4 -1
- package/src/tests/apps-sync-engine.test.ts +2540 -0
- package/src/tests/apps-sync.test.ts +1627 -0
- package/src/tests/asset-key-api.test.ts +48 -2
- package/src/tests/asset-key-invariants.test.ts +92 -0
- package/src/tests/asset-key-migration.test.ts +174 -8
- package/src/tests/asset-key-normalization.test.ts +2 -0
- package/src/tests/credential-broker.test.ts +27 -2
- package/src/tests/mcp-oauth-dcr-reuse.test.ts +743 -0
- package/src/tests/mcp-oauth-ensure-token.test.ts +45 -0
- package/src/tests/mcp-oauth-manual-client.test.ts +135 -2
- package/src/tests/mcp-oauth-queries.test.ts +153 -0
- package/src/tests/mcp-oauth-wrapper.test.ts +432 -0
- package/src/tests/memory-http-recall-gating.test.ts +2 -0
- package/src/tests/openapi-response-contract.test.ts +138 -0
- package/src/tests/rbac-admission-e2e.test.ts +68 -0
- package/src/tests/script-connections.test.ts +3 -0
- package/src/tests/scripts-runtime-fetch.test.ts +121 -0
- package/src/tests/scripts-runtime-secret-egress.test.ts +4 -2
- package/src/tests/seed-scripts.test.ts +2 -2
- package/src/tests/swarm-tool-result-gate.test.ts +32 -0
- package/src/tests/task-working-dir.test.ts +8 -4
- package/src/tests/whoami-route.test.ts +127 -0
- package/src/tests/workflow-engine-v2.test.ts +80 -7
- package/src/tests/workflow-swarm-script.test.ts +104 -0
- package/src/tools/app-get.ts +10 -2
- package/src/tools/app-rollback.ts +1 -0
- package/src/tools/app-sync.ts +112 -0
- package/src/tools/script-common.ts +20 -0
- package/src/tools/tool-config.ts +2 -1
- package/src/tools/utils.ts +11 -0
- package/src/tools/workflows/create-workflow.ts +4 -1
- package/src/types.ts +1394 -1206
- package/src/utils/template.ts +8 -1
- package/src/utils/zod-openapi.ts +11 -0
- package/src/workflows/engine.ts +27 -4
- package/src/workflows/executors/swarm-script.ts +28 -0
- package/src/workflows/retry-poller.ts +19 -2
- package/templates/skills/apps/content.md +94 -5
- package/templates/skills/asset-namespaces/content.md +2 -2
- package/templates/skills/scheduled-task-resilience/SKILL.md +102 -0
- package/templates/skills/scheduled-task-resilience/config.json +14 -0
- package/templates/skills/scheduled-task-resilience/content.md +97 -0
- package/dist/cli-7wmybrt5.js +0 -115
- package/dist/cli-d91pvmq1.js +0 -14
|
@@ -208,6 +208,110 @@ describe("SwarmScriptExecutor", () => {
|
|
|
208
208
|
expect(result.output?.scriptName).toBe("add-one");
|
|
209
209
|
});
|
|
210
210
|
|
|
211
|
+
test("named script source with an interpolation token fails with the node and token named", async () => {
|
|
212
|
+
await saveScript(
|
|
213
|
+
"unresolved-source",
|
|
214
|
+
`export default async () => ({ value: "{{trigger.payload}}" });`,
|
|
215
|
+
);
|
|
216
|
+
|
|
217
|
+
const executor = new SwarmScriptExecutor(deps);
|
|
218
|
+
const wf = makeWorkflow({ nodes: [] });
|
|
219
|
+
const result = await executor.run({
|
|
220
|
+
config: { scriptName: "unresolved-source", args: { payload: "safe-argv-data" } },
|
|
221
|
+
context: { trigger: { payload: "must-not-enter-source" } },
|
|
222
|
+
meta: {
|
|
223
|
+
runId: crypto.randomUUID(),
|
|
224
|
+
stepId: crypto.randomUUID(),
|
|
225
|
+
nodeId: "named-script-node",
|
|
226
|
+
workflowId: wf.id,
|
|
227
|
+
dryRun: false,
|
|
228
|
+
},
|
|
229
|
+
});
|
|
230
|
+
|
|
231
|
+
expect(result.status).toBe("failed");
|
|
232
|
+
expect(result.error).toContain('node "named-script-node"');
|
|
233
|
+
expect(result.error).toContain("{{trigger.payload}}");
|
|
234
|
+
expect(result.error).toContain("config.args");
|
|
235
|
+
expect(result.output).toBeUndefined();
|
|
236
|
+
});
|
|
237
|
+
|
|
238
|
+
test("named script source may contain literal mustache template data", async () => {
|
|
239
|
+
await saveScript(
|
|
240
|
+
"literal-mustache-source",
|
|
241
|
+
`export default async () => ({ template: "Hello {{customer_name}}" });`,
|
|
242
|
+
);
|
|
243
|
+
|
|
244
|
+
const executor = new SwarmScriptExecutor(deps);
|
|
245
|
+
const wf = makeWorkflow({ nodes: [] });
|
|
246
|
+
const result = await executor.run({
|
|
247
|
+
config: { scriptName: "literal-mustache-source" },
|
|
248
|
+
context: {},
|
|
249
|
+
meta: {
|
|
250
|
+
runId: crypto.randomUUID(),
|
|
251
|
+
stepId: crypto.randomUUID(),
|
|
252
|
+
nodeId: "literal-template-node",
|
|
253
|
+
workflowId: wf.id,
|
|
254
|
+
dryRun: false,
|
|
255
|
+
},
|
|
256
|
+
});
|
|
257
|
+
|
|
258
|
+
expect(result.status).toBe("success");
|
|
259
|
+
expect(result.output?.result).toEqual({ template: "Hello {{customer_name}}" });
|
|
260
|
+
});
|
|
261
|
+
|
|
262
|
+
test("unrelated upstream node names do not turn literal source templates into workflow tokens", async () => {
|
|
263
|
+
await saveScript(
|
|
264
|
+
"literal-upstream-name",
|
|
265
|
+
`export default async () => ({ template: "Hello {{customer.name}}" });`,
|
|
266
|
+
);
|
|
267
|
+
const wf = makeWorkflow({
|
|
268
|
+
nodes: [
|
|
269
|
+
{ id: "customer", type: "echo", config: { value: "Ada" }, next: "script" },
|
|
270
|
+
{
|
|
271
|
+
id: "script",
|
|
272
|
+
type: "swarm-script",
|
|
273
|
+
config: { scriptName: "literal-upstream-name" },
|
|
274
|
+
},
|
|
275
|
+
],
|
|
276
|
+
});
|
|
277
|
+
|
|
278
|
+
const runId = await startWorkflowExecution(wf, {}, registry);
|
|
279
|
+
const run = getWorkflowRun(runId);
|
|
280
|
+
const steps = getWorkflowRunStepsByRunId(runId);
|
|
281
|
+
const scriptStep = steps.find((step) => step.nodeId === "script");
|
|
282
|
+
|
|
283
|
+
expect(run?.status).toBe("completed");
|
|
284
|
+
expect(scriptStep?.status).toBe("completed");
|
|
285
|
+
expect(scriptStep?.output).toMatchObject({
|
|
286
|
+
result: { template: "Hello {{customer.name}}" },
|
|
287
|
+
});
|
|
288
|
+
});
|
|
289
|
+
|
|
290
|
+
test("declared input aliases still identify workflow tokens in named script source", async () => {
|
|
291
|
+
await saveScript(
|
|
292
|
+
"declared-alias-source",
|
|
293
|
+
`export default async () => ({ value: "{{customer.name}}" });`,
|
|
294
|
+
);
|
|
295
|
+
|
|
296
|
+
const executor = new SwarmScriptExecutor(deps);
|
|
297
|
+
const wf = makeWorkflow({ nodes: [] });
|
|
298
|
+
const result = await executor.run({
|
|
299
|
+
config: { scriptName: "declared-alias-source" },
|
|
300
|
+
context: { upstream: { name: "Ada" } },
|
|
301
|
+
meta: {
|
|
302
|
+
runId: crypto.randomUUID(),
|
|
303
|
+
stepId: crypto.randomUUID(),
|
|
304
|
+
nodeId: "declared-alias-node",
|
|
305
|
+
workflowId: wf.id,
|
|
306
|
+
dryRun: false,
|
|
307
|
+
inputCtx: { customer: { name: "Ada" } },
|
|
308
|
+
},
|
|
309
|
+
});
|
|
310
|
+
|
|
311
|
+
expect(result.status).toBe("failed");
|
|
312
|
+
expect(result.error).toContain("{{customer.name}}");
|
|
313
|
+
});
|
|
314
|
+
|
|
211
315
|
test("swarm-script executor includes API connection descriptors in ctx.api", async () => {
|
|
212
316
|
await upsertScriptConnection({
|
|
213
317
|
slug: "workflowVendor",
|
package/src/tools/app-get.ts
CHANGED
|
@@ -2,6 +2,7 @@ import type { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
|
|
|
2
2
|
import * as z from "zod";
|
|
3
3
|
import { type AppRow, listAppRows } from "@/apps/row-store";
|
|
4
4
|
import { getApp } from "@/apps/store";
|
|
5
|
+
import { collectAppSyncStatus } from "@/apps/sync";
|
|
5
6
|
import { getAgentById } from "@/be/db";
|
|
6
7
|
import { AppQueryParamsError, applyQuery } from "@/http/apps";
|
|
7
8
|
import { can } from "@/rbac";
|
|
@@ -55,9 +56,16 @@ export const registerAppGetTool = (server: McpServer) => {
|
|
|
55
56
|
const app = getApp(appId);
|
|
56
57
|
if (!app) return toolErr(`App ${appId} not found.`);
|
|
57
58
|
|
|
59
|
+
const syncStatus = collectAppSyncStatus(app.id);
|
|
60
|
+
const hasSyncStatus = Object.keys(syncStatus).length > 0;
|
|
61
|
+
// Supplying `details` suppresses the registrar's JSON-data fallback, so
|
|
62
|
+
// text-only harnesses must get the sync freshness surface here too.
|
|
63
|
+
const details = hasSyncStatus
|
|
64
|
+
? `${JSON.stringify(app, null, 2)}\n\nSync status (model:source):\n${JSON.stringify(syncStatus, null, 2)}`
|
|
65
|
+
: JSON.stringify(app, null, 2);
|
|
58
66
|
return toolOk(`App "${app.name}" (${app.id}).`, {
|
|
59
|
-
details
|
|
60
|
-
data: { app },
|
|
67
|
+
details,
|
|
68
|
+
data: { app, ...(hasSyncStatus ? { syncStatus } : {}) },
|
|
61
69
|
});
|
|
62
70
|
},
|
|
63
71
|
);
|
|
@@ -65,6 +65,7 @@ export const registerAppRollbackTool = (server: McpServer) => {
|
|
|
65
65
|
migration,
|
|
66
66
|
forceElementBreak,
|
|
67
67
|
changedByAgentId: requestInfo.agentId,
|
|
68
|
+
writerAgentId: requestInfo.agentId,
|
|
68
69
|
});
|
|
69
70
|
const url = `/apps/${rolledBack.app.id}`;
|
|
70
71
|
return toolOk(`App "${rolledBack.app.name}" rolled back to v${version}.`, {
|
|
@@ -0,0 +1,112 @@
|
|
|
1
|
+
import type { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
|
|
2
|
+
import * as z from "zod";
|
|
3
|
+
import { AppNameSchema } from "@/apps/definition";
|
|
4
|
+
import { getApp } from "@/apps/store";
|
|
5
|
+
import { runAppSync, type SyncPassResult } from "@/apps/sync";
|
|
6
|
+
import { getAgentById } from "@/be/db";
|
|
7
|
+
import { can } from "@/rbac";
|
|
8
|
+
import { createToolRegistrar, swarmToolOutputSchema, toolErr, toolOk } from "@/tools/utils";
|
|
9
|
+
|
|
10
|
+
function cell(value: unknown): string {
|
|
11
|
+
return String(value ?? "—")
|
|
12
|
+
.replaceAll("\\", "\\\\")
|
|
13
|
+
.replaceAll("|", "\\|")
|
|
14
|
+
.replace(/\r\n|\r|\n/g, "<br>");
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
/** One row per pass: what it wrote, and why it did not, if it failed. */
|
|
18
|
+
function renderPasses(passes: SyncPassResult[]): string {
|
|
19
|
+
if (passes.length === 0) return "No sync passes ran.";
|
|
20
|
+
const header = "| model | source | created | updated | refreshed | stale | error |";
|
|
21
|
+
const separator = "| --- | --- | --- | --- | --- | --- | --- |";
|
|
22
|
+
const body = passes.map((pass) =>
|
|
23
|
+
[
|
|
24
|
+
cell(pass.model),
|
|
25
|
+
cell(pass.source),
|
|
26
|
+
cell(pass.created),
|
|
27
|
+
cell(pass.updated),
|
|
28
|
+
cell(pass.refreshed),
|
|
29
|
+
cell(pass.markedStale),
|
|
30
|
+
cell(pass.error ?? (pass.skipped ? "skipped: already running" : undefined)),
|
|
31
|
+
]
|
|
32
|
+
.map((value) => ` ${value} `)
|
|
33
|
+
.join("|"),
|
|
34
|
+
);
|
|
35
|
+
const warnings = passes.flatMap((pass) =>
|
|
36
|
+
pass.warnings.map((warning) => `- ${pass.model}.${pass.source}: ${warning}`),
|
|
37
|
+
);
|
|
38
|
+
return [header, separator, ...body.map((row) => `|${row}|`), ...warnings].join("\n");
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
export const registerAppSyncTool = (server: McpServer) => {
|
|
42
|
+
createToolRegistrar(server)(
|
|
43
|
+
"app-sync",
|
|
44
|
+
{
|
|
45
|
+
title: "Sync an app's sources",
|
|
46
|
+
description:
|
|
47
|
+
"Refresh an app's declared sources: pull each selected (model x source) pair and reconcile its rows.",
|
|
48
|
+
// Writes rows, but only ever projects the source's own view onto them.
|
|
49
|
+
annotations: { destructiveHint: false },
|
|
50
|
+
rbac: { permission: "app.use" },
|
|
51
|
+
inputSchema: z.object({
|
|
52
|
+
appId: z.string().min(1).describe("App ID whose sources should sync."),
|
|
53
|
+
model: AppNameSchema.optional().describe("Limit the sync to one model."),
|
|
54
|
+
source: AppNameSchema.optional().describe("Limit the sync to one declared source name."),
|
|
55
|
+
}),
|
|
56
|
+
outputSchema: swarmToolOutputSchema({
|
|
57
|
+
passes: z.array(z.looseObject({})).optional(),
|
|
58
|
+
ok: z.boolean().optional(),
|
|
59
|
+
}),
|
|
60
|
+
},
|
|
61
|
+
async ({ appId, model, source }, requestInfo) => {
|
|
62
|
+
if (!requestInfo.agentId) return toolErr('Agent ID not found. Set the "X-Agent-ID" header.');
|
|
63
|
+
const agent = getAgentById(requestInfo.agentId);
|
|
64
|
+
const decision = can({
|
|
65
|
+
principal: { kind: "agent", agentId: requestInfo.agentId, isLead: agent?.isLead ?? false },
|
|
66
|
+
verb: "app.use",
|
|
67
|
+
resource: { kind: "app", appId },
|
|
68
|
+
source: "mcp",
|
|
69
|
+
});
|
|
70
|
+
if (!decision.allow) return toolErr(decision.reason);
|
|
71
|
+
const app = getApp(appId);
|
|
72
|
+
if (!app) return toolErr(`App ${appId} not found.`);
|
|
73
|
+
|
|
74
|
+
const result = await runAppSync({
|
|
75
|
+
appId,
|
|
76
|
+
...(model === undefined ? {} : { model }),
|
|
77
|
+
...(source === undefined ? {} : { source }),
|
|
78
|
+
invokedBy: `agent:${requestInfo.agentId}`,
|
|
79
|
+
});
|
|
80
|
+
|
|
81
|
+
if (result.issues && result.issues.length > 0) {
|
|
82
|
+
// Covers both "no matching pair" and "definition needs repair".
|
|
83
|
+
return toolErr(`Cannot sync app "${app.name}" (${app.id}).`, {
|
|
84
|
+
details: result.issues.map((issue) => `${issue.path}: ${issue.message}`).join("\n"),
|
|
85
|
+
data: { ok: false },
|
|
86
|
+
});
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
const totals = result.passes.reduce(
|
|
90
|
+
(sum, pass) => ({
|
|
91
|
+
created: sum.created + pass.created,
|
|
92
|
+
updated: sum.updated + pass.updated,
|
|
93
|
+
refreshed: sum.refreshed + pass.refreshed,
|
|
94
|
+
markedStale: sum.markedStale + pass.markedStale,
|
|
95
|
+
}),
|
|
96
|
+
{ created: 0, updated: 0, refreshed: 0, markedStale: 0 },
|
|
97
|
+
);
|
|
98
|
+
const summary =
|
|
99
|
+
`${result.passes.length} pass(es): ${totals.created} created, ${totals.updated} updated, ` +
|
|
100
|
+
`${totals.refreshed} refreshed, ${totals.markedStale} marked stale.`;
|
|
101
|
+
const details = renderPasses(result.passes);
|
|
102
|
+
const data = { ok: result.ok, passes: result.passes };
|
|
103
|
+
if (!result.ok) {
|
|
104
|
+
return toolErr(`Sync failed for app "${app.name}" (${app.id}) — ${summary}`, {
|
|
105
|
+
details,
|
|
106
|
+
data,
|
|
107
|
+
});
|
|
108
|
+
}
|
|
109
|
+
return toolOk(`Synced app "${app.name}" (${app.id}) — ${summary}`, { details, data });
|
|
110
|
+
},
|
|
111
|
+
);
|
|
112
|
+
};
|
|
@@ -42,6 +42,15 @@ function apiBaseUrl(): string {
|
|
|
42
42
|
return getMcpBaseUrl();
|
|
43
43
|
}
|
|
44
44
|
|
|
45
|
+
/** `{path, message}` renders as one `path: message` line; anything else verbatim. */
|
|
46
|
+
function renderIssue(issue: unknown): string {
|
|
47
|
+
if (typeof issue === "string") return issue;
|
|
48
|
+
if (isRecord(issue) && typeof issue.message === "string") {
|
|
49
|
+
return typeof issue.path === "string" ? `${issue.path}: ${issue.message}` : issue.message;
|
|
50
|
+
}
|
|
51
|
+
return JSON.stringify(issue);
|
|
52
|
+
}
|
|
53
|
+
|
|
45
54
|
type ScriptFailure = { message: string; details?: string };
|
|
46
55
|
|
|
47
56
|
/**
|
|
@@ -86,6 +95,17 @@ function describeScriptFailure(
|
|
|
86
95
|
};
|
|
87
96
|
}
|
|
88
97
|
|
|
98
|
+
// Path-bearing rejections (e.g. a delete blocked by an app that still
|
|
99
|
+
// wires this script): without this branch the paths never reach the text
|
|
100
|
+
// channel and the model cannot see what to unwire.
|
|
101
|
+
const issues = body && Array.isArray(body.issues) ? (body.issues as unknown[]) : undefined;
|
|
102
|
+
if (issues && issues.length > 0) {
|
|
103
|
+
return {
|
|
104
|
+
message: `${error ?? `Scripts API request failed with ${status}`} — ${issues.length} issue(s)`,
|
|
105
|
+
details: capDetails(issues.map(renderIssue).join("\n")),
|
|
106
|
+
};
|
|
107
|
+
}
|
|
108
|
+
|
|
89
109
|
return { message: error ?? `Scripts API request failed with ${status}` };
|
|
90
110
|
}
|
|
91
111
|
|
package/src/tools/tool-config.ts
CHANGED
|
@@ -163,11 +163,12 @@ export const DEFERRED_TOOLS = new Set([
|
|
|
163
163
|
"resolve-user",
|
|
164
164
|
"manage-user",
|
|
165
165
|
|
|
166
|
-
// Pages and apps (
|
|
166
|
+
// Pages and apps (11)
|
|
167
167
|
"app-get",
|
|
168
168
|
"app-list",
|
|
169
169
|
"app-patch",
|
|
170
170
|
"app-query",
|
|
171
|
+
"app-sync",
|
|
171
172
|
"app-upsert",
|
|
172
173
|
"app-history",
|
|
173
174
|
"app-diff",
|
package/src/tools/utils.ts
CHANGED
|
@@ -261,6 +261,17 @@ export const NUDGES: Record<string, (result: SwarmToolResult) => string | undefi
|
|
|
261
261
|
? "Rate memories that help or mislead you with memory_rate."
|
|
262
262
|
: undefined;
|
|
263
263
|
},
|
|
264
|
+
"app-get": (r) => {
|
|
265
|
+
if (!r.ok) return undefined;
|
|
266
|
+
const definition = (
|
|
267
|
+
r.data as { app?: { definition?: { queries?: object; actions?: object } } | null } | undefined
|
|
268
|
+
)?.app?.definition;
|
|
269
|
+
const hasQueries = Object.keys(definition?.queries ?? {}).length > 0;
|
|
270
|
+
const hasActions = Object.keys(definition?.actions ?? {}).length > 0;
|
|
271
|
+
return hasQueries || hasActions
|
|
272
|
+
? "This app's callable surface is `definition.queries` (run with `app-query`, passing any `$param` values) and `definition.actions` (invoke with `POST /api/apps/<id>/actions/<name>`; `sync` actions refresh sources)."
|
|
273
|
+
: undefined;
|
|
274
|
+
},
|
|
264
275
|
// kv-get is spill-exempt (retrieval path), so oversized values reach the
|
|
265
276
|
// harness whole and get natively truncated there. Steer big-value work
|
|
266
277
|
// toward scripts, where the full value stays out of the model's context.
|
|
@@ -31,7 +31,10 @@ export const registerCreateWorkflowTool = (server: McpServer) => {
|
|
|
31
31
|
"- Nodes are linked via 'next' (string or port-based record).\n" +
|
|
32
32
|
"- CROSS-NODE DATA: To use output from an upstream node, you MUST declare an 'inputs' mapping on the downstream node. " +
|
|
33
33
|
'Example: inputs: { "cityData": "generate-city" } → then use {{cityData.taskOutput.field}} in config templates. ' +
|
|
34
|
-
"Without 'inputs',
|
|
34
|
+
"Without 'inputs', built-in trigger/input/workflow/swarm/run context remains available, but upstream outputs do not. " +
|
|
35
|
+
"Agent-task templates may interpolate trigger and declared upstream aliases. SECURITY: executable source for script/swarm-script nodes does not: " +
|
|
36
|
+
"inline script source allows only input/workflow/swarm/run values, while named swarm-script source is not workflow-interpolated. " +
|
|
37
|
+
"Pass dynamic trigger or upstream values through config.args (argv for inline scripts; the args object for swarm-script).\n" +
|
|
35
38
|
"- STRUCTURED OUTPUT: For agent-task nodes, put outputSchema inside 'config' to validate the agent's raw JSON output. " +
|
|
36
39
|
"Node-level outputSchema validates the executor's return ({taskId, taskOutput}), which is different.\n" +
|
|
37
40
|
"- Agent-task config: { template, outputSchema?, agentId?, tags?, priority?, dir?, vcsRepo?, model? }.\n" +
|