@desplega.ai/agent-swarm 1.123.0 → 1.124.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/{actions-e4c9f0xy.js → actions-z3d0fk7z.js} +6 -6
- package/dist/{app-vmvmj2v3.js → app-q2rc9tdr.js} +4 -4
- package/dist/{assistant-yn7a792a.js → assistant-3stjq7fz.js} +9 -9
- package/dist/{boot-reembed-xg1r86h2.js → boot-reembed-5867whnb.js} +3 -3
- package/dist/{boot-reembed-chxty50a.js → boot-reembed-v6r1df32.js} +4 -4
- package/dist/{boot-scrub-logs-exwddvp5.js → boot-scrub-logs-8b8jegk3.js} +2 -2
- package/dist/{cli-1592nfd6.js → cli-0925phzv.js} +1 -1
- package/dist/{cli-hbbxmhec.js → cli-2yr6edr7.js} +4 -4
- package/dist/{cli-8cq4z4x7.js → cli-3z4q09tn.js} +182 -19
- package/dist/{cli-vz9drmce.js → cli-5jn6wnd9.js} +17 -8
- package/dist/{cli-5arfywtm.js → cli-5ncdb7ff.js} +19 -9
- package/dist/{cli-3tv9z4wx.js → cli-92bqgbgy.js} +1 -1
- package/dist/{cli-fjf04hpa.js → cli-dq2ssyf0.js} +48 -56
- package/dist/{cli-mngsj9fx.js → cli-gkvf8d83.js} +1 -1
- package/dist/{cli-mepk1j0t.js → cli-gzjsamwc.js} +2 -2
- package/dist/{cli-c9m59f83.js → cli-h89g0qrd.js} +1 -1
- package/dist/{cli-n8mf7x4b.js → cli-jp4mdj9d.js} +2 -2
- package/dist/{cli-822d4bjk.js → cli-kk58tg33.js} +7 -7
- package/dist/{cli-9hj1adgp.js → cli-mcez7z4p.js} +1 -1
- package/dist/{cli-byv3720g.js → cli-mmk5vagg.js} +2 -2
- package/dist/{cli-zdp11t8m.js → cli-p0ec4vv6.js} +1 -1
- package/dist/{cli-h2say6xk.js → cli-p0zgv8pj.js} +3 -1
- package/dist/{cli-8ef5ky75.js → cli-perb20ma.js} +1 -1
- package/dist/{cli-zkj6f2sk.js → cli-qf7acvyr.js} +90 -4
- package/dist/{cli-kmm1rwmp.js → cli-qwsagsmg.js} +1 -1
- package/dist/{cli-we91ywvy.js → cli-sqv6c9b8.js} +6 -6
- package/dist/{cli-np0yhmmk.js → cli-vm20sh8d.js} +3 -3
- package/dist/{cli-p11rf59d.js → cli-wn3m6q54.js} +3 -3
- package/dist/{cli-mkpf4ndf.js → cli-x268say8.js} +2976 -5621
- package/dist/{cli-ngbsk4yx.js → cli-y43n987v.js} +1 -1
- package/dist/{cli-4509mwm5.js → cli-ykthjw29.js} +8 -8
- package/dist/{cli-9eka6jwv.js → cli-zxb72ctk.js} +2 -2
- package/dist/cli.js +12 -10
- package/dist/{commands-sjk3zfyr.js → commands-sda76yn8.js} +2 -2
- package/dist/{db-2je5agjb.js → db-vn9e4rqj.js} +6 -2
- package/dist/{e2b-2s4psnbf.js → e2b-q345y8s4.js} +0 -4
- package/dist/{handlers-yzv369hf.js → handlers-xfja18ac.js} +9 -9
- package/dist/{hook-rfsj2r8x.js → hook-2medrp47.js} +1 -40
- package/dist/{http-v9rr144m.js → http-937pyjfc.js} +99 -44
- package/dist/{index-nr2199m6.js → index-2bqd2q9v.js} +11 -11
- package/dist/{index-3xtk777g.js → index-gw4en7jg.js} +9 -9
- package/dist/{index-c3tan44f.js → index-knqczmnm.js} +10 -10
- package/dist/{index-smjdc3h9.js → index-vah60vxp.js} +8 -8
- package/dist/{keepalive-5ds53bjd.js → keepalive-20rd6hxh.js} +5 -5
- package/dist/{lead-5xhez25b.js → lead-7w2k3a49.js} +21 -21
- package/dist/{maintenance-b43d24q6.js → maintenance-p3x918yw.js} +4 -4
- package/dist/{oauth-refresh-sweep-qe286vn2.js → oauth-refresh-sweep-shm0nzm4.js} +4 -4
- package/dist/{onboard-c4r3ds3z.js → onboard-py9dnf54.js} +6 -2
- package/dist/{otel-impl-nv3rjw61.js → otel-impl-7yjkfg68.js} +1 -1
- package/dist/{pi-mono-adapter-2syx1x4d.js → pi-mono-adapter-z6h98fva.js} +4 -38
- package/dist/{pricing-refresh-5ct2wp4k.js → pricing-refresh-3snb6370.js} +4 -4
- package/dist/{rbac-roles-m502w0qv.js → rbac-roles-5e72bbpk.js} +3 -3
- package/dist/{rbac-roles-b82cxpxn.js → rbac-roles-gqxdp8st.js} +4 -4
- package/dist/{seed-pricing-jhjphbf0.js → seed-pricing-bg42d7h2.js} +3 -3
- package/dist/{setup-zbaspydq.js → setup-nzk3kbbq.js} +2 -2
- package/dist/{worker-snvhpax5.js → worker-0p1jdzkm.js} +21 -21
- package/openapi.json +146 -1
- package/package.json +3 -1
- package/src/be/db.ts +81 -4
- package/src/be/models-catalog.ts +156 -0
- package/src/be/modelsdev-cache.ts +14 -1
- package/src/be/pricing-refresh.ts +2 -0
- package/src/be/scripts/typecheck.ts +17 -4
- package/src/be/seed-scripts/catalog/task-context-gathering.ts +1 -1
- package/src/commands/onboard/env-generator.ts +12 -0
- package/src/e2b/env.ts +0 -4
- package/src/hooks/hook.ts +0 -67
- package/src/http/all-routes.ts +1 -0
- package/src/http/index.ts +2 -0
- package/src/http/models-catalog.ts +65 -0
- package/src/http/workflows.ts +16 -4
- package/src/prompts/base-prompt.ts +8 -7
- package/src/prompts/session-templates.ts +106 -12
- package/src/providers/pi-mono-adapter.ts +12 -2
- package/src/providers/pi-mono-extension.ts +0 -64
- package/src/providers/pricing-sources.md +16 -1
- package/src/scripts-runtime/eval-harness.ts +18 -1
- package/src/scripts-runtime/swarm-sdk.ts +10 -0
- package/src/scripts-runtime/types/stdlib.d.ts +13 -0
- package/src/scripts-runtime/types/swarm-sdk.d.ts +13 -0
- package/src/server-user.ts +4 -7
- package/src/slack/blocks.ts +21 -8
- package/src/slack/responses.ts +27 -51
- package/src/slack/watcher.ts +22 -5
- package/src/telemetry.ts +169 -3
- package/src/tests/base-prompt.test.ts +79 -0
- package/src/tests/mcp-tools-user.test.ts +62 -16
- package/src/tests/mcp-tools.test.ts +6 -3
- package/src/tests/memory-edit.test.ts +21 -4
- package/src/tests/model-groups-live-catalog.test.ts +65 -0
- package/src/tests/models-catalog.test.ts +119 -0
- package/src/tests/oauth-access-token-tool.test.ts +71 -1
- package/src/tests/pricing-refresh.test.ts +25 -0
- package/src/tests/prompt-template-session.test.ts +142 -5
- package/src/tests/rbac-charact-skills.test.ts +81 -14
- package/src/tests/script-apis-mcp.test.ts +56 -8
- package/src/tests/scripts-mcp-e2e.test.ts +157 -8
- package/src/tests/scripts-typecheck.test.ts +17 -0
- package/src/tests/sdk-allowlist.test.ts +34 -1
- package/src/tests/seed-scripts.test.ts +43 -0
- package/src/tests/slack-blocks.test.ts +2 -1
- package/src/tests/slack-inline-output.test.ts +24 -30
- package/src/tests/slack-watcher.test.ts +42 -0
- package/src/tests/steer-task-tool.test.ts +37 -8
- package/src/tests/steering-transport.test.ts +18 -3
- package/src/tests/swarm-tool-result-gate.test.ts +399 -0
- package/src/tests/task-tools-ctx.test.ts +49 -17
- package/src/tests/task-tools-ownership.test.ts +41 -13
- package/src/tests/telemetry-init.test.ts +376 -2
- package/src/tests/tool-output-agent-id.test.ts +14 -2
- package/src/tests/workflow-http-v2.test.ts +116 -0
- package/src/tools/accept-steer.ts +45 -53
- package/src/tools/cancel-task.ts +31 -41
- package/src/tools/context-diff.ts +19 -63
- package/src/tools/context-history.ts +22 -51
- package/src/tools/create-channel.ts +24 -39
- package/src/tools/create-metric.ts +19 -82
- package/src/tools/create-page.ts +20 -81
- package/src/tools/credential-bindings/tool.ts +125 -246
- package/src/tools/db-query.ts +13 -24
- package/src/tools/delete-channel.ts +25 -71
- package/src/tools/delete-page.ts +27 -75
- package/src/tools/get-metrics.ts +29 -22
- package/src/tools/get-swarm.ts +44 -15
- package/src/tools/get-task-details.ts +150 -32
- package/src/tools/get-tasks.ts +63 -29
- package/src/tools/inject-learning.ts +8 -38
- package/src/tools/join-swarm.ts +45 -47
- package/src/tools/kv/kv-delete.ts +13 -37
- package/src/tools/kv/kv-get.ts +38 -29
- package/src/tools/kv/kv-incr.ts +28 -56
- package/src/tools/kv/kv-list.ts +40 -31
- package/src/tools/kv/kv-set.ts +34 -76
- package/src/tools/list-channels.ts +24 -17
- package/src/tools/list-services.ts +37 -45
- package/src/tools/manage-user.ts +53 -66
- package/src/tools/mcp-servers/mcp-server-create.ts +19 -55
- package/src/tools/mcp-servers/mcp-server-delete.ts +9 -39
- package/src/tools/mcp-servers/mcp-server-get.ts +9 -35
- package/src/tools/mcp-servers/mcp-server-install.ts +13 -60
- package/src/tools/mcp-servers/mcp-server-list.ts +28 -26
- package/src/tools/mcp-servers/mcp-server-uninstall.ts +10 -36
- package/src/tools/mcp-servers/mcp-server-update.ts +13 -57
- package/src/tools/memory-delete.ts +16 -46
- package/src/tools/memory-edit.ts +48 -49
- package/src/tools/memory-get.ts +40 -36
- package/src/tools/memory-rate.ts +14 -44
- package/src/tools/memory-search.ts +46 -52
- package/src/tools/my-agent-info.ts +39 -33
- package/src/tools/oauth-access-token.ts +9 -24
- package/src/tools/poll-task.ts +50 -77
- package/src/tools/post-message.ts +24 -38
- package/src/tools/prompt-templates/delete.ts +16 -48
- package/src/tools/prompt-templates/get.ts +45 -42
- package/src/tools/prompt-templates/list.ts +29 -44
- package/src/tools/prompt-templates/preview.ts +14 -40
- package/src/tools/prompt-templates/set.ts +30 -52
- package/src/tools/read-messages.ts +45 -54
- package/src/tools/register-agentmail-inbox.ts +28 -84
- package/src/tools/register-kapso-number.ts +20 -50
- package/src/tools/register-service.ts +36 -44
- package/src/tools/repos/get-repos.ts +29 -21
- package/src/tools/repos/update-repo.ts +30 -27
- package/src/tools/request-human-input.ts +10 -33
- package/src/tools/resolve-user.ts +59 -16
- package/src/tools/schedules/create-schedule.ts +58 -173
- package/src/tools/schedules/delete-schedule.ts +16 -56
- package/src/tools/schedules/list-schedules.ts +41 -67
- package/src/tools/schedules/patch-schedule.ts +57 -153
- package/src/tools/schedules/run-schedule-now.ts +18 -59
- package/src/tools/schedules/update-schedule.ts +57 -153
- package/src/tools/script-apis.ts +46 -28
- package/src/tools/script-common.ts +129 -34
- package/src/tools/script-connections/tool.ts +59 -154
- package/src/tools/script-query-types.ts +43 -3
- package/src/tools/script-run.ts +41 -2
- package/src/tools/script-runs.ts +69 -1
- package/src/tools/script-search.ts +24 -1
- package/src/tools/script-upsert.ts +18 -3
- package/src/tools/send-task.ts +32 -81
- package/src/tools/skills/skill-create.ts +14 -39
- package/src/tools/skills/skill-delete.ts +14 -42
- package/src/tools/skills/skill-get-file.ts +10 -43
- package/src/tools/skills/skill-get.ts +9 -32
- package/src/tools/skills/skill-install-remote.ts +16 -52
- package/src/tools/skills/skill-install.ts +14 -52
- package/src/tools/skills/skill-list.ts +24 -26
- package/src/tools/skills/skill-publish.ts +18 -54
- package/src/tools/skills/skill-search.ts +21 -18
- package/src/tools/skills/skill-sync-remote.ts +12 -24
- package/src/tools/skills/skill-uninstall.ts +10 -26
- package/src/tools/skills/skill-update.ts +24 -81
- package/src/tools/slack-delete.ts +8 -32
- package/src/tools/slack-download-file.ts +17 -57
- package/src/tools/slack-list-channels.ts +16 -43
- package/src/tools/slack-post.ts +11 -40
- package/src/tools/slack-read.ts +33 -103
- package/src/tools/slack-reply.ts +16 -53
- package/src/tools/slack-start-thread.ts +14 -55
- package/src/tools/slack-update.ts +8 -35
- package/src/tools/slack-upload-file.ts +22 -122
- package/src/tools/steer-task.ts +22 -35
- package/src/tools/store-progress.ts +11 -26
- package/src/tools/swarm-config/delete-config.ts +19 -57
- package/src/tools/swarm-config/get-config.ts +36 -44
- package/src/tools/swarm-config/list-config.ts +36 -44
- package/src/tools/swarm-config/set-config.ts +35 -73
- package/src/tools/swarm-x.ts +20 -26
- package/src/tools/task-action.ts +26 -46
- package/src/tools/task-tool-ctx.ts +5 -13
- package/src/tools/tracker/tracker-link-task.ts +8 -22
- package/src/tools/tracker/tracker-map-agent.ts +8 -22
- package/src/tools/tracker/tracker-status.ts +27 -19
- package/src/tools/tracker/tracker-sync-status.ts +8 -18
- package/src/tools/tracker/tracker-unlink.ts +4 -16
- package/src/tools/unregister-service.ts +19 -59
- package/src/tools/update-profile.ts +60 -100
- package/src/tools/update-service-status.ts +42 -67
- package/src/tools/utils.ts +189 -8
- package/src/tools/whatsapp-message.ts +19 -32
- package/src/tools/workflows/cancel-workflow-run.ts +4 -16
- package/src/tools/workflows/create-workflow.ts +9 -38
- package/src/tools/workflows/delete-workflow.ts +5 -17
- package/src/tools/workflows/get-workflow-run.ts +41 -31
- package/src/tools/workflows/get-workflow.ts +10 -21
- package/src/tools/workflows/list-workflow-runs.ts +107 -34
- package/src/tools/workflows/list-workflows.ts +5 -17
- package/src/tools/workflows/patch-workflow-node.ts +11 -39
- package/src/tools/workflows/patch-workflow.ts +11 -36
- package/src/tools/workflows/retry-workflow-run.ts +4 -16
- package/src/tools/workflows/trigger-workflow.ts +22 -58
- package/src/tools/workflows/update-workflow.ts +10 -42
- package/templates/skills/swarm-scripts/SKILL.md +1 -1
|
@@ -336,9 +336,8 @@ registerTemplate({
|
|
|
336
336
|
You have access to agent-fs — a persistent, searchable filesystem shared across the swarm.
|
|
337
337
|
Use the \`agent-fs\` CLI for all thoughts, research, plans, and shared documents.
|
|
338
338
|
|
|
339
|
-
The \`agent-fs\` skill (
|
|
340
|
-
|
|
341
|
-
interactive CLI documentation.
|
|
339
|
+
The \`agent-fs\` skill (installed in your skills directory) provides a full CLI
|
|
340
|
+
reference. You can also run \`agent-fs docs\` for interactive CLI documentation.
|
|
342
341
|
|
|
343
342
|
### Writing to your personal drive (default)
|
|
344
343
|
\`\`\`bash
|
|
@@ -434,10 +433,68 @@ Use this to debug issues and propose improvements to your own infrastructure.
|
|
|
434
433
|
category: "system",
|
|
435
434
|
});
|
|
436
435
|
|
|
436
|
+
/**
|
|
437
|
+
* Script authoring contract — the call convention every script must follow.
|
|
438
|
+
*
|
|
439
|
+
* Included at the top of `system.agent.script_rubric` so it reaches every
|
|
440
|
+
* composite that carries the rubric (lead, worker, worker.pi, lead.pi) without
|
|
441
|
+
* a per-composite edit. The scripts-only composite path renders after one of
|
|
442
|
+
* those composites, so it must NOT restate the signature (duplicate guidance).
|
|
443
|
+
*
|
|
444
|
+
* Facts here are derived from `src/scripts-runtime/ctx.ts` (+ the generated
|
|
445
|
+
* `types/swarm-sdk.d.ts`); do not add ctx members that don't exist.
|
|
446
|
+
*/
|
|
447
|
+
registerTemplate({
|
|
448
|
+
eventType: "system.agent.script_authoring_contract",
|
|
449
|
+
header: "",
|
|
450
|
+
defaultBody: `
|
|
451
|
+
### Script Authoring Contract (read BEFORE writing any script)
|
|
452
|
+
|
|
453
|
+
**Entry point — \`args\` FIRST, \`ctx\` SECOND.** A one-parameter \`function (ctx)\` can execute through \`script-run\`, but at runtime that parameter receives \`args\`, so every \`ctx.*\` access throws. \`script-upsert\` also rejects an untyped parameter under strict typechecking. This is the single most common cause of failed script runs.
|
|
454
|
+
|
|
455
|
+
\`\`\`ts
|
|
456
|
+
import type { ScriptContext } from "swarm-sdk";
|
|
457
|
+
import * as z from "zod";
|
|
458
|
+
|
|
459
|
+
export const argsSchema = z.object({ taskId: z.string(), limit: z.number().optional() });
|
|
460
|
+
|
|
461
|
+
export default async function (args: z.infer<typeof argsSchema>, ctx: ScriptContext) {
|
|
462
|
+
const res = await ctx.swarm.task_get({ taskId: args.taskId });
|
|
463
|
+
const task = ((res as { data?: unknown }).data ?? res) as { title?: string };
|
|
464
|
+
return { title: task?.title };
|
|
465
|
+
}
|
|
466
|
+
\`\`\`
|
|
467
|
+
|
|
468
|
+
**Typechecking:** inline source passed to \`script-run\` executes without a compile-time typecheck. \`script-upsert\` typechecks before saving, so import \`ScriptContext\` from \`"swarm-sdk"\` as shown above to make inline code promotion-safe. Use \`script-query-types\` for the authoritative SDK and stdlib declarations.
|
|
469
|
+
|
|
470
|
+
**What \`ctx\` actually holds for inline/named scripts (\`script-run\` / \`script-upsert\`) — nothing else:**
|
|
471
|
+
- \`ctx.swarm.*\` — the swarm SDK: \`task_get\`, \`task_send\`, \`task_storeProgress\`, \`task_action\`, \`task_list\`, \`message_post\`, \`message_read\`, \`slack_reply\`, \`memory_search\`, \`kv_get\`/\`kv_set\`/\`kv_del\`/\`kv_incr\`/\`kv_list\`, \`swarm_get\`, \`agent_info\`, and more. Responses are usually wrapped — prefer \`res?.data ?? res\`.
|
|
472
|
+
- \`ctx.swarm.config\` — \`apiKey\`, \`agentId\`, \`mcpBaseUrl\`, plus \`ctx.swarm.config.get("KEY")\` for user values. All are \`Redacted\` wrappers: they stringify to \`<redacted>\`, and you must never unwrap them into a return value, log line, or request body you build by hand.
|
|
473
|
+
- \`ctx.api.<slug>\` / \`ctx.mcp.<slug>\` — typed clients for connections the lead registered (\`ctx.api.<slug>.<operationId>(...)\`, \`ctx.api.<slug>.graphql(query, vars)\`, \`ctx.mcp.<slug>.<toolName>(args)\`). They exist ONLY for registered connections — introspect with \`Object.keys(ctx.api ?? {})\` / \`Object.keys(ctx.mcp ?? {})\` before assuming one is there.
|
|
474
|
+
- \`ctx.stdlib\` — \`fetch\`, \`fetchJson\` (retries + 30s timeout), \`grep\`, \`glob\`, \`table\`, \`Redacted\`.
|
|
475
|
+
- \`ctx.logger\` — \`log\` / \`warn\` / \`error\`.
|
|
476
|
+
|
|
477
|
+
There is NO ambient task context: \`taskId\` must arrive through \`args\`. \`agentId\` IS propagated automatically (\`X-Agent-ID\` header).
|
|
478
|
+
|
|
479
|
+
**Durable workflow scripts (\`launch-script-run\`) get a DIFFERENT \`ctx\`:** \`ctx.run\` (\`id\`/\`agentId\`/\`args\`), \`ctx.step.rawLlm(label, config)\` / \`ctx.step.agentTask(label, config)\` / \`ctx.step.swarmScript(label, config)\` (durable, journaled steps), plus \`ctx.swarm.*\`, \`ctx.stdlib\`, \`ctx.logger\` as above. Durable runs have NO \`ctx.api\`/\`ctx.mcp\` connection clients and no \`ctx.swarm.config\` — call connections from an inner script via \`ctx.step.swarmScript\` instead.
|
|
480
|
+
|
|
481
|
+
**\`argsSchema\` convention:** export a Zod schema named \`argsSchema\` from every named script. \`script-upsert\` converts it to JSON Schema, so callers, schedules, and workflows can see the script's input contract. Without it the script's args are undocumented.
|
|
482
|
+
|
|
483
|
+
**Secrets — never paste a raw value:**
|
|
484
|
+
- Prefer a registered connection (\`ctx.api.<slug>\` / \`ctx.mcp.<slug>\`): its credentials are attached server-side and never enter the script.
|
|
485
|
+
- Otherwise put the placeholder \`[REDACTED:<CONFIG_KEY>]\` in the header or query value (e.g. \`Authorization: Bearer [REDACTED:GITHUB_TOKEN]\`). The runtime substitutes the real value at egress, and only for that credential binding's allowed hosts.
|
|
486
|
+
- NEVER bake a raw secret — or the output of \`get-config\` with \`includeSecrets: true\` — into script source, script args, a schedule's \`taskTemplate\`, or a task description. Those are stored as plaintext and replayed on every run. Use a connection or a credential binding instead.
|
|
487
|
+
`,
|
|
488
|
+
variables: [],
|
|
489
|
+
category: "system",
|
|
490
|
+
});
|
|
491
|
+
|
|
437
492
|
registerTemplate({
|
|
438
493
|
eventType: "system.agent.script_rubric",
|
|
439
494
|
header: "",
|
|
440
495
|
defaultBody: `
|
|
496
|
+
{{@template[system.agent.script_authoring_contract]}}
|
|
497
|
+
|
|
441
498
|
### Agent Scripts — for bulk, repetitive, or data-heavy work
|
|
442
499
|
|
|
443
500
|
Use **scripts** (\`script-upsert\` + \`script-run\`) when a task involves repetitive SDK calls, large data processing, or deterministic multi-step pipelines. Scripts run out-of-process and return only their final result.
|
|
@@ -479,13 +536,7 @@ registerTemplate({
|
|
|
479
536
|
|
|
480
537
|
This swarm runs in **scripts-only mode**. The ONLY swarm MCP tools available are the script tools: \`script-search\`, \`script-run\`, \`script-upsert\`, \`script-delete\`, \`script-query-types\`, \`launch-script-run\`, \`get-script-run\`, \`list-script-runs\` (your harness may expose them under a prefix, e.g. \`mcp__agent-swarm__script-run\` — use the exact registered tool id). They are already loaded. Named tools like \`store-progress\`, \`send-task\`, \`post-message\`, \`memory-search\` do NOT exist here — do not search for them.
|
|
481
538
|
|
|
482
|
-
|
|
483
|
-
|
|
484
|
-
\`\`\`ts
|
|
485
|
-
export default async function (args: any, ctx: any) { /* ... */ }
|
|
486
|
-
\`\`\`
|
|
487
|
-
|
|
488
|
-
The full SDK is \`ctx.swarm.*\` — task lifecycle (\`task_get\`, \`task_send\`, \`task_storeProgress\`, \`task_action\`, \`task_list\`), messaging (\`message_post\`, \`message_read\`), Slack (\`slack_reply\`, \`slack_post\`, \`slack_read\`), memory, kv, swarm info (\`swarm_get\`, \`agent_info\`), and more. Responses are usually wrapped — prefer \`res?.data ?? res\`.
|
|
539
|
+
The script authoring contract above (entry signature, \`ctx\` shape, secret handling) applies here unchanged. The full SDK is \`ctx.swarm.*\` — task lifecycle (\`task_get\`, \`task_send\`, \`task_storeProgress\`, \`task_action\`, \`task_list\`), messaging (\`message_post\`, \`message_read\`), Slack (\`slack_reply\`, \`slack_post\`, \`slack_read\`), memory, kv, swarm info (\`swarm_get\`, \`agent_info\`), and more. Responses are usually wrapped — prefer \`res?.data ?? res\`.
|
|
489
540
|
|
|
490
541
|
**Built-in coordination scripts — USE THESE FIRST (\`script-run\` with \`name\` + \`args\`):**
|
|
491
542
|
- \`delegate\` {agentName, task, parentTaskId?} → subtask for an agent by name; returns {taskId}
|
|
@@ -535,6 +586,18 @@ You have access to the \`context-mode\` MCP tools (\`batch_execute\`, \`execute\
|
|
|
535
586
|
|
|
536
587
|
{{@template[system.agent.script_rubric]}}
|
|
537
588
|
|
|
589
|
+
{{@template[system.agent.scheduling]}}
|
|
590
|
+
`,
|
|
591
|
+
variables: [],
|
|
592
|
+
category: "system",
|
|
593
|
+
});
|
|
594
|
+
|
|
595
|
+
// Standalone so the pi composites (which drop `system.agent.context_mode` and
|
|
596
|
+
// its ctx_* tool advertisement) can still include the scheduling rules.
|
|
597
|
+
registerTemplate({
|
|
598
|
+
eventType: "system.agent.scheduling",
|
|
599
|
+
header: "",
|
|
600
|
+
defaultBody: `
|
|
538
601
|
### Scheduling — Pick the Right targetType
|
|
539
602
|
|
|
540
603
|
When creating a schedule, match \`targetType\` to the work being fired:
|
|
@@ -807,8 +870,9 @@ registerTemplate({
|
|
|
807
870
|
// Pi-specific worker composite. Identical to `system.session.worker` except it
|
|
808
871
|
// omits only the `system.agent.context_mode` MCP-tool block — pi has no
|
|
809
872
|
// context-mode MCP wiring yet, so advertising the `ctx_*` tools would point at
|
|
810
|
-
// phantom tools. It still includes the shared script rubric
|
|
811
|
-
// guidance so pi sessions get the same bulk-work decision
|
|
873
|
+
// phantom tools. It still includes the shared script rubric, scheduling rules,
|
|
874
|
+
// and seed-script guidance so pi sessions get the same bulk-work decision
|
|
875
|
+
// policy (DES-514).
|
|
812
876
|
registerTemplate({
|
|
813
877
|
eventType: "system.session.worker.pi",
|
|
814
878
|
header: "",
|
|
@@ -819,6 +883,36 @@ registerTemplate({
|
|
|
819
883
|
{{@template[system.agent.filesystem]}}
|
|
820
884
|
{{@template[system.agent.self_awareness]}}
|
|
821
885
|
{{@template[system.agent.script_rubric]}}
|
|
886
|
+
{{@template[system.agent.scheduling]}}
|
|
887
|
+
{{@template[system.agent.seed_scripts]}}
|
|
888
|
+
|
|
889
|
+
{{@template[system.agent.system]}}
|
|
890
|
+
{{@template[system.agent.share_urls]}}
|
|
891
|
+
{{@template[system.agent.code_quality]}}`,
|
|
892
|
+
variables: [
|
|
893
|
+
{ name: "role", description: "The agent's role" },
|
|
894
|
+
{ name: "agentId", description: "The agent's unique identifier" },
|
|
895
|
+
],
|
|
896
|
+
category: "session",
|
|
897
|
+
});
|
|
898
|
+
|
|
899
|
+
// Pi-specific lead composite. Identical to `system.session.lead` except it
|
|
900
|
+
// omits only the `system.agent.context_mode` MCP-tool block — pi has no
|
|
901
|
+
// context-mode MCP wiring, so a pi lead would otherwise be told about `ctx_*`
|
|
902
|
+
// tools that don't exist. It still includes the shared script rubric (which
|
|
903
|
+
// carries the script authoring contract), scheduling rules, and seed-script
|
|
904
|
+
// guidance.
|
|
905
|
+
registerTemplate({
|
|
906
|
+
eventType: "system.session.lead.pi",
|
|
907
|
+
header: "",
|
|
908
|
+
defaultBody: `{{@template[system.agent.role]}}
|
|
909
|
+
|
|
910
|
+
{{@template[system.agent.register]}}
|
|
911
|
+
{{@template[system.agent.lead]}}
|
|
912
|
+
{{@template[system.agent.filesystem]}}
|
|
913
|
+
{{@template[system.agent.self_awareness]}}
|
|
914
|
+
{{@template[system.agent.script_rubric]}}
|
|
915
|
+
{{@template[system.agent.scheduling]}}
|
|
822
916
|
{{@template[system.agent.seed_scripts]}}
|
|
823
917
|
|
|
824
918
|
{{@template[system.agent.system]}}
|
|
@@ -287,8 +287,12 @@ function jsonSchemaToTypeBox(schema: Record<string, unknown>): TSchema {
|
|
|
287
287
|
return Type.Unsafe(schema);
|
|
288
288
|
}
|
|
289
289
|
|
|
290
|
-
/**
|
|
291
|
-
|
|
290
|
+
/**
|
|
291
|
+
* Convert MCP tools to pi-mono ToolDefinition objects.
|
|
292
|
+
* Exported for the isError-propagation conformance test — pi-agent-core
|
|
293
|
+
* derives a tool result's error flag solely from execute() throwing.
|
|
294
|
+
*/
|
|
295
|
+
export function mcpToolsToDefinitions(
|
|
292
296
|
mcpClient: McpHttpClient,
|
|
293
297
|
tools: Array<{ name: string; description?: string; inputSchema: Record<string, unknown> }>,
|
|
294
298
|
): ToolDefinition[] {
|
|
@@ -303,6 +307,12 @@ function mcpToolsToDefinitions(
|
|
|
303
307
|
.map((c) => c.text ?? "")
|
|
304
308
|
.filter(Boolean)
|
|
305
309
|
.join("\n");
|
|
310
|
+
// Propagate MCP isError: pi-agent-core derives a tool result's error flag
|
|
311
|
+
// from whether execute() throws, so a resolved return would silently
|
|
312
|
+
// report failed tool calls as successes to the model.
|
|
313
|
+
if (result.isError) {
|
|
314
|
+
throw new Error(text || `Tool ${tool.name} failed with no error message`);
|
|
315
|
+
}
|
|
306
316
|
return {
|
|
307
317
|
content: [{ type: "text" as const, text: text || "(no output)" }],
|
|
308
318
|
details: undefined,
|
|
@@ -165,31 +165,6 @@ async function syncSetupScriptToServer(
|
|
|
165
165
|
}
|
|
166
166
|
}
|
|
167
167
|
|
|
168
|
-
/**
|
|
169
|
-
* Check if a path is under the agent's own subdirectory on the shared disk.
|
|
170
|
-
* Shared disk categories: thoughts, memory, downloads, misc.
|
|
171
|
-
* Each agent can only write to /workspace/shared/{category}/{agentId}/
|
|
172
|
-
*/
|
|
173
|
-
function isOwnedSharedPath(path: string, agentId: string): boolean {
|
|
174
|
-
const sharedCategories = ["thoughts", "memory", "downloads", "misc"];
|
|
175
|
-
return sharedCategories.some((cat) => path.startsWith(`/workspace/shared/${cat}/${agentId}/`));
|
|
176
|
-
}
|
|
177
|
-
|
|
178
|
-
/**
|
|
179
|
-
* Build the shared disk write warning message for a given agent ID.
|
|
180
|
-
*/
|
|
181
|
-
function sharedDiskWriteWarning(agentId: string): string {
|
|
182
|
-
return (
|
|
183
|
-
`⚠️ This write will fail: You don't have write access to this directory.\n\n` +
|
|
184
|
-
`On shared workspaces, each agent can only write to their own directories:\n` +
|
|
185
|
-
`- /workspace/shared/thoughts/${agentId}/\n` +
|
|
186
|
-
`- /workspace/shared/memory/${agentId}/\n` +
|
|
187
|
-
`- /workspace/shared/downloads/${agentId}/\n` +
|
|
188
|
-
`- /workspace/shared/misc/${agentId}/\n\n` +
|
|
189
|
-
`You CAN read any file on the shared disk. For writes, use your own subdirectory.`
|
|
190
|
-
);
|
|
191
|
-
}
|
|
192
|
-
|
|
193
168
|
/** Auto-index a file written to memory directory */
|
|
194
169
|
async function autoIndexMemoryFile(config: SwarmHooksConfig, editedPath: string): Promise<void> {
|
|
195
170
|
try {
|
|
@@ -505,24 +480,6 @@ export function createSwarmHooksExtension(config: SwarmHooksConfig): ExtensionFa
|
|
|
505
480
|
}
|
|
506
481
|
}
|
|
507
482
|
|
|
508
|
-
// Shared disk write prevention (Archil only — skip in local dev)
|
|
509
|
-
if (process.env.ARCHIL_MOUNT_TOKEN) {
|
|
510
|
-
// Pi-mono uses lowercase tool names: "write", "edit"
|
|
511
|
-
if (event.toolName === "write" || event.toolName === "edit") {
|
|
512
|
-
const toolInput =
|
|
513
|
-
"input" in event
|
|
514
|
-
? (event.input as { file_path?: string; path?: string } | undefined)
|
|
515
|
-
: undefined;
|
|
516
|
-
const targetPath = toolInput?.file_path || toolInput?.path || "";
|
|
517
|
-
if (
|
|
518
|
-
targetPath.startsWith("/workspace/shared/") &&
|
|
519
|
-
!isOwnedSharedPath(targetPath, config.agentId)
|
|
520
|
-
) {
|
|
521
|
-
console.log(sharedDiskWriteWarning(config.agentId));
|
|
522
|
-
}
|
|
523
|
-
}
|
|
524
|
-
}
|
|
525
|
-
|
|
526
483
|
return undefined;
|
|
527
484
|
});
|
|
528
485
|
|
|
@@ -562,27 +519,6 @@ export function createSwarmHooksExtension(config: SwarmHooksConfig): ExtensionFa
|
|
|
562
519
|
}
|
|
563
520
|
}
|
|
564
521
|
|
|
565
|
-
// Shared disk write failure detection (Archil only — safety net)
|
|
566
|
-
if (process.env.ARCHIL_MOUNT_TOKEN) {
|
|
567
|
-
// Pi-mono uses lowercase tool names
|
|
568
|
-
if (event.toolName === "write" || event.toolName === "edit" || event.toolName === "bash") {
|
|
569
|
-
const resultStr = JSON.stringify(event.content ?? []);
|
|
570
|
-
if (resultStr.includes("Read-only file system")) {
|
|
571
|
-
const resultInput = event.input as { file_path?: string; path?: string } | undefined;
|
|
572
|
-
const resultPath = resultInput?.file_path || resultInput?.path || "";
|
|
573
|
-
if (
|
|
574
|
-
resultPath.startsWith("/workspace/shared/") &&
|
|
575
|
-
!isOwnedSharedPath(resultPath, config.agentId)
|
|
576
|
-
) {
|
|
577
|
-
console.log(sharedDiskWriteWarning(config.agentId));
|
|
578
|
-
} else if (!resultPath) {
|
|
579
|
-
// Bash tool — no file_path, just warn generically
|
|
580
|
-
console.log(sharedDiskWriteWarning(config.agentId));
|
|
581
|
-
}
|
|
582
|
-
}
|
|
583
|
-
}
|
|
584
|
-
}
|
|
585
|
-
|
|
586
522
|
// File sync: check if tool wrote to identity files or memory dirs
|
|
587
523
|
// Pi-mono uses tool names: "write", "edit" (lowercase, unlike Claude's "Write", "Edit")
|
|
588
524
|
const toolName = event.toolName;
|
|
@@ -17,6 +17,20 @@ rate by hand should also update this file.
|
|
|
17
17
|
- **Pinned local entries**: safe by construction. The runtime refresh only adds
|
|
18
18
|
pricing rows; it does not rewrite or delete the committed snapshot.
|
|
19
19
|
|
|
20
|
+
## Live UI catalog: GET /api/models-catalog
|
|
21
|
+
|
|
22
|
+
- **Runtime module**: `src/be/models-catalog.ts`; route in
|
|
23
|
+
`src/http/models-catalog.ts`.
|
|
24
|
+
- Every successful models.dev fetch in the runtime refresh above also updates
|
|
25
|
+
an in-memory slim catalog (openrouter / anthropic / openai / amazon-bedrock
|
|
26
|
+
only, picker-relevant fields only). The UI model picker
|
|
27
|
+
(`apps/ui/src/lib/agent-runtime-models.ts` via `useModelsCatalog()`) prefers
|
|
28
|
+
this over its build-time snapshot, so new models appear without a deploy.
|
|
29
|
+
- Pinned limited-availability entries (`PINNED_MODELSDEV_ENTRIES`) are
|
|
30
|
+
re-merged from the vendored snapshot when models.dev doesn't list them yet.
|
|
31
|
+
- Until the first successful fetch (or when models.dev is unreachable) the
|
|
32
|
+
endpoint serves the vendored snapshot with `source: "snapshot"`.
|
|
33
|
+
|
|
20
34
|
## Fallback/UI catalog: vendored models.dev snapshot
|
|
21
35
|
|
|
22
36
|
- **Fallback path**: `src/be/modelsdev-cache.json`
|
|
@@ -26,7 +40,8 @@ rate by hand should also update this file.
|
|
|
26
40
|
`seedPricingFromModelsDev()`,
|
|
27
41
|
called from `src/server.ts` after `initDb`.
|
|
28
42
|
- **Role**: cold-start fallback seed for pricing when models.dev is unavailable,
|
|
29
|
-
plus the
|
|
43
|
+
plus the fallback for the UI model picker while `GET /api/models-catalog`
|
|
44
|
+
hasn't resolved (names, labels, and context windows).
|
|
30
45
|
- **Projection rules** (see the same module for code-level detail):
|
|
31
46
|
- Anthropic models → rows under `provider='claude'` AND `provider='claude-managed'`.
|
|
32
47
|
Shortnames (`opus`, `sonnet`, `haiku`) ALSO get rows keyed by the current
|
|
@@ -17,8 +17,17 @@ type StructuredError = {
|
|
|
17
17
|
userFrames: StackFrame[];
|
|
18
18
|
userScriptLine?: number;
|
|
19
19
|
userScriptColumn?: number;
|
|
20
|
+
ctxSignatureHint?: string;
|
|
20
21
|
};
|
|
21
22
|
|
|
23
|
+
// Matches runtime errors that look like a swapped/misused (args, ctx) signature,
|
|
24
|
+
// e.g. destructuring `ctx.api`/`ctx.kv`/`ctx.fetchJson`/`ctx.log` off `undefined`/`null`
|
|
25
|
+
// because the script read them off `args` instead.
|
|
26
|
+
const CTX_UNDEFINED_ACCESS_RE = /cannot read propert(?:y|ies)(?: .*)? of (?:undefined|null)/i;
|
|
27
|
+
const CTX_MEMBER_RE = /\b(api|kv|fetchJson|log)\b/i;
|
|
28
|
+
const CTX_SIGNATURE_HINT =
|
|
29
|
+
"Hint: swarm scripts export default async function (args, ctx) — args comes first, ctx second.";
|
|
30
|
+
|
|
22
31
|
function buildStructuredError(err: unknown, userModulePath: string): StructuredError {
|
|
23
32
|
const errObj = err instanceof Error ? err : new Error(String(err));
|
|
24
33
|
const name = errObj.name || "Error";
|
|
@@ -48,6 +57,11 @@ function buildStructuredError(err: unknown, userModulePath: string): StructuredE
|
|
|
48
57
|
}
|
|
49
58
|
}
|
|
50
59
|
|
|
60
|
+
const ctxSignatureHint =
|
|
61
|
+
CTX_UNDEFINED_ACCESS_RE.test(message) && CTX_MEMBER_RE.test(message)
|
|
62
|
+
? CTX_SIGNATURE_HINT
|
|
63
|
+
: undefined;
|
|
64
|
+
|
|
51
65
|
return {
|
|
52
66
|
name,
|
|
53
67
|
message,
|
|
@@ -55,6 +69,7 @@ function buildStructuredError(err: unknown, userModulePath: string): StructuredE
|
|
|
55
69
|
userFrames,
|
|
56
70
|
userScriptLine: userFrames[0]?.line,
|
|
57
71
|
userScriptColumn: userFrames[0]?.column,
|
|
72
|
+
...(ctxSignatureHint ? { ctxSignatureHint } : {}),
|
|
58
73
|
};
|
|
59
74
|
}
|
|
60
75
|
|
|
@@ -103,7 +118,9 @@ try {
|
|
|
103
118
|
|
|
104
119
|
const mod = await import(userModulePath);
|
|
105
120
|
if (typeof mod.default !== "function") {
|
|
106
|
-
throw new Error(
|
|
121
|
+
throw new Error(
|
|
122
|
+
"Swarm script must export a default function. Script must export default async function (args, ctx) — args FIRST, ctx second.",
|
|
123
|
+
);
|
|
107
124
|
}
|
|
108
125
|
|
|
109
126
|
let validatedArgs = parsedArgs;
|
|
@@ -270,10 +270,20 @@ function bridgeRequestFor(name: string, args: unknown): BridgeRequest | null {
|
|
|
270
270
|
case "workflow_listRuns": {
|
|
271
271
|
const wfId = typeof body.workflowId === "string" ? body.workflowId : undefined;
|
|
272
272
|
if (!wfId) throw new Error("workflow_listRuns requires string `workflowId`");
|
|
273
|
+
const limit = body.limit === undefined ? 20 : body.limit;
|
|
274
|
+
const offset = body.offset === undefined ? 0 : body.offset;
|
|
275
|
+
if (!Number.isInteger(limit) || (limit as number) < 1 || (limit as number) > 100) {
|
|
276
|
+
throw new Error("workflow_listRuns `limit` must be an integer between 1 and 100");
|
|
277
|
+
}
|
|
278
|
+
if (!Number.isInteger(offset) || (offset as number) < 0) {
|
|
279
|
+
throw new Error("workflow_listRuns `offset` must be a non-negative integer");
|
|
280
|
+
}
|
|
273
281
|
return {
|
|
274
282
|
method: "GET",
|
|
275
283
|
path: appendQuery(`/api/workflows/${encodeURIComponent(wfId)}/runs`, {
|
|
276
284
|
status: body.status,
|
|
285
|
+
limit,
|
|
286
|
+
offset,
|
|
277
287
|
}),
|
|
278
288
|
};
|
|
279
289
|
}
|
|
@@ -182,6 +182,8 @@ declare module "swarm-sdk" {
|
|
|
182
182
|
workflow_listRuns(args: {
|
|
183
183
|
workflowId: string;
|
|
184
184
|
status?: "running" | "waiting" | "completed" | "failed" | "skipped" | "cancelled";
|
|
185
|
+
limit?: number;
|
|
186
|
+
offset?: number;
|
|
185
187
|
}): Promise<unknown>;
|
|
186
188
|
workflow_getRun(args: { id: string }): Promise<unknown>;
|
|
187
189
|
// --- prompt templates ---
|
|
@@ -438,6 +440,17 @@ declare module "swarm-sdk" {
|
|
|
438
440
|
logger: ScriptLogger;
|
|
439
441
|
}
|
|
440
442
|
|
|
443
|
+
/**
|
|
444
|
+
* A swarm script's default export. `args` comes FIRST, `ctx` second — never swap them.
|
|
445
|
+
*
|
|
446
|
+
* @example
|
|
447
|
+
* import type { ScriptContext } from "swarm-sdk";
|
|
448
|
+
*
|
|
449
|
+
* export default async function (args: { name: string }, ctx: ScriptContext) {
|
|
450
|
+
* await ctx.logger.log(`hello ${args.name}`);
|
|
451
|
+
* return { ok: true };
|
|
452
|
+
* }
|
|
453
|
+
*/
|
|
441
454
|
// biome-ignore lint/suspicious/noExplicitAny: scripts may narrow their args type at the entrypoint.
|
|
442
455
|
export type ScriptMain = (args: any, ctx: ScriptContext) => unknown | Promise<unknown>;
|
|
443
456
|
}
|
|
@@ -164,6 +164,8 @@ declare module "swarm-sdk" {
|
|
|
164
164
|
workflow_listRuns(args: {
|
|
165
165
|
workflowId: string;
|
|
166
166
|
status?: "running" | "waiting" | "completed" | "failed" | "skipped" | "cancelled";
|
|
167
|
+
limit?: number;
|
|
168
|
+
offset?: number;
|
|
167
169
|
}): Promise<unknown>;
|
|
168
170
|
workflow_getRun(args: { id: string }): Promise<unknown>;
|
|
169
171
|
// --- prompt templates ---
|
|
@@ -420,6 +422,17 @@ declare module "swarm-sdk" {
|
|
|
420
422
|
logger: ScriptLogger;
|
|
421
423
|
}
|
|
422
424
|
|
|
425
|
+
/**
|
|
426
|
+
* A swarm script's default export. `args` comes FIRST, `ctx` second — never swap them.
|
|
427
|
+
*
|
|
428
|
+
* @example
|
|
429
|
+
* import type { ScriptContext } from "swarm-sdk";
|
|
430
|
+
*
|
|
431
|
+
* export default async function (args: { name: string }, ctx: ScriptContext) {
|
|
432
|
+
* await ctx.logger.log(`hello ${args.name}`);
|
|
433
|
+
* return { ok: true };
|
|
434
|
+
* }
|
|
435
|
+
*/
|
|
423
436
|
// biome-ignore lint/suspicious/noExplicitAny: scripts may narrow their args type at the entrypoint.
|
|
424
437
|
export type ScriptMain = (args: any, ctx: ScriptContext) => unknown | Promise<unknown>;
|
|
425
438
|
|
package/src/server-user.ts
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
|
|
2
|
-
import type {
|
|
2
|
+
import type { ToolAnnotations } from "@modelcontextprotocol/sdk/types.js";
|
|
3
3
|
import * as z from "zod";
|
|
4
4
|
import pkg from "../package.json";
|
|
5
5
|
import { enqueueAdmissionRow } from "./be/rbac-audit";
|
|
@@ -33,7 +33,7 @@ import {
|
|
|
33
33
|
taskActionOutputSchema,
|
|
34
34
|
} from "./tools/task-action";
|
|
35
35
|
import { userCtx } from "./tools/task-tool-ctx";
|
|
36
|
-
import { createToolRegistrar } from "./tools/utils";
|
|
36
|
+
import { createToolRegistrar, type SwarmToolResult, toolErr } from "./tools/utils";
|
|
37
37
|
import { ModelTierSchema, type User } from "./types";
|
|
38
38
|
import { isSteeringEnabled } from "./utils/steering-enabled";
|
|
39
39
|
|
|
@@ -66,7 +66,7 @@ async function maybeDenyUserToolAdmission(
|
|
|
66
66
|
user: User,
|
|
67
67
|
toolName: string,
|
|
68
68
|
config: UserToolAdmissionConfig,
|
|
69
|
-
): Promise<
|
|
69
|
+
): Promise<SwarmToolResult | undefined> {
|
|
70
70
|
if (!isRbacEnabled()) return undefined;
|
|
71
71
|
|
|
72
72
|
const grant = getUserGrant(user.id);
|
|
@@ -86,10 +86,7 @@ async function maybeDenyUserToolAdmission(
|
|
|
86
86
|
|
|
87
87
|
if (decision.allow) return undefined;
|
|
88
88
|
|
|
89
|
-
return {
|
|
90
|
-
isError: true,
|
|
91
|
-
content: [{ type: "text", text: `Forbidden: ${decision.reason}` }],
|
|
92
|
-
};
|
|
89
|
+
return toolErr(`Forbidden: ${decision.reason}`);
|
|
93
90
|
}
|
|
94
91
|
|
|
95
92
|
export function createUserServer(user: User): McpServer {
|
package/src/slack/blocks.ts
CHANGED
|
@@ -394,15 +394,28 @@ const MAX_OUTPUT_LENGTH = 120;
|
|
|
394
394
|
/**
|
|
395
395
|
* Truncate output to the first sentence or MAX_OUTPUT_LENGTH, whichever is shorter.
|
|
396
396
|
*/
|
|
397
|
-
function truncateOutput(text: string): string {
|
|
397
|
+
function truncateOutput(text: string): { text: string; truncated: boolean } {
|
|
398
398
|
// Find first sentence boundary (. followed by space or end)
|
|
399
399
|
const sentenceEnd = text.search(/\.\s/);
|
|
400
|
-
const
|
|
401
|
-
if (
|
|
402
|
-
|
|
403
|
-
|
|
400
|
+
const sentenceCut = sentenceEnd !== -1 ? sentenceEnd + 1 : text.length;
|
|
401
|
+
if (sentenceCut === text.length && text.length <= MAX_OUTPUT_LENGTH) {
|
|
402
|
+
return { text, truncated: false };
|
|
403
|
+
}
|
|
404
|
+
|
|
405
|
+
let cut = Math.min(sentenceCut, MAX_OUTPUT_LENGTH);
|
|
406
|
+
if (cut === MAX_OUTPUT_LENGTH) {
|
|
407
|
+
const boundary = text.lastIndexOf(" ", MAX_OUTPUT_LENGTH);
|
|
408
|
+
cut = boundary >= MAX_OUTPUT_LENGTH / 2 ? boundary : MAX_OUTPUT_LENGTH;
|
|
409
|
+
}
|
|
404
410
|
const omitted = text.length - cut;
|
|
405
|
-
return
|
|
411
|
+
return {
|
|
412
|
+
text: `${text.slice(0, cut).trimEnd()}… (${omitted} more chars; open task for full output)`,
|
|
413
|
+
truncated: true,
|
|
414
|
+
};
|
|
415
|
+
}
|
|
416
|
+
|
|
417
|
+
export function isTreeOutputTruncated(text: string): boolean {
|
|
418
|
+
return truncateOutput(text).truncated;
|
|
406
419
|
}
|
|
407
420
|
|
|
408
421
|
/**
|
|
@@ -433,7 +446,7 @@ function renderChildDetail(node: TreeNode, indent: string): string[] {
|
|
|
433
446
|
}
|
|
434
447
|
|
|
435
448
|
if (node.status === "completed" && !node.slackReplySent && node.output) {
|
|
436
|
-
lines.push(`${indent}${truncateOutput(markdownToSlack(node.output))}`);
|
|
449
|
+
lines.push(`${indent}${truncateOutput(markdownToSlack(node.output)).text}`);
|
|
437
450
|
}
|
|
438
451
|
|
|
439
452
|
return lines;
|
|
@@ -457,7 +470,7 @@ function renderTree(root: TreeNode): string {
|
|
|
457
470
|
lines.push(` Error: ${root.failureReason}`);
|
|
458
471
|
}
|
|
459
472
|
if (root.status === "completed" && !root.slackReplySent && root.output) {
|
|
460
|
-
lines.push(` ${truncateOutput(markdownToSlack(root.output))}`);
|
|
473
|
+
lines.push(` ${truncateOutput(markdownToSlack(root.output)).text}`);
|
|
461
474
|
}
|
|
462
475
|
return lines.join("\n");
|
|
463
476
|
}
|
package/src/slack/responses.ts
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import type { WebClient } from "@slack/web-api";
|
|
2
|
-
import { getAgentById, getTaskAttachments } from "../be/db";
|
|
3
|
-
import type { Agent, AgentTask
|
|
2
|
+
import { getAgentById, getTaskAttachments, markTaskSlackReplySent } from "../be/db";
|
|
3
|
+
import type { Agent, AgentTask } from "../types";
|
|
4
4
|
import { getSlackApp } from "./app";
|
|
5
5
|
import {
|
|
6
6
|
buildCancelledBlocks,
|
|
@@ -11,6 +11,7 @@ import {
|
|
|
11
11
|
formatAttachmentsBlockForSlack,
|
|
12
12
|
formatDuration,
|
|
13
13
|
getTaskLink,
|
|
14
|
+
isTreeOutputTruncated,
|
|
14
15
|
markdownToSlack,
|
|
15
16
|
splitSlackSectionText,
|
|
16
17
|
} from "./blocks";
|
|
@@ -33,16 +34,9 @@ function classifySlackUpdateError(error: unknown): SlackUpdateResult {
|
|
|
33
34
|
}
|
|
34
35
|
|
|
35
36
|
const isDev = process.env.ENV === "development";
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
const
|
|
39
|
-
"done",
|
|
40
|
-
"completed",
|
|
41
|
-
"complete",
|
|
42
|
-
"task completed",
|
|
43
|
-
"process completed successfully",
|
|
44
|
-
"process completed successfully (no output captured)",
|
|
45
|
-
]);
|
|
37
|
+
// Ten 2,900-char sections keep each message's fallback text below Slack's
|
|
38
|
+
// 40,000-char message cap while allowing arbitrarily long output in batches.
|
|
39
|
+
const MAX_INLINE_OUTPUT_BLOCKS_PER_MESSAGE = 10;
|
|
46
40
|
|
|
47
41
|
/**
|
|
48
42
|
* Get the display name for an agent, with (dev) prefix if in development mode.
|
|
@@ -51,45 +45,23 @@ function getAgentDisplayName(agent: Agent): string {
|
|
|
51
45
|
return isDev ? `(dev) ${agent.name}` : agent.name;
|
|
52
46
|
}
|
|
53
47
|
|
|
54
|
-
export function shouldPostInlineCompletionOutput(
|
|
55
|
-
task: AgentTask,
|
|
56
|
-
attachments: readonly TaskAttachment[],
|
|
57
|
-
): boolean {
|
|
48
|
+
export function shouldPostInlineCompletionOutput(task: AgentTask): boolean {
|
|
58
49
|
if (task.status !== "completed") return false;
|
|
59
50
|
if (!task.slackChannelId || !task.slackThreadTs) return false;
|
|
60
51
|
if (task.slackReplySent) return false;
|
|
61
52
|
|
|
62
53
|
const output = task.output?.trim();
|
|
63
|
-
|
|
64
|
-
if (INLINE_OUTPUT_FALLBACKS.has(output.toLowerCase())) return false;
|
|
65
|
-
|
|
66
|
-
return !attachments.some((attachment) => attachment.isPrimary);
|
|
54
|
+
return !!output && isTreeOutputTruncated(markdownToSlack(output));
|
|
67
55
|
}
|
|
68
56
|
|
|
69
|
-
export function
|
|
57
|
+
export function formatInlineCompletionOutputChunks(opts: {
|
|
70
58
|
agentName: string;
|
|
71
59
|
taskId: string;
|
|
72
60
|
output: string;
|
|
73
|
-
}): string {
|
|
61
|
+
}): string[] {
|
|
74
62
|
const header = `✅ *${opts.agentName}* completed with output (${getTaskLink(opts.taskId)}):\n\n`;
|
|
75
|
-
const tail = `\n\n…(full output in task ${getTaskLink(opts.taskId)})`;
|
|
76
63
|
const slackOutput = markdownToSlack(opts.output.trim());
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
if (slackOutput.length <= maxBodyLength) {
|
|
80
|
-
return header + slackOutput;
|
|
81
|
-
}
|
|
82
|
-
|
|
83
|
-
const maxTruncatedBodyLength = Math.max(0, maxBodyLength - tail.length);
|
|
84
|
-
let cut = slackOutput.lastIndexOf("\n", maxTruncatedBodyLength);
|
|
85
|
-
if (cut < maxTruncatedBodyLength / 2) {
|
|
86
|
-
cut = slackOutput.lastIndexOf(" ", maxTruncatedBodyLength);
|
|
87
|
-
}
|
|
88
|
-
if (cut < maxTruncatedBodyLength / 2) {
|
|
89
|
-
cut = maxTruncatedBodyLength;
|
|
90
|
-
}
|
|
91
|
-
|
|
92
|
-
return header + slackOutput.slice(0, cut).trimEnd() + tail;
|
|
64
|
+
return splitSlackSectionText(header + slackOutput);
|
|
93
65
|
}
|
|
94
66
|
|
|
95
67
|
export async function sendInlineTaskOutput(task: AgentTask): Promise<boolean> {
|
|
@@ -104,24 +76,28 @@ export async function sendInlineTaskOutput(task: AgentTask): Promise<boolean> {
|
|
|
104
76
|
return false;
|
|
105
77
|
}
|
|
106
78
|
|
|
107
|
-
const
|
|
79
|
+
const chunks = formatInlineCompletionOutputChunks({
|
|
108
80
|
agentName: agent.name,
|
|
109
81
|
taskId: task.id,
|
|
110
82
|
output: task.output,
|
|
111
83
|
});
|
|
112
84
|
|
|
113
85
|
try {
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
86
|
+
for (let i = 0; i < chunks.length; i += MAX_INLINE_OUTPUT_BLOCKS_PER_MESSAGE) {
|
|
87
|
+
const batch = chunks.slice(i, i + MAX_INLINE_OUTPUT_BLOCKS_PER_MESSAGE);
|
|
88
|
+
await sendWithPersona(app.client, {
|
|
89
|
+
channel: task.slackChannelId,
|
|
90
|
+
thread_ts: task.slackThreadTs,
|
|
91
|
+
text: batch.join("\n\n"),
|
|
92
|
+
username: getAgentDisplayName(agent),
|
|
93
|
+
icon_emoji: getAgentEmoji(agent),
|
|
94
|
+
blocks: batch.map((chunk) => ({
|
|
95
|
+
type: "section",
|
|
96
|
+
text: { type: "mrkdwn", text: chunk },
|
|
97
|
+
})),
|
|
98
|
+
});
|
|
99
|
+
}
|
|
100
|
+
markTaskSlackReplySent(task.id);
|
|
125
101
|
return true;
|
|
126
102
|
} catch (error) {
|
|
127
103
|
console.error(`[Slack] Failed to send inline output for task ${task.id}:`, error);
|