@desplega.ai/agent-swarm 1.108.0 → 1.108.2
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/dist/{actions-sdr91cfp.js → actions-1hxdkcp6.js} +6 -6
- package/dist/{app-0mcf7mwz.js → app-vn2sy15m.js} +3 -3
- package/dist/{assistant-7f3cmabp.js → assistant-sj71nx88.js} +9 -9
- package/dist/{boot-reembed-a9btk2eh.js → boot-reembed-nc1dpmdj.js} +4 -4
- package/dist/{boot-reembed-47cfre4w.js → boot-reembed-nw9vp9ez.js} +3 -3
- package/dist/{boot-scrub-logs-hy8pqhej.js → boot-scrub-logs-g2mpp0cg.js} +2 -2
- package/dist/{cli-j9eqkarv.js → cli-1brke24m.js} +3 -3
- package/dist/{cli-vemx13wc.js → cli-1wzrmxaj.js} +3 -3
- package/dist/{cli-h2qhdwhr.js → cli-27pxayz8.js} +1 -1
- package/dist/{cli-a0w6tqyc.js → cli-3p0e74xv.js} +1 -1
- package/dist/{cli-xy83cmfc.js → cli-4wrdrmr3.js} +33 -6
- package/dist/{cli-vhpa5j27.js → cli-5m4da9px.js} +1 -1
- package/dist/{cli-9wv4e7qs.js → cli-6jf8xfnt.js} +7 -3
- package/dist/{cli-1zs2dm5w.js → cli-6z0y1339.js} +4 -4
- package/dist/{cli-mkn2ryba.js → cli-8gtrpqcw.js} +1 -1
- package/dist/{cli-7r40bgvr.js → cli-8kssf4wv.js} +4 -4
- package/dist/{cli-nmt32dxp.js → cli-9zz27c9g.js} +1 -1
- package/dist/{cli-m0k99m2s.js → cli-ae9hy8yh.js} +1 -1
- package/dist/{cli-entbgsd5.js → cli-b4hwyzev.js} +1 -1
- package/dist/{cli-3cfvx630.js → cli-b7ytq2z8.js} +2 -2
- package/dist/{cli-a8gkr14c.js → cli-bz6qha8p.js} +30 -4
- package/dist/{cli-tehpf2fm.js → cli-cr2wze6h.js} +1 -1
- package/dist/{cli-psz2xq0n.js → cli-er5kffrx.js} +5 -5
- package/dist/{cli-mmaz2btq.js → cli-fxk0as0h.js} +1 -1
- package/dist/{cli-kppbkzj4.js → cli-gg6zxrx1.js} +5 -5
- package/dist/{cli-cya9knf9.js → cli-hc7hxy2m.js} +2 -2
- package/dist/{cli-75b0wmer.js → cli-nt60dmfm.js} +2 -2
- package/dist/{cli-apdrt1ch.js → cli-tc19ec02.js} +1 -1
- package/dist/{cli-gx7gnvha.js → cli-xekgq1r8.js} +17 -17
- package/dist/cli.js +8 -8
- package/dist/{commands-4p93q1rf.js → commands-11d7nc0g.js} +2 -2
- package/dist/{db-hb0chrmg.js → db-0r0wz8a4.js} +2 -2
- package/dist/{handlers-pz3zc83m.js → handlers-v8th05yh.js} +9 -9
- package/dist/{hook-mye8ket2.js → hook-9n0np62b.js} +1 -1
- package/dist/{http-3nrx1tp1.js → http-pmaqk60c.js} +73 -37
- package/dist/{index-x9n06ze1.js → index-35x6ptdt.js} +8 -8
- package/dist/{index-17n7z8k8.js → index-9vma4xmv.js} +9 -9
- package/dist/{index-6sgpkhby.js → index-c9tx34ke.js} +6 -6
- package/dist/{index-rys8ctm6.js → index-vgvvr6yk.js} +7 -7
- package/dist/{keepalive-b6eax0y0.js → keepalive-pjd1kjv7.js} +4 -4
- package/dist/{lead-v1g4j7he.js → lead-czd1xgxr.js} +17 -17
- package/dist/{maintenance-xwf1vf0v.js → maintenance-xjqt3ne7.js} +4 -4
- package/dist/{onboard-vy0z8b11.js → onboard-whf6w0mr.js} +2 -2
- package/dist/{otel-impl-94sm94jm.js → otel-impl-9n4p19tt.js} +1 -1
- package/dist/{pricing-refresh-an9bxjcd.js → pricing-refresh-wg80me14.js} +4 -4
- package/dist/{seed-pricing-3t22x5ax.js → seed-pricing-garwba27.js} +3 -3
- package/dist/{setup-8hd3jk8s.js → setup-dktff2q2.js} +2 -2
- package/dist/{worker-s0xm8naj.js → worker-t26gwn0n.js} +17 -17
- package/openapi.json +1 -1
- package/package.json +1 -1
- package/plugin/skills/download-task-attachment/SKILL.md +59 -0
- package/src/be/seed/agent-fs-provision.ts +51 -4
- package/src/commands/runner.ts +40 -0
- package/src/commands/templates.ts +6 -1
- package/src/fs/local-fs-provider.ts +36 -4
- package/src/http/poll.ts +24 -2
- package/src/telemetry.ts +12 -2
- package/src/tests/agent-fs-provision-seeder.test.ts +204 -3
- package/src/tests/claude-adapter.test.ts +17 -3
- package/src/tests/fs-provider.test.ts +34 -0
- package/src/tests/http-api-integration.test.ts +51 -0
- package/src/tests/prompt-template-remaining.test.ts +25 -0
- package/src/tests/runner-attachments-section.test.ts +55 -0
- package/src/tests/telemetry-init.test.ts +23 -0
|
@@ -3,14 +3,14 @@ import {
|
|
|
3
3
|
extractArgsJsonSchema,
|
|
4
4
|
extractScriptSignature,
|
|
5
5
|
typecheckScript
|
|
6
|
-
} from "./cli-
|
|
6
|
+
} from "./cli-4wrdrmr3.js";
|
|
7
7
|
import {
|
|
8
8
|
getScript,
|
|
9
9
|
upsertScriptByName,
|
|
10
10
|
validateScriptImports
|
|
11
|
-
} from "./cli-
|
|
12
|
-
import"./cli-
|
|
13
|
-
import"./cli-
|
|
11
|
+
} from "./cli-b7ytq2z8.js";
|
|
12
|
+
import"./cli-hc7hxy2m.js";
|
|
13
|
+
import"./cli-5m4da9px.js";
|
|
14
14
|
import"./cli-q25ejbyn.js";
|
|
15
15
|
import {
|
|
16
16
|
computeContentHash,
|
|
@@ -19,11 +19,11 @@ import {
|
|
|
19
19
|
getSkillByName,
|
|
20
20
|
init_db,
|
|
21
21
|
updateSkill
|
|
22
|
-
} from "./cli-
|
|
22
|
+
} from "./cli-6jf8xfnt.js";
|
|
23
23
|
import"./cli-z2zcxes1.js";
|
|
24
24
|
import"./cli-q21d49ac.js";
|
|
25
25
|
import"./cli-anrj584m.js";
|
|
26
|
-
import"./cli-
|
|
26
|
+
import"./cli-tc19ec02.js";
|
|
27
27
|
import"./cli-4j2mrzag.js";
|
|
28
28
|
import"./cli-p9swy5t3.js";
|
|
29
29
|
|
|
@@ -20,21 +20,21 @@ import {
|
|
|
20
20
|
stopWaitPoller,
|
|
21
21
|
subscribeWaitToBus,
|
|
22
22
|
validateTemplateVariables
|
|
23
|
-
} from "./cli-
|
|
23
|
+
} from "./cli-er5kffrx.js";
|
|
24
24
|
import"./cli-f14fvzag.js";
|
|
25
|
-
import"./cli-
|
|
26
|
-
import"./cli-
|
|
27
|
-
import"./cli-
|
|
25
|
+
import"./cli-b7ytq2z8.js";
|
|
26
|
+
import"./cli-hc7hxy2m.js";
|
|
27
|
+
import"./cli-5m4da9px.js";
|
|
28
28
|
import"./cli-q25ejbyn.js";
|
|
29
|
-
import"./cli-
|
|
29
|
+
import"./cli-3p0e74xv.js";
|
|
30
30
|
import"./cli-dh55d5fg.js";
|
|
31
31
|
import {
|
|
32
32
|
interpolate
|
|
33
|
-
} from "./cli-
|
|
33
|
+
} from "./cli-6jf8xfnt.js";
|
|
34
34
|
import"./cli-z2zcxes1.js";
|
|
35
35
|
import"./cli-q21d49ac.js";
|
|
36
36
|
import"./cli-anrj584m.js";
|
|
37
|
-
import"./cli-
|
|
37
|
+
import"./cli-tc19ec02.js";
|
|
38
38
|
import"./cli-4j2mrzag.js";
|
|
39
39
|
import {
|
|
40
40
|
workflowEventBus
|
|
@@ -1,11 +1,11 @@
|
|
|
1
1
|
import {
|
|
2
2
|
ensureTokenOrThrow
|
|
3
|
-
} from "./cli-
|
|
4
|
-
import"./cli-
|
|
3
|
+
} from "./cli-cr2wze6h.js";
|
|
4
|
+
import"./cli-6jf8xfnt.js";
|
|
5
5
|
import"./cli-z2zcxes1.js";
|
|
6
6
|
import"./cli-q21d49ac.js";
|
|
7
7
|
import"./cli-anrj584m.js";
|
|
8
|
-
import"./cli-
|
|
8
|
+
import"./cli-tc19ec02.js";
|
|
9
9
|
import"./cli-4j2mrzag.js";
|
|
10
10
|
import {
|
|
11
11
|
__require
|
|
@@ -54,7 +54,7 @@ async function notifySlack(text) {
|
|
|
54
54
|
return;
|
|
55
55
|
}
|
|
56
56
|
try {
|
|
57
|
-
const { getSlackApp } = await import("./app-
|
|
57
|
+
const { getSlackApp } = await import("./app-vn2sy15m.js");
|
|
58
58
|
const app = getSlackApp();
|
|
59
59
|
if (!app) {
|
|
60
60
|
console.warn("[OAuth Keepalive] Slack not available, cannot send notification");
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import {
|
|
2
2
|
runAgent
|
|
3
|
-
} from "./cli-
|
|
3
|
+
} from "./cli-bz6qha8p.js";
|
|
4
4
|
import"./cli-w6zp4qcx.js";
|
|
5
5
|
import"./cli-m3enfr8q.js";
|
|
6
6
|
import"./cli-amncg1dq.js";
|
|
@@ -15,7 +15,7 @@ import"./cli-d6n0thxw.js";
|
|
|
15
15
|
import"./cli-hx1dcevq.js";
|
|
16
16
|
import {
|
|
17
17
|
getEnabledCapabilities
|
|
18
|
-
} from "./cli-
|
|
18
|
+
} from "./cli-xekgq1r8.js";
|
|
19
19
|
import"./cli-1z7048dy.js";
|
|
20
20
|
import"./cli-dcfyyh3t.js";
|
|
21
21
|
import"./cli-gz9sbyk3.js";
|
|
@@ -25,32 +25,32 @@ import"./cli-tng3131n.js";
|
|
|
25
25
|
import"./cli-zm7njwzd.js";
|
|
26
26
|
import"./cli-tghre7g6.js";
|
|
27
27
|
import"./cli-tkmm5nf9.js";
|
|
28
|
-
import"./cli-
|
|
29
|
-
import"./cli-
|
|
30
|
-
import"./cli-
|
|
31
|
-
import"./cli-
|
|
28
|
+
import"./cli-cr2wze6h.js";
|
|
29
|
+
import"./cli-nt60dmfm.js";
|
|
30
|
+
import"./cli-b4hwyzev.js";
|
|
31
|
+
import"./cli-27pxayz8.js";
|
|
32
32
|
import"./cli-fad8m16k.js";
|
|
33
|
-
import"./cli-
|
|
34
|
-
import"./cli-
|
|
33
|
+
import"./cli-6z0y1339.js";
|
|
34
|
+
import"./cli-er5kffrx.js";
|
|
35
35
|
import"./cli-f14fvzag.js";
|
|
36
|
-
import"./cli-
|
|
37
|
-
import"./cli-
|
|
38
|
-
import"./cli-
|
|
36
|
+
import"./cli-b7ytq2z8.js";
|
|
37
|
+
import"./cli-hc7hxy2m.js";
|
|
38
|
+
import"./cli-5m4da9px.js";
|
|
39
39
|
import"./cli-q25ejbyn.js";
|
|
40
|
-
import"./cli-
|
|
41
|
-
import"./cli-
|
|
40
|
+
import"./cli-9zz27c9g.js";
|
|
41
|
+
import"./cli-gg6zxrx1.js";
|
|
42
42
|
import"./cli-rkndnn90.js";
|
|
43
43
|
import"./cli-b0p7rfnd.js";
|
|
44
|
-
import"./cli-
|
|
44
|
+
import"./cli-fxk0as0h.js";
|
|
45
45
|
import"./cli-6xd0bvf8.js";
|
|
46
46
|
import"./cli-wspgs9bt.js";
|
|
47
|
-
import"./cli-
|
|
47
|
+
import"./cli-3p0e74xv.js";
|
|
48
48
|
import"./cli-dh55d5fg.js";
|
|
49
|
-
import"./cli-
|
|
49
|
+
import"./cli-6jf8xfnt.js";
|
|
50
50
|
import"./cli-z2zcxes1.js";
|
|
51
51
|
import"./cli-q21d49ac.js";
|
|
52
52
|
import"./cli-anrj584m.js";
|
|
53
|
-
import"./cli-
|
|
53
|
+
import"./cli-tc19ec02.js";
|
|
54
54
|
import"./cli-4j2mrzag.js";
|
|
55
55
|
import"./cli-e15jhafb.js";
|
|
56
56
|
import"./cli-9aryf9tv.js";
|
|
@@ -1,13 +1,13 @@
|
|
|
1
1
|
import {
|
|
2
2
|
reembedAllScripts
|
|
3
|
-
} from "./cli-
|
|
4
|
-
import"./cli-
|
|
3
|
+
} from "./cli-hc7hxy2m.js";
|
|
4
|
+
import"./cli-5m4da9px.js";
|
|
5
5
|
import"./cli-q25ejbyn.js";
|
|
6
|
-
import"./cli-
|
|
6
|
+
import"./cli-6jf8xfnt.js";
|
|
7
7
|
import"./cli-z2zcxes1.js";
|
|
8
8
|
import"./cli-q21d49ac.js";
|
|
9
9
|
import"./cli-anrj584m.js";
|
|
10
|
-
import"./cli-
|
|
10
|
+
import"./cli-tc19ec02.js";
|
|
11
11
|
import"./cli-4j2mrzag.js";
|
|
12
12
|
import"./cli-p9swy5t3.js";
|
|
13
13
|
|
|
@@ -3,7 +3,7 @@ import {
|
|
|
3
3
|
createDefaultMcpJson,
|
|
4
4
|
createDefaultSettingsLocal,
|
|
5
5
|
createHooksConfig
|
|
6
|
-
} from "./cli-
|
|
6
|
+
} from "./cli-8gtrpqcw.js";
|
|
7
7
|
import {
|
|
8
8
|
require_jsx_dev_runtime
|
|
9
9
|
} from "./cli-wx82tw1k.js";
|
|
@@ -13,7 +13,7 @@ import {
|
|
|
13
13
|
import {
|
|
14
14
|
init_package,
|
|
15
15
|
package_default
|
|
16
|
-
} from "./cli-
|
|
16
|
+
} from "./cli-tc19ec02.js";
|
|
17
17
|
import {
|
|
18
18
|
Box_default,
|
|
19
19
|
Select,
|
|
@@ -2,13 +2,13 @@ import {
|
|
|
2
2
|
PRICING_REFRESH_INTERVAL_MS,
|
|
3
3
|
refreshPricingFromModelsDev,
|
|
4
4
|
startPricingRefreshLoop
|
|
5
|
-
} from "./cli-
|
|
6
|
-
import"./cli-
|
|
7
|
-
import"./cli-
|
|
5
|
+
} from "./cli-nt60dmfm.js";
|
|
6
|
+
import"./cli-b4hwyzev.js";
|
|
7
|
+
import"./cli-6jf8xfnt.js";
|
|
8
8
|
import"./cli-z2zcxes1.js";
|
|
9
9
|
import"./cli-q21d49ac.js";
|
|
10
10
|
import"./cli-anrj584m.js";
|
|
11
|
-
import"./cli-
|
|
11
|
+
import"./cli-tc19ec02.js";
|
|
12
12
|
import"./cli-4j2mrzag.js";
|
|
13
13
|
import"./cli-p9swy5t3.js";
|
|
14
14
|
export {
|
|
@@ -1,12 +1,12 @@
|
|
|
1
1
|
import {
|
|
2
2
|
buildModelsDevSeedRows,
|
|
3
3
|
seedPricingFromModelsDev
|
|
4
|
-
} from "./cli-
|
|
5
|
-
import"./cli-
|
|
4
|
+
} from "./cli-b4hwyzev.js";
|
|
5
|
+
import"./cli-6jf8xfnt.js";
|
|
6
6
|
import"./cli-z2zcxes1.js";
|
|
7
7
|
import"./cli-q21d49ac.js";
|
|
8
8
|
import"./cli-anrj584m.js";
|
|
9
|
-
import"./cli-
|
|
9
|
+
import"./cli-tc19ec02.js";
|
|
10
10
|
import"./cli-4j2mrzag.js";
|
|
11
11
|
import"./cli-p9swy5t3.js";
|
|
12
12
|
export {
|
|
@@ -4,14 +4,14 @@ import {
|
|
|
4
4
|
createDefaultMcpJson,
|
|
5
5
|
createDefaultSettingsLocal,
|
|
6
6
|
createHooksConfig
|
|
7
|
-
} from "./cli-
|
|
7
|
+
} from "./cli-8gtrpqcw.js";
|
|
8
8
|
import {
|
|
9
9
|
require_jsx_dev_runtime
|
|
10
10
|
} from "./cli-wx82tw1k.js";
|
|
11
11
|
import {
|
|
12
12
|
getApiKey
|
|
13
13
|
} from "./cli-f14fvzag.js";
|
|
14
|
-
import"./cli-
|
|
14
|
+
import"./cli-tc19ec02.js";
|
|
15
15
|
import {
|
|
16
16
|
Box_default,
|
|
17
17
|
Spinner,
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import {
|
|
2
2
|
runAgent
|
|
3
|
-
} from "./cli-
|
|
3
|
+
} from "./cli-bz6qha8p.js";
|
|
4
4
|
import"./cli-w6zp4qcx.js";
|
|
5
5
|
import"./cli-m3enfr8q.js";
|
|
6
6
|
import"./cli-amncg1dq.js";
|
|
@@ -15,7 +15,7 @@ import"./cli-d6n0thxw.js";
|
|
|
15
15
|
import"./cli-hx1dcevq.js";
|
|
16
16
|
import {
|
|
17
17
|
getEnabledCapabilities
|
|
18
|
-
} from "./cli-
|
|
18
|
+
} from "./cli-xekgq1r8.js";
|
|
19
19
|
import"./cli-1z7048dy.js";
|
|
20
20
|
import"./cli-dcfyyh3t.js";
|
|
21
21
|
import"./cli-gz9sbyk3.js";
|
|
@@ -25,32 +25,32 @@ import"./cli-tng3131n.js";
|
|
|
25
25
|
import"./cli-zm7njwzd.js";
|
|
26
26
|
import"./cli-tghre7g6.js";
|
|
27
27
|
import"./cli-tkmm5nf9.js";
|
|
28
|
-
import"./cli-
|
|
29
|
-
import"./cli-
|
|
30
|
-
import"./cli-
|
|
31
|
-
import"./cli-
|
|
28
|
+
import"./cli-cr2wze6h.js";
|
|
29
|
+
import"./cli-nt60dmfm.js";
|
|
30
|
+
import"./cli-b4hwyzev.js";
|
|
31
|
+
import"./cli-27pxayz8.js";
|
|
32
32
|
import"./cli-fad8m16k.js";
|
|
33
|
-
import"./cli-
|
|
34
|
-
import"./cli-
|
|
33
|
+
import"./cli-6z0y1339.js";
|
|
34
|
+
import"./cli-er5kffrx.js";
|
|
35
35
|
import"./cli-f14fvzag.js";
|
|
36
|
-
import"./cli-
|
|
37
|
-
import"./cli-
|
|
38
|
-
import"./cli-
|
|
36
|
+
import"./cli-b7ytq2z8.js";
|
|
37
|
+
import"./cli-hc7hxy2m.js";
|
|
38
|
+
import"./cli-5m4da9px.js";
|
|
39
39
|
import"./cli-q25ejbyn.js";
|
|
40
|
-
import"./cli-
|
|
41
|
-
import"./cli-
|
|
40
|
+
import"./cli-9zz27c9g.js";
|
|
41
|
+
import"./cli-gg6zxrx1.js";
|
|
42
42
|
import"./cli-rkndnn90.js";
|
|
43
43
|
import"./cli-b0p7rfnd.js";
|
|
44
|
-
import"./cli-
|
|
44
|
+
import"./cli-fxk0as0h.js";
|
|
45
45
|
import"./cli-6xd0bvf8.js";
|
|
46
46
|
import"./cli-wspgs9bt.js";
|
|
47
|
-
import"./cli-
|
|
47
|
+
import"./cli-3p0e74xv.js";
|
|
48
48
|
import"./cli-dh55d5fg.js";
|
|
49
|
-
import"./cli-
|
|
49
|
+
import"./cli-6jf8xfnt.js";
|
|
50
50
|
import"./cli-z2zcxes1.js";
|
|
51
51
|
import"./cli-q21d49ac.js";
|
|
52
52
|
import"./cli-anrj584m.js";
|
|
53
|
-
import"./cli-
|
|
53
|
+
import"./cli-tc19ec02.js";
|
|
54
54
|
import"./cli-4j2mrzag.js";
|
|
55
55
|
import"./cli-e15jhafb.js";
|
|
56
56
|
import"./cli-9aryf9tv.js";
|
package/openapi.json
CHANGED
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
"openapi": "3.1.0",
|
|
3
3
|
"info": {
|
|
4
4
|
"title": "Agent Swarm API",
|
|
5
|
-
"version": "1.108.
|
|
5
|
+
"version": "1.108.2",
|
|
6
6
|
"description": "Multi-agent orchestration API for Claude Code, Codex, and Gemini CLI. Enables task distribution, agent communication, and service discovery.\n\nMCP tools are documented separately in [MCP.md](./MCP.md)."
|
|
7
7
|
},
|
|
8
8
|
"servers": [
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@desplega.ai/agent-swarm",
|
|
3
|
-
"version": "1.108.
|
|
3
|
+
"version": "1.108.2",
|
|
4
4
|
"description": "Multi-agent orchestration for Claude Code, Codex, Gemini CLI, and other AI coding assistants",
|
|
5
5
|
"license": "MIT",
|
|
6
6
|
"author": "desplega.sh <contact@desplega.sh>",
|
|
@@ -0,0 +1,59 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: download-task-attachment
|
|
3
|
+
description: Fetch a file attached to your current task in ONE call. Use whenever a task carries an attachment (an image, PDF, or other file the requester uploaded) and you need its bytes on disk — the dispatch prompt lists attachments with a ready-to-run curl command, but if you're improvising (resumed session, follow-up task, or the recipe scrolled out of context) use this skill instead of reaching for the `agent-fs` CLI directly.
|
|
4
|
+
---
|
|
5
|
+
|
|
6
|
+
# Download a Task Attachment
|
|
7
|
+
|
|
8
|
+
Task attachments (files uploaded via the UI or API onto a task) are served
|
|
9
|
+
through a single **provider-agnostic** REST route — regardless of whether the
|
|
10
|
+
backing storage is `local-fs` or `agent-fs`. You do not need to know or care
|
|
11
|
+
which provider is active, and you do not need `agent-fs org`/`agent-fs drive`
|
|
12
|
+
discovery.
|
|
13
|
+
|
|
14
|
+
## The one-call recipe
|
|
15
|
+
|
|
16
|
+
```bash
|
|
17
|
+
curl "$MCP_BASE_URL/api/fs/tasks/$AGENT_SWARM_TASK_ID/files/<attachmentId>/raw" \
|
|
18
|
+
-H "X-Agent-ID: $AGENT_ID" \
|
|
19
|
+
-H "Authorization: Bearer ${AGENT_SWARM_API_KEY:-$API_KEY}" \
|
|
20
|
+
-o /tmp/<name>
|
|
21
|
+
```
|
|
22
|
+
|
|
23
|
+
- `$MCP_BASE_URL`, `$AGENT_ID`, `$AGENT_SWARM_TASK_ID` (or `$AGENT_SWARM_AGENT_ID`/`TASK_FILE`-derived taskId), and `$AGENT_SWARM_API_KEY`/`$API_KEY` are already present in every worker container's env — no new plumbing needed.
|
|
24
|
+
- `<attachmentId>` and `<name>`/`mimeType` come from the task's attachment list (see below).
|
|
25
|
+
- The route resolves the active file-storage provider (`local-fs` in dev, `agent-fs` in prod) server-side and streams the raw bytes back with the correct `Content-Type` — you get one call no matter which provider is behind it.
|
|
26
|
+
|
|
27
|
+
## Finding the attachment ID
|
|
28
|
+
|
|
29
|
+
If the dispatch prompt already injected an `## Attachments` section with the
|
|
30
|
+
curl command pre-filled, just run it — that's the fast path (see
|
|
31
|
+
`buildAttachmentsSection()` in `src/commands/runner.ts`).
|
|
32
|
+
|
|
33
|
+
Otherwise, pull it from the task:
|
|
34
|
+
|
|
35
|
+
```
|
|
36
|
+
get-task-details taskId=<your task id>
|
|
37
|
+
```
|
|
38
|
+
|
|
39
|
+
The response includes an `attachments` array: `{id, name, mimeType, sizeBytes}`.
|
|
40
|
+
Use `id` as `<attachmentId>` in the curl above.
|
|
41
|
+
|
|
42
|
+
## Why not the `agent-fs` CLI directly?
|
|
43
|
+
|
|
44
|
+
`agent-fs` is a general-purpose CLI for the swarm's own agent-fs filesystem —
|
|
45
|
+
it requires you to already know the org/drive a file lives in, and task
|
|
46
|
+
attachments don't always carry that (older rows, or attachments stored via
|
|
47
|
+
`local-fs` in dev have no org/drive at all). Reaching for `agent-fs cat` /
|
|
48
|
+
`agent-fs download` on a task attachment means guessing subcommand names,
|
|
49
|
+
discovering the right org via `agent-fs org list`, and re-trying — several
|
|
50
|
+
tool calls where one `curl` suffices. This was root-caused from a real
|
|
51
|
+
session that burned 7 tool calls (~64s) doing exactly that detour before
|
|
52
|
+
succeeding; see `runbooks/harness-providers.md` and PR that added
|
|
53
|
+
`buildAttachmentsSection()` for the full trace.
|
|
54
|
+
|
|
55
|
+
## Gotchas
|
|
56
|
+
|
|
57
|
+
- The route requires `X-Agent-ID` + bearer auth like any other swarm API call — same headers you'd use for any MCP-adjacent REST call.
|
|
58
|
+
- `mimeType` on the attachment record reflects the real upload `Content-Type` (not a filename-extension guess) — trust it when deciding how to handle the downloaded bytes (image vs PDF vs text).
|
|
59
|
+
- If you get a 404, double-check you're using the **attachment ID** (from `attachments[].id`), not the display `name`.
|
|
@@ -32,6 +32,12 @@ type InviteTarget = {
|
|
|
32
32
|
role: Role;
|
|
33
33
|
};
|
|
34
34
|
|
|
35
|
+
type OrgMember = {
|
|
36
|
+
email?: string | null;
|
|
37
|
+
role?: string | null;
|
|
38
|
+
user?: { email?: string | null } | null;
|
|
39
|
+
};
|
|
40
|
+
|
|
35
41
|
type FetchLike = typeof fetch;
|
|
36
42
|
|
|
37
43
|
// The provision seeder runs synchronously before the HTTP server binds its port.
|
|
@@ -92,8 +98,15 @@ async function provisionAgentFs(item: AgentFsSeedItem): Promise<void> {
|
|
|
92
98
|
apiUrl: item.apiUrl,
|
|
93
99
|
registerEmail: item.registerEmail,
|
|
94
100
|
});
|
|
101
|
+
const currentRoles = await getCurrentOrgMemberRoles(
|
|
102
|
+
item.apiUrl,
|
|
103
|
+
shared.authHeaders,
|
|
104
|
+
shared.orgId,
|
|
105
|
+
);
|
|
95
106
|
|
|
96
107
|
for (const invite of item.invites) {
|
|
108
|
+
const currentRole = currentRoles.get(invite.email.toLowerCase());
|
|
109
|
+
if (currentRole && roleRank(currentRole) >= roleRank(invite.role)) continue;
|
|
97
110
|
await inviteToSharedOrg(item.apiUrl, shared.authHeaders, shared.orgId, invite);
|
|
98
111
|
}
|
|
99
112
|
|
|
@@ -212,7 +225,11 @@ export async function ensureAgentFsCredentialsForAgent(agentId: string): Promise
|
|
|
212
225
|
const apiKey = registered.apiKey ?? "";
|
|
213
226
|
if (!apiKey) throw new Error(`agent-fs registration did not return an apiKey for ${agentId}`);
|
|
214
227
|
|
|
215
|
-
await
|
|
228
|
+
const currentRoles = await getCurrentOrgMemberRoles(apiUrl, shared.authHeaders, shared.orgId);
|
|
229
|
+
const currentRole = currentRoles.get(email.toLowerCase());
|
|
230
|
+
if (!currentRole || roleRank(currentRole) < roleRank("editor")) {
|
|
231
|
+
await inviteToSharedOrg(apiUrl, shared.authHeaders, shared.orgId, { email, role: "editor" });
|
|
232
|
+
}
|
|
216
233
|
|
|
217
234
|
upsertSwarmConfig({
|
|
218
235
|
scope: "agent",
|
|
@@ -309,6 +326,36 @@ async function inviteToSharedOrg(
|
|
|
309
326
|
}
|
|
310
327
|
}
|
|
311
328
|
|
|
329
|
+
async function getCurrentOrgMemberRoles(
|
|
330
|
+
apiUrl: string,
|
|
331
|
+
headers: Record<string, string>,
|
|
332
|
+
orgId: string,
|
|
333
|
+
): Promise<Map<string, Role>> {
|
|
334
|
+
const response = await agentFsRequest<{ members?: OrgMember[] }>(
|
|
335
|
+
apiUrl,
|
|
336
|
+
`/orgs/${encodeURIComponent(orgId)}/members`,
|
|
337
|
+
{ headers },
|
|
338
|
+
);
|
|
339
|
+
const roles = new Map<string, Role>();
|
|
340
|
+
|
|
341
|
+
for (const member of response.members ?? []) {
|
|
342
|
+
const email = (member.email ?? member.user?.email ?? "").trim().toLowerCase();
|
|
343
|
+
const role = normalizeRole(member.role);
|
|
344
|
+
if (email && role) roles.set(email, role);
|
|
345
|
+
}
|
|
346
|
+
|
|
347
|
+
return roles;
|
|
348
|
+
}
|
|
349
|
+
|
|
350
|
+
function normalizeRole(role: string | null | undefined): Role | null {
|
|
351
|
+
if (role === "viewer" || role === "editor" || role === "admin") return role;
|
|
352
|
+
return null;
|
|
353
|
+
}
|
|
354
|
+
|
|
355
|
+
function roleRank(role: Role): number {
|
|
356
|
+
return { viewer: 0, editor: 1, admin: 2 }[role];
|
|
357
|
+
}
|
|
358
|
+
|
|
312
359
|
async function agentFsRequest<T = unknown>(
|
|
313
360
|
apiUrl: string,
|
|
314
361
|
path: string,
|
|
@@ -363,15 +410,15 @@ function resolveInviteTargets(): InviteTarget[] {
|
|
|
363
410
|
|
|
364
411
|
for (const user of getAllUsers()) {
|
|
365
412
|
if (!user.email) continue;
|
|
366
|
-
const role =
|
|
413
|
+
const role = isExplicitViewerRole(user.role) ? "viewer" : "editor";
|
|
367
414
|
targets.set(user.email.toLowerCase(), { email: user.email, role });
|
|
368
415
|
}
|
|
369
416
|
|
|
370
417
|
return [...targets.values()].sort((a, b) => a.email.localeCompare(b.email));
|
|
371
418
|
}
|
|
372
419
|
|
|
373
|
-
function
|
|
374
|
-
return /\b(
|
|
420
|
+
function isExplicitViewerRole(role: string | undefined): boolean {
|
|
421
|
+
return /\b(viewer|read[-\s]?only|guest)\b/i.test(role ?? "");
|
|
375
422
|
}
|
|
376
423
|
|
|
377
424
|
function provisionHashFromCurrentState(): string {
|
package/src/commands/runner.ts
CHANGED
|
@@ -2289,6 +2289,43 @@ async function pollForTriggerOnce(opts: PollOptions): Promise<Trigger | null> {
|
|
|
2289
2289
|
return null; // Timeout reached, no trigger found
|
|
2290
2290
|
}
|
|
2291
2291
|
|
|
2292
|
+
/**
|
|
2293
|
+
* Build a ready-to-run fetch recipe for each task attachment, so the agent
|
|
2294
|
+
* can download the bytes in one call via the provider-agnostic
|
|
2295
|
+
* `/api/fs/tasks/{taskId}/files/{attachmentId}/raw` route — instead of having
|
|
2296
|
+
* to discover the file's storage provider/org/drive itself (e.g. guessing at
|
|
2297
|
+
* the `agent-fs` CLI with no org context). MCP_BASE_URL/API_KEY/AGENT_ID are
|
|
2298
|
+
* already present in every worker container's env.
|
|
2299
|
+
*/
|
|
2300
|
+
export function buildAttachmentsSection(
|
|
2301
|
+
taskId: string | undefined,
|
|
2302
|
+
attachmentsRaw: unknown,
|
|
2303
|
+
): string {
|
|
2304
|
+
if (!taskId || !Array.isArray(attachmentsRaw) || attachmentsRaw.length === 0) return "";
|
|
2305
|
+
|
|
2306
|
+
const lines = attachmentsRaw
|
|
2307
|
+
.filter((a): a is Record<string, unknown> => !!a && typeof a === "object")
|
|
2308
|
+
.map((a) => {
|
|
2309
|
+
const id = typeof a.id === "string" ? a.id : undefined;
|
|
2310
|
+
const name = typeof a.name === "string" ? a.name : id;
|
|
2311
|
+
if (!id || !name) return null;
|
|
2312
|
+
const details = [
|
|
2313
|
+
typeof a.mimeType === "string" ? a.mimeType : null,
|
|
2314
|
+
typeof a.sizeBytes === "number" ? `${a.sizeBytes} bytes` : null,
|
|
2315
|
+
]
|
|
2316
|
+
.filter(Boolean)
|
|
2317
|
+
.join(", ");
|
|
2318
|
+
const url = `$MCP_BASE_URL/api/fs/tasks/${taskId}/files/${id}/raw`;
|
|
2319
|
+
const cmd = `curl -s -H "Authorization: Bearer \${AGENT_SWARM_API_KEY:-$API_KEY}" -H "X-Agent-ID: $AGENT_ID" "${url}" -o /tmp/${name}`;
|
|
2320
|
+
return `- ${name}${details ? ` (${details})` : ""}: \`${cmd}\``;
|
|
2321
|
+
})
|
|
2322
|
+
.filter((line): line is string => line !== null);
|
|
2323
|
+
|
|
2324
|
+
if (lines.length === 0) return "";
|
|
2325
|
+
|
|
2326
|
+
return `\n\n📎 Attachment(s) — fetch directly, no need to discover the storage path yourself:\n${lines.join("\n")}`;
|
|
2327
|
+
}
|
|
2328
|
+
|
|
2292
2329
|
/** Build prompt based on trigger type */
|
|
2293
2330
|
async function buildPromptForTrigger(
|
|
2294
2331
|
trigger: Trigger,
|
|
@@ -2325,10 +2362,13 @@ async function buildPromptForTrigger(
|
|
|
2325
2362
|
? `\n\nRequested by: ${requestedBy.name}${requestedBy.email ? ` (${requestedBy.email})` : ""}`
|
|
2326
2363
|
: "";
|
|
2327
2364
|
|
|
2365
|
+
const attachmentsSection = buildAttachmentsSection(trigger.taskId, taskObj?.attachments);
|
|
2366
|
+
|
|
2328
2367
|
const result = await resolveTemplateAsync("task.trigger.assigned", {
|
|
2329
2368
|
work_on_task_cmd: hasMcp ? fmt("work-on-task") : "",
|
|
2330
2369
|
task_id: hasMcp ? trigger.taskId : "",
|
|
2331
2370
|
task_desc_section: taskDescSection + requestedBySection,
|
|
2371
|
+
attachments_section: attachmentsSection,
|
|
2332
2372
|
output_instructions: outputInstructions,
|
|
2333
2373
|
});
|
|
2334
2374
|
return result.text;
|
|
@@ -17,11 +17,16 @@ import { registerTemplate } from "../prompts/registry";
|
|
|
17
17
|
registerTemplate({
|
|
18
18
|
eventType: "task.trigger.assigned",
|
|
19
19
|
header: "",
|
|
20
|
-
defaultBody: `{{work_on_task_cmd}} {{task_id}}{{task_desc_section}}{{output_instructions}}`,
|
|
20
|
+
defaultBody: `{{work_on_task_cmd}} {{task_id}}{{task_desc_section}}{{attachments_section}}{{output_instructions}}`,
|
|
21
21
|
variables: [
|
|
22
22
|
{ name: "work_on_task_cmd", description: "Formatted /work-on-task command" },
|
|
23
23
|
{ name: "task_id", description: "Task ID" },
|
|
24
24
|
{ name: "task_desc_section", description: "Task description section or empty string" },
|
|
25
|
+
{
|
|
26
|
+
name: "attachments_section",
|
|
27
|
+
description:
|
|
28
|
+
"Ready-to-run fetch recipe per task attachment (or empty string if none) — lets the agent download an attachment in one call instead of discovering the path/provider itself",
|
|
29
|
+
},
|
|
25
30
|
{
|
|
26
31
|
name: "output_instructions",
|
|
27
32
|
description:
|
|
@@ -37,6 +37,12 @@ export class LocalFsProvider implements FileStorageProvider {
|
|
|
37
37
|
}
|
|
38
38
|
|
|
39
39
|
await Bun.write(target, await new Response(body).arrayBuffer());
|
|
40
|
+
// Persist the real upload Content-Type alongside the blob — `Bun.file(target).type`
|
|
41
|
+
// only guesses from the storage path's extension, which is wrong whenever the
|
|
42
|
+
// stored key doesn't carry the original filename's extension (see head()).
|
|
43
|
+
if (options.contentType) {
|
|
44
|
+
await Bun.write(this.metaPath(target), JSON.stringify({ contentType: options.contentType }));
|
|
45
|
+
}
|
|
40
46
|
return this.head(scope);
|
|
41
47
|
}
|
|
42
48
|
|
|
@@ -52,12 +58,16 @@ export class LocalFsProvider implements FileStorageProvider {
|
|
|
52
58
|
const target = this.localPath(scope);
|
|
53
59
|
try {
|
|
54
60
|
const info = await stat(target);
|
|
61
|
+
const storedContentType = await this.readStoredContentType(target);
|
|
55
62
|
return {
|
|
56
63
|
providerId: this.id,
|
|
57
64
|
key: providerPath(scope),
|
|
58
65
|
taskId: scope.taskId,
|
|
59
66
|
name: scope.name,
|
|
60
|
-
|
|
67
|
+
// Prefer the Content-Type recorded at upload time; fall back to
|
|
68
|
+
// extension-based sniffing only for files that predate the sidecar
|
|
69
|
+
// metadata (or were written directly on disk without going through upload()).
|
|
70
|
+
contentType: storedContentType ?? Bun.file(target).type ?? undefined,
|
|
61
71
|
sizeBytes: info.size,
|
|
62
72
|
updatedAt: info.mtime.toISOString(),
|
|
63
73
|
};
|
|
@@ -82,12 +92,17 @@ export class LocalFsProvider implements FileStorageProvider {
|
|
|
82
92
|
}
|
|
83
93
|
|
|
84
94
|
async delete(scope: FileScope): Promise<void> {
|
|
85
|
-
|
|
95
|
+
const target = this.localPath(scope);
|
|
96
|
+
await rm(target, { force: true });
|
|
97
|
+
await rm(this.metaPath(target), { force: true });
|
|
86
98
|
}
|
|
87
99
|
|
|
88
100
|
async copy(source: FileScope, destination: FileScope): Promise<FileObject> {
|
|
101
|
+
const sourceHead = await this.head(source);
|
|
89
102
|
const response = await this.download(source);
|
|
90
|
-
await this.upload(destination, await response.arrayBuffer()
|
|
103
|
+
await this.upload(destination, await response.arrayBuffer(), {
|
|
104
|
+
contentType: sourceHead.contentType,
|
|
105
|
+
});
|
|
91
106
|
return this.head(destination);
|
|
92
107
|
}
|
|
93
108
|
|
|
@@ -133,6 +148,23 @@ export class LocalFsProvider implements FileStorageProvider {
|
|
|
133
148
|
return join(this.rootDir, providerPath(scope));
|
|
134
149
|
}
|
|
135
150
|
|
|
151
|
+
private metaPath(target: string): string {
|
|
152
|
+
return `${target}.meta.json`;
|
|
153
|
+
}
|
|
154
|
+
|
|
155
|
+
private async readStoredContentType(target: string): Promise<string | undefined> {
|
|
156
|
+
try {
|
|
157
|
+
const metaFile = Bun.file(this.metaPath(target));
|
|
158
|
+
if (!(await metaFile.exists())) {
|
|
159
|
+
return undefined;
|
|
160
|
+
}
|
|
161
|
+
const meta = await metaFile.json();
|
|
162
|
+
return typeof meta?.contentType === "string" ? meta.contentType : undefined;
|
|
163
|
+
} catch {
|
|
164
|
+
return undefined;
|
|
165
|
+
}
|
|
166
|
+
}
|
|
167
|
+
|
|
136
168
|
private async walk(dir: string, visit: (path: string) => Promise<void>): Promise<void> {
|
|
137
169
|
let entries: import("node:fs").Dirent<string>[];
|
|
138
170
|
try {
|
|
@@ -148,7 +180,7 @@ export class LocalFsProvider implements FileStorageProvider {
|
|
|
148
180
|
const path = join(dir, entry.name);
|
|
149
181
|
if (entry.isDirectory()) {
|
|
150
182
|
await this.walk(path, visit);
|
|
151
|
-
} else if (entry.isFile()) {
|
|
183
|
+
} else if (entry.isFile() && !entry.name.endsWith(".meta.json")) {
|
|
152
184
|
await visit(path);
|
|
153
185
|
}
|
|
154
186
|
}
|