@strands-agents/sdk 1.4.0 → 1.6.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/README.md +11 -11
- package/dist/src/__fixtures__/agent-helpers.d.ts.map +1 -1
- package/dist/src/__fixtures__/agent-helpers.js +9 -0
- package/dist/src/__fixtures__/agent-helpers.js.map +1 -1
- package/dist/src/__fixtures__/register-node-defaults.d.ts +2 -0
- package/dist/src/__fixtures__/register-node-defaults.d.ts.map +1 -0
- package/dist/src/__fixtures__/register-node-defaults.js +6 -0
- package/dist/src/__fixtures__/register-node-defaults.js.map +1 -0
- package/dist/src/__fixtures__/test-sandbox.node.d.ts.map +1 -1
- package/dist/src/__fixtures__/test-sandbox.node.js +4 -3
- package/dist/src/__fixtures__/test-sandbox.node.js.map +1 -1
- package/dist/src/__tests__/default-slot.test.d.ts +2 -0
- package/dist/src/__tests__/default-slot.test.d.ts.map +1 -0
- package/dist/src/__tests__/default-slot.test.js +33 -0
- package/dist/src/__tests__/default-slot.test.js.map +1 -0
- package/dist/src/a2a/__tests__/async-lock.test.d.ts +2 -0
- package/dist/src/a2a/__tests__/async-lock.test.d.ts.map +1 -0
- package/dist/src/a2a/__tests__/async-lock.test.js +137 -0
- package/dist/src/a2a/__tests__/async-lock.test.js.map +1 -0
- package/dist/src/a2a/__tests__/executor.test.js +146 -8
- package/dist/src/a2a/__tests__/executor.test.js.map +1 -1
- package/dist/src/a2a/__tests__/server.test.js +20 -0
- package/dist/src/a2a/__tests__/server.test.js.map +1 -1
- package/dist/src/a2a/async-lock.d.ts +22 -0
- package/dist/src/a2a/async-lock.d.ts.map +1 -0
- package/dist/src/a2a/async-lock.js +38 -0
- package/dist/src/a2a/async-lock.js.map +1 -0
- package/dist/src/a2a/executor.d.ts +59 -24
- package/dist/src/a2a/executor.d.ts.map +1 -1
- package/dist/src/a2a/executor.js +209 -32
- package/dist/src/a2a/executor.js.map +1 -1
- package/dist/src/a2a/index.d.ts +1 -1
- package/dist/src/a2a/index.d.ts.map +1 -1
- package/dist/src/a2a/index.js +1 -1
- package/dist/src/a2a/index.js.map +1 -1
- package/dist/src/a2a/server.d.ts +18 -2
- package/dist/src/a2a/server.d.ts.map +1 -1
- package/dist/src/a2a/server.js +13 -2
- package/dist/src/a2a/server.js.map +1 -1
- package/dist/src/agent/__tests__/agent.context-manager.test.d.ts +2 -0
- package/dist/src/agent/__tests__/agent.context-manager.test.d.ts.map +1 -0
- package/dist/src/agent/__tests__/agent.context-manager.test.js +107 -0
- package/dist/src/agent/__tests__/agent.context-manager.test.js.map +1 -0
- package/dist/src/agent/__tests__/agent.stateful-model.test.js +2 -2
- package/dist/src/agent/__tests__/agent.stateful-model.test.js.map +1 -1
- package/dist/src/agent/__tests__/agent.tracer.test.node.js +14 -0
- package/dist/src/agent/__tests__/agent.tracer.test.node.js.map +1 -1
- package/dist/src/agent/agent.d.ts +135 -2
- package/dist/src/agent/agent.d.ts.map +1 -1
- package/dist/src/agent/agent.js +506 -189
- package/dist/src/agent/agent.js.map +1 -1
- package/dist/src/context-manager/modes/agentic/agentic-context.d.ts +19 -0
- package/dist/src/context-manager/modes/agentic/agentic-context.d.ts.map +1 -0
- package/dist/src/context-manager/modes/agentic/agentic-context.js +245 -0
- package/dist/src/context-manager/modes/agentic/agentic-context.js.map +1 -0
- package/dist/src/conversation-manager/__tests__/agentic-context.test.d.ts +2 -0
- package/dist/src/conversation-manager/__tests__/agentic-context.test.d.ts.map +1 -0
- package/dist/src/conversation-manager/__tests__/agentic-context.test.js +332 -0
- package/dist/src/conversation-manager/__tests__/agentic-context.test.js.map +1 -0
- package/dist/src/conversation-manager/__tests__/context-compression.test.d.ts +2 -0
- package/dist/src/conversation-manager/__tests__/context-compression.test.d.ts.map +1 -0
- package/dist/src/conversation-manager/__tests__/context-compression.test.js +176 -0
- package/dist/src/conversation-manager/__tests__/context-compression.test.js.map +1 -0
- package/dist/src/conversation-manager/__tests__/pin.test.d.ts +2 -0
- package/dist/src/conversation-manager/__tests__/pin.test.d.ts.map +1 -0
- package/dist/src/conversation-manager/__tests__/pin.test.js +119 -0
- package/dist/src/conversation-manager/__tests__/pin.test.js.map +1 -0
- package/dist/src/conversation-manager/__tests__/sliding-window-conversation-manager.test.js +49 -0
- package/dist/src/conversation-manager/__tests__/sliding-window-conversation-manager.test.js.map +1 -1
- package/dist/src/conversation-manager/__tests__/summarizing-conversation-manager.test.js +58 -0
- package/dist/src/conversation-manager/__tests__/summarizing-conversation-manager.test.js.map +1 -1
- package/dist/src/conversation-manager/__tests__/token-usage-middleware.test.d.ts +2 -0
- package/dist/src/conversation-manager/__tests__/token-usage-middleware.test.d.ts.map +1 -0
- package/dist/src/conversation-manager/__tests__/token-usage-middleware.test.js +138 -0
- package/dist/src/conversation-manager/__tests__/token-usage-middleware.test.js.map +1 -0
- package/dist/src/conversation-manager/compression/context-compression.d.ts +39 -0
- package/dist/src/conversation-manager/compression/context-compression.d.ts.map +1 -0
- package/dist/src/conversation-manager/compression/context-compression.js +150 -0
- package/dist/src/conversation-manager/compression/context-compression.js.map +1 -0
- package/dist/src/conversation-manager/compression/pin-message.d.ts +45 -0
- package/dist/src/conversation-manager/compression/pin-message.d.ts.map +1 -0
- package/dist/src/conversation-manager/compression/pin-message.js +106 -0
- package/dist/src/conversation-manager/compression/pin-message.js.map +1 -0
- package/dist/src/conversation-manager/conversation-manager.d.ts +2 -0
- package/dist/src/conversation-manager/conversation-manager.d.ts.map +1 -1
- package/dist/src/conversation-manager/conversation-manager.js +2 -2
- package/dist/src/conversation-manager/conversation-manager.js.map +1 -1
- package/dist/src/conversation-manager/sliding-window-conversation-manager.d.ts +7 -0
- package/dist/src/conversation-manager/sliding-window-conversation-manager.d.ts.map +1 -1
- package/dist/src/conversation-manager/sliding-window-conversation-manager.js +30 -38
- package/dist/src/conversation-manager/sliding-window-conversation-manager.js.map +1 -1
- package/dist/src/conversation-manager/summarizing-conversation-manager.d.ts +7 -19
- package/dist/src/conversation-manager/summarizing-conversation-manager.d.ts.map +1 -1
- package/dist/src/conversation-manager/summarizing-conversation-manager.js +20 -109
- package/dist/src/conversation-manager/summarizing-conversation-manager.js.map +1 -1
- package/dist/src/default-slot.d.ts +6 -0
- package/dist/src/default-slot.d.ts.map +1 -0
- package/dist/src/default-slot.js +18 -0
- package/dist/src/default-slot.js.map +1 -0
- package/dist/src/errors.d.ts +8 -0
- package/dist/src/errors.d.ts.map +1 -1
- package/dist/src/errors.js +11 -0
- package/dist/src/errors.js.map +1 -1
- package/dist/src/index.d.ts +8 -1
- package/dist/src/index.d.ts.map +1 -1
- package/dist/src/index.js +7 -1
- package/dist/src/index.js.map +1 -1
- package/dist/src/index.node.d.ts +2 -0
- package/dist/src/index.node.d.ts.map +1 -0
- package/dist/src/index.node.js +9 -0
- package/dist/src/index.node.js.map +1 -0
- package/dist/src/injection/__tests__/message-injection.test.d.ts +2 -0
- package/dist/src/injection/__tests__/message-injection.test.d.ts.map +1 -0
- package/dist/src/injection/__tests__/message-injection.test.js +200 -0
- package/dist/src/injection/__tests__/message-injection.test.js.map +1 -0
- package/dist/src/injection/index.d.ts +6 -0
- package/dist/src/injection/index.d.ts.map +1 -0
- package/dist/src/injection/index.js +2 -0
- package/dist/src/injection/index.js.map +1 -0
- package/dist/src/injection/message-injection.d.ts +65 -0
- package/dist/src/injection/message-injection.d.ts.map +1 -0
- package/dist/src/injection/message-injection.js +134 -0
- package/dist/src/injection/message-injection.js.map +1 -0
- package/dist/src/injection/types.d.ts +63 -0
- package/dist/src/injection/types.d.ts.map +1 -0
- package/dist/src/injection/types.js +2 -0
- package/dist/src/injection/types.js.map +1 -0
- package/dist/src/injection/xml.d.ts +27 -0
- package/dist/src/injection/xml.d.ts.map +1 -0
- package/dist/src/injection/xml.js +31 -0
- package/dist/src/injection/xml.js.map +1 -0
- package/dist/src/interrupt.d.ts +5 -1
- package/dist/src/interrupt.d.ts.map +1 -1
- package/dist/src/interrupt.js +6 -0
- package/dist/src/interrupt.js.map +1 -1
- package/dist/src/memory/__tests__/memory-manager.test.d.ts +2 -0
- package/dist/src/memory/__tests__/memory-manager.test.d.ts.map +1 -0
- package/dist/src/memory/__tests__/memory-manager.test.js +679 -0
- package/dist/src/memory/__tests__/memory-manager.test.js.map +1 -0
- package/dist/src/memory/extraction/__tests__/extraction.test.d.ts +2 -0
- package/dist/src/memory/extraction/__tests__/extraction.test.d.ts.map +1 -0
- package/dist/src/memory/extraction/__tests__/extraction.test.js +637 -0
- package/dist/src/memory/extraction/__tests__/extraction.test.js.map +1 -0
- package/dist/src/memory/extraction/__tests__/model-extractor.test.d.ts +2 -0
- package/dist/src/memory/extraction/__tests__/model-extractor.test.d.ts.map +1 -0
- package/dist/src/memory/extraction/__tests__/model-extractor.test.js +68 -0
- package/dist/src/memory/extraction/__tests__/model-extractor.test.js.map +1 -0
- package/dist/src/memory/extraction/__tests__/resolve-extraction-config.test.d.ts +2 -0
- package/dist/src/memory/extraction/__tests__/resolve-extraction-config.test.d.ts.map +1 -0
- package/dist/src/memory/extraction/__tests__/resolve-extraction-config.test.js +81 -0
- package/dist/src/memory/extraction/__tests__/resolve-extraction-config.test.js.map +1 -0
- package/dist/src/memory/extraction/coordinator.d.ts +128 -0
- package/dist/src/memory/extraction/coordinator.d.ts.map +1 -0
- package/dist/src/memory/extraction/coordinator.js +245 -0
- package/dist/src/memory/extraction/coordinator.js.map +1 -0
- package/dist/src/memory/extraction/model-extractor.d.ts +32 -0
- package/dist/src/memory/extraction/model-extractor.d.ts.map +1 -0
- package/dist/src/memory/extraction/model-extractor.js +118 -0
- package/dist/src/memory/extraction/model-extractor.js.map +1 -0
- package/dist/src/memory/extraction/resolve-extraction-config.d.ts +46 -0
- package/dist/src/memory/extraction/resolve-extraction-config.d.ts.map +1 -0
- package/dist/src/memory/extraction/resolve-extraction-config.js +59 -0
- package/dist/src/memory/extraction/resolve-extraction-config.js.map +1 -0
- package/dist/src/memory/extraction/triggers.d.ts +41 -0
- package/dist/src/memory/extraction/triggers.d.ts.map +1 -0
- package/dist/src/memory/extraction/triggers.js +59 -0
- package/dist/src/memory/extraction/triggers.js.map +1 -0
- package/dist/src/memory/extraction/types.d.ts +133 -0
- package/dist/src/memory/extraction/types.d.ts.map +1 -0
- package/dist/src/memory/extraction/types.js +19 -0
- package/dist/src/memory/extraction/types.js.map +1 -0
- package/dist/src/memory/index.d.ts +10 -0
- package/dist/src/memory/index.d.ts.map +1 -0
- package/dist/src/memory/index.js +5 -0
- package/dist/src/memory/index.js.map +1 -0
- package/dist/src/memory/memory-manager.d.ts +178 -0
- package/dist/src/memory/memory-manager.d.ts.map +1 -0
- package/dist/src/memory/memory-manager.js +526 -0
- package/dist/src/memory/memory-manager.js.map +1 -0
- package/dist/src/memory/types.d.ts +278 -0
- package/dist/src/memory/types.d.ts.map +1 -0
- package/dist/src/memory/types.js +2 -0
- package/dist/src/memory/types.js.map +1 -0
- package/dist/src/middleware/__tests__/agent-middleware.test.d.ts +2 -0
- package/dist/src/middleware/__tests__/agent-middleware.test.d.ts.map +1 -0
- package/dist/src/middleware/__tests__/agent-middleware.test.js +1206 -0
- package/dist/src/middleware/__tests__/agent-middleware.test.js.map +1 -0
- package/dist/src/middleware/__tests__/copy-on-input.test.d.ts +2 -0
- package/dist/src/middleware/__tests__/copy-on-input.test.d.ts.map +1 -0
- package/dist/src/middleware/__tests__/copy-on-input.test.js +379 -0
- package/dist/src/middleware/__tests__/copy-on-input.test.js.map +1 -0
- package/dist/src/middleware/__tests__/custom-stages.test.d.ts +2 -0
- package/dist/src/middleware/__tests__/custom-stages.test.d.ts.map +1 -0
- package/dist/src/middleware/__tests__/custom-stages.test.js +97 -0
- package/dist/src/middleware/__tests__/custom-stages.test.js.map +1 -0
- package/dist/src/middleware/__tests__/middleware-interrupts.test.d.ts +2 -0
- package/dist/src/middleware/__tests__/middleware-interrupts.test.d.ts.map +1 -0
- package/dist/src/middleware/__tests__/middleware-interrupts.test.js +267 -0
- package/dist/src/middleware/__tests__/middleware-interrupts.test.js.map +1 -0
- package/dist/src/middleware/__tests__/registry.test.d.ts +2 -0
- package/dist/src/middleware/__tests__/registry.test.d.ts.map +1 -0
- package/dist/src/middleware/__tests__/registry.test.js +525 -0
- package/dist/src/middleware/__tests__/registry.test.js.map +1 -0
- package/dist/src/middleware/index.d.ts +5 -0
- package/dist/src/middleware/index.d.ts.map +1 -0
- package/dist/src/middleware/index.js +3 -0
- package/dist/src/middleware/index.js.map +1 -0
- package/dist/src/middleware/registry.d.ts +58 -0
- package/dist/src/middleware/registry.d.ts.map +1 -0
- package/dist/src/middleware/registry.js +107 -0
- package/dist/src/middleware/registry.js.map +1 -0
- package/dist/src/middleware/stages.d.ts +145 -0
- package/dist/src/middleware/stages.d.ts.map +1 -0
- package/dist/src/middleware/stages.js +34 -0
- package/dist/src/middleware/stages.js.map +1 -0
- package/dist/src/middleware/types.d.ts +88 -0
- package/dist/src/middleware/types.d.ts.map +1 -0
- package/dist/src/middleware/types.js +2 -0
- package/dist/src/middleware/types.js.map +1 -0
- package/dist/src/models/__tests__/anthropic.test.js +16 -1
- package/dist/src/models/__tests__/anthropic.test.js.map +1 -1
- package/dist/src/models/__tests__/bedrock.test.js +39 -0
- package/dist/src/models/__tests__/bedrock.test.js.map +1 -1
- package/dist/src/models/__tests__/model.test.js +46 -3
- package/dist/src/models/__tests__/model.test.js.map +1 -1
- package/dist/src/models/anthropic.js +2 -2
- package/dist/src/models/anthropic.js.map +1 -1
- package/dist/src/models/bedrock.d.ts.map +1 -1
- package/dist/src/models/bedrock.js +12 -5
- package/dist/src/models/bedrock.js.map +1 -1
- package/dist/src/models/defaults.d.ts.map +1 -1
- package/dist/src/models/defaults.js +2 -0
- package/dist/src/models/defaults.js.map +1 -1
- package/dist/src/models/model.d.ts.map +1 -1
- package/dist/src/models/model.js +7 -3
- package/dist/src/models/model.js.map +1 -1
- package/dist/src/models/openai/__tests__/responses.test.js +8 -14
- package/dist/src/models/openai/__tests__/responses.test.js.map +1 -1
- package/dist/src/sandbox/__tests__/default.test.browser.d.ts +2 -0
- package/dist/src/sandbox/__tests__/default.test.browser.d.ts.map +1 -0
- package/dist/src/sandbox/__tests__/default.test.browser.js +11 -0
- package/dist/src/sandbox/__tests__/default.test.browser.js.map +1 -0
- package/dist/src/sandbox/__tests__/default.test.node.d.ts +2 -0
- package/dist/src/sandbox/__tests__/default.test.node.d.ts.map +1 -0
- package/dist/src/sandbox/__tests__/default.test.node.js +23 -0
- package/dist/src/sandbox/__tests__/default.test.node.js.map +1 -0
- package/dist/src/sandbox/__tests__/docker.test.node.d.ts +2 -0
- package/dist/src/sandbox/__tests__/docker.test.node.d.ts.map +1 -0
- package/dist/src/sandbox/__tests__/docker.test.node.js +89 -0
- package/dist/src/sandbox/__tests__/docker.test.node.js.map +1 -0
- package/dist/src/sandbox/__tests__/errors.test.node.d.ts +2 -0
- package/dist/src/sandbox/__tests__/errors.test.node.d.ts.map +1 -0
- package/dist/src/sandbox/__tests__/errors.test.node.js +33 -0
- package/dist/src/sandbox/__tests__/errors.test.node.js.map +1 -0
- package/dist/src/sandbox/__tests__/not-a-sandbox-local-environment.test.node.d.ts +2 -0
- package/dist/src/sandbox/__tests__/not-a-sandbox-local-environment.test.node.d.ts.map +1 -0
- package/dist/src/sandbox/__tests__/not-a-sandbox-local-environment.test.node.js +124 -0
- package/dist/src/sandbox/__tests__/not-a-sandbox-local-environment.test.node.js.map +1 -0
- package/dist/src/sandbox/__tests__/posix-shell.test.node.js +50 -4
- package/dist/src/sandbox/__tests__/posix-shell.test.node.js.map +1 -1
- package/dist/src/sandbox/__tests__/ssh.test.node.d.ts +2 -0
- package/dist/src/sandbox/__tests__/ssh.test.node.d.ts.map +1 -0
- package/dist/src/sandbox/__tests__/ssh.test.node.js +262 -0
- package/dist/src/sandbox/__tests__/ssh.test.node.js.map +1 -0
- package/dist/src/sandbox/base.d.ts +17 -4
- package/dist/src/sandbox/base.d.ts.map +1 -1
- package/dist/src/sandbox/base.js +10 -2
- package/dist/src/sandbox/base.js.map +1 -1
- package/dist/src/sandbox/constants.d.ts +18 -0
- package/dist/src/sandbox/constants.d.ts.map +1 -1
- package/dist/src/sandbox/constants.js +20 -0
- package/dist/src/sandbox/constants.js.map +1 -1
- package/dist/src/sandbox/default.d.ts +3 -0
- package/dist/src/sandbox/default.d.ts.map +1 -0
- package/dist/src/sandbox/default.js +3 -0
- package/dist/src/sandbox/default.js.map +1 -0
- package/dist/src/sandbox/docker.d.ts +38 -0
- package/dist/src/sandbox/docker.d.ts.map +1 -0
- package/dist/src/sandbox/docker.js +61 -0
- package/dist/src/sandbox/docker.js.map +1 -0
- package/dist/src/sandbox/errors.d.ts +26 -0
- package/dist/src/sandbox/errors.d.ts.map +1 -0
- package/dist/src/sandbox/errors.js +35 -0
- package/dist/src/sandbox/errors.js.map +1 -0
- package/dist/src/sandbox/index.d.ts +5 -0
- package/dist/src/sandbox/index.d.ts.map +1 -0
- package/dist/src/sandbox/index.js +4 -0
- package/dist/src/sandbox/index.js.map +1 -0
- package/dist/src/sandbox/not-a-sandbox-local-environment.d.ts +16 -0
- package/dist/src/sandbox/not-a-sandbox-local-environment.d.ts.map +1 -0
- package/dist/src/sandbox/not-a-sandbox-local-environment.js +83 -0
- package/dist/src/sandbox/not-a-sandbox-local-environment.js.map +1 -0
- package/dist/src/sandbox/posix-shell.d.ts +21 -0
- package/dist/src/sandbox/posix-shell.d.ts.map +1 -1
- package/dist/src/sandbox/posix-shell.js +41 -3
- package/dist/src/sandbox/posix-shell.js.map +1 -1
- package/dist/src/sandbox/ssh.d.ts +56 -0
- package/dist/src/sandbox/ssh.d.ts.map +1 -0
- package/dist/src/sandbox/ssh.js +121 -0
- package/dist/src/sandbox/ssh.js.map +1 -0
- package/dist/src/sandbox/stream-process.d.ts.map +1 -1
- package/dist/src/sandbox/stream-process.js +3 -2
- package/dist/src/sandbox/stream-process.js.map +1 -1
- package/dist/src/tsconfig.tsbuildinfo +1 -1
- package/dist/src/types/agent.d.ts +21 -0
- package/dist/src/types/agent.d.ts.map +1 -1
- package/dist/src/types/agent.js.map +1 -1
- package/dist/src/types/messages.d.ts +9 -1
- package/dist/src/types/messages.d.ts.map +1 -1
- package/dist/src/types/messages.js +13 -1
- package/dist/src/types/messages.js.map +1 -1
- package/dist/src/vended-interventions/cedar/__tests__/cedar.test.node.d.ts +2 -0
- package/dist/src/vended-interventions/cedar/__tests__/cedar.test.node.d.ts.map +1 -0
- package/dist/src/vended-interventions/cedar/__tests__/cedar.test.node.js +675 -0
- package/dist/src/vended-interventions/cedar/__tests__/cedar.test.node.js.map +1 -0
- package/dist/src/vended-interventions/cedar/cedar.d.ts +102 -0
- package/dist/src/vended-interventions/cedar/cedar.d.ts.map +1 -0
- package/dist/src/vended-interventions/cedar/cedar.js +228 -0
- package/dist/src/vended-interventions/cedar/cedar.js.map +1 -0
- package/dist/src/vended-interventions/cedar/index.d.ts +3 -0
- package/dist/src/vended-interventions/cedar/index.d.ts.map +1 -0
- package/dist/src/vended-interventions/cedar/index.js +2 -0
- package/dist/src/vended-interventions/cedar/index.js.map +1 -0
- package/dist/src/vended-interventions/cedar/schema-generator.d.ts +10 -0
- package/dist/src/vended-interventions/cedar/schema-generator.d.ts.map +1 -0
- package/dist/src/vended-interventions/cedar/schema-generator.js +33 -0
- package/dist/src/vended-interventions/cedar/schema-generator.js.map +1 -0
- package/dist/src/vended-memory-stores/bedrock-knowledge-base/__tests__/bedrock-knowledge-base-store.test.d.ts +2 -0
- package/dist/src/vended-memory-stores/bedrock-knowledge-base/__tests__/bedrock-knowledge-base-store.test.d.ts.map +1 -0
- package/dist/src/vended-memory-stores/bedrock-knowledge-base/__tests__/bedrock-knowledge-base-store.test.js +611 -0
- package/dist/src/vended-memory-stores/bedrock-knowledge-base/__tests__/bedrock-knowledge-base-store.test.js.map +1 -0
- package/dist/src/vended-memory-stores/bedrock-knowledge-base/index.d.ts +2 -0
- package/dist/src/vended-memory-stores/bedrock-knowledge-base/index.d.ts.map +1 -0
- package/dist/src/vended-memory-stores/bedrock-knowledge-base/index.js +2 -0
- package/dist/src/vended-memory-stores/bedrock-knowledge-base/index.js.map +1 -0
- package/dist/src/vended-memory-stores/bedrock-knowledge-base/store.d.ts +230 -0
- package/dist/src/vended-memory-stores/bedrock-knowledge-base/store.d.ts.map +1 -0
- package/dist/src/vended-memory-stores/bedrock-knowledge-base/store.js +370 -0
- package/dist/src/vended-memory-stores/bedrock-knowledge-base/store.js.map +1 -0
- package/dist/src/vended-plugins/context-injector/__tests__/plugin.test.d.ts +2 -0
- package/dist/src/vended-plugins/context-injector/__tests__/plugin.test.d.ts.map +1 -0
- package/dist/src/vended-plugins/context-injector/__tests__/plugin.test.js +96 -0
- package/dist/src/vended-plugins/context-injector/__tests__/plugin.test.js.map +1 -0
- package/dist/src/vended-plugins/context-injector/index.d.ts +25 -0
- package/dist/src/vended-plugins/context-injector/index.d.ts.map +1 -0
- package/dist/src/vended-plugins/context-injector/index.js +23 -0
- package/dist/src/vended-plugins/context-injector/index.js.map +1 -0
- package/dist/src/vended-plugins/context-injector/plugin.d.ts +55 -0
- package/dist/src/vended-plugins/context-injector/plugin.d.ts.map +1 -0
- package/dist/src/vended-plugins/context-injector/plugin.js +41 -0
- package/dist/src/vended-plugins/context-injector/plugin.js.map +1 -0
- package/dist/src/vended-plugins/context-offloader/__tests__/file-storage-sandbox.test.node.d.ts +2 -0
- package/dist/src/vended-plugins/context-offloader/__tests__/file-storage-sandbox.test.node.d.ts.map +1 -0
- package/dist/src/vended-plugins/context-offloader/__tests__/file-storage-sandbox.test.node.js +68 -0
- package/dist/src/vended-plugins/context-offloader/__tests__/file-storage-sandbox.test.node.js.map +1 -0
- package/dist/src/vended-plugins/context-offloader/__tests__/plugin.test.js +43 -4
- package/dist/src/vended-plugins/context-offloader/__tests__/plugin.test.js.map +1 -1
- package/dist/src/vended-plugins/context-offloader/__tests__/plugin.test.node.d.ts +2 -0
- package/dist/src/vended-plugins/context-offloader/__tests__/plugin.test.node.d.ts.map +1 -0
- package/dist/src/vended-plugins/context-offloader/__tests__/plugin.test.node.js +93 -0
- package/dist/src/vended-plugins/context-offloader/__tests__/plugin.test.node.js.map +1 -0
- package/dist/src/vended-plugins/context-offloader/__tests__/storage.test.js +68 -0
- package/dist/src/vended-plugins/context-offloader/__tests__/storage.test.js.map +1 -1
- package/dist/src/vended-plugins/context-offloader/index.d.ts +1 -1
- package/dist/src/vended-plugins/context-offloader/index.d.ts.map +1 -1
- package/dist/src/vended-plugins/context-offloader/plugin.d.ts +4 -1
- package/dist/src/vended-plugins/context-offloader/plugin.d.ts.map +1 -1
- package/dist/src/vended-plugins/context-offloader/plugin.js +40 -8
- package/dist/src/vended-plugins/context-offloader/plugin.js.map +1 -1
- package/dist/src/vended-plugins/context-offloader/search.d.ts.map +1 -1
- package/dist/src/vended-plugins/context-offloader/search.js +3 -5
- package/dist/src/vended-plugins/context-offloader/search.js.map +1 -1
- package/dist/src/vended-plugins/context-offloader/storage.d.ts +58 -6
- package/dist/src/vended-plugins/context-offloader/storage.d.ts.map +1 -1
- package/dist/src/vended-plugins/context-offloader/storage.js +136 -14
- package/dist/src/vended-plugins/context-offloader/storage.js.map +1 -1
- package/dist/src/vended-plugins/goal/__tests__/plugin.test.d.ts +2 -0
- package/dist/src/vended-plugins/goal/__tests__/plugin.test.d.ts.map +1 -0
- package/dist/src/vended-plugins/goal/__tests__/plugin.test.js +736 -0
- package/dist/src/vended-plugins/goal/__tests__/plugin.test.js.map +1 -0
- package/dist/src/vended-plugins/goal/index.d.ts +21 -0
- package/dist/src/vended-plugins/goal/index.d.ts.map +1 -0
- package/dist/src/vended-plugins/goal/index.js +20 -0
- package/dist/src/vended-plugins/goal/index.js.map +1 -0
- package/dist/src/vended-plugins/goal/judge.d.ts +41 -0
- package/dist/src/vended-plugins/goal/judge.d.ts.map +1 -0
- package/dist/src/vended-plugins/goal/judge.js +92 -0
- package/dist/src/vended-plugins/goal/judge.js.map +1 -0
- package/dist/src/vended-plugins/goal/plugin.d.ts +214 -0
- package/dist/src/vended-plugins/goal/plugin.d.ts.map +1 -0
- package/dist/src/vended-plugins/goal/plugin.js +287 -0
- package/dist/src/vended-plugins/goal/plugin.js.map +1 -0
- package/dist/src/vended-plugins/index.d.ts +3 -1
- package/dist/src/vended-plugins/index.d.ts.map +1 -1
- package/dist/src/vended-plugins/index.js +3 -1
- package/dist/src/vended-plugins/index.js.map +1 -1
- package/dist/src/vended-plugins/skills/__tests__/agent-skills.test.node.js +17 -7
- package/dist/src/vended-plugins/skills/__tests__/agent-skills.test.node.js.map +1 -1
- package/dist/src/vended-plugins/skills/agent-skills.d.ts +21 -7
- package/dist/src/vended-plugins/skills/agent-skills.d.ts.map +1 -1
- package/dist/src/vended-plugins/skills/agent-skills.js +144 -77
- package/dist/src/vended-plugins/skills/agent-skills.js.map +1 -1
- package/dist/src/vended-tools/bash/__tests__/bash.test.node.js +44 -4
- package/dist/src/vended-tools/bash/__tests__/bash.test.node.js.map +1 -1
- package/dist/src/vended-tools/bash/bash.d.ts +3 -24
- package/dist/src/vended-tools/bash/bash.d.ts.map +1 -1
- package/dist/src/vended-tools/bash/bash.js +9 -9
- package/dist/src/vended-tools/bash/bash.js.map +1 -1
- package/dist/src/vended-tools/bash/index.d.ts +3 -1
- package/dist/src/vended-tools/bash/index.d.ts.map +1 -1
- package/dist/src/vended-tools/bash/index.js +2 -1
- package/dist/src/vended-tools/bash/index.js.map +1 -1
- package/dist/src/vended-tools/bash/make-bash.d.ts +22 -0
- package/dist/src/vended-tools/bash/make-bash.d.ts.map +1 -0
- package/dist/src/vended-tools/bash/make-bash.js +40 -0
- package/dist/src/vended-tools/bash/make-bash.js.map +1 -0
- package/dist/src/vended-tools/bash/types.d.ts +1 -0
- package/dist/src/vended-tools/bash/types.d.ts.map +1 -1
- package/dist/src/vended-tools/bash/types.js +2 -0
- package/dist/src/vended-tools/bash/types.js.map +1 -1
- package/dist/src/vended-tools/file-editor/__tests__/file-editor.test.node.js +83 -1
- package/dist/src/vended-tools/file-editor/__tests__/file-editor.test.node.js.map +1 -1
- package/dist/src/vended-tools/file-editor/file-editor.d.ts +19 -10
- package/dist/src/vended-tools/file-editor/file-editor.d.ts.map +1 -1
- package/dist/src/vended-tools/file-editor/file-editor.js +188 -218
- package/dist/src/vended-tools/file-editor/file-editor.js.map +1 -1
- package/dist/src/vended-tools/file-editor/index.d.ts +2 -1
- package/dist/src/vended-tools/file-editor/index.d.ts.map +1 -1
- package/dist/src/vended-tools/file-editor/index.js +1 -1
- package/dist/src/vended-tools/file-editor/index.js.map +1 -1
- package/package.json +59 -6
- package/dist/src/utils/shell-quote.d.ts +0 -12
- package/dist/src/utils/shell-quote.d.ts.map +0 -1
- package/dist/src/utils/shell-quote.js +0 -14
- package/dist/src/utils/shell-quote.js.map +0 -1
|
@@ -0,0 +1,637 @@
|
|
|
1
|
+
import { describe, it, expect, vi } from 'vitest';
|
|
2
|
+
import { MemoryManager } from '../../memory-manager.js';
|
|
3
|
+
import { InvocationTrigger, IntervalTrigger } from '../triggers.js';
|
|
4
|
+
import { ExtractionCoordinator, SAVE_FAILURES_BEFORE_BACKOFF, BACKOFF_PROBE_INTERVAL, } from '../coordinator.js';
|
|
5
|
+
import { resolveExtractionConfig } from '../resolve-extraction-config.js';
|
|
6
|
+
import { Message, TextBlock, ToolUseBlock } from '../../../types/messages.js';
|
|
7
|
+
import { AfterInvocationEvent, MessageAddedEvent } from '../../../hooks/events.js';
|
|
8
|
+
import { createMockAgent } from '../../../__fixtures__/agent-helpers.js';
|
|
9
|
+
import { MockMessageModel } from '../../../__fixtures__/mock-message-model.js';
|
|
10
|
+
/**
|
|
11
|
+
* Builds a writable store with an extraction config. `sink` chooses which write method(s) it has,
|
|
12
|
+
* letting tests target each route (extractor → `add`; no extractor → `addMessages`) and the
|
|
13
|
+
* construction-time sink validation.
|
|
14
|
+
*/
|
|
15
|
+
function createExtractionStore(name, extraction, sink = 'both', options) {
|
|
16
|
+
const store = {
|
|
17
|
+
name,
|
|
18
|
+
writable: true,
|
|
19
|
+
extraction,
|
|
20
|
+
search: vi.fn().mockResolvedValue(options?.entries ?? []),
|
|
21
|
+
add: vi.fn().mockResolvedValue(undefined),
|
|
22
|
+
addMessages: vi.fn().mockResolvedValue(undefined),
|
|
23
|
+
};
|
|
24
|
+
if (sink === 'add')
|
|
25
|
+
delete store.addMessages;
|
|
26
|
+
if (sink === 'addMessages')
|
|
27
|
+
delete store.add;
|
|
28
|
+
return store;
|
|
29
|
+
}
|
|
30
|
+
/** Wraps a store into the {@link ExtractionBinding} pair the coordinator takes, resolving its config. */
|
|
31
|
+
function asExtractionStore(store) {
|
|
32
|
+
return { store, config: resolveExtractionConfig(store.extraction, store) };
|
|
33
|
+
}
|
|
34
|
+
function userMsg(text) {
|
|
35
|
+
return new Message({ role: 'user', content: [new TextBlock(text)] });
|
|
36
|
+
}
|
|
37
|
+
/** Drives the lifecycle: adds messages, then fires the AfterInvocationEvent hook(s). */
|
|
38
|
+
async function addMessages(agent, ...messages) {
|
|
39
|
+
for (const message of messages) {
|
|
40
|
+
await invokeAll(agent, new MessageAddedEvent({ agent, message, invocationState: {} }));
|
|
41
|
+
}
|
|
42
|
+
}
|
|
43
|
+
async function invokeAll(agent, event) {
|
|
44
|
+
const hooks = agent.trackedHooks.filter((h) => h.eventType === event.constructor);
|
|
45
|
+
for (const hook of hooks) {
|
|
46
|
+
await hook.callback(event);
|
|
47
|
+
}
|
|
48
|
+
}
|
|
49
|
+
/**
|
|
50
|
+
* Fires the AfterInvocationEvent hook(s), then flushes the manager so the fire-and-forget background
|
|
51
|
+
* extraction has completed before assertions run.
|
|
52
|
+
*/
|
|
53
|
+
async function fireInvocation(agent, mm) {
|
|
54
|
+
await invokeAll(agent, new AfterInvocationEvent({ agent, invocationState: {} }));
|
|
55
|
+
await mm.flush();
|
|
56
|
+
}
|
|
57
|
+
describe('MemoryManager extraction', () => {
|
|
58
|
+
describe('constructor validation', () => {
|
|
59
|
+
it('throws when an extraction store is not writable', () => {
|
|
60
|
+
const store = {
|
|
61
|
+
name: 's',
|
|
62
|
+
writable: false,
|
|
63
|
+
search: vi.fn(),
|
|
64
|
+
extraction: { trigger: [new InvocationTrigger()] },
|
|
65
|
+
};
|
|
66
|
+
expect(() => new MemoryManager({ stores: [store] })).toThrow('not writable');
|
|
67
|
+
});
|
|
68
|
+
it('throws when an extraction config has no triggers', () => {
|
|
69
|
+
const store = createExtractionStore('s', { trigger: [] });
|
|
70
|
+
expect(() => new MemoryManager({ stores: [store] })).toThrow('no triggers');
|
|
71
|
+
});
|
|
72
|
+
it('allows a store writable only via addMessages', () => {
|
|
73
|
+
const store = createExtractionStore('s', { trigger: [new InvocationTrigger()] }, 'addMessages');
|
|
74
|
+
expect(() => new MemoryManager({ stores: [store] })).not.toThrow();
|
|
75
|
+
});
|
|
76
|
+
it('throws when a writable store has neither add nor addMessages', () => {
|
|
77
|
+
const store = { name: 's', writable: true, search: vi.fn() };
|
|
78
|
+
expect(() => new MemoryManager({ stores: [store] })).toThrow('no add or addMessages');
|
|
79
|
+
});
|
|
80
|
+
it('rejects addToolConfig targeting an addMessages-only store', () => {
|
|
81
|
+
const store = createExtractionStore('s', { trigger: [new InvocationTrigger()] }, 'addMessages');
|
|
82
|
+
expect(() => new MemoryManager({ stores: [store], addToolConfig: true })).toThrow('no writable stores implement add');
|
|
83
|
+
});
|
|
84
|
+
it('throws when extraction has an extractor but the store has no add', () => {
|
|
85
|
+
const extractor = { extract: vi.fn() };
|
|
86
|
+
const store = createExtractionStore('s', { trigger: [new InvocationTrigger()], extractor }, 'addMessages');
|
|
87
|
+
expect(() => new MemoryManager({ stores: [store] })).toThrow('has an extractor but no add method');
|
|
88
|
+
});
|
|
89
|
+
});
|
|
90
|
+
describe('config resolution', () => {
|
|
91
|
+
it('enables extraction with defaults via the boolean shorthand', async () => {
|
|
92
|
+
// `extraction: true` on an addMessages-capable store -> passthrough (no implicit model call).
|
|
93
|
+
const store = createExtractionStore('s', true, 'addMessages');
|
|
94
|
+
const mm = new MemoryManager({ stores: [store] });
|
|
95
|
+
const agent = createMockAgent();
|
|
96
|
+
mm.initAgent(agent);
|
|
97
|
+
await addMessages(agent, userMsg('remember this'));
|
|
98
|
+
// The default trigger fires every DEFAULT_EXTRACTION_TRIGGER_TURNS turns; flush forces the write.
|
|
99
|
+
await mm.flush();
|
|
100
|
+
expect(store.addMessages).toHaveBeenCalledTimes(1);
|
|
101
|
+
});
|
|
102
|
+
it('treats extraction: false as disabled (no hooks, no writes)', async () => {
|
|
103
|
+
const store = createExtractionStore('s', false, 'addMessages');
|
|
104
|
+
const mm = new MemoryManager({ stores: [store] });
|
|
105
|
+
const agent = createMockAgent();
|
|
106
|
+
mm.initAgent(agent);
|
|
107
|
+
await addMessages(agent, userMsg('ignored'));
|
|
108
|
+
await mm.flush();
|
|
109
|
+
expect(store.addMessages).not.toHaveBeenCalled();
|
|
110
|
+
expect(agent.trackedHooks.filter((h) => h.eventType === AfterInvocationEvent)).toHaveLength(0);
|
|
111
|
+
});
|
|
112
|
+
it('defaults an add-only store to a ModelExtractor (extractor route)', async () => {
|
|
113
|
+
const store = createExtractionStore('s', { trigger: [new InvocationTrigger()] }, 'add');
|
|
114
|
+
// The default ModelExtractor calls the agent's model; the mock returns a JSON array of facts.
|
|
115
|
+
const model = new MockMessageModel();
|
|
116
|
+
model.addTurn({ type: 'textBlock', text: '[{"content":"a durable fact"}]' });
|
|
117
|
+
const mm = new MemoryManager({ stores: [store] });
|
|
118
|
+
const agent = createMockAgent({ extra: { model: model } });
|
|
119
|
+
mm.initAgent(agent);
|
|
120
|
+
await addMessages(agent, userMsg('I like dark mode'));
|
|
121
|
+
await fireInvocation(agent, mm);
|
|
122
|
+
expect(store.add).toHaveBeenCalledWith('a durable fact', undefined);
|
|
123
|
+
});
|
|
124
|
+
it('defaults a both-sinks store to the passthrough (no implicit model call)', async () => {
|
|
125
|
+
const store = createExtractionStore('s', { trigger: [new InvocationTrigger()] }, 'both');
|
|
126
|
+
const mm = new MemoryManager({ stores: [store] });
|
|
127
|
+
const agent = createMockAgent();
|
|
128
|
+
mm.initAgent(agent);
|
|
129
|
+
await addMessages(agent, userMsg('hi'));
|
|
130
|
+
await fireInvocation(agent, mm);
|
|
131
|
+
expect(store.addMessages).toHaveBeenCalledTimes(1);
|
|
132
|
+
expect(store.add).not.toHaveBeenCalled();
|
|
133
|
+
});
|
|
134
|
+
});
|
|
135
|
+
describe('no-extractor passthrough', () => {
|
|
136
|
+
it('hands the raw MessageData batch to addMessages, roles preserved', async () => {
|
|
137
|
+
const store = createExtractionStore('s', { trigger: [new InvocationTrigger()] }, 'addMessages');
|
|
138
|
+
const mm = new MemoryManager({ stores: [store] });
|
|
139
|
+
const agent = createMockAgent();
|
|
140
|
+
mm.initAgent(agent);
|
|
141
|
+
await addMessages(agent, userMsg('I prefer dark mode'), new Message({ role: 'assistant', content: [new TextBlock('Noted')] }));
|
|
142
|
+
await fireInvocation(agent, mm);
|
|
143
|
+
expect(store.addMessages).toHaveBeenCalledTimes(1);
|
|
144
|
+
const batch = store.addMessages.mock.calls[0][0];
|
|
145
|
+
expect(batch).toHaveLength(2);
|
|
146
|
+
expect(batch[0].role).toBe('user');
|
|
147
|
+
expect(batch[1].role).toBe('assistant');
|
|
148
|
+
});
|
|
149
|
+
});
|
|
150
|
+
describe('addMessages context (per-message seqs)', () => {
|
|
151
|
+
it('passes sequenceNumbers index-aligned with the filtered batch', async () => {
|
|
152
|
+
const store = createExtractionStore('s', { trigger: [new InvocationTrigger()] }, 'addMessages');
|
|
153
|
+
const mm = new MemoryManager({ stores: [store] });
|
|
154
|
+
const agent = createMockAgent();
|
|
155
|
+
mm.initAgent(agent);
|
|
156
|
+
await addMessages(agent, userMsg('first'), userMsg('second'));
|
|
157
|
+
await fireInvocation(agent, mm);
|
|
158
|
+
const batch = store.addMessages.mock.calls[0][0];
|
|
159
|
+
const ctx = store.addMessages.mock.calls[0][1];
|
|
160
|
+
// Assert the whole context so an unexpected field added to the envelope later is caught.
|
|
161
|
+
expect(ctx).toEqual({ sequenceNumbers: [0, 1] });
|
|
162
|
+
expect(ctx.sequenceNumbers).toHaveLength(batch.length);
|
|
163
|
+
});
|
|
164
|
+
it('keeps a message seq when a message before it is filtered to empty (gap)', async () => {
|
|
165
|
+
const store = createExtractionStore('s', { trigger: [new InvocationTrigger()] }, 'addMessages');
|
|
166
|
+
const mm = new MemoryManager({ stores: [store] });
|
|
167
|
+
const agent = createMockAgent();
|
|
168
|
+
mm.initAgent(agent);
|
|
169
|
+
// seq 0 (kept), seq 1 (tool-only, filtered to empty and dropped), seq 2 (kept).
|
|
170
|
+
const toolOnly = new Message({
|
|
171
|
+
role: 'assistant',
|
|
172
|
+
content: [new ToolUseBlock({ name: 't', toolUseId: '1', input: {} })],
|
|
173
|
+
});
|
|
174
|
+
await addMessages(agent, userMsg('a'), toolOnly, userMsg('b'));
|
|
175
|
+
await fireInvocation(agent, mm);
|
|
176
|
+
const batch = store.addMessages.mock.calls[0][0];
|
|
177
|
+
const ctx = store.addMessages.mock.calls[0][1];
|
|
178
|
+
expect(batch).toHaveLength(2);
|
|
179
|
+
// The dropped message leaves a gap: 1 is missing, the surviving messages keep their own seqs.
|
|
180
|
+
expect(ctx).toEqual({ sequenceNumbers: [0, 2] });
|
|
181
|
+
});
|
|
182
|
+
it('re-fires the same seqs for a multi-message batch whose save failed (stable across retries)', async () => {
|
|
183
|
+
const store = createExtractionStore('s', { trigger: [new InvocationTrigger()] }, 'addMessages');
|
|
184
|
+
store.addMessages.mockRejectedValueOnce(new Error('backend down'));
|
|
185
|
+
const mm = new MemoryManager({ stores: [store] });
|
|
186
|
+
const agent = createMockAgent();
|
|
187
|
+
mm.initAgent(agent);
|
|
188
|
+
await addMessages(agent, userMsg('first'), userMsg('second'));
|
|
189
|
+
await fireInvocation(agent, mm); // fails, mark rolled back
|
|
190
|
+
await fireInvocation(agent, mm); // retries the same batch
|
|
191
|
+
expect(store.addMessages).toHaveBeenCalledTimes(2);
|
|
192
|
+
const first = store.addMessages.mock.calls[0][1];
|
|
193
|
+
const second = store.addMessages.mock.calls[1][1];
|
|
194
|
+
expect(first).toEqual({ sequenceNumbers: [0, 1] });
|
|
195
|
+
// The retry carries the identical context in order, not a fresh renumbering.
|
|
196
|
+
expect(second).toEqual(first);
|
|
197
|
+
});
|
|
198
|
+
it('anchors the seq to the message, not the batch position', async () => {
|
|
199
|
+
const store = createExtractionStore('s', { trigger: [new InvocationTrigger()] }, 'addMessages');
|
|
200
|
+
const mm = new MemoryManager({ stores: [store] });
|
|
201
|
+
const agent = createMockAgent();
|
|
202
|
+
mm.initAgent(agent);
|
|
203
|
+
// First turn (seq 0) saves cleanly; second turn (seq 1) fails then retries.
|
|
204
|
+
await addMessages(agent, userMsg('turn one'));
|
|
205
|
+
await fireInvocation(agent, mm);
|
|
206
|
+
store.addMessages.mockRejectedValueOnce(new Error('backend down'));
|
|
207
|
+
await addMessages(agent, userMsg('turn two'));
|
|
208
|
+
await fireInvocation(agent, mm); // fails, rolled back
|
|
209
|
+
await fireInvocation(agent, mm); // retries turn two
|
|
210
|
+
expect(store.addMessages).toHaveBeenCalledTimes(3);
|
|
211
|
+
const retried = store.addMessages.mock.calls[2][1];
|
|
212
|
+
// Retried batch carries the message's original seq (1), not a renumbered 0.
|
|
213
|
+
expect(retried).toEqual({ sequenceNumbers: [1] });
|
|
214
|
+
});
|
|
215
|
+
it('gives each store index-aligned seqs from the shared buffer', async () => {
|
|
216
|
+
const a = createExtractionStore('a', { trigger: [new InvocationTrigger()] }, 'addMessages');
|
|
217
|
+
const b = createExtractionStore('b', { trigger: [new InvocationTrigger()] }, 'addMessages');
|
|
218
|
+
const mm = new MemoryManager({ stores: [a, b] });
|
|
219
|
+
const agent = createMockAgent();
|
|
220
|
+
mm.initAgent(agent);
|
|
221
|
+
await addMessages(agent, userMsg('first'), userMsg('second'));
|
|
222
|
+
await fireInvocation(agent, mm);
|
|
223
|
+
const ctxA = a.addMessages.mock.calls[0][1];
|
|
224
|
+
const ctxB = b.addMessages.mock.calls[0][1];
|
|
225
|
+
expect(ctxA).toEqual({ sequenceNumbers: [0, 1] });
|
|
226
|
+
expect(ctxB).toEqual({ sequenceNumbers: [0, 1] });
|
|
227
|
+
});
|
|
228
|
+
it('does not pass sequenceNumbers on the extractor route', async () => {
|
|
229
|
+
const extractor = { extract: vi.fn().mockResolvedValue([{ content: 'fact' }]) };
|
|
230
|
+
const store = createExtractionStore('s', { trigger: [new InvocationTrigger()], extractor }, 'both');
|
|
231
|
+
const mm = new MemoryManager({ stores: [store] });
|
|
232
|
+
const agent = createMockAgent();
|
|
233
|
+
mm.initAgent(agent);
|
|
234
|
+
await addMessages(agent, userMsg('something happened'));
|
|
235
|
+
await fireInvocation(agent, mm);
|
|
236
|
+
expect(store.addMessages).not.toHaveBeenCalled();
|
|
237
|
+
// The extractor receives (messages, context); the context is the extractor envelope only, with
|
|
238
|
+
// no sequenceNumbers. Assert the whole object so a future stray field is caught positively.
|
|
239
|
+
expect(extractor.extract).toHaveBeenCalledTimes(1);
|
|
240
|
+
const extractArgs = extractor.extract.mock.calls[0];
|
|
241
|
+
expect(extractArgs).toHaveLength(2);
|
|
242
|
+
expect(extractArgs[1]).toEqual({ defaultModel: undefined });
|
|
243
|
+
});
|
|
244
|
+
});
|
|
245
|
+
describe('extractor route', () => {
|
|
246
|
+
it('calls the extractor and writes each entry via add', async () => {
|
|
247
|
+
const extractor = {
|
|
248
|
+
extract: vi.fn().mockResolvedValue([{ content: 'fact one' }, { content: 'fact two', metadata: { k: 'v' } }]),
|
|
249
|
+
};
|
|
250
|
+
const store = createExtractionStore('s', { trigger: [new InvocationTrigger()], extractor }, 'both');
|
|
251
|
+
const mm = new MemoryManager({ stores: [store] });
|
|
252
|
+
const agent = createMockAgent();
|
|
253
|
+
mm.initAgent(agent);
|
|
254
|
+
await addMessages(agent, userMsg('something happened'));
|
|
255
|
+
await fireInvocation(agent, mm);
|
|
256
|
+
expect(extractor.extract).toHaveBeenCalledTimes(1);
|
|
257
|
+
expect(store.add).toHaveBeenCalledTimes(2);
|
|
258
|
+
expect(store.add.mock.calls[0][0]).toBe('fact one');
|
|
259
|
+
expect(store.add.mock.calls[1][0]).toBe('fact two');
|
|
260
|
+
expect(store.add.mock.calls[1][1]).toEqual({ k: 'v' });
|
|
261
|
+
// Extractor route never uses the batch sink.
|
|
262
|
+
expect(store.addMessages).not.toHaveBeenCalled();
|
|
263
|
+
});
|
|
264
|
+
it('passes the agent model as defaultModel to the extractor', async () => {
|
|
265
|
+
const extractor = { extract: vi.fn().mockResolvedValue([]) };
|
|
266
|
+
const store = createExtractionStore('s', { trigger: [new InvocationTrigger()], extractor });
|
|
267
|
+
const mm = new MemoryManager({ stores: [store] });
|
|
268
|
+
const fakeModel = { id: 'model' };
|
|
269
|
+
const agent = createMockAgent({ extra: { model: fakeModel } });
|
|
270
|
+
mm.initAgent(agent);
|
|
271
|
+
await addMessages(agent, userMsg('hi'));
|
|
272
|
+
await fireInvocation(agent, mm);
|
|
273
|
+
expect(extractor.extract).toHaveBeenCalledWith(expect.any(Array), { defaultModel: fakeModel });
|
|
274
|
+
});
|
|
275
|
+
it('writes entries concurrently rather than serially', async () => {
|
|
276
|
+
// Both add() calls should be in flight before either resolves -> the second is invoked while
|
|
277
|
+
// the first is still pending (would be impossible with a serial await loop).
|
|
278
|
+
let firstInvokedDuringSecond = false;
|
|
279
|
+
let secondStarted = false;
|
|
280
|
+
const extractor = {
|
|
281
|
+
extract: vi.fn().mockResolvedValue([{ content: 'a' }, { content: 'b' }]),
|
|
282
|
+
};
|
|
283
|
+
const store = createExtractionStore('s', { trigger: [new InvocationTrigger()], extractor }, 'add');
|
|
284
|
+
let firstResolve;
|
|
285
|
+
store.add
|
|
286
|
+
.mockImplementationOnce(() => new Promise((resolve) => {
|
|
287
|
+
firstResolve = () => {
|
|
288
|
+
firstInvokedDuringSecond = secondStarted;
|
|
289
|
+
resolve();
|
|
290
|
+
};
|
|
291
|
+
}))
|
|
292
|
+
.mockImplementationOnce(() => {
|
|
293
|
+
secondStarted = true;
|
|
294
|
+
firstResolve();
|
|
295
|
+
return Promise.resolve();
|
|
296
|
+
});
|
|
297
|
+
const mm = new MemoryManager({ stores: [store] });
|
|
298
|
+
const agent = createMockAgent();
|
|
299
|
+
mm.initAgent(agent);
|
|
300
|
+
await addMessages(agent, userMsg('x'));
|
|
301
|
+
await fireInvocation(agent, mm);
|
|
302
|
+
expect(store.add).toHaveBeenCalledTimes(2);
|
|
303
|
+
expect(firstInvokedDuringSecond).toBe(true);
|
|
304
|
+
});
|
|
305
|
+
it('rolls back and retries the whole batch if any entry write fails', async () => {
|
|
306
|
+
const extractor = {
|
|
307
|
+
extract: vi.fn().mockResolvedValue([{ content: 'a' }, { content: 'b' }]),
|
|
308
|
+
};
|
|
309
|
+
const store = createExtractionStore('s', { trigger: [new InvocationTrigger()], extractor }, 'add');
|
|
310
|
+
// First batch: second entry fails -> AggregateError -> mark rolled back.
|
|
311
|
+
store.add.mockResolvedValueOnce(undefined).mockRejectedValueOnce(new Error('write failed'));
|
|
312
|
+
const mm = new MemoryManager({ stores: [store] });
|
|
313
|
+
const agent = createMockAgent();
|
|
314
|
+
mm.initAgent(agent);
|
|
315
|
+
await addMessages(agent, userMsg('x'));
|
|
316
|
+
await fireInvocation(agent, mm); // fails, rolled back
|
|
317
|
+
await fireInvocation(agent, mm); // retries the same batch
|
|
318
|
+
// 2 writes first attempt + 2 on retry.
|
|
319
|
+
expect(store.add).toHaveBeenCalledTimes(4);
|
|
320
|
+
expect(extractor.extract).toHaveBeenCalledTimes(2);
|
|
321
|
+
});
|
|
322
|
+
});
|
|
323
|
+
describe('message filter', () => {
|
|
324
|
+
it('drops toolUse/toolResult blocks by default and empties', async () => {
|
|
325
|
+
const store = createExtractionStore('s', { trigger: [new InvocationTrigger()] }, 'addMessages');
|
|
326
|
+
const mm = new MemoryManager({ stores: [store] });
|
|
327
|
+
const agent = createMockAgent();
|
|
328
|
+
mm.initAgent(agent);
|
|
329
|
+
const toolOnly = new Message({
|
|
330
|
+
role: 'assistant',
|
|
331
|
+
content: [new ToolUseBlock({ name: 't', toolUseId: '1', input: {} })],
|
|
332
|
+
});
|
|
333
|
+
await addMessages(agent, userMsg('keep me'), toolOnly);
|
|
334
|
+
await fireInvocation(agent, mm);
|
|
335
|
+
const batch = store.addMessages.mock.calls[0][0];
|
|
336
|
+
// tool-only message dropped entirely; text message kept.
|
|
337
|
+
expect(batch).toHaveLength(1);
|
|
338
|
+
expect(batch[0].role).toBe('user');
|
|
339
|
+
});
|
|
340
|
+
it('honors a custom filter', async () => {
|
|
341
|
+
const store = createExtractionStore('s', { trigger: [new InvocationTrigger()], filter: { exclude: ['text'] } }, 'addMessages');
|
|
342
|
+
const mm = new MemoryManager({ stores: [store] });
|
|
343
|
+
const agent = createMockAgent();
|
|
344
|
+
mm.initAgent(agent);
|
|
345
|
+
await addMessages(agent, userMsg('this is text and should be excluded'));
|
|
346
|
+
await fireInvocation(agent, mm);
|
|
347
|
+
// The only message was text, excluded -> emptied -> nothing to write.
|
|
348
|
+
expect(store.addMessages).not.toHaveBeenCalled();
|
|
349
|
+
});
|
|
350
|
+
});
|
|
351
|
+
describe('high-water-mark dedup', () => {
|
|
352
|
+
it('processes only messages added since the last fire', async () => {
|
|
353
|
+
const store = createExtractionStore('s', { trigger: [new InvocationTrigger()] }, 'addMessages');
|
|
354
|
+
const mm = new MemoryManager({ stores: [store] });
|
|
355
|
+
const agent = createMockAgent();
|
|
356
|
+
mm.initAgent(agent);
|
|
357
|
+
await addMessages(agent, userMsg('turn one'));
|
|
358
|
+
await fireInvocation(agent, mm);
|
|
359
|
+
await addMessages(agent, userMsg('turn two'));
|
|
360
|
+
await fireInvocation(agent, mm);
|
|
361
|
+
expect(store.addMessages).toHaveBeenCalledTimes(2);
|
|
362
|
+
expect(store.addMessages.mock.calls[0][0]).toHaveLength(1);
|
|
363
|
+
const second = store.addMessages.mock.calls[1][0];
|
|
364
|
+
expect(second).toHaveLength(1);
|
|
365
|
+
expect(second[0].content[0].text).toBe('turn two');
|
|
366
|
+
});
|
|
367
|
+
it('does nothing when no new messages since the mark', async () => {
|
|
368
|
+
const store = createExtractionStore('s', { trigger: [new InvocationTrigger()] }, 'addMessages');
|
|
369
|
+
const mm = new MemoryManager({ stores: [store] });
|
|
370
|
+
const agent = createMockAgent();
|
|
371
|
+
mm.initAgent(agent);
|
|
372
|
+
await addMessages(agent, userMsg('only turn'));
|
|
373
|
+
await fireInvocation(agent, mm);
|
|
374
|
+
await fireInvocation(agent, mm); // no new messages
|
|
375
|
+
expect(store.addMessages).toHaveBeenCalledTimes(1);
|
|
376
|
+
});
|
|
377
|
+
it('retries the same messages on the next fire if a write fails', async () => {
|
|
378
|
+
const store = createExtractionStore('s', { trigger: [new InvocationTrigger()] }, 'addMessages');
|
|
379
|
+
store.addMessages.mockRejectedValueOnce(new Error('backend down'));
|
|
380
|
+
const mm = new MemoryManager({ stores: [store] });
|
|
381
|
+
const agent = createMockAgent();
|
|
382
|
+
mm.initAgent(agent);
|
|
383
|
+
await addMessages(agent, userMsg('important'));
|
|
384
|
+
await fireInvocation(agent, mm); // fails, mark rolled back
|
|
385
|
+
await fireInvocation(agent, mm); // retries
|
|
386
|
+
expect(store.addMessages).toHaveBeenCalledTimes(2);
|
|
387
|
+
expect(store.addMessages.mock.calls[1][0]).toHaveLength(1);
|
|
388
|
+
});
|
|
389
|
+
});
|
|
390
|
+
describe('backing off and recovering from a failing store', () => {
|
|
391
|
+
/** Runs one turn: adds a message, fires the trigger, flushes. */
|
|
392
|
+
async function turn(agent, mm, text) {
|
|
393
|
+
await addMessages(agent, userMsg(text));
|
|
394
|
+
await fireInvocation(agent, mm);
|
|
395
|
+
}
|
|
396
|
+
/** A coordinator with one always-failing store, driven via process() for exact attempt counts. */
|
|
397
|
+
function failingCoordinator() {
|
|
398
|
+
const store = createExtractionStore('s', { trigger: [new InvocationTrigger()] }, 'addMessages');
|
|
399
|
+
store.addMessages.mockRejectedValue(new Error('backend down'));
|
|
400
|
+
const coordinator = new ExtractionCoordinator([asExtractionStore(store)], {});
|
|
401
|
+
return { coordinator, store };
|
|
402
|
+
}
|
|
403
|
+
it('backs off to periodic probes after SAVE_FAILURES_BEFORE_BACKOFF failures in a row', async () => {
|
|
404
|
+
const { coordinator, store } = failingCoordinator();
|
|
405
|
+
// Each call buffers a message and requests a save; every save fails. Run enough backed-off
|
|
406
|
+
// requests for exactly two probe intervals.
|
|
407
|
+
const PROBES = 2;
|
|
408
|
+
for (let i = 0; i < SAVE_FAILURES_BEFORE_BACKOFF + BACKOFF_PROBE_INTERVAL * PROBES; i++) {
|
|
409
|
+
coordinator.record(userMsg(`m${i}`).toJSON());
|
|
410
|
+
await coordinator.process(store);
|
|
411
|
+
}
|
|
412
|
+
// Attempts every request until backoff, then only every BACKOFF_PROBE_INTERVAL-th request.
|
|
413
|
+
expect(store.addMessages).toHaveBeenCalledTimes(SAVE_FAILURES_BEFORE_BACKOFF + PROBES);
|
|
414
|
+
});
|
|
415
|
+
it('recovers and saves the buffered backlog when the store comes back', async () => {
|
|
416
|
+
const store = createExtractionStore('s', { trigger: [new InvocationTrigger()] }, 'addMessages');
|
|
417
|
+
store.addMessages.mockRejectedValue(new Error('down'));
|
|
418
|
+
const mm = new MemoryManager({ stores: [store] });
|
|
419
|
+
const agent = createMockAgent();
|
|
420
|
+
mm.initAgent(agent);
|
|
421
|
+
// Enter backoff.
|
|
422
|
+
for (let i = 0; i < SAVE_FAILURES_BEFORE_BACKOFF; i++) {
|
|
423
|
+
await turn(agent, mm, `down${i}`);
|
|
424
|
+
}
|
|
425
|
+
// Store recovers; run probe-interval turns so a probe lands and succeeds.
|
|
426
|
+
store.addMessages.mockReset();
|
|
427
|
+
store.addMessages.mockResolvedValue(undefined);
|
|
428
|
+
for (let i = 0; i < BACKOFF_PROBE_INTERVAL; i++) {
|
|
429
|
+
await turn(agent, mm, `up${i}`);
|
|
430
|
+
}
|
|
431
|
+
// The recovering probe saved, and its batch includes the messages buffered during the outage.
|
|
432
|
+
expect(store.addMessages).toHaveBeenCalled();
|
|
433
|
+
const saved = store.addMessages.mock.calls.flatMap((c) => c[0]);
|
|
434
|
+
const texts = saved.flatMap((m) => m.content.map((b) => ('text' in b ? b.text : '')));
|
|
435
|
+
expect(texts).toContain('down0');
|
|
436
|
+
expect(texts).toContain('up0');
|
|
437
|
+
});
|
|
438
|
+
it('a healthy store keeps saving every request while a sibling is backed off', async () => {
|
|
439
|
+
const bad = createExtractionStore('bad', { trigger: [new InvocationTrigger()] }, 'addMessages');
|
|
440
|
+
bad.addMessages.mockRejectedValue(new Error('down'));
|
|
441
|
+
const good = createExtractionStore('good', { trigger: [new InvocationTrigger()] }, 'addMessages');
|
|
442
|
+
const coordinator = new ExtractionCoordinator([asExtractionStore(bad), asExtractionStore(good)], {});
|
|
443
|
+
const PROBES = 2;
|
|
444
|
+
const requests = SAVE_FAILURES_BEFORE_BACKOFF + BACKOFF_PROBE_INTERVAL * PROBES;
|
|
445
|
+
for (let i = 0; i < requests; i++) {
|
|
446
|
+
coordinator.record(userMsg(`m${i}`).toJSON());
|
|
447
|
+
await coordinator.process(bad);
|
|
448
|
+
await coordinator.process(good);
|
|
449
|
+
}
|
|
450
|
+
// Good store saves every request; bad store stops at backoff + its probes.
|
|
451
|
+
expect(good.addMessages).toHaveBeenCalledTimes(requests);
|
|
452
|
+
expect(bad.addMessages).toHaveBeenCalledTimes(SAVE_FAILURES_BEFORE_BACKOFF + PROBES);
|
|
453
|
+
});
|
|
454
|
+
it('flush resolves even when a store is failing', async () => {
|
|
455
|
+
const store = createExtractionStore('s', { trigger: [new InvocationTrigger()] }, 'addMessages');
|
|
456
|
+
store.addMessages.mockRejectedValue(new Error('down'));
|
|
457
|
+
const mm = new MemoryManager({ stores: [store] });
|
|
458
|
+
const agent = createMockAgent();
|
|
459
|
+
mm.initAgent(agent);
|
|
460
|
+
await addMessages(agent, userMsg('x'));
|
|
461
|
+
await expect(fireInvocation(agent, mm)).resolves.toBeUndefined();
|
|
462
|
+
await expect(mm.flush()).resolves.toBeUndefined();
|
|
463
|
+
});
|
|
464
|
+
it('flush bypasses backoff and writes the backlog of a recovered store', async () => {
|
|
465
|
+
const store = createExtractionStore('s', { trigger: [new InvocationTrigger()] }, 'addMessages');
|
|
466
|
+
store.addMessages.mockRejectedValue(new Error('down'));
|
|
467
|
+
const coordinator = new ExtractionCoordinator([asExtractionStore(store)], {});
|
|
468
|
+
// Drive the store into backoff.
|
|
469
|
+
for (let i = 0; i < SAVE_FAILURES_BEFORE_BACKOFF; i++) {
|
|
470
|
+
coordinator.record(userMsg(`down${i}`).toJSON());
|
|
471
|
+
await coordinator.process(store);
|
|
472
|
+
}
|
|
473
|
+
// Store recovers and a final message arrives, but no probe has landed yet.
|
|
474
|
+
store.addMessages.mockReset();
|
|
475
|
+
store.addMessages.mockResolvedValue(undefined);
|
|
476
|
+
coordinator.record(userMsg('final').toJSON());
|
|
477
|
+
// A single flush must write the backlog despite backoff (not be probe-gated to a no-op).
|
|
478
|
+
await coordinator.flush();
|
|
479
|
+
expect(store.addMessages).toHaveBeenCalledTimes(1);
|
|
480
|
+
const saved = store.addMessages.mock.calls[0][0].flatMap((m) => m.content.map((b) => ('text' in b ? b.text : '')));
|
|
481
|
+
expect(saved).toContain('final');
|
|
482
|
+
expect(saved).toContain('down0');
|
|
483
|
+
});
|
|
484
|
+
it('a fully-filtered (empty) turn does not reset the failure streak', async () => {
|
|
485
|
+
// A no-extractor store; the default filter drops tool blocks. A turn of only tool blocks
|
|
486
|
+
// filters to empty, so the backend is never called - it must not be mistaken for a recovery and
|
|
487
|
+
// clear the prior failures. We prove that by showing backoff still engages at the threshold.
|
|
488
|
+
const store = createExtractionStore('s', { trigger: [new InvocationTrigger()] }, 'addMessages');
|
|
489
|
+
store.addMessages.mockRejectedValue(new Error('down'));
|
|
490
|
+
const coordinator = new ExtractionCoordinator([asExtractionStore(store)], {});
|
|
491
|
+
// One short of backoff.
|
|
492
|
+
for (let i = 0; i < SAVE_FAILURES_BEFORE_BACKOFF - 1; i++) {
|
|
493
|
+
coordinator.record(userMsg(`m${i}`).toJSON());
|
|
494
|
+
await coordinator.process(store);
|
|
495
|
+
}
|
|
496
|
+
// An all-tool-blocks turn filters to empty: backend not called, streak not reset.
|
|
497
|
+
const toolOnly = new Message({
|
|
498
|
+
role: 'assistant',
|
|
499
|
+
content: [new ToolUseBlock({ name: 't', toolUseId: '1', input: {} })],
|
|
500
|
+
});
|
|
501
|
+
coordinator.record(toolOnly.toJSON());
|
|
502
|
+
await coordinator.process(store);
|
|
503
|
+
// The Nth real failure now tips into backoff (it would not if the empty turn had reset to 0).
|
|
504
|
+
coordinator.record(userMsg('nth').toJSON());
|
|
505
|
+
await coordinator.process(store);
|
|
506
|
+
// Backed off: the next request is probe-gated, so the backend isn't called immediately.
|
|
507
|
+
store.addMessages.mockClear();
|
|
508
|
+
coordinator.record(userMsg('after').toJSON());
|
|
509
|
+
await coordinator.process(store);
|
|
510
|
+
expect(store.addMessages).not.toHaveBeenCalled();
|
|
511
|
+
});
|
|
512
|
+
});
|
|
513
|
+
describe('triggers', () => {
|
|
514
|
+
it('IntervalTrigger fires every N invocations', async () => {
|
|
515
|
+
const store = createExtractionStore('s', { trigger: [new IntervalTrigger({ turns: 2 })] }, 'addMessages');
|
|
516
|
+
const mm = new MemoryManager({ stores: [store] });
|
|
517
|
+
const agent = createMockAgent();
|
|
518
|
+
mm.initAgent(agent);
|
|
519
|
+
// Fire the raw hook (not the flushing helper) so we observe interval gating, not flush's
|
|
520
|
+
// force-completion.
|
|
521
|
+
await addMessages(agent, userMsg('a'));
|
|
522
|
+
await invokeAll(agent, new AfterInvocationEvent({ agent, invocationState: {} })); // count 1, no fire
|
|
523
|
+
expect(store.addMessages).not.toHaveBeenCalled();
|
|
524
|
+
await addMessages(agent, userMsg('b'));
|
|
525
|
+
await fireInvocation(agent, mm); // count 2, fire (+ flush drains it)
|
|
526
|
+
expect(store.addMessages).toHaveBeenCalledTimes(1);
|
|
527
|
+
expect(store.addMessages.mock.calls[0][0]).toHaveLength(2);
|
|
528
|
+
});
|
|
529
|
+
it('IntervalTrigger rejects non-positive turns', () => {
|
|
530
|
+
expect(() => new IntervalTrigger({ turns: 0 })).toThrow('positive integer');
|
|
531
|
+
});
|
|
532
|
+
it('accepts a single trigger (not wrapped in an array)', async () => {
|
|
533
|
+
const store = createExtractionStore('s', { trigger: new InvocationTrigger() }, 'addMessages');
|
|
534
|
+
const mm = new MemoryManager({ stores: [store] });
|
|
535
|
+
const agent = createMockAgent();
|
|
536
|
+
mm.initAgent(agent);
|
|
537
|
+
await addMessages(agent, userMsg('hi'));
|
|
538
|
+
await fireInvocation(agent, mm);
|
|
539
|
+
expect(store.addMessages).toHaveBeenCalledTimes(1);
|
|
540
|
+
});
|
|
541
|
+
it('composes multiple triggers (extraction fires on any)', async () => {
|
|
542
|
+
// Both an interval(every 2) and an invocation(every turn): the invocation trigger fires turn 1.
|
|
543
|
+
const store = createExtractionStore('s', { trigger: [new IntervalTrigger({ turns: 2 }), new InvocationTrigger()] }, 'addMessages');
|
|
544
|
+
const mm = new MemoryManager({ stores: [store] });
|
|
545
|
+
const agent = createMockAgent();
|
|
546
|
+
mm.initAgent(agent);
|
|
547
|
+
await addMessages(agent, userMsg('a'));
|
|
548
|
+
await fireInvocation(agent, mm);
|
|
549
|
+
// Invocation trigger fired on turn 1 even though interval would not have.
|
|
550
|
+
expect(store.addMessages).toHaveBeenCalledTimes(1);
|
|
551
|
+
});
|
|
552
|
+
it('does not register hooks when no store has extraction', () => {
|
|
553
|
+
const store = { name: 's', writable: true, search: vi.fn(), add: vi.fn() };
|
|
554
|
+
const mm = new MemoryManager({ stores: [store] });
|
|
555
|
+
const agent = createMockAgent();
|
|
556
|
+
mm.initAgent(agent);
|
|
557
|
+
expect(agent.trackedHooks).toHaveLength(0);
|
|
558
|
+
});
|
|
559
|
+
});
|
|
560
|
+
describe('background execution', () => {
|
|
561
|
+
it('does not block the AfterInvocationEvent hook on the store write', async () => {
|
|
562
|
+
// A store whose write hangs until we release it. If extraction blocked the loop, awaiting the
|
|
563
|
+
// hook would never resolve while the write is pending.
|
|
564
|
+
let release;
|
|
565
|
+
const blocked = new Promise((resolve) => {
|
|
566
|
+
release = resolve;
|
|
567
|
+
});
|
|
568
|
+
const store = createExtractionStore('s', { trigger: [new InvocationTrigger()] }, 'addMessages');
|
|
569
|
+
store.addMessages.mockReturnValue(blocked);
|
|
570
|
+
const mm = new MemoryManager({ stores: [store] });
|
|
571
|
+
const agent = createMockAgent();
|
|
572
|
+
mm.initAgent(agent);
|
|
573
|
+
await addMessages(agent, userMsg('hello'));
|
|
574
|
+
// Fire the hook directly (not via the flushing helper) — it must resolve while the write hangs.
|
|
575
|
+
await invokeAll(agent, new AfterInvocationEvent({ agent, invocationState: {} }));
|
|
576
|
+
expect(store.addMessages).toHaveBeenCalledTimes(1);
|
|
577
|
+
// The write is still pending; releasing it lets flush() resolve.
|
|
578
|
+
release();
|
|
579
|
+
await mm.flush();
|
|
580
|
+
});
|
|
581
|
+
it('flush awaits the in-flight extraction write', async () => {
|
|
582
|
+
let resolved = false;
|
|
583
|
+
let release;
|
|
584
|
+
const blocked = new Promise((resolve) => {
|
|
585
|
+
release = () => {
|
|
586
|
+
resolved = true;
|
|
587
|
+
resolve();
|
|
588
|
+
};
|
|
589
|
+
});
|
|
590
|
+
const store = createExtractionStore('s', { trigger: [new InvocationTrigger()] }, 'addMessages');
|
|
591
|
+
store.addMessages.mockReturnValue(blocked);
|
|
592
|
+
const mm = new MemoryManager({ stores: [store] });
|
|
593
|
+
const agent = createMockAgent();
|
|
594
|
+
mm.initAgent(agent);
|
|
595
|
+
await addMessages(agent, userMsg('hello'));
|
|
596
|
+
await invokeAll(agent, new AfterInvocationEvent({ agent, invocationState: {} }));
|
|
597
|
+
const flushed = mm.flush();
|
|
598
|
+
release();
|
|
599
|
+
await flushed;
|
|
600
|
+
expect(resolved).toBe(true);
|
|
601
|
+
});
|
|
602
|
+
it('flush is a no-op when extraction is not configured', async () => {
|
|
603
|
+
const store = { name: 's', writable: true, search: vi.fn(), add: vi.fn() };
|
|
604
|
+
const mm = new MemoryManager({ stores: [store] });
|
|
605
|
+
mm.initAgent(createMockAgent());
|
|
606
|
+
await expect(mm.flush()).resolves.toBeUndefined();
|
|
607
|
+
});
|
|
608
|
+
it('flush force-extracts a buffered tail whose trigger never fired', async () => {
|
|
609
|
+
// IntervalTrigger(5) but the session ends after 2 turns -> the trigger never fires. flush()
|
|
610
|
+
// must still extract the buffered messages rather than lose them on graceful shutdown.
|
|
611
|
+
const store = createExtractionStore('s', { trigger: [new IntervalTrigger({ turns: 5 })] }, 'addMessages');
|
|
612
|
+
const mm = new MemoryManager({ stores: [store] });
|
|
613
|
+
const agent = createMockAgent();
|
|
614
|
+
mm.initAgent(agent);
|
|
615
|
+
await addMessages(agent, userMsg('a'));
|
|
616
|
+
await invokeAll(agent, new AfterInvocationEvent({ agent, invocationState: {} })); // count 1, no fire
|
|
617
|
+
await addMessages(agent, userMsg('b'));
|
|
618
|
+
await invokeAll(agent, new AfterInvocationEvent({ agent, invocationState: {} })); // count 2, no fire
|
|
619
|
+
expect(store.addMessages).not.toHaveBeenCalled();
|
|
620
|
+
await mm.flush();
|
|
621
|
+
expect(store.addMessages).toHaveBeenCalledTimes(1);
|
|
622
|
+
expect(store.addMessages.mock.calls[0][0]).toHaveLength(2);
|
|
623
|
+
});
|
|
624
|
+
it('flush does not re-extract messages already processed by a fired trigger', async () => {
|
|
625
|
+
const store = createExtractionStore('s', { trigger: [new InvocationTrigger()] }, 'addMessages');
|
|
626
|
+
const mm = new MemoryManager({ stores: [store] });
|
|
627
|
+
const agent = createMockAgent();
|
|
628
|
+
mm.initAgent(agent);
|
|
629
|
+
await addMessages(agent, userMsg('a'));
|
|
630
|
+
await fireInvocation(agent, mm); // invocation trigger already extracted + flushed
|
|
631
|
+
expect(store.addMessages).toHaveBeenCalledTimes(1);
|
|
632
|
+
await mm.flush(); // nothing fresh -> no-op
|
|
633
|
+
expect(store.addMessages).toHaveBeenCalledTimes(1);
|
|
634
|
+
});
|
|
635
|
+
});
|
|
636
|
+
});
|
|
637
|
+
//# sourceMappingURL=extraction.test.js.map
|