@desplega.ai/agent-swarm 2.5.2 → 2.5.4
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-csh0zz3b.js → acp-adapter-ek38cwsv.js} +5 -5
- package/dist/{actions-6xx079d1.js → actions-92mepq03.js} +19 -19
- package/dist/{amp-adapter-8n6f0hz8.js → amp-adapter-9gaxg015.js} +4 -4
- package/dist/{app-ft6r6v23.js → app-ctn8gsgx.js} +3 -3
- package/dist/{assistant-vt2cyfzd.js → assistant-zyr5kz47.js} +21 -21
- package/dist/{boot-reembed-tgrfy27t.js → boot-reembed-dwzfttdr.js} +1 -1
- package/dist/{boot-reembed-r3e808ky.js → boot-reembed-w75maga2.js} +1 -1
- package/dist/{boot-scrub-sweep-masdwxkm.js → boot-scrub-sweep-e9kq43kf.js} +3 -3
- package/dist/{boot-seeds-0n82qc7q.js → boot-seeds-x28v3fhb.js} +4 -4
- package/dist/{claude-adapter-s7j47k06.js → claude-adapter-f0afqnne.js} +5 -5
- package/dist/{claude-managed-adapter-gt6pa35d.js → claude-managed-adapter-x81a7r65.js} +3 -3
- package/dist/{claude-managed-setup-3hzrgrnk.js → claude-managed-setup-bqzwjd9x.js} +2 -2
- package/dist/{claude-sdk-session-0zrdzyx2.js → claude-sdk-session-7b7whkaq.js} +9307 -9254
- package/dist/{cli-a5ebc95n.js → cli-0shx9j93.js} +16 -13
- package/dist/{cli-4qkg9968.js → cli-19202kew.js} +4 -4
- package/dist/{cli-cyfgxnvb.js → cli-1r5ad7j5.js} +19 -17
- package/dist/{cli-jmcjbjkt.js → cli-25n3mv5d.js} +9 -7
- package/dist/cli-2655k16q.js +140 -0
- package/dist/{cli-wadzapwb.js → cli-277c31dw.js} +1 -1
- package/dist/{cli-n6kmqngw.js → cli-4b2z6n0m.js} +29 -7
- package/dist/{cli-7ccf7akh.js → cli-5bnsc0e1.js} +9 -7
- package/dist/{cli-qrjv7t18.js → cli-621ce7kw.js} +1 -1
- package/dist/{cli-xzge46sj.js → cli-64mh9mf3.js} +20 -6
- package/dist/{cli-b6tnjdvx.js → cli-6a1xg1p7.js} +6 -2
- package/dist/{cli-ydg6ep51.js → cli-6n8axjv1.js} +1 -1
- package/dist/{cli-e91vshb1.js → cli-7p4jxgtt.js} +5 -5
- package/dist/{cli-f3pfn4x3.js → cli-9j129brq.js} +9 -3
- package/dist/{cli-xw9q173h.js → cli-9s05422w.js} +1 -1
- package/dist/{cli-15xd0j7w.js → cli-abs8ndtr.js} +12 -12
- package/dist/{cli-h5d22pfs.js → cli-asq5ydm4.js} +219 -309
- package/dist/{cli-w9etnnxk.js → cli-bf8ma3f1.js} +120 -50
- package/dist/{cli-0vgy5wfw.js → cli-bvpgr9ny.js} +2 -2
- package/dist/{cli-3e9vs93g.js → cli-c61mbdds.js} +4 -4
- package/dist/{cli-e5bxnjps.js → cli-c952c7ac.js} +5 -5
- package/dist/{cli-p859hcc8.js → cli-dew6x255.js} +1 -1
- package/dist/{cli-tx00c0h6.js → cli-dqygeeh5.js} +17 -1
- package/dist/{cli-gpv60278.js → cli-e42vss7b.js} +2 -2
- package/dist/{cli-b63d08rs.js → cli-eftzyh9y.js} +1 -1
- package/dist/{cli-fgn7yg5r.js → cli-ethgy7ha.js} +1 -1
- package/dist/{cli-86zpfje5.js → cli-ff1tm9n9.js} +1 -1
- package/dist/{cli-cf63dnxj.js → cli-g9vd9ece.js} +37 -12
- package/dist/{cli-j3dwd4kz.js → cli-gbfxp48k.js} +770 -126
- package/dist/{cli-n2918sed.js → cli-gn5af0x5.js} +21 -21
- package/dist/{cli-d172svcw.js → cli-gwhjmh8g.js} +4 -4
- package/dist/{cli-stxdrqs2.js → cli-hh5we352.js} +24 -5
- package/dist/{cli-0ebey9hg.js → cli-j6rktc91.js} +1 -1
- package/dist/{cli-csh8fnbg.js → cli-jb30dzvk.js} +473 -58
- package/dist/{cli-vaxw3bg0.js → cli-jbnn4vxb.js} +3 -3
- package/dist/{cli-ha22h0eg.js → cli-k4tjsdx9.js} +45 -40
- package/dist/{cli-f55b6ky0.js → cli-m4ecdxvz.js} +40 -13
- package/dist/{cli-mntxtkhn.js → cli-mz9afxxf.js} +2 -1
- package/dist/{cli-ta8bd7th.js → cli-pzatvw94.js} +1 -1
- package/dist/{cli-3qv8naq8.js → cli-rr7zx19j.js} +1591 -353
- package/dist/{cli-b1tv22wr.js → cli-s682zgqz.js} +1 -1
- package/dist/{cli-bpbcpd8b.js → cli-sbwsxvtd.js} +1 -1
- package/dist/{cli-0xk505e8.js → cli-sxgtf5fw.js} +1 -1
- package/dist/{cli-6y43v79h.js → cli-tsma7p8v.js} +1 -1
- package/dist/{cli-edtm741g.js → cli-wghjkr0e.js} +5 -5
- package/dist/{cli-aaqnqpyw.js → cli-wx2nj19m.js} +30 -11
- package/dist/{cli-y3dzwyy3.js → cli-x0f2v69c.js} +180 -60
- package/dist/{cli-kcnwn85g.js → cli-xtrz6bzs.js} +7 -7
- package/dist/{cli-14v1sxxy.js → cli-xzfq4at3.js} +7 -7
- package/dist/cli.js +20 -20
- package/dist/{codex-adapter-brh2tcxr.js → codex-adapter-wch12q99.js} +3 -3
- package/dist/{codex-hook-e2he7aca.js → codex-hook-brpnycn5.js} +1 -1
- package/dist/{codex-session-runner-0dnj21qs.js → codex-session-runner-41pfcr95.js} +2 -2
- package/dist/{commands-1cnvseqx.js → commands-9849dkj6.js} +2 -2
- package/dist/{controller-kc7s9xhn.js → controller-ja0sd95p.js} +29 -3
- package/dist/{controllers-connect-jzx7t4bn.js → controllers-connect-xp05dj6x.js} +20 -20
- package/dist/{cursor-adapter-qpwp5sfg.js → cursor-adapter-623qxqew.js} +1 -2
- package/dist/{db-4vq88jkc.js → db-47vj40dy.js} +3 -3
- package/dist/{db-v5ytg5pw.js → db-w759qmx0.js} +2 -2
- package/dist/{dispatcher-trtcrqae.js → dispatcher-7b8e4z2j.js} +17 -17
- package/dist/{docker-nfy2gc8t.js → docker-s84hjs91.js} +2 -2
- package/dist/{dsh-adapter-f20xwn1z.js → dsh-adapter-2ezw3xea.js} +2 -3
- package/dist/{external-channel-n9bymc2n.js → external-channel-e2s028da.js} +2 -2
- package/dist/{grok-adapter-4kpmmnr6.js → grok-adapter-ys7gaqee.js} +6 -6
- package/dist/{handlers-ptsraqa8.js → handlers-wy1dr6ja.js} +20 -20
- package/dist/{heartbeat-60qtxtjf.js → heartbeat-w3mntghj.js} +20 -20
- package/dist/{hook-rbktcstg.js → hook-3jjywnpb.js} +9 -3
- package/dist/{hook-ngtvh729.js → hook-g7kvv6x9.js} +3 -2
- package/dist/{http-z8xy72gp.js → http-tkp7d4kb.js} +2984 -2483
- package/dist/{index-r70q0xjx.js → index-1ef1myq2.js} +17 -17
- package/dist/{index-a9nsavhx.js → index-3n8m9ea2.js} +1 -1
- package/dist/{index-cbxkmfc5.js → index-9pn1pfkf.js} +1 -1
- package/dist/{index-s193j5fh.js → index-nmm79qgr.js} +20 -20
- package/dist/{index-6srwvd5b.js → index-p4xfz37z.js} +1 -1
- package/dist/{index-06qkmcx6.js → index-q7m26kvs.js} +33 -23
- package/dist/{index-1cscynkb.js → index-xdaahthb.js} +17 -17
- package/dist/{keepalive-a9srcphj.js → keepalive-v4ta8wgg.js} +3 -3
- package/dist/{lead-3devneqy.js → lead-sawnyqxk.js} +35 -35
- package/dist/{maintenance-0er16h2y.js → maintenance-84n1ex21.js} +1 -1
- package/dist/{oauth-refresh-sweep-b65jcyw2.js → oauth-refresh-sweep-vjnnandw.js} +2 -2
- package/dist/{onboard-wqywz5py.js → onboard-sfmpe4np.js} +2 -2
- package/dist/{opencode-adapter-e56086m1.js → opencode-adapter-jmpst4q4.js} +4 -4
- package/dist/{otel-impl-vk32s884.js → otel-impl-v6cc0zve.js} +1 -1
- package/dist/{pi-mono-adapter-tzps8f38.js → pi-mono-adapter-fk8fkcf6.js} +33 -6
- package/dist/{pricing-refresh-t27anv4z.js → pricing-refresh-fsk1e1zc.js} +3 -3
- package/dist/{rbac-roles-452665y0.js → rbac-roles-prmkdnkw.js} +2 -2
- package/dist/{rbac-roles-bmcgsxjn.js → rbac-roles-zxmxqa39.js} +1 -1
- package/dist/{render-v2-0ttsk2j4.js → render-v2-pjwxvnkw.js} +3 -3
- package/dist/{resume-a26kvr41.js → resume-frdtgfa0.js} +9 -5
- package/dist/{session-access-dgh9nkwk.js → session-access-fpez4y5q.js} +4 -4
- package/dist/{setup-9vc9qahm.js → setup-mq6m3m8q.js} +1 -1
- package/dist/{work-objects-vn8nkxwn.js → work-objects-kfahhjsq.js} +20 -20
- package/dist/{worker-1x1h8vnm.js → worker-v5e5rxj3.js} +35 -35
- package/openapi.json +3078 -2424
- package/package.json +7 -7
- package/plugin/commands/create-pr.md +2 -2
- package/plugin/pi-skills/create-pr/SKILL.md +2 -2
- package/src/be/approval-config-question.ts +389 -0
- package/src/be/automation-preflight.ts +37 -3
- package/src/be/db/approvals.ts +48 -0
- package/src/be/db/session-tree.ts +73 -15
- package/src/be/db/skill-search.ts +87 -0
- package/src/be/db/tasks/read.ts +29 -2
- package/src/be/db/tasks/write.ts +15 -5
- package/src/be/db/workflow-idempotency.ts +183 -0
- package/src/be/db/workflow-runs.ts +366 -8
- package/src/be/db.ts +92 -22
- package/src/be/migrations/239_skills_fts.sql +40 -0
- package/src/be/migrations/240_workflow_run_parent_step_attempt.sql +37 -0
- package/src/be/migrations/241_skills_meta_fts.sql +39 -0
- package/src/be/migrations/242_workflow_idempotency_keys.sql +40 -0
- package/src/be/sealed-json.ts +1 -0
- package/src/be/seed-scripts/catalog/compound-insights.inline.ts +2 -2
- package/src/be/seed-scripts/catalog/compound-insights.ts +2 -2
- package/src/be/seed-skills/bundled-files.generated.json +1 -1
- package/src/be/sql-connections.ts +7 -1
- package/src/be/swarm-config-guard.ts +25 -0
- package/src/be/users.ts +9 -0
- package/src/commands/controller.ts +33 -0
- package/src/commands/runner.ts +6 -2
- package/src/extensions/apply-task-create.ts +57 -11
- package/src/extensions/catalog.generated.json +4 -4
- package/src/extensions/contract-types.generated.ts +1 -1
- package/src/extensions/contract.ts +25 -0
- package/src/extensions/dispatcher.ts +4 -0
- package/src/extensions/post-bridge.ts +8 -0
- package/src/hooks/context-bound-note.ts +196 -0
- package/src/hooks/hook.ts +12 -0
- package/src/http/approval-requests.ts +209 -38
- package/src/http/config.ts +70 -53
- package/src/http/idempotency-key.ts +117 -0
- package/src/http/sessions.ts +27 -2
- package/src/http/workflows.ts +129 -24
- package/src/integrations/chat-sdk/inbound.ts +5 -4
- package/src/prompts/context-bound-note.ts +48 -0
- package/src/prompts/session-templates.ts +25 -1
- package/src/providers/amp-adapter.ts +30 -8
- package/src/providers/claude-adapter.ts +29 -4
- package/src/providers/claude-managed-adapter.ts +26 -0
- package/src/providers/claude-sdk-session.ts +10 -0
- package/src/providers/claude-session-events.ts +13 -0
- package/src/providers/codex-adapter.ts +12 -0
- package/src/providers/dsh-adapter.ts +1 -1
- package/src/providers/opencode-adapter.ts +18 -0
- package/src/providers/pi-mono-adapter.ts +35 -0
- package/src/runtime-providers/docker.ts +6 -2
- package/src/scripts-runtime/types/swarm-extension.d.ts +35 -0
- package/src/tests/amp-adapter.test.ts +97 -0
- package/src/tests/approval-config-question.test.ts +702 -0
- package/src/tests/approval-requests.test.ts +10 -7
- package/src/tests/approval-sweeps.test.ts +12 -6
- package/src/tests/base-prompt.test.ts +1 -1
- package/src/tests/chat-sdk-inbound.test.ts +20 -0
- package/src/tests/claude-hook-output.test.ts +79 -1
- package/src/tests/claude-managed-adapter.test.ts +275 -0
- package/src/tests/codex-adapter.test.ts +163 -0
- package/src/tests/context-bound-note.test.ts +269 -0
- package/src/tests/dsh-adapter.test.ts +1 -1
- package/src/tests/entrypoint-gitlab-credential-helper.test.ts +273 -0
- package/src/tests/extensions-example-comms-adherence-gate.test.ts +321 -12
- package/src/tests/extensions-pre-task.test.ts +273 -0
- package/src/tests/fetch-resolved-env.test.ts +17 -0
- package/src/tests/fixtures/extensions/record-blocks/hooks.ts +10 -0
- package/src/tests/fixtures/extensions/record-blocks/manifest.json +7 -0
- package/src/tests/fixtures/fake-amp.ts +22 -8
- package/src/tests/fixtures/held-template-render.ts +42 -0
- package/src/tests/fixtures/workflow-idempotency-trigger.child.ts +73 -0
- package/src/tests/list-endpoint-slimming.test.ts +33 -0
- package/src/tests/migration-240-workflow-run-parent-step-attempt.test.ts +55 -0
- package/src/tests/openapi-route-headers.test.ts +2 -0
- package/src/tests/opencode-adapter.test.ts +134 -0
- package/src/tests/pi-mono-adapter.test.ts +105 -0
- package/src/tests/prompt-template-session.test.ts +4 -3
- package/src/tests/runner-push-delivery.test.ts +25 -0
- package/src/tests/runtime-controller-liveness.test.ts +133 -0
- package/src/tests/runtime-providers-docker.test.ts +2 -0
- package/src/tests/script-connections.test.ts +9 -0
- package/src/tests/script-run-config-scope.test.ts +91 -3
- package/src/tests/session-logs.test.ts +70 -0
- package/src/tests/sessions-tree.test.ts +205 -1
- package/src/tests/skill-search-long-content.test.ts +193 -0
- package/src/tests/skill-search.test.ts +173 -22
- package/src/tests/workflow-cascade-cancel.test.ts +1169 -0
- package/src/tests/workflow-foreach-supersede-retry.test.ts +488 -0
- package/src/tests/workflow-idempotency.test.ts +915 -0
- package/src/tests/workflow-sub-workflow.test.ts +538 -1
- package/src/tools/request-human-input.ts +27 -5
- package/src/tools/workflows/cancel-workflow-run.ts +1 -1
- package/src/types.ts +7 -0
- package/src/utils/context-window.ts +8 -6
- package/src/workflows/approval-cancel.ts +15 -13
- package/src/workflows/approval-notifications.ts +15 -4
- package/src/workflows/checkpoint.ts +72 -35
- package/src/workflows/engine.ts +262 -93
- package/src/workflows/executors/base.ts +6 -0
- package/src/workflows/executors/foreach.ts +2 -2
- package/src/workflows/executors/human-in-the-loop.ts +25 -1
- package/src/workflows/executors/sub-workflow.ts +45 -16
- package/src/workflows/foreach-driver.ts +10 -3
- package/src/workflows/foreach-join.ts +29 -6
- package/src/workflows/recovery.ts +68 -1
- package/src/workflows/resume.ts +228 -77
- package/src/workflows/retry-poller.ts +74 -7
- package/src/workflows/task-step-routing.ts +29 -7
- package/templates/ai-toolbox.manifest.json +8 -8
- package/templates/community/open-prompt-showreel/PLAYBOOK.md +5 -6
- package/templates/community/open-prompt-showreel/lead-prompt.md +1 -1
- package/templates/community/open-prompt-showreel/skills/open-prompt-showreel/SKILL.md +4 -4
- package/templates/extensions/comms-adherence-gate/README.md +38 -13
- package/templates/extensions/comms-adherence-gate/hooks.ts +272 -69
- package/templates/extensions/comms-adherence-gate/manifest.json +2 -2
- package/templates/skills/agent-browser/SKILL.md +4 -4
- package/templates/skills/agent-browser/content.md +4 -4
- package/templates/skills/code-quality/config.json +1 -1
- package/templates/skills/code-quality/content.md +2 -2
- package/templates/skills/delegate-work/content.md +29 -25
- package/templates/skills/delegate-work/files/scripts/codex-exec.sh +5 -5
- package/templates/skills/qa/content.md +2 -2
- package/templates/skills/researching/content.md +2 -0
- package/templates/skills/turso-interaction/content.md +1 -1
- package/templates/skills/work-on-task/SKILL.md +1 -1
- package/templates/skills/work-on-task/content.md +1 -1
- package/templates/skills/workflow-iterate/content.md +8 -1
|
@@ -418,7 +418,7 @@ describe("Approval Requests", () => {
|
|
|
418
418
|
expect(result).toBeNull();
|
|
419
419
|
});
|
|
420
420
|
|
|
421
|
-
test("does not resolve a workflow approval after its run
|
|
421
|
+
test("does not resolve a workflow approval after its run is cancelled", async () => {
|
|
422
422
|
const workflow = await createWorkflow({
|
|
423
423
|
name: `approval-cancel-${crypto.randomUUID()}`,
|
|
424
424
|
definition: { nodes: [] },
|
|
@@ -432,11 +432,12 @@ describe("Approval Requests", () => {
|
|
|
432
432
|
nodeId: "approval",
|
|
433
433
|
nodeType: "human-in-the-loop",
|
|
434
434
|
});
|
|
435
|
-
|
|
436
|
-
|
|
435
|
+
// Asked while the run was live; the run was cancelled after, leaving
|
|
436
|
+
// the request pending (a cancelled step's trigger would close it).
|
|
437
437
|
const approval = await createApprovalRequest(
|
|
438
438
|
makeApprovalData({ workflowRunId: runId, workflowRunStepId: stepId }),
|
|
439
439
|
);
|
|
440
|
+
await updateWorkflowRun(runId, { status: "cancelled" });
|
|
440
441
|
|
|
441
442
|
const result = await resolveApprovalRequest(
|
|
442
443
|
approval.id,
|
|
@@ -1864,15 +1865,17 @@ describe("Approval Requests", () => {
|
|
|
1864
1865
|
|
|
1865
1866
|
test("cancel on a waiting run cancels the run, its step, and its task", async () => {
|
|
1866
1867
|
const { runId, stepId, approvalId } = await makeRun("waiting");
|
|
1868
|
+
// Linked directly: the engine binds a step's task while the step is
|
|
1869
|
+
// still running, and this step is already parked.
|
|
1867
1870
|
const task = await createTaskExtended("step task", {
|
|
1868
1871
|
agentId: owner.id,
|
|
1869
1872
|
source: "mcp",
|
|
1870
1873
|
workflowRunId: runId,
|
|
1871
|
-
workflowRunStepId: stepId,
|
|
1872
1874
|
});
|
|
1873
|
-
await getDbClient().run(
|
|
1874
|
-
|
|
1875
|
-
|
|
1875
|
+
await getDbClient().run(
|
|
1876
|
+
"UPDATE agent_tasks SET status = 'in_progress', workflowRunStepId = ? WHERE id = ?",
|
|
1877
|
+
[stepId, task.id],
|
|
1878
|
+
);
|
|
1876
1879
|
|
|
1877
1880
|
const res = await post(
|
|
1878
1881
|
`/api/approval-requests/${approvalId}/cancel`,
|
|
@@ -223,15 +223,17 @@ describe("approval sweeps", () => {
|
|
|
223
223
|
|
|
224
224
|
test("cancels the waiting run, its step, and its linked task", async () => {
|
|
225
225
|
const { runId, stepId } = await makeRun("waiting");
|
|
226
|
+
// Linked directly: the engine binds a step's task while the step is
|
|
227
|
+
// still running, and this step is already parked.
|
|
226
228
|
const task = await createTaskExtended("step task", {
|
|
227
229
|
agentId,
|
|
228
230
|
source: "mcp",
|
|
229
231
|
workflowRunId: runId,
|
|
230
|
-
workflowRunStepId: stepId,
|
|
231
232
|
});
|
|
232
|
-
await getDbClient().run(
|
|
233
|
-
|
|
234
|
-
|
|
233
|
+
await getDbClient().run(
|
|
234
|
+
"UPDATE agent_tasks SET status = 'in_progress', workflowRunStepId = ? WHERE id = ?",
|
|
235
|
+
[stepId, task.id],
|
|
236
|
+
);
|
|
235
237
|
const id = await makeRequest({ ageDays: 8, workflowRunId: runId, workflowRunStepId: stepId });
|
|
236
238
|
|
|
237
239
|
const result = await autoCancelStaleApprovalRequests();
|
|
@@ -300,8 +302,10 @@ describe("approval sweeps", () => {
|
|
|
300
302
|
});
|
|
301
303
|
|
|
302
304
|
test("leaves a failed run as it is", async () => {
|
|
303
|
-
|
|
305
|
+
// The request was asked while the run was live; the run failed after.
|
|
306
|
+
const { runId, stepId } = await makeRun("waiting");
|
|
304
307
|
const id = await makeRequest({ ageDays: 8, workflowRunId: runId, workflowRunStepId: stepId });
|
|
308
|
+
await updateWorkflowRun(runId, { status: "failed" });
|
|
305
309
|
|
|
306
310
|
const result = await autoCancelStaleApprovalRequests();
|
|
307
311
|
|
|
@@ -415,13 +419,15 @@ describe("approval sweeps", () => {
|
|
|
415
419
|
});
|
|
416
420
|
|
|
417
421
|
test("times out a request on a completed run", async () => {
|
|
418
|
-
|
|
422
|
+
// The request was asked while the run was live; the run completed after.
|
|
423
|
+
const { runId, stepId } = await makeRun("waiting");
|
|
419
424
|
const id = await makeRequest({
|
|
420
425
|
ageDays: 0,
|
|
421
426
|
expiresInMs: -60_000,
|
|
422
427
|
workflowRunId: runId,
|
|
423
428
|
workflowRunStepId: stepId,
|
|
424
429
|
});
|
|
430
|
+
await updateWorkflowRun(runId, { status: "completed" });
|
|
425
431
|
await timeoutExpiredApprovalRequests();
|
|
426
432
|
expect((await getApprovalRequestById(id))!.status).toBe("timeout");
|
|
427
433
|
});
|
|
@@ -490,7 +490,7 @@ describe("getBasePrompt: outputs section", () => {
|
|
|
490
490
|
expect(result).toContain("## Outputs");
|
|
491
491
|
expect(result).toContain(AGENT_FS_LINE);
|
|
492
492
|
expect(result).toContain(
|
|
493
|
-
|
|
493
|
+
`Give people the live app link \`\${AGENT_FS_LIVE_URL}/file/~/<org>/<drive>/<path>\`. Make an \`agent-fs share-create\` link only when the requester asks for one or the reader has no agent-fs login.`,
|
|
494
494
|
);
|
|
495
495
|
expect(result).not.toContain(NO_AGENT_FS_LINE);
|
|
496
496
|
});
|
|
@@ -332,6 +332,26 @@ describe("Chat SDK inbound pipeline", () => {
|
|
|
332
332
|
}
|
|
333
333
|
});
|
|
334
334
|
|
|
335
|
+
test("a mention in a subscribed group gets 👀 before the flush, and the flush does not react again", async () => {
|
|
336
|
+
await postTelegramUpdate(telegramUpdate(group(-2991), "@swarm_test_bot first ask"));
|
|
337
|
+
await flushChatBuffers();
|
|
338
|
+
const ask = telegramUpdate(group(-2991), "@swarm_test_bot second ask");
|
|
339
|
+
await postTelegramUpdate(ask);
|
|
340
|
+
expect(reactionsOn(-2991, ask)).toEqual(["👀"]);
|
|
341
|
+
await flushChatBuffers();
|
|
342
|
+
expect(await chatTasks(-2991)).toHaveLength(2);
|
|
343
|
+
expect(reactionsOn(-2991, ask)).toEqual(["👀"]);
|
|
344
|
+
});
|
|
345
|
+
|
|
346
|
+
test("a !now mention in a subscribed group gets ⚡, not 👀", async () => {
|
|
347
|
+
await postTelegramUpdate(telegramUpdate(group(-2992), "@swarm_test_bot first ask"));
|
|
348
|
+
await flushChatBuffers();
|
|
349
|
+
const ask = telegramUpdate(group(-2992), "@swarm_test_bot !now second ask");
|
|
350
|
+
await postTelegramUpdate(ask);
|
|
351
|
+
expect(await chatTasks(-2992)).toHaveLength(2);
|
|
352
|
+
expect(reactionsOn(-2992, ask)).toEqual(["⚡"]);
|
|
353
|
+
});
|
|
354
|
+
|
|
335
355
|
test("two asks back to back each anchor to their own message, not the parent's", async () => {
|
|
336
356
|
const firstAsk = telegramUpdate(group(-2010), "@swarm_test_bot !now summarize the PR");
|
|
337
357
|
await postTelegramUpdate(firstAsk);
|
|
@@ -82,7 +82,11 @@ afterAll(async () => {
|
|
|
82
82
|
await rm(projectDir, { recursive: true, force: true });
|
|
83
83
|
});
|
|
84
84
|
|
|
85
|
-
async function runHook(
|
|
85
|
+
async function runHook(
|
|
86
|
+
event: Record<string, unknown>,
|
|
87
|
+
taskId: string,
|
|
88
|
+
extraEnv: Record<string, string> = {},
|
|
89
|
+
) {
|
|
86
90
|
const taskFile = join(projectDir, `task-${taskId}.json`);
|
|
87
91
|
await Bun.write(
|
|
88
92
|
taskFile,
|
|
@@ -92,6 +96,8 @@ async function runHook(event: Record<string, unknown>, taskId: string) {
|
|
|
92
96
|
...process.env,
|
|
93
97
|
CLAUDE_PROJECT_DIR: projectDir,
|
|
94
98
|
TASK_FILE: taskFile,
|
|
99
|
+
CONTEXT_BOUND_NOTE_TOKENS: undefined,
|
|
100
|
+
...extraEnv,
|
|
95
101
|
};
|
|
96
102
|
return runChild(["bun", HOOK_PATH], {
|
|
97
103
|
cwd: projectDir,
|
|
@@ -107,6 +113,9 @@ function parseSoleJson(stdout: string): Record<string, unknown> {
|
|
|
107
113
|
return JSON.parse(trimmed) as Record<string, unknown>;
|
|
108
114
|
}
|
|
109
115
|
|
|
116
|
+
/** The note is off unless CONTEXT_BOUND_NOTE_TOKENS is set. */
|
|
117
|
+
const noteOn = { CONTEXT_BOUND_NOTE_TOKENS: "200000" };
|
|
118
|
+
|
|
110
119
|
const bashTouch = {
|
|
111
120
|
tool_name: "Bash",
|
|
112
121
|
tool_input: { command: "touch /tmp/probe" },
|
|
@@ -230,4 +239,73 @@ describe("claude hook stdout", () => {
|
|
|
230
239
|
},
|
|
231
240
|
CHILD_PROCESS_TEST_BUDGET_MS,
|
|
232
241
|
);
|
|
242
|
+
|
|
243
|
+
test(
|
|
244
|
+
"worker context-bound note fires once as PostToolUse additionalContext",
|
|
245
|
+
async () => {
|
|
246
|
+
Object.assign(state, { isLead: false, cancelledTaskId: null, shouldBlockPolling: false });
|
|
247
|
+
const taskId = crypto.randomUUID();
|
|
248
|
+
await Bun.write(
|
|
249
|
+
join(projectDir, `task-${taskId}.json.context.json`),
|
|
250
|
+
JSON.stringify({ contextTokens: 250_000, model: "claude-opus-5-5" }),
|
|
251
|
+
);
|
|
252
|
+
const event = { hook_event_name: "PostToolUse", ...bashTouch, tool_response: {} };
|
|
253
|
+
|
|
254
|
+
const first = await runHook(event, taskId, noteOn);
|
|
255
|
+
const out = parseSoleJson(first.stdout) as { hookSpecificOutput: Record<string, string> };
|
|
256
|
+
expect(out.hookSpecificOutput.hookEventName).toBe("PostToolUse");
|
|
257
|
+
expect(out.hookSpecificOutput.additionalContext).toStartWith(
|
|
258
|
+
"Context is 250,000 tokens (25% of the 1,000,000-token window). Keep this task bounded",
|
|
259
|
+
);
|
|
260
|
+
|
|
261
|
+
const second = await runHook(event, taskId, noteOn);
|
|
262
|
+
expect(second.exitCode).toBe(0);
|
|
263
|
+
expect(second.stdout.trim()).toBe("");
|
|
264
|
+
},
|
|
265
|
+
CHILD_PROCESS_TEST_BUDGET_MS * 2,
|
|
266
|
+
);
|
|
267
|
+
|
|
268
|
+
test(
|
|
269
|
+
"a subagent tool call does not consume the context-bound note",
|
|
270
|
+
async () => {
|
|
271
|
+
Object.assign(state, { isLead: false, cancelledTaskId: null, shouldBlockPolling: false });
|
|
272
|
+
const taskId = crypto.randomUUID();
|
|
273
|
+
await Bun.write(
|
|
274
|
+
join(projectDir, `task-${taskId}.json.context.json`),
|
|
275
|
+
JSON.stringify({ contextTokens: 250_000, model: "made-up-model" }),
|
|
276
|
+
);
|
|
277
|
+
const event = { hook_event_name: "PostToolUse", ...bashTouch, tool_response: {} };
|
|
278
|
+
|
|
279
|
+
const child = await runHook({ ...event, agent_id: "child" }, taskId, noteOn);
|
|
280
|
+
expect(child.exitCode).toBe(0);
|
|
281
|
+
expect(child.stdout.trim()).toBe("");
|
|
282
|
+
|
|
283
|
+
// Unknown model: no window share rather than a percent of the 200k fallback.
|
|
284
|
+
const main = await runHook(event, taskId, noteOn);
|
|
285
|
+
const out = parseSoleJson(main.stdout) as { hookSpecificOutput: Record<string, string> };
|
|
286
|
+
expect(out.hookSpecificOutput.additionalContext).toStartWith(
|
|
287
|
+
"Context is 250,000 tokens. Keep this task bounded",
|
|
288
|
+
);
|
|
289
|
+
},
|
|
290
|
+
CHILD_PROCESS_TEST_BUDGET_MS * 2,
|
|
291
|
+
);
|
|
292
|
+
|
|
293
|
+
test(
|
|
294
|
+
"the context-bound note is off when CONTEXT_BOUND_NOTE_TOKENS is unset",
|
|
295
|
+
async () => {
|
|
296
|
+
Object.assign(state, { isLead: false, cancelledTaskId: null, shouldBlockPolling: false });
|
|
297
|
+
const taskId = crypto.randomUUID();
|
|
298
|
+
await Bun.write(
|
|
299
|
+
join(projectDir, `task-${taskId}.json.context.json`),
|
|
300
|
+
JSON.stringify({ contextTokens: 900_000, model: "claude-opus-5-5" }),
|
|
301
|
+
);
|
|
302
|
+
const result = await runHook(
|
|
303
|
+
{ hook_event_name: "PostToolUse", ...bashTouch, tool_response: {} },
|
|
304
|
+
taskId,
|
|
305
|
+
);
|
|
306
|
+
expect(result.exitCode).toBe(0);
|
|
307
|
+
expect(result.stdout.trim()).toBe("");
|
|
308
|
+
},
|
|
309
|
+
CHILD_PROCESS_TEST_BUDGET_MS,
|
|
310
|
+
);
|
|
233
311
|
});
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import { afterAll, afterEach, beforeAll, describe, expect, test } from "bun:test";
|
|
2
2
|
import { mkdirSync, rmSync } from "node:fs";
|
|
3
3
|
import { join } from "node:path";
|
|
4
|
+
import { renderContextBoundNote } from "../prompts/context-bound-note";
|
|
4
5
|
import { createProviderAdapter } from "../providers";
|
|
5
6
|
import {
|
|
6
7
|
ClaudeManagedAdapter,
|
|
@@ -13,6 +14,7 @@ import {
|
|
|
13
14
|
getClaudeManagedModelPricing,
|
|
14
15
|
} from "../providers/claude-managed-models";
|
|
15
16
|
import type { ProviderEvent, ProviderSessionConfig } from "../providers/types";
|
|
17
|
+
import { HELD_NOTE_TEXT, holdTemplateRender } from "./fixtures/held-template-render";
|
|
16
18
|
|
|
17
19
|
// Stash + restore env vars so this file plays nicely with the rest of the
|
|
18
20
|
// suite (other tests don't expect MANAGED_AGENT_ID / MANAGED_ENVIRONMENT_ID
|
|
@@ -1503,3 +1505,276 @@ describe("ClaudeManagedAdapter (Phase 6) — full happy-path integration", () =>
|
|
|
1503
1505
|
expect(result.output).toBe("Read the file and here's what I found.");
|
|
1504
1506
|
});
|
|
1505
1507
|
});
|
|
1508
|
+
|
|
1509
|
+
describe("ClaudeManagedAdapter — one-time context-bound note", () => {
|
|
1510
|
+
const tmpLogDir = `/tmp/claude-managed-adapter-bound-note-${Date.now()}`;
|
|
1511
|
+
let seq = 0;
|
|
1512
|
+
|
|
1513
|
+
beforeAll(() => {
|
|
1514
|
+
mkdirSync(tmpLogDir, { recursive: true });
|
|
1515
|
+
process.env.ANTHROPIC_API_KEY = "sk-test";
|
|
1516
|
+
process.env.MANAGED_AGENT_ID = "agent_x";
|
|
1517
|
+
process.env.MANAGED_ENVIRONMENT_ID = "env_x";
|
|
1518
|
+
});
|
|
1519
|
+
|
|
1520
|
+
afterAll(() => {
|
|
1521
|
+
rmSync(tmpLogDir, { recursive: true, force: true });
|
|
1522
|
+
});
|
|
1523
|
+
|
|
1524
|
+
/** A main-thread model request whose prompt is `input + cacheRead + cacheWrite`. */
|
|
1525
|
+
function requestEnd(input: unknown, cacheRead: unknown = 0, cacheWrite: unknown = 0) {
|
|
1526
|
+
seq += 1;
|
|
1527
|
+
return {
|
|
1528
|
+
type: "span.model_request_end",
|
|
1529
|
+
id: `req-${seq}`,
|
|
1530
|
+
processed_at: "2026-01-01T00:00:00Z",
|
|
1531
|
+
is_error: false,
|
|
1532
|
+
model_request_start_id: `start-${seq}`,
|
|
1533
|
+
model_usage: {
|
|
1534
|
+
input_tokens: input,
|
|
1535
|
+
output_tokens: 10,
|
|
1536
|
+
cache_read_input_tokens: cacheRead,
|
|
1537
|
+
cache_creation_input_tokens: cacheWrite,
|
|
1538
|
+
},
|
|
1539
|
+
};
|
|
1540
|
+
}
|
|
1541
|
+
|
|
1542
|
+
function toolUse(kind: "agent.tool_use" | "agent.mcp_tool_use" = "agent.tool_use") {
|
|
1543
|
+
seq += 1;
|
|
1544
|
+
return {
|
|
1545
|
+
type: kind,
|
|
1546
|
+
id: `tu-${seq}`,
|
|
1547
|
+
processed_at: "2026-01-01T00:00:00Z",
|
|
1548
|
+
name: "read_file",
|
|
1549
|
+
...(kind === "agent.mcp_tool_use" ? { mcp_server_name: "agent-swarm" } : {}),
|
|
1550
|
+
input: { path: `/tmp/${seq}` },
|
|
1551
|
+
};
|
|
1552
|
+
}
|
|
1553
|
+
|
|
1554
|
+
const idle = () => ({
|
|
1555
|
+
type: "session.status_idle",
|
|
1556
|
+
id: `idle-${++seq}`,
|
|
1557
|
+
processed_at: "2026-01-01T00:00:00Z",
|
|
1558
|
+
stop_reason: { type: "end_turn" },
|
|
1559
|
+
});
|
|
1560
|
+
|
|
1561
|
+
/** Run a session over `events` and return the texts of every message sent after the prompt. */
|
|
1562
|
+
async function runNotes(
|
|
1563
|
+
events: Array<Record<string, unknown>>,
|
|
1564
|
+
threshold: string,
|
|
1565
|
+
onSend?: Parameters<typeof makeFakeClient>[0]["onSend"],
|
|
1566
|
+
model?: string,
|
|
1567
|
+
) {
|
|
1568
|
+
const spy = makeFakeClient({
|
|
1569
|
+
streamEvents: async function* () {
|
|
1570
|
+
for (const e of events) yield e;
|
|
1571
|
+
},
|
|
1572
|
+
onSend,
|
|
1573
|
+
});
|
|
1574
|
+
const adapter = new ClaudeManagedAdapter({ client: spy.client });
|
|
1575
|
+
const session = await adapter.createSession(
|
|
1576
|
+
tConfig({
|
|
1577
|
+
logFile: join(tmpLogDir, `note-${++seq}.log`),
|
|
1578
|
+
env: { CONTEXT_BOUND_NOTE_TOKENS: threshold },
|
|
1579
|
+
...(model ? { model } : {}),
|
|
1580
|
+
}),
|
|
1581
|
+
);
|
|
1582
|
+
const result = await session.waitForCompletion();
|
|
1583
|
+
// The note is rendered and sent off the event loop; let it land.
|
|
1584
|
+
await Bun.sleep(150);
|
|
1585
|
+
const notes = spy.sent.slice(1).map((sent) => {
|
|
1586
|
+
const content = sent.events[0]?.content as Array<{ text?: string }> | undefined;
|
|
1587
|
+
return { type: sent.events[0]?.type, text: content?.[0]?.text };
|
|
1588
|
+
});
|
|
1589
|
+
return { result, notes };
|
|
1590
|
+
}
|
|
1591
|
+
|
|
1592
|
+
test("fires once when a tool call follows a request at the threshold, not again", async () => {
|
|
1593
|
+
const { result, notes } = await runNotes(
|
|
1594
|
+
[
|
|
1595
|
+
requestEnd(150_000, 40_000, 10_000),
|
|
1596
|
+
toolUse(),
|
|
1597
|
+
requestEnd(240_000, 10_000, 0),
|
|
1598
|
+
toolUse("agent.mcp_tool_use"),
|
|
1599
|
+
requestEnd(260_000),
|
|
1600
|
+
toolUse(),
|
|
1601
|
+
idle(),
|
|
1602
|
+
],
|
|
1603
|
+
"200000",
|
|
1604
|
+
);
|
|
1605
|
+
expect(result.isError).toBe(false);
|
|
1606
|
+
expect(notes).toEqual([
|
|
1607
|
+
{ type: "user.message", text: await renderContextBoundNote(200_000, "claude-sonnet-4-6") },
|
|
1608
|
+
]);
|
|
1609
|
+
});
|
|
1610
|
+
|
|
1611
|
+
test("the note shows the window share for a known model and omits it for an unknown one", async () => {
|
|
1612
|
+
const noteFor = async (model: string) =>
|
|
1613
|
+
(await runNotes([requestEnd(250_000), toolUse(), idle()], "200000", undefined, model)).notes;
|
|
1614
|
+
const rest =
|
|
1615
|
+
"Keep this task bounded: finish the current step, store-progress, and complete or defer instead of starting new exploration.";
|
|
1616
|
+
expect(await noteFor("claude-sonnet-4-6")).toEqual([
|
|
1617
|
+
{
|
|
1618
|
+
type: "user.message",
|
|
1619
|
+
text: `Context is 250,000 tokens (25% of the 1,000,000-token window). ${rest}`,
|
|
1620
|
+
},
|
|
1621
|
+
]);
|
|
1622
|
+
expect(await noteFor("made-up-model")).toEqual([
|
|
1623
|
+
{ type: "user.message", text: `Context is 250,000 tokens. ${rest}` },
|
|
1624
|
+
]);
|
|
1625
|
+
});
|
|
1626
|
+
|
|
1627
|
+
test("no-op below the threshold, then fires once it is crossed", async () => {
|
|
1628
|
+
const { notes } = await runNotes(
|
|
1629
|
+
[requestEnd(199_999), toolUse(), requestEnd(205_000), toolUse(), idle()],
|
|
1630
|
+
"200000",
|
|
1631
|
+
);
|
|
1632
|
+
expect(notes).toEqual([
|
|
1633
|
+
{ type: "user.message", text: await renderContextBoundNote(205_000, "claude-sonnet-4-6") },
|
|
1634
|
+
]);
|
|
1635
|
+
});
|
|
1636
|
+
|
|
1637
|
+
test("off at 0 and when unset", async () => {
|
|
1638
|
+
const events = () => [requestEnd(500_000), toolUse(), idle()];
|
|
1639
|
+
expect((await runNotes(events(), "0")).notes).toEqual([]);
|
|
1640
|
+
expect((await runNotes(events(), "")).notes).toEqual([]);
|
|
1641
|
+
// Control: the same session with a threshold gets the note.
|
|
1642
|
+
expect((await runNotes(events(), "200000")).notes).toHaveLength(1);
|
|
1643
|
+
});
|
|
1644
|
+
|
|
1645
|
+
test("no note when the request ends the turn without a tool call", async () => {
|
|
1646
|
+
expect((await runNotes([requestEnd(300_000), idle()], "200000")).notes).toEqual([]);
|
|
1647
|
+
// Control: a tool call after the same request carries the note.
|
|
1648
|
+
expect((await runNotes([requestEnd(300_000), toolUse(), idle()], "200000")).notes).toHaveLength(
|
|
1649
|
+
1,
|
|
1650
|
+
);
|
|
1651
|
+
});
|
|
1652
|
+
|
|
1653
|
+
test("fails open on missing or corrupt usage", async () => {
|
|
1654
|
+
const { result, notes } = await runNotes(
|
|
1655
|
+
[
|
|
1656
|
+
// Missing: a tool call before any request reported usage.
|
|
1657
|
+
toolUse(),
|
|
1658
|
+
// Corrupt: non-numeric usage fields.
|
|
1659
|
+
requestEnd("lots", null, {}),
|
|
1660
|
+
toolUse(),
|
|
1661
|
+
requestEnd(Number.NaN),
|
|
1662
|
+
toolUse(),
|
|
1663
|
+
// Control: a valid request afterwards still gets the note.
|
|
1664
|
+
requestEnd(210_000),
|
|
1665
|
+
toolUse(),
|
|
1666
|
+
idle(),
|
|
1667
|
+
],
|
|
1668
|
+
"200000",
|
|
1669
|
+
);
|
|
1670
|
+
expect(result.isError).toBe(false);
|
|
1671
|
+
expect(notes).toEqual([
|
|
1672
|
+
{ type: "user.message", text: await renderContextBoundNote(210_000, "claude-sonnet-4-6") },
|
|
1673
|
+
]);
|
|
1674
|
+
});
|
|
1675
|
+
|
|
1676
|
+
test("fails open when sending the note rejects", async () => {
|
|
1677
|
+
let sends = 0;
|
|
1678
|
+
const { result, notes } = await runNotes(
|
|
1679
|
+
[requestEnd(300_000), toolUse(), requestEnd(310_000), toolUse(), idle()],
|
|
1680
|
+
"200000",
|
|
1681
|
+
() => {
|
|
1682
|
+
sends += 1;
|
|
1683
|
+
if (sends > 1) throw new Error("send failed");
|
|
1684
|
+
},
|
|
1685
|
+
);
|
|
1686
|
+
expect(result.isError).toBe(false);
|
|
1687
|
+
// One attempt only: a failed send does not re-arm the note.
|
|
1688
|
+
expect(notes).toHaveLength(1);
|
|
1689
|
+
});
|
|
1690
|
+
|
|
1691
|
+
describe("note rendered late", () => {
|
|
1692
|
+
const toolResult = (toolUseId: string) => ({
|
|
1693
|
+
type: "agent.tool_result",
|
|
1694
|
+
id: `tr-${++seq}`,
|
|
1695
|
+
processed_at: "2026-01-01T00:00:00Z",
|
|
1696
|
+
tool_use_id: toolUseId,
|
|
1697
|
+
content: [],
|
|
1698
|
+
});
|
|
1699
|
+
|
|
1700
|
+
/**
|
|
1701
|
+
* Holds the render until the stream reaches `releaseAt`. Returns the
|
|
1702
|
+
* `user.message` events sent after the prompt, read once the late render
|
|
1703
|
+
* had time to land.
|
|
1704
|
+
*/
|
|
1705
|
+
async function lateNotes(
|
|
1706
|
+
releaseAt: "tool-open" | "tool-returned" | "idle" | "abort",
|
|
1707
|
+
): Promise<{ notes: unknown[]; isError: boolean }> {
|
|
1708
|
+
const held = holdTemplateRender();
|
|
1709
|
+
const tool = toolUse();
|
|
1710
|
+
let unblock!: () => void;
|
|
1711
|
+
const blocked = new Promise<void>((resolve) => {
|
|
1712
|
+
unblock = resolve;
|
|
1713
|
+
});
|
|
1714
|
+
const spy = makeFakeClient({
|
|
1715
|
+
streamEvents: async function* () {
|
|
1716
|
+
yield requestEnd(300_000);
|
|
1717
|
+
yield tool;
|
|
1718
|
+
await held.requested;
|
|
1719
|
+
if (releaseAt === "tool-open") {
|
|
1720
|
+
held.release();
|
|
1721
|
+
await Bun.sleep(50);
|
|
1722
|
+
}
|
|
1723
|
+
yield toolResult(tool.id);
|
|
1724
|
+
if (releaseAt === "tool-returned") {
|
|
1725
|
+
held.release();
|
|
1726
|
+
await Bun.sleep(50);
|
|
1727
|
+
}
|
|
1728
|
+
if (releaseAt === "abort") await blocked;
|
|
1729
|
+
yield idle();
|
|
1730
|
+
},
|
|
1731
|
+
});
|
|
1732
|
+
try {
|
|
1733
|
+
const session = await new ClaudeManagedAdapter({ client: spy.client }).createSession(
|
|
1734
|
+
tConfig({
|
|
1735
|
+
logFile: join(tmpLogDir, `late-${++seq}.log`),
|
|
1736
|
+
env: { CONTEXT_BOUND_NOTE_TOKENS: "200000" },
|
|
1737
|
+
}),
|
|
1738
|
+
);
|
|
1739
|
+
if (releaseAt === "abort") {
|
|
1740
|
+
await held.requested;
|
|
1741
|
+
await session.abort();
|
|
1742
|
+
unblock();
|
|
1743
|
+
}
|
|
1744
|
+
const result = await session.waitForCompletion();
|
|
1745
|
+
held.release();
|
|
1746
|
+
await Bun.sleep(100);
|
|
1747
|
+
const notes = spy.sent
|
|
1748
|
+
.slice(1)
|
|
1749
|
+
.filter((sent) => sent.events[0]?.type === "user.message")
|
|
1750
|
+
.map((sent) => sent.events[0]);
|
|
1751
|
+
return { notes, isError: result.isError };
|
|
1752
|
+
} finally {
|
|
1753
|
+
unblock();
|
|
1754
|
+
held.stop();
|
|
1755
|
+
}
|
|
1756
|
+
}
|
|
1757
|
+
|
|
1758
|
+
test("control: a note ready while its tool call is open is sent", async () => {
|
|
1759
|
+
const { notes, isError } = await lateNotes("tool-open");
|
|
1760
|
+
expect(isError).toBe(false);
|
|
1761
|
+
expect(notes).toEqual([
|
|
1762
|
+
{ type: "user.message", content: [{ type: "text", text: HELD_NOTE_TEXT }] },
|
|
1763
|
+
]);
|
|
1764
|
+
});
|
|
1765
|
+
|
|
1766
|
+
test("dropped once the tool result is back and the final reply is pending", async () => {
|
|
1767
|
+
expect((await lateNotes("tool-returned")).notes).toEqual([]);
|
|
1768
|
+
});
|
|
1769
|
+
|
|
1770
|
+
test("dropped after the session went idle", async () => {
|
|
1771
|
+
const { notes, isError } = await lateNotes("idle");
|
|
1772
|
+
expect(isError).toBe(false);
|
|
1773
|
+
expect(notes).toEqual([]);
|
|
1774
|
+
});
|
|
1775
|
+
|
|
1776
|
+
test("dropped after abort", async () => {
|
|
1777
|
+
expect((await lateNotes("abort")).notes).toEqual([]);
|
|
1778
|
+
});
|
|
1779
|
+
});
|
|
1780
|
+
});
|