wednesdayai 0.4.12-dev.716 → 0.4.12-dev.717
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/{agent-turn-runner-Bvj00mHU.js → agent-turn-runner-BsdyAURQ.js} +3 -3
- package/dist/{audio-preflight-yuM9Q3qh.js → audio-preflight-ifCgpG3c.js} +5 -5
- package/dist/{audio-transcription-runner-CgPA8_eG.js → audio-transcription-runner-ipSQoPp8.js} +1 -1
- package/dist/build-info.json +3 -3
- package/dist/bundled/boot-md/handler.js +12 -12
- package/dist/bundled/session-journal/handler.js +12 -12
- package/dist/bundled/session-memory/handler.js +12 -12
- package/dist/canvas-host/a2ui/.bundle.hash +1 -1
- package/dist/{chrome-BkJgMZjT.js → chrome-DTJprCGv.js} +8 -8
- package/dist/cli/daemon-cli.js +1 -1
- package/dist/{command-queue-DucYxKKF.js → command-queue-BTAxEdto.js} +1 -1
- package/dist/{command-registry-NOHWjbDr.js → command-registry-C7nkhshN.js} +7 -7
- package/dist/{completion-cli-BfKoz1jW.js → completion-cli-CjPs9az4.js} +2 -2
- package/dist/{completion-cli-BQ5pz74O.js → completion-cli-DozvikmZ.js} +1 -1
- package/dist/{config-cli-BGFZu6JD.js → config-cli-Dagtz6FG.js} +1 -1
- package/dist/{config-cli-cFzUpYdg.js → config-cli-Dv1Dad57.js} +1 -1
- package/dist/config.js +9 -9
- package/dist/{configure-DYAZJ3GF.js → configure-BbKf530X.js} +2 -2
- package/dist/{configure-DrrwPxlu.js → configure-CBJSHejz.js} +2 -2
- package/dist/{daemon-cli-B3Q460ai.js → daemon-cli-CiPY543O.js} +5 -5
- package/dist/{daemon-cli-C5cAb15Z.js → daemon-cli-YmPcKhvo.js} +5 -5
- package/dist/daemon-cli.js +44 -3
- package/dist/{daemon-install-DGU1qIIK.js → daemon-install-BuETscSF.js} +3 -3
- package/dist/{daemon-install-CBIxgo4p.js → daemon-install-CGg5r1wy.js} +3 -3
- package/dist/{deliver-fwGuXC0n.js → deliver-CVWbwPkb.js} +1 -1
- package/dist/{diagnostic-BB6k_yiT.js → diagnostic-DI-K8155.js} +1 -1
- package/dist/{diagnostics-C4-TUVnm.js → diagnostics-C6ntZHc3.js} +1 -1
- package/dist/{diagnostics-FIcvxigK.js → diagnostics-Ds3p_NPY.js} +1 -1
- package/dist/{doctor-completion-CtR8yetH.js → doctor-completion-CJYV2FQ5.js} +1 -1
- package/dist/{doctor-completion-B94pFOFt.js → doctor-completion-D4T2GzR7.js} +1 -1
- package/dist/entry.js +2 -2
- package/dist/{exec-approvals-cli-t7rAulRf.js → exec-approvals-cli-BAJ3sxzP.js} +3 -3
- package/dist/{exec-approvals-cli-DsXabPF_.js → exec-approvals-cli-DIUYxvM0.js} +3 -3
- package/dist/{gateway-cli-DI7_gxH-.js → gateway-cli-Cyivh-5T.js} +10 -10
- package/dist/{gateway-cli-C_veSlCx.js → gateway-cli-DtFDQj5d.js} +10 -10
- package/dist/index.js +1 -1
- package/dist/{inspect-DVHAPbcZ.js → inspect-BXlZnWkg.js} +1 -1
- package/dist/{inspect-gvjq38z1.js → inspect-LfUprDjx.js} +1 -1
- package/dist/{lifecycle-core-BdCcww82.js → lifecycle-core-CuMY0U_8.js} +3 -3
- package/dist/{lifecycle-core-B3-ezmTx.js → lifecycle-core-M-5Z8Xml.js} +3 -3
- package/dist/llm-slug-generator.js +12 -12
- package/dist/{node-cli-m0j_YD0V.js → node-cli-Csimlxec.js} +5 -5
- package/dist/{node-cli-yC-cX-mJ.js → node-cli-dh4qWKpT.js} +5 -5
- package/dist/{node-service-BnwLXY5x.js → node-service-BnyKZNqa.js} +1 -1
- package/dist/{node-service-r64yexlb.js → node-service-Ys7JZFz-.js} +1 -1
- package/dist/{onboard-D8MZoOum.js → onboard-DuNuHUvh.js} +2 -2
- package/dist/{onboard-B9-DxI_m.js → onboard-_pVU_O8s.js} +2 -2
- package/dist/{onboarding-0WSY8o8f.js → onboarding-BBb8ZbfX.js} +1 -1
- package/dist/{onboarding-BfpXrIw7.js → onboarding-BcI8N4WK.js} +1 -1
- package/dist/{onboarding.finalize-QITC8MsS.js → onboarding.finalize-BXkz7oPa.js} +7 -7
- package/dist/{onboarding.finalize-BnIw1M2b.js → onboarding.finalize-D9fyrhAS.js} +6 -6
- package/dist/{pi-embedded-BBId5n_k.js → pi-embedded-Qyba-Rws.js} +28 -28
- package/dist/{pi-embedded-helpers-DVvJRC5S.js → pi-embedded-helpers-5JApWu7A.js} +3 -3
- package/dist/{pi-embedded-utils-BoaQCwP7.js → pi-embedded-utils-kHMmP2Xh.js} +1 -1
- package/dist/plugin-sdk/{agent-scope-BQYVQxfQ.js → agent-scope-DU8uJiiv.js} +2 -2
- package/dist/plugin-sdk/agent-turn-execute-DIrSWfHZ.js +61 -0
- package/dist/plugin-sdk/{agent-turn-runner-DL5jOnXC.js → agent-turn-runner-CmDXH4Z8.js} +6 -6
- package/dist/plugin-sdk/{api-key-rotation-BW3rq9Tg.js → api-key-rotation-Oe0hkmVQ.js} +2 -2
- package/dist/plugin-sdk/{artifacts-C8xK4Y3e.js → artifacts-Dk-7TtTn.js} +1 -1
- package/dist/plugin-sdk/{audio-preflight-Bi8RI9zM.js → audio-preflight-8hRbl8Io.js} +22 -22
- package/dist/plugin-sdk/{audio-transcription-runner-CGgNZVZv.js → audio-transcription-runner-CUsZFiEA.js} +11 -11
- package/dist/plugin-sdk/{auth-profiles-CIKTWees.js → auth-profiles-OgKl-vr-.js} +5 -5
- package/dist/plugin-sdk/{channel-activity-Ck83U_es.js → channel-activity-OIauZrkN.js} +3 -3
- package/dist/plugin-sdk/{chrome-CT_6RvJg.js → chrome-VZ0Zuu2w.js} +4 -4
- package/dist/plugin-sdk/{chunk-D6yDhUpF.js → chunk-BWz-lIzS.js} +4 -4
- package/dist/plugin-sdk/{command-queue-DH8k55DC.js → command-queue-D_UFlrwC.js} +2 -2
- package/dist/plugin-sdk/{commands-registry-DuwlD_aI.js → commands-registry-XW2hmbAE.js} +3 -3
- package/dist/plugin-sdk/{deliver-_UyigkEK.js → deliver-B7W4NNsR.js} +11 -11
- package/dist/plugin-sdk/deliver-CJP9TznW.js +32 -0
- package/dist/plugin-sdk/{diagnostic-Dk57KVjE.js → diagnostic-BRUM6uNI.js} +7 -7
- package/dist/plugin-sdk/{diagnostic-session-state-BjuWxrAM.js → diagnostic-session-state-BsHrQ7U3.js} +1 -1
- package/dist/plugin-sdk/{fetch-D1jXg-ju.js → fetch-C1vgNaXJ.js} +1 -1
- package/dist/plugin-sdk/{fetch-guard-DnwfMurj.js → fetch-guard-B2Wnl9Vi.js} +4 -4
- package/dist/plugin-sdk/{fetch-DFZXuu3W.js → fetch-hEreHolP.js} +7 -7
- package/dist/plugin-sdk/{fetch-timeout-D7XVWfxj.js → fetch-timeout-A1m6BOhm.js} +1 -1
- package/dist/plugin-sdk/{fs-safe-BQ5cHy9G.js → fs-safe-ClebEeLz.js} +3 -3
- package/dist/plugin-sdk/{image-ops-0TjWNKUx.js → image-ops-Bm03YsK2.js} +1 -1
- package/dist/plugin-sdk/{inbound-context-Bfr5DkdO.js → inbound-context-Cmgtf5my.js} +1 -1
- package/dist/plugin-sdk/{ir-CbL0sVL2.js → ir-1CthVxHv.js} +5 -5
- package/dist/plugin-sdk/{logger-D5s3kiib.js → logger-BkzoyfHW.js} +1 -1
- package/dist/plugin-sdk/{manager-CHBt3PC-.js → manager-C4CJpRd9.js} +8 -8
- package/dist/plugin-sdk/manager-CPcI0Fhz.js +16 -0
- package/dist/plugin-sdk/{markdown-tables-CBGsimjo.js → markdown-tables-DZM4NfLU.js} +3 -3
- package/dist/plugin-sdk/{outbound-attachment-C5x2htXW.js → outbound-attachment-GMgXpDQm.js} +2 -2
- package/dist/plugin-sdk/{outbound-message-hooks-2YmJP9k-.js → outbound-message-hooks-BUHUdpjM.js} +3 -3
- package/dist/plugin-sdk/{path-alias-guards-BIPHhakQ.js → path-alias-guards-Dft7g-_v.js} +1 -1
- package/dist/plugin-sdk/{pi-embedded-helpers-CELD3Ns3.js → pi-embedded-helpers-B-OOYT64.js} +11 -11
- package/dist/plugin-sdk/{pi-embedded-utils-AFQ-oCFM.js → pi-embedded-utils-BoSGTbYO.js} +3 -3
- package/dist/plugin-sdk/{pi-model-discovery-C37z3zkx.js → pi-model-discovery-DF6l-F8s.js} +2 -2
- package/dist/plugin-sdk/provider-dispatcher-CAfyX0H-.js +61 -0
- package/dist/plugin-sdk/provider-image-L_SRDPHU.js +22 -0
- package/dist/plugin-sdk/{provider-image-CbZOYl0b.js → provider-image-zNa1Cccq.js} +5 -5
- package/dist/plugin-sdk/provider-media-understanding.js +7 -7
- package/dist/plugin-sdk/{proxy-CjNR_U3u.js → proxy-CXI1ycJj.js} +2 -2
- package/dist/plugin-sdk/{proxy-env-ubutfW07.js → proxy-env-B3rQCyUO.js} +2 -2
- package/dist/plugin-sdk/{proxy-fetch-Cv4b428x.js → proxy-fetch-CNBkczcW.js} +1 -1
- package/dist/plugin-sdk/{pw-ai-hVucoF_I.js → pw-ai-JJrE-B1e.js} +10 -10
- package/dist/plugin-sdk/{qmd-manager-Bv5B9pi9.js → qmd-manager-CkQhKIXI.js} +8 -8
- package/dist/plugin-sdk/{query-expansion-CWwH9pqd.js → query-expansion-DjZsVofz.js} +3 -3
- package/dist/plugin-sdk/{recorder-C3qdhG-p.js → recorder-BU_faNrp.js} +4 -4
- package/dist/plugin-sdk/{redact-BEcZiZI1.js → redact-CdcLrd0n.js} +1 -1
- package/dist/plugin-sdk/{registry-BXmLBUZj.js → registry-tm0HFWKN.js} +7 -7
- package/dist/plugin-sdk/replies-BFjGWAPs.js +25 -0
- package/dist/plugin-sdk/{replies-CjeAGcJx.js → replies-CS2GXrd-.js} +4 -4
- package/dist/plugin-sdk/{reply-directives-OuX5Mba_.js → reply-directives-hfOVgXpA.js} +2 -2
- package/dist/plugin-sdk/{reply-prefix-C83iSHUo.js → reply-prefix-BKQgC6Vj.js} +2 -2
- package/dist/plugin-sdk/{resolve-route-BLBv69wV.js → resolve-route-tqQv32nr.js} +5 -5
- package/dist/plugin-sdk/runs-CPA1-H-2.js +25 -0
- package/dist/plugin-sdk/{runs-CPWQbljV.js → runs-H2FY7UEk.js} +8 -8
- package/dist/plugin-sdk/{send-BwoWjRrJ.js → send-BMhuEEtY.js} +9 -9
- package/dist/plugin-sdk/{send-BUAw8fe4.js → send-BjR1QHnz.js} +7 -7
- package/dist/plugin-sdk/{send-B2Kip2qd.js → send-CEtGyK32.js} +8 -8
- package/dist/plugin-sdk/{send-DE9YP1gQ.js → send-CF3PEjRy.js} +14 -14
- package/dist/plugin-sdk/send-CRtSAH8I.js +25 -0
- package/dist/plugin-sdk/send-CzxwgKvR.js +27 -0
- package/dist/plugin-sdk/{send-D_2cxLLi.js → send-DUJMd2d7.js} +20 -20
- package/dist/plugin-sdk/{send-CfSEKKlU.js → send-DdPdUcD0.js} +6 -6
- package/dist/plugin-sdk/send-DuCB2xbl.js +23 -0
- package/dist/plugin-sdk/send-DyYaWr3E.js +24 -0
- package/dist/plugin-sdk/{session-meta-D3W538Wj.js → session-meta-oR-S9C5J.js} +3 -3
- package/dist/plugin-sdk/{skill-commands-ByVQek_m.js → skill-commands-Bh43SlDc.js} +6 -6
- package/dist/plugin-sdk/{skills-BGgry_YW.js → skills-CatBv8W4.js} +6 -6
- package/dist/plugin-sdk/{store-C6ZLKWUS.js → store-6JKvp2Zu.js} +2 -2
- package/dist/plugin-sdk/subagent-registry-DwnBzoKn.js +61 -0
- package/dist/plugin-sdk/{subsystem-0F3fEqJW.js → subsystem-CchgWuXK.js} +4 -4
- package/dist/plugin-sdk/{tables-BCSRuiq_.js → tables-BPCP4CpE.js} +1 -1
- package/dist/plugin-sdk/testing.js +55 -55
- package/dist/plugin-sdk/{tokens-O0pGqOuf.js → tokens-Cioh8xR5.js} +1 -1
- package/dist/plugin-sdk/{tool-loop-detection-BG8uP1aD.js → tool-loop-detection-BN0lIxLx.js} +1 -1
- package/dist/plugin-sdk/{tools-PVG4MyCb.js → tools-4rcPGSNh.js} +93 -93
- package/dist/plugin-sdk/{user-run-recording-iS-qLIN9.js → user-run-recording-DlpzQk0X.js} +3 -3
- package/dist/{program-CLZlTcBu.js → program-CB5O-AyY.js} +2 -2
- package/dist/{program-context-DN3ucEgK.js → program-context-DxwZAeg4.js} +12 -12
- package/dist/{prompt-select-styled-BNUoeaMg.js → prompt-select-styled-1UYyuvWX.js} +7 -7
- package/dist/{prompt-select-styled-CtLCU2VG.js → prompt-select-styled-CfTLn2TL.js} +7 -7
- package/dist/{provider-image-Bi2L9LWh.js → provider-image-Cb4z6zUu.js} +1 -1
- package/dist/{pw-ai-pYtWxKPR.js → pw-ai-DziGw6Jy.js} +1 -1
- package/dist/{register.configure-CiYkPB_K.js → register.configure-DUg0MNnJ.js} +4 -4
- package/dist/{register.configure-Ceru0oh0.js → register.configure-PJZ3F6Y4.js} +4 -4
- package/dist/{register.maintenance-D-kzdtog.js → register.maintenance-9XyX4HYf.js} +10 -10
- package/dist/{register.maintenance-BCSq69vI.js → register.maintenance-BBM-rluy.js} +9 -9
- package/dist/{register.onboard-BPko9BHz.js → register.onboard-CEzQdSxa.js} +2 -2
- package/dist/{register.onboard-BHBpxcRv.js → register.onboard-id-BqwSw.js} +2 -2
- package/dist/{register.setup-Bjx48C4T.js → register.setup-BKBe-Sjp.js} +2 -2
- package/dist/{register.setup-BE-sR-Dc.js → register.setup-BiOdr528.js} +2 -2
- package/dist/{register.status-health-sessions-BczZXjHB.js → register.status-health-sessions-BVIHCaPP.js} +5 -5
- package/dist/{register.status-health-sessions-CQQWiYcu.js → register.status-health-sessions-CeRiMlKm.js} +5 -5
- package/dist/{register.subclis-Bn9Re4fV.js → register.subclis-IXr-WEhq.js} +6 -6
- package/dist/{run-main-Cfccgza4.js → run-main-CsSM1uCJ.js} +6 -6
- package/dist/{runs-BauIdJqy.js → runs-ucQd8Fc-.js} +3 -3
- package/dist/{send-Dd2SE0h0.js → send-TscXeVY8.js} +1 -1
- package/dist/{service-Hy5BSgmp.js → service-C_LTJsJU.js} +1 -1
- package/dist/{service-RnhuB5cG.js → service-Dkcqg-rd.js} +1 -1
- package/dist/{shared-Bc-7t0_-.js → shared-6Uo_6p6X.js} +1 -1
- package/dist/{shared-CJd0QML9.js → shared-Cj18nzyW.js} +1 -1
- package/dist/{status-Dh0qVJb_.js → status-Czr0TmiN.js} +3 -3
- package/dist/{status-PwhjcAYw.js → status-DJsk1JjI.js} +3 -3
- package/dist/{systemd-B5tdOLBg.js → systemd-BJqlwupF.js} +1 -1
- package/dist/{systemd-DsBMxUv1.js → systemd-D6tSdsgx.js} +1 -1
- package/dist/{systemd-hints-DH86kn6U.js → systemd-hints-Cp0IfOPD.js} +45 -4
- package/dist/{systemd-hints-WHzM5Tf8.js → systemd-hints-DzBfweRH.js} +45 -4
- package/dist/{systemd-linger-BPzLwumk.js → systemd-linger-Dk1GWduD.js} +1 -1
- package/dist/{systemd-linger-EvpJ4ZxF.js → systemd-linger-NvIOU-Ez.js} +1 -1
- package/dist/{update-cli-f5iTpbNn.js → update-cli-B4_xRNut.js} +11 -11
- package/dist/{update-cli-BSxfgTyN.js → update-cli-DDmfG1gm.js} +12 -12
- package/extensions/acpx/package.json +1 -1
- package/extensions/anthropic/package.json +1 -1
- package/extensions/bifrost/package.json +1 -1
- package/extensions/bluebubbles/package.json +1 -1
- package/extensions/copilot-proxy/package.json +1 -1
- package/extensions/deepgram/package.json +1 -1
- package/extensions/deepinfra/package.json +1 -1
- package/extensions/diagnostics-otel/package.json +1 -1
- package/extensions/diffs/package.json +1 -1
- package/extensions/discord/package.json +1 -1
- package/extensions/dream-cycle/package.json +1 -1
- package/extensions/elevenlabs/package.json +1 -1
- package/extensions/feishu/package.json +1 -1
- package/extensions/gemini-search/package.json +1 -1
- package/extensions/google/package.json +1 -1
- package/extensions/google-gemini-cli-auth/package.json +1 -1
- package/extensions/googlechat/package.json +1 -1
- package/extensions/grok-search/package.json +1 -1
- package/extensions/groq/package.json +1 -1
- package/extensions/imessage/package.json +1 -1
- package/extensions/irc/package.json +1 -1
- package/extensions/kimi-search/package.json +1 -1
- package/extensions/learning-core/package.json +1 -1
- package/extensions/learning-core/src/bounded-store-reads.conformance.test.ts +23 -411
- package/extensions/learning-core/src/bounded-store-reads.pg.integration.test.ts +52 -0
- package/extensions/learning-core/src/bounded-store-reads.test-helpers.ts +387 -0
- package/extensions/learning-core/src/lesson-store-conformance.pg.integration.test.ts +77 -0
- package/extensions/learning-core/src/lesson-store-conformance.test-helpers.ts +1406 -0
- package/extensions/learning-core/src/lesson-store-conformance.test.ts +27 -1509
- package/extensions/learning-core/src/lesson-store-postgres.test.ts +9 -7
- package/extensions/line/package.json +1 -1
- package/extensions/llm-task/package.json +1 -1
- package/extensions/lobster/package.json +1 -1
- package/extensions/matrix/CHANGELOG.md +1 -1
- package/extensions/matrix/package.json +1 -1
- package/extensions/mattermost/package.json +1 -1
- package/extensions/memory-core/package.json +1 -1
- package/extensions/memory-lancedb/package.json +1 -1
- package/extensions/minimax/package.json +1 -1
- package/extensions/minimax-portal-auth/package.json +1 -1
- package/extensions/mistral/package.json +1 -1
- package/extensions/moonshot/package.json +1 -1
- package/extensions/msteams/CHANGELOG.md +1 -1
- package/extensions/msteams/package.json +1 -1
- package/extensions/nextcloud-talk/package.json +1 -1
- package/extensions/nostr/CHANGELOG.md +1 -1
- package/extensions/nostr/package.json +1 -1
- package/extensions/open-prose/package.json +1 -1
- package/extensions/openai/package.json +1 -1
- package/extensions/openai-realtime/package.json +1 -1
- package/extensions/openrouter/package.json +1 -1
- package/extensions/perplexity-search/package.json +1 -1
- package/extensions/searxng-search/package.json +1 -1
- package/extensions/signal/package.json +1 -1
- package/extensions/slack/package.json +1 -1
- package/extensions/synology-chat/package.json +1 -1
- package/extensions/telegram/package.json +1 -1
- package/extensions/tlon/package.json +1 -1
- package/extensions/twitch/CHANGELOG.md +1 -1
- package/extensions/twitch/package.json +1 -1
- package/extensions/voice-call/CHANGELOG.md +1 -1
- package/extensions/voice-call/package.json +1 -1
- package/extensions/whatsapp/package.json +1 -1
- package/extensions/zai/package.json +1 -1
- package/extensions/zalo/CHANGELOG.md +1 -1
- package/extensions/zalo/package.json +1 -1
- package/extensions/zalouser/CHANGELOG.md +1 -1
- package/extensions/zalouser/package.json +1 -1
- package/package.json +1 -1
- package/dist/plugin-sdk/agent-turn-execute-Dd5AJiKY.js +0 -61
- package/dist/plugin-sdk/deliver-DuDcszu1.js +0 -32
- package/dist/plugin-sdk/manager-Bqpqu2GS.js +0 -16
- package/dist/plugin-sdk/provider-dispatcher-C4-urx2x.js +0 -61
- package/dist/plugin-sdk/provider-image-Bumd1GBG.js +0 -22
- package/dist/plugin-sdk/replies-0gHRQjbA.js +0 -25
- package/dist/plugin-sdk/runs-B3DB63tB.js +0 -25
- package/dist/plugin-sdk/send-C28pxKhC.js +0 -27
- package/dist/plugin-sdk/send-COofA0BG.js +0 -23
- package/dist/plugin-sdk/send-NUHfC-G_.js +0 -25
- package/dist/plugin-sdk/send-Tm5-7bLz.js +0 -24
- package/dist/plugin-sdk/subagent-registry-B0gD-Lof.js +0 -61
|
@@ -1,5 +1,4 @@
|
|
|
1
1
|
import { execFile } from "node:child_process";
|
|
2
|
-
import crypto from "node:crypto";
|
|
3
2
|
import fs from "node:fs/promises";
|
|
4
3
|
import os from "node:os";
|
|
5
4
|
import path from "node:path";
|
|
@@ -8,456 +7,69 @@ import { promisify } from "node:util";
|
|
|
8
7
|
import v8 from "node:v8";
|
|
9
8
|
import vm from "node:vm";
|
|
10
9
|
import { afterAll, afterEach, beforeAll, beforeEach, describe, expect, it } from "vitest";
|
|
11
|
-
import { readRankingPool, RANKING_SCAN_MAX_ROWS } from "./consolidation-judge.js";
|
|
12
|
-
import { withCandidateDefaults } from "./extraction-candidate.js";
|
|
13
|
-
import { createLearningOverview } from "./gateway-methods.js";
|
|
14
|
-
import { withLessonDefaults } from "./lesson-record.js";
|
|
15
10
|
import {
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
11
|
+
candidate,
|
|
12
|
+
lesson,
|
|
13
|
+
meteredPool,
|
|
14
|
+
meteredSqlite,
|
|
15
|
+
metering,
|
|
16
|
+
NOW,
|
|
17
|
+
PAST,
|
|
18
|
+
proposal,
|
|
19
|
+
registerBoundedSqlLegSuite,
|
|
20
|
+
TEST_SCHEMA,
|
|
21
|
+
type SqlLeg,
|
|
22
|
+
} from "./bounded-store-reads.test-helpers.js";
|
|
23
|
+
import { createLearningOverview } from "./gateway-methods.js";
|
|
24
|
+
import { PostgresLessonStore, type PgPoolLike } from "./lesson-store-postgres.js";
|
|
21
25
|
import { createInMemoryPgPool } from "./lesson-store-postgres.test-helpers.js";
|
|
22
26
|
import { type SqliteDatabaseLike, SqliteLessonStore } from "./lesson-store-sqlite.js";
|
|
23
27
|
import type { LogScanStats } from "./log-scan.js";
|
|
24
28
|
import { FileLearningStore, FS_OVERVIEW_MAX_BYTES } from "./store.js";
|
|
25
|
-
import {
|
|
26
|
-
LESSON_EXCERPT_MAX_BYTES,
|
|
27
|
-
LESSON_PAGE_MAX_BYTES,
|
|
28
|
-
type ExtractionCandidate,
|
|
29
|
-
type LearningLesson,
|
|
30
|
-
type LessonStore,
|
|
31
|
-
type SkillProposal,
|
|
32
|
-
} from "./types.js";
|
|
33
|
-
|
|
34
|
-
// Spec "Bounded store reads, scans, and success evidence": verification asserts rows AND bytes
|
|
35
|
-
// read, not just result length. Every SQL leg runs through a meter that counts the rows the
|
|
36
|
-
// database handed back and the payload bytes among them, so a materialize-then-filter read (the
|
|
37
|
-
// pre-change readPayloads path) fails here even when its result is correct.
|
|
38
|
-
|
|
39
|
-
type Meter = { rows: number; payloadBytes: number; excerptBytes: number };
|
|
40
|
-
|
|
41
|
-
function newMeter(): Meter {
|
|
42
|
-
return { rows: 0, payloadBytes: 0, excerptBytes: 0 };
|
|
43
|
-
}
|
|
44
|
-
|
|
45
|
-
// One wrapped handle per test (the stores memoise schema init on the handle), metering into
|
|
46
|
-
// whichever meter the last make() installed.
|
|
47
|
-
let activeMeter: Meter = newMeter();
|
|
48
|
-
|
|
49
|
-
function countRows(rows: unknown[]): void {
|
|
50
|
-
const meter = activeMeter;
|
|
51
|
-
for (const row of rows) {
|
|
52
|
-
meter.rows += 1;
|
|
53
|
-
for (const [column, value] of Object.entries(row as Record<string, unknown>)) {
|
|
54
|
-
if (column === "payload_json") {
|
|
55
|
-
meter.payloadBytes += Buffer.byteLength(
|
|
56
|
-
typeof value === "string" ? value : JSON.stringify(value),
|
|
57
|
-
);
|
|
58
|
-
} else if (column === "excerpt" && value instanceof Uint8Array) {
|
|
59
|
-
meter.excerptBytes += value.length;
|
|
60
|
-
}
|
|
61
|
-
}
|
|
62
|
-
}
|
|
63
|
-
}
|
|
64
|
-
|
|
65
|
-
function meteredSqlite(db: DatabaseSync): SqliteDatabaseLike {
|
|
66
|
-
return {
|
|
67
|
-
exec: (sql) => db.exec(sql),
|
|
68
|
-
prepare(sql) {
|
|
69
|
-
const statement = db.prepare(sql);
|
|
70
|
-
return {
|
|
71
|
-
run: (...params) => statement.run(...(params as never[])),
|
|
72
|
-
get: (...params) => {
|
|
73
|
-
const row = statement.get(...(params as never[]));
|
|
74
|
-
countRows(row === undefined ? [] : [row]);
|
|
75
|
-
return row;
|
|
76
|
-
},
|
|
77
|
-
all: (...params) => {
|
|
78
|
-
const rows = statement.all(...(params as never[]));
|
|
79
|
-
countRows(rows);
|
|
80
|
-
return rows;
|
|
81
|
-
},
|
|
82
|
-
};
|
|
83
|
-
},
|
|
84
|
-
};
|
|
85
|
-
}
|
|
86
|
-
|
|
87
|
-
function meteredPool(pool: PgPoolLike): PgPoolLike {
|
|
88
|
-
const wrap = async (result: Promise<{ rows: Record<string, unknown>[] }>) => {
|
|
89
|
-
const settled = await result;
|
|
90
|
-
countRows(settled.rows);
|
|
91
|
-
return settled;
|
|
92
|
-
};
|
|
93
|
-
return {
|
|
94
|
-
query: (sql, values) => wrap(pool.query(sql, values)),
|
|
95
|
-
async connect(): Promise<PgClientLike> {
|
|
96
|
-
const client = await pool.connect();
|
|
97
|
-
return {
|
|
98
|
-
query: (sql, values) => wrap(client.query(sql, values)),
|
|
99
|
-
release: () => client.release(),
|
|
100
|
-
};
|
|
101
|
-
},
|
|
102
|
-
};
|
|
103
|
-
}
|
|
104
|
-
|
|
105
|
-
const RUN_TOKEN = crypto.randomUUID().replaceAll("-", "");
|
|
106
|
-
const TEST_SCHEMA = `plugin_learning_core_bounded_${RUN_TOKEN}`;
|
|
107
|
-
const NOW = "2026-06-01T00:00:00.000Z";
|
|
108
|
-
const PAST = "2026-05-01T00:00:00.000Z";
|
|
109
|
-
const FUTURE = "2027-01-01T00:00:00.000Z";
|
|
29
|
+
import type { ExtractionCandidate, LearningLesson } from "./types.js";
|
|
110
30
|
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
/** Fixture sizes; the live leg is smaller only to keep a shared server fast. */
|
|
115
|
-
scale: number;
|
|
116
|
-
make: (meter: Meter) => LessonStore;
|
|
117
|
-
};
|
|
31
|
+
// The sqlite and in-memory-double legs of the metered SQL suite in
|
|
32
|
+
// bounded-store-reads.test-helpers.ts. Its live-server leg is
|
|
33
|
+
// bounded-store-reads.pg.integration.test.ts (`pnpm test:pg`).
|
|
118
34
|
|
|
119
35
|
let db: DatabaseSync;
|
|
120
36
|
let memPool: ReturnType<typeof createInMemoryPgPool>;
|
|
121
|
-
let
|
|
122
|
-
let handles: { sqlite: SqliteDatabaseLike; mem: PgPoolLike; live?: PgPoolLike };
|
|
123
|
-
|
|
124
|
-
function metering<T>(meter: Meter, value: T): T {
|
|
125
|
-
activeMeter = meter;
|
|
126
|
-
return value;
|
|
127
|
-
}
|
|
37
|
+
let handles: { sqlite: SqliteDatabaseLike; mem: PgPoolLike };
|
|
128
38
|
|
|
129
39
|
const SQL_LEGS: SqlLeg[] = [
|
|
130
40
|
{
|
|
131
41
|
label: "sqlite",
|
|
132
|
-
available: true,
|
|
133
42
|
scale: 1,
|
|
134
43
|
make: (meter) => metering(meter, new SqliteLessonStore({ db: handles.sqlite, scope: {} })),
|
|
44
|
+
writer: (scope) => new SqliteLessonStore({ db: handles.sqlite, scope }),
|
|
135
45
|
},
|
|
136
46
|
{
|
|
137
47
|
label: "postgres (in-memory double)",
|
|
138
|
-
available: true,
|
|
139
48
|
scale: 1,
|
|
140
49
|
make: (meter) =>
|
|
141
50
|
metering(
|
|
142
51
|
meter,
|
|
143
52
|
new PostgresLessonStore({ pool: handles.mem, schema: TEST_SCHEMA, scope: {} }),
|
|
144
53
|
),
|
|
145
|
-
|
|
146
|
-
{
|
|
147
|
-
label: "postgres (live server)",
|
|
148
|
-
available: Boolean(process.env.OPENCLAW_DB_URL),
|
|
149
|
-
scale: 0.5,
|
|
150
|
-
make: (meter) =>
|
|
151
|
-
metering(
|
|
152
|
-
meter,
|
|
153
|
-
new PostgresLessonStore({ pool: handles.live!, schema: TEST_SCHEMA, scope: {} }),
|
|
154
|
-
),
|
|
54
|
+
writer: (scope) => new PostgresLessonStore({ pool: handles.mem, schema: TEST_SCHEMA, scope }),
|
|
155
55
|
},
|
|
156
56
|
];
|
|
157
57
|
|
|
158
|
-
beforeAll(() => {
|
|
159
|
-
if (process.env.OPENCLAW_DB_URL) {
|
|
160
|
-
livePool = createDefaultLearningPgPool(process.env.OPENCLAW_DB_URL);
|
|
161
|
-
}
|
|
162
|
-
});
|
|
163
|
-
|
|
164
58
|
beforeEach(() => {
|
|
165
59
|
db = new DatabaseSync(":memory:");
|
|
166
60
|
memPool = createInMemoryPgPool();
|
|
167
61
|
handles = {
|
|
168
62
|
sqlite: meteredSqlite(db),
|
|
169
63
|
mem: meteredPool(memPool),
|
|
170
|
-
...(livePool ? { live: meteredPool(livePool) } : {}),
|
|
171
64
|
};
|
|
172
65
|
});
|
|
173
66
|
|
|
174
|
-
afterEach(
|
|
67
|
+
afterEach(() => {
|
|
175
68
|
db.close();
|
|
176
|
-
if (livePool) {
|
|
177
|
-
await livePool.query(`DROP SCHEMA IF EXISTS ${TEST_SCHEMA} CASCADE`);
|
|
178
|
-
}
|
|
179
|
-
});
|
|
180
|
-
|
|
181
|
-
afterAll(async () => {
|
|
182
|
-
await livePool?.end?.();
|
|
183
69
|
});
|
|
184
70
|
|
|
185
|
-
function iso(ms: number): string {
|
|
186
|
-
return new Date(Date.parse(PAST) + ms).toISOString();
|
|
187
|
-
}
|
|
188
|
-
|
|
189
|
-
function candidate(index: number, status: ExtractionCandidate["status"]): ExtractionCandidate {
|
|
190
|
-
return withCandidateDefaults(
|
|
191
|
-
{
|
|
192
|
-
sessionId: `session-${String(index).padStart(6, "0")}`,
|
|
193
|
-
agentId: `agent-${index % 3}`,
|
|
194
|
-
workspaceId: `ws-${index % 2}`,
|
|
195
|
-
excerpt: `candidate ${index} ${"x".repeat(900)}`,
|
|
196
|
-
tags: index % 2 === 0 ? ["correction"] : ["failure"],
|
|
197
|
-
status,
|
|
198
|
-
},
|
|
199
|
-
iso(index * 1000),
|
|
200
|
-
);
|
|
201
|
-
}
|
|
202
|
-
|
|
203
|
-
function lesson(index: number, text: string, opts?: { invalidAt?: string; validFrom?: string }) {
|
|
204
|
-
const at = iso(index * 1000);
|
|
205
|
-
return withLessonDefaults(
|
|
206
|
-
{
|
|
207
|
-
id: `lesson-${String(index).padStart(6, "0")}`,
|
|
208
|
-
scope: { agentId: `agent-${index % 3}`, workspaceId: `ws-${index % 2}` },
|
|
209
|
-
kind: "procedure" as const,
|
|
210
|
-
text,
|
|
211
|
-
confidence: 0.9,
|
|
212
|
-
occurrences: 1,
|
|
213
|
-
distinctSessionIds: [`s-${index}`],
|
|
214
|
-
evidence: [],
|
|
215
|
-
createdAt: at,
|
|
216
|
-
updatedAt: at,
|
|
217
|
-
...(opts?.validFrom ? { validFrom: opts.validFrom } : {}),
|
|
218
|
-
...(opts?.invalidAt ? { invalidAt: opts.invalidAt } : {}),
|
|
219
|
-
},
|
|
220
|
-
at,
|
|
221
|
-
);
|
|
222
|
-
}
|
|
223
|
-
|
|
224
|
-
function proposal(index: number, status: SkillProposal["status"]): SkillProposal {
|
|
225
|
-
return {
|
|
226
|
-
id: `proposal-${index}`,
|
|
227
|
-
lessonIds: [`lesson-${String(index).padStart(6, "0")}`],
|
|
228
|
-
status,
|
|
229
|
-
skillName: `skill-${index}`,
|
|
230
|
-
title: `Proposal ${index}`,
|
|
231
|
-
body: "b".repeat(4000),
|
|
232
|
-
scannerFindings: [],
|
|
233
|
-
createdAt: iso(index),
|
|
234
|
-
updatedAt: iso(index),
|
|
235
|
-
};
|
|
236
|
-
}
|
|
237
|
-
|
|
238
|
-
// Mixed 1-, 2- and 3-byte characters, so a byte cut can land inside a character.
|
|
239
|
-
const LONG_TEXT = `${"é".repeat(3000)}${"界".repeat(2000)} tail`;
|
|
240
|
-
|
|
241
|
-
async function seedScoped(
|
|
242
|
-
writer: (scope: { workspaceId: string; agentId: string }) => LessonStore,
|
|
243
|
-
items: Array<{
|
|
244
|
-
scope: { workspaceId: string; agentId: string };
|
|
245
|
-
write: (store: LessonStore) => Promise<void>;
|
|
246
|
-
}>,
|
|
247
|
-
): Promise<void> {
|
|
248
|
-
for (const item of items) {
|
|
249
|
-
await item.write(writer(item.scope));
|
|
250
|
-
}
|
|
251
|
-
}
|
|
252
|
-
|
|
253
71
|
for (const leg of SQL_LEGS) {
|
|
254
|
-
|
|
255
|
-
d(`bounded SQL store reads — ${leg.label}`, () => {
|
|
256
|
-
// Writes go through a store scoped to the row's own scope (as production capture does), reads
|
|
257
|
-
// through the unscoped store the gateway and the passes use.
|
|
258
|
-
const scopedWriter = (meter: Meter) => (scope: { workspaceId: string; agentId: string }) => {
|
|
259
|
-
activeMeter = meter;
|
|
260
|
-
if (leg.label === "sqlite") {
|
|
261
|
-
return new SqliteLessonStore({ db: handles.sqlite, scope });
|
|
262
|
-
}
|
|
263
|
-
const pool = leg.label.includes("live") ? handles.live! : handles.mem;
|
|
264
|
-
return new PostgresLessonStore({ pool, schema: TEST_SCHEMA, scope });
|
|
265
|
-
};
|
|
266
|
-
const scopeOf = (index: number) => ({
|
|
267
|
-
agentId: `agent-${index % 3}`,
|
|
268
|
-
workspaceId: `ws-${index % 2}`,
|
|
269
|
-
});
|
|
270
|
-
|
|
271
|
-
it("pending candidates: rows and payload bytes read are the pending set only, keyset-paged", async () => {
|
|
272
|
-
const total = Math.round(2000 * leg.scale);
|
|
273
|
-
const pendingEvery = 100;
|
|
274
|
-
const seedMeter = newMeter();
|
|
275
|
-
const writer = scopedWriter(seedMeter);
|
|
276
|
-
const all: ExtractionCandidate[] = [];
|
|
277
|
-
for (let index = 0; index < total; index += 1) {
|
|
278
|
-
const entry = candidate(index, index % pendingEvery === 0 ? "pending" : "processed");
|
|
279
|
-
all.push(entry);
|
|
280
|
-
await writer(scopeOf(index)).upsertCandidate(entry);
|
|
281
|
-
}
|
|
282
|
-
const expected = all.filter((entry) => entry.status === "pending");
|
|
283
|
-
const pendingBytes = expected.reduce(
|
|
284
|
-
(sum, entry) => sum + Buffer.byteLength(JSON.stringify(entry)),
|
|
285
|
-
0,
|
|
286
|
-
);
|
|
287
|
-
const allBytes = all.reduce(
|
|
288
|
-
(sum, entry) => sum + Buffer.byteLength(JSON.stringify(entry)),
|
|
289
|
-
0,
|
|
290
|
-
);
|
|
291
|
-
|
|
292
|
-
const meter = newMeter();
|
|
293
|
-
const store = leg.make(meter);
|
|
294
|
-
const pending = await store.listPendingCandidates();
|
|
295
|
-
expect(pending.map((entry) => entry.sessionId)).toEqual(
|
|
296
|
-
expected.map((entry) => entry.sessionId),
|
|
297
|
-
);
|
|
298
|
-
expect(meter.rows, "rows read must be the pending rows, not every candidate").toBe(
|
|
299
|
-
expected.length,
|
|
300
|
-
);
|
|
301
|
-
// jsonb re-serialization may reorder keys but never grows a row by more than its key quotes.
|
|
302
|
-
expect(meter.payloadBytes).toBeLessThanOrEqual(pendingBytes * 1.1);
|
|
303
|
-
expect(meter.payloadBytes).toBeLessThan(allBytes / 20);
|
|
304
|
-
|
|
305
|
-
// Keyset pages: LIMIT applies in SQL, pages chain without gaps or repeats, tags filter.
|
|
306
|
-
const pageMeter = newMeter();
|
|
307
|
-
const paged = leg.make(pageMeter);
|
|
308
|
-
const first = await paged.listPendingCandidatesPage({ limit: 7 });
|
|
309
|
-
expect(pageMeter.rows).toBe(7);
|
|
310
|
-
const seen = [...first.candidates];
|
|
311
|
-
let cursor = first.nextCursor;
|
|
312
|
-
while (cursor) {
|
|
313
|
-
const page = await paged.listPendingCandidatesPage({ after: cursor, limit: 7 });
|
|
314
|
-
seen.push(...page.candidates);
|
|
315
|
-
cursor = page.nextCursor;
|
|
316
|
-
}
|
|
317
|
-
expect(seen.map((entry) => entry.sessionId)).toEqual(
|
|
318
|
-
expected.map((entry) => entry.sessionId),
|
|
319
|
-
);
|
|
320
|
-
const tagged = await paged.listPendingCandidates({ tags: ["failure"] });
|
|
321
|
-
expect(tagged.map((entry) => entry.sessionId)).toEqual(
|
|
322
|
-
expected.filter((entry) => entry.tags.includes("failure")).map((entry) => entry.sessionId),
|
|
323
|
-
);
|
|
324
|
-
const limited = await paged.listPendingCandidates({ limit: 3 });
|
|
325
|
-
expect(limited.map((entry) => entry.sessionId)).toEqual(
|
|
326
|
-
expected.slice(0, 3).map((entry) => entry.sessionId),
|
|
327
|
-
);
|
|
328
|
-
});
|
|
329
|
-
|
|
330
|
-
it("similar-lesson ranking reads { id, excerpt } pages: no payload bytes, 2 KiB rows, 256 KiB pages", async () => {
|
|
331
|
-
const activeCount = Math.round(450 * leg.scale);
|
|
332
|
-
const seedMeter = newMeter();
|
|
333
|
-
const writer = scopedWriter(seedMeter);
|
|
334
|
-
const activeIds: string[] = [];
|
|
335
|
-
for (let index = 0; index < activeCount; index += 1) {
|
|
336
|
-
const entry = lesson(index, index % 5 === 0 ? `short ${index}` : `${LONG_TEXT} ${index}`);
|
|
337
|
-
activeIds.push(entry.id);
|
|
338
|
-
await writer(scopeOf(index)).upsertLesson(entry);
|
|
339
|
-
}
|
|
340
|
-
// Inactive rows the SQL predicate must drop: invalidated, and not yet valid.
|
|
341
|
-
for (let index = activeCount; index < activeCount + 40; index += 1) {
|
|
342
|
-
await writer(scopeOf(index)).upsertLesson(
|
|
343
|
-
lesson(index, LONG_TEXT, index % 2 === 0 ? { invalidAt: PAST } : { validFrom: FUTURE }),
|
|
344
|
-
);
|
|
345
|
-
}
|
|
346
|
-
|
|
347
|
-
const meter = newMeter();
|
|
348
|
-
const store = leg.make(meter);
|
|
349
|
-
const first = await store.listActiveLessonsPage({ now: NOW, limit: 200 });
|
|
350
|
-
expect(meter.payloadBytes, "no payload_json may leave the database").toBe(0);
|
|
351
|
-
expect(first.lessons.length).toBeLessThanOrEqual(200);
|
|
352
|
-
expect(first.nextCursor).toBeDefined();
|
|
353
|
-
const firstBytes = first.lessons.reduce(
|
|
354
|
-
(sum, entry) => sum + Buffer.byteLength(entry.excerpt),
|
|
355
|
-
0,
|
|
356
|
-
);
|
|
357
|
-
expect(firstBytes).toBeLessThanOrEqual(LESSON_PAGE_MAX_BYTES);
|
|
358
|
-
expect(
|
|
359
|
-
meter.excerptBytes,
|
|
360
|
-
"the database returned at most one page of excerpts",
|
|
361
|
-
).toBeLessThanOrEqual(LESSON_PAGE_MAX_BYTES);
|
|
362
|
-
for (const entry of first.lessons) {
|
|
363
|
-
expect(Buffer.byteLength(entry.excerpt)).toBeLessThanOrEqual(LESSON_EXCERPT_MAX_BYTES);
|
|
364
|
-
expect(entry.excerpt).not.toContain("�");
|
|
365
|
-
}
|
|
366
|
-
|
|
367
|
-
const walkMeter = newMeter();
|
|
368
|
-
const walker = leg.make(walkMeter);
|
|
369
|
-
const pool = await readRankingPool(walker, NOW);
|
|
370
|
-
expect(pool.truncated).toBe(false);
|
|
371
|
-
expect(pool.lessons.map((entry) => entry.id).toSorted()).toEqual(activeIds.toSorted());
|
|
372
|
-
expect(walkMeter.rows, "every active row read exactly once, inactive never").toBe(
|
|
373
|
-
activeCount,
|
|
374
|
-
);
|
|
375
|
-
expect(walkMeter.payloadBytes).toBe(0);
|
|
376
|
-
});
|
|
377
|
-
|
|
378
|
-
it("the ranking scan stops at its 2,000-row ceiling", async () => {
|
|
379
|
-
if (leg.label.includes("live")) {
|
|
380
|
-
return;
|
|
381
|
-
}
|
|
382
|
-
const writer = scopedWriter(newMeter());
|
|
383
|
-
for (let index = 0; index < RANKING_SCAN_MAX_ROWS + 50; index += 1) {
|
|
384
|
-
await writer(scopeOf(index)).upsertLesson(lesson(index, `tiny ${index}`));
|
|
385
|
-
}
|
|
386
|
-
const meter = newMeter();
|
|
387
|
-
const pool = await readRankingPool(leg.make(meter), NOW);
|
|
388
|
-
expect(pool.truncated).toBe(true);
|
|
389
|
-
expect(pool.lessons).toHaveLength(RANKING_SCAN_MAX_ROWS);
|
|
390
|
-
expect(meter.rows).toBe(RANKING_SCAN_MAX_ROWS);
|
|
391
|
-
});
|
|
392
|
-
|
|
393
|
-
it("overview counts are SQL aggregates: a handful of rows, no payload bytes", async () => {
|
|
394
|
-
const seedMeter = newMeter();
|
|
395
|
-
const writer = scopedWriter(seedMeter);
|
|
396
|
-
const lessons = Math.round(300 * leg.scale);
|
|
397
|
-
const items: Parameters<typeof seedScoped>[1] = [];
|
|
398
|
-
for (let index = 0; index < lessons; index += 1) {
|
|
399
|
-
items.push({
|
|
400
|
-
scope: scopeOf(index),
|
|
401
|
-
write: (store) =>
|
|
402
|
-
store.upsertLesson(
|
|
403
|
-
lesson(index, "t", index % 10 === 0 ? { invalidAt: PAST } : undefined),
|
|
404
|
-
),
|
|
405
|
-
});
|
|
406
|
-
}
|
|
407
|
-
const statuses: SkillProposal["status"][] = ["pending", "applied", "rejected", "quarantined"];
|
|
408
|
-
for (let index = 0; index < 40; index += 1) {
|
|
409
|
-
items.push({
|
|
410
|
-
scope: scopeOf(index),
|
|
411
|
-
write: (store) => store.upsertProposal(proposal(index, statuses[index % 4])),
|
|
412
|
-
});
|
|
413
|
-
}
|
|
414
|
-
await seedScoped(writer, items);
|
|
415
|
-
|
|
416
|
-
const meter = newMeter();
|
|
417
|
-
const store = leg.make(meter);
|
|
418
|
-
const counts = await store.countOverview({ now: NOW });
|
|
419
|
-
expect(counts).toEqual({
|
|
420
|
-
proposals: { pending: 10, applied: 10, rejected: 10, quarantined: 10 },
|
|
421
|
-
lessons: lessons - lessons / 10,
|
|
422
|
-
scopes: 6,
|
|
423
|
-
truncated: false,
|
|
424
|
-
});
|
|
425
|
-
expect(meter.payloadBytes).toBe(0);
|
|
426
|
-
expect(meter.rows).toBeLessThanOrEqual(8);
|
|
427
|
-
|
|
428
|
-
// The real entry point: learning.overview over the same store.
|
|
429
|
-
const calls: Array<{ ok: boolean; payload?: unknown }> = [];
|
|
430
|
-
const handlerMeter = newMeter();
|
|
431
|
-
await createLearningOverview({
|
|
432
|
-
readResolution: async () => ({}),
|
|
433
|
-
lessonStore: () => leg.make(handlerMeter),
|
|
434
|
-
now: () => NOW,
|
|
435
|
-
})({
|
|
436
|
-
params: {},
|
|
437
|
-
respond: (ok: boolean, payload?: unknown) => calls.push({ ok, payload }),
|
|
438
|
-
} as never);
|
|
439
|
-
expect(calls[0]).toMatchObject({
|
|
440
|
-
ok: true,
|
|
441
|
-
payload: { lessons: counts.lessons, scopes: 6, truncated: false },
|
|
442
|
-
});
|
|
443
|
-
expect(handlerMeter.payloadBytes).toBe(0);
|
|
444
|
-
});
|
|
445
|
-
|
|
446
|
-
it("getLesson and getLessonsByIds read only the named rows", async () => {
|
|
447
|
-
const writer = scopedWriter(newMeter());
|
|
448
|
-
for (let index = 0; index < 200; index += 1) {
|
|
449
|
-
await writer(scopeOf(index)).upsertLesson(lesson(index, LONG_TEXT));
|
|
450
|
-
}
|
|
451
|
-
const meter = newMeter();
|
|
452
|
-
const store = leg.make(meter);
|
|
453
|
-
const found = await store.getLessonsByIds(["lesson-000003", "lesson-000150", "missing"]);
|
|
454
|
-
expect(found.map((entry) => entry.id).toSorted()).toEqual(["lesson-000003", "lesson-000150"]);
|
|
455
|
-
expect(meter.rows).toBe(2);
|
|
456
|
-
const one = newMeter();
|
|
457
|
-
expect((await leg.make(one).getLesson("lesson-000007"))?.id).toBe("lesson-000007");
|
|
458
|
-
expect(one.rows).toBe(1);
|
|
459
|
-
});
|
|
460
|
-
});
|
|
72
|
+
registerBoundedSqlLegSuite(leg);
|
|
461
73
|
}
|
|
462
74
|
|
|
463
75
|
// --- fs-jsonl: a large log under a memory ceiling ---
|
|
@@ -0,0 +1,52 @@
|
|
|
1
|
+
import { afterAll, afterEach, beforeAll, beforeEach, describe } from "vitest";
|
|
2
|
+
import {
|
|
3
|
+
meteredPool,
|
|
4
|
+
metering,
|
|
5
|
+
registerBoundedSqlLegSuite,
|
|
6
|
+
TEST_SCHEMA,
|
|
7
|
+
type SqlLeg,
|
|
8
|
+
} from "./bounded-store-reads.test-helpers.js";
|
|
9
|
+
import {
|
|
10
|
+
createDefaultLearningPgPool,
|
|
11
|
+
PostgresLessonStore,
|
|
12
|
+
type PgPoolLike,
|
|
13
|
+
} from "./lesson-store-postgres.js";
|
|
14
|
+
|
|
15
|
+
// Live-server leg of the bounded store reads suite (`pnpm test:pg`): the metered SQL contract the
|
|
16
|
+
// sqlite and in-memory-double legs run in bounded-store-reads.conformance.test.ts, against a real
|
|
17
|
+
// Postgres.
|
|
18
|
+
const databaseUrl = process.env.OPENCLAW_DATABASE_URL;
|
|
19
|
+
const live = process.env.OPENCLAW_LIVE_PG_TEST === "1" && Boolean(databaseUrl);
|
|
20
|
+
|
|
21
|
+
let livePool: PgPoolLike & { end?: () => Promise<void> };
|
|
22
|
+
// A fresh metered handle per test: the stores memoise schema init on the handle, and every test
|
|
23
|
+
// ends by dropping the schema.
|
|
24
|
+
let liveHandle: PgPoolLike;
|
|
25
|
+
|
|
26
|
+
const liveLeg: SqlLeg = {
|
|
27
|
+
label: "postgres (live server)",
|
|
28
|
+
scale: 0.5,
|
|
29
|
+
make: (meter) =>
|
|
30
|
+
metering(meter, new PostgresLessonStore({ pool: liveHandle, schema: TEST_SCHEMA, scope: {} })),
|
|
31
|
+
writer: (scope) => new PostgresLessonStore({ pool: liveHandle, schema: TEST_SCHEMA, scope }),
|
|
32
|
+
};
|
|
33
|
+
|
|
34
|
+
describe.skipIf(!live)("live postgres", () => {
|
|
35
|
+
beforeAll(() => {
|
|
36
|
+
livePool = createDefaultLearningPgPool(databaseUrl!);
|
|
37
|
+
});
|
|
38
|
+
|
|
39
|
+
beforeEach(() => {
|
|
40
|
+
liveHandle = meteredPool(livePool);
|
|
41
|
+
});
|
|
42
|
+
|
|
43
|
+
afterEach(async () => {
|
|
44
|
+
await livePool.query(`DROP SCHEMA IF EXISTS ${TEST_SCHEMA} CASCADE`);
|
|
45
|
+
});
|
|
46
|
+
|
|
47
|
+
afterAll(async () => {
|
|
48
|
+
await livePool.end?.();
|
|
49
|
+
});
|
|
50
|
+
|
|
51
|
+
registerBoundedSqlLegSuite(liveLeg);
|
|
52
|
+
});
|