@xopcai/xopc 0.0.119 → 0.0.121
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/browser-ext/manifest.json +1 -1
- package/dist/extensions/dashscope/src/image-generation-provider.d.ts +6 -5
- package/dist/extensions/dashscope/src/image-generation-provider.js +40 -64
- package/dist/extensions/dashscope/src/image-generation-provider.js.map +1 -1
- package/dist/extensions/dashscope/src/index.d.ts +1 -1
- package/dist/extensions/dashscope/src/index.js +2 -2
- package/dist/extensions/dashscope/xopc.extension.json +3 -2
- package/dist/extensions/minimax/src/image-generation-provider.d.ts +7 -5
- package/dist/extensions/minimax/src/image-generation-provider.js +43 -37
- package/dist/extensions/minimax/src/image-generation-provider.js.map +1 -1
- package/dist/extensions/minimax/src/index.d.ts +1 -1
- package/dist/extensions/minimax/src/index.js +2 -2
- package/dist/extensions/minimax/xopc.extension.json +1 -1
- package/dist/extensions/telegram/xopc.extension.json +1 -1
- package/dist/gateway/static/root/assets/Combination-HAlzriaz.js +41 -0
- package/dist/gateway/static/root/assets/{agent-avatar-dicebear-CU8J6zou.js → agent-avatar-dicebear-4AW4JgSU.js} +1 -1
- package/dist/gateway/static/root/assets/agents-2SVUUPlp.js +2 -0
- package/dist/gateway/static/root/assets/{app-management-settings-panel-B9vfYoEr.js → app-management-settings-panel-CE6GkGU2.js} +1 -1
- package/dist/gateway/static/root/assets/{appearance-settings-ClOzIO24.js → appearance-settings-BmaUjghw.js} +1 -1
- package/dist/gateway/static/root/assets/{apps-page-DU0dz9Zo.js → apps-page-BWeQ1I49.js} +1 -1
- package/dist/gateway/static/root/assets/{archive-plugin-BkVIgZzA.js → archive-plugin-CX4_KWut.js} +1 -1
- package/dist/gateway/static/root/assets/{attachment-load-CaCMWh1D.js → attachment-load-jf-PIIUU.js} +1 -1
- package/dist/gateway/static/root/assets/{automation-suggestion-card-DhGDpKP3.js → automation-suggestion-card-BHk5rtJz.js} +1 -1
- package/dist/gateway/static/root/assets/automations-page-QHotl53D.js +1 -0
- package/dist/gateway/static/root/assets/{binary-plugins-B4TO_bsn.js → binary-plugins-DG9XQikd.js} +1 -1
- package/dist/gateway/static/root/assets/{block-editor-DJRWwWNI.js → block-editor-Up_VIxLJ.js} +6 -6
- package/dist/gateway/static/root/assets/browser-settings-page-Cmwi6IIQ.js +8 -0
- package/dist/gateway/static/root/assets/{capability-presets-api-Befi89CL.js → capability-presets-api-CGM4OyMw.js} +1 -1
- package/dist/gateway/static/root/assets/{capability-presets-settings-panel-_G8eb2vJ.js → capability-presets-settings-panel-CzWt0LBb.js} +1 -1
- package/dist/gateway/static/root/assets/{channel-recipient-api-BNSuJb5f.js → channel-recipient-api-BEByL4_o.js} +1 -1
- package/dist/gateway/static/root/assets/channels-settings-BOMCUe2J.js +1 -0
- package/dist/gateway/static/root/assets/connectors-page-C-Dexk_F.js +2 -0
- package/dist/gateway/static/root/assets/dist-Mys7eFJ5.js +1 -0
- package/dist/gateway/static/root/assets/dist-wO9a2_ID.js +1 -0
- package/dist/gateway/static/root/assets/dreaming-settings-WPEj8pir.js +1 -0
- package/dist/gateway/static/root/assets/{extension-debug-page-DYh402XV.js → extension-debug-page-DZxa_j7-.js} +1 -1
- package/dist/gateway/static/root/assets/{extension-page-Cr_TizrN.js → extension-page-CseziW53.js} +1 -1
- package/dist/gateway/static/root/assets/extension-settings-page-B_9J-xyo.js +1 -0
- package/dist/gateway/static/root/assets/{fetch-Y7AEX4H3.js → fetch-Cs4ck8zA.js} +1 -1
- package/dist/gateway/static/root/assets/{gateway-config-api-RbMmAOpX.js → gateway-config-api-Dl223a7r.js} +1 -1
- package/dist/gateway/static/root/assets/gateway-settings-DdzbtW9R.js +1 -0
- package/dist/gateway/static/root/assets/{gateway-startup-retry-DiD_Tx4N.js → gateway-startup-retry-IvePwjFE.js} +1 -1
- package/dist/gateway/static/root/assets/goal-detail-page-Dxldogms.js +1 -0
- package/dist/gateway/static/root/assets/{goals-config-api-DqxVi0p5.js → goals-config-api-CUCP6yCl.js} +1 -1
- package/dist/gateway/static/root/assets/goals-page-DU5Nd6Xp.js +2 -0
- package/dist/gateway/static/root/assets/goals-settings-Dj87qPxv.js +1 -0
- package/dist/gateway/static/root/assets/heartbeat-settings-Cgm5JmwH.js +1 -0
- package/dist/gateway/static/root/assets/index-BU1pvxmS.js +191 -0
- package/dist/gateway/static/root/assets/index-l54WfSqe.css +1 -0
- package/dist/gateway/static/root/assets/{keyboard-shortcuts-settings-Dvg53qnd.js → keyboard-shortcuts-settings-DJgYw0nB.js} +1 -1
- package/dist/gateway/static/root/assets/{locale-store-DjbfSsfA.js → locale-store-Ba72bFmP.js} +1 -1
- package/dist/gateway/static/root/assets/logs-page-C10aS4XO.js +2 -0
- package/dist/gateway/static/root/assets/{markdown-split-CpHxbtWJ.js → markdown-split-gnQ_EiGo.js} +1 -1
- package/dist/gateway/static/root/assets/{media-plugins-SFPQRSxq.js → media-plugins-CQV-mgJn.js} +1 -1
- package/dist/gateway/static/root/assets/messages-C6Kc4Jhf.js +3 -0
- package/dist/gateway/static/root/assets/models-hub-panel-B35CAfg6.js +2 -0
- package/dist/gateway/static/root/assets/note-detail-page-Brlwse8x.js +2 -0
- package/dist/gateway/static/root/assets/{note-image-lightbox-BUYURRgn.js → note-image-lightbox-BthwKjLA.js} +1 -1
- package/dist/gateway/static/root/assets/{notes-page-BsIBSFyZ.js → notes-page-CoAwRc7L.js} +1 -1
- package/dist/gateway/static/root/assets/{preview-open-alternatives-BHz9iWYA.js → preview-open-alternatives-BPMXC-2u.js} +1 -1
- package/dist/gateway/static/root/assets/remote-access-hub-B36RQ05h.js +1 -0
- package/dist/gateway/static/root/assets/{save-bar-controls-CefkOlRg.js → save-bar-controls-BEHqwZjz.js} +1 -1
- package/dist/gateway/static/root/assets/schedule-field-CMY25m97.js +1 -0
- package/dist/gateway/static/root/assets/schema-form-CxjxsFfg.js +1 -0
- package/dist/gateway/static/root/assets/sessions-page-73THChnj.js +1 -0
- package/dist/gateway/static/root/assets/settings-advanced-gate-CR9VsY-R.js +1 -0
- package/dist/gateway/static/root/assets/{settings-collapsible-section-BouO6JTi.js → settings-collapsible-section-s-kiLAxP.js} +1 -1
- package/dist/gateway/static/root/assets/settings-form-section-CIxIEYxU.js +1 -0
- package/dist/gateway/static/root/assets/settings-page-BJ1AhfKI.js +1 -0
- package/dist/gateway/static/root/assets/{settings-page-layout-BJ4Gsef5.js → settings-page-layout--6qT7ZJa.js} +1 -1
- package/dist/gateway/static/root/assets/{setup-status-panel-CrUjHPQo.js → setup-status-panel-C1TtS1vJ.js} +1 -1
- package/dist/gateway/static/root/assets/{share-preview-page-BDy2btRN.js → share-preview-page-BZADSbS1.js} +1 -1
- package/dist/gateway/static/root/assets/shares-settings-gqC8Rmhy.js +1 -0
- package/dist/gateway/static/root/assets/skill-reload-api-C9P-WYvS.js +1 -0
- package/dist/gateway/static/root/assets/skills-page-CEpSvOhF.js +2 -0
- package/dist/gateway/static/root/assets/{system-settings-panel-CRngrlnZ.js → system-settings-panel-Czjf0kLJ.js} +1 -1
- package/dist/gateway/static/root/assets/{theme-store-CD2wtgZV.js → theme-store-Bt6vAkSB.js} +1 -1
- package/dist/gateway/static/root/assets/{typed-models-lib-BltBc0b-.js → typed-models-lib-Cmt8SYdI.js} +1 -1
- package/dist/gateway/static/root/assets/{use-channel-catalog-CjN-51e6.js → use-channel-catalog-DyM-ckEQ.js} +1 -1
- package/dist/gateway/static/root/assets/user-profile-settings-panel-BygLv8vm.js +1 -0
- package/dist/gateway/static/root/assets/{utils-B5M5-YvK.js → utils-CloW8coB.js} +1 -1
- package/dist/gateway/static/root/assets/voice-api-key-field-BsoqJba6.js +1 -0
- package/dist/gateway/static/root/assets/{voice-player-store-B7ykUlHM.js → voice-player-store-DduWlJYV.js} +1 -1
- package/dist/gateway/static/root/assets/workflow-run-setup-panel-CcQeX6l5.js +3 -0
- package/dist/gateway/static/root/assets/workflows-page-CeA8A9Ef.js +18 -0
- package/dist/gateway/static/root/index.html +9 -10
- package/dist/package.js +1 -1
- package/dist/src/agent/context/workspace-templates/AGENTS.md +3 -2
- package/dist/src/agent/embedded/xopc-auth-storage.d.ts +1 -5
- package/dist/src/agent/embedded/xopc-auth-storage.js +2 -8
- package/dist/src/agent/embedded/xopc-auth-storage.js.map +1 -1
- package/dist/src/agent/embedded/xopc-tools-bridge.js +23 -0
- package/dist/src/agent/embedded/xopc-tools-bridge.js.map +1 -1
- package/dist/src/agent/goals/checklist-judge.js +1 -1
- package/dist/src/agent/goals/checklist-judge.js.map +1 -1
- package/dist/src/agent/goals/judge.js +1 -1
- package/dist/src/agent/goals/judge.js.map +1 -1
- package/dist/src/agent/image/tool-model-config.d.ts +2 -0
- package/dist/src/agent/image/tool-model-config.js +2 -2
- package/dist/src/agent/image/tool-model-config.js.map +1 -1
- package/dist/src/agent/image/understanding/pi-ai-provider.js +1 -1
- package/dist/src/agent/image/understanding/pi-ai-provider.js.map +1 -1
- package/dist/src/agent/memory/builtin-memory-store.d.ts +1 -1
- package/dist/src/agent/memory/builtin-memory-store.js +1 -1
- package/dist/src/agent/memory/builtin-memory-store.js.map +1 -1
- package/dist/src/agent/memory/builtin-provider.d.ts +2 -2
- package/dist/src/agent/memory/builtin-provider.js +2 -2
- package/dist/src/agent/memory/builtin-provider.js.map +1 -1
- package/dist/src/agent/memory/compaction.d.ts +1 -1
- package/dist/src/agent/memory/compaction.js +1 -1
- package/dist/src/agent/memory/compaction.js.map +1 -1
- package/dist/src/agent/memory/dreaming/light-sweep.d.ts +0 -7
- package/dist/src/agent/memory/dreaming/light-sweep.js +16 -277
- package/dist/src/agent/memory/dreaming/light-sweep.js.map +1 -1
- package/dist/src/agent/memory/dreaming/rem-patterns.d.ts +1 -1
- package/dist/src/agent/memory/dreaming/rem-patterns.js +1 -1
- package/dist/src/agent/memory/dreaming/rem-patterns.js.map +1 -1
- package/dist/src/agent/memory/dreaming/short-term-store.d.ts +3 -3
- package/dist/src/agent/memory/dreaming/short-term-store.js +5 -5
- package/dist/src/agent/memory/dreaming/short-term-store.js.map +1 -1
- package/dist/src/agent/memory/dreaming/utils.d.ts +2 -2
- package/dist/src/agent/memory/dreaming/utils.js +2 -2
- package/dist/src/agent/memory/dreaming/utils.js.map +1 -1
- package/dist/src/agent/memory/index.d.ts +0 -2
- package/dist/src/agent/memory/index.js +1 -2
- package/dist/src/agent/middleware/self-verify.d.ts +3 -0
- package/dist/src/agent/middleware/self-verify.js +18 -0
- package/dist/src/agent/middleware/self-verify.js.map +1 -1
- package/dist/src/agent/orchestration/agent-event-handler.js +28 -3
- package/dist/src/agent/orchestration/agent-event-handler.js.map +1 -1
- package/dist/src/agent/prompt/sections/memory-skills.js +11 -10
- package/dist/src/agent/prompt/sections/memory-skills.js.map +1 -1
- package/dist/src/agent/prompt/sections/tooling.js +23 -7
- package/dist/src/agent/prompt/sections/tooling.js.map +1 -1
- package/dist/src/agent/prompt/service-prompt-builder.js +1 -0
- package/dist/src/agent/prompt/service-prompt-builder.js.map +1 -1
- package/dist/src/agent/prompt/system-prompt.d.ts +1 -0
- package/dist/src/agent/prompt/system-prompt.js +13 -1
- package/dist/src/agent/prompt/system-prompt.js.map +1 -1
- package/dist/src/agent/reply/startup-context.js +6 -105
- package/dist/src/agent/reply/startup-context.js.map +1 -1
- package/dist/src/agent/sandbox/path-policy.js +30 -6
- package/dist/src/agent/sandbox/path-policy.js.map +1 -1
- package/dist/src/agent/service/btw-query.js +1 -1
- package/dist/src/agent/service/btw-query.js.map +1 -1
- package/dist/src/agent/service.d.ts +0 -3
- package/dist/src/agent/service.js +0 -41
- package/dist/src/agent/service.js.map +1 -1
- package/dist/src/agent/starter-agents.js +16 -16
- package/dist/src/agent/starter-agents.js.map +1 -1
- package/dist/src/agent/tools/image-generate-tool.js +12 -1
- package/dist/src/agent/tools/image-generate-tool.js.map +1 -1
- package/dist/src/agent/tools/memory-tool.js +2 -2
- package/dist/src/agent/tools/memory-tool.js.map +1 -1
- package/dist/src/agent/tools/session-search-tool.js +1 -1
- package/dist/src/agent/tools/session-search-tool.js.map +1 -1
- package/dist/src/agent/tools/web-extract.js +1 -1
- package/dist/src/agent/tools/web-extract.js.map +1 -1
- package/dist/src/agent-manifest/index.js +2 -2
- package/dist/src/agent-manifest/schema.d.ts +66 -0
- package/dist/src/agent-manifest/schema.js +28 -2
- package/dist/src/agent-manifest/schema.js.map +1 -1
- package/dist/src/automations/draft/automation-draft-service.js +1 -1
- package/dist/src/automations/draft/automation-draft-service.js.map +1 -1
- package/dist/src/cli/commands/agent.js +2 -2
- package/dist/src/cli/commands/gateway/lifecycle.js +1 -1
- package/dist/src/cli/commands/init.js +1 -1
- package/dist/src/cli/commands/init.js.map +1 -1
- package/dist/src/cli/commands/models.js +1 -1
- package/dist/src/cli/commands/onboard/model.js +161 -11
- package/dist/src/cli/commands/onboard/model.js.map +1 -1
- package/dist/src/cli/commands/onboard.js +1 -1
- package/dist/src/cli/commands/profile.js +1 -1
- package/dist/src/cli/commands/tunnel.js +1 -1
- package/dist/src/cli/commands/update.js +1 -1
- package/dist/src/cli/extension-cli-register.js +1 -1
- package/dist/src/cli/templates.js +3 -2
- package/dist/src/cli/templates.js.map +1 -1
- package/dist/src/config/agent-profile.js +1 -1
- package/dist/src/config/default-model.d.ts +11 -0
- package/dist/src/config/default-model.js +19 -0
- package/dist/src/config/default-model.js.map +1 -0
- package/dist/src/config/index.js +3 -3
- package/dist/src/config/schema.d.ts +73 -13
- package/dist/src/config/schema.js +59 -24
- package/dist/src/config/schema.js.map +1 -1
- package/dist/src/connectors/registries/smithery.js +10 -9
- package/dist/src/connectors/registries/smithery.js.map +1 -1
- package/dist/src/daemon/install-plan.js +2 -1
- package/dist/src/daemon/install-plan.js.map +1 -1
- package/dist/src/gateway/agents-admin.js +1 -1
- package/dist/src/gateway/global-defaults-admin.js +6 -16
- package/dist/src/gateway/global-defaults-admin.js.map +1 -1
- package/dist/src/gateway/hono/lib/config-payload.d.ts +24 -0
- package/dist/src/gateway/hono/routes/agents.js +1 -79
- package/dist/src/gateway/hono/routes/agents.js.map +1 -1
- package/dist/src/gateway/hono/routes/config-patch/misc.js +4 -1
- package/dist/src/gateway/hono/routes/config-patch/misc.js.map +1 -1
- package/dist/src/gateway/hono/routes/models.js +132 -14
- package/dist/src/gateway/hono/routes/models.js.map +1 -1
- package/dist/src/gateway/hono/routes/voice.js +1 -1
- package/dist/src/gateway/hono/routes/voice.js.map +1 -1
- package/dist/src/gateway/image-capabilities.d.ts +11 -1
- package/dist/src/gateway/image-capabilities.js +32 -43
- package/dist/src/gateway/image-capabilities.js.map +1 -1
- package/dist/src/gateway/run-loop.js +1 -1
- package/dist/src/gateway/workspace-editor-path.js +9 -1
- package/dist/src/gateway/workspace-editor-path.js.map +1 -1
- package/dist/src/generated/bundled-image-generation-providers.d.ts +4 -2
- package/dist/src/generated/bundled-image-generation-providers.js +6 -4
- package/dist/src/generated/bundled-image-generation-providers.js.map +1 -1
- package/dist/src/infra/update-restart.js +1 -1
- package/dist/src/mcp/channel-bridge.js +1 -1
- package/dist/src/media/paths.js +3 -4
- package/dist/src/media/paths.js.map +1 -1
- package/dist/src/notes/service.js +1 -1
- package/dist/src/notes/service.js.map +1 -1
- package/dist/src/providers/domestic-presets.d.ts +615 -0
- package/dist/src/providers/domestic-presets.js +834 -0
- package/dist/src/providers/domestic-presets.js.map +1 -0
- package/dist/src/providers/env-keys.js +29 -5
- package/dist/src/providers/env-keys.js.map +1 -1
- package/dist/src/providers/extension-stream-bridge.js +1 -1
- package/dist/src/providers/extension-stream-bridge.js.map +1 -1
- package/dist/src/providers/index.d.ts +5 -5
- package/dist/src/providers/index.js +101 -21
- package/dist/src/providers/index.js.map +1 -1
- package/dist/src/providers/model-discovery.d.ts +17 -0
- package/dist/src/providers/model-discovery.js +61 -0
- package/dist/src/providers/model-discovery.js.map +1 -0
- package/dist/src/providers/model-registry.d.ts +1 -1
- package/dist/src/providers/model-registry.js +1 -1
- package/dist/src/providers/model-registry.js.map +1 -1
- package/dist/src/providers/presentation.js +185 -14
- package/dist/src/providers/presentation.js.map +1 -1
- package/dist/src/session/session-title.js +1 -1
- package/dist/src/session/session-title.js.map +1 -1
- package/dist/src/storage/sqlite/row-mappers.js +3 -1
- package/dist/src/storage/sqlite/row-mappers.js.map +1 -1
- package/dist/src/tui/backends/embedded-backend.js +1 -2
- package/dist/src/tui/backends/embedded-backend.js.map +1 -1
- package/dist/src/tui/xopc-tui-keybindings.d.ts +1 -1
- package/dist/src/voice/tts/summarize.js +1 -1
- package/dist/src/voice/tts/summarize.js.map +1 -1
- package/dist/src/workflows/draft/workflow-draft-service.js +1 -1
- package/dist/src/workflows/draft/workflow-draft-service.js.map +1 -1
- package/package.json +6 -6
- package/dist/gateway/static/root/assets/Combination-BzaXOLd9.js +0 -41
- package/dist/gateway/static/root/assets/agents-B0hkpLhP.js +0 -2
- package/dist/gateway/static/root/assets/automations-page-CTedroVo.js +0 -1
- package/dist/gateway/static/root/assets/browser-settings-page-FCDwOOBK.js +0 -8
- package/dist/gateway/static/root/assets/channels-settings-C3d5JBJX.js +0 -1
- package/dist/gateway/static/root/assets/connectors-page-yaUNvt5u.js +0 -2
- package/dist/gateway/static/root/assets/dist-By6Rh2ee.js +0 -1
- package/dist/gateway/static/root/assets/dist-CkobR7mk.js +0 -1
- package/dist/gateway/static/root/assets/dist-DokQNVMQ.js +0 -1
- package/dist/gateway/static/root/assets/dreaming-settings-DNrdg1Ve.js +0 -1
- package/dist/gateway/static/root/assets/extension-settings-page-BU1KoKRj.js +0 -1
- package/dist/gateway/static/root/assets/gateway-settings-C79wkXef.js +0 -1
- package/dist/gateway/static/root/assets/goal-detail-page-9paXuHdo.js +0 -1
- package/dist/gateway/static/root/assets/goals-page-CS0R-Af2.js +0 -2
- package/dist/gateway/static/root/assets/goals-settings-CSuzZ8Ae.js +0 -1
- package/dist/gateway/static/root/assets/heartbeat-settings-DXPaYJm3.js +0 -1
- package/dist/gateway/static/root/assets/index-BoKafJIZ.js +0 -191
- package/dist/gateway/static/root/assets/index-CArhCcYn.css +0 -1
- package/dist/gateway/static/root/assets/logs-page-DLXPcksQ.js +0 -2
- package/dist/gateway/static/root/assets/messages-CrDHEbss.js +0 -3
- package/dist/gateway/static/root/assets/models-hub-panel-BDF2iyRk.js +0 -2
- package/dist/gateway/static/root/assets/note-detail-page-CSb_hwig.js +0 -2
- package/dist/gateway/static/root/assets/remote-access-hub-CwzSovcS.js +0 -1
- package/dist/gateway/static/root/assets/schedule-field-CmTRoOcE.js +0 -1
- package/dist/gateway/static/root/assets/schema-form-FMKv1vGu.js +0 -1
- package/dist/gateway/static/root/assets/sessions-page-PHmX-NmA.js +0 -1
- package/dist/gateway/static/root/assets/settings-advanced-gate-BRAeKJN4.js +0 -1
- package/dist/gateway/static/root/assets/settings-form-section-D-4lBl6X.js +0 -1
- package/dist/gateway/static/root/assets/settings-page-KKrbFjy8.js +0 -1
- package/dist/gateway/static/root/assets/shares-settings-B18OBmH5.js +0 -1
- package/dist/gateway/static/root/assets/skill-reload-api-C28-2uKQ.js +0 -1
- package/dist/gateway/static/root/assets/skills-page-Bm2ebkwl.js +0 -2
- package/dist/gateway/static/root/assets/user-profile-settings-panel-DJ1dKwZy.js +0 -1
- package/dist/gateway/static/root/assets/voice-api-key-field-CeEevm7z.js +0 -1
- package/dist/gateway/static/root/assets/workflow-run-setup-panel-C4PzUQjB.js +0 -3
- package/dist/gateway/static/root/assets/workflows-page-BL84_-Ky.js +0 -18
- package/dist/src/agent/memory/memory-flush.d.ts +0 -27
- package/dist/src/agent/memory/memory-flush.js +0 -124
- package/dist/src/agent/memory/memory-flush.js.map +0 -1
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"judge.js","names":[],"sources":["../../../../src/agent/goals/judge.ts"],"sourcesContent":["import type { UserMessage } from '@earendil-works/pi-ai';\nimport { complete } from '@earendil-works/pi-ai';\n\nimport { getApiKey, resolveModel } from '../../providers/index.js';\n\nimport type { GoalUiLocale } from './goal-locale.js';\nimport {\n goalUiLocaleOrFallback,\n judgeFreeformBuiltinMessages,\n JUDGE_REASON_EN,\n judgeResponseLanguageNote,\n localizeJudgeReasonText,\n} from './goal-locale.js';\n\nconst JUDGE_RESPONSE_SNIPPET_CHARS = 4000;\n\n/**\n * Extract visible text from a pi-ai `AssistantMessage.content` array.\n * Handles `TextContent` (`type: 'text'`) and falls back to `ThinkingContent`\n * (`type: 'thinking'`) when the text blocks are empty — reasoning models\n * (DeepSeek-R1, Qwen-thinking, etc.) may place the entire response inside\n * thinking blocks, leaving the text portion blank.\n */\nexport function extractAssistantText(content: unknown): string {\n if (!Array.isArray(content)) return '';\n\n let textParts = '';\n let thinkingParts = '';\n\n for (const block of content) {\n if (!block || typeof block !== 'object') continue;\n const typed = block as Record<string, unknown>;\n const blockType = typed.type;\n if (blockType === 'text' && typeof typed.text === 'string') {\n textParts += typed.text;\n } else if (blockType === 'thinking') {\n const thinking =\n typeof typed.thinking === 'string'\n ? typed.thinking\n : typeof typed.text === 'string'\n ? typed.text\n : '';\n thinkingParts += thinking;\n }\n }\n\n // Prefer text blocks; fall back to thinking blocks when text is empty.\n return textParts.trim() ? textParts : thinkingParts;\n}\n\n/**\n * Strip markdown code fences (opening AND closing) from raw model output.\n * Handles `` ```json ``, `` ``` ``, and trailing `` ``` `` with optional whitespace.\n */\nexport function stripCodeFences(raw: string): string {\n let text = raw.trim();\n\n // Remove opening code fence: ```<optional-lang>\\n\n const openMatch = text.match(/^`{3,}[^\\n]*\\n?/);\n if (openMatch) {\n text = text.slice(openMatch[0].length);\n }\n\n // Remove closing code fence: \\n```<optional-whitespace>\n const closeMatch = text.match(/\\n?`{3,}\\s*$/);\n if (closeMatch) {\n text = text.slice(0, -closeMatch[0].length);\n }\n\n return text.trim();\n}\n\n/** Mirrors `hermes_cli/goals.py` — strict judge, JSON-only reply. */\nexport const JUDGE_SYSTEM_PROMPT =\n 'You are a strict judge evaluating whether an autonomous agent has ' +\n \"achieved a user's stated goal. You receive the goal text and the \" +\n \"agent's most recent response. Your only job is to decide whether \" +\n 'the goal is fully satisfied based on that response.\\n\\n' +\n 'A goal is DONE only when:\\n' +\n '- The response explicitly confirms the goal was completed, OR\\n' +\n '- The response clearly shows the final deliverable was produced, OR\\n' +\n '- The response explains the goal is unachievable / blocked / needs ' +\n 'user input (treat this as DONE with reason describing the block).\\n\\n' +\n 'Otherwise the goal is NOT done — CONTINUE.\\n\\n' +\n 'Reply ONLY with a single JSON object on one line:\\n' +\n '{\"done\": <true|false>, \"reason\": \"<one-sentence rationale>\"}';\n\nexport const JUDGE_USER_PROMPT_TEMPLATE =\n 'Goal:\\n{goal}\\n\\n' + \"Agent's most recent response:\\n{response}\\n\\n\" + 'Is the goal satisfied?';\n\nexport const DEFAULT_JUDGE_TIMEOUT_MS = 60_000;\n\nexport function truncateGoalText(text: string, limit: number): string {\n if (!text) return '';\n if (text.length <= limit) return text;\n return text.slice(0, limit) + '… [truncated]';\n}\n\nexport async function resolveGoalJudgeApiKey(\n model: ReturnType<typeof resolveModel>,\n): Promise<string | undefined> {\n try {\n return await getApiKey(model.provider);\n } catch {\n return undefined;\n }\n}\n\nexport function getAssistantMessageErrorReason(message: unknown): string | null {\n if (!message || typeof message !== 'object') return null;\n\n const record = message as Record<string, unknown>;\n const stopReason = record.stopReason;\n const errorMessage = typeof record.errorMessage === 'string' ? record.errorMessage.trim() : '';\n\n if (stopReason === 'error') {\n return errorMessage || 'Judge model call failed.';\n }\n\n return null;\n}\n\n/** Parse judge JSON — fail-open to **continue** (Hermes semantics). */\nexport function parseJudgeResponseFailOpen(raw: string): {\n done: boolean;\n reason: string;\n parseFailed: boolean;\n} {\n if (!raw?.trim()) {\n return { done: false, reason: JUDGE_REASON_EN.judge_returned_empty, parseFailed: true };\n }\n\n const text = stripCodeFences(raw);\n\n let data: Record<string, unknown> | null = null;\n try {\n data = JSON.parse(text) as Record<string, unknown>;\n } catch {\n const start = text.indexOf('{');\n const end = text.lastIndexOf('}');\n if (start !== -1 && end > start) {\n try {\n data = JSON.parse(text.slice(start, end + 1)) as Record<string, unknown>;\n } catch {\n data = null;\n }\n }\n }\n\n if (!data || typeof data !== 'object') {\n return { done: false, reason: JUDGE_REASON_EN.judge_reply_not_json, parseFailed: true };\n }\n\n const doneVal = data.done;\n let done: boolean;\n if (typeof doneVal === 'string') {\n done = ['true', 'yes', '1', 'done'].includes(doneVal.trim().toLowerCase());\n } else {\n done = Boolean(doneVal);\n }\n const reason = typeof data.reason === 'string' ? data.reason.trim() : '';\n return { done, reason: reason || JUDGE_REASON_EN.no_reason_provided, parseFailed: false };\n}\n\nexport type GoalJudgeVerdict = 'done' | 'continue' | 'skipped';\n\n/**\n * Ask the configured model whether the goal is satisfied.\n * Fail-open: any error → `continue` (Hermes — turn budget is the backstop).\n */\nexport async function judgeGoalHermesStyle(\n goal: string,\n lastResponse: string,\n judgeModelRef: string,\n signal?: AbortSignal,\n opts?: { judgeTimeoutMs?: number; uiLocale?: GoalUiLocale },\n): Promise<{ verdict: GoalJudgeVerdict; reason: string; parseFailed: boolean }> {\n const locale = goalUiLocaleOrFallback(opts?.uiLocale);\n const b = judgeFreeformBuiltinMessages(locale);\n\n if (!goal.trim()) {\n return { verdict: 'skipped', reason: b.emptyGoal, parseFailed: false };\n }\n if (!lastResponse.trim()) {\n return { verdict: 'continue', reason: b.emptyResponse, parseFailed: false };\n }\n\n let model: ReturnType<typeof resolveModel>;\n try {\n model = resolveModel(judgeModelRef);\n } catch {\n return { verdict: 'continue', reason: b.noModel, parseFailed: false };\n }\n\n const userContent =\n JUDGE_USER_PROMPT_TEMPLATE.replace('{goal}', truncateGoalText(goal, 2000)).replace(\n '{response}',\n truncateGoalText(lastResponse, JUDGE_RESPONSE_SNIPPET_CHARS),\n ) + judgeResponseLanguageNote(locale);\n\n const timeoutMs =\n typeof opts?.judgeTimeoutMs === 'number' && Number.isFinite(opts.judgeTimeoutMs)\n ? Math.max(5_000, Math.min(120_000, Math.floor(opts.judgeTimeoutMs)))\n : DEFAULT_JUDGE_TIMEOUT_MS;\n const controller = new AbortController();\n const timer = setTimeout(() => controller.abort(), timeoutMs);\n const merged = signal ? AbortSignal.any([signal, controller.signal]) : controller.signal;\n\n try {\n const combinedUser: UserMessage = {\n role: 'user',\n content: `${JUDGE_SYSTEM_PROMPT}\\n\\n${userContent}`,\n timestamp: Date.now(),\n };\n\n const apiKey = await resolveGoalJudgeApiKey(model);\n const result = await complete(\n model,\n {\n messages: [combinedUser],\n },\n { apiKey, maxTokens: 200, temperature: 0, signal: merged },\n );\n const errorReason = getAssistantMessageErrorReason(result);\n if (errorReason) {\n return { verdict: 'continue', reason: b.callFailed, parseFailed: false };\n }\n\n const text = extractAssistantText(result.content);\n const { done, reason, parseFailed } = parseJudgeResponseFailOpen(text);\n const reasonOut = localizeJudgeReasonText(reason, locale);\n return { verdict: done ? 'done' : 'continue', reason: reasonOut, parseFailed };\n } catch {\n return { verdict: 'continue', reason: b.callFailed, parseFailed: false };\n } finally {\n clearTimeout(timer);\n }\n}\n"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"judge.js","names":[],"sources":["../../../../src/agent/goals/judge.ts"],"sourcesContent":["import type { UserMessage } from '@earendil-works/pi-ai';\nimport { complete } from '@earendil-works/pi-ai/compat';\n\nimport { getApiKey, resolveModel } from '../../providers/index.js';\n\nimport type { GoalUiLocale } from './goal-locale.js';\nimport {\n goalUiLocaleOrFallback,\n judgeFreeformBuiltinMessages,\n JUDGE_REASON_EN,\n judgeResponseLanguageNote,\n localizeJudgeReasonText,\n} from './goal-locale.js';\n\nconst JUDGE_RESPONSE_SNIPPET_CHARS = 4000;\n\n/**\n * Extract visible text from a pi-ai `AssistantMessage.content` array.\n * Handles `TextContent` (`type: 'text'`) and falls back to `ThinkingContent`\n * (`type: 'thinking'`) when the text blocks are empty — reasoning models\n * (DeepSeek-R1, Qwen-thinking, etc.) may place the entire response inside\n * thinking blocks, leaving the text portion blank.\n */\nexport function extractAssistantText(content: unknown): string {\n if (!Array.isArray(content)) return '';\n\n let textParts = '';\n let thinkingParts = '';\n\n for (const block of content) {\n if (!block || typeof block !== 'object') continue;\n const typed = block as Record<string, unknown>;\n const blockType = typed.type;\n if (blockType === 'text' && typeof typed.text === 'string') {\n textParts += typed.text;\n } else if (blockType === 'thinking') {\n const thinking =\n typeof typed.thinking === 'string'\n ? typed.thinking\n : typeof typed.text === 'string'\n ? typed.text\n : '';\n thinkingParts += thinking;\n }\n }\n\n // Prefer text blocks; fall back to thinking blocks when text is empty.\n return textParts.trim() ? textParts : thinkingParts;\n}\n\n/**\n * Strip markdown code fences (opening AND closing) from raw model output.\n * Handles `` ```json ``, `` ``` ``, and trailing `` ``` `` with optional whitespace.\n */\nexport function stripCodeFences(raw: string): string {\n let text = raw.trim();\n\n // Remove opening code fence: ```<optional-lang>\\n\n const openMatch = text.match(/^`{3,}[^\\n]*\\n?/);\n if (openMatch) {\n text = text.slice(openMatch[0].length);\n }\n\n // Remove closing code fence: \\n```<optional-whitespace>\n const closeMatch = text.match(/\\n?`{3,}\\s*$/);\n if (closeMatch) {\n text = text.slice(0, -closeMatch[0].length);\n }\n\n return text.trim();\n}\n\n/** Mirrors `hermes_cli/goals.py` — strict judge, JSON-only reply. */\nexport const JUDGE_SYSTEM_PROMPT =\n 'You are a strict judge evaluating whether an autonomous agent has ' +\n \"achieved a user's stated goal. You receive the goal text and the \" +\n \"agent's most recent response. Your only job is to decide whether \" +\n 'the goal is fully satisfied based on that response.\\n\\n' +\n 'A goal is DONE only when:\\n' +\n '- The response explicitly confirms the goal was completed, OR\\n' +\n '- The response clearly shows the final deliverable was produced, OR\\n' +\n '- The response explains the goal is unachievable / blocked / needs ' +\n 'user input (treat this as DONE with reason describing the block).\\n\\n' +\n 'Otherwise the goal is NOT done — CONTINUE.\\n\\n' +\n 'Reply ONLY with a single JSON object on one line:\\n' +\n '{\"done\": <true|false>, \"reason\": \"<one-sentence rationale>\"}';\n\nexport const JUDGE_USER_PROMPT_TEMPLATE =\n 'Goal:\\n{goal}\\n\\n' + \"Agent's most recent response:\\n{response}\\n\\n\" + 'Is the goal satisfied?';\n\nexport const DEFAULT_JUDGE_TIMEOUT_MS = 60_000;\n\nexport function truncateGoalText(text: string, limit: number): string {\n if (!text) return '';\n if (text.length <= limit) return text;\n return text.slice(0, limit) + '… [truncated]';\n}\n\nexport async function resolveGoalJudgeApiKey(\n model: ReturnType<typeof resolveModel>,\n): Promise<string | undefined> {\n try {\n return await getApiKey(model.provider);\n } catch {\n return undefined;\n }\n}\n\nexport function getAssistantMessageErrorReason(message: unknown): string | null {\n if (!message || typeof message !== 'object') return null;\n\n const record = message as Record<string, unknown>;\n const stopReason = record.stopReason;\n const errorMessage = typeof record.errorMessage === 'string' ? record.errorMessage.trim() : '';\n\n if (stopReason === 'error') {\n return errorMessage || 'Judge model call failed.';\n }\n\n return null;\n}\n\n/** Parse judge JSON — fail-open to **continue** (Hermes semantics). */\nexport function parseJudgeResponseFailOpen(raw: string): {\n done: boolean;\n reason: string;\n parseFailed: boolean;\n} {\n if (!raw?.trim()) {\n return { done: false, reason: JUDGE_REASON_EN.judge_returned_empty, parseFailed: true };\n }\n\n const text = stripCodeFences(raw);\n\n let data: Record<string, unknown> | null = null;\n try {\n data = JSON.parse(text) as Record<string, unknown>;\n } catch {\n const start = text.indexOf('{');\n const end = text.lastIndexOf('}');\n if (start !== -1 && end > start) {\n try {\n data = JSON.parse(text.slice(start, end + 1)) as Record<string, unknown>;\n } catch {\n data = null;\n }\n }\n }\n\n if (!data || typeof data !== 'object') {\n return { done: false, reason: JUDGE_REASON_EN.judge_reply_not_json, parseFailed: true };\n }\n\n const doneVal = data.done;\n let done: boolean;\n if (typeof doneVal === 'string') {\n done = ['true', 'yes', '1', 'done'].includes(doneVal.trim().toLowerCase());\n } else {\n done = Boolean(doneVal);\n }\n const reason = typeof data.reason === 'string' ? data.reason.trim() : '';\n return { done, reason: reason || JUDGE_REASON_EN.no_reason_provided, parseFailed: false };\n}\n\nexport type GoalJudgeVerdict = 'done' | 'continue' | 'skipped';\n\n/**\n * Ask the configured model whether the goal is satisfied.\n * Fail-open: any error → `continue` (Hermes — turn budget is the backstop).\n */\nexport async function judgeGoalHermesStyle(\n goal: string,\n lastResponse: string,\n judgeModelRef: string,\n signal?: AbortSignal,\n opts?: { judgeTimeoutMs?: number; uiLocale?: GoalUiLocale },\n): Promise<{ verdict: GoalJudgeVerdict; reason: string; parseFailed: boolean }> {\n const locale = goalUiLocaleOrFallback(opts?.uiLocale);\n const b = judgeFreeformBuiltinMessages(locale);\n\n if (!goal.trim()) {\n return { verdict: 'skipped', reason: b.emptyGoal, parseFailed: false };\n }\n if (!lastResponse.trim()) {\n return { verdict: 'continue', reason: b.emptyResponse, parseFailed: false };\n }\n\n let model: ReturnType<typeof resolveModel>;\n try {\n model = resolveModel(judgeModelRef);\n } catch {\n return { verdict: 'continue', reason: b.noModel, parseFailed: false };\n }\n\n const userContent =\n JUDGE_USER_PROMPT_TEMPLATE.replace('{goal}', truncateGoalText(goal, 2000)).replace(\n '{response}',\n truncateGoalText(lastResponse, JUDGE_RESPONSE_SNIPPET_CHARS),\n ) + judgeResponseLanguageNote(locale);\n\n const timeoutMs =\n typeof opts?.judgeTimeoutMs === 'number' && Number.isFinite(opts.judgeTimeoutMs)\n ? Math.max(5_000, Math.min(120_000, Math.floor(opts.judgeTimeoutMs)))\n : DEFAULT_JUDGE_TIMEOUT_MS;\n const controller = new AbortController();\n const timer = setTimeout(() => controller.abort(), timeoutMs);\n const merged = signal ? AbortSignal.any([signal, controller.signal]) : controller.signal;\n\n try {\n const combinedUser: UserMessage = {\n role: 'user',\n content: `${JUDGE_SYSTEM_PROMPT}\\n\\n${userContent}`,\n timestamp: Date.now(),\n };\n\n const apiKey = await resolveGoalJudgeApiKey(model);\n const result = await complete(\n model,\n {\n messages: [combinedUser],\n },\n { apiKey, maxTokens: 200, temperature: 0, signal: merged },\n );\n const errorReason = getAssistantMessageErrorReason(result);\n if (errorReason) {\n return { verdict: 'continue', reason: b.callFailed, parseFailed: false };\n }\n\n const text = extractAssistantText(result.content);\n const { done, reason, parseFailed } = parseJudgeResponseFailOpen(text);\n const reasonOut = localizeJudgeReasonText(reason, locale);\n return { verdict: done ? 'done' : 'continue', reason: reasonOut, parseFailed };\n } catch {\n return { verdict: 'continue', reason: b.callFailed, parseFailed: false };\n } finally {\n clearTimeout(timer);\n }\n}\n"],"mappings":"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"}
|
|
@@ -2,6 +2,8 @@ import type { AgentModelConfig, Config } from '../../config/schema.js';
|
|
|
2
2
|
export type ToolModelConfig = {
|
|
3
3
|
primary?: string;
|
|
4
4
|
fallbacks?: string[];
|
|
5
|
+
timeoutMs?: number;
|
|
6
|
+
autoProviderFallback?: boolean;
|
|
5
7
|
};
|
|
6
8
|
export declare function hasToolModelConfig(model: ToolModelConfig | undefined): boolean;
|
|
7
9
|
export declare function resolveDefaultModelRef(cfg?: Config): {
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { getAgentDefaultModelRef, init_schema, parseModelRef } from "../../config/schema.js";
|
|
1
|
+
import { getAgentDefaultImageModelConfig, getAgentDefaultModelRef, init_schema, parseModelRef } from "../../config/schema.js";
|
|
2
2
|
import { getDefaultModelSync, getModelsByProvider, init_providers, isProviderConfiguredSync } from "../../providers/index.js";
|
|
3
3
|
import { resolveAgentModelFallbackValues, resolveAgentModelPrimaryValue } from "../../config/model-input.js";
|
|
4
4
|
//#region src/agent/image/tool-model-config.ts
|
|
@@ -52,7 +52,7 @@ function firstVisionModelRef(provider) {
|
|
|
52
52
|
* Effective image understanding model inferred from configured providers.
|
|
53
53
|
*/
|
|
54
54
|
function resolveImageModelConfigForTool(params) {
|
|
55
|
-
const explicit = coerceToolModelConfig(void 0);
|
|
55
|
+
const explicit = coerceToolModelConfig(params.cfg ? getAgentDefaultImageModelConfig(params.cfg) : void 0);
|
|
56
56
|
if (hasToolModelConfig(explicit)) return explicit;
|
|
57
57
|
const primary = resolveDefaultModelRef(params.cfg);
|
|
58
58
|
const primaryCandidates = [];
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"tool-model-config.js","names":[],"sources":["../../../../src/agent/image/tool-model-config.ts"],"sourcesContent":["import type { AgentModelConfig, Config } from '../../config/schema.js';\nimport {
|
|
1
|
+
{"version":3,"file":"tool-model-config.js","names":[],"sources":["../../../../src/agent/image/tool-model-config.ts"],"sourcesContent":["import type { AgentModelConfig, Config } from '../../config/schema.js';\nimport {\n getAgentDefaultImageModelConfig,\n getAgentDefaultModelRef,\n parseModelRef,\n} from '../../config/schema.js';\nimport {\n resolveAgentModelFallbackValues,\n resolveAgentModelPrimaryValue,\n} from '../../config/model-input.js';\nimport { getDefaultModelSync, getModelsByProvider, isProviderConfiguredSync } from '../../providers/index.js';\n\nexport type ToolModelConfig = {\n primary?: string;\n fallbacks?: string[];\n timeoutMs?: number;\n autoProviderFallback?: boolean;\n};\n\nexport function hasToolModelConfig(model: ToolModelConfig | undefined): boolean {\n return Boolean(\n model?.primary?.trim() || (model?.fallbacks ?? []).some((entry) => entry.trim().length > 0),\n );\n}\n\nexport function resolveDefaultModelRef(cfg?: Config): { provider: string; model: string } {\n const ref = cfg ? getAgentDefaultModelRef(cfg) : undefined;\n if (ref) {\n const p = parseModelRef(ref);\n if (p) {\n return p;\n }\n }\n const fallback = getDefaultModelSync(cfg);\n const p2 = parseModelRef(fallback);\n if (p2) {\n return p2;\n }\n return { provider: 'deepseek', model: 'deepseek-v4-flash' };\n}\n\nexport function coerceToolModelConfig(model?: AgentModelConfig): ToolModelConfig {\n const primary = resolveAgentModelPrimaryValue(model);\n const fallbacks = resolveAgentModelFallbackValues(model);\n return {\n ...(primary?.trim() ? { primary: primary.trim() } : {}),\n ...(fallbacks.length > 0 ? { fallbacks } : {}),\n };\n}\n\nexport function buildToolModelConfigFromCandidates(params: {\n explicit: ToolModelConfig;\n candidates: Array<string | null | undefined>;\n}): ToolModelConfig | null {\n if (hasToolModelConfig(params.explicit)) {\n return params.explicit;\n }\n\n const deduped: string[] = [];\n for (const candidate of params.candidates) {\n const trimmed = candidate?.trim();\n if (!trimmed || !trimmed.includes('/')) {\n continue;\n }\n const provider = trimmed.slice(0, trimmed.indexOf('/')).trim();\n if (!provider || !isProviderConfiguredSync(provider)) {\n continue;\n }\n if (!deduped.includes(trimmed)) {\n deduped.push(trimmed);\n }\n }\n\n if (deduped.length === 0) {\n return null;\n }\n\n return {\n primary: deduped[0],\n ...(deduped.length > 1 ? { fallbacks: deduped.slice(1) } : {}),\n };\n}\n\nfunction firstVisionModelRef(provider: string): string | undefined {\n const m = getModelsByProvider(provider).find((x) => x.input?.includes('image'));\n return m ? `${provider}/${m.id}` : undefined;\n}\n\n/**\n * Effective image understanding model inferred from configured providers.\n */\nexport function resolveImageModelConfigForTool(params: { cfg?: Config }): ToolModelConfig | null {\n const explicit = coerceToolModelConfig(\n params.cfg ? getAgentDefaultImageModelConfig(params.cfg) : undefined,\n );\n if (hasToolModelConfig(explicit)) {\n return explicit;\n }\n\n const primary = resolveDefaultModelRef(params.cfg);\n const primaryCandidates: string[] = [];\n const vision = firstVisionModelRef(primary.provider);\n if (vision) {\n primaryCandidates.push(vision);\n }\n if (primary.provider === 'openai') {\n primaryCandidates.push('openai/gpt-4o-mini');\n }\n if (primary.provider === 'anthropic') {\n primaryCandidates.push('anthropic/claude-sonnet-4-5');\n }\n if (primary.provider === 'google') {\n primaryCandidates.push('google/gemini-2.0-flash');\n }\n\n return buildToolModelConfigFromCandidates({\n explicit,\n candidates: [\n ...primaryCandidates,\n firstVisionModelRef('openai') ?? 'openai/gpt-4o-mini',\n firstVisionModelRef('anthropic') ?? 'anthropic/claude-sonnet-4-5',\n ],\n });\n}\n"],"mappings":";;;;aAKgC;gBAK8E;AAS9G,SAAgB,mBAAmB,OAA6C;AAC9E,QAAO,QACL,OAAO,SAAS,MAAM,KAAK,OAAO,aAAa,EAAE,EAAE,MAAM,UAAU,MAAM,MAAM,CAAC,SAAS,EAAE,CAC5F;;AAGH,SAAgB,uBAAuB,KAAmD;CACxF,MAAM,MAAM,MAAM,wBAAwB,IAAI,GAAG,KAAA;AACjD,KAAI,KAAK;EACP,MAAM,IAAI,cAAc,IAAI;AAC5B,MAAI,EACF,QAAO;;CAIX,MAAM,KAAK,cADM,oBAAoB,IACJ,CAAC;AAClC,KAAI,GACF,QAAO;AAET,QAAO;EAAE,UAAU;EAAY,OAAO;EAAqB;;AAG7D,SAAgB,sBAAsB,OAA2C;CAC/E,MAAM,UAAU,8BAA8B,MAAM;CACpD,MAAM,YAAY,gCAAgC,MAAM;AACxD,QAAO;EACL,GAAI,SAAS,MAAM,GAAG,EAAE,SAAS,QAAQ,MAAM,EAAE,GAAG,EAAE;EACtD,GAAI,UAAU,SAAS,IAAI,EAAE,WAAW,GAAG,EAAE;EAC9C;;AAGH,SAAgB,mCAAmC,QAGxB;AACzB,KAAI,mBAAmB,OAAO,SAAS,CACrC,QAAO,OAAO;CAGhB,MAAM,UAAoB,EAAE;AAC5B,MAAK,MAAM,aAAa,OAAO,YAAY;EACzC,MAAM,UAAU,WAAW,MAAM;AACjC,MAAI,CAAC,WAAW,CAAC,QAAQ,SAAS,IAAI,CACpC;EAEF,MAAM,WAAW,QAAQ,MAAM,GAAG,QAAQ,QAAQ,IAAI,CAAC,CAAC,MAAM;AAC9D,MAAI,CAAC,YAAY,CAAC,yBAAyB,SAAS,CAClD;AAEF,MAAI,CAAC,QAAQ,SAAS,QAAQ,CAC5B,SAAQ,KAAK,QAAQ;;AAIzB,KAAI,QAAQ,WAAW,EACrB,QAAO;AAGT,QAAO;EACL,SAAS,QAAQ;EACjB,GAAI,QAAQ,SAAS,IAAI,EAAE,WAAW,QAAQ,MAAM,EAAE,EAAE,GAAG,EAAE;EAC9D;;AAGH,SAAS,oBAAoB,UAAsC;CACjE,MAAM,IAAI,oBAAoB,SAAS,CAAC,MAAM,MAAM,EAAE,OAAO,SAAS,QAAQ,CAAC;AAC/E,QAAO,IAAI,GAAG,SAAS,GAAG,EAAE,OAAO,KAAA;;;;;AAMrC,SAAgB,+BAA+B,QAAkD;CAC/F,MAAM,WAAW,sBACf,OAAO,MAAM,gCAAgC,OAAO,IAAI,GAAG,KAAA,EAC5D;AACD,KAAI,mBAAmB,SAAS,CAC9B,QAAO;CAGT,MAAM,UAAU,uBAAuB,OAAO,IAAI;CAClD,MAAM,oBAA8B,EAAE;CACtC,MAAM,SAAS,oBAAoB,QAAQ,SAAS;AACpD,KAAI,OACF,mBAAkB,KAAK,OAAO;AAEhC,KAAI,QAAQ,aAAa,SACvB,mBAAkB,KAAK,qBAAqB;AAE9C,KAAI,QAAQ,aAAa,YACvB,mBAAkB,KAAK,8BAA8B;AAEvD,KAAI,QAAQ,aAAa,SACvB,mBAAkB,KAAK,0BAA0B;AAGnD,QAAO,mCAAmC;EACxC;EACA,YAAY;GACV,GAAG;GACH,oBAAoB,SAAS,IAAI;GACjC,oBAAoB,YAAY,IAAI;GACrC;EACF,CAAC"}
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import { getApiKey, getModelsByProvider, init_providers, resolveModel } from "../../../providers/index.js";
|
|
2
2
|
import { registerImageUnderstandingProvider, registerImageUnderstandingProviderFactory } from "./provider-registry.js";
|
|
3
3
|
import { coerceImageAssistantText } from "../image-helpers.js";
|
|
4
|
-
import { complete } from "@earendil-works/pi-ai";
|
|
4
|
+
import { complete } from "@earendil-works/pi-ai/compat";
|
|
5
5
|
//#region src/agent/image/understanding/pi-ai-provider.ts
|
|
6
6
|
init_providers();
|
|
7
7
|
function resolveMaxTokens(modelMaxTokens, requestedMaxTokens = 4096) {
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"pi-ai-provider.js","names":[],"sources":["../../../../../src/agent/image/understanding/pi-ai-provider.ts"],"sourcesContent":["import { complete, type Api, type Context, type Model } from '@earendil-works/pi-ai';\nimport { resolveModel, getApiKey, getModelsByProvider } from '../../../providers/index.js';\nimport { coerceImageAssistantText } from '../image-helpers.js';\nimport {\n registerImageUnderstandingProvider,\n registerImageUnderstandingProviderFactory,\n} from './provider-registry.js';\nimport type { ImageUnderstandingProvider } from './types.js';\n\nfunction resolveMaxTokens(modelMaxTokens: number | undefined, requestedMaxTokens = 4096): number {\n if (\n typeof modelMaxTokens !== 'number' ||\n !Number.isFinite(modelMaxTokens) ||\n modelMaxTokens <= 0\n ) {\n return requestedMaxTokens;\n }\n return Math.min(requestedMaxTokens, modelMaxTokens);\n}\n\nexport function buildPiAiImageUnderstandingProvider(providerId: string): ImageUnderstandingProvider {\n return {\n id: providerId,\n label: `pi-ai (${providerId})`,\n async isConfigured() {\n const apiKey = await getApiKey(providerId);\n return Boolean(apiKey);\n },\n async describeImages(modelId, request) {\n const modelRef = `${providerId}/${modelId}`;\n const model = resolveModel(modelRef) as Model<Api>;\n if (!model.input?.includes('image')) {\n throw new Error(`Model does not support images: ${modelRef}`);\n }\n const apiKey = await getApiKey(providerId);\n if (!apiKey) {\n throw new Error(`No API key configured for provider: ${providerId}`);\n }\n\n const context: Context = {\n messages: [\n {\n role: 'user',\n content: [\n { type: 'text', text: request.prompt },\n ...request.images.map((img) => ({\n type: 'image' as const,\n data: img.buffer.toString('base64'),\n mimeType: img.mimeType || 'image/jpeg',\n })),\n ],\n timestamp: Date.now(),\n },\n ],\n };\n\n const maxTokens = resolveMaxTokens(model.maxTokens, request.maxTokens ?? 512);\n const timeoutMs = request.timeoutMs ?? 60_000;\n const timeoutSignal =\n typeof AbortSignal !== 'undefined' && typeof AbortSignal.timeout === 'function'\n ? AbortSignal.timeout(timeoutMs)\n : undefined;\n const signal = (() => {\n if (timeoutSignal && request.signal && typeof AbortSignal.any === 'function') {\n return AbortSignal.any([request.signal, timeoutSignal]);\n }\n if (request.signal) {\n return request.signal;\n }\n if (timeoutSignal) {\n return timeoutSignal;\n }\n const controller = new AbortController();\n setTimeout(() => controller.abort(), timeoutMs);\n return controller.signal;\n })();\n\n const message = await complete(model, context, {\n apiKey,\n maxTokens,\n signal,\n });\n\n const text = coerceImageAssistantText({\n message,\n provider: providerId,\n model: modelId,\n });\n return { text, provider: providerId, model: modelId };\n },\n };\n}\n\nfor (const providerId of ['openai', 'anthropic', 'google']) {\n registerImageUnderstandingProvider(buildPiAiImageUnderstandingProvider(providerId));\n}\n\nregisterImageUnderstandingProviderFactory((providerId) => {\n const hasVisionModel = getModelsByProvider(providerId).some((model) =>\n model.input?.includes('image'),\n );\n return hasVisionModel ? buildPiAiImageUnderstandingProvider(providerId) : undefined;\n});\n"],"mappings":";;;;;gBAC2F;AAQ3F,SAAS,iBAAiB,gBAAoC,qBAAqB,MAAc;AAC/F,KACE,OAAO,mBAAmB,YAC1B,CAAC,OAAO,SAAS,eAAe,IAChC,kBAAkB,EAElB,QAAO;AAET,QAAO,KAAK,IAAI,oBAAoB,eAAe;;AAGrD,SAAgB,oCAAoC,YAAgD;AAClG,QAAO;EACL,IAAI;EACJ,OAAO,UAAU,WAAW;EAC5B,MAAM,eAAe;GACnB,MAAM,SAAS,MAAM,UAAU,WAAW;AAC1C,UAAO,QAAQ,OAAO;;EAExB,MAAM,eAAe,SAAS,SAAS;GACrC,MAAM,WAAW,GAAG,WAAW,GAAG;GAClC,MAAM,QAAQ,aAAa,SAAS;AACpC,OAAI,CAAC,MAAM,OAAO,SAAS,QAAQ,CACjC,OAAM,IAAI,MAAM,kCAAkC,WAAW;GAE/D,MAAM,SAAS,MAAM,UAAU,WAAW;AAC1C,OAAI,CAAC,OACH,OAAM,IAAI,MAAM,uCAAuC,aAAa;GAGtE,MAAM,UAAmB,EACvB,UAAU,CACR;IACE,MAAM;IACN,SAAS,CACP;KAAE,MAAM;KAAQ,MAAM,QAAQ;KAAQ,EACtC,GAAG,QAAQ,OAAO,KAAK,SAAS;KAC9B,MAAM;KACN,MAAM,IAAI,OAAO,SAAS,SAAS;KACnC,UAAU,IAAI,YAAY;KAC3B,EAAE,CACJ;IACD,WAAW,KAAK,KAAK;IACtB,CACF,EACF;GAED,MAAM,YAAY,iBAAiB,MAAM,WAAW,QAAQ,aAAa,IAAI;GAC7E,MAAM,YAAY,QAAQ,aAAa;GACvC,MAAM,gBACJ,OAAO,gBAAgB,eAAe,OAAO,YAAY,YAAY,aACjE,YAAY,QAAQ,UAAU,GAC9B,KAAA;AA2BN,UAAO;IAAE,MALI,yBAAyB;KACpC,SAAA,MAPoB,SAAS,OAAO,SAAS;MAC7C;MACA;MACA,eAlBoB;AACpB,WAAI,iBAAiB,QAAQ,UAAU,OAAO,YAAY,QAAQ,WAChE,QAAO,YAAY,IAAI,CAAC,QAAQ,QAAQ,cAAc,CAAC;AAEzD,WAAI,QAAQ,OACV,QAAO,QAAQ;AAEjB,WAAI,cACF,QAAO;OAET,MAAM,aAAa,IAAI,iBAAiB;AACxC,wBAAiB,WAAW,OAAO,EAAE,UAAU;AAC/C,cAAO,WAAW;UAMZ;MACP,CAAC;KAIA,UAAU;KACV,OAAO;KACR,CACY;IAAE,UAAU;IAAY,OAAO;IAAS;;EAExD;;AAGH,KAAK,MAAM,cAAc;CAAC;CAAU;CAAa;CAAS,CACxD,oCAAmC,oCAAoC,WAAW,CAAC;AAGrF,2CAA2C,eAAe;AAIxD,QAHuB,oBAAoB,WAAW,CAAC,MAAM,UAC3D,MAAM,OAAO,SAAS,QAAQ,CAEX,GAAG,oCAAoC,WAAW,GAAG,KAAA;EAC1E"}
|
|
1
|
+
{"version":3,"file":"pi-ai-provider.js","names":[],"sources":["../../../../../src/agent/image/understanding/pi-ai-provider.ts"],"sourcesContent":["import { complete, type Api, type Context, type Model } from '@earendil-works/pi-ai/compat';\nimport { resolveModel, getApiKey, getModelsByProvider } from '../../../providers/index.js';\nimport { coerceImageAssistantText } from '../image-helpers.js';\nimport {\n registerImageUnderstandingProvider,\n registerImageUnderstandingProviderFactory,\n} from './provider-registry.js';\nimport type { ImageUnderstandingProvider } from './types.js';\n\nfunction resolveMaxTokens(modelMaxTokens: number | undefined, requestedMaxTokens = 4096): number {\n if (\n typeof modelMaxTokens !== 'number' ||\n !Number.isFinite(modelMaxTokens) ||\n modelMaxTokens <= 0\n ) {\n return requestedMaxTokens;\n }\n return Math.min(requestedMaxTokens, modelMaxTokens);\n}\n\nexport function buildPiAiImageUnderstandingProvider(providerId: string): ImageUnderstandingProvider {\n return {\n id: providerId,\n label: `pi-ai (${providerId})`,\n async isConfigured() {\n const apiKey = await getApiKey(providerId);\n return Boolean(apiKey);\n },\n async describeImages(modelId, request) {\n const modelRef = `${providerId}/${modelId}`;\n const model = resolveModel(modelRef) as Model<Api>;\n if (!model.input?.includes('image')) {\n throw new Error(`Model does not support images: ${modelRef}`);\n }\n const apiKey = await getApiKey(providerId);\n if (!apiKey) {\n throw new Error(`No API key configured for provider: ${providerId}`);\n }\n\n const context: Context = {\n messages: [\n {\n role: 'user',\n content: [\n { type: 'text', text: request.prompt },\n ...request.images.map((img) => ({\n type: 'image' as const,\n data: img.buffer.toString('base64'),\n mimeType: img.mimeType || 'image/jpeg',\n })),\n ],\n timestamp: Date.now(),\n },\n ],\n };\n\n const maxTokens = resolveMaxTokens(model.maxTokens, request.maxTokens ?? 512);\n const timeoutMs = request.timeoutMs ?? 60_000;\n const timeoutSignal =\n typeof AbortSignal !== 'undefined' && typeof AbortSignal.timeout === 'function'\n ? AbortSignal.timeout(timeoutMs)\n : undefined;\n const signal = (() => {\n if (timeoutSignal && request.signal && typeof AbortSignal.any === 'function') {\n return AbortSignal.any([request.signal, timeoutSignal]);\n }\n if (request.signal) {\n return request.signal;\n }\n if (timeoutSignal) {\n return timeoutSignal;\n }\n const controller = new AbortController();\n setTimeout(() => controller.abort(), timeoutMs);\n return controller.signal;\n })();\n\n const message = await complete(model, context, {\n apiKey,\n maxTokens,\n signal,\n });\n\n const text = coerceImageAssistantText({\n message,\n provider: providerId,\n model: modelId,\n });\n return { text, provider: providerId, model: modelId };\n },\n };\n}\n\nfor (const providerId of ['openai', 'anthropic', 'google']) {\n registerImageUnderstandingProvider(buildPiAiImageUnderstandingProvider(providerId));\n}\n\nregisterImageUnderstandingProviderFactory((providerId) => {\n const hasVisionModel = getModelsByProvider(providerId).some((model) =>\n model.input?.includes('image'),\n );\n return hasVisionModel ? buildPiAiImageUnderstandingProvider(providerId) : undefined;\n});\n"],"mappings":";;;;;gBAC2F;AAQ3F,SAAS,iBAAiB,gBAAoC,qBAAqB,MAAc;AAC/F,KACE,OAAO,mBAAmB,YAC1B,CAAC,OAAO,SAAS,eAAe,IAChC,kBAAkB,EAElB,QAAO;AAET,QAAO,KAAK,IAAI,oBAAoB,eAAe;;AAGrD,SAAgB,oCAAoC,YAAgD;AAClG,QAAO;EACL,IAAI;EACJ,OAAO,UAAU,WAAW;EAC5B,MAAM,eAAe;GACnB,MAAM,SAAS,MAAM,UAAU,WAAW;AAC1C,UAAO,QAAQ,OAAO;;EAExB,MAAM,eAAe,SAAS,SAAS;GACrC,MAAM,WAAW,GAAG,WAAW,GAAG;GAClC,MAAM,QAAQ,aAAa,SAAS;AACpC,OAAI,CAAC,MAAM,OAAO,SAAS,QAAQ,CACjC,OAAM,IAAI,MAAM,kCAAkC,WAAW;GAE/D,MAAM,SAAS,MAAM,UAAU,WAAW;AAC1C,OAAI,CAAC,OACH,OAAM,IAAI,MAAM,uCAAuC,aAAa;GAGtE,MAAM,UAAmB,EACvB,UAAU,CACR;IACE,MAAM;IACN,SAAS,CACP;KAAE,MAAM;KAAQ,MAAM,QAAQ;KAAQ,EACtC,GAAG,QAAQ,OAAO,KAAK,SAAS;KAC9B,MAAM;KACN,MAAM,IAAI,OAAO,SAAS,SAAS;KACnC,UAAU,IAAI,YAAY;KAC3B,EAAE,CACJ;IACD,WAAW,KAAK,KAAK;IACtB,CACF,EACF;GAED,MAAM,YAAY,iBAAiB,MAAM,WAAW,QAAQ,aAAa,IAAI;GAC7E,MAAM,YAAY,QAAQ,aAAa;GACvC,MAAM,gBACJ,OAAO,gBAAgB,eAAe,OAAO,YAAY,YAAY,aACjE,YAAY,QAAQ,UAAU,GAC9B,KAAA;AA2BN,UAAO;IAAE,MALI,yBAAyB;KACpC,SAAA,MAPoB,SAAS,OAAO,SAAS;MAC7C;MACA;MACA,eAlBoB;AACpB,WAAI,iBAAiB,QAAQ,UAAU,OAAO,YAAY,QAAQ,WAChE,QAAO,YAAY,IAAI,CAAC,QAAQ,QAAQ,cAAc,CAAC;AAEzD,WAAI,QAAQ,OACV,QAAO,QAAQ;AAEjB,WAAI,cACF,QAAO;OAET,MAAM,aAAa,IAAI,iBAAiB;AACxC,wBAAiB,WAAW,OAAO,EAAE,UAAU;AAC/C,cAAO,WAAW;UAMZ;MACP,CAAC;KAIA,UAAU;KACV,OAAO;KACR,CACY;IAAE,UAAU;IAAY,OAAO;IAAS;;EAExD;;AAGH,KAAK,MAAM,cAAc;CAAC;CAAU;CAAa;CAAS,CACxD,oCAAmC,oCAAoC,WAAW,CAAC;AAGrF,2CAA2C,eAAe;AAIxD,QAHuB,oBAAoB,WAAW,CAAC,MAAM,UAC3D,MAAM,OAAO,SAAS,QAAQ,CAEX,GAAG,oCAAoC,WAAW,GAAG,KAAA;EAC1E"}
|
|
@@ -16,7 +16,7 @@ export declare class BuiltinMemoryStore {
|
|
|
16
16
|
get memoriesDir(): string;
|
|
17
17
|
/** Global user memory file (`~/.xopc/user/MEMORY.md`). */
|
|
18
18
|
get userMemoryPath(): string;
|
|
19
|
-
/** Workspace whose
|
|
19
|
+
/** Workspace whose profile and curated files participate in local memory search. */
|
|
20
20
|
get workspaceDir(): string;
|
|
21
21
|
pathFor(target: 'memory' | 'user'): string;
|
|
22
22
|
/**
|
|
@@ -32,7 +32,7 @@ var BuiltinMemoryStore = class {
|
|
|
32
32
|
get userMemoryPath() {
|
|
33
33
|
return this.config.userMemoryPath;
|
|
34
34
|
}
|
|
35
|
-
/** Workspace whose
|
|
35
|
+
/** Workspace whose profile and curated files participate in local memory search. */
|
|
36
36
|
get workspaceDir() {
|
|
37
37
|
return this.config.workspaceDir;
|
|
38
38
|
}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"builtin-memory-store.js","names":[],"sources":["../../../../src/agent/memory/builtin-memory-store.ts"],"sourcesContent":["/**\n * Bounded curated memory: agent home `memories/MEMORY.md` + global `user/MEMORY.md`, §-delimited entries.\n * Snapshot for system prompt is captured at load time and not mutated until next load.\n */\n\nimport { existsSync, mkdirSync, readFileSync } from 'node:fs';\nimport { mkdir, readFile, rename, writeFile } from 'node:fs/promises';\nimport { dirname, join } from 'node:path';\nimport lockfile from 'proper-lockfile';\n\nimport type { MemorySnapshot, MemoryStoreConfig } from './types.js';\n\nexport const MEMORY_ENTRY_DELIMITER = '\\n§\\n';\n\nconst DEFAULT_MEMORY_LIMIT = 2200;\nconst DEFAULT_USER_LIMIT = 1375;\n\nexport class BuiltinMemoryStore {\n private memoryEntries: string[] = [];\n private userEntries: string[] = [];\n private snapshot: MemorySnapshot = { memory: '', user: '' };\n\n constructor(private readonly config: MemoryStoreConfig) {}\n\n /** When false, global user memory is omitted from the snapshot and should not be edited via `curated_memory`. */\n isUserProfileEnabled(): boolean {\n return this.config.userProfileEnabled !== false;\n }\n\n /** Agent-scoped curated memory directory (`~/.xopc/agents/<id>/memories/`). */\n get memoriesDir(): string {\n return this.config.memoriesDir;\n }\n\n /** Global user memory file (`~/.xopc/user/MEMORY.md`). */\n get userMemoryPath(): string {\n return this.config.userMemoryPath;\n }\n\n /** Workspace whose MEMORY.md and daily notes participate in local memory search. */\n get workspaceDir(): string {\n return this.config.workspaceDir;\n }\n\n pathFor(target: 'memory' | 'user'): string {\n return target === 'memory' ? join(this.memoriesDir, 'MEMORY.md') : this.userMemoryPath;\n }\n\n /**\n * Load from disk and freeze {@link getSnapshot}. Sync for agent creation (prefix cache stability).\n */\n loadFromDiskSync(): void {\n mkdirSync(this.memoriesDir, { recursive: true });\n this.memoryEntries = this.parseFileContent(\n this.readPathSync(join(this.memoriesDir, 'MEMORY.md')),\n );\n this.userEntries =\n this.config.userProfileEnabled === false\n ? []\n : this.parseFileContent(this.readPathSync(this.userMemoryPath));\n this.memoryEntries = dedupePreserveOrder(this.memoryEntries);\n this.userEntries = dedupePreserveOrder(this.userEntries);\n this.snapshot = {\n memory: this.renderBlock('memory', this.memoryEntries),\n user: this.renderBlock('user', this.userEntries),\n };\n }\n\n private readPathSync(path: string): string {\n try {\n if (!existsSync(path)) return '';\n return readFileSync(path, { encoding: 'utf-8' });\n } catch {\n return '';\n }\n }\n\n private parseFileContent(raw: string): string[] {\n if (!raw.trim()) return [];\n return raw\n .split(MEMORY_ENTRY_DELIMITER)\n .map((e) => e.trim())\n .filter(Boolean);\n }\n\n getSnapshot(): MemorySnapshot {\n return this.snapshot;\n }\n\n /**\n * Live entries for read/debug (may differ from snapshot after mutations).\n */\n getLiveEntries(target: 'memory' | 'user'): string[] {\n return target === 'memory' ? [...this.memoryEntries] : [...this.userEntries];\n }\n\n async add(\n target: 'memory' | 'user',\n content: string,\n ): Promise<{ success: boolean; error?: string; message?: string }> {\n const trimmed = content.trim();\n if (!trimmed) {\n return { success: false, error: 'Content cannot be empty.' };\n }\n const scanError = scanForThreats(trimmed);\n if (scanError) {\n return { success: false, error: scanError };\n }\n\n return this.withFileLock(target, async () => {\n await this.reloadTargetFromDisk(target);\n const entries = target === 'memory' ? this.memoryEntries : this.userEntries;\n const limit = this.charLimit(target);\n\n if (entries.includes(trimmed)) {\n return { success: false, error: 'Entry already exists' };\n }\n\n const newTotal = calculateTotal([...entries, trimmed]);\n if (newTotal > limit) {\n return {\n success: false,\n error: `Memory at ${calculateTotal(entries)}/${limit} chars. Adding would exceed limit.`,\n };\n }\n\n entries.push(trimmed);\n await this.flushTargetToDisk(target);\n return { success: true, message: 'Entry added.' };\n });\n }\n\n async replace(\n target: 'memory' | 'user',\n oldText: string,\n newContent: string,\n ): Promise<{ success: boolean; error?: string; message?: string }> {\n const ot = oldText.trim();\n const nc = newContent.trim();\n if (!ot) {\n return { success: false, error: 'old_text cannot be empty.' };\n }\n if (!nc) {\n return { success: false, error: 'new_content cannot be empty. Use remove to delete entries.' };\n }\n const scanError = scanForThreats(nc);\n if (scanError) {\n return { success: false, error: scanError };\n }\n\n return this.withFileLock(target, async () => {\n await this.reloadTargetFromDisk(target);\n const entries = target === 'memory' ? this.memoryEntries : this.userEntries;\n const matches: Array<{ index: number; entry: string }> = [];\n for (let i = 0; i < entries.length; i++) {\n if (entries[i].includes(ot)) {\n matches.push({ index: i, entry: entries[i] });\n }\n }\n\n if (matches.length === 0) {\n return { success: false, error: `No entry matched '${ot}'.` };\n }\n\n if (matches.length > 1) {\n const uniqueTexts = new Set(matches.map((m) => m.entry));\n if (uniqueTexts.size > 1) {\n const previews = matches.map((m) =>\n m.entry.length > 80 ? `${m.entry.slice(0, 80)}...` : m.entry,\n );\n return {\n success: false,\n error: `Multiple entries matched '${ot}'. Be more specific. Previews: ${previews.join(' | ')}`,\n };\n }\n }\n\n const idx = matches[0].index;\n const limit = this.charLimit(target);\n const testEntries = [...entries];\n testEntries[idx] = nc;\n\n if (calculateTotal(testEntries) > limit) {\n return {\n success: false,\n error: `Replacement would put memory at ${calculateTotal(testEntries)}/${limit} chars.`,\n };\n }\n\n entries[idx] = nc;\n await this.flushTargetToDisk(target);\n return { success: true, message: 'Entry replaced.' };\n });\n }\n\n async remove(\n target: 'memory' | 'user',\n oldText: string,\n ): Promise<{ success: boolean; error?: string; message?: string }> {\n const ot = oldText.trim();\n if (!ot) {\n return { success: false, error: 'old_text cannot be empty.' };\n }\n\n return this.withFileLock(target, async () => {\n await this.reloadTargetFromDisk(target);\n const entries = target === 'memory' ? this.memoryEntries : this.userEntries;\n const matches: number[] = [];\n for (let i = 0; i < entries.length; i++) {\n if (entries[i].includes(ot)) {\n matches.push(i);\n }\n }\n\n if (matches.length === 0) {\n return { success: false, error: `No entry matched '${ot}'.` };\n }\n\n if (matches.length > 1) {\n const uniqueTexts = new Set(matches.map((i) => entries[i]));\n if (uniqueTexts.size > 1) {\n const previews = matches.map((i) => {\n const e = entries[i];\n return e.length > 80 ? `${e.slice(0, 80)}...` : e;\n });\n return {\n success: false,\n error: `Multiple entries matched '${ot}'. Be more specific. Previews: ${previews.join(' | ')}`,\n };\n }\n }\n\n entries.splice(matches[0], 1);\n await this.flushTargetToDisk(target);\n return { success: true, message: 'Entry removed.' };\n });\n }\n\n private async withFileLock<T>(\n target: 'memory' | 'user',\n fn: () => Promise<T>,\n ): Promise<T> {\n const filePath = this.pathFor(target);\n await mkdir(dirname(filePath), { recursive: true });\n if (!existsSync(filePath)) {\n await writeFile(filePath, '', 'utf-8');\n }\n await lockfile.lock(filePath, { retries: 3, stale: 10_000 });\n try {\n return await fn();\n } finally {\n await lockfile.unlock(filePath);\n }\n }\n\n private async reloadTargetFromDisk(target: 'memory' | 'user'): Promise<void> {\n const path = this.pathFor(target);\n let raw = '';\n try {\n raw = await readFile(path, 'utf-8');\n } catch {\n raw = '';\n }\n const parsed = dedupePreserveOrder(this.parseFileContent(raw));\n if (target === 'memory') {\n this.memoryEntries = parsed;\n } else {\n this.userEntries = parsed;\n }\n }\n\n private async flushTargetToDisk(target: 'memory' | 'user'): Promise<void> {\n const entries = target === 'memory' ? this.memoryEntries : this.userEntries;\n const filePath = this.pathFor(target);\n const content = entries.join(MEMORY_ENTRY_DELIMITER);\n const tmpPath = `${filePath}.tmp.${Date.now()}`;\n await mkdir(dirname(filePath), { recursive: true });\n await writeFile(tmpPath, content, 'utf-8');\n await rename(tmpPath, filePath);\n }\n\n private charLimit(target: 'memory' | 'user'): number {\n return target === 'memory'\n ? (this.config.memoryCharLimit ?? DEFAULT_MEMORY_LIMIT)\n : (this.config.userCharLimit ?? DEFAULT_USER_LIMIT);\n }\n\n private renderBlock(target: 'memory' | 'user', entries: string[]): string {\n if (entries.length === 0) {\n return '';\n }\n\n const limit = this.charLimit(target);\n const content = entries.join(MEMORY_ENTRY_DELIMITER);\n const current = content.length;\n const pct = Math.min(100, Math.round((current / limit) * 100));\n\n const header =\n target === 'user'\n ? `USER PROFILE (who the user is) [${pct}% — ${current.toLocaleString()}/${limit.toLocaleString()} chars]`\n : `MEMORY (your personal notes) [${pct}% — ${current.toLocaleString()}/${limit.toLocaleString()} chars]`;\n\n const separator = '═'.repeat(46);\n return `${separator}\\n${header}\\n${separator}\\n${content}`;\n }\n}\n\nfunction dedupePreserveOrder(entries: string[]): string[] {\n const seen = new Set<string>();\n const out: string[] = [];\n for (const e of entries) {\n if (seen.has(e)) {\n continue;\n }\n seen.add(e);\n out.push(e);\n }\n return out;\n}\n\nfunction calculateTotal(entries: string[]): number {\n if (entries.length === 0) {\n return 0;\n }\n return entries.join(MEMORY_ENTRY_DELIMITER).length;\n}\n\nexport function scanForThreats(content: string): string | null {\n const patterns: Array<{ regex: RegExp; id: string }> = [\n { regex: /ignore\\s+(previous|all|above|prior)\\s+instructions/i, id: 'prompt_injection' },\n { regex: /you\\s+are\\s+now\\s+/i, id: 'role_hijack' },\n { regex: /do\\s+not\\s+tell\\s+the\\s+user/i, id: 'deception_hide' },\n { regex: /system\\s+prompt\\s+override/i, id: 'sys_prompt_override' },\n {\n regex: /disregard\\s+(your|all|any)\\s+(instructions|rules|guidelines)/i,\n id: 'disregard_rules',\n },\n {\n regex: /act\\s+as\\s+(if|though)\\s+you\\s+(have\\s+no|don't\\s+have)\\s+(restrictions|limits|rules)/i,\n id: 'bypass_restrictions',\n },\n { regex: /curl\\s+[^\\n]*\\$\\{?\\w*(KEY|TOKEN|SECRET|PASSWORD|CREDENTIAL|API)/i, id: 'exfil_curl' },\n { regex: /wget\\s+[^\\n]*\\$\\{?\\w*(KEY|TOKEN|SECRET|PASSWORD|CREDENTIAL|API)/i, id: 'exfil_wget' },\n {\n regex: /cat\\s+[^\\n]*(\\.env|credentials|\\.netrc|\\.pgpass|\\.npmrc|\\.pypirc)/i,\n id: 'read_secrets',\n },\n { regex: /authorized_keys/i, id: 'ssh_backdoor' },\n { regex: /\\$HOME\\/\\.ssh|\\~\\/\\.ssh/i, id: 'ssh_access' },\n ];\n\n for (const { regex, id } of patterns) {\n if (regex.test(content)) {\n return `Blocked: content matches threat pattern '${id}'. Memory entries are injected into the system prompt and must not contain injection payloads.`;\n }\n }\n\n const invisibleChars = [\n '\\u200b',\n '\\u200c',\n '\\u200d',\n '\\u2060',\n '\\ufeff',\n '\\u202a',\n '\\u202b',\n '\\u202c',\n '\\u202d',\n '\\u202e',\n ];\n for (const char of invisibleChars) {\n if (content.includes(char)) {\n const code = char.charCodeAt(0).toString(16).toUpperCase().padStart(4, '0');\n return `Blocked: content contains invisible unicode character U+${code} (possible injection).`;\n }\n }\n\n return null;\n}\n"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"builtin-memory-store.js","names":[],"sources":["../../../../src/agent/memory/builtin-memory-store.ts"],"sourcesContent":["/**\n * Bounded curated memory: agent home `memories/MEMORY.md` + global `user/MEMORY.md`, §-delimited entries.\n * Snapshot for system prompt is captured at load time and not mutated until next load.\n */\n\nimport { existsSync, mkdirSync, readFileSync } from 'node:fs';\nimport { mkdir, readFile, rename, writeFile } from 'node:fs/promises';\nimport { dirname, join } from 'node:path';\nimport lockfile from 'proper-lockfile';\n\nimport type { MemorySnapshot, MemoryStoreConfig } from './types.js';\n\nexport const MEMORY_ENTRY_DELIMITER = '\\n§\\n';\n\nconst DEFAULT_MEMORY_LIMIT = 2200;\nconst DEFAULT_USER_LIMIT = 1375;\n\nexport class BuiltinMemoryStore {\n private memoryEntries: string[] = [];\n private userEntries: string[] = [];\n private snapshot: MemorySnapshot = { memory: '', user: '' };\n\n constructor(private readonly config: MemoryStoreConfig) {}\n\n /** When false, global user memory is omitted from the snapshot and should not be edited via `curated_memory`. */\n isUserProfileEnabled(): boolean {\n return this.config.userProfileEnabled !== false;\n }\n\n /** Agent-scoped curated memory directory (`~/.xopc/agents/<id>/memories/`). */\n get memoriesDir(): string {\n return this.config.memoriesDir;\n }\n\n /** Global user memory file (`~/.xopc/user/MEMORY.md`). */\n get userMemoryPath(): string {\n return this.config.userMemoryPath;\n }\n\n /** Workspace whose profile and curated files participate in local memory search. */\n get workspaceDir(): string {\n return this.config.workspaceDir;\n }\n\n pathFor(target: 'memory' | 'user'): string {\n return target === 'memory' ? join(this.memoriesDir, 'MEMORY.md') : this.userMemoryPath;\n }\n\n /**\n * Load from disk and freeze {@link getSnapshot}. Sync for agent creation (prefix cache stability).\n */\n loadFromDiskSync(): void {\n mkdirSync(this.memoriesDir, { recursive: true });\n this.memoryEntries = this.parseFileContent(\n this.readPathSync(join(this.memoriesDir, 'MEMORY.md')),\n );\n this.userEntries =\n this.config.userProfileEnabled === false\n ? []\n : this.parseFileContent(this.readPathSync(this.userMemoryPath));\n this.memoryEntries = dedupePreserveOrder(this.memoryEntries);\n this.userEntries = dedupePreserveOrder(this.userEntries);\n this.snapshot = {\n memory: this.renderBlock('memory', this.memoryEntries),\n user: this.renderBlock('user', this.userEntries),\n };\n }\n\n private readPathSync(path: string): string {\n try {\n if (!existsSync(path)) return '';\n return readFileSync(path, { encoding: 'utf-8' });\n } catch {\n return '';\n }\n }\n\n private parseFileContent(raw: string): string[] {\n if (!raw.trim()) return [];\n return raw\n .split(MEMORY_ENTRY_DELIMITER)\n .map((e) => e.trim())\n .filter(Boolean);\n }\n\n getSnapshot(): MemorySnapshot {\n return this.snapshot;\n }\n\n /**\n * Live entries for read/debug (may differ from snapshot after mutations).\n */\n getLiveEntries(target: 'memory' | 'user'): string[] {\n return target === 'memory' ? [...this.memoryEntries] : [...this.userEntries];\n }\n\n async add(\n target: 'memory' | 'user',\n content: string,\n ): Promise<{ success: boolean; error?: string; message?: string }> {\n const trimmed = content.trim();\n if (!trimmed) {\n return { success: false, error: 'Content cannot be empty.' };\n }\n const scanError = scanForThreats(trimmed);\n if (scanError) {\n return { success: false, error: scanError };\n }\n\n return this.withFileLock(target, async () => {\n await this.reloadTargetFromDisk(target);\n const entries = target === 'memory' ? this.memoryEntries : this.userEntries;\n const limit = this.charLimit(target);\n\n if (entries.includes(trimmed)) {\n return { success: false, error: 'Entry already exists' };\n }\n\n const newTotal = calculateTotal([...entries, trimmed]);\n if (newTotal > limit) {\n return {\n success: false,\n error: `Memory at ${calculateTotal(entries)}/${limit} chars. Adding would exceed limit.`,\n };\n }\n\n entries.push(trimmed);\n await this.flushTargetToDisk(target);\n return { success: true, message: 'Entry added.' };\n });\n }\n\n async replace(\n target: 'memory' | 'user',\n oldText: string,\n newContent: string,\n ): Promise<{ success: boolean; error?: string; message?: string }> {\n const ot = oldText.trim();\n const nc = newContent.trim();\n if (!ot) {\n return { success: false, error: 'old_text cannot be empty.' };\n }\n if (!nc) {\n return { success: false, error: 'new_content cannot be empty. Use remove to delete entries.' };\n }\n const scanError = scanForThreats(nc);\n if (scanError) {\n return { success: false, error: scanError };\n }\n\n return this.withFileLock(target, async () => {\n await this.reloadTargetFromDisk(target);\n const entries = target === 'memory' ? this.memoryEntries : this.userEntries;\n const matches: Array<{ index: number; entry: string }> = [];\n for (let i = 0; i < entries.length; i++) {\n if (entries[i].includes(ot)) {\n matches.push({ index: i, entry: entries[i] });\n }\n }\n\n if (matches.length === 0) {\n return { success: false, error: `No entry matched '${ot}'.` };\n }\n\n if (matches.length > 1) {\n const uniqueTexts = new Set(matches.map((m) => m.entry));\n if (uniqueTexts.size > 1) {\n const previews = matches.map((m) =>\n m.entry.length > 80 ? `${m.entry.slice(0, 80)}...` : m.entry,\n );\n return {\n success: false,\n error: `Multiple entries matched '${ot}'. Be more specific. Previews: ${previews.join(' | ')}`,\n };\n }\n }\n\n const idx = matches[0].index;\n const limit = this.charLimit(target);\n const testEntries = [...entries];\n testEntries[idx] = nc;\n\n if (calculateTotal(testEntries) > limit) {\n return {\n success: false,\n error: `Replacement would put memory at ${calculateTotal(testEntries)}/${limit} chars.`,\n };\n }\n\n entries[idx] = nc;\n await this.flushTargetToDisk(target);\n return { success: true, message: 'Entry replaced.' };\n });\n }\n\n async remove(\n target: 'memory' | 'user',\n oldText: string,\n ): Promise<{ success: boolean; error?: string; message?: string }> {\n const ot = oldText.trim();\n if (!ot) {\n return { success: false, error: 'old_text cannot be empty.' };\n }\n\n return this.withFileLock(target, async () => {\n await this.reloadTargetFromDisk(target);\n const entries = target === 'memory' ? this.memoryEntries : this.userEntries;\n const matches: number[] = [];\n for (let i = 0; i < entries.length; i++) {\n if (entries[i].includes(ot)) {\n matches.push(i);\n }\n }\n\n if (matches.length === 0) {\n return { success: false, error: `No entry matched '${ot}'.` };\n }\n\n if (matches.length > 1) {\n const uniqueTexts = new Set(matches.map((i) => entries[i]));\n if (uniqueTexts.size > 1) {\n const previews = matches.map((i) => {\n const e = entries[i];\n return e.length > 80 ? `${e.slice(0, 80)}...` : e;\n });\n return {\n success: false,\n error: `Multiple entries matched '${ot}'. Be more specific. Previews: ${previews.join(' | ')}`,\n };\n }\n }\n\n entries.splice(matches[0], 1);\n await this.flushTargetToDisk(target);\n return { success: true, message: 'Entry removed.' };\n });\n }\n\n private async withFileLock<T>(\n target: 'memory' | 'user',\n fn: () => Promise<T>,\n ): Promise<T> {\n const filePath = this.pathFor(target);\n await mkdir(dirname(filePath), { recursive: true });\n if (!existsSync(filePath)) {\n await writeFile(filePath, '', 'utf-8');\n }\n await lockfile.lock(filePath, { retries: 3, stale: 10_000 });\n try {\n return await fn();\n } finally {\n await lockfile.unlock(filePath);\n }\n }\n\n private async reloadTargetFromDisk(target: 'memory' | 'user'): Promise<void> {\n const path = this.pathFor(target);\n let raw = '';\n try {\n raw = await readFile(path, 'utf-8');\n } catch {\n raw = '';\n }\n const parsed = dedupePreserveOrder(this.parseFileContent(raw));\n if (target === 'memory') {\n this.memoryEntries = parsed;\n } else {\n this.userEntries = parsed;\n }\n }\n\n private async flushTargetToDisk(target: 'memory' | 'user'): Promise<void> {\n const entries = target === 'memory' ? this.memoryEntries : this.userEntries;\n const filePath = this.pathFor(target);\n const content = entries.join(MEMORY_ENTRY_DELIMITER);\n const tmpPath = `${filePath}.tmp.${Date.now()}`;\n await mkdir(dirname(filePath), { recursive: true });\n await writeFile(tmpPath, content, 'utf-8');\n await rename(tmpPath, filePath);\n }\n\n private charLimit(target: 'memory' | 'user'): number {\n return target === 'memory'\n ? (this.config.memoryCharLimit ?? DEFAULT_MEMORY_LIMIT)\n : (this.config.userCharLimit ?? DEFAULT_USER_LIMIT);\n }\n\n private renderBlock(target: 'memory' | 'user', entries: string[]): string {\n if (entries.length === 0) {\n return '';\n }\n\n const limit = this.charLimit(target);\n const content = entries.join(MEMORY_ENTRY_DELIMITER);\n const current = content.length;\n const pct = Math.min(100, Math.round((current / limit) * 100));\n\n const header =\n target === 'user'\n ? `USER PROFILE (who the user is) [${pct}% — ${current.toLocaleString()}/${limit.toLocaleString()} chars]`\n : `MEMORY (your personal notes) [${pct}% — ${current.toLocaleString()}/${limit.toLocaleString()} chars]`;\n\n const separator = '═'.repeat(46);\n return `${separator}\\n${header}\\n${separator}\\n${content}`;\n }\n}\n\nfunction dedupePreserveOrder(entries: string[]): string[] {\n const seen = new Set<string>();\n const out: string[] = [];\n for (const e of entries) {\n if (seen.has(e)) {\n continue;\n }\n seen.add(e);\n out.push(e);\n }\n return out;\n}\n\nfunction calculateTotal(entries: string[]): number {\n if (entries.length === 0) {\n return 0;\n }\n return entries.join(MEMORY_ENTRY_DELIMITER).length;\n}\n\nexport function scanForThreats(content: string): string | null {\n const patterns: Array<{ regex: RegExp; id: string }> = [\n { regex: /ignore\\s+(previous|all|above|prior)\\s+instructions/i, id: 'prompt_injection' },\n { regex: /you\\s+are\\s+now\\s+/i, id: 'role_hijack' },\n { regex: /do\\s+not\\s+tell\\s+the\\s+user/i, id: 'deception_hide' },\n { regex: /system\\s+prompt\\s+override/i, id: 'sys_prompt_override' },\n {\n regex: /disregard\\s+(your|all|any)\\s+(instructions|rules|guidelines)/i,\n id: 'disregard_rules',\n },\n {\n regex: /act\\s+as\\s+(if|though)\\s+you\\s+(have\\s+no|don't\\s+have)\\s+(restrictions|limits|rules)/i,\n id: 'bypass_restrictions',\n },\n { regex: /curl\\s+[^\\n]*\\$\\{?\\w*(KEY|TOKEN|SECRET|PASSWORD|CREDENTIAL|API)/i, id: 'exfil_curl' },\n { regex: /wget\\s+[^\\n]*\\$\\{?\\w*(KEY|TOKEN|SECRET|PASSWORD|CREDENTIAL|API)/i, id: 'exfil_wget' },\n {\n regex: /cat\\s+[^\\n]*(\\.env|credentials|\\.netrc|\\.pgpass|\\.npmrc|\\.pypirc)/i,\n id: 'read_secrets',\n },\n { regex: /authorized_keys/i, id: 'ssh_backdoor' },\n { regex: /\\$HOME\\/\\.ssh|\\~\\/\\.ssh/i, id: 'ssh_access' },\n ];\n\n for (const { regex, id } of patterns) {\n if (regex.test(content)) {\n return `Blocked: content matches threat pattern '${id}'. Memory entries are injected into the system prompt and must not contain injection payloads.`;\n }\n }\n\n const invisibleChars = [\n '\\u200b',\n '\\u200c',\n '\\u200d',\n '\\u2060',\n '\\ufeff',\n '\\u202a',\n '\\u202b',\n '\\u202c',\n '\\u202d',\n '\\u202e',\n ];\n for (const char of invisibleChars) {\n if (content.includes(char)) {\n const code = char.charCodeAt(0).toString(16).toUpperCase().padStart(4, '0');\n return `Blocked: content contains invisible unicode character U+${code} (possible injection).`;\n }\n }\n\n return null;\n}\n"],"mappings":"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"}
|
|
@@ -2,8 +2,8 @@ import type { BuiltinMemoryStore } from './builtin-memory-store.js';
|
|
|
2
2
|
import type { MemoryProvider, MemoryProviderInitOptions } from './provider.js';
|
|
3
3
|
import type { MemoryDeleteRequest, MemoryListRequest, MemoryReadRequest, MemoryReadResult, MemoryRecord, MemorySearchRequest, MemorySearchResult, MemoryUpdateRequest, MemoryWriteRequest, MemoryWriteResult, MemorySyncEvent } from './types.js';
|
|
4
4
|
/**
|
|
5
|
-
* Builtin local memory provider backed by agent-home curated files,
|
|
6
|
-
*
|
|
5
|
+
* Builtin local memory provider backed by profile memory, agent-home curated files,
|
|
6
|
+
* and the local SQLite FTS index.
|
|
7
7
|
*/
|
|
8
8
|
export declare class BuiltinMemoryProvider implements MemoryProvider {
|
|
9
9
|
private readonly store;
|
|
@@ -3,8 +3,8 @@ import "../../storage/sqlite/index.js";
|
|
|
3
3
|
import { memoryGet, memorySearch } from "../prompt/memory/index.js";
|
|
4
4
|
//#region src/agent/memory/builtin-provider.ts
|
|
5
5
|
/**
|
|
6
|
-
* Builtin local memory provider backed by agent-home curated files,
|
|
7
|
-
*
|
|
6
|
+
* Builtin local memory provider backed by profile memory, agent-home curated files,
|
|
7
|
+
* and the local SQLite FTS index.
|
|
8
8
|
*/
|
|
9
9
|
var BuiltinMemoryProvider = class {
|
|
10
10
|
id = "local";
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"builtin-provider.js","names":[],"sources":["../../../../src/agent/memory/builtin-provider.ts"],"sourcesContent":["import type { BuiltinMemoryStore } from './builtin-memory-store.js';\nimport type { MemoryProvider, MemoryProviderInitOptions } from './provider.js';\nimport { memoryGet, memorySearch } from '../prompt/memory/index.js';\nimport {\n appendMemorySignal,\n deleteMemoryRecord,\n getMemoryRecord,\n listMemoryRecords,\n searchMemoryRecords,\n upsertMemoryRecord,\n} from '../../storage/sqlite/index.js';\nimport type {\n MemoryDeleteRequest,\n MemoryListRequest,\n MemoryReadRequest,\n MemoryReadResult,\n MemoryRecord,\n MemorySearchRequest,\n MemorySearchResult,\n MemoryUpdateRequest,\n MemoryWriteRequest,\n MemoryWriteResult,\n MemorySyncEvent,\n} from './types.js';\n\n/**\n * Builtin local memory provider backed by agent-home curated files, workspace MEMORY.md,\n * daily notes, and the local SQLite FTS index.\n */\nexport class BuiltinMemoryProvider implements MemoryProvider {\n readonly id = 'local';\n readonly displayName = 'Local Markdown Memory';\n readonly capabilities = {\n search: true,\n read: true,\n write: true,\n update: true,\n delete: true,\n keywordSearch: true,\n semanticSearch: false,\n hybridSearch: false,\n citations: true,\n sync: false,\n local: true,\n };\n\n private workspaceDir = '';\n private agentId = 'main';\n\n constructor(private readonly store: BuiltinMemoryStore) {}\n\n isAvailable(): boolean {\n return true;\n }\n\n async initialize(_sessionId: string, options?: MemoryProviderInitOptions): Promise<void> {\n this.workspaceDir = options?.workspace ?? options?.agentWorkspace ?? this.store.workspaceDir;\n this.agentId = String(options?.agentId ?? this.agentId);\n }\n\n systemPromptBlock(): string {\n return '';\n }\n\n async prefetch(): Promise<string> {\n return '';\n }\n\n queuePrefetch(): void {}\n\n getToolSchemas() {\n return [];\n }\n\n async search(request: MemorySearchRequest): Promise<MemorySearchResult[]> {\n const agentId = request.scope?.agentId ?? this.agentId;\n const workspaceId = request.scope?.workspaceId ?? this.workspaceDir;\n const recordHits = searchMemoryRecords({\n query: request.query,\n agentId,\n workspaceId,\n providerId: this.id,\n kinds: request.kinds,\n maxResults: request.maxResults,\n minScore: request.minScore,\n });\n if (recordHits.length > 0) {\n return recordHits;\n }\n\n const results = await memorySearch(this.workspaceDir, request.query, {\n maxResults: request.maxResults,\n minScore: request.minScore,\n memoriesDir: this.store.memoriesDir,\n userMemoryPath: this.store.userMemoryPath,\n agentId,\n });\n return results.map((entry) => {\n const start = entry.lineNumbers[0] ?? 1;\n const end = entry.lineNumbers[entry.lineNumbers.length - 1] ?? start;\n const id = `${entry.file}#L${start}-L${end}`;\n const kind = inferKindFromPath(entry.file);\n const record = upsertMemoryRecord({\n id,\n providerId: this.id,\n kind,\n agentId,\n workspaceId,\n content: entry.lines,\n source: {\n provider: this.id,\n path: entry.file,\n lineStart: start,\n lineEnd: end,\n },\n confidence: entry.score,\n });\n return {\n record,\n score: entry.score,\n snippet: entry.lines,\n citation: {\n providerId: this.id,\n recordId: id,\n path: entry.file,\n lineStart: start,\n lineEnd: end,\n },\n };\n });\n }\n\n async read(request: MemoryReadRequest): Promise<MemoryReadResult | null> {\n if (request.id && !request.path) {\n const existing = getMemoryRecord(request.id);\n if (existing) {\n return { record: existing };\n }\n }\n const result = memoryGet(\n this.workspaceDir,\n request.path ?? request.id ?? '',\n request.from,\n request.lines,\n this.store.memoriesDir,\n this.store.userMemoryPath,\n );\n if (!result) return null;\n const path = request.path ?? request.id ?? '';\n const record = upsertMemoryRecord({\n id: `${path}#L${result.lineNumbers.start}-L${result.lineNumbers.end}`,\n providerId: this.id,\n kind: inferKindFromPath(path),\n agentId: request.scope?.agentId ?? this.agentId,\n workspaceId: request.scope?.workspaceId ?? this.workspaceDir,\n content: result.content,\n source: {\n provider: this.id,\n path,\n lineStart: result.lineNumbers.start,\n lineEnd: result.lineNumbers.end,\n },\n });\n return {\n record,\n lineNumbers: result.lineNumbers,\n };\n }\n\n async get(id: string): Promise<MemoryRecord | null> {\n const parsed = parseLineAddress(id);\n const result = await this.read({\n path: parsed.path,\n from: parsed.lineStart,\n lines: parsed.lineEnd != null ? parsed.lineEnd - parsed.lineStart + 1 : undefined,\n });\n return result?.record ?? null;\n }\n\n async list(request: MemoryListRequest): Promise<MemoryRecord[]> {\n const records = listMemoryRecords({\n providerId: this.id,\n agentId: request.scope?.agentId ?? this.agentId,\n workspaceId: request.scope?.workspaceId ?? this.workspaceDir,\n kind: request.kind,\n limit: 200,\n });\n if (records.length > 0 || request.kind) {\n return request.target\n ? records.filter((record) => targetForKind(record.kind) === request.target)\n : records;\n }\n\n const target = request.target ?? (request.kind === 'user_profile' ? 'user' : 'memory');\n return this.store.getLiveEntries(target).map((content, index) =>\n upsertMemoryRecord({\n id: `curated:${target}:${index + 1}`,\n providerId: this.id,\n kind: target === 'user' ? 'user_profile' : 'agent_note',\n agentId: request.scope?.agentId ?? this.agentId,\n workspaceId: request.scope?.workspaceId ?? this.workspaceDir,\n content,\n source: {\n provider: this.id,\n path: sourcePathForTarget(target),\n },\n }),\n );\n }\n\n async write(request: MemoryWriteRequest): Promise<MemoryWriteResult> {\n const target = request.target ?? (request.kind === 'user_profile' ? 'user' : 'memory');\n if (request.status === 'candidate') {\n const record = upsertMemoryRecord({\n providerId: this.id,\n kind: target === 'user' ? 'user_profile' : request.kind,\n agentId: request.scope?.agentId ?? this.agentId,\n workspaceId: request.scope?.workspaceId ?? this.workspaceDir,\n sessionKey: request.scope?.sessionKey,\n content: request.content,\n source: {\n ...(request.source ?? {}),\n provider: this.id,\n path: sourcePathForTarget(target),\n },\n confidence: request.confidence,\n tags: request.tags,\n status: 'candidate',\n sensitivity: request.sensitivity,\n evidence: request.evidence,\n reviewAfter: request.reviewAfter,\n expiresAt: request.expiresAt,\n });\n return {\n success: true,\n message: 'Memory candidate added to inbox',\n record,\n };\n }\n const result = await this.store.add(target, request.content);\n if (!result.success) return { success: false, error: result.error };\n return {\n success: true,\n message: result.message,\n record: upsertMemoryRecord({\n id: `curated:${target}:${Date.now()}`,\n providerId: this.id,\n kind: target === 'user' ? 'user_profile' : request.kind,\n agentId: request.scope?.agentId ?? this.agentId,\n workspaceId: request.scope?.workspaceId ?? this.workspaceDir,\n sessionKey: request.scope?.sessionKey,\n content: request.content,\n source: {\n ...(request.source ?? {}),\n provider: this.id,\n path: sourcePathForTarget(target),\n },\n tags: request.tags,\n }),\n };\n }\n\n async update(request: MemoryUpdateRequest): Promise<MemoryWriteResult> {\n const target = request.target ?? 'memory';\n const result = await this.store.replace(target, request.matchText ?? request.id ?? '', request.content);\n if (!result.success) return { success: false, error: result.error };\n if (request.id) {\n const existing = getMemoryRecord(request.id);\n upsertMemoryRecord({\n id: request.id,\n providerId: this.id,\n kind: existing?.kind ?? (target === 'user' ? 'user_profile' : 'agent_note'),\n agentId: existing?.scope.agentId ?? request.scope?.agentId ?? this.agentId,\n workspaceId: existing?.scope.workspaceId ?? request.scope?.workspaceId ?? this.workspaceDir,\n sessionKey: existing?.scope.sessionKey ?? request.scope?.sessionKey,\n content: request.content,\n source: existing?.source ?? { provider: this.id, path: sourcePathForTarget(target) },\n tags: existing?.tags,\n });\n } else if (request.matchText) {\n for (const existing of listMemoryRecords({\n providerId: this.id,\n agentId: request.scope?.agentId ?? this.agentId,\n workspaceId: request.scope?.workspaceId ?? this.workspaceDir,\n kind: target === 'user' ? 'user_profile' : 'agent_note',\n limit: 200,\n })) {\n if (!existing.content.includes(request.matchText)) continue;\n upsertMemoryRecord({\n id: existing.id,\n providerId: this.id,\n kind: existing.kind,\n agentId: existing.scope.agentId,\n workspaceId: existing.scope.workspaceId,\n sessionKey: existing.scope.sessionKey,\n content: request.content,\n source: existing.source,\n tags: existing.tags,\n });\n }\n }\n return { success: true, message: result.message };\n }\n\n async delete(request: MemoryDeleteRequest): Promise<MemoryWriteResult> {\n const target = request.target ?? 'memory';\n const result = await this.store.remove(target, request.matchText ?? request.id ?? '');\n if (!result.success) return { success: false, error: result.error };\n if (request.id) {\n deleteMemoryRecord(request.id);\n } else if (request.matchText) {\n for (const existing of listMemoryRecords({\n providerId: this.id,\n agentId: request.scope?.agentId ?? this.agentId,\n workspaceId: request.scope?.workspaceId ?? this.workspaceDir,\n kind: target === 'user' ? 'user_profile' : 'agent_note',\n limit: 200,\n })) {\n if (existing.content.includes(request.matchText)) {\n deleteMemoryRecord(existing.id);\n }\n }\n }\n return { success: true, message: result.message };\n }\n\n sync(event: MemorySyncEvent): void {\n if (event.type !== 'signal') return;\n appendMemorySignal({\n signal: event.signal,\n providerId: this.id,\n agentId: this.agentId,\n workspaceId: this.workspaceDir,\n sessionKey:\n typeof event.signal.metadata?.sessionKey === 'string'\n ? event.signal.metadata.sessionKey\n : undefined,\n });\n }\n\n async shutdown(): Promise<void> {}\n}\n\nfunction inferKindFromPath(path: string): MemoryRecord['kind'] {\n const normalized = path.replace(/\\\\/g, '/');\n if (normalized === 'user/MEMORY.md' || normalized.endsWith('/user/MEMORY.md')) return 'user_profile';\n if (/^memory\\/\\d{4}-\\d{2}-\\d{2}\\.md$/.test(normalized)) return 'daily_note';\n if (normalized.endsWith('MEMORY.md')) return 'agent_note';\n return 'workspace_fact';\n}\n\nfunction targetForKind(kind: MemoryRecord['kind']): 'memory' | 'user' {\n return kind === 'user_profile' ? 'user' : 'memory';\n}\n\nfunction sourcePathForTarget(target: 'memory' | 'user'): string {\n return target === 'user' ? 'user/MEMORY.md' : 'MEMORY.md';\n}\n\nfunction parseLineAddress(id: string): { path: string; lineStart?: number; lineEnd?: number } {\n const match = /^(.*)#L(\\d+)(?:-L(\\d+))?$/.exec(id);\n if (!match) return { path: id };\n const lineStart = Number.parseInt(match[2] ?? '1', 10);\n const lineEnd = Number.parseInt(match[3] ?? match[2] ?? '1', 10);\n return {\n path: match[1] ?? id,\n lineStart: Number.isFinite(lineStart) ? lineStart : undefined,\n lineEnd: Number.isFinite(lineEnd) ? lineEnd : undefined,\n };\n}\n"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"builtin-provider.js","names":[],"sources":["../../../../src/agent/memory/builtin-provider.ts"],"sourcesContent":["import type { BuiltinMemoryStore } from './builtin-memory-store.js';\nimport type { MemoryProvider, MemoryProviderInitOptions } from './provider.js';\nimport { memoryGet, memorySearch } from '../prompt/memory/index.js';\nimport {\n appendMemorySignal,\n deleteMemoryRecord,\n getMemoryRecord,\n listMemoryRecords,\n searchMemoryRecords,\n upsertMemoryRecord,\n} from '../../storage/sqlite/index.js';\nimport type {\n MemoryDeleteRequest,\n MemoryListRequest,\n MemoryReadRequest,\n MemoryReadResult,\n MemoryRecord,\n MemorySearchRequest,\n MemorySearchResult,\n MemoryUpdateRequest,\n MemoryWriteRequest,\n MemoryWriteResult,\n MemorySyncEvent,\n} from './types.js';\n\n/**\n * Builtin local memory provider backed by profile memory, agent-home curated files,\n * and the local SQLite FTS index.\n */\nexport class BuiltinMemoryProvider implements MemoryProvider {\n readonly id = 'local';\n readonly displayName = 'Local Markdown Memory';\n readonly capabilities = {\n search: true,\n read: true,\n write: true,\n update: true,\n delete: true,\n keywordSearch: true,\n semanticSearch: false,\n hybridSearch: false,\n citations: true,\n sync: false,\n local: true,\n };\n\n private workspaceDir = '';\n private agentId = 'main';\n\n constructor(private readonly store: BuiltinMemoryStore) {}\n\n isAvailable(): boolean {\n return true;\n }\n\n async initialize(_sessionId: string, options?: MemoryProviderInitOptions): Promise<void> {\n this.workspaceDir = options?.workspace ?? options?.agentWorkspace ?? this.store.workspaceDir;\n this.agentId = String(options?.agentId ?? this.agentId);\n }\n\n systemPromptBlock(): string {\n return '';\n }\n\n async prefetch(): Promise<string> {\n return '';\n }\n\n queuePrefetch(): void {}\n\n getToolSchemas() {\n return [];\n }\n\n async search(request: MemorySearchRequest): Promise<MemorySearchResult[]> {\n const agentId = request.scope?.agentId ?? this.agentId;\n const workspaceId = request.scope?.workspaceId ?? this.workspaceDir;\n const recordHits = searchMemoryRecords({\n query: request.query,\n agentId,\n workspaceId,\n providerId: this.id,\n kinds: request.kinds,\n maxResults: request.maxResults,\n minScore: request.minScore,\n });\n if (recordHits.length > 0) {\n return recordHits;\n }\n\n const results = await memorySearch(this.workspaceDir, request.query, {\n maxResults: request.maxResults,\n minScore: request.minScore,\n memoriesDir: this.store.memoriesDir,\n userMemoryPath: this.store.userMemoryPath,\n agentId,\n });\n return results.map((entry) => {\n const start = entry.lineNumbers[0] ?? 1;\n const end = entry.lineNumbers[entry.lineNumbers.length - 1] ?? start;\n const id = `${entry.file}#L${start}-L${end}`;\n const kind = inferKindFromPath(entry.file);\n const record = upsertMemoryRecord({\n id,\n providerId: this.id,\n kind,\n agentId,\n workspaceId,\n content: entry.lines,\n source: {\n provider: this.id,\n path: entry.file,\n lineStart: start,\n lineEnd: end,\n },\n confidence: entry.score,\n });\n return {\n record,\n score: entry.score,\n snippet: entry.lines,\n citation: {\n providerId: this.id,\n recordId: id,\n path: entry.file,\n lineStart: start,\n lineEnd: end,\n },\n };\n });\n }\n\n async read(request: MemoryReadRequest): Promise<MemoryReadResult | null> {\n if (request.id && !request.path) {\n const existing = getMemoryRecord(request.id);\n if (existing) {\n return { record: existing };\n }\n }\n const result = memoryGet(\n this.workspaceDir,\n request.path ?? request.id ?? '',\n request.from,\n request.lines,\n this.store.memoriesDir,\n this.store.userMemoryPath,\n );\n if (!result) return null;\n const path = request.path ?? request.id ?? '';\n const record = upsertMemoryRecord({\n id: `${path}#L${result.lineNumbers.start}-L${result.lineNumbers.end}`,\n providerId: this.id,\n kind: inferKindFromPath(path),\n agentId: request.scope?.agentId ?? this.agentId,\n workspaceId: request.scope?.workspaceId ?? this.workspaceDir,\n content: result.content,\n source: {\n provider: this.id,\n path,\n lineStart: result.lineNumbers.start,\n lineEnd: result.lineNumbers.end,\n },\n });\n return {\n record,\n lineNumbers: result.lineNumbers,\n };\n }\n\n async get(id: string): Promise<MemoryRecord | null> {\n const parsed = parseLineAddress(id);\n const result = await this.read({\n path: parsed.path,\n from: parsed.lineStart,\n lines: parsed.lineEnd != null ? parsed.lineEnd - parsed.lineStart + 1 : undefined,\n });\n return result?.record ?? null;\n }\n\n async list(request: MemoryListRequest): Promise<MemoryRecord[]> {\n const records = listMemoryRecords({\n providerId: this.id,\n agentId: request.scope?.agentId ?? this.agentId,\n workspaceId: request.scope?.workspaceId ?? this.workspaceDir,\n kind: request.kind,\n limit: 200,\n });\n if (records.length > 0 || request.kind) {\n return request.target\n ? records.filter((record) => targetForKind(record.kind) === request.target)\n : records;\n }\n\n const target = request.target ?? (request.kind === 'user_profile' ? 'user' : 'memory');\n return this.store.getLiveEntries(target).map((content, index) =>\n upsertMemoryRecord({\n id: `curated:${target}:${index + 1}`,\n providerId: this.id,\n kind: target === 'user' ? 'user_profile' : 'agent_note',\n agentId: request.scope?.agentId ?? this.agentId,\n workspaceId: request.scope?.workspaceId ?? this.workspaceDir,\n content,\n source: {\n provider: this.id,\n path: sourcePathForTarget(target),\n },\n }),\n );\n }\n\n async write(request: MemoryWriteRequest): Promise<MemoryWriteResult> {\n const target = request.target ?? (request.kind === 'user_profile' ? 'user' : 'memory');\n if (request.status === 'candidate') {\n const record = upsertMemoryRecord({\n providerId: this.id,\n kind: target === 'user' ? 'user_profile' : request.kind,\n agentId: request.scope?.agentId ?? this.agentId,\n workspaceId: request.scope?.workspaceId ?? this.workspaceDir,\n sessionKey: request.scope?.sessionKey,\n content: request.content,\n source: {\n ...(request.source ?? {}),\n provider: this.id,\n path: sourcePathForTarget(target),\n },\n confidence: request.confidence,\n tags: request.tags,\n status: 'candidate',\n sensitivity: request.sensitivity,\n evidence: request.evidence,\n reviewAfter: request.reviewAfter,\n expiresAt: request.expiresAt,\n });\n return {\n success: true,\n message: 'Memory candidate added to inbox',\n record,\n };\n }\n const result = await this.store.add(target, request.content);\n if (!result.success) return { success: false, error: result.error };\n return {\n success: true,\n message: result.message,\n record: upsertMemoryRecord({\n id: `curated:${target}:${Date.now()}`,\n providerId: this.id,\n kind: target === 'user' ? 'user_profile' : request.kind,\n agentId: request.scope?.agentId ?? this.agentId,\n workspaceId: request.scope?.workspaceId ?? this.workspaceDir,\n sessionKey: request.scope?.sessionKey,\n content: request.content,\n source: {\n ...(request.source ?? {}),\n provider: this.id,\n path: sourcePathForTarget(target),\n },\n tags: request.tags,\n }),\n };\n }\n\n async update(request: MemoryUpdateRequest): Promise<MemoryWriteResult> {\n const target = request.target ?? 'memory';\n const result = await this.store.replace(target, request.matchText ?? request.id ?? '', request.content);\n if (!result.success) return { success: false, error: result.error };\n if (request.id) {\n const existing = getMemoryRecord(request.id);\n upsertMemoryRecord({\n id: request.id,\n providerId: this.id,\n kind: existing?.kind ?? (target === 'user' ? 'user_profile' : 'agent_note'),\n agentId: existing?.scope.agentId ?? request.scope?.agentId ?? this.agentId,\n workspaceId: existing?.scope.workspaceId ?? request.scope?.workspaceId ?? this.workspaceDir,\n sessionKey: existing?.scope.sessionKey ?? request.scope?.sessionKey,\n content: request.content,\n source: existing?.source ?? { provider: this.id, path: sourcePathForTarget(target) },\n tags: existing?.tags,\n });\n } else if (request.matchText) {\n for (const existing of listMemoryRecords({\n providerId: this.id,\n agentId: request.scope?.agentId ?? this.agentId,\n workspaceId: request.scope?.workspaceId ?? this.workspaceDir,\n kind: target === 'user' ? 'user_profile' : 'agent_note',\n limit: 200,\n })) {\n if (!existing.content.includes(request.matchText)) continue;\n upsertMemoryRecord({\n id: existing.id,\n providerId: this.id,\n kind: existing.kind,\n agentId: existing.scope.agentId,\n workspaceId: existing.scope.workspaceId,\n sessionKey: existing.scope.sessionKey,\n content: request.content,\n source: existing.source,\n tags: existing.tags,\n });\n }\n }\n return { success: true, message: result.message };\n }\n\n async delete(request: MemoryDeleteRequest): Promise<MemoryWriteResult> {\n const target = request.target ?? 'memory';\n const result = await this.store.remove(target, request.matchText ?? request.id ?? '');\n if (!result.success) return { success: false, error: result.error };\n if (request.id) {\n deleteMemoryRecord(request.id);\n } else if (request.matchText) {\n for (const existing of listMemoryRecords({\n providerId: this.id,\n agentId: request.scope?.agentId ?? this.agentId,\n workspaceId: request.scope?.workspaceId ?? this.workspaceDir,\n kind: target === 'user' ? 'user_profile' : 'agent_note',\n limit: 200,\n })) {\n if (existing.content.includes(request.matchText)) {\n deleteMemoryRecord(existing.id);\n }\n }\n }\n return { success: true, message: result.message };\n }\n\n sync(event: MemorySyncEvent): void {\n if (event.type !== 'signal') return;\n appendMemorySignal({\n signal: event.signal,\n providerId: this.id,\n agentId: this.agentId,\n workspaceId: this.workspaceDir,\n sessionKey:\n typeof event.signal.metadata?.sessionKey === 'string'\n ? event.signal.metadata.sessionKey\n : undefined,\n });\n }\n\n async shutdown(): Promise<void> {}\n}\n\nfunction inferKindFromPath(path: string): MemoryRecord['kind'] {\n const normalized = path.replace(/\\\\/g, '/');\n if (normalized === 'user/MEMORY.md' || normalized.endsWith('/user/MEMORY.md')) return 'user_profile';\n if (/^memory\\/\\d{4}-\\d{2}-\\d{2}\\.md$/.test(normalized)) return 'daily_note';\n if (normalized.endsWith('MEMORY.md')) return 'agent_note';\n return 'workspace_fact';\n}\n\nfunction targetForKind(kind: MemoryRecord['kind']): 'memory' | 'user' {\n return kind === 'user_profile' ? 'user' : 'memory';\n}\n\nfunction sourcePathForTarget(target: 'memory' | 'user'): string {\n return target === 'user' ? 'user/MEMORY.md' : 'MEMORY.md';\n}\n\nfunction parseLineAddress(id: string): { path: string; lineStart?: number; lineEnd?: number } {\n const match = /^(.*)#L(\\d+)(?:-L(\\d+))?$/.exec(id);\n if (!match) return { path: id };\n const lineStart = Number.parseInt(match[2] ?? '1', 10);\n const lineEnd = Number.parseInt(match[3] ?? match[2] ?? '1', 10);\n return {\n path: match[1] ?? id,\n lineStart: Number.isFinite(lineStart) ? lineStart : undefined,\n lineEnd: Number.isFinite(lineEnd) ? lineEnd : undefined,\n };\n}\n"],"mappings":"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"}
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import type { AgentMessage } from '@earendil-works/pi-agent-core';
|
|
2
|
-
import { type Model, type Api } from '@earendil-works/pi-ai';
|
|
2
|
+
import { type Model, type Api } from '@earendil-works/pi-ai/compat';
|
|
3
3
|
import { type ConversationSummary } from './summary-generator.js';
|
|
4
4
|
export interface CompactionResult {
|
|
5
5
|
summary: string;
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { readAgentMessageContent } from "./agent-message-access.js";
|
|
2
2
|
import { formatSummaryAsText, generateStructuredSummary } from "./summary-generator.js";
|
|
3
|
-
import { complete } from "@earendil-works/pi-ai";
|
|
3
|
+
import { complete } from "@earendil-works/pi-ai/compat";
|
|
4
4
|
//#region src/agent/memory/compaction.ts
|
|
5
5
|
const DEFAULT_CONFIG = {
|
|
6
6
|
enabled: true,
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"compaction.js","names":[],"sources":["../../../../src/agent/memory/compaction.ts"],"sourcesContent":["import type { AgentMessage } from '@earendil-works/pi-agent-core';\nimport { complete, type Model, type Api, type UserMessage } from '@earendil-works/pi-ai';\nimport { readAgentMessageContent } from './agent-message-access.js';\nimport { generateStructuredSummary, formatSummaryAsText, type ConversationSummary } from './summary-generator.js';\n\nexport interface CompactionResult {\n summary: string;\n firstKeptIndex: number;\n tokensBefore: number;\n tokensAfter: number;\n compacted: boolean;\n structuredSummary?: ConversationSummary;\n compactedUsage?: {\n input: number;\n output: number;\n total: number;\n cost?: number;\n };\n}\n\nexport interface CompactionConfig {\n enabled: boolean;\n mode: 'extractive' | 'abstractive' | 'structured';\n reserveTokens: number;\n triggerThreshold: number;\n minMessagesBeforeCompact: number;\n keepRecentMessages: number;\n summaryMaxTokens: number;\n evictionWindow: number;\n retentionWindow: number;\n preserveReasoning: boolean;\n accumulateUsage: boolean;\n}\n\nconst DEFAULT_CONFIG: CompactionConfig = {\n enabled: true,\n mode: 'abstractive',\n reserveTokens: 8000,\n triggerThreshold: 0.8,\n minMessagesBeforeCompact: 10,\n keepRecentMessages: 10,\n summaryMaxTokens: 500,\n evictionWindow: 0.2,\n retentionWindow: 6,\n preserveReasoning: true,\n accumulateUsage: true,\n};\n\nfunction estimateTokens(text: string): number {\n return Math.ceil(text.length / 4);\n}\n\nfunction estimateMessageTokens(msg: AgentMessage): number {\n let text = '';\n const raw = readAgentMessageContent(msg);\n\n if (typeof raw === 'string') {\n text = raw;\n } else if (Array.isArray(raw)) {\n text = raw\n .filter(c => c.type === 'text')\n .map(c => (c as { text?: string }).text || '')\n .join('\\n');\n }\n \n return estimateTokens(text) + 10;\n}\n\n// Internal: Reasoning extraction utilities\ninterface ReasoningDetails {\n thinking?: string;\n signature?: string;\n}\n\nfunction extractLastReasoning(messages: AgentMessage[]): ReasoningDetails | null {\n for (let i = messages.length - 1; i >= 0; i--) {\n const msg = messages[i];\n if (msg.role === 'assistant') {\n const rd = (msg as unknown as { reasoning_details?: ReasoningDetails }).reasoning_details;\n if (rd && (rd.thinking || rd.signature)) {\n return rd;\n }\n }\n }\n return null;\n}\n\n// Internal: Inject reasoning into first assistant message\nfunction injectReasoningIntoFirstAssistant(\n messages: AgentMessage[],\n reasoning: ReasoningDetails\n): void {\n for (const msg of messages) {\n if (msg.role === 'assistant') {\n const existing = (msg as unknown as { reasoning_details?: ReasoningDetails }).reasoning_details;\n if (!existing || (!existing.thinking && !existing.signature)) {\n (msg as unknown as { reasoning_details?: ReasoningDetails }).reasoning_details = reasoning;\n }\n break;\n }\n }\n}\n\n// Internal: Message usage tracking\ninterface MessageUsage {\n input: number;\n output: number;\n total: number;\n cost?: number;\n}\n\nexport function accumulateUsage(messages: AgentMessage[]): MessageUsage | undefined {\n let totalInput = 0;\n let totalOutput = 0;\n let totalCost = 0;\n let hasUsage = false;\n\n for (const msg of messages) {\n const usage = (msg as unknown as { usage?: MessageUsage }).usage;\n if (usage) {\n hasUsage = true;\n totalInput += usage.input || 0;\n totalOutput += usage.output || 0;\n totalCost += usage.cost || 0;\n }\n }\n\n if (!hasUsage) return undefined;\n\n return {\n input: totalInput,\n output: totalOutput,\n total: totalInput + totalOutput,\n cost: totalCost > 0 ? totalCost : undefined,\n };\n}\n\n// Internal: Droppable message filtering\ntype DroppableMessage = AgentMessage & {\n droppable?: boolean;\n};\n\nfunction filterDroppableMessages(messages: AgentMessage[]): AgentMessage[] {\n return messages.filter(msg => !(msg as DroppableMessage).droppable);\n}\n\nfunction findNthTurnFromEnd(messages: AgentMessage[], n: number): number {\n if (n <= 0) return messages.length;\n \n let turnsFound = 0;\n let lastRole: string | null = null;\n \n for (let i = messages.length - 1; i >= 0; i--) {\n const msg = messages[i];\n \n if (msg.role === 'user' && lastRole !== 'user') {\n turnsFound++;\n if (turnsFound === n) {\n return i;\n }\n }\n lastRole = msg.role;\n }\n \n return 0;\n}\n\n// Internal: Calculate compaction range\nfunction calculateCompactionRange(\n messages: AgentMessage[],\n config: CompactionConfig\n): { start: number; end: number } | null {\n const totalMessages = messages.length;\n \n if (totalMessages < config.minMessagesBeforeCompact) {\n return null;\n }\n \n const evictionEnd = Math.floor(totalMessages * config.evictionWindow);\n const retentionStart = findNthTurnFromEnd(messages, config.retentionWindow);\n const retentionEnd = retentionStart > 0 ? retentionStart - 1 : 0;\n const compactionEnd = Math.min(evictionEnd, retentionEnd);\n \n if (compactionEnd <= 1) {\n return null;\n }\n \n return { start: 0, end: compactionEnd };\n}\n\nexport class SessionCompactor {\n private config: CompactionConfig;\n \n constructor(\n config?: Partial<CompactionConfig>,\n private model?: Model<Api>\n ) {\n this.config = { ...DEFAULT_CONFIG, ...config };\n }\n\n needsCompaction(\n messages: AgentMessage[],\n contextWindow: number\n ): { needed: boolean; reason: string; usagePercent?: number } {\n if (!this.config.enabled) {\n return { needed: false, reason: 'disabled' };\n }\n\n if (messages.length < this.config.minMessagesBeforeCompact) {\n return { needed: false, reason: 'not_enough_messages' };\n }\n\n const totalTokens = this.estimateTotalTokens(messages);\n const usagePercent = totalTokens / contextWindow;\n\n if (usagePercent > this.config.triggerThreshold) {\n return { \n needed: true, \n reason: 'threshold_exceeded',\n usagePercent \n };\n }\n\n return { needed: false, reason: 'within_threshold' };\n }\n\n async compact(\n messages: AgentMessage[],\n instructions?: string,\n force?: boolean,\n ): Promise<CompactionResult> {\n const effectiveMessages = filterDroppableMessages(messages);\n\n const minRequired = this.config.minMessagesBeforeCompact;\n if (!force && effectiveMessages.length < minRequired) {\n return {\n summary: '',\n firstKeptIndex: 0,\n tokensBefore: this.estimateTotalTokens(effectiveMessages),\n tokensAfter: this.estimateTotalTokens(effectiveMessages),\n compacted: false,\n };\n }\n\n if (force && effectiveMessages.length < 2) {\n return {\n summary: '',\n firstKeptIndex: 0,\n tokensBefore: this.estimateTotalTokens(effectiveMessages),\n tokensAfter: this.estimateTotalTokens(effectiveMessages),\n compacted: false,\n };\n }\n\n const range = calculateCompactionRange(effectiveMessages, this.config);\n let messagesToSummarize: AgentMessage[];\n let keptMessages: AgentMessage[];\n\n if (range) {\n messagesToSummarize = effectiveMessages.slice(0, range.end);\n keptMessages = effectiveMessages.slice(range.end);\n } else if (force) {\n const keep = Math.min(\n this.config.keepRecentMessages,\n Math.max(1, effectiveMessages.length - 1),\n );\n messagesToSummarize = effectiveMessages.slice(0, effectiveMessages.length - keep);\n keptMessages = effectiveMessages.slice(-keep);\n } else {\n const keepRecent = this.config.keepRecentMessages;\n messagesToSummarize = effectiveMessages.slice(0, -keepRecent);\n keptMessages = effectiveMessages.slice(-keepRecent);\n }\n\n if (messagesToSummarize.length === 0) {\n return {\n summary: '',\n firstKeptIndex: 0,\n tokensBefore: this.estimateTotalTokens(effectiveMessages),\n tokensAfter: this.estimateTotalTokens(effectiveMessages),\n compacted: false,\n };\n }\n\n let preservedReasoning: ReasoningDetails | null = null;\n if (this.config.preserveReasoning) {\n preservedReasoning = extractLastReasoning(messagesToSummarize);\n if (preservedReasoning) {\n injectReasoningIntoFirstAssistant(keptMessages, preservedReasoning);\n }\n }\n\n const summary = await this.generateSummary(messagesToSummarize, instructions);\n const structuredSummary = this.config.mode === 'structured' \n ? generateStructuredSummary(messagesToSummarize)\n : undefined;\n\n const tokensBefore = this.estimateTotalTokens(effectiveMessages);\n const summaryTokens = estimateTokens(summary) + 20;\n const keptTokens = this.estimateTotalTokens(keptMessages);\n const tokensAfter = summaryTokens + keptTokens;\n\n let compactedUsage: MessageUsage | undefined;\n if (this.config.accumulateUsage) {\n compactedUsage = accumulateUsage(messagesToSummarize);\n }\n\n return {\n summary,\n firstKeptIndex: keptMessages.length > 0 ? effectiveMessages.length - keptMessages.length : 0,\n tokensBefore,\n tokensAfter,\n compacted: true,\n structuredSummary,\n compactedUsage,\n };\n }\n\n private async generateSummary(messages: AgentMessage[], instructions?: string): Promise<string> {\n if (this.model && (this.config.mode === 'abstractive' || this.config.mode === 'structured')) {\n try {\n if (this.config.mode === 'structured') {\n const structured = generateStructuredSummary(messages);\n return formatSummaryAsText(structured, true);\n } else {\n return await this.llmAbstractiveSummary(messages, instructions);\n }\n } catch (err) {\n console.warn('[Compactor] LLM summarization failed, falling back to extractive', err);\n }\n }\n\n return this.extractiveSummary(messages);\n }\n\n private async llmAbstractiveSummary(messages: AgentMessage[], instructions?: string): Promise<string> {\n if (!this.model) {\n throw new Error('Model not available');\n }\n\n const conversation = this.formatMessages(messages);\n const extra = instructions?.trim()\n ? `\\nAdditional focus from the user:\\n${instructions.trim()}\\n`\n : '';\n const prompt = `Summarize the following conversation in 2-3 concise sentences. Focus on:\n1. What the user was trying to accomplish\n2. Key decisions, outcomes, or solutions\n3. Any important context that should be preserved\n${extra}\nConversation:\n${conversation}\n\nSummary:`;\n\n const controller = new AbortController();\n const timeoutId = setTimeout(() => controller.abort(), 30000);\n\n try {\n const summaryMessage: UserMessage = { role: 'user', content: prompt, timestamp: Date.now() };\n const result = await complete(this.model, { \n messages: [summaryMessage]\n }, {\n maxTokens: this.config.summaryMaxTokens,\n temperature: 0.3,\n signal: controller.signal as any,\n });\n\n const text = Array.isArray(result.content)\n ? result.content.filter((c: any) => c.type === 'text').map((c: any) => c.text).join('')\n : '';\n\n return text.trim();\n } catch (err) {\n if (err instanceof Error && err.name === 'AbortError') {\n throw new Error('LLM summarization timed out after 30 seconds');\n }\n throw err;\n } finally {\n clearTimeout(timeoutId);\n }\n }\n\n private formatMessages(messages: AgentMessage[]): string {\n return messages\n .map(m => {\n const role = m.role;\n const raw = readAgentMessageContent(m);\n const content = typeof raw === 'string'\n ? raw\n : (raw as Array<{ type: string; text?: string }>).filter(c => c.type === 'text').map(c => c.text || '').join('\\n');\n return `[${role}]: ${content}`;\n })\n .join('\\n\\n');\n }\n\n private extractiveSummary(messages: AgentMessage[]): string {\n const userMessages = messages\n .filter(m => m.role === 'user')\n .slice(-5)\n .map(m => {\n const raw = readAgentMessageContent(m);\n if (typeof raw === 'string') return raw;\n if (Array.isArray(raw)) {\n return raw\n .filter(c => c.type === 'text')\n .map(c => (c as { text?: string }).text || '')\n .join('\\n');\n }\n return '';\n })\n .filter(t => t.length > 0)\n .join('; ');\n\n return `Previous conversation covered: ${userMessages.slice(0, 300)}...`;\n }\n\n estimateTotalTokens(messages: AgentMessage[]): number {\n return messages.reduce((sum, msg) => sum + estimateMessageTokens(msg), 0);\n }\n\n applyCompaction(\n messages: AgentMessage[],\n result: CompactionResult\n ): AgentMessage[] {\n if (!result.compacted || !result.summary) {\n return messages;\n }\n\n const summaryMessage: AgentMessage & { usage?: MessageUsage } = {\n role: 'user',\n content: [{ type: 'text', text: `[Previous conversation summary]: ${result.summary}` }],\n timestamp: Date.now(),\n };\n\n if (result.compactedUsage) {\n summaryMessage.usage = result.compactedUsage;\n }\n\n const keptMessages = messages.slice(result.firstKeptIndex);\n return [summaryMessage, ...keptMessages];\n }\n}\n"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"compaction.js","names":[],"sources":["../../../../src/agent/memory/compaction.ts"],"sourcesContent":["import type { AgentMessage } from '@earendil-works/pi-agent-core';\nimport { complete, type Model, type Api, type UserMessage } from '@earendil-works/pi-ai/compat';\nimport { readAgentMessageContent } from './agent-message-access.js';\nimport { generateStructuredSummary, formatSummaryAsText, type ConversationSummary } from './summary-generator.js';\n\nexport interface CompactionResult {\n summary: string;\n firstKeptIndex: number;\n tokensBefore: number;\n tokensAfter: number;\n compacted: boolean;\n structuredSummary?: ConversationSummary;\n compactedUsage?: {\n input: number;\n output: number;\n total: number;\n cost?: number;\n };\n}\n\nexport interface CompactionConfig {\n enabled: boolean;\n mode: 'extractive' | 'abstractive' | 'structured';\n reserveTokens: number;\n triggerThreshold: number;\n minMessagesBeforeCompact: number;\n keepRecentMessages: number;\n summaryMaxTokens: number;\n evictionWindow: number;\n retentionWindow: number;\n preserveReasoning: boolean;\n accumulateUsage: boolean;\n}\n\nconst DEFAULT_CONFIG: CompactionConfig = {\n enabled: true,\n mode: 'abstractive',\n reserveTokens: 8000,\n triggerThreshold: 0.8,\n minMessagesBeforeCompact: 10,\n keepRecentMessages: 10,\n summaryMaxTokens: 500,\n evictionWindow: 0.2,\n retentionWindow: 6,\n preserveReasoning: true,\n accumulateUsage: true,\n};\n\nfunction estimateTokens(text: string): number {\n return Math.ceil(text.length / 4);\n}\n\nfunction estimateMessageTokens(msg: AgentMessage): number {\n let text = '';\n const raw = readAgentMessageContent(msg);\n\n if (typeof raw === 'string') {\n text = raw;\n } else if (Array.isArray(raw)) {\n text = raw\n .filter(c => c.type === 'text')\n .map(c => (c as { text?: string }).text || '')\n .join('\\n');\n }\n \n return estimateTokens(text) + 10;\n}\n\n// Internal: Reasoning extraction utilities\ninterface ReasoningDetails {\n thinking?: string;\n signature?: string;\n}\n\nfunction extractLastReasoning(messages: AgentMessage[]): ReasoningDetails | null {\n for (let i = messages.length - 1; i >= 0; i--) {\n const msg = messages[i];\n if (msg.role === 'assistant') {\n const rd = (msg as unknown as { reasoning_details?: ReasoningDetails }).reasoning_details;\n if (rd && (rd.thinking || rd.signature)) {\n return rd;\n }\n }\n }\n return null;\n}\n\n// Internal: Inject reasoning into first assistant message\nfunction injectReasoningIntoFirstAssistant(\n messages: AgentMessage[],\n reasoning: ReasoningDetails\n): void {\n for (const msg of messages) {\n if (msg.role === 'assistant') {\n const existing = (msg as unknown as { reasoning_details?: ReasoningDetails }).reasoning_details;\n if (!existing || (!existing.thinking && !existing.signature)) {\n (msg as unknown as { reasoning_details?: ReasoningDetails }).reasoning_details = reasoning;\n }\n break;\n }\n }\n}\n\n// Internal: Message usage tracking\ninterface MessageUsage {\n input: number;\n output: number;\n total: number;\n cost?: number;\n}\n\nexport function accumulateUsage(messages: AgentMessage[]): MessageUsage | undefined {\n let totalInput = 0;\n let totalOutput = 0;\n let totalCost = 0;\n let hasUsage = false;\n\n for (const msg of messages) {\n const usage = (msg as unknown as { usage?: MessageUsage }).usage;\n if (usage) {\n hasUsage = true;\n totalInput += usage.input || 0;\n totalOutput += usage.output || 0;\n totalCost += usage.cost || 0;\n }\n }\n\n if (!hasUsage) return undefined;\n\n return {\n input: totalInput,\n output: totalOutput,\n total: totalInput + totalOutput,\n cost: totalCost > 0 ? totalCost : undefined,\n };\n}\n\n// Internal: Droppable message filtering\ntype DroppableMessage = AgentMessage & {\n droppable?: boolean;\n};\n\nfunction filterDroppableMessages(messages: AgentMessage[]): AgentMessage[] {\n return messages.filter(msg => !(msg as DroppableMessage).droppable);\n}\n\nfunction findNthTurnFromEnd(messages: AgentMessage[], n: number): number {\n if (n <= 0) return messages.length;\n \n let turnsFound = 0;\n let lastRole: string | null = null;\n \n for (let i = messages.length - 1; i >= 0; i--) {\n const msg = messages[i];\n \n if (msg.role === 'user' && lastRole !== 'user') {\n turnsFound++;\n if (turnsFound === n) {\n return i;\n }\n }\n lastRole = msg.role;\n }\n \n return 0;\n}\n\n// Internal: Calculate compaction range\nfunction calculateCompactionRange(\n messages: AgentMessage[],\n config: CompactionConfig\n): { start: number; end: number } | null {\n const totalMessages = messages.length;\n \n if (totalMessages < config.minMessagesBeforeCompact) {\n return null;\n }\n \n const evictionEnd = Math.floor(totalMessages * config.evictionWindow);\n const retentionStart = findNthTurnFromEnd(messages, config.retentionWindow);\n const retentionEnd = retentionStart > 0 ? retentionStart - 1 : 0;\n const compactionEnd = Math.min(evictionEnd, retentionEnd);\n \n if (compactionEnd <= 1) {\n return null;\n }\n \n return { start: 0, end: compactionEnd };\n}\n\nexport class SessionCompactor {\n private config: CompactionConfig;\n \n constructor(\n config?: Partial<CompactionConfig>,\n private model?: Model<Api>\n ) {\n this.config = { ...DEFAULT_CONFIG, ...config };\n }\n\n needsCompaction(\n messages: AgentMessage[],\n contextWindow: number\n ): { needed: boolean; reason: string; usagePercent?: number } {\n if (!this.config.enabled) {\n return { needed: false, reason: 'disabled' };\n }\n\n if (messages.length < this.config.minMessagesBeforeCompact) {\n return { needed: false, reason: 'not_enough_messages' };\n }\n\n const totalTokens = this.estimateTotalTokens(messages);\n const usagePercent = totalTokens / contextWindow;\n\n if (usagePercent > this.config.triggerThreshold) {\n return { \n needed: true, \n reason: 'threshold_exceeded',\n usagePercent \n };\n }\n\n return { needed: false, reason: 'within_threshold' };\n }\n\n async compact(\n messages: AgentMessage[],\n instructions?: string,\n force?: boolean,\n ): Promise<CompactionResult> {\n const effectiveMessages = filterDroppableMessages(messages);\n\n const minRequired = this.config.minMessagesBeforeCompact;\n if (!force && effectiveMessages.length < minRequired) {\n return {\n summary: '',\n firstKeptIndex: 0,\n tokensBefore: this.estimateTotalTokens(effectiveMessages),\n tokensAfter: this.estimateTotalTokens(effectiveMessages),\n compacted: false,\n };\n }\n\n if (force && effectiveMessages.length < 2) {\n return {\n summary: '',\n firstKeptIndex: 0,\n tokensBefore: this.estimateTotalTokens(effectiveMessages),\n tokensAfter: this.estimateTotalTokens(effectiveMessages),\n compacted: false,\n };\n }\n\n const range = calculateCompactionRange(effectiveMessages, this.config);\n let messagesToSummarize: AgentMessage[];\n let keptMessages: AgentMessage[];\n\n if (range) {\n messagesToSummarize = effectiveMessages.slice(0, range.end);\n keptMessages = effectiveMessages.slice(range.end);\n } else if (force) {\n const keep = Math.min(\n this.config.keepRecentMessages,\n Math.max(1, effectiveMessages.length - 1),\n );\n messagesToSummarize = effectiveMessages.slice(0, effectiveMessages.length - keep);\n keptMessages = effectiveMessages.slice(-keep);\n } else {\n const keepRecent = this.config.keepRecentMessages;\n messagesToSummarize = effectiveMessages.slice(0, -keepRecent);\n keptMessages = effectiveMessages.slice(-keepRecent);\n }\n\n if (messagesToSummarize.length === 0) {\n return {\n summary: '',\n firstKeptIndex: 0,\n tokensBefore: this.estimateTotalTokens(effectiveMessages),\n tokensAfter: this.estimateTotalTokens(effectiveMessages),\n compacted: false,\n };\n }\n\n let preservedReasoning: ReasoningDetails | null = null;\n if (this.config.preserveReasoning) {\n preservedReasoning = extractLastReasoning(messagesToSummarize);\n if (preservedReasoning) {\n injectReasoningIntoFirstAssistant(keptMessages, preservedReasoning);\n }\n }\n\n const summary = await this.generateSummary(messagesToSummarize, instructions);\n const structuredSummary = this.config.mode === 'structured' \n ? generateStructuredSummary(messagesToSummarize)\n : undefined;\n\n const tokensBefore = this.estimateTotalTokens(effectiveMessages);\n const summaryTokens = estimateTokens(summary) + 20;\n const keptTokens = this.estimateTotalTokens(keptMessages);\n const tokensAfter = summaryTokens + keptTokens;\n\n let compactedUsage: MessageUsage | undefined;\n if (this.config.accumulateUsage) {\n compactedUsage = accumulateUsage(messagesToSummarize);\n }\n\n return {\n summary,\n firstKeptIndex: keptMessages.length > 0 ? effectiveMessages.length - keptMessages.length : 0,\n tokensBefore,\n tokensAfter,\n compacted: true,\n structuredSummary,\n compactedUsage,\n };\n }\n\n private async generateSummary(messages: AgentMessage[], instructions?: string): Promise<string> {\n if (this.model && (this.config.mode === 'abstractive' || this.config.mode === 'structured')) {\n try {\n if (this.config.mode === 'structured') {\n const structured = generateStructuredSummary(messages);\n return formatSummaryAsText(structured, true);\n } else {\n return await this.llmAbstractiveSummary(messages, instructions);\n }\n } catch (err) {\n console.warn('[Compactor] LLM summarization failed, falling back to extractive', err);\n }\n }\n\n return this.extractiveSummary(messages);\n }\n\n private async llmAbstractiveSummary(messages: AgentMessage[], instructions?: string): Promise<string> {\n if (!this.model) {\n throw new Error('Model not available');\n }\n\n const conversation = this.formatMessages(messages);\n const extra = instructions?.trim()\n ? `\\nAdditional focus from the user:\\n${instructions.trim()}\\n`\n : '';\n const prompt = `Summarize the following conversation in 2-3 concise sentences. Focus on:\n1. What the user was trying to accomplish\n2. Key decisions, outcomes, or solutions\n3. Any important context that should be preserved\n${extra}\nConversation:\n${conversation}\n\nSummary:`;\n\n const controller = new AbortController();\n const timeoutId = setTimeout(() => controller.abort(), 30000);\n\n try {\n const summaryMessage: UserMessage = { role: 'user', content: prompt, timestamp: Date.now() };\n const result = await complete(this.model, { \n messages: [summaryMessage]\n }, {\n maxTokens: this.config.summaryMaxTokens,\n temperature: 0.3,\n signal: controller.signal as any,\n });\n\n const text = Array.isArray(result.content)\n ? result.content.filter((c: any) => c.type === 'text').map((c: any) => c.text).join('')\n : '';\n\n return text.trim();\n } catch (err) {\n if (err instanceof Error && err.name === 'AbortError') {\n throw new Error('LLM summarization timed out after 30 seconds');\n }\n throw err;\n } finally {\n clearTimeout(timeoutId);\n }\n }\n\n private formatMessages(messages: AgentMessage[]): string {\n return messages\n .map(m => {\n const role = m.role;\n const raw = readAgentMessageContent(m);\n const content = typeof raw === 'string'\n ? raw\n : (raw as Array<{ type: string; text?: string }>).filter(c => c.type === 'text').map(c => c.text || '').join('\\n');\n return `[${role}]: ${content}`;\n })\n .join('\\n\\n');\n }\n\n private extractiveSummary(messages: AgentMessage[]): string {\n const userMessages = messages\n .filter(m => m.role === 'user')\n .slice(-5)\n .map(m => {\n const raw = readAgentMessageContent(m);\n if (typeof raw === 'string') return raw;\n if (Array.isArray(raw)) {\n return raw\n .filter(c => c.type === 'text')\n .map(c => (c as { text?: string }).text || '')\n .join('\\n');\n }\n return '';\n })\n .filter(t => t.length > 0)\n .join('; ');\n\n return `Previous conversation covered: ${userMessages.slice(0, 300)}...`;\n }\n\n estimateTotalTokens(messages: AgentMessage[]): number {\n return messages.reduce((sum, msg) => sum + estimateMessageTokens(msg), 0);\n }\n\n applyCompaction(\n messages: AgentMessage[],\n result: CompactionResult\n ): AgentMessage[] {\n if (!result.compacted || !result.summary) {\n return messages;\n }\n\n const summaryMessage: AgentMessage & { usage?: MessageUsage } = {\n role: 'user',\n content: [{ type: 'text', text: `[Previous conversation summary]: ${result.summary}` }],\n timestamp: Date.now(),\n };\n\n if (result.compactedUsage) {\n summaryMessage.usage = result.compactedUsage;\n }\n\n const keptMessages = messages.slice(result.firstKeptIndex);\n return [summaryMessage, ...keptMessages];\n }\n}\n"],"mappings":"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"}
|
|
@@ -1,11 +1,4 @@
|
|
|
1
1
|
import type { DreamingLightConfig } from './config.js';
|
|
2
|
-
/**
|
|
3
|
-
* Scan recent daily memory files (memory/YYYY-MM-DD.md) and collect signal
|
|
4
|
-
* entries into the short-term store. Deduplicates near-identical snippets
|
|
5
|
-
* using trigram similarity.
|
|
6
|
-
*
|
|
7
|
-
* This is the "light sleep" phase: fast, frequent, cheap.
|
|
8
|
-
*/
|
|
9
2
|
export declare function runLightSweep(params: {
|
|
10
3
|
workspaceDir: string;
|
|
11
4
|
config?: Partial<DreamingLightConfig>;
|