@desplega.ai/agent-swarm 2.7.5 → 2.7.6
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/{acp-adapter-g3ads55s.js → acp-adapter-bp91d37f.js} +4 -4
- package/dist/{actions-zwfykqqk.js → actions-yj0eeaqh.js} +18 -17
- package/dist/{amp-adapter-z3s0ssdt.js → amp-adapter-4pg9h34q.js} +1 -1
- package/dist/{app-vmp4fgep.js → app-909e97cr.js} +3 -3
- package/dist/{assistant-vwde653f.js → assistant-qdyb5fyy.js} +24 -22
- package/dist/{boot-reembed-375t7tbk.js → boot-reembed-fk0s9z18.js} +1 -1
- package/dist/{boot-reembed-gq9rcpnj.js → boot-reembed-gs1rzrnh.js} +1 -1
- package/dist/{boot-scrub-sweep-nd938kyf.js → boot-scrub-sweep-4qfd4kfx.js} +2 -2
- package/dist/{boot-seeds-aykx51v3.js → boot-seeds-yr00k13d.js} +4 -4
- package/dist/{boxd-k1j26g3h.js → boxd-vdmfssd9.js} +4 -4
- package/dist/{claude-adapter-8j4hm099.js → claude-adapter-qta1f1xn.js} +3 -3
- package/dist/{claude-managed-setup-0wyynpn1.js → claude-managed-setup-sc6j4dm0.js} +1 -1
- package/dist/{claude-sdk-session-q6wj5hmy.js → claude-sdk-session-zgrz6yvc.js} +9169 -9070
- package/dist/{cli-gjr2y2xk.js → cli-0agcv1v5.js} +4 -1
- package/dist/{cli-vqypxzh4.js → cli-0at8w34w.js} +7 -7
- package/dist/{cli-7a42nfy0.js → cli-0qatpy8q.js} +3 -3
- package/dist/{cli-p599f1qm.js → cli-0yv5z2pg.js} +1 -1
- package/dist/{cli-0gey5fzy.js → cli-386tyxxg.js} +2 -2
- package/dist/{cli-5pag6rmn.js → cli-3ypk9vk1.js} +1 -1
- package/dist/{cli-qxnf8z2z.js → cli-436532ck.js} +2 -2
- package/dist/{cli-z8eh3ex8.js → cli-4necwtsv.js} +4 -4
- package/dist/{cli-zh3fp8v2.js → cli-54t00gt8.js} +1 -1
- package/dist/{cli-hf6zt5m6.js → cli-69szn7w7.js} +17 -1
- package/dist/{cli-nn4d8dne.js → cli-89knwags.js} +2 -2
- package/dist/{cli-spknvxbq.js → cli-8bc1w0tw.js} +10 -10
- package/dist/{cli-rqkxrmgw.js → cli-8snvgs5r.js} +38 -13
- package/dist/{cli-rhw40jgb.js → cli-bjb3z51z.js} +2 -2
- package/dist/{cli-xtjhfmpc.js → cli-c60vetmm.js} +1 -1
- package/dist/{cli-0731hvbw.js → cli-decfb6v8.js} +2 -2
- package/dist/{cli-bhjt02p8.js → cli-drr5v6av.js} +1 -1
- package/dist/{cli-fqzx7gwa.js → cli-dxae2yg7.js} +2 -2
- package/dist/{cli-tqgg40ek.js → cli-e3r2neck.js} +8 -7
- package/dist/{cli-zppwg9p9.js → cli-ekwks2vs.js} +1 -1
- package/dist/{cli-nzxr556s.js → cli-es6va8jk.js} +26 -26
- package/dist/{cli-a47xy8eq.js → cli-faxe6thg.js} +1 -1
- package/dist/cli-gdhpcbhf.js +55 -0
- package/dist/{cli-82jg7fp9.js → cli-he5nxaa8.js} +1 -1
- package/dist/{cli-8veytvbh.js → cli-he9wchme.js} +1 -1
- package/dist/{cli-pywf3eh5.js → cli-hgaazk69.js} +1 -1
- package/dist/{cli-0bxqnvq9.js → cli-hr2e2fft.js} +28 -6
- package/dist/{cli-1ayk1eas.js → cli-k2gzwsj2.js} +135 -26
- package/dist/{cli-xbkdm710.js → cli-kqdg1bsb.js} +3 -3
- package/dist/cli-m2z8jagn.js +12078 -0
- package/dist/{cli-g61qxmge.js → cli-m48g6p95.js} +3 -3
- package/dist/{cli-7wspm5gm.js → cli-nhrvpaj7.js} +20 -4
- package/dist/{cli-afrrppsw.js → cli-nwj0ee2w.js} +2 -2
- package/dist/{cli-3h4nhjn8.js → cli-p5gp8pcx.js} +9 -7
- package/dist/{cli-rm6hqj3z.js → cli-pd7vagab.js} +33 -139
- package/dist/{cli-mjzynrse.js → cli-q0k1w5zd.js} +1 -1
- package/dist/{cli-8hs41zy2.js → cli-rmmmrjkd.js} +1 -1
- package/dist/{cli-kg8hchyg.js → cli-sa02jsqt.js} +1 -1
- package/dist/{cli-1338796d.js → cli-shpbx5ee.js} +142 -98
- package/dist/{cli-82ae98pz.js → cli-svj5090z.js} +2 -2
- package/dist/{cli-bnz7a7qf.js → cli-vvpx3gsx.js} +1 -1
- package/dist/{cli-mmwm7zbf.js → cli-w6dt9ged.js} +1 -1
- package/dist/{cli-wvaawacd.js → cli-waqbxg08.js} +5230 -66
- package/dist/{cli-j3jvnpe8.js → cli-wp9sp1qb.js} +1 -1
- package/dist/{cli-nmbmy64z.js → cli-xbbndvjy.js} +3 -3
- package/dist/{cli-vbyfwh92.js → cli-xds5f77d.js} +2 -2
- package/dist/{cli-7wcyk2s5.js → cli-xnqgpshy.js} +1 -1
- package/dist/{cli-rpm2sgv0.js → cli-xnrwazav.js} +19 -2
- package/dist/{cli-gf5hbmdr.js → cli-yjdgea32.js} +3 -3
- package/dist/{cli-mry0qzym.js → cli-yn4kxs06.js} +123 -10
- package/dist/{cli-hn9hxmz9.js → cli-yx4kfngt.js} +1 -1
- package/dist/{cli-y24sc29k.js → cli-yy3rhsdw.js} +30 -8
- package/dist/{cli-6s7vdcta.js → cli-zbbwez0r.js} +4 -4
- package/dist/{cli-z6jkx23q.js → cli-zm72anhg.js} +9 -9
- package/dist/cli.js +22 -21
- package/dist/{codex-adapter-q6k3gnyg.js → codex-adapter-ayd1fb6h.js} +2 -2
- package/dist/{codex-session-runner-3mwmk3d9.js → codex-session-runner-jesa32xc.js} +2 -2
- package/dist/{commands-evrenjn9.js → commands-hanewgf2.js} +9 -3
- package/dist/{controller-xjp8vswh.js → controller-dt1n3c1q.js} +8 -3
- package/dist/{controllers-connect-pp5jnxvc.js → controllers-connect-xx8x64n5.js} +20 -19
- package/dist/{cursor-adapter-yf47w4t0.js → cursor-adapter-9s4dwwdw.js} +1 -1
- package/dist/{db-zxpw6aht.js → db-3vttcbv4.js} +3 -3
- package/dist/{db-rgfsh91f.js → db-9dv0g1ab.js} +11 -3
- package/dist/{devtools-82fzqra8.js → devtools-qxfgbsqz.js} +1 -1
- package/dist/{dispatcher-smasgws7.js → dispatcher-tx9hd1sv.js} +17 -16
- package/dist/{docker-rrjhaqt9.js → docker-c1a3gqmf.js} +89 -22
- package/dist/{dsh-adapter-ftyy8dnp.js → dsh-adapter-007y4ejs.js} +1 -1
- package/dist/{e2b-qcaj675y.js → e2b-a19d28rz.js} +2 -2
- package/dist/{e2b-w1p0bnpm.js → e2b-we6hs6q5.js} +4 -4
- package/dist/{easter-egg-3d-yyts7p16.js → easter-egg-3d-rgbrqb8s.js} +1 -1
- package/dist/{embedding-space-1z0mbg95.js → embedding-space-ny1e2940.js} +6 -6
- package/dist/{embeddings-ygeg7tm7.js → embeddings-2t386t13.js} +1 -1
- package/dist/{execute-14qhqp4d.js → execute-pksh0ceh.js} +1 -1
- package/dist/{external-channel-ttgrs3sx.js → external-channel-phgmhmd7.js} +2 -2
- package/dist/{fx-adapter-gt6pncj2.js → fx-adapter-fy8rr921.js} +5 -5
- package/dist/{google-generative-ai-yvmdxyxa.js → google-generative-ai-8y3m3k4b.js} +1 -1
- package/dist/{google-vertex-jrtdh9gs.js → google-vertex-w3v98n7b.js} +1 -1
- package/dist/{grok-adapter-sm4m5k4g.js → grok-adapter-q0abvpaj.js} +5 -5
- package/dist/handlers-by3sfxyx.js +32 -0
- package/dist/{heartbeat-qahz4p62.js → heartbeat-kadyek8p.js} +20 -19
- package/dist/{hook-kj3d398m.js → hook-5zs3msf6.js} +2 -2
- package/dist/{hook-4fd5wq5w.js → hook-9k551xnj.js} +2 -2
- package/dist/{http-1ted0j1k.js → http-mhz9vw1f.js} +213 -80
- package/dist/index-4f2pvxae.js +37 -0
- package/dist/index-98gz7tmp.js +30 -0
- package/dist/{index-5rk6rfzr.js → index-fvzke5kx.js} +1 -1
- package/dist/index-k08z2cak.js +33 -0
- package/dist/{index-yqhz88yy.js → index-pxn30mse.js} +8 -8
- package/dist/{index-6rq3r5zb.js → index-q361cg2f.js} +1 -1
- package/dist/{index-7v51awbn.js → index-rvr00sg7.js} +20 -13
- package/dist/{index-q4b917zs.js → index-vdygbq10.js} +1 -1
- package/dist/{keepalive-6cf311t5.js → keepalive-w56f32cg.js} +3 -3
- package/dist/{lead-fjy418cw.js → lead-enq35nqp.js} +31 -30
- package/dist/{maintenance-a4bm1bmt.js → maintenance-7mdgxcea.js} +1 -1
- package/dist/{oauth-refresh-sweep-3h0dfw1k.js → oauth-refresh-sweep-29txy9vk.js} +2 -2
- package/dist/{onboard-myn5hraa.js → onboard-kpsrkarj.js} +4 -4
- package/dist/{opencode-adapter-p2nkb5am.js → opencode-adapter-j25vm3rr.js} +3 -3
- package/dist/{otel-impl-a1a0ef6b.js → otel-impl-6kkct7a8.js} +1 -1
- package/dist/{pi-mono-adapter-rzmpb72a.js → pi-mono-adapter-exhkzhn2.js} +4 -4
- package/dist/{pricing-refresh-83drrnxj.js → pricing-refresh-n2x7jh6k.js} +3 -3
- package/dist/{rbac-roles-s8y6ecb5.js → rbac-roles-81ypx6es.js} +1 -1
- package/dist/{rbac-roles-0fb6e7pj.js → rbac-roles-b5htkjby.js} +2 -2
- package/dist/{render-v2-twmbgrz4.js → render-v2-qn6jmpk4.js} +3 -3
- package/dist/{resume-zfqc36xj.js → resume-wd30wx23.js} +6 -6
- package/dist/{runtime-qwggdpqr.js → runtime-4hsg7h60.js} +1 -1
- package/dist/{session-access-sc9pn9da.js → session-access-5qvfevhk.js} +4 -4
- package/dist/{session-source-backfill-qh07xr8g.js → session-source-backfill-n1czavbr.js} +2 -2
- package/dist/{setup-4fr0m7a1.js → setup-dnb56qy5.js} +3 -3
- package/dist/{virtual-modules-fh4ha15b.js → virtual-modules-76xxv9fs.js} +2 -2
- package/dist/{work-objects-0wr8zxct.js → work-objects-4n21tfkm.js} +20 -19
- package/dist/{worker-follow-up-ktyyazyp.js → worker-follow-up-77hvzxww.js} +17 -16
- package/dist/{worker-0c5tyzqe.js → worker-gg1576j3.js} +31 -30
- package/openapi.json +93 -1
- package/package.json +8 -7
- package/src/be/db/script-runs.ts +23 -0
- package/src/be/db/tasks/read.ts +122 -3
- package/src/be/db-client.ts +8 -5
- package/src/be/db.ts +15 -4
- package/src/be/memory/providers/sqlite-store.ts +49 -12
- package/src/be/migrations/260_runtime_pool_cache.sql +20 -0
- package/src/be/migrations/262_session_visibility_indexes.sql +18 -0
- package/src/be/migrations/263_task_list_filter_index.sql +11 -0
- package/src/be/migrations/264_slack_thread_cover_index.sql +9 -0
- package/src/be/migrations/266_script_runs_source_task.sql +11 -0
- package/src/be/runtime-pools/cache-cleanup.ts +109 -0
- package/src/be/runtime-pools/db.ts +65 -6
- package/src/be/runtime-pools/remote-controller.ts +5 -0
- package/src/be/runtime-pools/supervisor.ts +33 -2
- package/src/be/scripts/typecheck.ts +22 -0
- package/src/be/seed-scripts/catalog/task-failure-audit.ts +5 -1
- package/src/commands/controller.ts +4 -0
- package/src/commands/entrypoint-api-readiness.ts +74 -0
- package/src/http/index.ts +1 -1
- package/src/http/list-total-cache.ts +14 -7
- package/src/http/memory.ts +38 -9
- package/src/http/script-runs.ts +174 -5
- package/src/http/status.ts +10 -2
- package/src/http/tasks.ts +4 -2
- package/src/providers/amp-adapter.ts +1 -1
- package/src/providers/fx-adapter.ts +1 -1
- package/src/runtime-providers/controller-protocol.ts +14 -1
- package/src/runtime-providers/controller.ts +5 -0
- package/src/runtime-providers/docker.ts +108 -23
- package/src/runtime-providers/types.ts +16 -0
- package/src/script-workflows/workflow-ctx.ts +8 -0
- package/src/scripts-runtime/types/stdlib.d.ts +22 -0
- package/src/scripts-runtime/types/swarm-sdk.d.ts +22 -0
- package/src/server-user.ts +1 -0
- package/src/slack/commands.ts +5 -1
- package/src/slack/handlers.ts +37 -2
- package/src/slack/left-threads.ts +27 -4
- package/src/tasks/follow-up-redelegation-guard.ts +155 -0
- package/src/tests/claude-artifacts-read-script.test.ts +209 -0
- package/src/tests/db-client.test.ts +21 -0
- package/src/tests/db-task-read-contract.test.ts +8 -2
- package/src/tests/db-task-write-contract.test.ts +5 -1
- package/src/tests/entrypoint-api-readiness.test.ts +416 -210
- package/src/tests/explicit-model-validation.test.ts +14 -8
- package/src/tests/follow-up-redelivery-guard.test.ts +432 -202
- package/src/tests/list-total-cache.test.ts +27 -2
- package/src/tests/memory-fts-populate.test.ts +11 -9
- package/src/tests/memory-retrieval-purge.test.ts +80 -0
- package/src/tests/migration-260-runtime-pool-cache.test.ts +118 -0
- package/src/tests/query-audit-plans.test.ts +27 -0
- package/src/tests/runtime-cache-cleanup.test.ts +367 -0
- package/src/tests/runtime-e2b-pools.test.ts +41 -0
- package/src/tests/runtime-pools-db.test.ts +98 -0
- package/src/tests/runtime-pools-http.test.ts +29 -0
- package/src/tests/runtime-providers-docker.test.ts +253 -0
- package/src/tests/runtime-remote-controller.test.ts +66 -2
- package/src/tests/runtime-supervisor.test.ts +66 -2
- package/src/tests/script-connections.test.ts +11 -0
- package/src/tests/script-runs-http.test.ts +684 -2
- package/src/tests/seed-scripts.test.ts +26 -0
- package/src/tests/slack-leave-thread.test.ts +145 -5
- package/src/tests/slack-render-v2-query-plans.test.ts +24 -2
- package/src/tests/status.test.ts +11 -0
- package/src/tests/tasks-readable-row.test.ts +155 -0
- package/src/tools/send-task.ts +35 -47
- package/src/tools/slack-leave-thread.ts +1 -1
- package/src/tools/task-dedup.ts +45 -21
- package/src/types.ts +38 -0
- package/templates/skills/composio-google-docs/content.md +10 -0
- package/templates/skills/script-workflows/content.md +19 -1
- package/templates/skills/swarm-docs/content.md +1 -0
- package/templates/skills/swarm-scripts/SKILL.md +2 -0
- package/templates/skills/swarm-scripts/content.md +2 -0
- package/dist/cli-dznrgaep.js +0 -30
- package/dist/cli-w3hcrxht.js +0 -6985
- package/dist/handlers-vwmba1m2.js +0 -31
- package/dist/index-5r71rf98.js +0 -32
- package/dist/index-anmwxs87.js +0 -29
- package/dist/index-spp0ra5c.js +0 -36
|
@@ -15,6 +15,10 @@ async function executeCommand(
|
|
|
15
15
|
command: ControllerCommand,
|
|
16
16
|
): Promise<unknown> {
|
|
17
17
|
switch (command.op) {
|
|
18
|
+
case "release-cache":
|
|
19
|
+
if (!controller.releaseCache) throw new Error("Controller does not support cache cleanup");
|
|
20
|
+
await controller.releaseCache(command.payload);
|
|
21
|
+
return {};
|
|
18
22
|
case "provision":
|
|
19
23
|
return await controller.provision(command.payload);
|
|
20
24
|
case "terminate":
|
|
@@ -0,0 +1,74 @@
|
|
|
1
|
+
/** Worker boot step; deliberately standalone so both worker images can run it before setup. */
|
|
2
|
+
export interface ApiReadinessDependencies {
|
|
3
|
+
fetch: (url: string, init: RequestInit) => Promise<Response>;
|
|
4
|
+
now: () => number;
|
|
5
|
+
sleep: (ms: number) => Promise<void>;
|
|
6
|
+
log: (message: string) => void;
|
|
7
|
+
}
|
|
8
|
+
|
|
9
|
+
const defaults: ApiReadinessDependencies = {
|
|
10
|
+
fetch: (url, init) => fetch(url, init),
|
|
11
|
+
now: () => performance.now(),
|
|
12
|
+
sleep: (ms) => Bun.sleep(ms),
|
|
13
|
+
log: (message) => console.log(message),
|
|
14
|
+
};
|
|
15
|
+
|
|
16
|
+
/** Poll the unauthenticated health endpoint before any provider reads configuration. */
|
|
17
|
+
export async function waitForApiReady(
|
|
18
|
+
baseUrl: string,
|
|
19
|
+
timeoutValue = "90",
|
|
20
|
+
dependencies: Partial<ApiReadinessDependencies> = {},
|
|
21
|
+
): Promise<boolean> {
|
|
22
|
+
const { fetch: probe, now, sleep, log } = { ...defaults, ...dependencies };
|
|
23
|
+
const raw = timeoutValue || "90";
|
|
24
|
+
const seconds = Number(raw);
|
|
25
|
+
if (!/^\d+$/.test(raw) || !Number.isSafeInteger(seconds) || seconds <= 0) {
|
|
26
|
+
log(
|
|
27
|
+
`[entrypoint] FATAL: WORKER_API_READY_TIMEOUT_SECONDS must be a positive integer, got '${raw}'; exiting.`,
|
|
28
|
+
);
|
|
29
|
+
return false;
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
const url = `${baseUrl.replace(/\/$/, "")}/health`;
|
|
33
|
+
const deadline = now() + seconds * 1000;
|
|
34
|
+
log(`[entrypoint] Waiting for API readiness at ${url} (timeout ${raw}s)...`);
|
|
35
|
+
while (true) {
|
|
36
|
+
const controller = new AbortController();
|
|
37
|
+
const timer = setTimeout(
|
|
38
|
+
() => controller.abort(),
|
|
39
|
+
Math.min(3000, Math.max(1, deadline - now())),
|
|
40
|
+
);
|
|
41
|
+
try {
|
|
42
|
+
const response = await probe(url, { redirect: "manual", signal: controller.signal });
|
|
43
|
+
// curl -o /dev/null waited for the whole response, without retaining its body.
|
|
44
|
+
if (response.body) {
|
|
45
|
+
for await (const _chunk of response.body) {
|
|
46
|
+
// Discard health output, including error bodies, within the request timeout.
|
|
47
|
+
}
|
|
48
|
+
}
|
|
49
|
+
// Match curl -f without -L: redirects are successful and are not followed.
|
|
50
|
+
if (response.status > 0 && response.status < 400 && now() <= deadline) {
|
|
51
|
+
log(`[entrypoint] API ready at ${url}`);
|
|
52
|
+
return true;
|
|
53
|
+
}
|
|
54
|
+
} catch {
|
|
55
|
+
// The API can still be booting; retry connection, HTTP and timeout failures alike.
|
|
56
|
+
} finally {
|
|
57
|
+
clearTimeout(timer);
|
|
58
|
+
}
|
|
59
|
+
if (now() >= deadline) {
|
|
60
|
+
log(`[entrypoint] FATAL: API readiness timed out after ${raw}s waiting for ${url}; exiting.`);
|
|
61
|
+
return false;
|
|
62
|
+
}
|
|
63
|
+
await sleep(Math.min(1000, deadline - now()));
|
|
64
|
+
}
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
if (import.meta.main) {
|
|
68
|
+
process.exitCode = (await waitForApiReady(
|
|
69
|
+
Bun.argv[2] ?? "http://host.docker.internal:3013",
|
|
70
|
+
Bun.argv[3],
|
|
71
|
+
))
|
|
72
|
+
? 0
|
|
73
|
+
: 1;
|
|
74
|
+
}
|
package/src/http/index.ts
CHANGED
|
@@ -877,7 +877,7 @@ httpServer
|
|
|
877
877
|
// Delete expired MCP overflow values (10-min tick, off the spill path)
|
|
878
878
|
startMcpOverflowSweep();
|
|
879
879
|
|
|
880
|
-
// Start expired-memory garbage collector (1-hour tick,
|
|
880
|
+
// Start expired-memory garbage collector (1-hour tick, first run 2 min after boot)
|
|
881
881
|
await startMemoryGc();
|
|
882
882
|
|
|
883
883
|
// Start the opt-in DB retention sweep after config hydration. Every key is
|
|
@@ -3,8 +3,11 @@
|
|
|
3
3
|
*
|
|
4
4
|
* A list total is a COUNT over the same filters as the page, and on the large
|
|
5
5
|
* tables behind the sessions and task lists it costs as much as the page
|
|
6
|
-
* itself.
|
|
7
|
-
* triage move) shows at once where the user looks first.
|
|
6
|
+
* itself. By default page 1 (offset 0) always counts, so a write (a new
|
|
7
|
+
* session, a triage move) shows at once where the user looks first. A list
|
|
8
|
+
* whose count is expensive and whose total may lag can pass
|
|
9
|
+
* `firstPageTtlMs`: page 1 then reuses a count younger than that, so
|
|
10
|
+
* dashboard tabs that poll the same list share one count. Later pages reuse
|
|
8
11
|
* that count for 30 s. A cold later page (deep link, expired entry) counts
|
|
9
12
|
* too, so `total` is always set.
|
|
10
13
|
*/
|
|
@@ -13,7 +16,7 @@ export const LIST_TOTAL_TTL_MS = 30_000;
|
|
|
13
16
|
const MAX_ENTRIES = 500;
|
|
14
17
|
|
|
15
18
|
interface Entry {
|
|
16
|
-
|
|
19
|
+
countedAt: number;
|
|
17
20
|
total: Promise<number>;
|
|
18
21
|
}
|
|
19
22
|
|
|
@@ -34,19 +37,23 @@ function filterKey(value: unknown): string {
|
|
|
34
37
|
);
|
|
35
38
|
}
|
|
36
39
|
|
|
37
|
-
export function createListTotalCache(
|
|
40
|
+
export function createListTotalCache(
|
|
41
|
+
ttlMs = LIST_TOTAL_TTL_MS,
|
|
42
|
+
firstPageTtlMs = 0,
|
|
43
|
+
): ListTotalCache {
|
|
38
44
|
const entries = new Map<string, Entry>();
|
|
39
45
|
return {
|
|
40
46
|
get(filters, offset, compute) {
|
|
41
47
|
const key = filterKey(filters);
|
|
42
48
|
const now = Date.now();
|
|
43
49
|
const hit = entries.get(key);
|
|
44
|
-
|
|
50
|
+
const maxAge = (offset ?? 0) > 0 ? ttlMs : Math.min(ttlMs, firstPageTtlMs);
|
|
51
|
+
if (hit && hit.countedAt + maxAge > now) return hit.total;
|
|
45
52
|
if (hit) entries.delete(key);
|
|
46
53
|
|
|
47
|
-
// Cache the promise, so a
|
|
54
|
+
// Cache the promise, so a request that arrives mid-count shares it.
|
|
48
55
|
const total = compute();
|
|
49
|
-
entries.set(key, {
|
|
56
|
+
entries.set(key, { countedAt: now, total });
|
|
50
57
|
total.catch(() => {
|
|
51
58
|
if (entries.get(key)?.total === total) entries.delete(key);
|
|
52
59
|
});
|
package/src/http/memory.ts
CHANGED
|
@@ -1568,17 +1568,31 @@ const MEMORY_GC_INTERVAL_MS = 60 * 60 * 1000; // 1 hour
|
|
|
1568
1568
|
let memoryGcTimer: ReturnType<typeof setInterval> | null = null;
|
|
1569
1569
|
|
|
1570
1570
|
const SEARCH_RETRIEVAL_TTL_DAYS = 90;
|
|
1571
|
+
/** Rows per purge statement. The purge yields the event loop between batches. */
|
|
1572
|
+
const SEARCH_RETRIEVAL_PURGE_BATCH = 2000;
|
|
1571
1573
|
|
|
1572
|
-
async function purgeStaleSearchRetrievals(
|
|
1574
|
+
export async function purgeStaleSearchRetrievals(
|
|
1575
|
+
batchSize = SEARCH_RETRIEVAL_PURGE_BATCH,
|
|
1576
|
+
): Promise<number> {
|
|
1573
1577
|
try {
|
|
1574
1578
|
const cutoff = new Date(
|
|
1575
1579
|
Date.now() - SEARCH_RETRIEVAL_TTL_DAYS * 24 * 60 * 60 * 1000,
|
|
1576
1580
|
).toISOString();
|
|
1577
|
-
|
|
1578
|
-
|
|
1579
|
-
|
|
1580
|
-
|
|
1581
|
-
|
|
1581
|
+
let purged = 0;
|
|
1582
|
+
for (;;) {
|
|
1583
|
+
// `+eventType` keeps the planner on the retrievedAt range. Without
|
|
1584
|
+
// sqlite_stat1 it prefers the eventType equality, which reads every
|
|
1585
|
+
// search row to test its date, even when nothing is stale.
|
|
1586
|
+
const result = await getDbClient().run(
|
|
1587
|
+
`DELETE FROM memory_retrieval WHERE rowid IN (
|
|
1588
|
+
SELECT rowid FROM memory_retrieval
|
|
1589
|
+
WHERE +eventType = 'search' AND retrievedAt < ? LIMIT ?)`,
|
|
1590
|
+
[cutoff, batchSize],
|
|
1591
|
+
);
|
|
1592
|
+
purged += result.changes;
|
|
1593
|
+
if (result.changes < batchSize) return purged;
|
|
1594
|
+
await new Promise<void>((resolve) => setImmediate(resolve));
|
|
1595
|
+
}
|
|
1582
1596
|
} catch (err) {
|
|
1583
1597
|
console.error("[memory-gc] Search retrieval purge failed:", (err as Error).message);
|
|
1584
1598
|
return 0;
|
|
@@ -1602,11 +1616,22 @@ async function runMemoryGcTick(label: "Initial" | "Periodic"): Promise<void> {
|
|
|
1602
1616
|
}
|
|
1603
1617
|
}
|
|
1604
1618
|
|
|
1605
|
-
|
|
1619
|
+
/** The first purge waits this long, so it does not compete with boot work. */
|
|
1620
|
+
const MEMORY_GC_INITIAL_DELAY_MS = 2 * 60 * 1000;
|
|
1621
|
+
let memoryGcInitialTimer: ReturnType<typeof setTimeout> | null = null;
|
|
1622
|
+
|
|
1623
|
+
export async function startMemoryGc(
|
|
1624
|
+
intervalMs = MEMORY_GC_INTERVAL_MS,
|
|
1625
|
+
initialDelayMs = MEMORY_GC_INITIAL_DELAY_MS,
|
|
1626
|
+
): Promise<void> {
|
|
1606
1627
|
if (memoryGcTimer) return;
|
|
1607
1628
|
|
|
1608
|
-
//
|
|
1609
|
-
|
|
1629
|
+
// Clear any backlog soon after startup, but off the boot path.
|
|
1630
|
+
memoryGcInitialTimer = setTimeout(() => {
|
|
1631
|
+
memoryGcInitialTimer = null;
|
|
1632
|
+
void runMemoryGcTick("Initial");
|
|
1633
|
+
}, initialDelayMs);
|
|
1634
|
+
if (typeof memoryGcInitialTimer?.unref === "function") memoryGcInitialTimer.unref();
|
|
1610
1635
|
|
|
1611
1636
|
memoryGcTimer = setInterval(() => {
|
|
1612
1637
|
void runMemoryGcTick("Periodic");
|
|
@@ -1615,6 +1640,10 @@ export async function startMemoryGc(intervalMs = MEMORY_GC_INTERVAL_MS): Promise
|
|
|
1615
1640
|
}
|
|
1616
1641
|
|
|
1617
1642
|
export function stopMemoryGc(): void {
|
|
1643
|
+
if (memoryGcInitialTimer) {
|
|
1644
|
+
clearTimeout(memoryGcInitialTimer);
|
|
1645
|
+
memoryGcInitialTimer = null;
|
|
1646
|
+
}
|
|
1618
1647
|
if (memoryGcTimer) {
|
|
1619
1648
|
clearInterval(memoryGcTimer);
|
|
1620
1649
|
memoryGcTimer = null;
|
package/src/http/script-runs.ts
CHANGED
|
@@ -13,10 +13,12 @@ import {
|
|
|
13
13
|
getLatestScriptRunStepTaskByContextKey,
|
|
14
14
|
getScriptRun,
|
|
15
15
|
getScriptRunByIdempotencyKey,
|
|
16
|
+
getScriptRunFollowUpGuard,
|
|
16
17
|
getScriptRunJournalStep,
|
|
17
18
|
getTaskById,
|
|
18
19
|
listScriptRunJournalSteps,
|
|
19
20
|
listScriptRuns,
|
|
21
|
+
setScriptRunFollowUpGuard,
|
|
20
22
|
updateScriptRun,
|
|
21
23
|
updateScriptRunIfNotTerminal,
|
|
22
24
|
updateScriptRunIfRunning,
|
|
@@ -24,6 +26,7 @@ import {
|
|
|
24
26
|
} from "../be/db";
|
|
25
27
|
import { explicitModelErrorForAgent } from "../be/model-validation";
|
|
26
28
|
import { refuseSourceWithSecrets } from "../be/scripts/source-secrets";
|
|
29
|
+
import { can } from "../rbac";
|
|
27
30
|
import { lintWorkflowLabels } from "../script-workflows/label-lint";
|
|
28
31
|
import { scriptRunMaxAgentTasks, scriptRunMaxSteps } from "../script-workflows/limits";
|
|
29
32
|
import {
|
|
@@ -32,6 +35,12 @@ import {
|
|
|
32
35
|
terminateScriptRunProcess,
|
|
33
36
|
} from "../script-workflows/supervisor";
|
|
34
37
|
import {
|
|
38
|
+
evaluateFollowUpRedelegation,
|
|
39
|
+
type FollowUpRedelegationBlock,
|
|
40
|
+
followUpThreadForSourceTask,
|
|
41
|
+
} from "../tasks/follow-up-redelegation-guard";
|
|
42
|
+
import {
|
|
43
|
+
type AgentTask,
|
|
35
44
|
AgentTaskStatusSchema,
|
|
36
45
|
RoutingReasonSchema,
|
|
37
46
|
ScriptRunJournalEntrySchema,
|
|
@@ -111,6 +120,8 @@ const agentTaskBodySchema = z.object({
|
|
|
111
120
|
parentTaskId: z.string().uuid().optional(),
|
|
112
121
|
requestedByUserId: z.string().optional(),
|
|
113
122
|
outputSchema: z.record(z.string(), z.unknown()).optional(),
|
|
123
|
+
/** Dispatch even when the follow-up re-delegation guard blocks it (same flag as send-task). */
|
|
124
|
+
allowRedelegation: z.boolean().optional(),
|
|
114
125
|
});
|
|
115
126
|
|
|
116
127
|
const scriptRunCreatedSchema = z.object({
|
|
@@ -298,10 +309,70 @@ const agentTaskRoute = route({
|
|
|
298
309
|
description: "Agent task created or still running",
|
|
299
310
|
schema: z.object({ taskId: z.string(), status: AgentTaskStatusSchema }),
|
|
300
311
|
},
|
|
312
|
+
403: {
|
|
313
|
+
description:
|
|
314
|
+
"Script run belongs to another agent, or the run's agent may not create a child task under parentTaskId",
|
|
315
|
+
},
|
|
301
316
|
404: { description: "Script run not found" },
|
|
317
|
+
409: { description: "Agent task failed, was cancelled, or was superseded" },
|
|
318
|
+
422: { description: "Blocked by the follow-up re-delegation guard" },
|
|
302
319
|
},
|
|
303
320
|
});
|
|
304
321
|
|
|
322
|
+
/**
|
|
323
|
+
* Send-task's task.parent.attach decision for a `ctx.step.agentTask` step: a
|
|
324
|
+
* step inherits its parent's contextKey (its Slack or chat thread) and
|
|
325
|
+
* requester, so the run's agent needs the right to hold a child under it.
|
|
326
|
+
* Returns the 403 message, or null when allowed. A parent id with no task row
|
|
327
|
+
* passes: a step under it inherits nothing, as on send-task.
|
|
328
|
+
*/
|
|
329
|
+
async function stepParentRefusal(runAgentId: string, parentTaskId: string): Promise<string | null> {
|
|
330
|
+
const parentTask = await getTaskById(parentTaskId);
|
|
331
|
+
if (!parentTask) return null;
|
|
332
|
+
const runAgent = await getAgentById(runAgentId);
|
|
333
|
+
const decision = can({
|
|
334
|
+
principal: { kind: "agent", agentId: runAgentId, isLead: runAgent?.isLead ?? false },
|
|
335
|
+
verb: "task.parent.attach",
|
|
336
|
+
resource: {
|
|
337
|
+
kind: "task",
|
|
338
|
+
taskId: parentTask.id,
|
|
339
|
+
requestedByUserId: parentTask.requestedByUserId,
|
|
340
|
+
creatorAgentId: parentTask.creatorAgentId,
|
|
341
|
+
agentId: parentTask.agentId,
|
|
342
|
+
},
|
|
343
|
+
source: "http",
|
|
344
|
+
});
|
|
345
|
+
return decision.allow
|
|
346
|
+
? null
|
|
347
|
+
: `You don't have context for parent task ${parentTaskId}. Only its assignee, its creator, or a lead can create a child task under it.`;
|
|
348
|
+
}
|
|
349
|
+
|
|
350
|
+
/**
|
|
351
|
+
* The launching session's task (X-Source-Task-Id), kept only when it belongs
|
|
352
|
+
* to the launching agent so a run cannot borrow another agent's session.
|
|
353
|
+
*/
|
|
354
|
+
async function resolveRunSourceTaskId(
|
|
355
|
+
req: IncomingMessage,
|
|
356
|
+
agentId: string,
|
|
357
|
+
): Promise<string | undefined> {
|
|
358
|
+
const raw = req.headers["x-source-task-id"];
|
|
359
|
+
const sourceTaskId = Array.isArray(raw) ? raw[0] : raw;
|
|
360
|
+
if (!sourceTaskId) return undefined;
|
|
361
|
+
const task = await getTaskById(sourceTaskId);
|
|
362
|
+
return task?.agentId === agentId ? task.id : undefined;
|
|
363
|
+
}
|
|
364
|
+
|
|
365
|
+
/** One `ctx.step.agentTask` dispatch: the step task, or the guard rule that blocked it. */
|
|
366
|
+
type StepDispatch =
|
|
367
|
+
| {
|
|
368
|
+
task: AgentTask;
|
|
369
|
+
/** True when this call inserted the step, false on a replay. */
|
|
370
|
+
created?: boolean;
|
|
371
|
+
blocked?: undefined;
|
|
372
|
+
override?: FollowUpRedelegationBlock["rule"];
|
|
373
|
+
}
|
|
374
|
+
| { task?: undefined; blocked: FollowUpRedelegationBlock; override?: undefined };
|
|
375
|
+
|
|
305
376
|
async function requireAgent(res: ServerResponse, agentId: string | undefined) {
|
|
306
377
|
if (!agentId) {
|
|
307
378
|
jsonError(res, "X-Agent-ID required for script runs API", 400);
|
|
@@ -408,6 +479,7 @@ export async function handleScriptRuns(
|
|
|
408
479
|
// Cap check and insert commit together: N concurrent POSTs otherwise all
|
|
409
480
|
// read cap-1 active runs and all insert, exceeding the cap.
|
|
410
481
|
const cap = scriptRunConcurrencyCap();
|
|
482
|
+
const sourceTaskId = await resolveRunSourceTaskId(req, agent.id);
|
|
411
483
|
const creation = await getDbClient().transaction(async () => {
|
|
412
484
|
if ((await countActiveScriptRuns()) >= cap) return null;
|
|
413
485
|
return await createScriptRun({
|
|
@@ -418,6 +490,7 @@ export async function handleScriptRuns(
|
|
|
418
490
|
scriptName: parsed.body.scriptName,
|
|
419
491
|
idempotencyKey: parsed.body.idempotencyKey,
|
|
420
492
|
requestedByUserId: parsed.body.requestedByUserId,
|
|
493
|
+
sourceTaskId,
|
|
421
494
|
createdBy: parsed.body.requestedByUserId,
|
|
422
495
|
});
|
|
423
496
|
});
|
|
@@ -629,9 +702,29 @@ export async function handleScriptRuns(
|
|
|
629
702
|
return true;
|
|
630
703
|
}
|
|
631
704
|
|
|
705
|
+
// Only the run's own harness dispatches its steps (it sends the run's
|
|
706
|
+
// agent id). Step tasks carry that agent as creator, so another agent
|
|
707
|
+
// must not create them.
|
|
708
|
+
if (agentId !== run.agentId) {
|
|
709
|
+
jsonError(res, "Script run belongs to another agent", 403);
|
|
710
|
+
return true;
|
|
711
|
+
}
|
|
712
|
+
|
|
713
|
+
// The requested parent, checked before any lookup or insert so a refused
|
|
714
|
+
// call creates nothing.
|
|
715
|
+
if (parsed.body.parentTaskId) {
|
|
716
|
+
const refusal = await stepParentRefusal(run.agentId, parsed.body.parentTaskId);
|
|
717
|
+
if (refusal) {
|
|
718
|
+
jsonError(res, refusal, 403);
|
|
719
|
+
return true;
|
|
720
|
+
}
|
|
721
|
+
}
|
|
722
|
+
|
|
632
723
|
const contextKey = `script-run:${run.id}:${parsed.body.stepKey}`;
|
|
633
724
|
let task = await getLatestScriptRunStepTaskByContextKey(contextKey);
|
|
725
|
+
let dispatchedHere = false;
|
|
634
726
|
if (!task) {
|
|
727
|
+
const taskDescription = parsed.body.template ?? parsed.body.task ?? parsed.body.stepKey;
|
|
635
728
|
const modelError = await explicitModelErrorForAgent({
|
|
636
729
|
model: parsed.body.model,
|
|
637
730
|
allowCustomModel: parsed.body.allowCustomModel,
|
|
@@ -641,10 +734,47 @@ export async function handleScriptRuns(
|
|
|
641
734
|
jsonError(res, modelError, 400);
|
|
642
735
|
return true;
|
|
643
736
|
}
|
|
737
|
+
const followUpThread = await followUpThreadForSourceTask(run.sourceTaskId);
|
|
738
|
+
// The replay lookup, the guard decision and the insert share one write
|
|
739
|
+
// transaction. It serializes this step against the run's other steps,
|
|
740
|
+
// other runs and send-task dispatches into the same thread.
|
|
741
|
+
let dispatched: StepDispatch;
|
|
644
742
|
try {
|
|
645
|
-
|
|
646
|
-
|
|
647
|
-
{
|
|
743
|
+
dispatched = await getDbClient().transaction(async (): Promise<StepDispatch> => {
|
|
744
|
+
const replayed = await getLatestScriptRunStepTaskByContextKey(contextKey);
|
|
745
|
+
if (replayed) return { task: replayed };
|
|
746
|
+
|
|
747
|
+
// Follow-up re-delegation guard, as in send-task. A run is one
|
|
748
|
+
// dispatch: its first new step decides for the whole run, and every
|
|
749
|
+
// later step follows that decision. Later steps never compare against
|
|
750
|
+
// the run's own completed steps, a long run cannot use up the thread's
|
|
751
|
+
// loop cap, and parallel steps are dispatched all or none.
|
|
752
|
+
let override: FollowUpRedelegationBlock["rule"] | undefined;
|
|
753
|
+
if (followUpThread) {
|
|
754
|
+
const decision = await getScriptRunFollowUpGuard(run.id);
|
|
755
|
+
if (decision !== "passed") {
|
|
756
|
+
const blocked: FollowUpRedelegationBlock | null =
|
|
757
|
+
decision === null
|
|
758
|
+
? await evaluateFollowUpRedelegation({
|
|
759
|
+
...followUpThread,
|
|
760
|
+
creatorAgentId: run.agentId,
|
|
761
|
+
taskDescription,
|
|
762
|
+
targetAgentId: parsed.body.agentId,
|
|
763
|
+
})
|
|
764
|
+
: {
|
|
765
|
+
rule: decision,
|
|
766
|
+
message: `Blocked by the follow-up re-delegation guard (${decision} rule): an earlier step of this script run was blocked, and a run counts as one dispatch. If it must run, pass allowRedelegation: true.`,
|
|
767
|
+
};
|
|
768
|
+
if (blocked && !parsed.body.allowRedelegation) {
|
|
769
|
+
if (decision === null) await setScriptRunFollowUpGuard(run.id, blocked.rule);
|
|
770
|
+
return { blocked };
|
|
771
|
+
}
|
|
772
|
+
override = blocked?.rule;
|
|
773
|
+
await setScriptRunFollowUpGuard(run.id, "passed");
|
|
774
|
+
}
|
|
775
|
+
}
|
|
776
|
+
|
|
777
|
+
const created = await createTaskExtended(taskDescription, {
|
|
648
778
|
agentId: parsed.body.agentId,
|
|
649
779
|
routingReason:
|
|
650
780
|
parsed.body.routingReason ?? (parsed.body.agentId ? "human_pinned" : undefined),
|
|
@@ -660,6 +790,20 @@ export async function handleScriptRuns(
|
|
|
660
790
|
offeredTo: parsed.body.offerMode ? parsed.body.agentId : undefined,
|
|
661
791
|
taskType: "script-run-step",
|
|
662
792
|
source: "mcp",
|
|
793
|
+
// Counts the run toward the launcher's follow-up loop cap.
|
|
794
|
+
creatorAgentId: run.agentId,
|
|
795
|
+
// With no parent, the step inherits the launching session's Slack
|
|
796
|
+
// thread, as a send-task from that session does.
|
|
797
|
+
sourceTaskId: run.sourceTaskId,
|
|
798
|
+
// A guarded run's steps are its dispatches into the follow-up's
|
|
799
|
+
// thread, whatever their parent, so the guard sees them there.
|
|
800
|
+
...(followUpThread
|
|
801
|
+
? {
|
|
802
|
+
slackChannelId: followUpThread.channelId,
|
|
803
|
+
slackThreadTs: followUpThread.threadTs,
|
|
804
|
+
slackUserId: followUpThread.userId,
|
|
805
|
+
}
|
|
806
|
+
: {}),
|
|
663
807
|
dir: parsed.body.dir,
|
|
664
808
|
vcsRepo: parsed.body.vcsRepo,
|
|
665
809
|
model: parsed.body.model,
|
|
@@ -667,8 +811,9 @@ export async function handleScriptRuns(
|
|
|
667
811
|
requestedByUserId: parsed.body.requestedByUserId ?? run.requestedByUserId,
|
|
668
812
|
outputSchema: parsed.body.outputSchema,
|
|
669
813
|
contextKey,
|
|
670
|
-
}
|
|
671
|
-
|
|
814
|
+
});
|
|
815
|
+
return { task: created, created: true, override };
|
|
816
|
+
});
|
|
672
817
|
} catch (error) {
|
|
673
818
|
if (error instanceof ExtensionAgentAssignmentError) {
|
|
674
819
|
jsonError(res, error.message, 400);
|
|
@@ -676,6 +821,30 @@ export async function handleScriptRuns(
|
|
|
676
821
|
}
|
|
677
822
|
throw error;
|
|
678
823
|
}
|
|
824
|
+
if (dispatched.override) {
|
|
825
|
+
console.log(
|
|
826
|
+
`[script-runs] follow-up re-delegation override: rule=${dispatched.override} runId=${run.id} creatorAgentId=${run.agentId} sourceTaskId=${run.sourceTaskId} thread=${followUpThread?.channelId}/${followUpThread?.threadTs}`,
|
|
827
|
+
);
|
|
828
|
+
}
|
|
829
|
+
if (!dispatched.task) {
|
|
830
|
+
json(res, { error: dispatched.blocked.message, rule: dispatched.blocked.rule }, 422);
|
|
831
|
+
return true;
|
|
832
|
+
}
|
|
833
|
+
task = dispatched.task;
|
|
834
|
+
dispatchedHere = dispatched.created === true;
|
|
835
|
+
}
|
|
836
|
+
|
|
837
|
+
// A replay answers from the selected step, whatever parent this call sent
|
|
838
|
+
// (or omitted), so authorize the parent the step actually holds. Every
|
|
839
|
+
// replay lookup above lands here before any output is returned. A step this
|
|
840
|
+
// call just dispatched was authorized above; refusing it now would leave
|
|
841
|
+
// the work running behind a 403.
|
|
842
|
+
if (!dispatchedHere && task.parentTaskId) {
|
|
843
|
+
const refusal = await stepParentRefusal(run.agentId, task.parentTaskId);
|
|
844
|
+
if (refusal) {
|
|
845
|
+
jsonError(res, refusal, 403);
|
|
846
|
+
return true;
|
|
847
|
+
}
|
|
679
848
|
}
|
|
680
849
|
|
|
681
850
|
const deadline = Date.now() + 30_000;
|
package/src/http/status.ts
CHANGED
|
@@ -45,6 +45,7 @@ import { getSlackConnectionState } from "../slack/connection-state";
|
|
|
45
45
|
import { isTelemetryEnabled } from "../telemetry";
|
|
46
46
|
import { type AgentCredStatus, AutomationIntegrationIdSchema, ProviderNameSchema } from "../types";
|
|
47
47
|
import { isRuntimePoolsEnabled } from "../utils/runtime-pools-flag";
|
|
48
|
+
import { resolveAppUrl } from "./config-values";
|
|
48
49
|
import { route } from "./route-def";
|
|
49
50
|
import { json, jsonError } from "./utils";
|
|
50
51
|
|
|
@@ -116,6 +117,12 @@ export const StatusIdentitySchema = z.object({
|
|
|
116
117
|
* cloud installs can be sliced server-side.
|
|
117
118
|
*/
|
|
118
119
|
org_id: z.string().nullable(),
|
|
120
|
+
/**
|
|
121
|
+
* The swarm's public dashboard URL (`APP_URL`, then `DASHBOARD_URL`, with
|
|
122
|
+
* swarm_config precedence). Null when neither is set. The dashboard reads
|
|
123
|
+
* links to it in agent output as its own. Absent on older APIs.
|
|
124
|
+
*/
|
|
125
|
+
dashboard_url: z.string().nullable().optional(),
|
|
119
126
|
});
|
|
120
127
|
export type StatusIdentity = z.infer<typeof StatusIdentitySchema>;
|
|
121
128
|
|
|
@@ -334,7 +341,7 @@ export async function rollupCredStatusForProvider(provider: string): Promise<Cre
|
|
|
334
341
|
|
|
335
342
|
// ─── Identity ────────────────────────────────────────────────────────────────
|
|
336
343
|
|
|
337
|
-
function buildIdentity(): StatusIdentity {
|
|
344
|
+
function buildIdentity(dashboardUrl: string | null): StatusIdentity {
|
|
338
345
|
const cloudRaw = process.env.SWARM_CLOUD;
|
|
339
346
|
const hideRaw = process.env.SWARM_HIDE_CLOUD_PROMO;
|
|
340
347
|
return {
|
|
@@ -345,6 +352,7 @@ function buildIdentity(): StatusIdentity {
|
|
|
345
352
|
marketing_url: process.env.SWARM_MARKETING_URL?.trim() || null,
|
|
346
353
|
hide_cloud_promo: hideRaw === "true" || hideRaw === "1",
|
|
347
354
|
org_id: process.env.SWARM_ORG_ID?.trim() || null,
|
|
355
|
+
dashboard_url: dashboardUrl,
|
|
348
356
|
};
|
|
349
357
|
}
|
|
350
358
|
|
|
@@ -759,7 +767,7 @@ export async function buildStatusPayload(): Promise<StatusResponse> {
|
|
|
759
767
|
const instance = await getInstanceFeedbackSignals();
|
|
760
768
|
return {
|
|
761
769
|
runtime_pools: { enabled: isRuntimePoolsEnabled(), supervisor: runtimeSupervisorStatus() },
|
|
762
|
-
identity: buildIdentity(),
|
|
770
|
+
identity: buildIdentity(await resolveAppUrl()),
|
|
763
771
|
setup,
|
|
764
772
|
activity: { ...activity, leads_ready: readyLeads },
|
|
765
773
|
agent_fs: {
|
package/src/http/tasks.ts
CHANGED
|
@@ -84,7 +84,7 @@ import {
|
|
|
84
84
|
import { ensure } from "../utils/business-use";
|
|
85
85
|
import { getRequestAuth } from "../utils/request-auth-context";
|
|
86
86
|
import { scrubSecrets } from "../utils/secret-scrubber";
|
|
87
|
-
import { createListTotalCache } from "./list-total-cache";
|
|
87
|
+
import { createListTotalCache, LIST_TOTAL_TTL_MS } from "./list-total-cache";
|
|
88
88
|
import {
|
|
89
89
|
agentFirstPrincipal,
|
|
90
90
|
authUserId,
|
|
@@ -224,7 +224,9 @@ const SupersedeTaskResponseSchema = z.object({
|
|
|
224
224
|
|
|
225
225
|
// ─── Route Definitions ───────────────────────────────────────────────────────
|
|
226
226
|
|
|
227
|
-
|
|
227
|
+
// The task list total scans every visible task, and only the pager shows it,
|
|
228
|
+
// so page 1 polls reuse a total younger than 15 s.
|
|
229
|
+
const taskTotals = createListTotalCache(LIST_TOTAL_TTL_MS, 15_000);
|
|
228
230
|
|
|
229
231
|
const listTasks = route({
|
|
230
232
|
method: "get",
|
|
@@ -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.1793363705-g5c9ae5";
|
|
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";
|
|
@@ -21,7 +21,7 @@ import type {
|
|
|
21
21
|
* Dockerfile.worker; bump the two together). Every ACP shape this adapter
|
|
22
22
|
* relies on was measured against it.
|
|
23
23
|
*/
|
|
24
|
-
export const FX_PINNED_VERSION = "0.0.
|
|
24
|
+
export const FX_PINNED_VERSION = "0.0.16";
|
|
25
25
|
|
|
26
26
|
/** OpenAI's Chat Completions prefix; fx appends `/chat/completions`. */
|
|
27
27
|
export const FX_OPENAI_BASE_URL = "https://api.openai.com/v1";
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { z } from "zod";
|
|
2
|
-
import { RuntimeProviderKindSchema } from "../types";
|
|
2
|
+
import { RuntimePoolCacheSchema, RuntimeProviderKindSchema } from "../types";
|
|
3
3
|
|
|
4
4
|
export const CONTROLLER_PROTOCOL_VERSION = 1;
|
|
5
5
|
export const CONTROLLER_COMMAND_TIMEOUT_MS = 60_000;
|
|
@@ -16,6 +16,7 @@ const traitsSchema = z.object({
|
|
|
16
16
|
canProvision: z.boolean(),
|
|
17
17
|
ephemeralDisk: z.boolean(),
|
|
18
18
|
supportsPause: z.boolean(),
|
|
19
|
+
persistentCache: z.boolean().optional(),
|
|
19
20
|
maxTtlSec: z.number().positive().optional(),
|
|
20
21
|
maxContinuousSec: z.number().positive().optional(),
|
|
21
22
|
});
|
|
@@ -31,9 +32,21 @@ const provisionSchema = z.object({
|
|
|
31
32
|
.object({ cpu: z.number().positive().optional(), memoryMb: z.number().positive().optional() })
|
|
32
33
|
.optional(),
|
|
33
34
|
ttlSec: z.number().positive().optional(),
|
|
35
|
+
cache: RuntimePoolCacheSchema.extend({
|
|
36
|
+
key: z.string().regex(/^[a-zA-Z0-9][a-zA-Z0-9_.-]+$/),
|
|
37
|
+
}).optional(),
|
|
34
38
|
});
|
|
35
39
|
const command = { type: z.literal("cmd"), id: z.string().min(1) };
|
|
36
40
|
export const ControllerCommandSchema = z.discriminatedUnion("op", [
|
|
41
|
+
z.object({
|
|
42
|
+
...command,
|
|
43
|
+
op: z.literal("release-cache"),
|
|
44
|
+
payload: z.object({
|
|
45
|
+
key: z.string().regex(/^[a-zA-Z0-9][a-zA-Z0-9_.-]+$/),
|
|
46
|
+
poolId: z.string().min(1),
|
|
47
|
+
ownerId: z.string().min(1),
|
|
48
|
+
}),
|
|
49
|
+
}),
|
|
37
50
|
z.object({ ...command, op: z.literal("provision"), payload: provisionSchema }),
|
|
38
51
|
z.object({
|
|
39
52
|
...command,
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import { scrubSecrets } from "../utils/secret-scrubber";
|
|
2
2
|
import type {
|
|
3
3
|
ProvisionRequest,
|
|
4
|
+
RuntimeCacheReference,
|
|
4
5
|
RuntimeHandle,
|
|
5
6
|
RuntimeProvider,
|
|
6
7
|
RuntimeProviderTraits,
|
|
@@ -25,6 +26,7 @@ export interface ControllerCommands {
|
|
|
25
26
|
terminate(handle: RuntimeHandle, reason: string): Promise<void>;
|
|
26
27
|
status(handles: RuntimeHandle[]): Promise<Record<string, RuntimeStatus>>;
|
|
27
28
|
list(ownerId: string): Promise<RuntimeHandle[]>;
|
|
29
|
+
releaseCache?(cache: RuntimeCacheReference): Promise<void>;
|
|
28
30
|
renew?(handle: RuntimeHandle, ttlSec: number): Promise<{ endAt: string }>;
|
|
29
31
|
pause?(handle: RuntimeHandle): Promise<void>;
|
|
30
32
|
resume?(handle: RuntimeHandle): Promise<void>;
|
|
@@ -36,6 +38,7 @@ export class BuiltinController implements ControllerCommands {
|
|
|
36
38
|
readonly renew?: ControllerCommands["renew"];
|
|
37
39
|
readonly pause?: ControllerCommands["pause"];
|
|
38
40
|
readonly resume?: ControllerCommands["resume"];
|
|
41
|
+
readonly releaseCache?: ControllerCommands["releaseCache"];
|
|
39
42
|
|
|
40
43
|
constructor(
|
|
41
44
|
readonly id: string,
|
|
@@ -47,6 +50,8 @@ export class BuiltinController implements ControllerCommands {
|
|
|
47
50
|
this.workerApiUrl = workerApiUrl;
|
|
48
51
|
if (provider.renew)
|
|
49
52
|
this.renew = (handle, ttlSec) => this.call(() => provider.renew!(target, handle, ttlSec));
|
|
53
|
+
if (provider.releaseCache)
|
|
54
|
+
this.releaseCache = (cache) => this.call(() => provider.releaseCache!(target, cache));
|
|
50
55
|
if (provider.pause) this.pause = (handle) => this.call(() => provider.pause!(target, handle));
|
|
51
56
|
if (provider.resume)
|
|
52
57
|
this.resume = (handle) => this.call(() => provider.resume!(target, handle));
|