@desplega.ai/agent-swarm 2.4.3 → 2.4.5
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-1885dpr7.js → acp-adapter-2wdkktv6.js} +1 -2
- package/dist/{actions-pn76n32q.js → actions-c40ebcm4.js} +18 -18
- package/dist/{amp-adapter-af84rx1d.js → amp-adapter-wb67m95z.js} +2 -2
- package/dist/{app-6sv8mhzm.js → app-353vre8g.js} +3 -3
- package/dist/{assistant-sc2tf825.js → assistant-sj1njh23.js} +21 -21
- package/dist/{boot-reembed-y3m0ydzj.js → boot-reembed-b9c3az3c.js} +1 -1
- package/dist/{boot-reembed-gvxjs2py.js → boot-reembed-qdkvxeem.js} +1 -1
- package/dist/{boot-scrub-sweep-1j9mk05f.js → boot-scrub-sweep-m3wh8eks.js} +2 -2
- package/dist/{boot-seeds-37sp626b.js → boot-seeds-j2273s54.js} +4 -4
- package/dist/{claude-adapter-rp0jtnwr.js → claude-adapter-m7q0a085.js} +2 -2
- package/dist/{claude-managed-adapter-h2y889ge.js → claude-managed-adapter-5ptb6tzs.js} +2 -2
- package/dist/{claude-managed-setup-rdt248k7.js → claude-managed-setup-smrqk5j5.js} +3 -3
- package/dist/{claude-sdk-session-53rcw5we.js → claude-sdk-session-38xnz21e.js} +8284 -8267
- package/dist/{cli-6eyncg0y.js → cli-018r4jq6.js} +34 -6
- package/dist/{cli-sds5zzs8.js → cli-1f3qzw6y.js} +78 -12
- package/dist/{cli-f0p0f6ja.js → cli-1sp861s4.js} +7 -7
- package/dist/{cli-dm8p2dpf.js → cli-1syn2xy1.js} +10 -10
- package/dist/{cli-tkf4x6ha.js → cli-37j04k05.js} +11 -11
- package/dist/{cli-v87dr6e3.js → cli-3a45zcgm.js} +1 -1
- package/dist/{cli-1n9gdg56.js → cli-43x64hv3.js} +4 -4
- package/dist/{cli-p0qerd2r.js → cli-4zfqgjc5.js} +2 -2
- package/dist/{cli-aa3tb29r.js → cli-7c9qs530.js} +1 -1
- package/dist/{cli-9qtjfd60.js → cli-848hxdwj.js} +1 -1
- package/dist/{cli-1xsraagq.js → cli-94gwyg7m.js} +8 -8
- package/dist/{cli-0dxxwfkt.js → cli-9hstj09h.js} +17 -14
- package/dist/{cli-1012xb7j.js → cli-9yqgm9nd.js} +22 -22
- package/dist/{cli-pygdsgh8.js → cli-a92r1127.js} +1 -1
- package/dist/{cli-8vverzf7.js → cli-ba8ne1es.js} +5 -0
- package/dist/{cli-bxrqvpg5.js → cli-be2161h1.js} +7 -7
- package/dist/{cli-npfe9t1s.js → cli-bnes4z6b.js} +2 -2
- package/dist/{cli-aypbg9r2.js → cli-cxxe61f6.js} +1 -1
- package/dist/{cli-vq9gqarq.js → cli-dk44dxec.js} +1 -1
- package/dist/{cli-836ehxmt.js → cli-eww120g8.js} +3 -2
- package/dist/{cli-a0kcty51.js → cli-f01h25yt.js} +1 -1
- package/dist/{cli-mzkwkbdy.js → cli-f0g3pgvf.js} +1 -1
- package/dist/{cli-1ma335fz.js → cli-ftc1g5tj.js} +2 -2
- package/dist/{cli-4yef8ghv.js → cli-fv5hepyy.js} +3 -3
- package/dist/{cli-58vwn100.js → cli-g35m2xwm.js} +4 -4
- package/dist/{cli-qje9k2nh.js → cli-g9ypd5en.js} +12 -2
- package/dist/{cli-gx0j2khs.js → cli-j99azg3g.js} +3 -3
- package/dist/{cli-er83cjbn.js → cli-k8g16gz3.js} +416 -116
- package/dist/{cli-qp2m4btv.js → cli-kpw04va0.js} +2 -2
- package/dist/{cli-8y99kfey.js → cli-ms85309d.js} +33 -15
- package/dist/{cli-kgnkyabk.js → cli-n72rg53p.js} +1555 -645
- package/dist/{cli-e4d78xsv.js → cli-p14m2cnp.js} +1 -1
- package/dist/{cli-1eszr343.js → cli-qfxc84gh.js} +1 -1
- package/dist/{cli-wk28hzqn.js → cli-qwhy2qma.js} +1 -1
- package/dist/{cli-73ezfhha.js → cli-st8aw1m6.js} +228 -11
- package/dist/{cli-zvbb4n0e.js → cli-tw4p90jb.js} +92 -192
- package/dist/{cli-038fga5k.js → cli-v0yrfmdh.js} +148 -183
- package/dist/{cli-6aen3ezb.js → cli-v8fjakwf.js} +1 -1
- package/dist/{cli-99jez72r.js → cli-w9etnnxk.js} +6 -1
- package/dist/{cli-vcgscaan.js → cli-wt0jf9hm.js} +217 -80
- package/dist/{cli-s9rr4gr2.js → cli-xpa64rek.js} +1065 -305
- package/dist/{cli-h0mgxann.js → cli-z7d29rw9.js} +2 -2
- package/dist/cli.js +45 -20
- package/dist/{codex-adapter-2zrmab9b.js → codex-adapter-tn6k5mkg.js} +1 -1
- package/dist/{codex-session-runner-kbkz62vc.js → codex-session-runner-7vjt7jbw.js} +1 -1
- package/dist/{commands-hd574th6.js → commands-t7rz9146.js} +2 -2
- package/dist/{controller-pz5mcfg4.js → controller-m2jasma5.js} +2 -2
- package/dist/{controllers-connect-5rgq2hfk.js → controllers-connect-9ftpex99.js} +19 -19
- package/dist/{cursor-adapter-mgaprq0j.js → cursor-adapter-mjv79e4z.js} +1 -1
- package/dist/{db-6nf1207q.js → db-6sfrn9g3.js} +1 -1
- package/dist/{db-h06dxp05.js → db-rn673r06.js} +3 -3
- package/dist/{dispatcher-zthpy4m1.js → dispatcher-c155xz0p.js} +17 -17
- package/dist/{dsh-adapter-64dhw0j7.js → dsh-adapter-3mmszc1x.js} +1 -1
- package/dist/{external-channel-x01t7ke6.js → external-channel-35k6tkv8.js} +2 -2
- package/dist/{grok-adapter-h21jv594.js → grok-adapter-tfrq7tg1.js} +2 -3
- package/dist/handlers-7gw64dxw.js +39 -0
- package/dist/{heartbeat-zzfksckz.js → heartbeat-dyg4npkp.js} +20 -20
- package/dist/{hook-z4zhzj1f.js → hook-drspbwpe.js} +1 -1
- package/dist/{hook-3xfqhh2s.js → hook-x26b31bg.js} +1 -1
- package/dist/{http-q813tmya.js → http-pk972xyw.js} +571 -294
- package/dist/{index-va3y63es.js → index-6srwvd5b.js} +1 -2
- package/dist/{index-x8xbyn1t.js → index-arytgj5q.js} +1 -1
- package/dist/{index-wbbs14c5.js → index-bgdyvcnv.js} +17 -17
- package/dist/{index-s98037v0.js → index-cbxkmfc5.js} +1 -2
- package/dist/{index-c4ykd0f1.js → index-dr94zbnh.js} +13348 -11611
- package/dist/{index-m0a4ydqj.js → index-gabbcdn2.js} +17 -17
- package/dist/{index-220tk9bw.js → index-jwxkkex2.js} +139 -76
- package/dist/index-tfb26zhy.js +41 -0
- package/dist/{keepalive-y5t1my7m.js → keepalive-7yrng7px.js} +3 -3
- package/dist/{lead-2xwtq66b.js → lead-7e02z839.js} +29 -30
- package/dist/{maintenance-ybfsaedv.js → maintenance-e6cemdqt.js} +1 -1
- package/dist/{node-3v3f08f7.js → node-de6akczr.js} +10 -9
- package/dist/{oauth-refresh-sweep-909jmn12.js → oauth-refresh-sweep-yw3xdbcj.js} +2 -2
- package/dist/{onboard-xn6fkkys.js → onboard-0a9y5yrc.js} +2 -2
- package/dist/{otel-impl-e6mn0z13.js → otel-impl-trjpkfb7.js} +1 -1
- package/dist/{pricing-refresh-ygd362ep.js → pricing-refresh-d2b5my0y.js} +3 -3
- package/dist/{rbac-roles-mb470p4e.js → rbac-roles-hey3yv9d.js} +2 -2
- package/dist/{rbac-roles-c9axvfnz.js → rbac-roles-nxh5ahwk.js} +2 -2
- package/dist/{registry-rxw10xzg.js → registry-ajhexe44.js} +1 -1
- package/dist/{render-v2-g0306nh1.js → render-v2-rr3p2atg.js} +3 -3
- package/dist/{resume-v315hnq6.js → resume-2efkf1s1.js} +5 -5
- package/dist/{session-access-2zg285a1.js → session-access-s2mttxpw.js} +6 -4
- package/dist/{setup-a4mtf02k.js → setup-vypma5yt.js} +1 -1
- package/dist/{work-objects-9fr0fgtn.js → work-objects-rvhckdkp.js} +20 -20
- package/dist/{worker-qae22jeb.js → worker-5snytpbd.js} +29 -30
- package/dist/worker-token-xf11yyds.js +136 -0
- package/openapi.json +303 -13
- package/package.json +8 -8
- package/src/apps/action-runner.ts +4 -0
- package/src/apps/catalog.generated.json +209 -4
- package/src/apps/page-validator.ts +21 -3
- package/src/be/chunking.ts +42 -9
- package/src/be/db/tasks/draft-text.ts +15 -0
- package/src/be/db/tasks/read.ts +14 -1
- package/src/be/db.ts +5 -1
- package/src/be/memory/index-content.ts +36 -17
- package/src/be/memory/providers/openai-embedding.ts +115 -61
- package/src/be/memory/providers/sqlite-store.ts +69 -23
- package/src/be/memory/reranker.ts +31 -1
- package/src/be/memory/search-candidates.ts +47 -0
- package/src/be/memory/types.ts +14 -0
- package/src/be/migrations/232_agent_session_tokens.sql +41 -0
- package/src/be/scripts/typecheck.ts +2 -0
- package/src/be/seed-scripts/catalog/heartbeat-jev-gate.ts +116 -55
- package/src/be/seed-scripts/index.ts +2 -2
- package/src/be/swarm-config-guard.ts +40 -1
- package/src/be/task-attachment-store.ts +30 -0
- package/src/be/users.ts +169 -12
- package/src/cli.tsx +24 -0
- package/src/commands/worker-token.ts +164 -0
- package/src/extensions/catalog.generated.json +4 -4
- package/src/extensions/contract-types.generated.ts +1 -1
- package/src/extensions/contract.ts +7 -0
- package/src/fs/provider.ts +13 -1
- package/src/heartbeat/heartbeat.ts +31 -11
- package/src/http/apps.ts +43 -5
- package/src/http/codex-oauth-keep-warm.ts +27 -9
- package/src/http/config.ts +5 -3
- package/src/http/core.ts +2 -0
- package/src/http/fs.ts +54 -34
- package/src/http/mcp-body.ts +37 -0
- package/src/http/mcp-user.ts +3 -5
- package/src/http/mcp.ts +6 -7
- package/src/http/memory.ts +26 -15
- package/src/http/page-proxy.ts +61 -5
- package/src/http/pages-public.ts +27 -44
- package/src/http/pages.ts +4 -4
- package/src/http/script-runs.ts +2 -0
- package/src/http/session-access.ts +51 -2
- package/src/http/sessions.ts +249 -10
- package/src/http/tasks.ts +4 -32
- package/src/http/users.ts +12 -1
- package/src/integrations/chat-sdk/adapters.ts +7 -3
- package/src/integrations/chat-sdk/attachments.ts +90 -12
- package/src/integrations/chat-sdk/facts.ts +120 -0
- package/src/integrations/chat-sdk/inbound.ts +156 -8
- package/src/integrations/chat-sdk/limits.ts +24 -0
- package/src/integrations/chat-sdk/outbound-files.ts +186 -0
- package/src/integrations/chat-sdk/outbound.ts +234 -4
- package/src/integrations/chat-sdk/relay.ts +4 -1
- package/src/integrations/chat-sdk/router.ts +28 -2
- package/src/integrations/chat-sdk/steering.ts +9 -0
- package/src/integrations/chat-sdk/templates.ts +14 -0
- package/src/integrations/chat-sdk/thread-buffer.ts +4 -0
- package/src/mcp-limits.ts +20 -0
- package/src/pages/browser-sdk.ts +8 -3
- package/src/pages/feedback-overlay.ts +1 -1
- package/src/providers/acp-adapter.ts +33 -13
- package/src/providers/acp-swarm-events.ts +1 -1
- package/src/providers/amp-adapter.ts +1 -1
- package/src/providers/codex-adapter.ts +106 -119
- package/src/providers/codex-agents-md.ts +35 -229
- package/src/providers/codex-oauth/storage.ts +1 -1
- package/src/providers/cursor-adapter.ts +1 -1
- package/src/rbac/legacy-policy.ts +3 -1
- package/src/rbac/permissions.ts +4 -0
- package/src/realtime/transport.ts +1 -2
- package/src/script-workflows/workflow-ctx.ts +1 -0
- package/src/scripts-runtime/types/stdlib.d.ts +2 -0
- package/src/scripts-runtime/types/swarm-extension.d.ts +8 -0
- package/src/scripts-runtime/types/swarm-sdk.d.ts +2 -0
- package/src/server-user.ts +2 -0
- package/src/server.ts +2 -0
- package/src/slack/assistant.ts +2 -2
- package/src/slack/files.ts +2 -0
- package/src/slack/handlers.ts +4 -4
- package/src/slack/inbound-files.ts +39 -6
- package/src/slack/thread-buffer.ts +2 -1
- package/src/tasks/task-terminal-effects.ts +21 -17
- package/src/tests/acp-adapter.test.ts +10 -23
- package/src/tests/acp-fake-agent.ts +36 -0
- package/src/tests/acp-swarm-events.test.ts +1 -1
- package/src/tests/agent-session-tokens.test.ts +452 -0
- package/src/tests/apps-action-parent-task.test.ts +295 -0
- package/src/tests/apps-card-grid.test.ts +197 -0
- package/src/tests/apps-catalog-additions.test.ts +120 -0
- package/src/tests/apps-tree.test.ts +129 -0
- package/src/tests/chat-sdk-attachments-reply.test.ts +52 -0
- package/src/tests/chat-sdk-config.test.ts +11 -0
- package/src/tests/chat-sdk-facts.test.ts +23 -0
- package/src/tests/chat-sdk-inbound.test.ts +558 -4
- package/src/tests/chat-sdk-outbound-files.test.ts +652 -0
- package/src/tests/codex-adapter.test.ts +167 -242
- package/src/tests/codex-oauth-keep-warm.test.ts +85 -1
- package/src/tests/config-api-only-keys.test.ts +9 -0
- package/src/tests/config-session-auth.test.ts +14 -0
- package/src/tests/cors-allowlist.test.ts +1 -1
- package/src/tests/docs-index-footprint.test.js +2 -2
- package/src/tests/extensions-example-slack-reply-gate.test.ts +262 -91
- package/src/tests/grok-adapter.test.ts +3 -3
- package/src/tests/heartbeat-checklist.test.ts +33 -0
- package/src/tests/heartbeat-jev-gate-credentials.test.ts +44 -0
- package/src/tests/heartbeat-jev-gate.test.ts +265 -1
- package/src/tests/kv-page-proxy.test.ts +91 -5
- package/src/tests/launch-password-rejection.test.ts +2 -2
- package/src/tests/mcp-input-caps.test.ts +294 -0
- package/src/tests/memory-chunked-search-starvation.test.ts +200 -0
- package/src/tests/memory-embedding-input-cap.test.ts +114 -0
- package/src/tests/memory.test.ts +14 -0
- package/src/tests/migration-232-agent-session-tokens.test.ts +67 -0
- package/src/tests/page-guest-session.test.ts +19 -8
- package/src/tests/page-proxy-authed.test.ts +1 -1
- package/src/tests/page-proxy.test.ts +17 -10
- package/src/tests/page-session.test.ts +102 -1
- package/src/tests/pages-authed-mode.test.ts +82 -12
- package/src/tests/pages-password-mode.test.ts +5 -5
- package/src/tests/rbac-engine.test.ts +1 -0
- package/src/tests/realtime-transport.test.ts +13 -7
- package/src/tests/runtime-child-token-cleanup.test.ts +3 -3
- package/src/tests/script-connections.test.ts +2 -0
- package/src/tests/script-runs-http.test.ts +84 -0
- package/src/tests/scripts-http.test.ts +10 -1
- package/src/tests/session-route-posture.test.ts +2 -0
- package/src/tests/swarm-config-export-to-env.test.ts +22 -3
- package/src/tests/task-audio-transcription.test.ts +299 -0
- package/src/tests/task-completion-memory-chunking.test.ts +434 -0
- package/src/tests/task-terminal-secret-scrub.test.ts +6 -1
- package/src/tests/transcription.test.ts +299 -0
- package/src/tests/worker-token-cli.test.ts +76 -0
- package/src/tests/workflow-executors.test.ts +4 -2
- package/src/tests/workflow-system-one-decision-providers.test.ts +98 -1
- package/src/tests/workflow-system-one-decision.test.ts +2 -1
- package/src/tests/workflow-system-one-extraction.test.ts +805 -0
- package/src/tools/chat-access.ts +10 -0
- package/src/tools/chat-post.ts +31 -11
- package/src/tools/chat-reply.ts +32 -10
- package/src/tools/memory-search.ts +3 -1
- package/src/tools/system-one-decide.ts +17 -4
- package/src/types.ts +8 -1
- package/src/utils/config-env-export.ts +3 -1
- package/src/utils/page-session.ts +133 -20
- package/src/utils/request-auth-context.ts +2 -1
- package/src/utils/transcription.ts +246 -0
- package/src/workflows/definition.ts +7 -1
- package/src/workflows/engine.ts +18 -12
- package/src/workflows/executors/index.ts +1 -0
- package/src/workflows/executors/registry.ts +2 -0
- package/src/workflows/executors/system-one-decision.ts +84 -262
- package/src/workflows/executors/system-one-extract-wire.ts +57 -0
- package/src/workflows/executors/system-one-extraction.ts +830 -0
- package/src/workflows/executors/system-one-providers.ts +316 -114
- package/src/workflows/executors/system-one-transport.ts +264 -0
- package/templates/extensions/slack-reply-gate/README.md +24 -22
- package/templates/extensions/slack-reply-gate/hooks.ts +146 -95
- package/templates/extensions/slack-reply-gate/manifest.json +2 -2
- package/templates/official/lead/HEARTBEAT.md +1 -1
- package/templates/skills/apps/SKILL.md +31 -7
- package/templates/skills/apps/content.md +31 -7
- package/templates/skills/chat-sdk/content.md +6 -2
- package/templates/skills/code-quality/content.md +2 -1
- package/templates/skills/heartbeat-runbook/content.md +8 -8
- package/templates/skills/pages/content.md +6 -4
- package/templates/skills/swarm-scripts/SKILL.md +2 -2
- package/templates/skills/swarm-scripts/content.md +2 -2
- package/dist/cli-v1sjhn52.js +0 -7
- package/dist/handlers-2qjw434w.js +0 -39
- package/dist/index-d0g18bax.js +0 -41
|
@@ -7,7 +7,16 @@
|
|
|
7
7
|
*/
|
|
8
8
|
|
|
9
9
|
import { afterAll, afterEach, beforeAll, beforeEach, describe, expect, test } from "bun:test";
|
|
10
|
-
import {
|
|
10
|
+
import {
|
|
11
|
+
lstatSync,
|
|
12
|
+
mkdirSync,
|
|
13
|
+
mkdtempSync,
|
|
14
|
+
readlinkSync,
|
|
15
|
+
realpathSync,
|
|
16
|
+
rmSync,
|
|
17
|
+
symlinkSync,
|
|
18
|
+
} from "node:fs";
|
|
19
|
+
import { homedir, tmpdir } from "node:os";
|
|
11
20
|
import { join } from "node:path";
|
|
12
21
|
import type {
|
|
13
22
|
AgentMessageItem,
|
|
@@ -17,7 +26,6 @@ import type {
|
|
|
17
26
|
ThreadItem,
|
|
18
27
|
} from "../providers/codex-adapter";
|
|
19
28
|
import { buildCodexConfig, CodexAdapter } from "../providers/codex-adapter";
|
|
20
|
-
import { writeCodexAgentsMd } from "../providers/codex-agents-md";
|
|
21
29
|
import {
|
|
22
30
|
CODEX_DEFAULT_MODEL,
|
|
23
31
|
computeCodexCostUsd,
|
|
@@ -863,7 +871,7 @@ describe("CodexSession event mapping", () => {
|
|
|
863
871
|
|
|
864
872
|
const config = testConfig({
|
|
865
873
|
logFile: join(tmpLogDir, "happy.log"),
|
|
866
|
-
cwd: "",
|
|
874
|
+
cwd: "",
|
|
867
875
|
});
|
|
868
876
|
|
|
869
877
|
const { emitted, result } = await runSessionWithFakeThread(events, config);
|
|
@@ -1268,242 +1276,161 @@ describe("CodexAdapter.canResume", () => {
|
|
|
1268
1276
|
});
|
|
1269
1277
|
});
|
|
1270
1278
|
|
|
1271
|
-
describe("
|
|
1272
|
-
const
|
|
1273
|
-
|
|
1274
|
-
|
|
1275
|
-
|
|
1276
|
-
|
|
1277
|
-
|
|
1278
|
-
|
|
1279
|
-
|
|
1280
|
-
|
|
1281
|
-
|
|
1282
|
-
|
|
1283
|
-
|
|
1284
|
-
|
|
1285
|
-
|
|
1286
|
-
|
|
1287
|
-
|
|
1288
|
-
|
|
1289
|
-
|
|
1290
|
-
|
|
1291
|
-
|
|
1292
|
-
|
|
1293
|
-
|
|
1294
|
-
|
|
1295
|
-
|
|
1296
|
-
|
|
1297
|
-
|
|
1298
|
-
|
|
1299
|
-
|
|
1300
|
-
|
|
1301
|
-
|
|
1302
|
-
|
|
1303
|
-
|
|
1304
|
-
|
|
1305
|
-
|
|
1306
|
-
|
|
1307
|
-
|
|
1308
|
-
|
|
1309
|
-
|
|
1310
|
-
|
|
1311
|
-
|
|
1312
|
-
|
|
1313
|
-
|
|
1314
|
-
|
|
1315
|
-
|
|
1316
|
-
|
|
1317
|
-
|
|
1318
|
-
|
|
1319
|
-
|
|
1320
|
-
|
|
1321
|
-
|
|
1322
|
-
|
|
1323
|
-
|
|
1324
|
-
|
|
1325
|
-
|
|
1326
|
-
|
|
1327
|
-
|
|
1328
|
-
|
|
1329
|
-
|
|
1330
|
-
|
|
1331
|
-
|
|
1332
|
-
|
|
1333
|
-
|
|
1334
|
-
|
|
1335
|
-
|
|
1336
|
-
|
|
1337
|
-
|
|
1338
|
-
|
|
1339
|
-
|
|
1340
|
-
|
|
1341
|
-
|
|
1342
|
-
|
|
1343
|
-
|
|
1344
|
-
|
|
1345
|
-
|
|
1346
|
-
|
|
1347
|
-
|
|
1348
|
-
|
|
1349
|
-
|
|
1350
|
-
|
|
1351
|
-
|
|
1352
|
-
|
|
1353
|
-
|
|
1354
|
-
|
|
1355
|
-
|
|
1356
|
-
|
|
1357
|
-
|
|
1358
|
-
|
|
1359
|
-
const after = await Bun.file(agentsMd).text();
|
|
1360
|
-
expect(after).not.toContain("<swarm_system_prompt>");
|
|
1361
|
-
expect(after).toContain("# Keep me");
|
|
1362
|
-
});
|
|
1363
|
-
|
|
1364
|
-
test("prepends block when existing AGENTS.md has no block", async () => {
|
|
1365
|
-
const dir = join(tmpDir, "prepend-block");
|
|
1366
|
-
mkdirSync(dir, { recursive: true });
|
|
1367
|
-
await Bun.write(join(dir, "AGENTS.md"), "# Project instructions");
|
|
1368
|
-
|
|
1369
|
-
const handle = await writeCodexAgentsMd(dir, "swarm prompt");
|
|
1370
|
-
const agentsMd = join(dir, "AGENTS.md");
|
|
1371
|
-
|
|
1372
|
-
const updated = await Bun.file(agentsMd).text();
|
|
1373
|
-
expect(updated).toContain("swarm prompt");
|
|
1374
|
-
expect(updated).toContain("# Project instructions");
|
|
1375
|
-
expect(updated.indexOf("<swarm_system_prompt>")).toBeLessThan(
|
|
1376
|
-
updated.indexOf("# Project instructions"),
|
|
1279
|
+
describe("Codex session instructions", () => {
|
|
1280
|
+
for (const kind of [
|
|
1281
|
+
"file",
|
|
1282
|
+
"in-tree link",
|
|
1283
|
+
"out-of-tree link",
|
|
1284
|
+
"dangling link",
|
|
1285
|
+
"missing link",
|
|
1286
|
+
]) {
|
|
1287
|
+
// Windows requires symlink privileges; CI exercises the link cases on Linux.
|
|
1288
|
+
test.skipIf(kind !== "file" && process.platform === "win32")(
|
|
1289
|
+
`recovers a crashed legacy session before starting Codex (${kind})`,
|
|
1290
|
+
async () => {
|
|
1291
|
+
const dir = mkdtempSync(join(tmpdir(), "codex-legacy-instructions-"));
|
|
1292
|
+
const cwd = join(dir, "repo");
|
|
1293
|
+
mkdirSync(cwd);
|
|
1294
|
+
const agentsMd = join(cwd, "AGENTS.md");
|
|
1295
|
+
const claudeMd = join(cwd, "CLAUDE.md");
|
|
1296
|
+
const original = "# Repo rules\n";
|
|
1297
|
+
const edited = `${original}- Keep the agent's edit\n`;
|
|
1298
|
+
const outside = join(dir, "outside.md");
|
|
1299
|
+
const linkTarget =
|
|
1300
|
+
kind === "out-of-tree link"
|
|
1301
|
+
? "../outside.md"
|
|
1302
|
+
: kind === "dangling link"
|
|
1303
|
+
? "missing.md"
|
|
1304
|
+
: "CLAUDE.md";
|
|
1305
|
+
const realCwd = realpathSync(cwd);
|
|
1306
|
+
const stateDir = join(homedir(), ".cache", "agent-swarm", "codex-agents-md");
|
|
1307
|
+
const key = new Bun.CryptoHasher("sha256").update(realCwd).digest("hex");
|
|
1308
|
+
const recordPath = join(stateDir, `${key}.json`);
|
|
1309
|
+
try {
|
|
1310
|
+
await Bun.write(claudeMd, original);
|
|
1311
|
+
await Bun.write(outside, "OUTSIDE\n");
|
|
1312
|
+
if (kind !== "missing link") {
|
|
1313
|
+
await Bun.write(
|
|
1314
|
+
agentsMd,
|
|
1315
|
+
`<swarm_system_prompt>\nSTALE\n</swarm_system_prompt>\n${edited}`,
|
|
1316
|
+
);
|
|
1317
|
+
}
|
|
1318
|
+
if (kind !== "file") {
|
|
1319
|
+
mkdirSync(stateDir, { recursive: true });
|
|
1320
|
+
await Bun.write(recordPath, JSON.stringify({ cwd: realCwd, linkTarget }));
|
|
1321
|
+
}
|
|
1322
|
+
const fake = makeFakeAppServer(async (method, params, emit) => {
|
|
1323
|
+
if (method === "thread/start") {
|
|
1324
|
+
expect(params).toMatchObject({ developerInstructions: "new session instructions" });
|
|
1325
|
+
expect(await Bun.file(recordPath).exists()).toBe(false);
|
|
1326
|
+
if (kind === "file") {
|
|
1327
|
+
expect(lstatSync(agentsMd).isFile()).toBe(true);
|
|
1328
|
+
expect(await Bun.file(agentsMd).text()).toBe(edited);
|
|
1329
|
+
} else {
|
|
1330
|
+
expect(lstatSync(agentsMd).isSymbolicLink()).toBe(true);
|
|
1331
|
+
expect(readlinkSync(agentsMd)).toBe(linkTarget);
|
|
1332
|
+
}
|
|
1333
|
+
expect(await Bun.file(claudeMd).text()).toBe(
|
|
1334
|
+
kind === "in-tree link" ? edited : original,
|
|
1335
|
+
);
|
|
1336
|
+
expect(await Bun.file(outside).text()).toBe("OUTSIDE\n");
|
|
1337
|
+
return { thread: { id: "legacy-thread" } };
|
|
1338
|
+
}
|
|
1339
|
+
if (method === "turn/start") {
|
|
1340
|
+
queueMicrotask(() => {
|
|
1341
|
+
emit("turn/completed", {
|
|
1342
|
+
threadId: "legacy-thread",
|
|
1343
|
+
turn: { id: "legacy-turn", status: "completed", error: null },
|
|
1344
|
+
});
|
|
1345
|
+
});
|
|
1346
|
+
return { turn: { id: "legacy-turn" } };
|
|
1347
|
+
}
|
|
1348
|
+
throw new Error(`Unexpected method: ${method}`);
|
|
1349
|
+
});
|
|
1350
|
+
const session = await new CodexAdapter({
|
|
1351
|
+
bypassSubprocess: true,
|
|
1352
|
+
appServerFactory: () => fake.client as never,
|
|
1353
|
+
}).createSession(
|
|
1354
|
+
testConfig({
|
|
1355
|
+
cwd,
|
|
1356
|
+
systemPrompt: "new session instructions",
|
|
1357
|
+
logFile: join(dir, "session.log"),
|
|
1358
|
+
taskId: "",
|
|
1359
|
+
}),
|
|
1360
|
+
);
|
|
1361
|
+
await session.waitForCompletion();
|
|
1362
|
+
} finally {
|
|
1363
|
+
rmSync(recordPath, { force: true });
|
|
1364
|
+
rmSync(dir, { recursive: true, force: true });
|
|
1365
|
+
}
|
|
1366
|
+
},
|
|
1377
1367
|
);
|
|
1368
|
+
}
|
|
1378
1369
|
|
|
1379
|
-
|
|
1380
|
-
|
|
1381
|
-
|
|
1382
|
-
|
|
1383
|
-
|
|
1384
|
-
|
|
1385
|
-
|
|
1386
|
-
|
|
1387
|
-
|
|
1388
|
-
|
|
1389
|
-
|
|
1390
|
-
|
|
1391
|
-
|
|
1392
|
-
|
|
1393
|
-
|
|
1394
|
-
|
|
1395
|
-
|
|
1396
|
-
|
|
1397
|
-
|
|
1398
|
-
|
|
1399
|
-
|
|
1400
|
-
|
|
1401
|
-
|
|
1402
|
-
|
|
1403
|
-
|
|
1404
|
-
|
|
1405
|
-
|
|
1406
|
-
|
|
1407
|
-
|
|
1408
|
-
|
|
1409
|
-
|
|
1410
|
-
|
|
1411
|
-
|
|
1412
|
-
|
|
1413
|
-
|
|
1414
|
-
|
|
1415
|
-
|
|
1416
|
-
|
|
1417
|
-
|
|
1418
|
-
|
|
1419
|
-
|
|
1420
|
-
|
|
1421
|
-
|
|
1422
|
-
|
|
1423
|
-
|
|
1424
|
-
|
|
1425
|
-
|
|
1426
|
-
|
|
1427
|
-
|
|
1428
|
-
|
|
1429
|
-
|
|
1430
|
-
|
|
1431
|
-
|
|
1432
|
-
|
|
1433
|
-
|
|
1434
|
-
|
|
1435
|
-
|
|
1436
|
-
|
|
1437
|
-
|
|
1438
|
-
|
|
1439
|
-
|
|
1440
|
-
|
|
1441
|
-
|
|
1442
|
-
|
|
1443
|
-
const agentsMd = join(dir, "AGENTS.md");
|
|
1444
|
-
await Bun.write(claudeMd, original);
|
|
1445
|
-
symlinkSync("CLAUDE.md", agentsMd);
|
|
1446
|
-
|
|
1447
|
-
// First session never runs cleanup (crash).
|
|
1448
|
-
await writeCodexAgentsMd(dir, "first prompt", { stateDir });
|
|
1449
|
-
expect(lstatSync(agentsMd).isSymbolicLink()).toBe(false);
|
|
1450
|
-
|
|
1451
|
-
const handle = await writeCodexAgentsMd(dir, "second prompt", { stateDir });
|
|
1452
|
-
const during = readFileSync(agentsMd, "utf8");
|
|
1453
|
-
expect(during).toContain("second prompt");
|
|
1454
|
-
expect(during).not.toContain("first prompt");
|
|
1455
|
-
expect(readFileSync(claudeMd, "utf8")).toBe(original);
|
|
1456
|
-
|
|
1457
|
-
await handle.cleanup();
|
|
1458
|
-
expect(lstatSync(agentsMd).isSymbolicLink()).toBe(true);
|
|
1459
|
-
expect(readFileSync(claudeMd, "utf8")).toBe(original);
|
|
1460
|
-
});
|
|
1461
|
-
|
|
1462
|
-
test("ignores a forged symlink marker pointing out of the repo", async () => {
|
|
1463
|
-
const dir = join(tmpDir, "forged-marker");
|
|
1464
|
-
mkdirSync(dir, { recursive: true });
|
|
1465
|
-
const secretPath = join(tmpDir, "outside-secret.txt");
|
|
1466
|
-
const secret = "TOP-SECRET-CONTENTS\n";
|
|
1467
|
-
await Bun.write(secretPath, secret);
|
|
1468
|
-
const agentsMd = join(dir, "AGENTS.md");
|
|
1469
|
-
|
|
1470
|
-
for (const forged of ["../outside-secret.txt", secretPath]) {
|
|
1471
|
-
const original = `<swarm_system_prompt symlink="${encodeURIComponent(forged)}">\nold\n</swarm_system_prompt>\n# Rules\n`;
|
|
1472
|
-
await Bun.write(agentsMd, original);
|
|
1473
|
-
|
|
1474
|
-
const handle = await writeCodexAgentsMd(dir, "swarm prompt", { stateDir });
|
|
1475
|
-
expect(lstatSync(agentsMd).isSymbolicLink()).toBe(false);
|
|
1476
|
-
expect(readFileSync(agentsMd, "utf8")).not.toContain("TOP-SECRET");
|
|
1477
|
-
expect(readFileSync(secretPath, "utf8")).toBe(secret);
|
|
1478
|
-
|
|
1479
|
-
await handle.cleanup();
|
|
1480
|
-
expect(lstatSync(agentsMd).isSymbolicLink()).toBe(false);
|
|
1481
|
-
expect(readFileSync(agentsMd, "utf8")).toBe(original);
|
|
1482
|
-
expect(readFileSync(secretPath, "utf8")).toBe(secret);
|
|
1483
|
-
}
|
|
1484
|
-
});
|
|
1485
|
-
|
|
1486
|
-
test("never reads or writes an out-of-tree symlink target", async () => {
|
|
1487
|
-
const dir = join(tmpDir, "symlink-out-of-tree");
|
|
1488
|
-
mkdirSync(dir, { recursive: true });
|
|
1489
|
-
const secretPath = join(tmpDir, "outside-target.txt");
|
|
1490
|
-
const secret = "OUT-OF-TREE\n";
|
|
1491
|
-
await Bun.write(secretPath, secret);
|
|
1492
|
-
const agentsMd = join(dir, "AGENTS.md");
|
|
1493
|
-
symlinkSync("../outside-target.txt", agentsMd);
|
|
1494
|
-
|
|
1495
|
-
const handle = await writeCodexAgentsMd(dir, "swarm prompt", { stateDir });
|
|
1496
|
-
expect(lstatSync(agentsMd).isSymbolicLink()).toBe(false);
|
|
1497
|
-
const during = readFileSync(agentsMd, "utf8");
|
|
1498
|
-
expect(during).toContain("swarm prompt");
|
|
1499
|
-
expect(during).not.toContain("OUT-OF-TREE");
|
|
1500
|
-
|
|
1501
|
-
await Bun.write(agentsMd, `${during}- agent edit\n`);
|
|
1502
|
-
await handle.cleanup();
|
|
1503
|
-
|
|
1504
|
-
expect(readlinkSync(agentsMd)).toBe("../outside-target.txt");
|
|
1505
|
-
expect(readFileSync(secretPath, "utf8")).toBe(secret);
|
|
1506
|
-
});
|
|
1370
|
+
for (const kind of ["file", "missing", "symlink"]) {
|
|
1371
|
+
// Windows requires symlink privileges; CI exercises this case on Linux.
|
|
1372
|
+
test.skipIf(kind === "symlink" && process.platform === "win32")(
|
|
1373
|
+
`passes native instructions without changing ${kind} AGENTS.md during the session`,
|
|
1374
|
+
async () => {
|
|
1375
|
+
const dir = mkdtempSync(join(tmpdir(), "codex-instructions-"));
|
|
1376
|
+
const cwd = join(dir, "repo");
|
|
1377
|
+
mkdirSync(cwd);
|
|
1378
|
+
const agentsMd = join(cwd, "AGENTS.md");
|
|
1379
|
+
const claudeMd = join(cwd, "CLAUDE.md");
|
|
1380
|
+
const original = "# Repo rules\r\n\r\nKeep this file unchanged.\r\n";
|
|
1381
|
+
const systemPrompt = 'Swarm instructions: "quoted"\n第二行';
|
|
1382
|
+
await Bun.write(claudeMd, original);
|
|
1383
|
+
if (kind === "file") await Bun.write(agentsMd, original);
|
|
1384
|
+
if (kind === "symlink") symlinkSync("CLAUDE.md", agentsMd);
|
|
1385
|
+
|
|
1386
|
+
let startParams: unknown;
|
|
1387
|
+
const started = Promise.withResolvers<void>();
|
|
1388
|
+
const fake = makeFakeAppServer((method, params) => {
|
|
1389
|
+
if (method === "thread/start") {
|
|
1390
|
+
startParams = params;
|
|
1391
|
+
return { thread: { id: "instructions-thread" } };
|
|
1392
|
+
}
|
|
1393
|
+
if (method === "turn/start") {
|
|
1394
|
+
started.resolve();
|
|
1395
|
+
return { turn: { id: "instructions-turn" } };
|
|
1396
|
+
}
|
|
1397
|
+
throw new Error(`Unexpected method: ${method}`);
|
|
1398
|
+
});
|
|
1399
|
+
const assertRepoUnchanged = async () => {
|
|
1400
|
+
expect(await Bun.file(claudeMd).text()).toBe(original);
|
|
1401
|
+
if (kind === "missing") {
|
|
1402
|
+
expect(await Bun.file(agentsMd).exists()).toBe(false);
|
|
1403
|
+
} else {
|
|
1404
|
+
expect(await Bun.file(agentsMd).text()).toBe(original);
|
|
1405
|
+
expect(lstatSync(agentsMd).isSymbolicLink()).toBe(kind === "symlink");
|
|
1406
|
+
if (kind === "symlink") expect(readlinkSync(agentsMd)).toBe("CLAUDE.md");
|
|
1407
|
+
}
|
|
1408
|
+
};
|
|
1409
|
+
const session = await new CodexAdapter({
|
|
1410
|
+
bypassSubprocess: true,
|
|
1411
|
+
appServerFactory: () => fake.client as never,
|
|
1412
|
+
}).createSession(
|
|
1413
|
+
testConfig({ cwd, systemPrompt, logFile: join(dir, "session.log"), taskId: "" }),
|
|
1414
|
+
);
|
|
1415
|
+
try {
|
|
1416
|
+
await started.promise;
|
|
1417
|
+
await assertRepoUnchanged();
|
|
1418
|
+
expect(startParams).toMatchObject({ developerInstructions: systemPrompt });
|
|
1419
|
+
} finally {
|
|
1420
|
+
fake.emit("turn/completed", {
|
|
1421
|
+
threadId: "instructions-thread",
|
|
1422
|
+
turn: { id: "instructions-turn", status: "completed", error: null },
|
|
1423
|
+
});
|
|
1424
|
+
await session.waitForCompletion();
|
|
1425
|
+
try {
|
|
1426
|
+
await assertRepoUnchanged();
|
|
1427
|
+
} finally {
|
|
1428
|
+
rmSync(dir, { recursive: true, force: true });
|
|
1429
|
+
}
|
|
1430
|
+
}
|
|
1431
|
+
},
|
|
1432
|
+
);
|
|
1433
|
+
}
|
|
1507
1434
|
});
|
|
1508
1435
|
|
|
1509
1436
|
// ─── Phase 3: model catalogue ────────────────────────────────────────────────
|
|
@@ -2359,9 +2286,7 @@ describe("CodexSession session-end summarization", () => {
|
|
|
2359
2286
|
expect(indexCalls.length).toBe(0);
|
|
2360
2287
|
});
|
|
2361
2288
|
|
|
2362
|
-
test("runSummarize throws →
|
|
2363
|
-
// Use a real temp dir as cwd so we exercise writeCodexAgentsMd, then spy on
|
|
2364
|
-
// the resulting AGENTS.md file. After cleanup the file MUST be gone.
|
|
2289
|
+
test("runSummarize throws → logFileHandle.end() still runs", async () => {
|
|
2365
2290
|
const cwd = `/tmp/codex-summary-cleanup-${Date.now()}-${Math.random().toString(36).slice(2)}`;
|
|
2366
2291
|
mkdirSync(cwd, { recursive: true });
|
|
2367
2292
|
const agentsMdPath = join(cwd, "AGENTS.md");
|
|
@@ -2377,11 +2302,11 @@ describe("CodexSession session-end summarization", () => {
|
|
|
2377
2302
|
const config = testConfig({
|
|
2378
2303
|
logFile,
|
|
2379
2304
|
cwd,
|
|
2380
|
-
systemPrompt: "test system prompt",
|
|
2305
|
+
systemPrompt: "test system prompt",
|
|
2381
2306
|
});
|
|
2382
2307
|
await runSessionWithFakeThreadAndDeps(events, config, deps);
|
|
2383
2308
|
|
|
2384
|
-
//
|
|
2309
|
+
// Summarization failure must not create an instruction file.
|
|
2385
2310
|
expect(await Bun.file(agentsMdPath).exists()).toBe(false);
|
|
2386
2311
|
|
|
2387
2312
|
// Log file flush must have run despite the summarize throw — the log file
|
|
@@ -10,7 +10,8 @@ import { afterAll, afterEach, beforeAll, describe, expect, it } from "bun:test";
|
|
|
10
10
|
import { unlink } from "node:fs/promises";
|
|
11
11
|
import { createServer, type IncomingMessage, type Server, type ServerResponse } from "node:http";
|
|
12
12
|
import { closeDb, initDb, upsertKv } from "../be/db";
|
|
13
|
-
import {
|
|
13
|
+
import { validateConfigValue } from "../be/swarm-config-guard";
|
|
14
|
+
import { handleCodexOAuthKeepWarm, keepWarmMaxAgeMs } from "../http/codex-oauth-keep-warm";
|
|
14
15
|
import { handleOAuthLocks } from "../http/oauth-locks";
|
|
15
16
|
import { resetFetchForTesting, setFetchForTesting } from "../providers/codex-oauth/flow.js";
|
|
16
17
|
import type { CodexOAuthCredentials } from "../providers/codex-oauth/types.js";
|
|
@@ -176,6 +177,70 @@ describe("POST /api/oauth/keep-warm/codex", () => {
|
|
|
176
177
|
expect(slots.get(1)?.access).toBe("example-at_keepwarm");
|
|
177
178
|
});
|
|
178
179
|
|
|
180
|
+
it("refreshes a 6-day-old slot by default, before the ~6d20h point where OpenAI ended a session", async () => {
|
|
181
|
+
const slots = new Map<number, CodexOAuthCredentials>([
|
|
182
|
+
[
|
|
183
|
+
0,
|
|
184
|
+
{
|
|
185
|
+
access: "at_six_days",
|
|
186
|
+
refresh: "rt_six_days",
|
|
187
|
+
expires: Date.now() + 4 * 24 * 60 * 60 * 1000, // issued ~6 days ago (10d TTL)
|
|
188
|
+
accountId: "acc-six",
|
|
189
|
+
},
|
|
190
|
+
],
|
|
191
|
+
]);
|
|
192
|
+
installMockTransport(slots);
|
|
193
|
+
setFetchForTesting(
|
|
194
|
+
async () =>
|
|
195
|
+
new Response(
|
|
196
|
+
JSON.stringify({
|
|
197
|
+
access_token: "example-at_six_days_new",
|
|
198
|
+
refresh_token: "example-rt_six_days_new",
|
|
199
|
+
expires_in: 864000,
|
|
200
|
+
}),
|
|
201
|
+
{ status: 200, headers: { "Content-Type": "application/json" } },
|
|
202
|
+
),
|
|
203
|
+
);
|
|
204
|
+
|
|
205
|
+
const { req, res, captured } = fakeReqRes();
|
|
206
|
+
await handleCodexOAuthKeepWarm(req, res, ["api", "oauth", "keep-warm", "codex"]);
|
|
207
|
+
|
|
208
|
+
const body = JSON.parse(captured.body) as { results: Array<Record<string, unknown>> };
|
|
209
|
+
expect(body.results[0]).toMatchObject({ slot: 0, outcome: "refreshed" });
|
|
210
|
+
expect(slots.get(0)?.refresh).toBe("example-rt_six_days_new");
|
|
211
|
+
});
|
|
212
|
+
|
|
213
|
+
it("honors CODEX_OAUTH_KEEP_WARM_MAX_AGE_HOURS read at sweep time", async () => {
|
|
214
|
+
const saved = process.env.CODEX_OAUTH_KEEP_WARM_MAX_AGE_HOURS;
|
|
215
|
+
process.env.CODEX_OAUTH_KEEP_WARM_MAX_AGE_HOURS = "168";
|
|
216
|
+
try {
|
|
217
|
+
const slots = new Map<number, CodexOAuthCredentials>([
|
|
218
|
+
[
|
|
219
|
+
0,
|
|
220
|
+
{
|
|
221
|
+
access: "at_six_days",
|
|
222
|
+
refresh: "rt_six_days",
|
|
223
|
+
expires: Date.now() + 4 * 24 * 60 * 60 * 1000, // issued ~6 days ago, under 168h
|
|
224
|
+
accountId: "acc-six",
|
|
225
|
+
},
|
|
226
|
+
],
|
|
227
|
+
]);
|
|
228
|
+
installMockTransport(slots);
|
|
229
|
+
setFetchForTesting(async () => {
|
|
230
|
+
throw new Error("no /oauth/token exchange should happen under a 168h threshold");
|
|
231
|
+
});
|
|
232
|
+
|
|
233
|
+
const { req, res, captured } = fakeReqRes();
|
|
234
|
+
await handleCodexOAuthKeepWarm(req, res, ["api", "oauth", "keep-warm", "codex"]);
|
|
235
|
+
|
|
236
|
+
const body = JSON.parse(captured.body) as { results: Array<Record<string, unknown>> };
|
|
237
|
+
expect(body.results[0]).toMatchObject({ slot: 0, outcome: "warm" });
|
|
238
|
+
} finally {
|
|
239
|
+
if (saved === undefined) delete process.env.CODEX_OAUTH_KEEP_WARM_MAX_AGE_HOURS;
|
|
240
|
+
else process.env.CODEX_OAUTH_KEEP_WARM_MAX_AGE_HOURS = saved;
|
|
241
|
+
}
|
|
242
|
+
});
|
|
243
|
+
|
|
179
244
|
it("skips a slot with an active codex-auth-watch bench marker", async () => {
|
|
180
245
|
const keySuffix = "c-old"; // last 5 of accountId (no chatgpt_user_id in this fixture's access token)
|
|
181
246
|
await upsertKv({
|
|
@@ -284,6 +349,25 @@ describe("POST /api/oauth/keep-warm/codex", () => {
|
|
|
284
349
|
}
|
|
285
350
|
});
|
|
286
351
|
|
|
352
|
+
it("keepWarmMaxAgeMs defaults to 120h and ignores invalid or out-of-range values", () => {
|
|
353
|
+
const hour = 60 * 60 * 1000;
|
|
354
|
+
expect(keepWarmMaxAgeMs({})).toBe(120 * hour);
|
|
355
|
+
expect(keepWarmMaxAgeMs({ CODEX_OAUTH_KEEP_WARM_MAX_AGE_HOURS: " 72 " })).toBe(72 * hour);
|
|
356
|
+
expect(keepWarmMaxAgeMs({ CODEX_OAUTH_KEEP_WARM_MAX_AGE_HOURS: "216" })).toBe(216 * hour);
|
|
357
|
+
for (const bad of ["0", "217", "1.5", "-3", "abc", ""]) {
|
|
358
|
+
expect(keepWarmMaxAgeMs({ CODEX_OAUTH_KEEP_WARM_MAX_AGE_HOURS: bad })).toBe(120 * hour);
|
|
359
|
+
}
|
|
360
|
+
});
|
|
361
|
+
|
|
362
|
+
it("the config API rejects the values keepWarmMaxAgeMs would fall back from", () => {
|
|
363
|
+
const key = "CODEX_OAUTH_KEEP_WARM_MAX_AGE_HOURS";
|
|
364
|
+
expect(validateConfigValue(key, "1")).toBeNull();
|
|
365
|
+
expect(validateConfigValue(key, "216")).toBeNull();
|
|
366
|
+
expect(validateConfigValue(key, "0")).toContain("between 1 and 216");
|
|
367
|
+
expect(validateConfigValue(key, "217")).toContain("between 1 and 216");
|
|
368
|
+
expect(validateConfigValue(key, "1.5")).toContain("integer");
|
|
369
|
+
});
|
|
370
|
+
|
|
287
371
|
it("does not match unrelated paths", async () => {
|
|
288
372
|
const { req, res } = fakeReqRes();
|
|
289
373
|
const handled = await handleCodexOAuthKeepWarm(req, res, [
|
|
@@ -56,6 +56,12 @@ beforeAll(async () => {
|
|
|
56
56
|
value: "synthetic-api-only-signing-secret",
|
|
57
57
|
isSecret: true,
|
|
58
58
|
});
|
|
59
|
+
await upsertSwarmConfig({
|
|
60
|
+
scope: "global",
|
|
61
|
+
key: "TRANSCRIPTION_API_KEY",
|
|
62
|
+
value: "synthetic-api-only-transcription-key",
|
|
63
|
+
isSecret: true,
|
|
64
|
+
});
|
|
59
65
|
await upsertSwarmConfig({
|
|
60
66
|
scope: "global",
|
|
61
67
|
key: "SOME_WORKER_SECRET",
|
|
@@ -93,6 +99,7 @@ describe("API-only config keys are never served over HTTP", () => {
|
|
|
93
99
|
const { configs } = await getJson("/api/config/resolved?includeSecrets=true");
|
|
94
100
|
expect(configs.some((c) => c.key === BOOTSTRAP_KEY)).toBe(false);
|
|
95
101
|
expect(configs.some((c) => c.key === "SLACK_SIGNING_SECRET")).toBe(false);
|
|
102
|
+
expect(configs.some((c) => c.key === "TRANSCRIPTION_API_KEY")).toBe(false);
|
|
96
103
|
// A legitimate worker secret still comes through.
|
|
97
104
|
expect(configs.some((c) => c.key === "SOME_WORKER_SECRET")).toBe(true);
|
|
98
105
|
});
|
|
@@ -103,12 +110,14 @@ describe("API-only config keys are never served over HTTP", () => {
|
|
|
103
110
|
);
|
|
104
111
|
expect(configs.some((c) => c.key === BOOTSTRAP_KEY)).toBe(false);
|
|
105
112
|
expect(configs.some((c) => c.key === "SLACK_SIGNING_SECRET")).toBe(false);
|
|
113
|
+
expect(configs.some((c) => c.key === "TRANSCRIPTION_API_KEY")).toBe(false);
|
|
106
114
|
});
|
|
107
115
|
|
|
108
116
|
test("GET /api/config?scope=global strips the bootstrap key", async () => {
|
|
109
117
|
const { configs } = await getJson("/api/config?scope=global&includeSecrets=true");
|
|
110
118
|
expect(configs.some((c) => c.key === BOOTSTRAP_KEY)).toBe(false);
|
|
111
119
|
expect(configs.some((c) => c.key === "SLACK_SIGNING_SECRET")).toBe(false);
|
|
120
|
+
expect(configs.some((c) => c.key === "TRANSCRIPTION_API_KEY")).toBe(false);
|
|
112
121
|
expect(configs.some((c) => c.key === "SOME_WORKER_SECRET")).toBe(true);
|
|
113
122
|
});
|
|
114
123
|
});
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import { afterAll, beforeAll, describe, expect, test } from "bun:test";
|
|
2
2
|
import { createServer, type Server } from "node:http";
|
|
3
3
|
import { closeDb, createAgent, initDb, upsertSwarmConfig } from "../be/db";
|
|
4
|
+
import { mintAgentToken, revokeAgentTokens } from "../be/users";
|
|
4
5
|
import { handleConfig } from "../http/config";
|
|
5
6
|
import { handleCore } from "../http/core";
|
|
6
7
|
import { handleSessions } from "../http/sessions";
|
|
@@ -116,6 +117,19 @@ describe("config HTTP session-token authorization", () => {
|
|
|
116
117
|
expect((await response.json()).configs[0].value).toBe("global");
|
|
117
118
|
});
|
|
118
119
|
|
|
120
|
+
test("agent token resolved config without agentId reads its own agent scope", async () => {
|
|
121
|
+
const token = await mintAgentToken(workerId);
|
|
122
|
+
try {
|
|
123
|
+
const response = await fetch(`${baseUrl}/api/config/resolved?includeSecrets=true`, {
|
|
124
|
+
headers: headers(token.plaintext),
|
|
125
|
+
});
|
|
126
|
+
expect(response.status).toBe(200);
|
|
127
|
+
expect(await response.text()).toContain(SECRET);
|
|
128
|
+
} finally {
|
|
129
|
+
await revokeAgentTokens(workerId);
|
|
130
|
+
}
|
|
131
|
+
});
|
|
132
|
+
|
|
119
133
|
for (const method of ["PUT", "DELETE"] as const) {
|
|
120
134
|
test(`${method} cannot escalate a non-lead session by changing only X-Agent-ID`, async () => {
|
|
121
135
|
const token = await mint(workerId);
|
|
@@ -336,7 +336,7 @@ describe("page-session CORS over HTTP", () => {
|
|
|
336
336
|
const cookie = response.headers.get("set-cookie")!.split(";")[0]!;
|
|
337
337
|
for (const path of [`/p/${pageId}.json`, `/@swarm/api/pages/${pageId}`]) {
|
|
338
338
|
const page = await fetch(`${base}${path}`, {
|
|
339
|
-
headers: { Origin: origin, Cookie: cookie },
|
|
339
|
+
headers: { Origin: origin, Cookie: cookie, "X-Swarm-Page-Id": pageId },
|
|
340
340
|
});
|
|
341
341
|
expect(page.status).toBe(200);
|
|
342
342
|
expect(page.headers.get("access-control-allow-origin")).toBe(
|