@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
|
@@ -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(),
|
|
@@ -3,16 +3,23 @@ import * as z from "zod";
|
|
|
3
3
|
import { getAgentById } from "@/be/db";
|
|
4
4
|
import { getSlackApp } from "@/slack/app";
|
|
5
5
|
import { DEFAULT_DOWNLOAD_DIR, downloadFile, getFileInfo } from "@/slack/files";
|
|
6
|
+
import { attachableTask, attachSlackFilesForAgent } from "@/slack/inbound-files";
|
|
6
7
|
import { createToolRegistrar, swarmToolOutputSchema, toolErr, toolOk } from "@/tools/utils";
|
|
7
8
|
|
|
9
|
+
/** `F0123ABCD` out of a `files.slack.com/files-pri/<team>-<file>/…` URL. */
|
|
10
|
+
function fileIdFromUrl(url: string): string | undefined {
|
|
11
|
+
return /\/files-pri\/[A-Z0-9]+-([A-Z0-9]+)\//.exec(url)?.[1];
|
|
12
|
+
}
|
|
13
|
+
|
|
8
14
|
export const registerSlackDownloadFileTool = (server: McpServer) => {
|
|
9
15
|
createToolRegistrar(server)(
|
|
10
16
|
"slack-download-file",
|
|
11
17
|
{
|
|
12
18
|
title: "Download file from Slack",
|
|
13
19
|
description:
|
|
14
|
-
"Download a file from Slack by file ID or URL.
|
|
15
|
-
|
|
20
|
+
"Download a file from Slack by file ID or URL. From a task, the file is stored as an attachment of that task and the result carries a ready-to-run `fetchCommand` to get the bytes into your container. Without a task, the file is saved on the API server's disk, which your container usually can't read.",
|
|
21
|
+
// Not read-only: from a task it uploads the file and records an attachment.
|
|
22
|
+
annotations: { readOnlyHint: false, openWorldHint: true },
|
|
16
23
|
|
|
17
24
|
inputSchema: z.object({
|
|
18
25
|
fileId: z
|
|
@@ -24,11 +31,17 @@ export const registerSlackDownloadFileTool = (server: McpServer) => {
|
|
|
24
31
|
.url()
|
|
25
32
|
.optional()
|
|
26
33
|
.describe("Direct URL to download (url_private_download from a file object)."),
|
|
34
|
+
taskId: z
|
|
35
|
+
.uuid()
|
|
36
|
+
.optional()
|
|
37
|
+
.describe(
|
|
38
|
+
"Task to attach the file to. Defaults to the task you are working on; must be a task you own or created.",
|
|
39
|
+
),
|
|
27
40
|
savePath: z
|
|
28
41
|
.string()
|
|
29
42
|
.optional()
|
|
30
43
|
.describe(
|
|
31
|
-
"
|
|
44
|
+
"Only without a task: where to save the file on the API server (directory or full path). Defaults to /workspace/shared/downloads/{agentId}/slack/.",
|
|
32
45
|
),
|
|
33
46
|
filename: z
|
|
34
47
|
.string()
|
|
@@ -36,7 +49,15 @@ export const registerSlackDownloadFileTool = (server: McpServer) => {
|
|
|
36
49
|
.describe("Filename to use when saving. Only used if savePath is a directory."),
|
|
37
50
|
}),
|
|
38
51
|
outputSchema: swarmToolOutputSchema({
|
|
39
|
-
|
|
52
|
+
taskId: z.string().optional(),
|
|
53
|
+
attachmentId: z.string().optional(),
|
|
54
|
+
fetchCommand: z.string().optional(),
|
|
55
|
+
savedPath: z
|
|
56
|
+
.string()
|
|
57
|
+
.optional()
|
|
58
|
+
.describe(
|
|
59
|
+
"Only without a task: the path on the API server's disk, not in your container.",
|
|
60
|
+
),
|
|
40
61
|
fileInfo: z
|
|
41
62
|
.looseObject({
|
|
42
63
|
id: z.string().optional(),
|
|
@@ -47,7 +68,7 @@ export const registerSlackDownloadFileTool = (server: McpServer) => {
|
|
|
47
68
|
.optional(),
|
|
48
69
|
}),
|
|
49
70
|
},
|
|
50
|
-
async ({ fileId, url, savePath, filename }, requestInfo, _meta) => {
|
|
71
|
+
async ({ fileId, url, taskId, savePath, filename }, requestInfo, _meta) => {
|
|
51
72
|
if (!requestInfo.agentId) {
|
|
52
73
|
return toolErr("Agent ID not found.");
|
|
53
74
|
}
|
|
@@ -72,33 +93,42 @@ export const registerSlackDownloadFileTool = (server: McpServer) => {
|
|
|
72
93
|
return toolErr("Slack bot token not configured.");
|
|
73
94
|
}
|
|
74
95
|
|
|
96
|
+
const task = await attachableTask(agent.id, taskId ?? requestInfo.sourceTaskId);
|
|
97
|
+
if (taskId && !task) {
|
|
98
|
+
return toolErr("You don't have context for this task.");
|
|
99
|
+
}
|
|
100
|
+
|
|
75
101
|
try {
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
// If fileId provided, get file info first
|
|
87
|
-
if (fileId) {
|
|
88
|
-
const info = await getFileInfo(app.client, fileId);
|
|
102
|
+
const id = fileId ?? (url ? fileIdFromUrl(url) : undefined);
|
|
103
|
+
const info = id ? await getFileInfo(app.client, id) : null;
|
|
104
|
+
if (fileId && !info) {
|
|
105
|
+
return toolErr(`File not found: ${fileId}`);
|
|
106
|
+
}
|
|
107
|
+
const fileInfo = info
|
|
108
|
+
? { id: info.id, name: info.name, mimetype: info.mimetype, size: info.size }
|
|
109
|
+
: undefined;
|
|
110
|
+
|
|
111
|
+
if (task) {
|
|
89
112
|
if (!info) {
|
|
90
|
-
return toolErr(
|
|
113
|
+
return toolErr("Could not identify the Slack file in that URL; pass fileId instead.");
|
|
91
114
|
}
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
115
|
+
const outcome = (await attachSlackFilesForAgent(app.client, task, [info], agent.id)).get(
|
|
116
|
+
info.id,
|
|
117
|
+
);
|
|
118
|
+
if (!outcome || "reason" in outcome) {
|
|
119
|
+
return toolErr(`Failed to attach file: ${outcome?.reason ?? "unknown error"}`);
|
|
120
|
+
}
|
|
121
|
+
const { attachment, fetchCommand } = outcome;
|
|
122
|
+
return toolOk(
|
|
123
|
+
`Attached ${attachment.name} to task ${task.id} (attachment ${attachment.id}).`,
|
|
124
|
+
{
|
|
125
|
+
details: `Get it into your container with:\n${fetchCommand}`,
|
|
126
|
+
data: { taskId: task.id, attachmentId: attachment.id, fetchCommand, fileInfo },
|
|
127
|
+
},
|
|
128
|
+
);
|
|
100
129
|
}
|
|
101
130
|
|
|
131
|
+
const downloadUrl = info?.url_private_download ?? url;
|
|
102
132
|
if (!downloadUrl) {
|
|
103
133
|
return toolErr("No download URL available.");
|
|
104
134
|
}
|
|
@@ -125,8 +155,9 @@ export const registerSlackDownloadFileTool = (server: McpServer) => {
|
|
|
125
155
|
return toolErr(`Failed to download file: ${result.error}`);
|
|
126
156
|
}
|
|
127
157
|
|
|
128
|
-
|
|
129
|
-
|
|
158
|
+
return toolOk(`File saved on the API server at ${result.savedPath}.`, {
|
|
159
|
+
data: { savedPath: result.savedPath, fileInfo },
|
|
160
|
+
});
|
|
130
161
|
} catch (error) {
|
|
131
162
|
const errorMsg = error instanceof Error ? error.message : String(error);
|
|
132
163
|
return toolErr(`Failed to download file: ${errorMsg}`);
|
package/src/tools/slack-read.ts
CHANGED
|
@@ -4,13 +4,16 @@ import { getAgentById, getInboxMessageById, getTaskById } from "@/be/db";
|
|
|
4
4
|
import { can } from "@/rbac";
|
|
5
5
|
import { getSlackApp } from "@/slack/app";
|
|
6
6
|
import { withAutoJoin } from "@/slack/channel-join";
|
|
7
|
-
import { downloadFile } from "@/slack/files";
|
|
7
|
+
import { downloadFile, type SlackFile } from "@/slack/files";
|
|
8
|
+
import { attachableTask, attachSlackFilesForAgent } from "@/slack/inbound-files";
|
|
8
9
|
import { extractSlackMessageText } from "@/slack/message-text";
|
|
9
10
|
import { createToolRegistrar, swarmToolOutputSchema, toolErr, toolOk } from "@/tools/utils";
|
|
11
|
+
import type { AgentTask } from "@/types";
|
|
10
12
|
|
|
11
13
|
/**
|
|
12
|
-
*
|
|
13
|
-
* This
|
|
14
|
+
* Where files are auto-downloaded when there is no task to attach them to.
|
|
15
|
+
* This is the API server's disk: a worker container only sees it if the
|
|
16
|
+
* deployment mounts the shared volume there.
|
|
14
17
|
*/
|
|
15
18
|
const AUTO_DOWNLOAD_DIR = "/app/shared/downloads/slack";
|
|
16
19
|
|
|
@@ -21,7 +24,13 @@ const SlackFileSchema = z.looseObject({
|
|
|
21
24
|
filetype: z.string().optional(),
|
|
22
25
|
size: z.number().optional(),
|
|
23
26
|
url_private_download: z.string().optional(),
|
|
24
|
-
|
|
27
|
+
attachmentId: z.string().optional(),
|
|
28
|
+
fetchCommand: z.string().optional(),
|
|
29
|
+
notAttached: z.string().optional(),
|
|
30
|
+
localPath: z
|
|
31
|
+
.string()
|
|
32
|
+
.optional()
|
|
33
|
+
.describe("Only without a task: the path on the API server's disk, not in your container."),
|
|
25
34
|
});
|
|
26
35
|
|
|
27
36
|
const SlackMessageSchema = z.looseObject({
|
|
@@ -39,8 +48,9 @@ export const registerSlackReadTool = (server: McpServer) => {
|
|
|
39
48
|
{
|
|
40
49
|
title: "Read Slack thread/channel history",
|
|
41
50
|
description:
|
|
42
|
-
"Read messages from a Slack thread or channel. Use inboxMessageId or taskId to read from a thread you have context for, or provide channelId directly for channel history (leads only).",
|
|
43
|
-
|
|
51
|
+
"Read messages from a Slack thread or channel. Use inboxMessageId or taskId to read from a thread you have context for, or provide channelId directly for channel history (leads only). From a task, files in the messages are stored as attachments of that task, each with a ready-to-run `fetchCommand`.",
|
|
52
|
+
// Not read-only: from a task it uploads the thread's files as attachments.
|
|
53
|
+
annotations: { readOnlyHint: false, openWorldHint: true },
|
|
44
54
|
|
|
45
55
|
inputSchema: z.object({
|
|
46
56
|
inboxMessageId: z.uuid().optional().describe("Read thread history for an inbox message."),
|
|
@@ -63,7 +73,9 @@ export const registerSlackReadTool = (server: McpServer) => {
|
|
|
63
73
|
includeFiles: z
|
|
64
74
|
.boolean()
|
|
65
75
|
.default(true)
|
|
66
|
-
.describe(
|
|
76
|
+
.describe(
|
|
77
|
+
"Include file attachments in the response (default: true). From a task, they are also stored as attachments of that task.",
|
|
78
|
+
),
|
|
67
79
|
}),
|
|
68
80
|
outputSchema: swarmToolOutputSchema({
|
|
69
81
|
channelId: z.string().optional(),
|
|
@@ -87,6 +99,7 @@ export const registerSlackReadTool = (server: McpServer) => {
|
|
|
87
99
|
|
|
88
100
|
let slackChannelId: string | undefined = channelId;
|
|
89
101
|
let slackThreadTs: string | undefined = threadTs;
|
|
102
|
+
let contextTask: AgentTask | null = null;
|
|
90
103
|
|
|
91
104
|
// Determine Slack context from inbox message or task
|
|
92
105
|
if (inboxMessageId) {
|
|
@@ -110,6 +123,7 @@ export const registerSlackReadTool = (server: McpServer) => {
|
|
|
110
123
|
}
|
|
111
124
|
slackChannelId = task.slackChannelId;
|
|
112
125
|
slackThreadTs = task.slackThreadTs;
|
|
126
|
+
contextTask = task;
|
|
113
127
|
} else if (channelId) {
|
|
114
128
|
// Direct channel access requires lead privileges
|
|
115
129
|
const decision = can({
|
|
@@ -209,6 +223,27 @@ export const registerSlackReadTool = (server: McpServer) => {
|
|
|
209
223
|
// Get token for auto-download
|
|
210
224
|
const token = process.env.SLACK_BOT_TOKEN;
|
|
211
225
|
|
|
226
|
+
// Files go to the calling task's attachments; without one, to the API disk.
|
|
227
|
+
const attachTo =
|
|
228
|
+
contextTask ?? (await attachableTask(agent.id, taskId ?? requestInfo.sourceTaskId));
|
|
229
|
+
if (taskId && !attachTo) {
|
|
230
|
+
return toolErr("You don't have context for this task.", { data: { messages: [] } });
|
|
231
|
+
}
|
|
232
|
+
|
|
233
|
+
type FileInfo = {
|
|
234
|
+
id: string;
|
|
235
|
+
name: string;
|
|
236
|
+
mimetype: string;
|
|
237
|
+
filetype: string;
|
|
238
|
+
size: number;
|
|
239
|
+
url_private_download: string;
|
|
240
|
+
attachmentId?: string;
|
|
241
|
+
fetchCommand?: string;
|
|
242
|
+
notAttached?: string;
|
|
243
|
+
localPath?: string;
|
|
244
|
+
};
|
|
245
|
+
const toAttach: Array<{ raw: RawFile; info: FileInfo }> = [];
|
|
246
|
+
|
|
212
247
|
// Format messages
|
|
213
248
|
const messages: Array<{
|
|
214
249
|
user: string | undefined;
|
|
@@ -216,15 +251,7 @@ export const registerSlackReadTool = (server: McpServer) => {
|
|
|
216
251
|
isBot: boolean;
|
|
217
252
|
text: string;
|
|
218
253
|
ts: string;
|
|
219
|
-
files?:
|
|
220
|
-
id: string;
|
|
221
|
-
name: string;
|
|
222
|
-
mimetype: string;
|
|
223
|
-
filetype: string;
|
|
224
|
-
size: number;
|
|
225
|
-
url_private_download: string;
|
|
226
|
-
localPath?: string;
|
|
227
|
-
}>;
|
|
254
|
+
files?: FileInfo[];
|
|
228
255
|
}> = [];
|
|
229
256
|
|
|
230
257
|
for (const m of rawMessages) {
|
|
@@ -243,22 +270,12 @@ export const registerSlackReadTool = (server: McpServer) => {
|
|
|
243
270
|
}
|
|
244
271
|
|
|
245
272
|
// Extract file information if includeFiles is true
|
|
246
|
-
let files:
|
|
247
|
-
| Array<{
|
|
248
|
-
id: string;
|
|
249
|
-
name: string;
|
|
250
|
-
mimetype: string;
|
|
251
|
-
filetype: string;
|
|
252
|
-
size: number;
|
|
253
|
-
url_private_download: string;
|
|
254
|
-
localPath?: string;
|
|
255
|
-
}>
|
|
256
|
-
| undefined;
|
|
273
|
+
let files: FileInfo[] | undefined;
|
|
257
274
|
|
|
258
275
|
if (includeFiles && m.files && m.files.length > 0) {
|
|
259
276
|
files = [];
|
|
260
277
|
for (const f of m.files) {
|
|
261
|
-
const fileInfo:
|
|
278
|
+
const fileInfo: FileInfo = {
|
|
262
279
|
id: f.id,
|
|
263
280
|
name: f.name,
|
|
264
281
|
mimetype: f.mimetype,
|
|
@@ -267,8 +284,10 @@ export const registerSlackReadTool = (server: McpServer) => {
|
|
|
267
284
|
url_private_download: f.url_private_download,
|
|
268
285
|
};
|
|
269
286
|
|
|
270
|
-
|
|
271
|
-
|
|
287
|
+
if (attachTo) {
|
|
288
|
+
toAttach.push({ raw: f, info: fileInfo });
|
|
289
|
+
} else if (token && f.url_private_download) {
|
|
290
|
+
// Auto-download file if token is available
|
|
272
291
|
const savePath = `${AUTO_DOWNLOAD_DIR}/${f.id}_${f.name}`;
|
|
273
292
|
try {
|
|
274
293
|
const downloadResult = await downloadFile({
|
|
@@ -298,6 +317,24 @@ export const registerSlackReadTool = (server: McpServer) => {
|
|
|
298
317
|
});
|
|
299
318
|
}
|
|
300
319
|
|
|
320
|
+
if (attachTo && toAttach.length > 0) {
|
|
321
|
+
const outcomes = await attachSlackFilesForAgent(
|
|
322
|
+
client,
|
|
323
|
+
attachTo,
|
|
324
|
+
toAttach.map(({ raw }) => raw as SlackFile),
|
|
325
|
+
agent.id,
|
|
326
|
+
);
|
|
327
|
+
for (const { info } of toAttach) {
|
|
328
|
+
const outcome = outcomes.get(info.id);
|
|
329
|
+
if (outcome && "attachment" in outcome) {
|
|
330
|
+
info.attachmentId = outcome.attachment.id;
|
|
331
|
+
info.fetchCommand = outcome.fetchCommand;
|
|
332
|
+
} else {
|
|
333
|
+
info.notAttached = outcome?.reason ?? "not downloaded";
|
|
334
|
+
}
|
|
335
|
+
}
|
|
336
|
+
}
|
|
337
|
+
|
|
301
338
|
// Format for text output
|
|
302
339
|
const textOutput = messages
|
|
303
340
|
.map((m) => {
|
|
@@ -306,8 +343,12 @@ export const registerSlackReadTool = (server: McpServer) => {
|
|
|
306
343
|
const fileList = m.files
|
|
307
344
|
.map((f) => {
|
|
308
345
|
let line = ` - ${f.name} (${f.mimetype}, ${Math.round(f.size / 1024)} KB)`;
|
|
309
|
-
if (f.
|
|
310
|
-
line += ` [
|
|
346
|
+
if (f.fetchCommand) {
|
|
347
|
+
line += ` [Task attachment ${f.attachmentId}: ${f.fetchCommand}]`;
|
|
348
|
+
} else if (f.notAttached) {
|
|
349
|
+
line += ` [Not attached: ${f.notAttached}]`;
|
|
350
|
+
} else if (f.localPath) {
|
|
351
|
+
line += ` [Downloaded on the API server: ${f.localPath}]`;
|
|
311
352
|
}
|
|
312
353
|
return line;
|
|
313
354
|
})
|
|
@@ -7,25 +7,20 @@ import {
|
|
|
7
7
|
getAgentById,
|
|
8
8
|
getDbClient,
|
|
9
9
|
getResolvedConfig,
|
|
10
|
-
getSessionLogsByTaskId,
|
|
11
10
|
getTaskById,
|
|
12
11
|
insertTaskAttachment,
|
|
13
12
|
updateAgentStatusFromCapacity,
|
|
14
13
|
updateTaskProgress,
|
|
15
14
|
} from "@/be/db";
|
|
16
|
-
import { getEmbeddingProvider, getMemoryStore } from "@/be/memory";
|
|
17
|
-
import { getRetrievalsForTask } from "@/be/memory/raters/retrieval";
|
|
18
|
-
import { runServerRaters } from "@/be/memory/raters/run-server-raters";
|
|
19
15
|
import { AgentFsProvider } from "@/fs/agent-fs-provider";
|
|
20
|
-
import {
|
|
16
|
+
import { runTaskTerminalEffects } from "@/tasks/task-terminal-effects";
|
|
21
17
|
import {
|
|
22
18
|
getTaskOutputValidationError,
|
|
23
19
|
guardTerminalTaskResultWrite,
|
|
24
20
|
} from "@/tasks/terminal-result-guard";
|
|
25
|
-
import { createWorkerTaskFollowUp } from "@/tasks/worker-follow-up";
|
|
26
21
|
import { createToolRegistrar, swarmToolOutputSchema, toolErr, toolOk } from "@/tools/utils";
|
|
27
22
|
import { AgentTaskStatusSchema, AttachmentInputSchema, isTerminalTaskStatus } from "@/types";
|
|
28
|
-
import { scrubSecrets } from "
|
|
23
|
+
import { scrubSecrets } from "@/utils/secret-scrubber";
|
|
29
24
|
|
|
30
25
|
// Phase 11: the `cost` / `costData` field was removed from this tool's input
|
|
31
26
|
// schema. Adapters (claude/codex/pi/opencode/devin/claude-managed) are the
|
|
@@ -406,145 +401,20 @@ export const registerStoreProgressTool = (server: McpServer) => {
|
|
|
406
401
|
!("wasNoOp" in result && result.wasNoOp) &&
|
|
407
402
|
!("wasForcedOverwrite" in result && result.wasForcedOverwrite);
|
|
408
403
|
|
|
409
|
-
//
|
|
410
|
-
//
|
|
411
|
-
//
|
|
412
|
-
//
|
|
413
|
-
|
|
414
|
-
|
|
415
|
-
|
|
416
|
-
|
|
417
|
-
|
|
418
|
-
|
|
419
|
-
|
|
420
|
-
|
|
421
|
-
|
|
422
|
-
|
|
423
|
-
|
|
424
|
-
// Skip indexing if there's truly no content
|
|
425
|
-
if (taskContent.length < 30) return;
|
|
426
|
-
|
|
427
|
-
const store = getMemoryStore();
|
|
428
|
-
const provider = getEmbeddingProvider();
|
|
429
|
-
|
|
430
|
-
const memory = await store.store({
|
|
431
|
-
agentId: requestInfo.agentId ?? null,
|
|
432
|
-
content: taskContent,
|
|
433
|
-
name: `Task: ${result.task!.task.slice(0, 80)}`,
|
|
434
|
-
scope: "agent",
|
|
435
|
-
source: "task_completion",
|
|
436
|
-
sourceTaskId: taskId,
|
|
437
|
-
});
|
|
438
|
-
const embedding = await provider.embed(taskContent);
|
|
439
|
-
if (embedding) {
|
|
440
|
-
await store.updateEmbedding(memory.id, embedding, provider.name);
|
|
441
|
-
}
|
|
442
|
-
|
|
443
|
-
// Auto-promote high-value completions to swarm memory (P3)
|
|
444
|
-
const shouldShareWithSwarm =
|
|
445
|
-
status === "completed" &&
|
|
446
|
-
(result.task!.taskType === "research" ||
|
|
447
|
-
result.task!.tags?.includes("knowledge") ||
|
|
448
|
-
result.task!.tags?.includes("shared"));
|
|
449
|
-
|
|
450
|
-
if (shouldShareWithSwarm) {
|
|
451
|
-
try {
|
|
452
|
-
const swarmMemory = await store.store({
|
|
453
|
-
agentId: requestInfo.agentId ?? null,
|
|
454
|
-
scope: "swarm",
|
|
455
|
-
name: `Shared: ${result.task!.task.slice(0, 80)}`,
|
|
456
|
-
content: `Task completed by agent ${requestInfo.agentId}:\n\n${taskContent}`,
|
|
457
|
-
source: "task_completion",
|
|
458
|
-
sourceTaskId: taskId,
|
|
459
|
-
});
|
|
460
|
-
const swarmEmbedding = await provider.embed(taskContent);
|
|
461
|
-
if (swarmEmbedding) {
|
|
462
|
-
await store.updateEmbedding(swarmMemory.id, swarmEmbedding, provider.name);
|
|
463
|
-
}
|
|
464
|
-
} catch {
|
|
465
|
-
// Non-blocking — swarm memory promotion failure is not critical
|
|
466
|
-
}
|
|
467
|
-
}
|
|
468
|
-
} catch {
|
|
469
|
-
// Non-blocking — task completion memory failure should not affect task status
|
|
470
|
-
}
|
|
471
|
-
})().catch((err) =>
|
|
472
|
-
console.error(
|
|
473
|
-
"[store-progress] task completion memory write failed:",
|
|
474
|
-
scrubSecrets(err instanceof Error ? err.message : String(err)),
|
|
475
|
-
),
|
|
476
|
-
);
|
|
477
|
-
}
|
|
478
|
-
|
|
479
|
-
if (shouldRunTerminalSideEffects) {
|
|
480
|
-
// Memory rater v1.5 — fire server-side raters on task completion.
|
|
481
|
-
// Plan: thoughts/taras/plans/2026-05-05-memory-rater-v1.5/step-2.md §5
|
|
482
|
-
//
|
|
483
|
-
// Read `memory_retrieval` rows for this task + concatenated session_logs
|
|
484
|
-
// and hand both to `runServerRaters`, which iterates the allow-listed
|
|
485
|
-
// server raters (currently just `implicit-citation`), stamps source,
|
|
486
|
-
// applies the configured weight multiplier, and persists via
|
|
487
|
-
// `applyRating`. The orchestration is extracted so it can be unit-tested
|
|
488
|
-
// with stub raters (see `src/tests/run-server-raters.test.ts`).
|
|
489
|
-
//
|
|
490
|
-
// Fire-and-forget: rater failure must NEVER affect task status.
|
|
491
|
-
(async () => {
|
|
492
|
-
try {
|
|
493
|
-
const retrievals = await getRetrievalsForTask(taskId);
|
|
494
|
-
if (retrievals.length === 0) return;
|
|
495
|
-
|
|
496
|
-
const retrievedMemoryIds = retrievals.map((r) => r.memoryId);
|
|
497
|
-
const logs = await getSessionLogsByTaskId(taskId);
|
|
498
|
-
const evidence = logs.map((l) => l.content).join("\n");
|
|
499
|
-
|
|
500
|
-
await runServerRaters({
|
|
501
|
-
taskId,
|
|
502
|
-
agentId: requestInfo.agentId ?? "",
|
|
503
|
-
retrievedMemoryIds,
|
|
504
|
-
evidence,
|
|
505
|
-
});
|
|
506
|
-
} catch (err) {
|
|
507
|
-
console.error(
|
|
508
|
-
"[store-progress] server-rater fire failed:",
|
|
509
|
-
err instanceof Error ? err.message : String(err),
|
|
510
|
-
);
|
|
511
|
-
}
|
|
512
|
-
})().catch((err) =>
|
|
513
|
-
console.error(
|
|
514
|
-
"[store-progress] server rater run failed:",
|
|
515
|
-
scrubSecrets(err instanceof Error ? err.message : String(err)),
|
|
516
|
-
),
|
|
517
|
-
);
|
|
518
|
-
}
|
|
519
|
-
|
|
520
|
-
// Create follow-up task for the lead when a worker task finishes.
|
|
521
|
-
// This replaces the old poll-based tasks_finished trigger which was unreliable.
|
|
522
|
-
// Skip for workflow-managed tasks — the workflow engine handles sequencing via resume.ts.
|
|
523
|
-
// Skip on no-op (idempotent re-call on terminal task) to avoid duplicate follow-ups.
|
|
524
|
-
if (
|
|
525
|
-
status &&
|
|
526
|
-
result.success &&
|
|
527
|
-
result.task &&
|
|
528
|
-
!result.task.workflowRunId &&
|
|
529
|
-
!("wasNoOp" in result && result.wasNoOp) &&
|
|
530
|
-
!("wasForcedOverwrite" in result && result.wasForcedOverwrite)
|
|
531
|
-
) {
|
|
532
|
-
try {
|
|
533
|
-
const followUp = await createWorkerTaskFollowUp({
|
|
534
|
-
task: result.task,
|
|
535
|
-
status,
|
|
536
|
-
output,
|
|
537
|
-
failureReason,
|
|
538
|
-
});
|
|
539
|
-
if (followUp) {
|
|
540
|
-
console.log(
|
|
541
|
-
`[store-progress] Created follow-up task ${followUp.id.slice(0, 8)} for ${status} task ${taskId.slice(0, 8)}`,
|
|
542
|
-
);
|
|
543
|
-
}
|
|
544
|
-
} catch (err) {
|
|
545
|
-
// Non-blocking — follow-up task creation failure should not affect the store-progress response
|
|
546
|
-
console.warn(`[store-progress] Failed to create follow-up task: ${err}`);
|
|
547
|
-
}
|
|
404
|
+
// Post-commit terminal side effects (completion memory, server raters,
|
|
405
|
+
// lead follow-up). Shared with `defer-task` — see
|
|
406
|
+
// src/tasks/task-terminal-effects.ts. Skipped on no-op (idempotent
|
|
407
|
+
// re-call on a terminal task) and on forced text-only overwrites, so a
|
|
408
|
+
// replay never duplicates memories or follow-up tasks.
|
|
409
|
+
if (shouldRunTerminalSideEffects && status) {
|
|
410
|
+
await runTaskTerminalEffects({
|
|
411
|
+
task: result.task!,
|
|
412
|
+
status,
|
|
413
|
+
output,
|
|
414
|
+
failureReason,
|
|
415
|
+
agentId: requestInfo.agentId,
|
|
416
|
+
persistMemory,
|
|
417
|
+
});
|
|
548
418
|
}
|
|
549
419
|
|
|
550
420
|
const { success, message } = result;
|
package/src/tools/tool-config.ts
CHANGED
|
@@ -22,6 +22,7 @@ export const CORE_TOOLS = new Set([
|
|
|
22
22
|
"task-action", // claim/release/accept/reject
|
|
23
23
|
"send-task", // delegate subtasks
|
|
24
24
|
"get-tasks", // list/filter tasks
|
|
25
|
+
"defer-task", // complete now, wake up later to continue
|
|
25
26
|
|
|
26
27
|
// Memory (used at session start)
|
|
27
28
|
"memory-search", // recall relevant context
|
package/src/tools/utils.ts
CHANGED
|
@@ -229,6 +229,23 @@ const scriptRunNudge = (r: SwarmToolResult): string | undefined => {
|
|
|
229
229
|
return !r.ok && body?.error === "timeout" ? SCRIPT_RUN_TIMEOUT_NUDGE : scriptAuthoringNudge(r);
|
|
230
230
|
};
|
|
231
231
|
|
|
232
|
+
const SLACK_API_DISK_NUDGE =
|
|
233
|
+
"That path is on the API server's disk, not in your container — call this from a task you own (or pass its taskId) to get the file as a task attachment with a fetchCommand.";
|
|
234
|
+
|
|
235
|
+
const slackDownloadFileNudge = (r: SwarmToolResult): string | undefined =>
|
|
236
|
+
r.ok && (r.data as { savedPath?: unknown } | undefined)?.savedPath
|
|
237
|
+
? SLACK_API_DISK_NUDGE
|
|
238
|
+
: undefined;
|
|
239
|
+
|
|
240
|
+
const slackReadNudge = (r: SwarmToolResult): string | undefined => {
|
|
241
|
+
if (!r.ok) return undefined;
|
|
242
|
+
const messages = (r.data as { messages?: Array<{ files?: Array<{ localPath?: unknown }> }> })
|
|
243
|
+
?.messages;
|
|
244
|
+
return messages?.some((m) => m.files?.some((f) => Boolean(f.localPath)))
|
|
245
|
+
? SLACK_API_DISK_NUDGE
|
|
246
|
+
: undefined;
|
|
247
|
+
};
|
|
248
|
+
|
|
232
249
|
const workflowLongScriptTimeoutNudge = (r: SwarmToolResult): string | undefined => {
|
|
233
250
|
if (!r.ok) return undefined;
|
|
234
251
|
const hint = (r.data as { longScriptTimeoutHint?: unknown } | undefined)?.longScriptTimeoutHint;
|
|
@@ -241,6 +258,8 @@ const workflowLongScriptTimeoutNudge = (r: SwarmToolResult): string | undefined
|
|
|
241
258
|
* single sentence; derive only from already-scrubbed result fields.
|
|
242
259
|
*/
|
|
243
260
|
export const NUDGES: Record<string, (result: SwarmToolResult) => string | undefined> = {
|
|
261
|
+
"defer-task": (r) =>
|
|
262
|
+
r.ok ? "Stop working on this task now; the wake-up task will carry your note." : undefined,
|
|
244
263
|
"script-run": scriptRunNudge,
|
|
245
264
|
"script-upsert": scriptAuthoringNudge,
|
|
246
265
|
"launch-script-run": scriptAuthoringNudge,
|
|
@@ -249,6 +268,8 @@ export const NUDGES: Record<string, (result: SwarmToolResult) => string | undefi
|
|
|
249
268
|
"update-workflow": workflowLongScriptTimeoutNudge,
|
|
250
269
|
"patch-workflow": workflowLongScriptTimeoutNudge,
|
|
251
270
|
"patch-workflow-node": workflowLongScriptTimeoutNudge,
|
|
271
|
+
"slack-download-file": slackDownloadFileNudge,
|
|
272
|
+
"slack-read": slackReadNudge,
|
|
252
273
|
"script-search": (r) => {
|
|
253
274
|
if (!r.ok) return undefined;
|
|
254
275
|
// proxyScriptsApi wraps the parsed HTTP body as data = { status, data },
|