onbuzz 4.9.13 → 4.10.0
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/node_modules/glob/README.md +31 -5
- package/node_modules/glob/dist/commonjs/glob.d.ts +8 -0
- package/node_modules/glob/dist/commonjs/glob.d.ts.map +1 -1
- package/node_modules/glob/dist/commonjs/glob.js +2 -1
- package/node_modules/glob/dist/commonjs/glob.js.map +1 -1
- package/node_modules/glob/dist/commonjs/index.min.js +3 -3
- package/node_modules/glob/dist/commonjs/index.min.js.map +4 -4
- package/node_modules/glob/dist/commonjs/pattern.d.ts +3 -0
- package/node_modules/glob/dist/commonjs/pattern.d.ts.map +1 -1
- package/node_modules/glob/dist/commonjs/pattern.js +4 -0
- package/node_modules/glob/dist/commonjs/pattern.js.map +1 -1
- package/node_modules/glob/dist/esm/glob.d.ts +8 -0
- package/node_modules/glob/dist/esm/glob.d.ts.map +1 -1
- package/node_modules/glob/dist/esm/glob.js +2 -1
- package/node_modules/glob/dist/esm/glob.js.map +1 -1
- package/node_modules/glob/dist/esm/index.min.js +3 -3
- package/node_modules/glob/dist/esm/index.min.js.map +4 -4
- package/node_modules/glob/dist/esm/pattern.d.ts +3 -0
- package/node_modules/glob/dist/esm/pattern.d.ts.map +1 -1
- package/node_modules/glob/dist/esm/pattern.js +4 -0
- package/node_modules/glob/dist/esm/pattern.js.map +1 -1
- package/node_modules/{@isaacs → glob/node_modules}/balanced-match/README.md +7 -10
- package/node_modules/{@isaacs → glob/node_modules}/balanced-match/package.json +7 -18
- package/node_modules/{@isaacs → glob/node_modules}/brace-expansion/README.md +3 -6
- package/node_modules/{@isaacs → glob/node_modules}/brace-expansion/dist/commonjs/index.js +6 -4
- package/node_modules/glob/node_modules/brace-expansion/dist/commonjs/index.js.map +1 -0
- package/node_modules/{@isaacs → glob/node_modules}/brace-expansion/dist/esm/index.js +6 -4
- package/node_modules/glob/node_modules/brace-expansion/dist/esm/index.js.map +1 -0
- package/node_modules/{@isaacs → glob/node_modules}/brace-expansion/package.json +11 -7
- package/node_modules/glob/node_modules/minimatch/README.md +76 -1
- package/node_modules/glob/node_modules/minimatch/dist/commonjs/assert-valid-pattern.d.ts +1 -1
- package/node_modules/glob/node_modules/minimatch/dist/commonjs/assert-valid-pattern.d.ts.map +1 -1
- package/node_modules/glob/node_modules/minimatch/dist/commonjs/assert-valid-pattern.js.map +1 -1
- package/node_modules/glob/node_modules/minimatch/dist/commonjs/ast.d.ts +4 -2
- package/node_modules/glob/node_modules/minimatch/dist/commonjs/ast.d.ts.map +1 -1
- package/node_modules/glob/node_modules/minimatch/dist/commonjs/ast.js +309 -55
- package/node_modules/glob/node_modules/minimatch/dist/commonjs/ast.js.map +1 -1
- package/node_modules/glob/node_modules/minimatch/dist/commonjs/brace-expressions.d.ts.map +1 -1
- package/node_modules/glob/node_modules/minimatch/dist/commonjs/brace-expressions.js +2 -4
- package/node_modules/glob/node_modules/minimatch/dist/commonjs/brace-expressions.js.map +1 -1
- package/node_modules/glob/node_modules/minimatch/dist/commonjs/escape.d.ts +1 -1
- package/node_modules/glob/node_modules/minimatch/dist/commonjs/escape.d.ts.map +1 -1
- package/node_modules/glob/node_modules/minimatch/dist/commonjs/escape.js +4 -4
- package/node_modules/glob/node_modules/minimatch/dist/commonjs/escape.js.map +1 -1
- package/node_modules/glob/node_modules/minimatch/dist/commonjs/index.d.ts +81 -1
- package/node_modules/glob/node_modules/minimatch/dist/commonjs/index.d.ts.map +1 -1
- package/node_modules/glob/node_modules/minimatch/dist/commonjs/index.js +232 -134
- package/node_modules/glob/node_modules/minimatch/dist/commonjs/index.js.map +1 -1
- package/node_modules/glob/node_modules/minimatch/dist/commonjs/unescape.d.ts +1 -1
- package/node_modules/glob/node_modules/minimatch/dist/commonjs/unescape.d.ts.map +1 -1
- package/node_modules/glob/node_modules/minimatch/dist/commonjs/unescape.js +8 -8
- package/node_modules/glob/node_modules/minimatch/dist/commonjs/unescape.js.map +1 -1
- package/node_modules/glob/node_modules/minimatch/dist/esm/assert-valid-pattern.d.ts +1 -1
- package/node_modules/glob/node_modules/minimatch/dist/esm/assert-valid-pattern.d.ts.map +1 -1
- package/node_modules/glob/node_modules/minimatch/dist/esm/assert-valid-pattern.js.map +1 -1
- package/node_modules/glob/node_modules/minimatch/dist/esm/ast.d.ts +4 -2
- package/node_modules/glob/node_modules/minimatch/dist/esm/ast.d.ts.map +1 -1
- package/node_modules/glob/node_modules/minimatch/dist/esm/ast.js +309 -55
- package/node_modules/glob/node_modules/minimatch/dist/esm/ast.js.map +1 -1
- package/node_modules/glob/node_modules/minimatch/dist/esm/brace-expressions.d.ts.map +1 -1
- package/node_modules/glob/node_modules/minimatch/dist/esm/brace-expressions.js +2 -4
- package/node_modules/glob/node_modules/minimatch/dist/esm/brace-expressions.js.map +1 -1
- package/node_modules/glob/node_modules/minimatch/dist/esm/escape.d.ts +1 -1
- package/node_modules/glob/node_modules/minimatch/dist/esm/escape.d.ts.map +1 -1
- package/node_modules/glob/node_modules/minimatch/dist/esm/escape.js +4 -4
- package/node_modules/glob/node_modules/minimatch/dist/esm/escape.js.map +1 -1
- package/node_modules/glob/node_modules/minimatch/dist/esm/index.d.ts +81 -1
- package/node_modules/glob/node_modules/minimatch/dist/esm/index.d.ts.map +1 -1
- package/node_modules/glob/node_modules/minimatch/dist/esm/index.js +232 -134
- package/node_modules/glob/node_modules/minimatch/dist/esm/index.js.map +1 -1
- package/node_modules/glob/node_modules/minimatch/dist/esm/unescape.d.ts +1 -1
- package/node_modules/glob/node_modules/minimatch/dist/esm/unescape.d.ts.map +1 -1
- package/node_modules/glob/node_modules/minimatch/dist/esm/unescape.js +8 -8
- package/node_modules/glob/node_modules/minimatch/dist/esm/unescape.js.map +1 -1
- package/node_modules/glob/node_modules/minimatch/package.json +17 -11
- package/node_modules/glob/package.json +10 -13
- package/node_modules/minipass/LICENSE.md +55 -0
- package/node_modules/minipass/dist/commonjs/index.d.ts +12 -16
- package/node_modules/minipass/dist/commonjs/index.d.ts.map +1 -1
- package/node_modules/minipass/dist/commonjs/index.js +13 -3
- package/node_modules/minipass/dist/commonjs/index.js.map +1 -1
- package/node_modules/minipass/dist/esm/index.d.ts +12 -16
- package/node_modules/minipass/dist/esm/index.d.ts.map +1 -1
- package/node_modules/minipass/dist/esm/index.js +3 -1
- package/node_modules/minipass/dist/esm/index.js.map +1 -1
- package/node_modules/minipass/package.json +9 -14
- package/node_modules/path-scurry/node_modules/lru-cache/README.md +96 -10
- package/node_modules/path-scurry/node_modules/lru-cache/dist/commonjs/browser/diagnostics-channel-browser.d.ts.map +1 -0
- package/node_modules/path-scurry/node_modules/lru-cache/dist/commonjs/browser/diagnostics-channel-browser.js.map +1 -0
- package/node_modules/path-scurry/node_modules/lru-cache/dist/commonjs/browser/diagnostics-channel.d.ts +5 -0
- package/node_modules/path-scurry/node_modules/lru-cache/dist/commonjs/browser/diagnostics-channel.js +7 -0
- package/node_modules/path-scurry/node_modules/lru-cache/dist/commonjs/browser/index.d.ts +1400 -0
- package/node_modules/path-scurry/node_modules/lru-cache/dist/commonjs/browser/index.d.ts.map +1 -0
- package/node_modules/path-scurry/node_modules/lru-cache/dist/commonjs/browser/index.js +1726 -0
- package/node_modules/path-scurry/node_modules/lru-cache/dist/commonjs/browser/index.js.map +1 -0
- package/node_modules/path-scurry/node_modules/lru-cache/dist/commonjs/browser/index.min.js +2 -0
- package/node_modules/path-scurry/node_modules/lru-cache/dist/commonjs/browser/index.min.js.map +7 -0
- package/node_modules/path-scurry/node_modules/lru-cache/dist/commonjs/browser/perf.d.ts +12 -0
- package/node_modules/path-scurry/node_modules/lru-cache/dist/commonjs/browser/perf.d.ts.map +1 -0
- package/node_modules/path-scurry/node_modules/lru-cache/dist/commonjs/browser/perf.js +10 -0
- package/node_modules/path-scurry/node_modules/lru-cache/dist/commonjs/browser/perf.js.map +1 -0
- package/node_modules/path-scurry/node_modules/lru-cache/dist/commonjs/diagnostics-channel-cjs.cjs.map +1 -0
- package/node_modules/path-scurry/node_modules/lru-cache/dist/commonjs/diagnostics-channel-cjs.d.cts.map +1 -0
- package/node_modules/path-scurry/node_modules/lru-cache/dist/commonjs/diagnostics-channel.d.ts +5 -0
- package/node_modules/path-scurry/node_modules/lru-cache/dist/commonjs/diagnostics-channel.js +7 -0
- package/node_modules/path-scurry/node_modules/lru-cache/dist/commonjs/index.d.ts +109 -32
- package/node_modules/path-scurry/node_modules/lru-cache/dist/commonjs/index.d.ts.map +1 -1
- package/node_modules/path-scurry/node_modules/lru-cache/dist/commonjs/index.js +334 -197
- package/node_modules/path-scurry/node_modules/lru-cache/dist/commonjs/index.js.map +1 -1
- package/node_modules/path-scurry/node_modules/lru-cache/dist/commonjs/index.min.js +1 -1
- package/node_modules/path-scurry/node_modules/lru-cache/dist/commonjs/index.min.js.map +4 -4
- package/node_modules/path-scurry/node_modules/lru-cache/dist/commonjs/node/diagnostics-channel-node.d.ts.map +1 -0
- package/node_modules/path-scurry/node_modules/lru-cache/dist/commonjs/node/diagnostics-channel-node.js.map +1 -0
- package/node_modules/path-scurry/node_modules/lru-cache/dist/commonjs/node/diagnostics-channel.d.ts +5 -0
- package/node_modules/path-scurry/node_modules/lru-cache/dist/commonjs/node/diagnostics-channel.js +9 -0
- package/node_modules/path-scurry/node_modules/lru-cache/dist/commonjs/node/index.d.ts +1400 -0
- package/node_modules/path-scurry/node_modules/lru-cache/dist/commonjs/node/index.d.ts.map +1 -0
- package/node_modules/path-scurry/node_modules/lru-cache/dist/commonjs/node/index.js +1726 -0
- package/node_modules/path-scurry/node_modules/lru-cache/dist/commonjs/node/index.js.map +1 -0
- package/node_modules/path-scurry/node_modules/lru-cache/dist/commonjs/node/index.min.js +2 -0
- package/node_modules/path-scurry/node_modules/lru-cache/dist/commonjs/node/index.min.js.map +7 -0
- package/node_modules/path-scurry/node_modules/lru-cache/dist/commonjs/node/perf.d.ts +12 -0
- package/node_modules/path-scurry/node_modules/lru-cache/dist/commonjs/node/perf.d.ts.map +1 -0
- package/node_modules/path-scurry/node_modules/lru-cache/dist/commonjs/node/perf.js +10 -0
- package/node_modules/path-scurry/node_modules/lru-cache/dist/commonjs/node/perf.js.map +1 -0
- package/node_modules/path-scurry/node_modules/lru-cache/dist/commonjs/perf.d.ts +12 -0
- package/node_modules/path-scurry/node_modules/lru-cache/dist/commonjs/perf.d.ts.map +1 -0
- package/node_modules/path-scurry/node_modules/lru-cache/dist/commonjs/perf.js +10 -0
- package/node_modules/path-scurry/node_modules/lru-cache/dist/commonjs/perf.js.map +1 -0
- package/node_modules/path-scurry/node_modules/lru-cache/dist/esm/browser/diagnostics-channel-browser.d.ts.map +1 -0
- package/node_modules/path-scurry/node_modules/lru-cache/dist/esm/browser/diagnostics-channel-browser.js.map +1 -0
- package/node_modules/path-scurry/node_modules/lru-cache/dist/esm/browser/diagnostics-channel.d.ts +5 -0
- package/node_modules/path-scurry/node_modules/lru-cache/dist/esm/browser/diagnostics-channel.js +4 -0
- package/node_modules/path-scurry/node_modules/lru-cache/dist/esm/browser/index.d.ts +1400 -0
- package/node_modules/path-scurry/node_modules/lru-cache/dist/esm/browser/index.d.ts.map +1 -0
- package/node_modules/path-scurry/node_modules/lru-cache/dist/esm/browser/index.js +1722 -0
- package/node_modules/path-scurry/node_modules/lru-cache/dist/esm/browser/index.js.map +1 -0
- package/node_modules/path-scurry/node_modules/lru-cache/dist/esm/browser/index.min.js +2 -0
- package/node_modules/path-scurry/node_modules/lru-cache/dist/esm/browser/index.min.js.map +7 -0
- package/node_modules/path-scurry/node_modules/lru-cache/dist/esm/browser/perf.d.ts +12 -0
- package/node_modules/path-scurry/node_modules/lru-cache/dist/esm/browser/perf.d.ts.map +1 -0
- package/node_modules/path-scurry/node_modules/lru-cache/dist/esm/browser/perf.js +7 -0
- package/node_modules/path-scurry/node_modules/lru-cache/dist/esm/browser/perf.js.map +1 -0
- package/node_modules/path-scurry/node_modules/lru-cache/dist/esm/diagnostics-channel-esm.d.mts.map +1 -0
- package/node_modules/path-scurry/node_modules/lru-cache/dist/esm/diagnostics-channel-esm.mjs.map +1 -0
- package/node_modules/path-scurry/node_modules/lru-cache/dist/esm/diagnostics-channel.d.ts +5 -0
- package/node_modules/path-scurry/node_modules/lru-cache/dist/esm/diagnostics-channel.js +19 -0
- package/node_modules/path-scurry/node_modules/lru-cache/dist/esm/index.d.ts +109 -32
- package/node_modules/path-scurry/node_modules/lru-cache/dist/esm/index.d.ts.map +1 -1
- package/node_modules/path-scurry/node_modules/lru-cache/dist/esm/index.js +333 -196
- package/node_modules/path-scurry/node_modules/lru-cache/dist/esm/index.js.map +1 -1
- package/node_modules/path-scurry/node_modules/lru-cache/dist/esm/index.min.js +1 -1
- package/node_modules/path-scurry/node_modules/lru-cache/dist/esm/index.min.js.map +4 -4
- package/node_modules/path-scurry/node_modules/lru-cache/dist/esm/node/diagnostics-channel-node.d.ts.map +1 -0
- package/node_modules/path-scurry/node_modules/lru-cache/dist/esm/node/diagnostics-channel-node.js.map +1 -0
- package/node_modules/path-scurry/node_modules/lru-cache/dist/esm/node/diagnostics-channel.d.ts +5 -0
- package/node_modules/path-scurry/node_modules/lru-cache/dist/esm/node/diagnostics-channel.js +6 -0
- package/node_modules/path-scurry/node_modules/lru-cache/dist/esm/node/index.d.ts +1400 -0
- package/node_modules/path-scurry/node_modules/lru-cache/dist/esm/node/index.d.ts.map +1 -0
- package/node_modules/path-scurry/node_modules/lru-cache/dist/esm/node/index.js +1722 -0
- package/node_modules/path-scurry/node_modules/lru-cache/dist/esm/node/index.js.map +1 -0
- package/node_modules/path-scurry/node_modules/lru-cache/dist/esm/node/index.min.js +2 -0
- package/node_modules/path-scurry/node_modules/lru-cache/dist/esm/node/index.min.js.map +7 -0
- package/node_modules/path-scurry/node_modules/lru-cache/dist/esm/node/perf.d.ts +12 -0
- package/node_modules/path-scurry/node_modules/lru-cache/dist/esm/node/perf.d.ts.map +1 -0
- package/node_modules/path-scurry/node_modules/lru-cache/dist/esm/node/perf.js +7 -0
- package/node_modules/path-scurry/node_modules/lru-cache/dist/esm/node/perf.js.map +1 -0
- package/node_modules/path-scurry/node_modules/lru-cache/dist/esm/perf.d.ts +12 -0
- package/node_modules/path-scurry/node_modules/lru-cache/dist/esm/perf.d.ts.map +1 -0
- package/node_modules/path-scurry/node_modules/lru-cache/dist/esm/perf.js +7 -0
- package/node_modules/path-scurry/node_modules/lru-cache/dist/esm/perf.js.map +1 -0
- package/node_modules/path-scurry/node_modules/lru-cache/package.json +71 -18
- package/node_modules/path-scurry/package.json +8 -24
- package/package.json +1 -1
- package/scripts/debug-balance-probe.mjs +35 -35
- package/scripts/push-image.sh +43 -43
- package/scripts/setup-acr.sh +65 -65
- package/scripts/verify-optional-deps.js +96 -1
- package/src/__tests__/composioCliFlags.test.js +239 -239
- package/src/analyzers/CSSAnalyzer.js +298 -297
- package/src/analyzers/ConfigValidator.js +691 -690
- package/src/analyzers/ESLintAnalyzer.js +320 -320
- package/src/analyzers/JavaScriptAnalyzer.js +260 -261
- package/src/analyzers/PrettierFormatter.js +246 -247
- package/src/analyzers/PythonAnalyzer.js +283 -283
- package/src/analyzers/SecurityAnalyzer.js +729 -729
- package/src/analyzers/SparrowAnalyzer.js +341 -341
- package/src/analyzers/TypeScriptAnalyzer.js +247 -247
- package/src/analyzers/__tests__/CSSAnalyzer.test.js +41 -41
- package/src/analyzers/__tests__/ConfigValidator.test.js +362 -362
- package/src/analyzers/__tests__/JavaScriptAnalyzer.test.js +40 -40
- package/src/analyzers/__tests__/PythonAnalyzer.test.js +205 -208
- package/src/analyzers/__tests__/SecurityAnalyzer.test.js +303 -303
- package/src/analyzers/__tests__/TypeScriptAnalyzer.test.js +187 -187
- package/src/analyzers/codeCloneDetector/analyzer.js +344 -344
- package/src/analyzers/codeCloneDetector/detector.js +250 -250
- package/src/analyzers/codeCloneDetector/index.js +194 -192
- package/src/analyzers/codeCloneDetector/parser.js +199 -199
- package/src/core/__tests__/agentPool.test.js +866 -866
- package/src/core/__tests__/agentPoolAutoResume.test.js +209 -209
- package/src/core/__tests__/agentPoolWakeOnMessage.test.js +315 -315
- package/src/core/__tests__/agentScheduler.emptyResponseChatStall.test.js +213 -213
- package/src/core/__tests__/agentScheduler.errorCategorisation.test.js +246 -246
- package/src/core/__tests__/agentScheduler.firstChunkTimeout.test.js +138 -138
- package/src/core/__tests__/agentScheduler.modeTransitions.test.js +233 -233
- package/src/core/__tests__/agentScheduler.nativePromptPick.test.js +319 -319
- package/src/core/__tests__/agentScheduler.taskLifecycleInstruction.test.js +78 -78
- package/src/core/__tests__/agentScheduler.visualizer.test.js +258 -258
- package/src/core/__tests__/flowCheckpointStore.test.js +140 -140
- package/src/core/__tests__/flowEndToEnd.test.js +565 -565
- package/src/core/__tests__/flowFieldMapping.test.js +188 -189
- package/src/core/__tests__/flowLintClientMirror.test.js +96 -98
- package/src/core/__tests__/flowSavePayload.test.js +170 -169
- package/src/core/__tests__/flowTemplates.test.js +311 -311
- package/src/core/__tests__/flowVersionStore.test.js +123 -123
- package/src/core/__tests__/messageProcessor.test.js +669 -669
- package/src/core/__tests__/stateManager.test.js +0 -1
- package/src/core/agentPool.js +2474 -2475
- package/src/core/agentScheduler.js +1 -4
- package/src/core/contextManager.js +708 -708
- package/src/core/flowExecutor.js +1510 -1510
- package/src/core/flowFieldMapping.js +136 -138
- package/src/core/messageProcessor.js +953 -954
- package/src/core/orchestrator.js +593 -595
- package/src/core/stateManager.js +1765 -1752
- package/src/index.js +1221 -1221
- package/src/interfaces/__tests__/archivedAgentDelete.test.js +207 -207
- package/src/interfaces/__tests__/bulkAgentRoute.test.js +361 -361
- package/src/interfaces/__tests__/imageServing.test.js +228 -228
- package/src/interfaces/__tests__/remoteSessionAuth.test.js +308 -308
- package/src/interfaces/__tests__/videoJobsRoutes.test.js +178 -179
- package/src/interfaces/__tests__/webServer.marketplace.test.js +629 -629
- package/src/interfaces/schedulerRoutes.js +50 -50
- package/src/interfaces/terminal/__tests__/smoke/connection.test.js +341 -350
- package/src/interfaces/terminal/__tests__/smoke/enhancements.test.js +156 -156
- package/src/interfaces/terminal/__tests__/smoke/imports.test.js +325 -330
- package/src/interfaces/terminal/__tests__/smoke/tools.test.js +385 -388
- package/src/interfaces/terminal/api/session.js +265 -266
- package/src/interfaces/terminal/api/websocket.js +496 -497
- package/src/interfaces/terminal/components/AgentCreator.js +691 -705
- package/src/interfaces/terminal/components/AgentEditor.js +676 -678
- package/src/interfaces/terminal/components/AgentSwitcher.js +331 -330
- package/src/interfaces/terminal/components/ErrorPanel.js +263 -264
- package/src/interfaces/terminal/components/Header.js +28 -28
- package/src/interfaces/terminal/components/Layout.js +598 -603
- package/src/interfaces/terminal/components/MessageList.js +280 -281
- package/src/interfaces/terminal/components/SettingsPanel.js +410 -415
- package/src/interfaces/terminal/components/StatusBar.js +2 -0
- package/src/interfaces/terminal/index.js +168 -168
- package/src/interfaces/terminal/state/useAgentControl.js +496 -496
- package/src/interfaces/terminal/state/useAgents.js +537 -537
- package/src/interfaces/terminal/state/useMessages.js +629 -630
- package/src/interfaces/terminal/state/useTools.js +554 -554
- package/src/interfaces/terminal/utils/debugLogger.js +44 -44
- package/src/interfaces/terminal/utils/settingsStorage.js +232 -232
- package/src/interfaces/webServer.js +7578 -7579
- package/src/interfaces/webServer.js.bak +7046 -7046
- package/src/modules/fileExplorer/__tests__/zipDownload.test.js +237 -237
- package/src/modules/fileExplorer/controller.js +470 -469
- package/src/modules/fileExplorer/routes.js +285 -286
- package/src/modules/widget/__tests__/isDisabled.test.js +41 -41
- package/src/modules/widget/__tests__/routes.test.js +677 -678
- package/src/modules/widget/__tests__/runtime.test.js +401 -401
- package/src/modules/widget/__tests__/versioning.test.js +309 -309
- package/src/modules/widget/__tests__/webComponentRuntime.test.js +565 -565
- package/src/modules/widget/__tests__/widgetTool.test.js +316 -316
- package/src/modules/widget/routes.js +435 -435
- package/src/modules/widget/runtime/bundle.js +640 -640
- package/src/modules/widget/runtime/webComponentBundle.js +470 -470
- package/src/modules/widget/schema.js +182 -181
- package/src/modules/widget/widgetTool.js +1389 -1389
- package/src/services/__tests__/agentActivityService.test.js +401 -402
- package/src/services/__tests__/benchmarkService.test.js +184 -184
- package/src/services/__tests__/contextInjectionService.test.js +246 -246
- package/src/services/__tests__/conversationQuery.test.js +721 -723
- package/src/services/__tests__/credentialVault.test.js +469 -469
- package/src/services/__tests__/discordService.integration.test.js +638 -639
- package/src/services/__tests__/flowContextService.test.js +590 -590
- package/src/services/__tests__/memoryService.test.js +1 -1
- package/src/services/__tests__/messageSource.test.js +380 -380
- package/src/services/__tests__/modelRouterNaming.test.js +111 -111
- package/src/services/__tests__/projectDetector.test.js +34 -34
- package/src/services/__tests__/promptService.test.js +242 -242
- package/src/services/__tests__/telegramService.test.js +941 -941
- package/src/services/__tests__/tokenCountingService.test.js +48 -48
- package/src/services/agentActivityService.js +419 -420
- package/src/services/aiService.js +2997 -3001
- package/src/services/apiKeyManager.js +359 -359
- package/src/services/benchmarkService.js +196 -196
- package/src/services/codebaseKnowledgeService.js +2 -2
- package/src/services/composioService.js +738 -738
- package/src/services/conversationCompactionService.js +1258 -1257
- package/src/services/credentialVault.js +685 -685
- package/src/services/discordService.js +792 -793
- package/src/services/embeddings/__tests__/azureCustomProvider.test.js +232 -232
- package/src/services/embeddings/__tests__/embeddingService.test.js +417 -417
- package/src/services/embeddings/__tests__/localProvider.test.js +263 -263
- package/src/services/embeddings/autoRecall.js +218 -219
- package/src/services/embeddings/indexers/__tests__/agentIndexer.test.js +232 -232
- package/src/services/embeddings/indexers/__tests__/memoryIndexer.test.js +418 -418
- package/src/services/embeddings/indexers/__tests__/reminisceIndexer.test.js +356 -357
- package/src/services/embeddings/indexers/__tests__/skillsIndexer.test.js +145 -145
- package/src/services/embeddings/indexers/__tests__/taskIndexer.test.js +146 -146
- package/src/services/embeddings/indexers/composioIndexer.js +279 -279
- package/src/services/embeddings/providerInterface.js +206 -206
- package/src/services/embeddings/providers/localProvider.js +11 -7
- package/src/services/embeddings/providers/openaiProvider.js +101 -101
- package/src/services/embeddings/vectorStore/inMemoryJsonStore.js +356 -356
- package/src/services/errorHandler.js +809 -809
- package/src/services/flowContextService.js +586 -586
- package/src/services/grounding/MockAdapter.js +125 -125
- package/src/services/modelRouterService.js +26 -31
- package/src/services/modelsService.js +322 -322
- package/src/services/ollamaService.js +452 -452
- package/src/services/projectDetector.js +403 -404
- package/src/services/promptService.js +418 -418
- package/src/services/qualityInspector.js +795 -795
- package/src/services/scheduleService.js +726 -726
- package/src/services/serviceRegistry.js +386 -386
- package/src/services/telegrafBot.js +174 -174
- package/src/services/telegramService.js +1972 -1972
- package/src/services/visualEditorBridge.js +1033 -1033
- package/src/services/visualEditorServer.js +1769 -1774
- package/src/services/whatsappService.js +667 -668
- package/src/tools/__tests__/agentCommunicationTool.findAgent.test.js +226 -226
- package/src/tools/__tests__/agentCommunicationTool.test.js +3 -3
- package/src/tools/__tests__/agentDelayTool.test.js +342 -342
- package/src/tools/__tests__/baseTool.test.js +3 -3
- package/src/tools/__tests__/codeMapTool.test.js +915 -915
- package/src/tools/__tests__/fileContentReplaceTool.test.js +309 -309
- package/src/tools/__tests__/fileTreeTool.test.js +274 -274
- package/src/tools/__tests__/filesystemTool.test.js +815 -815
- package/src/tools/__tests__/foundryWebSearchTool.test.js +252 -252
- package/src/tools/__tests__/imageTool.validator.test.js +194 -194
- package/src/tools/__tests__/jobDoneTool.test.js +580 -581
- package/src/tools/__tests__/memoryTool.forgetStale.test.js +272 -272
- package/src/tools/__tests__/memoryTool.reminisce.test.js +2 -2
- package/src/tools/__tests__/memoryTool.reminisceSemanticSearch.test.js +301 -301
- package/src/tools/__tests__/memoryTool.semanticSearch.test.js +405 -405
- package/src/tools/__tests__/memoryTool.teamPool.test.js +293 -293
- package/src/tools/__tests__/memoryTool.test.js +1 -1
- package/src/tools/__tests__/seekTool.test.js +282 -282
- package/src/tools/__tests__/skillsTool.search.test.js +164 -164
- package/src/tools/__tests__/skillsTool.test.js +226 -226
- package/src/tools/__tests__/staticAnalysisTool.test.js +509 -509
- package/src/tools/__tests__/taskManagerTool.discipline.test.js +137 -137
- package/src/tools/__tests__/taskManagerTool.search.test.js +143 -143
- package/src/tools/__tests__/taskManagerTool.test.js +866 -866
- package/src/tools/__tests__/terminalTool.test.js +448 -448
- package/src/tools/__tests__/toolShapeForgiveness.test.js +259 -260
- package/src/tools/__tests__/userPromptTool.test.js +297 -297
- package/src/tools/__tests__/videoTool.jobs.test.js +147 -147
- package/src/tools/__tests__/webTool.e2e.test.js +609 -603
- package/src/tools/__tests__/webTool.unit.test.js +195 -195
- package/src/tools/__tests__/webTool.visionModel.test.js +75 -75
- package/src/tools/agentCommunicationTool.js +8 -10
- package/src/tools/agentDelayTool.js +496 -497
- package/src/tools/asyncToolManager.js +602 -603
- package/src/tools/baseTool.js +12 -11
- package/src/tools/cloneDetectionTool.js +576 -581
- package/src/tools/codeMapTool.js +0 -6
- package/src/tools/composioTool.js +617 -617
- package/src/tools/dependencyResolverTool.js +1211 -1212
- package/src/tools/desktop/DesktopTool.js +629 -638
- package/src/tools/desktop/__tests__/DesktopTool.e2e.test.js +306 -306
- package/src/tools/desktop/__tests__/DesktopTool.test.js +507 -507
- package/src/tools/desktop/__tests__/osController.test.js +364 -364
- package/src/tools/desktop/osController.js +491 -491
- package/src/tools/docxTool.js +623 -623
- package/src/tools/excelTool.js +636 -636
- package/src/tools/fileContentReplaceTool.js +5 -7
- package/src/tools/fileSystemTool.js +12 -19
- package/src/tools/fileTreeTool.js +840 -840
- package/src/tools/foundryWebSearchTool.js +273 -273
- package/src/tools/helpTool.js +198 -198
- package/src/tools/imageTool.js +1397 -1397
- package/src/tools/importAnalyzerTool.js +1056 -1056
- package/src/tools/jobDoneTool.js +495 -495
- package/src/tools/memoryTool.js +1 -1
- package/src/tools/office/pres/__tests__/presSystem.test.js +365 -365
- package/src/tools/office/pres/archetypes/agenda.js +61 -61
- package/src/tools/office/pres/archetypes/bentoGrid.js +218 -219
- package/src/tools/office/pres/archetypes/bigStat.js +140 -142
- package/src/tools/office/pres/archetypes/closing.js +70 -70
- package/src/tools/office/pres/archetypes/hero.js +70 -70
- package/src/tools/office/pres/archetypes/productHero.js +93 -94
- package/src/tools/office/pres/archetypes/table.js +73 -74
- package/src/tools/office/pres/backgrounds/orb.js +66 -66
- package/src/tools/office/pres/components.js +422 -423
- package/src/tools/officeTool.js +441 -441
- package/src/tools/pdfTool.js +625 -627
- package/src/tools/platformControlTool.js +1081 -1081
- package/src/tools/seekTool.js +917 -918
- package/src/tools/skillsTool.js +1 -1
- package/src/tools/staticAnalysisTool.js +2143 -2146
- package/src/tools/taskManagerTool.js +3324 -3324
- package/src/tools/terminalTool.js +2615 -2618
- package/src/tools/videoTool.js +1303 -1303
- package/src/tools/visionTool.js +508 -508
- package/src/tools/visualEditorTool.js +1289 -1290
- package/src/tools/webTool.js +3368 -3368
- package/src/tools/whatsappTool.js +464 -464
- package/src/types/__tests__/agent.test.js +499 -499
- package/src/types/__tests__/contextReference.test.js +606 -606
- package/src/types/__tests__/conversation.test.js +555 -555
- package/src/types/__tests__/toolCommand.test.js +584 -584
- package/src/types/contextReference.js +974 -971
- package/src/types/conversation.js +729 -729
- package/src/types/toolCommand.js +746 -746
- package/src/utilities/__tests__/attachmentValidator.test.js +80 -80
- package/src/utilities/__tests__/auditReport.test.js +328 -328
- package/src/utilities/__tests__/directoryAccessManager.test.js +388 -388
- package/src/utilities/__tests__/jsonRepair.test.js +103 -104
- package/src/utilities/__tests__/modeTransitionReasons.test.js +105 -105
- package/src/utilities/__tests__/platformUtils.test.js +80 -87
- package/src/utilities/__tests__/structuredFileValidator.test.js +261 -263
- package/src/utilities/__tests__/toolConstants.test.js +92 -94
- package/src/utilities/__tests__/useIsTouchDevice.detect.test.js +114 -114
- package/src/utilities/__tests__/webUiUtilSync.test.js +117 -117
- package/src/utilities/attachmentValidator.js +284 -288
- package/src/utilities/authCache.js.backup-1779570472481 +121 -121
- package/src/utilities/browserStealth.js +631 -630
- package/src/utilities/configManager.js +616 -617
- package/src/utilities/directoryAccessManager.js +564 -565
- package/src/utilities/fileProcessor.js +308 -307
- package/src/utilities/humanBehavior.js +454 -453
- package/src/utilities/logger.js +479 -479
- package/src/utilities/structuredFileValidator.js +696 -699
- package/src/utilities/tagParser.js +5 -10
- package/src/utilities/userDataDir.js +308 -308
- package/node_modules/@isaacs/brace-expansion/dist/commonjs/index.js.map +0 -1
- package/node_modules/@isaacs/brace-expansion/dist/esm/index.js.map +0 -1
- package/node_modules/minipass/LICENSE +0 -15
- /package/node_modules/{@isaacs → glob/node_modules}/balanced-match/LICENSE.md +0 -0
- /package/node_modules/{@isaacs → glob/node_modules}/balanced-match/dist/commonjs/index.d.ts +0 -0
- /package/node_modules/{@isaacs → glob/node_modules}/balanced-match/dist/commonjs/index.d.ts.map +0 -0
- /package/node_modules/{@isaacs → glob/node_modules}/balanced-match/dist/commonjs/index.js +0 -0
- /package/node_modules/{@isaacs → glob/node_modules}/balanced-match/dist/commonjs/index.js.map +0 -0
- /package/node_modules/{@isaacs → glob/node_modules}/balanced-match/dist/commonjs/package.json +0 -0
- /package/node_modules/{@isaacs → glob/node_modules}/balanced-match/dist/esm/index.d.ts +0 -0
- /package/node_modules/{@isaacs → glob/node_modules}/balanced-match/dist/esm/index.d.ts.map +0 -0
- /package/node_modules/{@isaacs → glob/node_modules}/balanced-match/dist/esm/index.js +0 -0
- /package/node_modules/{@isaacs → glob/node_modules}/balanced-match/dist/esm/index.js.map +0 -0
- /package/node_modules/{@isaacs → glob/node_modules}/balanced-match/dist/esm/package.json +0 -0
- /package/node_modules/{@isaacs → glob/node_modules}/brace-expansion/LICENSE +0 -0
- /package/node_modules/{@isaacs → glob/node_modules}/brace-expansion/dist/commonjs/index.d.ts +0 -0
- /package/node_modules/{@isaacs → glob/node_modules}/brace-expansion/dist/commonjs/index.d.ts.map +0 -0
- /package/node_modules/{@isaacs → glob/node_modules}/brace-expansion/dist/commonjs/package.json +0 -0
- /package/node_modules/{@isaacs → glob/node_modules}/brace-expansion/dist/esm/index.d.ts +0 -0
- /package/node_modules/{@isaacs → glob/node_modules}/brace-expansion/dist/esm/index.d.ts.map +0 -0
- /package/node_modules/{@isaacs → glob/node_modules}/brace-expansion/dist/esm/package.json +0 -0
|
@@ -1,418 +1,418 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* MemoryIndexer tests.
|
|
3
|
-
*
|
|
4
|
-
* Strategy: build a small EmbeddingService against the real VectorStore
|
|
5
|
-
* (in temp dir) with a stub provider that produces deterministic vectors
|
|
6
|
-
* based on text contents. Lets us exercise the FULL indexer path
|
|
7
|
-
* (embed → upsert → query → hybrid fusion) without any HTTP or ONNX.
|
|
8
|
-
*
|
|
9
|
-
* Pins:
|
|
10
|
-
* - isEnabled reflects the underlying service
|
|
11
|
-
* - indexOne / deleteOne / indexMany / backfill / search round-trip
|
|
12
|
-
* - Failures from the underlying embed call are swallowed with a warn
|
|
13
|
-
* (best-effort policy — memory writes never break)
|
|
14
|
-
* - memoryToText packing order: title → description → content
|
|
15
|
-
* - Empty / invalid memories are skipped (not failed)
|
|
16
|
-
* - hybrid:true is the default and surfaces substring-only matches
|
|
17
|
-
*/
|
|
18
|
-
|
|
19
|
-
import { describe, test, expect, jest, beforeEach, afterEach } from '@jest/globals';
|
|
20
|
-
import os from 'os';
|
|
21
|
-
import fsp from 'fs/promises';
|
|
22
|
-
import path from 'path';
|
|
23
|
-
import { MemoryIndexer, memoryToText } from '../memoryIndexer.js';
|
|
24
|
-
import { EmbeddingService } from '../../embeddingService.js';
|
|
25
|
-
import { EmbeddingProvider } from '../../providerInterface.js';
|
|
26
|
-
|
|
27
|
-
/**
|
|
28
|
-
* Stub provider that produces deterministic, recognisable vectors so we
|
|
29
|
-
* can assert ordering by content. Each text gets a vector dominated by
|
|
30
|
-
* a "fingerprint" position derived from the text — different texts get
|
|
31
|
-
* different fingerprints so the inner-product ordering matches text
|
|
32
|
-
* identity.
|
|
33
|
-
*/
|
|
34
|
-
class StubProvider extends EmbeddingProvider {
|
|
35
|
-
constructor(dimensions = 8) {
|
|
36
|
-
super();
|
|
37
|
-
this._dimensions = dimensions;
|
|
38
|
-
this.calls = [];
|
|
39
|
-
}
|
|
40
|
-
getInfo() {
|
|
41
|
-
return {
|
|
42
|
-
name: 'azure',
|
|
43
|
-
modelId: 'stub-mem',
|
|
44
|
-
dimensions: this._dimensions,
|
|
45
|
-
maxBatchSize: 16,
|
|
46
|
-
maxTokensPerText: 1000,
|
|
47
|
-
distinguishesQueryAndDocument: false,
|
|
48
|
-
isLocal: false,
|
|
49
|
-
};
|
|
50
|
-
}
|
|
51
|
-
getModelFingerprint() { return 'stub-mem:v1'; }
|
|
52
|
-
async _embed(texts, kind) {
|
|
53
|
-
this.calls.push({ texts: [...texts], kind });
|
|
54
|
-
return texts.map(t => {
|
|
55
|
-
// Lay one nonzero element per text — position derived from the
|
|
56
|
-
// alphabetic content. Mixed-content texts get a unique fingerprint.
|
|
57
|
-
const idx = (t.charCodeAt(0) || 0) % this._dimensions;
|
|
58
|
-
const vec = Array(this._dimensions).fill(0);
|
|
59
|
-
vec[idx] = 1;
|
|
60
|
-
// Add a small tail so identical first-chars still differ.
|
|
61
|
-
vec[(idx + 1) % this._dimensions] = (t.length % 7) * 0.1;
|
|
62
|
-
return vec;
|
|
63
|
-
});
|
|
64
|
-
}
|
|
65
|
-
}
|
|
66
|
-
|
|
67
|
-
function makeIndexer({ tmpDir, enabled = true } = {}) {
|
|
68
|
-
const apiKeyManager = { getEmbeddingApiKey: jest.fn(() => 'stub-key') };
|
|
69
|
-
if (!enabled) {
|
|
70
|
-
const svc = new EmbeddingService({
|
|
71
|
-
apiKeyManager,
|
|
72
|
-
baseUrl: 'https://x',
|
|
73
|
-
stateDir: tmpDir,
|
|
74
|
-
});
|
|
75
|
-
return new MemoryIndexer({ embeddingService: svc });
|
|
76
|
-
}
|
|
77
|
-
const provider = new StubProvider();
|
|
78
|
-
const svc = new EmbeddingService({
|
|
79
|
-
config: { provider: 'azure' },
|
|
80
|
-
apiKeyManager,
|
|
81
|
-
baseUrl: 'https://x',
|
|
82
|
-
stateDir: tmpDir,
|
|
83
|
-
providerOverrides: { azure: () => provider },
|
|
84
|
-
});
|
|
85
|
-
return { indexer: new MemoryIndexer({ embeddingService: svc }), svc, provider };
|
|
86
|
-
}
|
|
87
|
-
|
|
88
|
-
describe('memoryToText', () => {
|
|
89
|
-
test('packs title → description → content', () => {
|
|
90
|
-
const t = memoryToText({ title: 'T', description: 'D', content: 'C' });
|
|
91
|
-
expect(t).toBe('T\n\nD\n\nC');
|
|
92
|
-
});
|
|
93
|
-
|
|
94
|
-
test('drops empty fields (no double newlines from missing pieces)', () => {
|
|
95
|
-
expect(memoryToText({ title: 'T', content: 'C' })).toBe('T\n\nC');
|
|
96
|
-
expect(memoryToText({ content: 'C' })).toBe('C');
|
|
97
|
-
expect(memoryToText({})).toBe('');
|
|
98
|
-
expect(memoryToText(null)).toBe('');
|
|
99
|
-
});
|
|
100
|
-
});
|
|
101
|
-
|
|
102
|
-
describe('MemoryIndexer — disabled path', () => {
|
|
103
|
-
let tmpDir;
|
|
104
|
-
beforeEach(async () => { tmpDir = await fsp.mkdtemp(path.join(os.tmpdir(), 'loxia-memidx-')); });
|
|
105
|
-
afterEach(async () => { await fsp.rm(tmpDir, { recursive: true, force: true }); });
|
|
106
|
-
|
|
107
|
-
test('isEnabled is false when service is disabled', () => {
|
|
108
|
-
const idx = makeIndexer({ tmpDir, enabled: false });
|
|
109
|
-
expect(idx.isEnabled).toBe(false);
|
|
110
|
-
});
|
|
111
|
-
|
|
112
|
-
test('indexOne returns {indexed:false, reason:"disabled"}', async () => {
|
|
113
|
-
const idx = makeIndexer({ tmpDir, enabled: false });
|
|
114
|
-
const r = await idx.indexOne('a1', { id: 'm1', title: 'hi' });
|
|
115
|
-
expect(r).toEqual({ indexed: false, reason: 'disabled' });
|
|
116
|
-
});
|
|
117
|
-
|
|
118
|
-
test('search returns {enabled:false, hits:[]}', async () => {
|
|
119
|
-
const idx = makeIndexer({ tmpDir, enabled: false });
|
|
120
|
-
const r = await idx.search('a1', 'hello');
|
|
121
|
-
expect(r).toEqual({ enabled: false, hits: [] });
|
|
122
|
-
});
|
|
123
|
-
|
|
124
|
-
test('backfill returns {ok:false, reason:"disabled"}', async () => {
|
|
125
|
-
const idx = makeIndexer({ tmpDir, enabled: false });
|
|
126
|
-
const r = await idx.backfill('a1', [{ id: 'm', title: 't', content: 'c' }]);
|
|
127
|
-
expect(r).toEqual({ ok: false, reason: 'disabled' });
|
|
128
|
-
});
|
|
129
|
-
});
|
|
130
|
-
|
|
131
|
-
describe('MemoryIndexer — round-trip', () => {
|
|
132
|
-
let tmpDir, indexer,
|
|
133
|
-
beforeEach(async () => {
|
|
134
|
-
tmpDir = await fsp.mkdtemp(path.join(os.tmpdir(), 'loxia-memidx-'));
|
|
135
|
-
({ indexer,
|
|
136
|
-
});
|
|
137
|
-
afterEach(async () => { await fsp.rm(tmpDir, { recursive: true, force: true }); });
|
|
138
|
-
|
|
139
|
-
test('indexOne + search: the inserted memory becomes findable', async () => {
|
|
140
|
-
await indexer.indexOne('a1', {
|
|
141
|
-
id: 'm1', title: 'Telegram dedup', content: 'message floods', createdAt: '2026-05-25T12:00:00Z',
|
|
142
|
-
});
|
|
143
|
-
const r = await indexer.search('a1', 'Telegram dedup');
|
|
144
|
-
expect(r.enabled).toBe(true);
|
|
145
|
-
expect(r.hits.some(h => h.id === 'm1')).toBe(true);
|
|
146
|
-
});
|
|
147
|
-
|
|
148
|
-
test('indexOne with empty memory returns reason:"empty-text"', async () => {
|
|
149
|
-
const r = await indexer.indexOne('a1', { id: 'm-empty' });
|
|
150
|
-
expect(r).toEqual({ indexed: false, reason: 'empty-text' });
|
|
151
|
-
});
|
|
152
|
-
|
|
153
|
-
test('indexOne with invalid input returns reason:"invalid-input"', async () => {
|
|
154
|
-
expect(await indexer.indexOne(null, { id: 'm' })).toMatchObject({ reason: 'invalid-input' });
|
|
155
|
-
expect(await indexer.indexOne('a1', null)).toMatchObject({ reason: 'invalid-input' });
|
|
156
|
-
expect(await indexer.indexOne('a1', {})).toMatchObject({ reason: 'invalid-input' });
|
|
157
|
-
});
|
|
158
|
-
|
|
159
|
-
test('deleteOne removes the memory from the index', async () => {
|
|
160
|
-
await indexer.indexOne('a1', { id: 'm1', title: 'first', content: 'one' });
|
|
161
|
-
await indexer.indexOne('a1', { id: 'm2', title: 'second', content: 'two' });
|
|
162
|
-
const before = await indexer.search('a1', 'first');
|
|
163
|
-
expect(before.hits.some(h => h.id === 'm1')).toBe(true);
|
|
164
|
-
await indexer.deleteOne('a1', 'm1');
|
|
165
|
-
const after = await indexer.search('a1', 'first');
|
|
166
|
-
expect(after.hits.some(h => h.id === 'm1')).toBe(false);
|
|
167
|
-
});
|
|
168
|
-
|
|
169
|
-
test('indexMany batches memories and counts skipped', async () => {
|
|
170
|
-
const memories = [
|
|
171
|
-
{ id: 'm1', title: 'alpha', content: 'one' },
|
|
172
|
-
{ id: 'm2', title: 'beta', content: 'two' },
|
|
173
|
-
{ id: 'm3' }, // no text → skip
|
|
174
|
-
{ title: 'no id' }, // no id → skip
|
|
175
|
-
];
|
|
176
|
-
const result = await indexer.indexMany('a1', memories);
|
|
177
|
-
expect(result.indexed).toBe(2);
|
|
178
|
-
expect(result.skipped).toBe(2);
|
|
179
|
-
expect(result.failed).toBe(0);
|
|
180
|
-
});
|
|
181
|
-
|
|
182
|
-
test('indexMany sends a single batched embed call', async () => {
|
|
183
|
-
const memories = Array.from({ length: 5 }, (_, i) => ({
|
|
184
|
-
id: `m${i}`, title: `t${i}`, content: `c${i}`,
|
|
185
|
-
}));
|
|
186
|
-
await indexer.indexMany('a1', memories);
|
|
187
|
-
expect(provider.calls).toHaveLength(1); // one batched call
|
|
188
|
-
expect(provider.calls[0].texts).toHaveLength(5);
|
|
189
|
-
});
|
|
190
|
-
|
|
191
|
-
test('backfill replaces the entire index', async () => {
|
|
192
|
-
await indexer.indexOne('a1', { id: 'stale', title: 'old', content: 'old' });
|
|
193
|
-
const replacement = [
|
|
194
|
-
{ id: 'new1', title: 'fresh', content: 'one' },
|
|
195
|
-
{ id: 'new2', title: 'newer', content: 'two' },
|
|
196
|
-
];
|
|
197
|
-
const result = await indexer.backfill('a1', replacement);
|
|
198
|
-
expect(result.ok).toBe(true);
|
|
199
|
-
expect(result.indexed).toBe(2);
|
|
200
|
-
|
|
201
|
-
const search = await indexer.search('a1', 'fresh');
|
|
202
|
-
expect(search.hits.some(h => h.id === 'new1')).toBe(true);
|
|
203
|
-
expect(search.hits.some(h => h.id === 'stale')).toBe(false);
|
|
204
|
-
});
|
|
205
|
-
|
|
206
|
-
test('per-agent isolation: search on a1 does NOT return a2 memories', async () => {
|
|
207
|
-
await indexer.indexOne('a1', { id: 'm1', title: 'agent1-thing', content: 'x' });
|
|
208
|
-
await indexer.indexOne('a2', { id: 'm1', title: 'agent2-thing', content: 'y' });
|
|
209
|
-
const r1 = await indexer.search('a1', 'thing');
|
|
210
|
-
expect(r1.hits.length).toBeGreaterThan(0);
|
|
211
|
-
expect(r1.hits.every(h => h.metadata?.title === 'agent1-thing')).toBe(true);
|
|
212
|
-
});
|
|
213
|
-
|
|
214
|
-
test('hybrid search by default surfaces substring-only matches', async () => {
|
|
215
|
-
// Memory whose semantic vector won't match (stub generates by first char)
|
|
216
|
-
// but whose substring DOES match the query.
|
|
217
|
-
await indexer.indexOne('a1', {
|
|
218
|
-
id: 'm-sub',
|
|
219
|
-
title: 'arbitrary title',
|
|
220
|
-
content: 'this body mentions UNIQUE_TOKEN_X explicitly',
|
|
221
|
-
});
|
|
222
|
-
const r = await indexer.search('a1', 'UNIQUE_TOKEN_X');
|
|
223
|
-
expect(r.hits.some(h => h.id === 'm-sub')).toBe(true);
|
|
224
|
-
});
|
|
225
|
-
|
|
226
|
-
test('recency boost ranks fresh memories above stale ones on tie', async () => {
|
|
227
|
-
// Two memories with the same text → identical cosine. The fresh
|
|
228
|
-
// one's createdAt should make it rank first by default.
|
|
229
|
-
await indexer.indexOne('a1', {
|
|
230
|
-
id: 'fresh', title: 'shared', content: 'shared body',
|
|
231
|
-
createdAt: new Date(Date.now() - 1 * 24 * 60 * 60 * 1000).toISOString(),
|
|
232
|
-
});
|
|
233
|
-
await indexer.indexOne('a1', {
|
|
234
|
-
id: 'stale', title: 'shared', content: 'shared body',
|
|
235
|
-
createdAt: new Date(Date.now() - 200 * 24 * 60 * 60 * 1000).toISOString(),
|
|
236
|
-
});
|
|
237
|
-
const r = await indexer.search('a1', 'shared body');
|
|
238
|
-
expect(r.hits.length).toBeGreaterThanOrEqual(2);
|
|
239
|
-
const freshIdx = r.hits.findIndex(h => h.id === 'fresh');
|
|
240
|
-
const staleIdx = r.hits.findIndex(h => h.id === 'stale');
|
|
241
|
-
expect(freshIdx).toBeLessThan(staleIdx);
|
|
242
|
-
});
|
|
243
|
-
|
|
244
|
-
test('boost:false disables recency adjustment (raw cosine ranking)', async () => {
|
|
245
|
-
await indexer.indexOne('a1', { id: 'fresh', title: 'foo', content: 'foo' });
|
|
246
|
-
await indexer.indexOne('a1', { id: 'stale', title: 'foo', content: 'foo',
|
|
247
|
-
createdAt: new Date(Date.now() - 365 * 24 * 60 * 60 * 1000).toISOString() });
|
|
248
|
-
// With boost off, identical text → effectively identical scores; the
|
|
249
|
-
// order may be insertion-dependent but neither should be massively
|
|
250
|
-
// pulled away by recency.
|
|
251
|
-
const r = await indexer.search('a1', 'foo', { boost: false });
|
|
252
|
-
const scores = r.hits.map(h => h.score);
|
|
253
|
-
expect(Math.max(...scores) - Math.min(...scores)).toBeLessThan(0.05);
|
|
254
|
-
});
|
|
255
|
-
|
|
256
|
-
test('hybrid:false disables substring fusion', async () => {
|
|
257
|
-
await indexer.indexOne('a1', {
|
|
258
|
-
id: 'm-sub',
|
|
259
|
-
title: 'arbitrary',
|
|
260
|
-
content: 'this body mentions UNIQUE_TOKEN_X',
|
|
261
|
-
});
|
|
262
|
-
// Pure-semantic with stub provider and a wildly different query —
|
|
263
|
-
// we get nothing useful, but the call shouldn't throw.
|
|
264
|
-
const r = await indexer.search('a1', 'UNIQUE_TOKEN_X', { hybrid: false });
|
|
265
|
-
expect(r.enabled).toBe(true);
|
|
266
|
-
expect(Array.isArray(r.hits)).toBe(true);
|
|
267
|
-
});
|
|
268
|
-
});
|
|
269
|
-
|
|
270
|
-
describe('MemoryIndexer — team-pool (Phase 6c)', () => {
|
|
271
|
-
let tmpDir, indexer, svc;
|
|
272
|
-
beforeEach(async () => {
|
|
273
|
-
tmpDir = await fsp.mkdtemp(path.join(os.tmpdir(), 'loxia-memidx-team-'));
|
|
274
|
-
({ indexer, svc } = makeIndexer({ tmpDir }));
|
|
275
|
-
});
|
|
276
|
-
afterEach(async () => { await fsp.rm(tmpDir, { recursive: true, force: true }); });
|
|
277
|
-
|
|
278
|
-
test('publishToTeam stores under the team scope path', async () => {
|
|
279
|
-
await indexer.publishToTeam('team-A', {
|
|
280
|
-
id: 'mem-1', title: 'Useful note', content: 'shared knowledge',
|
|
281
|
-
}, 'agent-publisher');
|
|
282
|
-
const store = await svc.getStore({ teamId: 'team-A', surface: 'memory' });
|
|
283
|
-
expect(store._filePath).toMatch(/[\\/]teams[\\/]team-A[\\/]memory\.vec\.json$/);
|
|
284
|
-
expect((await store.stats()).count).toBe(1);
|
|
285
|
-
});
|
|
286
|
-
|
|
287
|
-
test('publishToTeam records publishedBy + publishedAt in metadata', async () => {
|
|
288
|
-
await indexer.publishToTeam('team-A', {
|
|
289
|
-
id: 'mem-1', title: 't', content: 'c',
|
|
290
|
-
}, 'agent-publisher');
|
|
291
|
-
const store = await svc.getStore({ teamId: 'team-A', surface: 'memory' });
|
|
292
|
-
const row = store._rows[0];
|
|
293
|
-
expect(row.metadata.publishedBy).toBe('agent-publisher');
|
|
294
|
-
expect(row.metadata.publishedAt).toBeTruthy();
|
|
295
|
-
expect(typeof row.metadata.publishedAt).toBe('string');
|
|
296
|
-
});
|
|
297
|
-
|
|
298
|
-
test('disabled service: publishToTeam returns {indexed:false, reason:"disabled"}', async () => {
|
|
299
|
-
// makeIndexer({enabled:false}) returns the bare indexer (no destructure).
|
|
300
|
-
const disabled = makeIndexer({ tmpDir, enabled: false });
|
|
301
|
-
const r = await disabled.publishToTeam('team-A', { id: 'mem-1', title: 't', content: 'c' }, 'agent');
|
|
302
|
-
expect(r).toEqual({ indexed: false, reason: 'disabled' });
|
|
303
|
-
});
|
|
304
|
-
|
|
305
|
-
test('invalid input (missing teamId or memory.id) → invalid-input', async () => {
|
|
306
|
-
const r1 = await indexer.publishToTeam(null, { id: 'm', title: 't', content: 'c' }, 'a');
|
|
307
|
-
const r2 = await indexer.publishToTeam('team-A', {}, 'a');
|
|
308
|
-
expect(r1.reason).toBe('invalid-input');
|
|
309
|
-
expect(r2.reason).toBe('invalid-input');
|
|
310
|
-
});
|
|
311
|
-
|
|
312
|
-
test('searchTeam returns hits from the team pool only (per-team isolation)', async () => {
|
|
313
|
-
await indexer.publishToTeam('team-A', { id: 'a1', title: 'alpha', content: 'foo' }, 'agent');
|
|
314
|
-
await indexer.publishToTeam('team-B', { id: 'b1', title: 'beta', content: 'bar' }, 'agent');
|
|
315
|
-
const rA = await indexer.searchTeam('team-A', 'alpha');
|
|
316
|
-
const rB = await indexer.searchTeam('team-B', 'beta');
|
|
317
|
-
expect(rA.hits.some(h => h.id === 'a1')).toBe(true);
|
|
318
|
-
expect(rA.hits.some(h => h.id === 'b1')).toBe(false);
|
|
319
|
-
expect(rB.hits.some(h => h.id === 'b1')).toBe(true);
|
|
320
|
-
expect(rB.hits.some(h => h.id === 'a1')).toBe(false);
|
|
321
|
-
});
|
|
322
|
-
|
|
323
|
-
test('searchTeam respects topK + hybrid + boost flags', async () => {
|
|
324
|
-
for (let i = 0; i < 5; i++) {
|
|
325
|
-
await indexer.publishToTeam('team-A', { id: `m${i}`, title: `t${i}`, content: 'shared content' }, 'agent');
|
|
326
|
-
}
|
|
327
|
-
const r = await indexer.searchTeam('team-A', 'shared content', { topK: 2 });
|
|
328
|
-
expect(r.hits.length).toBeLessThanOrEqual(2);
|
|
329
|
-
});
|
|
330
|
-
|
|
331
|
-
test('unpublishFromTeam removes the entry from the team pool', async () => {
|
|
332
|
-
await indexer.publishToTeam('team-A', { id: 'm1', title: 't', content: 'c' }, 'agent');
|
|
333
|
-
expect((await indexer.teamStats('team-A')).count).toBe(1);
|
|
334
|
-
await indexer.unpublishFromTeam('team-A', 'm1');
|
|
335
|
-
expect((await indexer.teamStats('team-A')).count).toBe(0);
|
|
336
|
-
});
|
|
337
|
-
|
|
338
|
-
test('agent\'s own memory and team pool are SEPARATE stores', async () => {
|
|
339
|
-
await indexer.indexOne('agent-1', { id: 'mine', title: 'private', content: 'p' });
|
|
340
|
-
await indexer.publishToTeam('team-A', { id: 'shared', title: 'team', content: 'shared' }, 'agent-1');
|
|
341
|
-
|
|
342
|
-
const own = await indexer.search('agent-1', 'private');
|
|
343
|
-
const team = await indexer.searchTeam('team-A', 'team');
|
|
344
|
-
expect(own.hits.some(h => h.id === 'mine')).toBe(true);
|
|
345
|
-
expect(own.hits.some(h => h.id === 'shared')).toBe(false); // not in personal pool
|
|
346
|
-
expect(team.hits.some(h => h.id === 'shared')).toBe(true);
|
|
347
|
-
expect(team.hits.some(h => h.id === 'mine')).toBe(false); // not in team pool
|
|
348
|
-
});
|
|
349
|
-
|
|
350
|
-
test('teamStats reports count, dimensions, and fingerprint', async () => {
|
|
351
|
-
await indexer.publishToTeam('team-A', { id: 'a1', title: 't', content: 'c' }, 'agent');
|
|
352
|
-
const s = await indexer.teamStats('team-A');
|
|
353
|
-
expect(s.enabled).toBe(true);
|
|
354
|
-
expect(s.count).toBe(1);
|
|
355
|
-
expect(s.dimensions).toBe(8); // stub dim
|
|
356
|
-
expect(s.modelFingerprint).toBe('stub-mem:v1');
|
|
357
|
-
});
|
|
358
|
-
});
|
|
359
|
-
|
|
360
|
-
describe('MemoryIndexer — failure resilience', () => {
|
|
361
|
-
let tmpDir;
|
|
362
|
-
beforeEach(async () => { tmpDir = await fsp.mkdtemp(path.join(os.tmpdir(), 'loxia-memidx-')); });
|
|
363
|
-
afterEach(async () => { await fsp.rm(tmpDir, { recursive: true, force: true }); });
|
|
364
|
-
|
|
365
|
-
test('indexOne swallows embed failure (does not throw)', async () => {
|
|
366
|
-
const apiKeyManager = { getEmbeddingApiKey: () => 'k' };
|
|
367
|
-
class FailingProvider extends StubProvider {
|
|
368
|
-
async _embed() { throw new Error('upstream down'); }
|
|
369
|
-
}
|
|
370
|
-
const svc = new EmbeddingService({
|
|
371
|
-
config: { provider: 'azure' },
|
|
372
|
-
apiKeyManager, baseUrl: 'https://x', stateDir: tmpDir,
|
|
373
|
-
providerOverrides: { azure: () => new FailingProvider() },
|
|
374
|
-
});
|
|
375
|
-
const idx = new MemoryIndexer({ embeddingService: svc });
|
|
376
|
-
const result = await idx.indexOne('a1', { id: 'm', title: 't', content: 'c' });
|
|
377
|
-
expect(result.indexed).toBe(false);
|
|
378
|
-
expect(result.reason).toBe('error');
|
|
379
|
-
expect(result.error).toMatch(/upstream/);
|
|
380
|
-
});
|
|
381
|
-
|
|
382
|
-
test('indexMany returns failed counts on embed failure', async () => {
|
|
383
|
-
const apiKeyManager = { getEmbeddingApiKey: () => 'k' };
|
|
384
|
-
class FailingProvider extends StubProvider {
|
|
385
|
-
async _embed() { throw new Error('rate-limited'); }
|
|
386
|
-
}
|
|
387
|
-
const svc = new EmbeddingService({
|
|
388
|
-
config: { provider: 'azure' },
|
|
389
|
-
apiKeyManager, baseUrl: 'https://x', stateDir: tmpDir,
|
|
390
|
-
providerOverrides: { azure: () => new FailingProvider() },
|
|
391
|
-
});
|
|
392
|
-
const idx = new MemoryIndexer({ embeddingService: svc });
|
|
393
|
-
const result = await idx.indexMany('a1', [
|
|
394
|
-
{ id: 'm1', title: 'a', content: 'x' },
|
|
395
|
-
{ id: 'm2', title: 'b', content: 'y' },
|
|
396
|
-
]);
|
|
397
|
-
expect(result.indexed).toBe(0);
|
|
398
|
-
expect(result.failed).toBe(2);
|
|
399
|
-
expect(result.error).toMatch(/rate-limited/);
|
|
400
|
-
});
|
|
401
|
-
|
|
402
|
-
test('search returns enabled:true but empty hits on failure', async () => {
|
|
403
|
-
const apiKeyManager = { getEmbeddingApiKey: () => 'k' };
|
|
404
|
-
class FailingProvider extends StubProvider {
|
|
405
|
-
async _embed() { throw new Error('boom'); }
|
|
406
|
-
}
|
|
407
|
-
const svc = new EmbeddingService({
|
|
408
|
-
config: { provider: 'azure' },
|
|
409
|
-
apiKeyManager, baseUrl: 'https://x', stateDir: tmpDir,
|
|
410
|
-
providerOverrides: { azure: () => new FailingProvider() },
|
|
411
|
-
});
|
|
412
|
-
const idx = new MemoryIndexer({ embeddingService: svc });
|
|
413
|
-
const r = await idx.search('a1', 'hello');
|
|
414
|
-
expect(r.enabled).toBe(true);
|
|
415
|
-
expect(r.hits).toEqual([]);
|
|
416
|
-
expect(r.error).toMatch(/boom/);
|
|
417
|
-
});
|
|
418
|
-
});
|
|
1
|
+
/**
|
|
2
|
+
* MemoryIndexer tests.
|
|
3
|
+
*
|
|
4
|
+
* Strategy: build a small EmbeddingService against the real VectorStore
|
|
5
|
+
* (in temp dir) with a stub provider that produces deterministic vectors
|
|
6
|
+
* based on text contents. Lets us exercise the FULL indexer path
|
|
7
|
+
* (embed → upsert → query → hybrid fusion) without any HTTP or ONNX.
|
|
8
|
+
*
|
|
9
|
+
* Pins:
|
|
10
|
+
* - isEnabled reflects the underlying service
|
|
11
|
+
* - indexOne / deleteOne / indexMany / backfill / search round-trip
|
|
12
|
+
* - Failures from the underlying embed call are swallowed with a warn
|
|
13
|
+
* (best-effort policy — memory writes never break)
|
|
14
|
+
* - memoryToText packing order: title → description → content
|
|
15
|
+
* - Empty / invalid memories are skipped (not failed)
|
|
16
|
+
* - hybrid:true is the default and surfaces substring-only matches
|
|
17
|
+
*/
|
|
18
|
+
|
|
19
|
+
import { describe, test, expect, jest, beforeEach, afterEach } from '@jest/globals';
|
|
20
|
+
import os from 'os';
|
|
21
|
+
import fsp from 'fs/promises';
|
|
22
|
+
import path from 'path';
|
|
23
|
+
import { MemoryIndexer, memoryToText } from '../memoryIndexer.js';
|
|
24
|
+
import { EmbeddingService } from '../../embeddingService.js';
|
|
25
|
+
import { EmbeddingProvider } from '../../providerInterface.js';
|
|
26
|
+
|
|
27
|
+
/**
|
|
28
|
+
* Stub provider that produces deterministic, recognisable vectors so we
|
|
29
|
+
* can assert ordering by content. Each text gets a vector dominated by
|
|
30
|
+
* a "fingerprint" position derived from the text — different texts get
|
|
31
|
+
* different fingerprints so the inner-product ordering matches text
|
|
32
|
+
* identity.
|
|
33
|
+
*/
|
|
34
|
+
class StubProvider extends EmbeddingProvider {
|
|
35
|
+
constructor(dimensions = 8) {
|
|
36
|
+
super();
|
|
37
|
+
this._dimensions = dimensions;
|
|
38
|
+
this.calls = [];
|
|
39
|
+
}
|
|
40
|
+
getInfo() {
|
|
41
|
+
return {
|
|
42
|
+
name: 'azure',
|
|
43
|
+
modelId: 'stub-mem',
|
|
44
|
+
dimensions: this._dimensions,
|
|
45
|
+
maxBatchSize: 16,
|
|
46
|
+
maxTokensPerText: 1000,
|
|
47
|
+
distinguishesQueryAndDocument: false,
|
|
48
|
+
isLocal: false,
|
|
49
|
+
};
|
|
50
|
+
}
|
|
51
|
+
getModelFingerprint() { return 'stub-mem:v1'; }
|
|
52
|
+
async _embed(texts, kind) {
|
|
53
|
+
this.calls.push({ texts: [...texts], kind });
|
|
54
|
+
return texts.map(t => {
|
|
55
|
+
// Lay one nonzero element per text — position derived from the
|
|
56
|
+
// alphabetic content. Mixed-content texts get a unique fingerprint.
|
|
57
|
+
const idx = (t.charCodeAt(0) || 0) % this._dimensions;
|
|
58
|
+
const vec = Array(this._dimensions).fill(0);
|
|
59
|
+
vec[idx] = 1;
|
|
60
|
+
// Add a small tail so identical first-chars still differ.
|
|
61
|
+
vec[(idx + 1) % this._dimensions] = (t.length % 7) * 0.1;
|
|
62
|
+
return vec;
|
|
63
|
+
});
|
|
64
|
+
}
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
function makeIndexer({ tmpDir, enabled = true } = {}) {
|
|
68
|
+
const apiKeyManager = { getEmbeddingApiKey: jest.fn(() => 'stub-key') };
|
|
69
|
+
if (!enabled) {
|
|
70
|
+
const svc = new EmbeddingService({
|
|
71
|
+
apiKeyManager,
|
|
72
|
+
baseUrl: 'https://x',
|
|
73
|
+
stateDir: tmpDir,
|
|
74
|
+
});
|
|
75
|
+
return new MemoryIndexer({ embeddingService: svc });
|
|
76
|
+
}
|
|
77
|
+
const provider = new StubProvider();
|
|
78
|
+
const svc = new EmbeddingService({
|
|
79
|
+
config: { provider: 'azure' },
|
|
80
|
+
apiKeyManager,
|
|
81
|
+
baseUrl: 'https://x',
|
|
82
|
+
stateDir: tmpDir,
|
|
83
|
+
providerOverrides: { azure: () => provider },
|
|
84
|
+
});
|
|
85
|
+
return { indexer: new MemoryIndexer({ embeddingService: svc }), svc, provider };
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
describe('memoryToText', () => {
|
|
89
|
+
test('packs title → description → content', () => {
|
|
90
|
+
const t = memoryToText({ title: 'T', description: 'D', content: 'C' });
|
|
91
|
+
expect(t).toBe('T\n\nD\n\nC');
|
|
92
|
+
});
|
|
93
|
+
|
|
94
|
+
test('drops empty fields (no double newlines from missing pieces)', () => {
|
|
95
|
+
expect(memoryToText({ title: 'T', content: 'C' })).toBe('T\n\nC');
|
|
96
|
+
expect(memoryToText({ content: 'C' })).toBe('C');
|
|
97
|
+
expect(memoryToText({})).toBe('');
|
|
98
|
+
expect(memoryToText(null)).toBe('');
|
|
99
|
+
});
|
|
100
|
+
});
|
|
101
|
+
|
|
102
|
+
describe('MemoryIndexer — disabled path', () => {
|
|
103
|
+
let tmpDir;
|
|
104
|
+
beforeEach(async () => { tmpDir = await fsp.mkdtemp(path.join(os.tmpdir(), 'loxia-memidx-')); });
|
|
105
|
+
afterEach(async () => { await fsp.rm(tmpDir, { recursive: true, force: true }); });
|
|
106
|
+
|
|
107
|
+
test('isEnabled is false when service is disabled', () => {
|
|
108
|
+
const idx = makeIndexer({ tmpDir, enabled: false });
|
|
109
|
+
expect(idx.isEnabled).toBe(false);
|
|
110
|
+
});
|
|
111
|
+
|
|
112
|
+
test('indexOne returns {indexed:false, reason:"disabled"}', async () => {
|
|
113
|
+
const idx = makeIndexer({ tmpDir, enabled: false });
|
|
114
|
+
const r = await idx.indexOne('a1', { id: 'm1', title: 'hi' });
|
|
115
|
+
expect(r).toEqual({ indexed: false, reason: 'disabled' });
|
|
116
|
+
});
|
|
117
|
+
|
|
118
|
+
test('search returns {enabled:false, hits:[]}', async () => {
|
|
119
|
+
const idx = makeIndexer({ tmpDir, enabled: false });
|
|
120
|
+
const r = await idx.search('a1', 'hello');
|
|
121
|
+
expect(r).toEqual({ enabled: false, hits: [] });
|
|
122
|
+
});
|
|
123
|
+
|
|
124
|
+
test('backfill returns {ok:false, reason:"disabled"}', async () => {
|
|
125
|
+
const idx = makeIndexer({ tmpDir, enabled: false });
|
|
126
|
+
const r = await idx.backfill('a1', [{ id: 'm', title: 't', content: 'c' }]);
|
|
127
|
+
expect(r).toEqual({ ok: false, reason: 'disabled' });
|
|
128
|
+
});
|
|
129
|
+
});
|
|
130
|
+
|
|
131
|
+
describe('MemoryIndexer — round-trip', () => {
|
|
132
|
+
let tmpDir, indexer, provider;
|
|
133
|
+
beforeEach(async () => {
|
|
134
|
+
tmpDir = await fsp.mkdtemp(path.join(os.tmpdir(), 'loxia-memidx-'));
|
|
135
|
+
({ indexer, provider } = makeIndexer({ tmpDir }));
|
|
136
|
+
});
|
|
137
|
+
afterEach(async () => { await fsp.rm(tmpDir, { recursive: true, force: true }); });
|
|
138
|
+
|
|
139
|
+
test('indexOne + search: the inserted memory becomes findable', async () => {
|
|
140
|
+
await indexer.indexOne('a1', {
|
|
141
|
+
id: 'm1', title: 'Telegram dedup', content: 'message floods', createdAt: '2026-05-25T12:00:00Z',
|
|
142
|
+
});
|
|
143
|
+
const r = await indexer.search('a1', 'Telegram dedup');
|
|
144
|
+
expect(r.enabled).toBe(true);
|
|
145
|
+
expect(r.hits.some(h => h.id === 'm1')).toBe(true);
|
|
146
|
+
});
|
|
147
|
+
|
|
148
|
+
test('indexOne with empty memory returns reason:"empty-text"', async () => {
|
|
149
|
+
const r = await indexer.indexOne('a1', { id: 'm-empty' });
|
|
150
|
+
expect(r).toEqual({ indexed: false, reason: 'empty-text' });
|
|
151
|
+
});
|
|
152
|
+
|
|
153
|
+
test('indexOne with invalid input returns reason:"invalid-input"', async () => {
|
|
154
|
+
expect(await indexer.indexOne(null, { id: 'm' })).toMatchObject({ reason: 'invalid-input' });
|
|
155
|
+
expect(await indexer.indexOne('a1', null)).toMatchObject({ reason: 'invalid-input' });
|
|
156
|
+
expect(await indexer.indexOne('a1', {})).toMatchObject({ reason: 'invalid-input' });
|
|
157
|
+
});
|
|
158
|
+
|
|
159
|
+
test('deleteOne removes the memory from the index', async () => {
|
|
160
|
+
await indexer.indexOne('a1', { id: 'm1', title: 'first', content: 'one' });
|
|
161
|
+
await indexer.indexOne('a1', { id: 'm2', title: 'second', content: 'two' });
|
|
162
|
+
const before = await indexer.search('a1', 'first');
|
|
163
|
+
expect(before.hits.some(h => h.id === 'm1')).toBe(true);
|
|
164
|
+
await indexer.deleteOne('a1', 'm1');
|
|
165
|
+
const after = await indexer.search('a1', 'first');
|
|
166
|
+
expect(after.hits.some(h => h.id === 'm1')).toBe(false);
|
|
167
|
+
});
|
|
168
|
+
|
|
169
|
+
test('indexMany batches memories and counts skipped', async () => {
|
|
170
|
+
const memories = [
|
|
171
|
+
{ id: 'm1', title: 'alpha', content: 'one' },
|
|
172
|
+
{ id: 'm2', title: 'beta', content: 'two' },
|
|
173
|
+
{ id: 'm3' }, // no text → skip
|
|
174
|
+
{ title: 'no id' }, // no id → skip
|
|
175
|
+
];
|
|
176
|
+
const result = await indexer.indexMany('a1', memories);
|
|
177
|
+
expect(result.indexed).toBe(2);
|
|
178
|
+
expect(result.skipped).toBe(2);
|
|
179
|
+
expect(result.failed).toBe(0);
|
|
180
|
+
});
|
|
181
|
+
|
|
182
|
+
test('indexMany sends a single batched embed call', async () => {
|
|
183
|
+
const memories = Array.from({ length: 5 }, (_, i) => ({
|
|
184
|
+
id: `m${i}`, title: `t${i}`, content: `c${i}`,
|
|
185
|
+
}));
|
|
186
|
+
await indexer.indexMany('a1', memories);
|
|
187
|
+
expect(provider.calls).toHaveLength(1); // one batched call
|
|
188
|
+
expect(provider.calls[0].texts).toHaveLength(5);
|
|
189
|
+
});
|
|
190
|
+
|
|
191
|
+
test('backfill replaces the entire index', async () => {
|
|
192
|
+
await indexer.indexOne('a1', { id: 'stale', title: 'old', content: 'old' });
|
|
193
|
+
const replacement = [
|
|
194
|
+
{ id: 'new1', title: 'fresh', content: 'one' },
|
|
195
|
+
{ id: 'new2', title: 'newer', content: 'two' },
|
|
196
|
+
];
|
|
197
|
+
const result = await indexer.backfill('a1', replacement);
|
|
198
|
+
expect(result.ok).toBe(true);
|
|
199
|
+
expect(result.indexed).toBe(2);
|
|
200
|
+
|
|
201
|
+
const search = await indexer.search('a1', 'fresh');
|
|
202
|
+
expect(search.hits.some(h => h.id === 'new1')).toBe(true);
|
|
203
|
+
expect(search.hits.some(h => h.id === 'stale')).toBe(false);
|
|
204
|
+
});
|
|
205
|
+
|
|
206
|
+
test('per-agent isolation: search on a1 does NOT return a2 memories', async () => {
|
|
207
|
+
await indexer.indexOne('a1', { id: 'm1', title: 'agent1-thing', content: 'x' });
|
|
208
|
+
await indexer.indexOne('a2', { id: 'm1', title: 'agent2-thing', content: 'y' });
|
|
209
|
+
const r1 = await indexer.search('a1', 'thing');
|
|
210
|
+
expect(r1.hits.length).toBeGreaterThan(0);
|
|
211
|
+
expect(r1.hits.every(h => h.metadata?.title === 'agent1-thing')).toBe(true);
|
|
212
|
+
});
|
|
213
|
+
|
|
214
|
+
test('hybrid search by default surfaces substring-only matches', async () => {
|
|
215
|
+
// Memory whose semantic vector won't match (stub generates by first char)
|
|
216
|
+
// but whose substring DOES match the query.
|
|
217
|
+
await indexer.indexOne('a1', {
|
|
218
|
+
id: 'm-sub',
|
|
219
|
+
title: 'arbitrary title',
|
|
220
|
+
content: 'this body mentions UNIQUE_TOKEN_X explicitly',
|
|
221
|
+
});
|
|
222
|
+
const r = await indexer.search('a1', 'UNIQUE_TOKEN_X');
|
|
223
|
+
expect(r.hits.some(h => h.id === 'm-sub')).toBe(true);
|
|
224
|
+
});
|
|
225
|
+
|
|
226
|
+
test('recency boost ranks fresh memories above stale ones on tie', async () => {
|
|
227
|
+
// Two memories with the same text → identical cosine. The fresh
|
|
228
|
+
// one's createdAt should make it rank first by default.
|
|
229
|
+
await indexer.indexOne('a1', {
|
|
230
|
+
id: 'fresh', title: 'shared', content: 'shared body',
|
|
231
|
+
createdAt: new Date(Date.now() - 1 * 24 * 60 * 60 * 1000).toISOString(),
|
|
232
|
+
});
|
|
233
|
+
await indexer.indexOne('a1', {
|
|
234
|
+
id: 'stale', title: 'shared', content: 'shared body',
|
|
235
|
+
createdAt: new Date(Date.now() - 200 * 24 * 60 * 60 * 1000).toISOString(),
|
|
236
|
+
});
|
|
237
|
+
const r = await indexer.search('a1', 'shared body');
|
|
238
|
+
expect(r.hits.length).toBeGreaterThanOrEqual(2);
|
|
239
|
+
const freshIdx = r.hits.findIndex(h => h.id === 'fresh');
|
|
240
|
+
const staleIdx = r.hits.findIndex(h => h.id === 'stale');
|
|
241
|
+
expect(freshIdx).toBeLessThan(staleIdx);
|
|
242
|
+
});
|
|
243
|
+
|
|
244
|
+
test('boost:false disables recency adjustment (raw cosine ranking)', async () => {
|
|
245
|
+
await indexer.indexOne('a1', { id: 'fresh', title: 'foo', content: 'foo' });
|
|
246
|
+
await indexer.indexOne('a1', { id: 'stale', title: 'foo', content: 'foo',
|
|
247
|
+
createdAt: new Date(Date.now() - 365 * 24 * 60 * 60 * 1000).toISOString() });
|
|
248
|
+
// With boost off, identical text → effectively identical scores; the
|
|
249
|
+
// order may be insertion-dependent but neither should be massively
|
|
250
|
+
// pulled away by recency.
|
|
251
|
+
const r = await indexer.search('a1', 'foo', { boost: false });
|
|
252
|
+
const scores = r.hits.map(h => h.score);
|
|
253
|
+
expect(Math.max(...scores) - Math.min(...scores)).toBeLessThan(0.05);
|
|
254
|
+
});
|
|
255
|
+
|
|
256
|
+
test('hybrid:false disables substring fusion', async () => {
|
|
257
|
+
await indexer.indexOne('a1', {
|
|
258
|
+
id: 'm-sub',
|
|
259
|
+
title: 'arbitrary',
|
|
260
|
+
content: 'this body mentions UNIQUE_TOKEN_X',
|
|
261
|
+
});
|
|
262
|
+
// Pure-semantic with stub provider and a wildly different query —
|
|
263
|
+
// we get nothing useful, but the call shouldn't throw.
|
|
264
|
+
const r = await indexer.search('a1', 'UNIQUE_TOKEN_X', { hybrid: false });
|
|
265
|
+
expect(r.enabled).toBe(true);
|
|
266
|
+
expect(Array.isArray(r.hits)).toBe(true);
|
|
267
|
+
});
|
|
268
|
+
});
|
|
269
|
+
|
|
270
|
+
describe('MemoryIndexer — team-pool (Phase 6c)', () => {
|
|
271
|
+
let tmpDir, indexer, svc;
|
|
272
|
+
beforeEach(async () => {
|
|
273
|
+
tmpDir = await fsp.mkdtemp(path.join(os.tmpdir(), 'loxia-memidx-team-'));
|
|
274
|
+
({ indexer, svc } = makeIndexer({ tmpDir }));
|
|
275
|
+
});
|
|
276
|
+
afterEach(async () => { await fsp.rm(tmpDir, { recursive: true, force: true }); });
|
|
277
|
+
|
|
278
|
+
test('publishToTeam stores under the team scope path', async () => {
|
|
279
|
+
await indexer.publishToTeam('team-A', {
|
|
280
|
+
id: 'mem-1', title: 'Useful note', content: 'shared knowledge',
|
|
281
|
+
}, 'agent-publisher');
|
|
282
|
+
const store = await svc.getStore({ teamId: 'team-A', surface: 'memory' });
|
|
283
|
+
expect(store._filePath).toMatch(/[\\/]teams[\\/]team-A[\\/]memory\.vec\.json$/);
|
|
284
|
+
expect((await store.stats()).count).toBe(1);
|
|
285
|
+
});
|
|
286
|
+
|
|
287
|
+
test('publishToTeam records publishedBy + publishedAt in metadata', async () => {
|
|
288
|
+
await indexer.publishToTeam('team-A', {
|
|
289
|
+
id: 'mem-1', title: 't', content: 'c',
|
|
290
|
+
}, 'agent-publisher');
|
|
291
|
+
const store = await svc.getStore({ teamId: 'team-A', surface: 'memory' });
|
|
292
|
+
const row = store._rows[0];
|
|
293
|
+
expect(row.metadata.publishedBy).toBe('agent-publisher');
|
|
294
|
+
expect(row.metadata.publishedAt).toBeTruthy();
|
|
295
|
+
expect(typeof row.metadata.publishedAt).toBe('string');
|
|
296
|
+
});
|
|
297
|
+
|
|
298
|
+
test('disabled service: publishToTeam returns {indexed:false, reason:"disabled"}', async () => {
|
|
299
|
+
// makeIndexer({enabled:false}) returns the bare indexer (no destructure).
|
|
300
|
+
const disabled = makeIndexer({ tmpDir, enabled: false });
|
|
301
|
+
const r = await disabled.publishToTeam('team-A', { id: 'mem-1', title: 't', content: 'c' }, 'agent');
|
|
302
|
+
expect(r).toEqual({ indexed: false, reason: 'disabled' });
|
|
303
|
+
});
|
|
304
|
+
|
|
305
|
+
test('invalid input (missing teamId or memory.id) → invalid-input', async () => {
|
|
306
|
+
const r1 = await indexer.publishToTeam(null, { id: 'm', title: 't', content: 'c' }, 'a');
|
|
307
|
+
const r2 = await indexer.publishToTeam('team-A', {}, 'a');
|
|
308
|
+
expect(r1.reason).toBe('invalid-input');
|
|
309
|
+
expect(r2.reason).toBe('invalid-input');
|
|
310
|
+
});
|
|
311
|
+
|
|
312
|
+
test('searchTeam returns hits from the team pool only (per-team isolation)', async () => {
|
|
313
|
+
await indexer.publishToTeam('team-A', { id: 'a1', title: 'alpha', content: 'foo' }, 'agent');
|
|
314
|
+
await indexer.publishToTeam('team-B', { id: 'b1', title: 'beta', content: 'bar' }, 'agent');
|
|
315
|
+
const rA = await indexer.searchTeam('team-A', 'alpha');
|
|
316
|
+
const rB = await indexer.searchTeam('team-B', 'beta');
|
|
317
|
+
expect(rA.hits.some(h => h.id === 'a1')).toBe(true);
|
|
318
|
+
expect(rA.hits.some(h => h.id === 'b1')).toBe(false);
|
|
319
|
+
expect(rB.hits.some(h => h.id === 'b1')).toBe(true);
|
|
320
|
+
expect(rB.hits.some(h => h.id === 'a1')).toBe(false);
|
|
321
|
+
});
|
|
322
|
+
|
|
323
|
+
test('searchTeam respects topK + hybrid + boost flags', async () => {
|
|
324
|
+
for (let i = 0; i < 5; i++) {
|
|
325
|
+
await indexer.publishToTeam('team-A', { id: `m${i}`, title: `t${i}`, content: 'shared content' }, 'agent');
|
|
326
|
+
}
|
|
327
|
+
const r = await indexer.searchTeam('team-A', 'shared content', { topK: 2 });
|
|
328
|
+
expect(r.hits.length).toBeLessThanOrEqual(2);
|
|
329
|
+
});
|
|
330
|
+
|
|
331
|
+
test('unpublishFromTeam removes the entry from the team pool', async () => {
|
|
332
|
+
await indexer.publishToTeam('team-A', { id: 'm1', title: 't', content: 'c' }, 'agent');
|
|
333
|
+
expect((await indexer.teamStats('team-A')).count).toBe(1);
|
|
334
|
+
await indexer.unpublishFromTeam('team-A', 'm1');
|
|
335
|
+
expect((await indexer.teamStats('team-A')).count).toBe(0);
|
|
336
|
+
});
|
|
337
|
+
|
|
338
|
+
test('agent\'s own memory and team pool are SEPARATE stores', async () => {
|
|
339
|
+
await indexer.indexOne('agent-1', { id: 'mine', title: 'private', content: 'p' });
|
|
340
|
+
await indexer.publishToTeam('team-A', { id: 'shared', title: 'team', content: 'shared' }, 'agent-1');
|
|
341
|
+
|
|
342
|
+
const own = await indexer.search('agent-1', 'private');
|
|
343
|
+
const team = await indexer.searchTeam('team-A', 'team');
|
|
344
|
+
expect(own.hits.some(h => h.id === 'mine')).toBe(true);
|
|
345
|
+
expect(own.hits.some(h => h.id === 'shared')).toBe(false); // not in personal pool
|
|
346
|
+
expect(team.hits.some(h => h.id === 'shared')).toBe(true);
|
|
347
|
+
expect(team.hits.some(h => h.id === 'mine')).toBe(false); // not in team pool
|
|
348
|
+
});
|
|
349
|
+
|
|
350
|
+
test('teamStats reports count, dimensions, and fingerprint', async () => {
|
|
351
|
+
await indexer.publishToTeam('team-A', { id: 'a1', title: 't', content: 'c' }, 'agent');
|
|
352
|
+
const s = await indexer.teamStats('team-A');
|
|
353
|
+
expect(s.enabled).toBe(true);
|
|
354
|
+
expect(s.count).toBe(1);
|
|
355
|
+
expect(s.dimensions).toBe(8); // stub dim
|
|
356
|
+
expect(s.modelFingerprint).toBe('stub-mem:v1');
|
|
357
|
+
});
|
|
358
|
+
});
|
|
359
|
+
|
|
360
|
+
describe('MemoryIndexer — failure resilience', () => {
|
|
361
|
+
let tmpDir;
|
|
362
|
+
beforeEach(async () => { tmpDir = await fsp.mkdtemp(path.join(os.tmpdir(), 'loxia-memidx-')); });
|
|
363
|
+
afterEach(async () => { await fsp.rm(tmpDir, { recursive: true, force: true }); });
|
|
364
|
+
|
|
365
|
+
test('indexOne swallows embed failure (does not throw)', async () => {
|
|
366
|
+
const apiKeyManager = { getEmbeddingApiKey: () => 'k' };
|
|
367
|
+
class FailingProvider extends StubProvider {
|
|
368
|
+
async _embed() { throw new Error('upstream down'); }
|
|
369
|
+
}
|
|
370
|
+
const svc = new EmbeddingService({
|
|
371
|
+
config: { provider: 'azure' },
|
|
372
|
+
apiKeyManager, baseUrl: 'https://x', stateDir: tmpDir,
|
|
373
|
+
providerOverrides: { azure: () => new FailingProvider() },
|
|
374
|
+
});
|
|
375
|
+
const idx = new MemoryIndexer({ embeddingService: svc });
|
|
376
|
+
const result = await idx.indexOne('a1', { id: 'm', title: 't', content: 'c' });
|
|
377
|
+
expect(result.indexed).toBe(false);
|
|
378
|
+
expect(result.reason).toBe('error');
|
|
379
|
+
expect(result.error).toMatch(/upstream/);
|
|
380
|
+
});
|
|
381
|
+
|
|
382
|
+
test('indexMany returns failed counts on embed failure', async () => {
|
|
383
|
+
const apiKeyManager = { getEmbeddingApiKey: () => 'k' };
|
|
384
|
+
class FailingProvider extends StubProvider {
|
|
385
|
+
async _embed() { throw new Error('rate-limited'); }
|
|
386
|
+
}
|
|
387
|
+
const svc = new EmbeddingService({
|
|
388
|
+
config: { provider: 'azure' },
|
|
389
|
+
apiKeyManager, baseUrl: 'https://x', stateDir: tmpDir,
|
|
390
|
+
providerOverrides: { azure: () => new FailingProvider() },
|
|
391
|
+
});
|
|
392
|
+
const idx = new MemoryIndexer({ embeddingService: svc });
|
|
393
|
+
const result = await idx.indexMany('a1', [
|
|
394
|
+
{ id: 'm1', title: 'a', content: 'x' },
|
|
395
|
+
{ id: 'm2', title: 'b', content: 'y' },
|
|
396
|
+
]);
|
|
397
|
+
expect(result.indexed).toBe(0);
|
|
398
|
+
expect(result.failed).toBe(2);
|
|
399
|
+
expect(result.error).toMatch(/rate-limited/);
|
|
400
|
+
});
|
|
401
|
+
|
|
402
|
+
test('search returns enabled:true but empty hits on failure', async () => {
|
|
403
|
+
const apiKeyManager = { getEmbeddingApiKey: () => 'k' };
|
|
404
|
+
class FailingProvider extends StubProvider {
|
|
405
|
+
async _embed() { throw new Error('boom'); }
|
|
406
|
+
}
|
|
407
|
+
const svc = new EmbeddingService({
|
|
408
|
+
config: { provider: 'azure' },
|
|
409
|
+
apiKeyManager, baseUrl: 'https://x', stateDir: tmpDir,
|
|
410
|
+
providerOverrides: { azure: () => new FailingProvider() },
|
|
411
|
+
});
|
|
412
|
+
const idx = new MemoryIndexer({ embeddingService: svc });
|
|
413
|
+
const r = await idx.search('a1', 'hello');
|
|
414
|
+
expect(r.enabled).toBe(true);
|
|
415
|
+
expect(r.hits).toEqual([]);
|
|
416
|
+
expect(r.error).toMatch(/boom/);
|
|
417
|
+
});
|
|
418
|
+
});
|