@desplega.ai/agent-swarm 1.146.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 +1 -1
- package/dist/{acp-adapter-jnq4612m.js → acp-adapter-9n319wqc.js} +42 -4
- package/dist/{actions-s41xcn3f.js → actions-2vxqvpr9.js} +9 -8
- package/dist/{app-j8bfw3f6.js → app-w066xfy0.js} +6 -5
- package/dist/assistant-kt31dj2h.js +236 -0
- package/dist/{boot-reembed-jxdz46jp.js → boot-reembed-az5rassp.js} +7 -6
- package/dist/{boot-reembed-x1476agm.js → boot-reembed-j3mm3rfz.js} +8 -7
- package/dist/{boot-scrub-logs-rab9h5bt.js → boot-scrub-logs-b6h54817.js} +4 -4
- package/dist/{claude-adapter-ep6qekbm.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-0wp3qrcy.js → claude-sdk-session-f0cc1kec.js} +8 -6
- package/dist/{cli-bafd6xnf.js → cli-0b6y7kxm.js} +1 -1
- package/dist/{cli-jyy3e193.js → cli-12fz972k.js} +4 -4
- package/dist/{cli-pzfc7j7g.js → cli-1th1728g.js} +2 -2
- package/dist/{cli-68h2mfrn.js → cli-3tgwnf43.js} +3 -3
- package/dist/{cli-q3qr220d.js → cli-4nr988h6.js} +1 -1
- package/dist/{cli-m4xsy03s.js → cli-53s590z8.js} +1 -1
- package/dist/{cli-c8q7292f.js → cli-54wwve05.js} +5 -4
- package/dist/{cli-rymrjneb.js → cli-719k9j2c.js} +2 -2
- package/dist/{cli-zn65x0w8.js → cli-89xfd7f9.js} +21 -47
- package/dist/{cli-506z6jqa.js → cli-8v0g1yc8.js} +1 -1
- package/dist/cli-9d8ntetj.js +42 -0
- package/dist/{cli-dhg6j8h3.js → cli-atcve9y8.js} +826 -737
- package/dist/{cli-46yck6nj.js → cli-b5bq9crk.js} +14 -31
- package/dist/cli-d8brzxjb.js +190 -0
- package/dist/{cli-a960nkfb.js → cli-de14znh8.js} +2 -2
- package/dist/{cli-s8sbe32s.js → cli-dttph1bw.js} +2 -2
- package/dist/cli-ep19k3mb.js +19 -0
- package/dist/{cli-d9pnxgth.js → cli-f146mn5s.js} +7 -50
- package/dist/{cli-qar1f940.js → cli-f7dsy61y.js} +5 -3
- package/dist/{cli-d4xqde2b.js → cli-ftht3zzj.js} +2780 -2587
- package/dist/{cli-pmwbdw36.js → cli-hbv0kq6w.js} +2 -2
- package/dist/{cli-fhsz3twr.js → cli-jsp14b6c.js} +171 -1
- package/dist/{cli-2zvtzevh.js → cli-kpv03zhj.js} +13 -11
- package/dist/{cli-a9bn9svs.js → cli-m8yrsg97.js} +2 -2
- package/dist/cli-m9sxbkhm.js +779 -0
- package/dist/cli-mmemxxdc.js +823 -0
- package/dist/{cli-6xwhtc64.js → cli-n8508nre.js} +1 -1
- package/dist/{cli-q0qq1sxw.js → cli-p1d5b073.js} +37 -2
- package/dist/{cli-yqeb27t1.js → cli-rw51bq3j.js} +13 -10
- package/dist/{cli-zww3vcnm.js → cli-smby5m17.js} +7 -6
- package/dist/{cli-ah5hjk49.js → cli-tzvk9haz.js} +94 -372
- package/dist/{cli-yxwe913z.js → cli-w9kb3bk1.js} +1 -1
- package/dist/{cli-skd674mt.js → cli-y6b6fb76.js} +25 -3
- package/dist/{cli-3k2g3kg2.js → cli-yz3djrm0.js} +9 -9
- package/dist/{cli-4secrq32.js → cli-zhjmqxzh.js} +1 -1
- package/dist/cli.js +13 -13
- package/dist/{codex-adapter-d6qajxj3.js → codex-adapter-zw4dwsx7.js} +2 -2
- package/dist/{codex-hook-n7m5k7cr.js → codex-hook-bw8p5nh4.js} +2 -2
- package/dist/{codex-session-runner-v9krqe4x.js → codex-session-runner-k981s65v.js} +2 -2
- package/dist/{commands-923wsas7.js → commands-sykrxx7e.js} +4 -4
- package/dist/{db-m1hy4zh5.js → db-sfbsc8wt.js} +6 -4
- package/dist/handlers-0aspcejm.js +603 -0
- package/dist/{hook-ee4hff56.js → hook-4nvqj2sb.js} +7 -5
- package/dist/{hook-bxe3bkdy.js → hook-hxccs7m5.js} +24 -49
- package/dist/{http-a8rx91me.js → http-y06z517d.js} +223 -92
- package/dist/{index-697zfzv1.js → index-hssww7kj.js} +15 -14
- package/dist/{index-3x9g3nbg.js → index-k8znfx0z.js} +13 -12
- package/dist/{index-1j9va48r.js → index-tce1rss4.js} +371 -110
- package/dist/{index-jj4q3x59.js → index-x5j894zf.js} +14 -13
- package/dist/{keepalive-sds4e4ce.js → keepalive-w733ax66.js} +6 -6
- package/dist/{lead-ggq6szk8.js → lead-75bjqa9d.js} +29 -27
- package/dist/{maintenance-093p41d4.js → maintenance-w4f1zjck.js} +8 -7
- package/dist/{oauth-refresh-sweep-dvzq4ckj.js → oauth-refresh-sweep-wdetky26.js} +5 -5
- package/dist/{onboard-ydr9tzw5.js → onboard-bb5net8s.js} +2 -2
- package/dist/{otel-impl-zx3tdf3j.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-7e6pqnzp.js → pricing-refresh-9nzsrgcq.js} +6 -6
- package/dist/{rbac-roles-p2rh6d0r.js → rbac-roles-r3hqjp09.js} +5 -5
- package/dist/{rbac-roles-0dv1wt0w.js → rbac-roles-z1jrffbt.js} +4 -4
- package/dist/{registry-9kdck5ye.js → registry-v5wytg0s.js} +1 -2
- package/dist/{render-v2-4k3jt4r2.js → render-v2-vbr89fnb.js} +6 -5
- package/dist/{seed-pricing-2cc1qmcd.js → seed-pricing-g62hy1pk.js} +7 -5
- package/dist/{setup-c5yss30k.js → setup-gnfqnptp.js} +2 -2
- package/dist/{worker-7srgeqcr.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 +583 -3428
- 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 +1 -1
- 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/mcp.ts +32 -0
- package/src/http/pages-public.ts +20 -4
- 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/prompts/session-templates.ts +4 -2
- package/src/providers/acp-adapter.ts +42 -6
- package/src/providers/pi-mono-adapter.ts +23 -6
- 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 +327 -192
- 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/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/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-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-dx3ew4d8.js +0 -212
- package/dist/cli-00kyrp1a.js +0 -317
- package/dist/cli-n1q8q14d.js +0 -177
- package/dist/cli-ss6vybmt.js +0 -269
- package/dist/handlers-e9vp2fr8.js +0 -592
- package/plugin/commands/work-on-task.md +0 -39
- package/plugin/pi-skills/work-on-task/SKILL.md +0 -39
|
@@ -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 },
|
package/src/types.ts
CHANGED
|
@@ -1145,8 +1145,8 @@ export type AgentLatestModel = z.infer<typeof AgentLatestModelSchema>;
|
|
|
1145
1145
|
|
|
1146
1146
|
/**
|
|
1147
1147
|
* Worker-reported Bedrock enumeration block. Only present when the pi harness
|
|
1148
|
-
* is in Bedrock
|
|
1149
|
-
* `MODEL_OVERRIDE=amazon-bedrock/*`). Rides inside `cred_status` JSON (no new
|
|
1148
|
+
* is in a Bedrock mode (`BEDROCK_AUTH_MODE=sdk`, `BEDROCK_AUTH_MODE=bearer`,
|
|
1149
|
+
* or `MODEL_OVERRIDE=amazon-bedrock/*`). Rides inside `cred_status` JSON (no new
|
|
1150
1150
|
* DB column). `models` is the intersection of the models invocable by this
|
|
1151
1151
|
* account/region (on-demand/ACTIVE foundation models ∪ inference profiles) with
|
|
1152
1152
|
* the set the pi-ai Converse harness can actually drive — Converse-incompatible
|
|
@@ -1287,6 +1287,20 @@ export type VersionMeta = {
|
|
|
1287
1287
|
changeReason?: string | null;
|
|
1288
1288
|
};
|
|
1289
1289
|
|
|
1290
|
+
/**
|
|
1291
|
+
* Compare-and-set token for a profile update, per field: the content hash the
|
|
1292
|
+
* writer based its edit on (e.g. what a session materialized from the DB). A
|
|
1293
|
+
* field whose current DB hash differs is dropped: the DB moved since the writer
|
|
1294
|
+
* read it, so its copy is stale, not an edit.
|
|
1295
|
+
*/
|
|
1296
|
+
export type ProfileExpectedHashes = Partial<Record<VersionableField, string>>;
|
|
1297
|
+
|
|
1298
|
+
export type ProfileSyncConflict = {
|
|
1299
|
+
field: VersionableField;
|
|
1300
|
+
expectedHash: string;
|
|
1301
|
+
currentHash: string;
|
|
1302
|
+
};
|
|
1303
|
+
|
|
1290
1304
|
// Channel Types
|
|
1291
1305
|
export const ChannelTypeSchema = z.enum(["public", "dm"]);
|
|
1292
1306
|
|
|
@@ -1534,6 +1548,7 @@ export const EventNameSchema = z.enum([
|
|
|
1534
1548
|
"system.error",
|
|
1535
1549
|
"system.profile_sync_rejected",
|
|
1536
1550
|
"system.profile_sync_reconciled",
|
|
1551
|
+
"system.profile_sync_conflict",
|
|
1537
1552
|
// Script catalog events
|
|
1538
1553
|
"script.global_upsert",
|
|
1539
1554
|
// Schedule events
|
|
@@ -1599,6 +1614,9 @@ export const ScheduledTaskSchema = z
|
|
|
1599
1614
|
lastRunAt: z.iso.datetime().optional(),
|
|
1600
1615
|
nextRunAt: z.iso.datetime().optional(),
|
|
1601
1616
|
createdByAgentId: z.string().optional(),
|
|
1617
|
+
// Set only by `defer-task`: the task this schedule wakes up to continue.
|
|
1618
|
+
// Passed through as the created task's `parentTaskId`.
|
|
1619
|
+
parentTaskId: z.string().optional(),
|
|
1602
1620
|
timezone: z.string().default("UTC"),
|
|
1603
1621
|
consecutiveErrors: z.number().int().min(0).default(0),
|
|
1604
1622
|
lastErrorAt: z.iso.datetime().optional(),
|
|
@@ -1692,6 +1710,21 @@ export const RepoGuidelinesSchema = z
|
|
|
1692
1710
|
|
|
1693
1711
|
export type RepoGuidelines = z.infer<typeof RepoGuidelinesSchema>;
|
|
1694
1712
|
|
|
1713
|
+
/** Upper bound per guideline list. The repository prompt section renders every
|
|
1714
|
+
* entry outside the bootstrap budget, so the cap lives at the write boundary. */
|
|
1715
|
+
export const REPO_GUIDELINES_MAX_ENTRIES = 50;
|
|
1716
|
+
|
|
1717
|
+
/** Write-side guidelines schema: same shape, bounded lists. Reads keep the
|
|
1718
|
+
* unbounded `RepoGuidelinesSchema` so an older row never fails a response. */
|
|
1719
|
+
export const RepoGuidelinesInputSchema = z
|
|
1720
|
+
.object({
|
|
1721
|
+
prChecks: z.array(z.string()).max(REPO_GUIDELINES_MAX_ENTRIES),
|
|
1722
|
+
mergeChecks: z.array(z.string()).max(REPO_GUIDELINES_MAX_ENTRIES),
|
|
1723
|
+
allowMerge: z.boolean().optional().default(false),
|
|
1724
|
+
review: z.array(z.string()).max(REPO_GUIDELINES_MAX_ENTRIES),
|
|
1725
|
+
})
|
|
1726
|
+
.openapi("RepoGuidelinesInput");
|
|
1727
|
+
|
|
1695
1728
|
export const RepoHooksSchema = z
|
|
1696
1729
|
.object({
|
|
1697
1730
|
enabled: z.boolean().default(false),
|
|
@@ -2212,10 +2245,10 @@ export type WorkflowVersion = z.infer<typeof WorkflowVersionSchema>;
|
|
|
2212
2245
|
// Pages — DB-backed lightweight artifacts (HTML or JSON spec) stored in
|
|
2213
2246
|
// SQLite and served at /p/:id. See plan: thoughts/taras/plans/2026-05-12-db-backed-pages/.
|
|
2214
2247
|
// PageContentTypeSchema + PageAuthModeSchema MUST stay in sync with the SQL
|
|
2215
|
-
// CHECK constraints in src/be/migrations/
|
|
2248
|
+
// CHECK constraints in src/be/migrations/148_pages_svg.sql.
|
|
2216
2249
|
// ---------------------------------------------------------------------------
|
|
2217
2250
|
|
|
2218
|
-
export const PageContentTypeSchema = z.enum(["text/html", "application/json"]);
|
|
2251
|
+
export const PageContentTypeSchema = z.enum(["text/html", "application/json", "image/svg+xml"]);
|
|
2219
2252
|
export type PageContentType = z.infer<typeof PageContentTypeSchema>;
|
|
2220
2253
|
|
|
2221
2254
|
export const PageAuthModeSchema = z.enum(["public", "authed", "password"]);
|