@desplega.ai/agent-swarm 2.6.0 → 2.7.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 -0
- package/dist/{acp-adapter-dgfakfpw.js → acp-adapter-ma1js5n7.js} +6 -6
- package/dist/{actions-fbqma8aa.js → actions-1vhmtvg9.js} +18 -18
- package/dist/{amp-adapter-en0jy6hs.js → amp-adapter-sxqqvmdk.js} +3 -3
- package/dist/{app-k8a5cgpp.js → app-mwx1d5a9.js} +3 -3
- package/dist/{assistant-a16433gr.js → assistant-yx1p6g82.js} +20 -20
- package/dist/{boot-reembed-f4nn561n.js → boot-reembed-5h37a2c2.js} +1 -1
- package/dist/{boot-reembed-6c1sjfnr.js → boot-reembed-7h78vmvv.js} +2 -1
- package/dist/{boot-scrub-sweep-d4w9gajf.js → boot-scrub-sweep-8mawd4gw.js} +3 -3
- package/dist/{boot-seeds-9bmkspyw.js → boot-seeds-ca84dhnn.js} +4 -4
- package/dist/{claude-adapter-nys1kjxa.js → claude-adapter-wqt4hfkq.js} +7 -7
- package/dist/{claude-managed-adapter-55207973.js → claude-managed-adapter-6fjqtr2z.js} +2 -2
- package/dist/{claude-managed-setup-3cm35gey.js → claude-managed-setup-rqv98sea.js} +2 -2
- package/dist/{claude-sdk-session-nw5yd8cp.js → claude-sdk-session-f29f3b2x.js} +7 -7
- package/dist/{cli-sxgtf5fw.js → cli-07q03xm2.js} +1 -1
- package/dist/{cli-k21gx5ht.js → cli-1hhbzkbb.js} +6 -6
- package/dist/{cli-v49fkghp.js → cli-1m9x8dr5.js} +1 -1
- package/dist/{cli-24j1jeeb.js → cli-2pvzmas1.js} +3 -3
- package/dist/{cli-w2656ka1.js → cli-2y7v133v.js} +7 -7
- package/dist/{cli-g8ka5j4j.js → cli-48gxjmcz.js} +94 -36
- package/dist/{cli-2sjhpsns.js → cli-54sj64fc.js} +1 -1
- package/dist/{cli-aj8djyqt.js → cli-60sty27p.js} +2 -2
- package/dist/{cli-4hxrf0p4.js → cli-6a9qfa8b.js} +308 -47
- package/dist/{cli-n7asmce6.js → cli-6mgdba42.js} +3 -3
- package/dist/{cli-gf95q3bm.js → cli-6srv653c.js} +3 -3
- package/dist/{cli-9abdgn5t.js → cli-86ehn4s5.js} +3 -3
- package/dist/{cli-bqztfyve.js → cli-8mbc9za9.js} +1 -1
- package/dist/{cli-a2rr0fmg.js → cli-8wmj18cz.js} +1 -1
- package/dist/{cli-c2habvsf.js → cli-9gsm2rzp.js} +8 -8
- package/dist/{cli-4rf0f5vg.js → cli-9pshx6rd.js} +4 -2
- package/dist/{cli-abkm71vr.js → cli-a6ds2epe.js} +1 -1
- package/dist/{cli-esakngh8.js → cli-a89zdqz9.js} +1 -1
- package/dist/{cli-3ndmpxxv.js → cli-ay40dnj3.js} +2 -2
- package/dist/{cli-kf4jree9.js → cli-aypd09nf.js} +1 -1
- package/dist/{cli-6y51ryva.js → cli-b3yzyc57.js} +1 -1
- package/dist/{cli-ag2yrnt5.js → cli-c668vvcf.js} +2 -2
- package/dist/{cli-5sckpqcx.js → cli-d20qvpr2.js} +1 -1
- package/dist/{cli-a34pr3w1.js → cli-e1595bgx.js} +2 -2
- package/dist/{cli-m70bajzr.js → cli-f12mr20z.js} +1 -1
- package/dist/{cli-dqygeeh5.js → cli-f1e0ptjj.js} +10 -1
- package/dist/{cli-mvjz5an3.js → cli-f4dbh7a5.js} +7 -7
- package/dist/{cli-az25fn5f.js → cli-gsfk58nw.js} +2 -2
- package/dist/{cli-1tem6ns9.js → cli-gt4h8cxw.js} +3 -2
- package/dist/{cli-cz37jqss.js → cli-h5jeg46p.js} +2 -2
- package/dist/{cli-emhjcj2y.js → cli-he9gbdx0.js} +60 -10
- package/dist/{cli-3fwqy71j.js → cli-j0ppywdr.js} +38 -31
- package/dist/{cli-5rj83bam.js → cli-j3y0x2n4.js} +34 -33
- package/dist/{cli-3gfvmzht.js → cli-j9bpmykh.js} +73 -25
- package/dist/{cli-m5a9m0ak.js → cli-kv7sbc2c.js} +3 -3
- package/dist/{cli-g20jjwt4.js → cli-m30k9777.js} +12 -12
- package/dist/{cli-683y0b3a.js → cli-m5e802r7.js} +2 -2
- package/dist/{cli-nn6vx2mq.js → cli-ptfq202x.js} +5 -3
- package/dist/{cli-d2dpf95n.js → cli-qj442fws.js} +1 -1
- package/dist/{cli-03gg9pd1.js → cli-rd5m70gj.js} +4 -4
- package/dist/{cli-gnsb1zxa.js → cli-s09j9vp5.js} +1 -1
- package/dist/{cli-zqne1qcj.js → cli-s0mgx214.js} +2 -2
- package/dist/{cli-sqma6vw1.js → cli-sgqm2582.js} +5 -5
- package/dist/{cli-nq677csy.js → cli-sjctjr48.js} +1 -1
- package/dist/{cli-3aqd4jaa.js → cli-twctnkng.js} +3 -3
- package/dist/{cli-j6rktc91.js → cli-v6shg7na.js} +1 -1
- package/dist/{cli-7enjwya9.js → cli-vz78s5wm.js} +1 -1
- package/dist/{cli-art7c1zh.js → cli-wqr7cea4.js} +4 -4
- package/dist/{cli-hfeb68z4.js → cli-x37r7qwf.js} +11 -3
- package/dist/{cli-ffvsj2qw.js → cli-yd2nreqw.js} +2 -2
- package/dist/{cli-ca6banwz.js → cli-ymjpkxgn.js} +6 -6
- package/dist/{cli-mtnj4fwa.js → cli-yxbsmx56.js} +5 -5
- package/dist/cli.js +18 -16
- package/dist/{codex-adapter-d95q4qsn.js → codex-adapter-zk59x2zh.js} +5 -5
- package/dist/{codex-hook-brpnycn5.js → codex-hook-xyjhvxwd.js} +1 -1
- package/dist/{codex-session-runner-gdwd8h0c.js → codex-session-runner-kh6s4j3n.js} +5 -5
- package/dist/{commands-jqmgk54b.js → commands-68et1qv2.js} +2 -2
- package/dist/{controller-z3jq589f.js → controller-fyrbnzg4.js} +2 -2
- package/dist/{controllers-connect-r2za39ef.js → controllers-connect-nep9h2xv.js} +19 -19
- package/dist/{cursor-adapter-4mr4jgkw.js → cursor-adapter-yss3pg7t.js} +1 -1
- package/dist/{db-w36wwb5g.js → db-28gjyvxj.js} +2 -2
- package/dist/{db-ppv2tze7.js → db-8ftwvzba.js} +3 -3
- package/dist/{dispatcher-pv5h77vf.js → dispatcher-hayexmkr.js} +17 -17
- package/dist/{dsh-adapter-d4pdpb3r.js → dsh-adapter-46wawff9.js} +1 -1
- package/dist/{easter-egg-3d-4sp4dvmc.js → easter-egg-3d-8d1e0h4m.js} +1 -1
- package/dist/{execute-jev839y1.js → execute-cjrfxfnm.js} +1 -1
- package/dist/{external-channel-y6nkzhnn.js → external-channel-xbmxcg09.js} +2 -2
- package/dist/{grok-adapter-t08b9ntm.js → grok-adapter-ag4wks2a.js} +7 -7
- package/dist/handlers-tzmevqb5.js +41 -0
- package/dist/{heartbeat-vn31xwdk.js → heartbeat-psfvcgwp.js} +20 -20
- package/dist/{hook-6d0zbvvt.js → hook-8pz04697.js} +5 -5
- package/dist/{hook-cpjs8q51.js → hook-cyq4mryn.js} +6 -6
- package/dist/{http-9c6medda.js → http-haj1nd6k.js} +109 -114
- package/dist/{index-qhjf2htm.js → index-jwt2bj6f.js} +261 -96
- package/dist/{index-g6k9tctj.js → index-q52f47pe.js} +1 -1
- package/dist/{index-pkma1yf8.js → index-syvhnvtw.js} +1 -1
- package/dist/{index-yqgbfgaj.js → index-y1ajx04a.js} +20 -20
- package/dist/{index-k0yt1tvq.js → index-z5h22pes.js} +17 -17
- package/dist/{index-38t35h0n.js → index-zyq8ct52.js} +17 -17
- package/dist/{keepalive-em0117bp.js → keepalive-xdvsamvj.js} +3 -3
- package/dist/{lead-23e7z59t.js → lead-hqg57kdp.js} +35 -35
- package/dist/{maintenance-kbme3qy9.js → maintenance-4y2xzh3a.js} +1 -1
- package/dist/{oauth-refresh-sweep-xysj4yse.js → oauth-refresh-sweep-jd4r7zbs.js} +8 -3
- package/dist/{onboard-p6q0vz97.js → onboard-18447q9q.js} +5 -5
- package/dist/{opencode-adapter-esv3k9yn.js → opencode-adapter-26b2frdf.js} +5 -5
- package/dist/{otel-impl-54qhqh1z.js → otel-impl-f9tgr8yw.js} +1 -1
- package/dist/{pi-mono-adapter-mymbwhj0.js → pi-mono-adapter-qn6qbga1.js} +33 -12
- package/dist/{pricing-refresh-yb75wv33.js → pricing-refresh-dw2tgb53.js} +3 -3
- package/dist/{rbac-roles-1vp2mw4p.js → rbac-roles-aygm2760.js} +2 -2
- package/dist/{rbac-roles-dexhc9x8.js → rbac-roles-s4nwj2m3.js} +1 -1
- package/dist/{render-v2-tjay5hhf.js → render-v2-yfvdfr0x.js} +3 -3
- package/dist/{resume-8ayf37g5.js → resume-1bmvjbf3.js} +7 -7
- package/dist/{runtime-hqe5cqcd.js → runtime-f7tpzedy.js} +1 -1
- package/dist/{session-access-fdhp987s.js → session-access-r5garej6.js} +4 -4
- package/dist/{session-source-backfill-qgtc86z1.js → session-source-backfill-424wzv0r.js} +2 -2
- package/dist/{setup-4rrmqcq3.js → setup-8tygvxkq.js} +1 -1
- package/dist/{virtual-modules-52km00d6.js → virtual-modules-xsw323b3.js} +2 -2
- package/dist/{work-objects-p28fgd70.js → work-objects-ggnghpgf.js} +20 -20
- package/dist/{worker-e3w7vye1.js → worker-sqjd1nqf.js} +35 -35
- package/openapi.json +69 -1
- package/package.json +5 -3
- package/src/be/automation-preflight.ts +2 -0
- package/src/be/db/api-keys.ts +9 -0
- package/src/be/db/workflow-runs.ts +36 -5
- package/src/be/db.ts +80 -22
- package/src/be/memory/task-learning.ts +155 -0
- package/src/be/migrations/245_script_executor.sql +3 -0
- package/src/be/oauth-refresh-sweep.ts +2 -1
- package/src/be/scripts/boot-reembed.ts +2 -0
- package/src/be/scripts/db.ts +14 -4
- package/src/be/scripts/embeddings.ts +2 -0
- package/src/be/scripts/run-saved.ts +1 -0
- package/src/be/scripts/typecheck.ts +9 -3
- package/src/be/seed-skills/index.ts +7 -0
- package/src/commands/onboard/compose-generator.ts +5 -3
- package/src/commands/provider-credentials.ts +2 -1
- package/src/heartbeat/heartbeat.ts +14 -1
- package/src/heartbeat/script-runner-probe.ts +47 -0
- package/src/http/api-keys.ts +24 -9
- package/src/http/idempotency-key.ts +1 -1
- package/src/http/index.ts +3 -3
- package/src/http/pages-public.ts +38 -5
- package/src/http/scripts.ts +27 -0
- package/src/http/workflows.ts +10 -0
- package/src/http/x.ts +5 -0
- package/src/prompts/base-prompt.ts +21 -1
- package/src/prompts/session-templates.ts +17 -3
- package/src/providers/amp-adapter.ts +1 -1
- package/src/providers/dsh-adapter.ts +1 -1
- package/src/providers/pi-mono-adapter.ts +41 -11
- package/src/scheduler/scheduler.ts +2 -1
- package/src/scripts-runtime/eval-harness.ts +8 -1
- package/src/scripts-runtime/executors/native.ts +22 -16
- package/src/scripts-runtime/executors/quickjs-prelude.ts +103 -1
- package/src/scripts-runtime/executors/quickjs-runner.ts +2 -1
- package/src/scripts-runtime/executors/quickjs.ts +7 -0
- package/src/scripts-runtime/executors/registry.ts +32 -0
- package/src/scripts-runtime/executors/types.ts +7 -0
- package/src/scripts-runtime/host-validated-args.ts +133 -0
- package/src/scripts-runtime/loader.ts +30 -3
- package/src/scripts-runtime/swarm-sdk.ts +9 -2
- package/src/scripts-runtime/types/stdlib.d.ts +9 -1
- package/src/scripts-runtime/types/swarm-sdk.d.ts +9 -1
- package/src/tests/api-key-status-by-suffix.test.ts +242 -0
- package/src/tests/base-prompt.test.ts +70 -2
- package/src/tests/community-linear-delivery-loop.test.ts +2099 -0
- package/src/tests/credential-check.test.ts +4 -2
- package/src/tests/docs-index.test.ts +135 -0
- package/src/tests/dsh-adapter.test.ts +1 -1
- package/src/tests/fetch-resolved-env.test.ts +16 -2
- package/src/tests/heartbeat-checklist.test.ts +89 -0
- package/src/tests/heartbeat-script-runner-probe.test.ts +83 -0
- package/src/tests/oauth-refresh-sweep.test.ts +34 -1
- package/src/tests/onboard-compose.test.ts +20 -0
- package/src/tests/opencode-adapter.test.ts +2 -1
- package/src/tests/pages-frame-ancestors.test.ts +162 -0
- package/src/tests/pi-codemode.test.ts +2 -1
- package/src/tests/pi-mono-adapter.test.ts +54 -0
- package/src/tests/pi-native-mcp.test.ts +13 -1
- package/src/tests/pi-tool-exposure.test.ts +8 -8
- package/src/tests/probe-tool-schema-tokens.test.ts +144 -0
- package/src/tests/prompt-template-session.test.ts +4 -3
- package/src/tests/request-composition-derive.test.ts +329 -0
- package/src/tests/script-connections.test.ts +2 -0
- package/src/tests/script-executor-conformance.test.ts +36 -1
- package/src/tests/script-executor-quickjs-pool.test.ts +50 -0
- package/src/tests/script-executor-quickjs.test.ts +140 -0
- package/src/tests/script-host-validated-args-native.test.ts +196 -0
- package/src/tests/script-per-script-executor.test.ts +415 -0
- package/src/tests/scripts-external-api.test.ts +45 -0
- package/src/tests/sdk-allowlist.test.ts +3 -2
- package/src/tests/self-improvement.test.ts +4 -4
- package/src/tests/store-progress-learning.test.ts +288 -0
- package/src/tests/ui-logs-parser.test.ts +180 -0
- package/src/tests/workflow-engine-v2.test.ts +116 -1
- package/src/tests/workflow-mcp-trigger-schema.test.ts +209 -0
- package/src/tests/workflow-run-finished-event.test.ts +263 -0
- package/src/tests/workflow-swarm-script.test.ts +34 -0
- package/src/tools/script-common.ts +1 -0
- package/src/tools/script-run.ts +7 -0
- package/src/tools/script-upsert.ts +7 -0
- package/src/tools/store-progress.ts +67 -1
- package/src/tools/workflows/trigger-workflow.ts +104 -10
- package/src/types.ts +12 -1
- package/src/utils/credentials.ts +5 -2
- package/src/workflows/engine.ts +18 -16
- package/src/workflows/executors/swarm-script.ts +5 -0
- package/src/workflows/foreach-driver.ts +5 -11
- package/src/workflows/node-timeout.ts +30 -0
- package/templates/community/linear-delivery-loop/PLAYBOOK.md +323 -0
- package/templates/community/linear-delivery-loop/README.md +38 -0
- package/templates/community/linear-delivery-loop/config.example.json +31 -0
- package/templates/community/linear-delivery-loop/lead-prompt.md +34 -0
- package/templates/community/linear-delivery-loop/schedule.json +10 -0
- package/templates/community/linear-delivery-loop/scripts/linear-delivery-setup.ts +148 -0
- package/templates/community/linear-delivery-loop/scripts/linear-delivery-tick.ts +1781 -0
- package/templates/skills/swarm-docs/config.json +14 -0
- package/templates/skills/swarm-docs/content.md +144 -0
- package/dist/handlers-eqsnmv95.js +0 -41
|
@@ -0,0 +1,47 @@
|
|
|
1
|
+
import { runScriptWithRunCredential } from "../be/scripts/run-credential";
|
|
2
|
+
import { scrubSecrets } from "../utils/secret-scrubber";
|
|
3
|
+
|
|
4
|
+
const PROBE_TIMEOUT_MS = 10_000;
|
|
5
|
+
|
|
6
|
+
/** Exercise the same executor and per-run credential as the gate, with a deadline even if the runner stalls. */
|
|
7
|
+
export async function probeScriptRunner(
|
|
8
|
+
agentId: string,
|
|
9
|
+
runner: typeof runScriptWithRunCredential = runScriptWithRunCredential,
|
|
10
|
+
): Promise<{ quiet: false; reason: string } | null> {
|
|
11
|
+
const controller = new AbortController();
|
|
12
|
+
let timer: ReturnType<typeof setTimeout> | undefined;
|
|
13
|
+
try {
|
|
14
|
+
const deadline = new Promise<never>((_, reject) => {
|
|
15
|
+
timer = setTimeout(() => {
|
|
16
|
+
controller.abort();
|
|
17
|
+
reject(new Error("timeout after 10000ms"));
|
|
18
|
+
}, PROBE_TIMEOUT_MS);
|
|
19
|
+
});
|
|
20
|
+
const output = await Promise.race([
|
|
21
|
+
runner({
|
|
22
|
+
source: "export default async function () { return true; }",
|
|
23
|
+
agentId,
|
|
24
|
+
fsMode: "none",
|
|
25
|
+
timeoutMs: PROBE_TIMEOUT_MS,
|
|
26
|
+
signal: controller.signal,
|
|
27
|
+
}),
|
|
28
|
+
deadline,
|
|
29
|
+
]);
|
|
30
|
+
if (output.exitCode !== 0 || output.error || output.runtimeError) {
|
|
31
|
+
throw new Error(
|
|
32
|
+
output.runtimeError?.message ??
|
|
33
|
+
output.error ??
|
|
34
|
+
`script exited with code ${output.exitCode}`,
|
|
35
|
+
);
|
|
36
|
+
}
|
|
37
|
+
return null;
|
|
38
|
+
} catch (error) {
|
|
39
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
40
|
+
return {
|
|
41
|
+
quiet: false,
|
|
42
|
+
reason: `script-runner-unhealthy: ${scrubSecrets(message)}`.slice(0, 300),
|
|
43
|
+
};
|
|
44
|
+
} finally {
|
|
45
|
+
clearTimeout(timer);
|
|
46
|
+
}
|
|
47
|
+
}
|
package/src/http/api-keys.ts
CHANGED
|
@@ -243,14 +243,28 @@ const getAvailable = route({
|
|
|
243
243
|
pattern: ["api", "keys", "available"],
|
|
244
244
|
summary: "Get available (non-rate-limited) key indices for a credential type",
|
|
245
245
|
tags: ["API Keys"],
|
|
246
|
-
query: z
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
|
|
246
|
+
query: z
|
|
247
|
+
.object({
|
|
248
|
+
keyType: z.string(),
|
|
249
|
+
totalKeys: z.coerce.number().int().min(1),
|
|
250
|
+
scope: z.string().optional(),
|
|
251
|
+
scopeId: z.string().optional(),
|
|
252
|
+
/** Model family the caller is about to run. Filters out keys with an active weekly window block for that family. */
|
|
253
|
+
model: z.enum(["fable", "opus", "sonnet", "haiku"]).optional(),
|
|
254
|
+
/**
|
|
255
|
+
* Comma-separated key suffixes of the caller's pool, in pool order. When set,
|
|
256
|
+
* key status is matched by suffix across all scopes (not by stored index or
|
|
257
|
+
* `scope`), and returned indices are positions in this list.
|
|
258
|
+
*/
|
|
259
|
+
keySuffixes: z
|
|
260
|
+
.string()
|
|
261
|
+
.transform((value) => value.split(","))
|
|
262
|
+
.optional(),
|
|
263
|
+
})
|
|
264
|
+
.refine((query) => !query.keySuffixes || query.keySuffixes.length === query.totalKeys, {
|
|
265
|
+
message: "keySuffixes must list exactly totalKeys suffixes",
|
|
266
|
+
path: ["keySuffixes"],
|
|
267
|
+
}),
|
|
254
268
|
responses: {
|
|
255
269
|
200: {
|
|
256
270
|
description: "List of available key indices",
|
|
@@ -690,7 +704,7 @@ export async function handleApiKeys(
|
|
|
690
704
|
const parsed = await getAvailable.parse(req, res, pathSegments, queryParams);
|
|
691
705
|
if (!parsed) return true;
|
|
692
706
|
|
|
693
|
-
const { keyType, totalKeys, scope, scopeId, model } = parsed.query;
|
|
707
|
+
const { keyType, totalKeys, scope, scopeId, model, keySuffixes } = parsed.query;
|
|
694
708
|
try {
|
|
695
709
|
const result = await getAvailableKeyIndices(
|
|
696
710
|
keyType,
|
|
@@ -698,6 +712,7 @@ export async function handleApiKeys(
|
|
|
698
712
|
scope,
|
|
699
713
|
scopeId ?? null,
|
|
700
714
|
model,
|
|
715
|
+
keySuffixes,
|
|
701
716
|
);
|
|
702
717
|
getAvailable.respond(res, 200, {
|
|
703
718
|
success: true,
|
|
@@ -8,7 +8,7 @@ import { getRequestAuth } from "../utils/request-auth-context";
|
|
|
8
8
|
import { jsonError } from "./utils";
|
|
9
9
|
|
|
10
10
|
/** Visible ASCII, at most 255 characters: room for a UUID, a ULID or a client's own scheme. */
|
|
11
|
-
const IDEMPOTENCY_KEY_PATTERN = /^[\x21-\x7E]{1,255}$/;
|
|
11
|
+
export const IDEMPOTENCY_KEY_PATTERN = /^[\x21-\x7E]{1,255}$/;
|
|
12
12
|
|
|
13
13
|
/** Set on a response answered from an earlier accepted request. */
|
|
14
14
|
export const IDEMPOTENT_REPLAYED_HEADER = "Idempotent-Replayed";
|
package/src/http/index.ts
CHANGED
|
@@ -517,7 +517,7 @@ async function shutdown() {
|
|
|
517
517
|
await stopChatSdk();
|
|
518
518
|
|
|
519
519
|
// Stop OAuth keepalive
|
|
520
|
-
if (
|
|
520
|
+
if (!isEnvFlagEnabled("OAUTH_KEEPALIVE_DISABLE", false)) {
|
|
521
521
|
const { stopOAuthKeepalive } = await import("../oauth/keepalive");
|
|
522
522
|
await stopOAuthKeepalive();
|
|
523
523
|
}
|
|
@@ -847,7 +847,7 @@ httpServer
|
|
|
847
847
|
startRuntimeSupervisor();
|
|
848
848
|
|
|
849
849
|
// Start OAuth token keepalive (proactive refresh to prevent expiry)
|
|
850
|
-
if (
|
|
850
|
+
if (!isEnvFlagEnabled("OAUTH_KEEPALIVE_DISABLE", false)) {
|
|
851
851
|
const { startOAuthKeepalive } = await import("../oauth/keepalive");
|
|
852
852
|
startOAuthKeepalive();
|
|
853
853
|
}
|
|
@@ -855,7 +855,7 @@ httpServer
|
|
|
855
855
|
// Start generic OAuth token refresh sweep (all oauth_apps providers,
|
|
856
856
|
// 15-min tick, first run ~1 min after boot). Complements the tracker-only
|
|
857
857
|
// keepalive above — see src/be/oauth-refresh-sweep.ts.
|
|
858
|
-
if (
|
|
858
|
+
if (!isEnvFlagEnabled("OAUTH_REFRESH_SWEEP_DISABLE", false)) {
|
|
859
859
|
const { startOAuthRefreshSweep } = await import("../be/oauth-refresh-sweep");
|
|
860
860
|
startOAuthRefreshSweep();
|
|
861
861
|
}
|
package/src/http/pages-public.ts
CHANGED
|
@@ -308,13 +308,45 @@ function getAppBaseUrl(): string {
|
|
|
308
308
|
return getAppUrl(LOCAL_PAGE_APP_URL);
|
|
309
309
|
}
|
|
310
310
|
|
|
311
|
+
// CSP host-source without a path: optional http(s) scheme, a host that may
|
|
312
|
+
// start with a `*.` wildcard label, and an optional port (digits or `*`).
|
|
313
|
+
// Bare `*`, keywords (`'none'`, `'self'`) and anything carrying `;` or a path
|
|
314
|
+
// are rejected so a bad env value cannot widen or break the policy.
|
|
315
|
+
const FRAME_ANCESTOR_SOURCE_RE =
|
|
316
|
+
/^(?:https?:\/\/)?(?:\*\.)?[a-z0-9](?:[a-z0-9-]*[a-z0-9])?(?:\.[a-z0-9](?:[a-z0-9-]*[a-z0-9])?)*(?::(?:\d{1,5}|\*))?$/i;
|
|
317
|
+
|
|
318
|
+
let cachedFrameAncestors: { raw: string; origins: string[] } | null = null;
|
|
319
|
+
|
|
320
|
+
/**
|
|
321
|
+
* Parse `PAGES_FRAME_ANCESTORS`: extra origins allowed to iframe PUBLIC pages
|
|
322
|
+
* (for example a marketing site embedding a chart). Comma or space separated.
|
|
323
|
+
* Invalid entries are dropped with one warning per distinct env value; the
|
|
324
|
+
* parse is cached on the raw string so a config reload picks up changes
|
|
325
|
+
* without re-warning on every request.
|
|
326
|
+
*/
|
|
327
|
+
export function getPageFrameAncestors(): string[] {
|
|
328
|
+
const raw = process.env.PAGES_FRAME_ANCESTORS ?? "";
|
|
329
|
+
if (cachedFrameAncestors?.raw === raw) return cachedFrameAncestors.origins;
|
|
330
|
+
const origins: string[] = [];
|
|
331
|
+
for (const entry of raw.split(/[\s,]+/)) {
|
|
332
|
+
const value = entry.replace(/\/+$/, "");
|
|
333
|
+
if (!value) continue;
|
|
334
|
+
if (FRAME_ANCESTOR_SOURCE_RE.test(value)) origins.push(value);
|
|
335
|
+
else console.warn(`[pages] ignoring invalid PAGES_FRAME_ANCESTORS entry: ${entry}`);
|
|
336
|
+
}
|
|
337
|
+
cachedFrameAncestors = { raw, origins };
|
|
338
|
+
return origins;
|
|
339
|
+
}
|
|
340
|
+
|
|
311
341
|
/**
|
|
312
342
|
* Build the `Content-Security-Policy` for the served HTML. Allows inline
|
|
313
343
|
* scripts (required for `BROWSER_SDK_JS`) but locks down everything else to
|
|
314
344
|
* `'self'`. The SPA iframes the page in step-6 with `sandbox="allow-scripts
|
|
315
|
-
* allow-forms"`; the CSP is a defence-in-depth layer.
|
|
345
|
+
* allow-forms"`; the CSP is a defence-in-depth layer. `PAGES_FRAME_ANCESTORS`
|
|
346
|
+
* applies only to public pages: authed and password pages stay embeddable by
|
|
347
|
+
* the app origins alone.
|
|
316
348
|
*/
|
|
317
|
-
function buildCsp(): string {
|
|
349
|
+
function buildCsp(page: Page): string {
|
|
318
350
|
// `frame-ancestors` lists every origin allowed to iframe `/p/:id`. We must
|
|
319
351
|
// include the SPA origin(s). Configured app URLs may be comma-separated so
|
|
320
352
|
// portless dev (`https://ui.swarm.localhost`), a Vite port (`http://localhost:5274`),
|
|
@@ -333,7 +365,8 @@ function buildCsp(): string {
|
|
|
333
365
|
"https://*.localhost",
|
|
334
366
|
"http://*.localhost",
|
|
335
367
|
];
|
|
336
|
-
const
|
|
368
|
+
const embedders = page.authMode === "public" ? getPageFrameAncestors() : [];
|
|
369
|
+
const ancestors = Array.from(new Set([...configured, ...devFallbacks, ...embedders]));
|
|
337
370
|
// Allow Tailwind Play CDN (`cdn.tailwindcss.com`) for scripts, Google
|
|
338
371
|
// Fonts (`fonts.googleapis.com` stylesheets + `fonts.gstatic.com` font
|
|
339
372
|
// files) for the swarm default typography, and same-origin /@swarm/api/*
|
|
@@ -609,7 +642,7 @@ export async function handlePagesPublic(
|
|
|
609
642
|
const headers: Record<string, string | string[]> = {
|
|
610
643
|
"Content-Type": "text/html; charset=utf-8",
|
|
611
644
|
"Cache-Control": "no-store",
|
|
612
|
-
"Content-Security-Policy": buildCsp(),
|
|
645
|
+
"Content-Security-Policy": buildCsp(page),
|
|
613
646
|
"X-Content-Type-Options": "nosniff",
|
|
614
647
|
};
|
|
615
648
|
if (inlineSetCookie.length > 0) headers["Set-Cookie"] = inlineSetCookie;
|
|
@@ -642,7 +675,7 @@ export async function handlePagesPublic(
|
|
|
642
675
|
const headers: Record<string, string | string[]> = {
|
|
643
676
|
"Content-Type": "text/html; charset=utf-8",
|
|
644
677
|
"Cache-Control": "no-store",
|
|
645
|
-
"Content-Security-Policy": buildCsp(),
|
|
678
|
+
"Content-Security-Policy": buildCsp(page),
|
|
646
679
|
// Defence-in-depth: prevent MIME sniffing and clickjacking outside the SPA.
|
|
647
680
|
"X-Content-Type-Options": "nosniff",
|
|
648
681
|
};
|
package/src/http/scripts.ts
CHANGED
|
@@ -40,12 +40,14 @@ import {
|
|
|
40
40
|
typecheckScript,
|
|
41
41
|
} from "../be/scripts/typecheck";
|
|
42
42
|
import { can } from "../rbac";
|
|
43
|
+
import { isExecutorAllowedByDeployment } from "../scripts-runtime/executors/registry";
|
|
43
44
|
import { extractScriptSignature, type ScriptSignature } from "../scripts-runtime/extract-signature";
|
|
44
45
|
import { checkQuickJSCompat, quickJSCompatWarnings } from "../scripts-runtime/quickjs-compat";
|
|
45
46
|
import {
|
|
46
47
|
ScriptApiAuthModeSchema,
|
|
47
48
|
ScriptApiRecordSchema,
|
|
48
49
|
type ScriptDetail,
|
|
50
|
+
ScriptExecutorNameSchema,
|
|
49
51
|
ScriptFsModeSchema,
|
|
50
52
|
type ScriptListItem,
|
|
51
53
|
type ScriptRecord,
|
|
@@ -67,6 +69,7 @@ const upsertBodySchema = z.object({
|
|
|
67
69
|
intent: z.string().default(""),
|
|
68
70
|
scope: ScriptScopeSchema.default("agent"),
|
|
69
71
|
fsMode: ScriptFsModeSchema.default("none"),
|
|
72
|
+
executor: ScriptExecutorNameSchema.nullable().optional(),
|
|
70
73
|
});
|
|
71
74
|
|
|
72
75
|
const runBodySchema = z
|
|
@@ -77,6 +80,7 @@ const runBodySchema = z
|
|
|
77
80
|
intent: z.string().default(""),
|
|
78
81
|
scope: ScriptScopeSchema.optional(),
|
|
79
82
|
fsMode: ScriptFsModeSchema.default("none"),
|
|
83
|
+
executor: ScriptExecutorNameSchema.nullable().optional(),
|
|
80
84
|
idempotencyKey: z.string().max(200).optional(),
|
|
81
85
|
})
|
|
82
86
|
.refine((body) => Boolean(body.name) !== Boolean(body.source), {
|
|
@@ -671,6 +675,15 @@ export async function handleScripts(
|
|
|
671
675
|
}
|
|
672
676
|
}
|
|
673
677
|
|
|
678
|
+
if (parsed.body.executor && !isExecutorAllowedByDeployment(parsed.body.executor)) {
|
|
679
|
+
jsonError(
|
|
680
|
+
res,
|
|
681
|
+
'Executor "native" is not permitted when deployment SCRIPT_EXECUTOR="quickjs"',
|
|
682
|
+
400,
|
|
683
|
+
);
|
|
684
|
+
return true;
|
|
685
|
+
}
|
|
686
|
+
|
|
674
687
|
const secretRefusal = refuseSourceWithSecrets(parsed.body.source);
|
|
675
688
|
if (secretRefusal) {
|
|
676
689
|
json(res, secretRefusal, 400);
|
|
@@ -708,6 +721,7 @@ export async function handleScripts(
|
|
|
708
721
|
signatureJson: signatureJsonFor(parsed.body.source),
|
|
709
722
|
argsJsonSchema,
|
|
710
723
|
fsMode: parsed.body.fsMode,
|
|
724
|
+
executor: parsed.body.executor,
|
|
711
725
|
agentId: agent.id,
|
|
712
726
|
isScratch: false,
|
|
713
727
|
typeChecked: true,
|
|
@@ -758,6 +772,7 @@ export async function handleScripts(
|
|
|
758
772
|
|
|
759
773
|
let source = parsed.body.source;
|
|
760
774
|
let fsMode = parsed.body.fsMode;
|
|
775
|
+
let executor = parsed.body.executor ?? null;
|
|
761
776
|
let namedScript: ScriptRecord | null = null;
|
|
762
777
|
if (parsed.body.name) {
|
|
763
778
|
const script = await resolveScript(parsed.body.name, agent.id, parsed.body.scope);
|
|
@@ -768,6 +783,7 @@ export async function handleScripts(
|
|
|
768
783
|
namedScript = script;
|
|
769
784
|
source = script.source;
|
|
770
785
|
fsMode = script.fsMode;
|
|
786
|
+
executor = parsed.body.executor !== undefined ? parsed.body.executor : script.executor;
|
|
771
787
|
}
|
|
772
788
|
|
|
773
789
|
if (fsMode === "workspace-rw") {
|
|
@@ -775,6 +791,15 @@ export async function handleScripts(
|
|
|
775
791
|
return true;
|
|
776
792
|
}
|
|
777
793
|
|
|
794
|
+
if (executor && !isExecutorAllowedByDeployment(executor)) {
|
|
795
|
+
jsonError(
|
|
796
|
+
res,
|
|
797
|
+
'Executor "native" is not permitted when deployment SCRIPT_EXECUTOR="quickjs"',
|
|
798
|
+
400,
|
|
799
|
+
);
|
|
800
|
+
return true;
|
|
801
|
+
}
|
|
802
|
+
|
|
778
803
|
const startedAt = new Date().toISOString();
|
|
779
804
|
// Touch before executing so an already-stale scratch script isn't reaped
|
|
780
805
|
// by the retention sweep while this run is still in flight.
|
|
@@ -786,6 +811,7 @@ export async function handleScripts(
|
|
|
786
811
|
source: source as string,
|
|
787
812
|
args: parsed.body.args,
|
|
788
813
|
fsMode,
|
|
814
|
+
executor,
|
|
789
815
|
agentId: agent.id,
|
|
790
816
|
runtimeInstanceId,
|
|
791
817
|
egressSecrets: credentials.egressSecrets,
|
|
@@ -954,6 +980,7 @@ export async function handleScripts(
|
|
|
954
980
|
isScratch: script.isScratch,
|
|
955
981
|
typeChecked: script.typeChecked,
|
|
956
982
|
fsMode: script.fsMode,
|
|
983
|
+
executor: script.executor,
|
|
957
984
|
createdByAgentId: script.createdByAgentId,
|
|
958
985
|
createdAt: script.createdAt,
|
|
959
986
|
updatedAt: script.updatedAt,
|
package/src/http/workflows.ts
CHANGED
|
@@ -307,6 +307,15 @@ const triggerWorkflowRoute = route({
|
|
|
307
307
|
"With an `Idempotency-Key`, a retry returns the accepted run's `runId` and `skipped` without starting, resuming or waiting for anything, even after the workflow was disabled or edited and whatever state the run is in now.",
|
|
308
308
|
tags: ["Workflows"],
|
|
309
309
|
params: z.object({ id: z.string() }),
|
|
310
|
+
query: z.object({
|
|
311
|
+
wait: z
|
|
312
|
+
.enum(["true", "false"])
|
|
313
|
+
.optional()
|
|
314
|
+
.describe(
|
|
315
|
+
"Default true: respond after the run's instant steps ran, when it finishes or parks at its first async step. " +
|
|
316
|
+
"false: respond as soon as the run is committed and walk it in the background.",
|
|
317
|
+
),
|
|
318
|
+
}),
|
|
310
319
|
headers: idempotencyKeyHeader("starts one run"),
|
|
311
320
|
responses: {
|
|
312
321
|
201: {
|
|
@@ -1002,6 +1011,7 @@ export async function handleWorkflows(
|
|
|
1002
1011
|
runId = await startWorkflowExecution(workflow, body, getExecutorRegistry(), {
|
|
1003
1012
|
triggerType: "api",
|
|
1004
1013
|
requestedByUserId: (await resolveHttpAuditUserId(req, myAgentId)) ?? undefined,
|
|
1014
|
+
detach: parsed.query.wait === "false",
|
|
1005
1015
|
// Recorded in the run's own transaction, before the walk starts.
|
|
1006
1016
|
accept: claim
|
|
1007
1017
|
? async (run) => {
|
package/src/http/x.ts
CHANGED
|
@@ -20,6 +20,7 @@ import {
|
|
|
20
20
|
MAX_SCRIPT_WALL_CLOCK_MS,
|
|
21
21
|
MIN_SCRIPT_WALL_CLOCK_MS,
|
|
22
22
|
} from "../scripts-runtime/executors/types";
|
|
23
|
+
import { HOST_VALIDATED_ARGS } from "../scripts-runtime/host-validated-args";
|
|
23
24
|
import type { RunScriptOutput } from "../scripts-runtime/loader";
|
|
24
25
|
import { scrubObject, scrubSecrets } from "../utils/secret-scrubber";
|
|
25
26
|
import { validateJsonSchema } from "../workflows/json-schema-validator";
|
|
@@ -214,6 +215,7 @@ export async function handleX(
|
|
|
214
215
|
// Scripts predating the schema column, or where extraction failed, have a
|
|
215
216
|
// null schema — we skip and let the in-subprocess Zod check surface as a
|
|
216
217
|
// runtime_error instead.
|
|
218
|
+
let hostValidatedArgs = false;
|
|
217
219
|
if (script.argsJsonSchema) {
|
|
218
220
|
try {
|
|
219
221
|
const schema = JSON.parse(script.argsJsonSchema) as Record<string, unknown>;
|
|
@@ -227,6 +229,7 @@ export async function handleX(
|
|
|
227
229
|
});
|
|
228
230
|
return true;
|
|
229
231
|
}
|
|
232
|
+
hostValidatedArgs = true;
|
|
230
233
|
} catch {
|
|
231
234
|
// Malformed stored schema — don't block execution on it.
|
|
232
235
|
}
|
|
@@ -245,7 +248,9 @@ export async function handleX(
|
|
|
245
248
|
const output = await runScriptWithRunCredential({
|
|
246
249
|
source: script.source,
|
|
247
250
|
args: args ?? null,
|
|
251
|
+
[HOST_VALIDATED_ARGS]: hostValidatedArgs,
|
|
248
252
|
fsMode: "none",
|
|
253
|
+
executor: script.executor,
|
|
249
254
|
agentId: endpoint.agentId,
|
|
250
255
|
// timeoutMs only raises the wall-clock limit (network-bound scripts can use
|
|
251
256
|
// the full window); the CPU ulimit (`ulimit -t`) is intentionally left at
|
|
@@ -271,12 +271,22 @@ export const getBasePrompt = async (args: BasePromptArgs): Promise<string> => {
|
|
|
271
271
|
renderToolsAndSkillsVars({
|
|
272
272
|
skillsSummary: args.skillsSummary,
|
|
273
273
|
mcpServers: args.mcpServers,
|
|
274
|
+
// pi defers installed MCP servers together with swarm tools.
|
|
275
|
+
mcpToolsDeferred: provider === "pi" && hasToolSearch,
|
|
274
276
|
nativeSkillDiscovery,
|
|
275
277
|
toolDiscovery: discoveryResult.text,
|
|
276
278
|
hasLocalEnv,
|
|
277
279
|
}),
|
|
278
280
|
);
|
|
279
281
|
prompt += toolsResult.text;
|
|
282
|
+
|
|
283
|
+
// Lead docs pointer: only when the lead has the `swarm-docs` skill. The
|
|
284
|
+
// skill list is unknown on claude-managed (the cloud sandbox owns skill
|
|
285
|
+
// delivery) and before the first skill load, so render it then.
|
|
286
|
+
if (role === "lead" && hasSwarmDocsSkill(args.skillsSummary)) {
|
|
287
|
+
const docsResult = await resolveTemplateAsync("system.agent.lead.docs", {});
|
|
288
|
+
prompt += docsResult.text;
|
|
289
|
+
}
|
|
280
290
|
}
|
|
281
291
|
|
|
282
292
|
// J. Agent notes (CLAUDE.md), only when it differs from the generated
|
|
@@ -339,6 +349,11 @@ export const getTaskContextPrompt = async (args: TaskContextPromptArgs): Promise
|
|
|
339
349
|
return prompt.replace(/\n{3,}/g, "\n\n").trim();
|
|
340
350
|
};
|
|
341
351
|
|
|
352
|
+
/** True when the skill list includes `swarm-docs`, or when the list is unknown. */
|
|
353
|
+
function hasSwarmDocsSkill(skills: { name: string }[] | undefined): boolean {
|
|
354
|
+
return skills === undefined || skills.some((skill) => skill.name === "swarm-docs");
|
|
355
|
+
}
|
|
356
|
+
|
|
342
357
|
/**
|
|
343
358
|
* Dynamic lines for `system.agent.tools_skills`. The static text lives in the
|
|
344
359
|
* template so operators can override or skip the section; only the lists that
|
|
@@ -347,6 +362,7 @@ export const getTaskContextPrompt = async (args: TaskContextPromptArgs): Promise
|
|
|
347
362
|
function renderToolsAndSkillsVars(input: {
|
|
348
363
|
skillsSummary?: { name: string; description: string }[];
|
|
349
364
|
mcpServers?: string[];
|
|
365
|
+
mcpToolsDeferred?: boolean;
|
|
350
366
|
nativeSkillDiscovery: boolean;
|
|
351
367
|
hasLocalEnv: boolean;
|
|
352
368
|
toolDiscovery: string;
|
|
@@ -382,7 +398,11 @@ function renderToolsAndSkillsVars(input: {
|
|
|
382
398
|
const servers = (input.mcpServers ?? []).filter((name) => name.trim().length > 0);
|
|
383
399
|
const mcpLine =
|
|
384
400
|
servers.length > 0
|
|
385
|
-
? `Connected MCP servers: ${servers.join(", ")}.
|
|
401
|
+
? `Connected MCP servers: ${servers.join(", ")}. ${
|
|
402
|
+
input.mcpToolsDeferred
|
|
403
|
+
? "Load their tools with your harness tool search."
|
|
404
|
+
: "Their tools are in your tool list."
|
|
405
|
+
}\n`
|
|
386
406
|
: "";
|
|
387
407
|
|
|
388
408
|
return {
|
|
@@ -15,8 +15,9 @@
|
|
|
15
15
|
* G secrets system.agent.secrets
|
|
16
16
|
*
|
|
17
17
|
* Deployment-gated blocks (E outputs, H slack, steering) and the per-agent and
|
|
18
|
-
* per-task sections (tools and skills, agent notes,
|
|
19
|
-
* profile) are appended by base-prompt.ts after the
|
|
18
|
+
* per-task sections (tools and skills, the lead docs pointer, agent notes,
|
|
19
|
+
* repository, requester profile) are appended by base-prompt.ts after the
|
|
20
|
+
* composite.
|
|
20
21
|
*
|
|
21
22
|
* Writing rules for every body: sentence case headings, one idea per sentence,
|
|
22
23
|
* active voice, plain words, no em dashes, a prohibition only where no positive
|
|
@@ -109,6 +110,19 @@ Your heartbeat runbook is the \`heartbeatMd\` profile field. Edit it with \`upda
|
|
|
109
110
|
category: "system",
|
|
110
111
|
});
|
|
111
112
|
|
|
113
|
+
// Lead docs pointer. base-prompt.ts appends it after the tools and skills
|
|
114
|
+
// section when the lead has the `swarm-docs` skill (or when the skill list is
|
|
115
|
+
// unknown). An operator override with an empty body switches it off.
|
|
116
|
+
registerTemplate({
|
|
117
|
+
eventType: "system.agent.lead.docs",
|
|
118
|
+
header: "",
|
|
119
|
+
defaultBody: `
|
|
120
|
+
Load the \`swarm-docs\` skill only when a person asks for something no skill, workflow, schedule or memory of yours covers, or asks where something is documented. Then follow its offer rules.
|
|
121
|
+
`,
|
|
122
|
+
variables: [],
|
|
123
|
+
category: "system",
|
|
124
|
+
});
|
|
125
|
+
|
|
112
126
|
// Remote providers (no MCP, no container): the task arrives in the session
|
|
113
127
|
// prompt and the final message is the output. No tools to name.
|
|
114
128
|
registerTemplate({
|
|
@@ -168,7 +182,7 @@ registerTemplate({
|
|
|
168
182
|
## Memory
|
|
169
183
|
|
|
170
184
|
Memories from past sessions are in your task message. Read them before you start. For a wider search, run the \`task-context-gathering\` script with the task ID and two to four queries.
|
|
171
|
-
|
|
185
|
+
When you learn something that would change how the next agent works (a fix, a pattern, a gotcha), pass it as \`learning\` on a \`store-progress\` call before the task ends. Completed task outputs are stored for you.
|
|
172
186
|
You MUST use the \`memory\` skill before you store, edit, or delete a memory.
|
|
173
187
|
`,
|
|
174
188
|
variables: [],
|
|
@@ -45,7 +45,7 @@ import type {
|
|
|
45
45
|
* default; the image installs exactly this version and the per-task settings
|
|
46
46
|
* set `amp.updates.mode: "disabled"`. Bump it deliberately, with a live run.
|
|
47
47
|
*/
|
|
48
|
-
export const AMP_PACKAGE = "@ampcode/cli@0.0.
|
|
48
|
+
export const AMP_PACKAGE = "@ampcode/cli@0.0.1793025586-g9211b5";
|
|
49
49
|
|
|
50
50
|
/** MCP server name. Amp exposes its tools as `agent_swarm` / `store_progress` (dashes become underscores). */
|
|
51
51
|
const AMP_MCP_SERVER_NAME = "agent-swarm";
|
|
@@ -37,7 +37,7 @@ import type {
|
|
|
37
37
|
} from "./types";
|
|
38
38
|
|
|
39
39
|
// npm's `latest` is older and lacks the JSON/stdin contract used here.
|
|
40
|
-
export const DSH_PACKAGE = "@deepseek-ai/dsh@0.2.1-rc.
|
|
40
|
+
export const DSH_PACKAGE = "@deepseek-ai/dsh@0.2.1-rc.4";
|
|
41
41
|
|
|
42
42
|
export function checkDshCredentials(env: Record<string, string | undefined>): CredStatus {
|
|
43
43
|
return env.DEEPSEEK_API_KEY?.trim() || env.OPENROUTER_API_KEY?.trim()
|
|
@@ -382,14 +382,15 @@ type ToolStructuredContent = NonNullable<AgentToolResult["structuredContent"]>;
|
|
|
382
382
|
export const SWARM_TOOL_NAMESPACE = { name: "agent-swarm" } as const;
|
|
383
383
|
|
|
384
384
|
/**
|
|
385
|
-
* `PI_TOOL_DEFERRAL`: hide non-core swarm tools
|
|
386
|
-
*
|
|
387
|
-
*
|
|
388
|
-
*
|
|
389
|
-
*
|
|
385
|
+
* `PI_TOOL_DEFERRAL`: hide non-core swarm tools, and installed MCP server
|
|
386
|
+
* tools, behind pi's `tool_search`. On by default: the deferred schemas cost
|
|
387
|
+
* more on every request than the one partial cache miss a load causes.
|
|
388
|
+
* `false` declares every tool up front. Read from `process.env` by both the
|
|
389
|
+
* adapter traits (which pick the prompt's tool-discovery line) and
|
|
390
|
+
* `createSession`, so the prompt and the session always agree.
|
|
390
391
|
*/
|
|
391
392
|
export function isPiToolDeferralEnabled(env: NodeJS.ProcessEnv = process.env): boolean {
|
|
392
|
-
return parseEnvFlag(env.PI_TOOL_DEFERRAL,
|
|
393
|
+
return parseEnvFlag(env.PI_TOOL_DEFERRAL, true);
|
|
393
394
|
}
|
|
394
395
|
|
|
395
396
|
/**
|
|
@@ -919,6 +920,28 @@ function requestContextTokens(message: unknown): number | undefined {
|
|
|
919
920
|
return usage.input + cacheRead + cacheWrite;
|
|
920
921
|
}
|
|
921
922
|
|
|
923
|
+
/**
|
|
924
|
+
* The session-log line for one LLM call's usage (token counts only, never
|
|
925
|
+
* content). The dashboard reads it as that call's cache-hit chip.
|
|
926
|
+
*/
|
|
927
|
+
function callUsageLine(message: unknown): string | undefined {
|
|
928
|
+
const usage = (message as { usage?: Record<string, unknown> } | null)?.usage;
|
|
929
|
+
if (typeof usage?.input !== "number") return undefined;
|
|
930
|
+
const count = (value: unknown) =>
|
|
931
|
+
typeof value === "number" && Number.isFinite(value) ? value : 0;
|
|
932
|
+
return JSON.stringify({
|
|
933
|
+
type: "call_usage",
|
|
934
|
+
harness: "pi",
|
|
935
|
+
usage: {
|
|
936
|
+
input: count(usage.input),
|
|
937
|
+
cacheRead: count(usage.cacheRead),
|
|
938
|
+
cacheWrite: count(usage.cacheWrite),
|
|
939
|
+
output: count(usage.output),
|
|
940
|
+
reasoning: count(usage.reasoning),
|
|
941
|
+
},
|
|
942
|
+
});
|
|
943
|
+
}
|
|
944
|
+
|
|
922
945
|
function describeAssistantTurn(message: unknown): AssistantTurnShape {
|
|
923
946
|
const msg = (message ?? {}) as {
|
|
924
947
|
content?: unknown;
|
|
@@ -1116,6 +1139,10 @@ export class PiMonoSession implements ProviderSession {
|
|
|
1116
1139
|
if (endMsg.role === "assistant") {
|
|
1117
1140
|
const turn = describeAssistantTurn(event.message);
|
|
1118
1141
|
this.lastAssistantTurn = turn;
|
|
1142
|
+
// Ahead of the turn's text and tool lines: the chip rides on the
|
|
1143
|
+
// call's first row.
|
|
1144
|
+
const usageLine = callUsageLine(event.message);
|
|
1145
|
+
if (usageLine) this.emit({ type: "raw_log", content: usageLine });
|
|
1119
1146
|
if (endMsg.stopReason === "error") {
|
|
1120
1147
|
// Candidate terminal failure. May still be cleared by a successful
|
|
1121
1148
|
// retry (auto_retry_end success / a later good message_end).
|
|
@@ -1684,12 +1711,15 @@ function escapeValues(values: unknown): Record<string, string> {
|
|
|
1684
1711
|
/**
|
|
1685
1712
|
* Map installed servers (`fetchInstalledMcpServers(..., "claude")` entries)
|
|
1686
1713
|
* to pi MCP configs. pi speaks stdio and streamable HTTP; `sse` entries map to
|
|
1687
|
-
* HTTP, which is how the previous pi client already reached them.
|
|
1688
|
-
* `
|
|
1714
|
+
* HTTP, which is how the previous pi client already reached them. With
|
|
1715
|
+
* `deferred`, their tools load through `tool_search` like non-core swarm
|
|
1716
|
+
* tools; otherwise they are declared up front.
|
|
1689
1717
|
*/
|
|
1690
1718
|
export function toPiMcpServers(
|
|
1691
1719
|
installed: Record<string, Record<string, unknown>> | null,
|
|
1720
|
+
options: { deferred?: boolean } = {},
|
|
1692
1721
|
): Record<string, McpServerConfig> {
|
|
1722
|
+
const exposure = options.deferred ? "deferred" : "direct";
|
|
1693
1723
|
const servers: Record<string, McpServerConfig> = {};
|
|
1694
1724
|
for (const [name, entry] of Object.entries(installed ?? {})) {
|
|
1695
1725
|
if (typeof entry.command === "string") {
|
|
@@ -1698,14 +1728,14 @@ export function toPiMcpServers(
|
|
|
1698
1728
|
command: entry.command,
|
|
1699
1729
|
args: Array.isArray(entry.args) ? entry.args.map(String) : [],
|
|
1700
1730
|
env: escapeValues(entry.env),
|
|
1701
|
-
exposure
|
|
1731
|
+
exposure,
|
|
1702
1732
|
};
|
|
1703
1733
|
} else if (typeof entry.url === "string") {
|
|
1704
1734
|
servers[name] = {
|
|
1705
1735
|
type: "http",
|
|
1706
1736
|
url: entry.url,
|
|
1707
1737
|
headers: escapeValues(entry.headers),
|
|
1708
|
-
exposure
|
|
1738
|
+
exposure,
|
|
1709
1739
|
};
|
|
1710
1740
|
}
|
|
1711
1741
|
}
|
|
@@ -1860,7 +1890,7 @@ export class PiMonoAdapter implements ProviderAdapter {
|
|
|
1860
1890
|
config.apiUrl && config.apiKey && config.agentId
|
|
1861
1891
|
? await fetchInstalledMcpServers(config.apiUrl, config.apiKey, config.agentId, "claude")
|
|
1862
1892
|
: null;
|
|
1863
|
-
const piMcpServers = toPiMcpServers(installedMcpServers);
|
|
1893
|
+
const piMcpServers = toPiMcpServers(installedMcpServers, { deferred: deferTools });
|
|
1864
1894
|
if (Object.keys(piMcpServers).length > 0) {
|
|
1865
1895
|
console.log(
|
|
1866
1896
|
`\x1b[2m[${config.role}]\x1b[0m Registering ${Object.keys(piMcpServers).length} installed MCP server(s) with pi`,
|
|
@@ -75,7 +75,7 @@ function resolveExecutorRegistry(): ExecutorRegistry | null {
|
|
|
75
75
|
* scripts-runtime — no agent/LLM in the loop. Reuses the same `runScriptWithRunCredential()`
|
|
76
76
|
* path as the `swarm-script` workflow executor.
|
|
77
77
|
*/
|
|
78
|
-
async function executeScheduleScript(schedule: ScheduledTask): Promise<void> {
|
|
78
|
+
export async function executeScheduleScript(schedule: ScheduledTask): Promise<void> {
|
|
79
79
|
if (!schedule.scriptName) {
|
|
80
80
|
throw new Error(`Schedule "${schedule.name}" has no scriptName (targetType=script)`);
|
|
81
81
|
}
|
|
@@ -106,6 +106,7 @@ async function executeScheduleScript(schedule: ScheduledTask): Promise<void> {
|
|
|
106
106
|
source: script.source,
|
|
107
107
|
args,
|
|
108
108
|
fsMode: "none",
|
|
109
|
+
executor: script.executor,
|
|
109
110
|
agentId,
|
|
110
111
|
egressSecrets: credentials.egressSecrets,
|
|
111
112
|
userConfig: credentials.userConfig,
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import { buildCtx } from "./ctx";
|
|
2
2
|
import { patchFetchWithEgressSubstitution } from "./egress-secrets";
|
|
3
3
|
import type { SwarmConfigPayload } from "./executors/types";
|
|
4
|
+
import { isHostValidationEquivalent } from "./host-validated-args";
|
|
4
5
|
import { ctxSignatureHintFor } from "./script-error-hints";
|
|
5
6
|
import { SwarmConfig } from "./swarm-config";
|
|
6
7
|
|
|
@@ -131,8 +132,14 @@ try {
|
|
|
131
132
|
);
|
|
132
133
|
}
|
|
133
134
|
|
|
135
|
+
const hostValidatedArgs = process.env.SWARM_SCRIPT_HOST_VALIDATED_ARGS === "1";
|
|
134
136
|
let validatedArgs = parsedArgs;
|
|
135
|
-
if (
|
|
137
|
+
if (
|
|
138
|
+
mod.argsSchema &&
|
|
139
|
+
typeof mod.argsSchema === "object" &&
|
|
140
|
+
"parse" in mod.argsSchema &&
|
|
141
|
+
!(hostValidatedArgs && isHostValidationEquivalent(mod.argsSchema, parsedArgs))
|
|
142
|
+
) {
|
|
136
143
|
try {
|
|
137
144
|
// biome-ignore lint/suspicious/noExplicitAny: argsSchema is a Zod schema at runtime
|
|
138
145
|
validatedArgs = (mod.argsSchema as any).parse(parsedArgs);
|