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,866 +1,866 @@
|
|
|
1
|
-
import { jest, describe, test, expect, beforeEach } from '@jest/globals';
|
|
2
|
-
import { createMockLogger
|
|
3
|
-
|
|
4
|
-
// Mock uuid
|
|
5
|
-
let uuidCounter = 0;
|
|
6
|
-
jest.unstable_mockModule('uuid', () => ({
|
|
7
|
-
v4: jest.fn(() => `mock-uuid-${++uuidCounter}`)
|
|
8
|
-
}));
|
|
9
|
-
|
|
10
|
-
const { default: TaskManagerTool } = await import('../taskManagerTool.js');
|
|
11
|
-
|
|
12
|
-
/**
|
|
13
|
-
* Helper: create a tool instance, a fake agent, and context.
|
|
14
|
-
*/
|
|
15
|
-
function createTestSetup() {
|
|
16
|
-
const logger = createMockLogger();
|
|
17
|
-
const tool = new TaskManagerTool({ description: 'test task manager' });
|
|
18
|
-
tool.logger = logger;
|
|
19
|
-
|
|
20
|
-
const agent = {
|
|
21
|
-
id: 'agent-1',
|
|
22
|
-
name: 'Test Agent',
|
|
23
|
-
lastActivity: null,
|
|
24
|
-
taskList: {
|
|
25
|
-
tasks: [],
|
|
26
|
-
lastUpdated: new Date().toISOString()
|
|
27
|
-
}
|
|
28
|
-
};
|
|
29
|
-
|
|
30
|
-
const agentPool = {
|
|
31
|
-
getAgent: jest.fn().mockResolvedValue(agent),
|
|
32
|
-
persistAgentState: jest.fn().mockResolvedValue(undefined)
|
|
33
|
-
};
|
|
34
|
-
|
|
35
|
-
const context = {
|
|
36
|
-
agentId: 'agent-1',
|
|
37
|
-
agentName: 'Test Agent',
|
|
38
|
-
agentPool,
|
|
39
|
-
projectDir: '/tmp/test'
|
|
40
|
-
};
|
|
41
|
-
|
|
42
|
-
return { tool, agent, agentPool, context, logger };
|
|
43
|
-
}
|
|
44
|
-
|
|
45
|
-
beforeEach(() => {
|
|
46
|
-
uuidCounter = 0;
|
|
47
|
-
});
|
|
48
|
-
|
|
49
|
-
describe('TaskManagerTool', () => {
|
|
50
|
-
// ── constructor ─────────────────────────────────────────────────
|
|
51
|
-
describe('constructor', () => {
|
|
52
|
-
test('initializes with supported actions, priorities, and statuses', () => {
|
|
53
|
-
const tool = new TaskManagerTool();
|
|
54
|
-
expect(tool.supportedActions).toContain('create');
|
|
55
|
-
expect(tool.supportedActions).toContain('sync');
|
|
56
|
-
expect(tool.supportedActions).toContain('analytics');
|
|
57
|
-
expect(tool.taskPriorities).toEqual(['urgent', 'high', 'medium', 'low']);
|
|
58
|
-
expect(tool.taskStatuses).toContain('pending');
|
|
59
|
-
expect(tool.taskStatuses).toContain('completed');
|
|
60
|
-
expect(tool.taskTemplates).toHaveProperty('bug-fix');
|
|
61
|
-
});
|
|
62
|
-
});
|
|
63
|
-
|
|
64
|
-
// ── getDescription ──────────────────────────────────────────────
|
|
65
|
-
describe('getDescription', () => {
|
|
66
|
-
test('returns a non-empty description string', () => {
|
|
67
|
-
const tool = new TaskManagerTool();
|
|
68
|
-
const desc = tool.getDescription();
|
|
69
|
-
expect(typeof desc).toBe('string');
|
|
70
|
-
expect(desc.length).toBeGreaterThan(50);
|
|
71
|
-
});
|
|
72
|
-
});
|
|
73
|
-
|
|
74
|
-
// ── parseParameters ─────────────────────────────────────────────
|
|
75
|
-
describe('parseParameters', () => {
|
|
76
|
-
test('returns rawContent for string input', () => {
|
|
77
|
-
const tool = new TaskManagerTool();
|
|
78
|
-
expect(tool.parseParameters('hello')).toEqual({ rawContent: 'hello' });
|
|
79
|
-
});
|
|
80
|
-
|
|
81
|
-
test('unwraps tag-parser format objects', () => {
|
|
82
|
-
const tool = new TaskManagerTool();
|
|
83
|
-
const result = tool.parseParameters({
|
|
84
|
-
action: { value: 'create', attributes: {} },
|
|
85
|
-
title: { value: 'My Task', attributes: {} }
|
|
86
|
-
});
|
|
87
|
-
expect(result.action).toBe('create');
|
|
88
|
-
expect(result.title).toBe('My Task');
|
|
89
|
-
});
|
|
90
|
-
|
|
91
|
-
test('passes through plain objects', () => {
|
|
92
|
-
const tool = new TaskManagerTool();
|
|
93
|
-
expect(tool.parseParameters({ action: 'list' })).toEqual({ action: 'list' });
|
|
94
|
-
});
|
|
95
|
-
|
|
96
|
-
test('returns non-object values as-is', () => {
|
|
97
|
-
const tool = new TaskManagerTool();
|
|
98
|
-
expect(tool.parseParameters(null)).toBeNull();
|
|
99
|
-
});
|
|
100
|
-
});
|
|
101
|
-
|
|
102
|
-
// ── create action ───────────────────────────────────────────────
|
|
103
|
-
describe('execute - create', () => {
|
|
104
|
-
test('creates a task with title, description, and priority', async () => {
|
|
105
|
-
const { tool, agent, context } = createTestSetup();
|
|
106
|
-
const result = await tool.execute(
|
|
107
|
-
{ action: 'create', title: 'Build API', description: 'REST endpoints', priority: 'high' },
|
|
108
|
-
context
|
|
109
|
-
);
|
|
110
|
-
expect(result.success).toBe(true);
|
|
111
|
-
expect(result.action).toBe('create');
|
|
112
|
-
expect(result.result.task.title).toBe('Build API');
|
|
113
|
-
expect(result.result.task.priority).toBe('high');
|
|
114
|
-
expect(result.result.task.status).toBe('pending');
|
|
115
|
-
expect(agent.taskList.tasks).toHaveLength(1);
|
|
116
|
-
});
|
|
117
|
-
|
|
118
|
-
test('uses medium as default priority', async () => {
|
|
119
|
-
const { tool, context } = createTestSetup();
|
|
120
|
-
const result = await tool.execute({ action: 'create', title: 'Default' }, context);
|
|
121
|
-
expect(result.result.task.priority).toBe('medium');
|
|
122
|
-
});
|
|
123
|
-
|
|
124
|
-
test('errors when title is missing', async () => {
|
|
125
|
-
const { tool, context } = createTestSetup();
|
|
126
|
-
const result = await tool.execute({ action: 'create', priority: 'low' }, context);
|
|
127
|
-
expect(result.success).toBe(false);
|
|
128
|
-
expect(result.error).toContain('title is required');
|
|
129
|
-
});
|
|
130
|
-
|
|
131
|
-
test('errors for invalid priority', async () => {
|
|
132
|
-
const { tool, context } = createTestSetup();
|
|
133
|
-
const result = await tool.execute(
|
|
134
|
-
{ action: 'create', title: 'Test', priority: 'superurgent' }, context
|
|
135
|
-
);
|
|
136
|
-
expect(result.success).toBe(false);
|
|
137
|
-
expect(result.error).toContain('Invalid priority');
|
|
138
|
-
});
|
|
139
|
-
});
|
|
140
|
-
|
|
141
|
-
// ── actions array format ────────────────────────────────────────
|
|
142
|
-
describe('execute - actions array format', () => {
|
|
143
|
-
test('unwraps first element of actions array', async () => {
|
|
144
|
-
const { tool, context } = createTestSetup();
|
|
145
|
-
const result = await tool.execute(
|
|
146
|
-
{ actions: [{ type: 'create', title: 'From array', priority: 'medium' }] },
|
|
147
|
-
context
|
|
148
|
-
);
|
|
149
|
-
expect(result.success).toBe(true);
|
|
150
|
-
expect(result.result.task.title).toBe('From array');
|
|
151
|
-
});
|
|
152
|
-
|
|
153
|
-
test('deep-unwraps tag-parser wrapped values in actions array', async () => {
|
|
154
|
-
const { tool, context } = createTestSetup();
|
|
155
|
-
const result = await tool.execute({
|
|
156
|
-
actions: [{
|
|
157
|
-
type: { value: 'create', attributes: {} },
|
|
158
|
-
title: { value: 'Wrapped', attributes: {} },
|
|
159
|
-
priority: 'medium'
|
|
160
|
-
}]
|
|
161
|
-
}, context);
|
|
162
|
-
expect(result.success).toBe(true);
|
|
163
|
-
expect(result.result.task.title).toBe('Wrapped');
|
|
164
|
-
});
|
|
165
|
-
});
|
|
166
|
-
|
|
167
|
-
// ── update action ───────────────────────────────────────────────
|
|
168
|
-
describe('execute - update', () => {
|
|
169
|
-
test('updates task status and priority', async () => {
|
|
170
|
-
const { tool, agent, context } = createTestSetup();
|
|
171
|
-
await tool.execute({ action: 'create', title: 'Task A', priority: 'low' }, context);
|
|
172
|
-
const taskId = agent.taskList.tasks[0].id;
|
|
173
|
-
const result = await tool.execute(
|
|
174
|
-
{ action: 'update', taskId, status: 'in_progress', priority: 'high' }, context
|
|
175
|
-
);
|
|
176
|
-
expect(result.success).toBe(true);
|
|
177
|
-
expect(result.result.task.status).toBe('in_progress');
|
|
178
|
-
expect(result.result.task.priority).toBe('high');
|
|
179
|
-
});
|
|
180
|
-
|
|
181
|
-
test('updates title and description', async () => {
|
|
182
|
-
const { tool, agent, context } = createTestSetup();
|
|
183
|
-
await tool.execute({ action: 'create', title: 'Old', priority: 'medium' }, context);
|
|
184
|
-
const taskId = agent.taskList.tasks[0].id;
|
|
185
|
-
const result = await tool.execute(
|
|
186
|
-
{ action: 'update', taskId, title: 'New Title', description: 'Desc' }, context
|
|
187
|
-
);
|
|
188
|
-
expect(result.result.task.title).toBe('New Title');
|
|
189
|
-
expect(result.result.task.description).toBe('Desc');
|
|
190
|
-
});
|
|
191
|
-
|
|
192
|
-
test('errors when taskId is missing', async () => {
|
|
193
|
-
const { tool, context } = createTestSetup();
|
|
194
|
-
const result = await tool.execute({ action: 'update', status: 'completed' }, context);
|
|
195
|
-
expect(result.success).toBe(false);
|
|
196
|
-
expect(result.error).toContain('Task ID is required');
|
|
197
|
-
});
|
|
198
|
-
|
|
199
|
-
test('errors for non-existent task', async () => {
|
|
200
|
-
const { tool, context } = createTestSetup();
|
|
201
|
-
const result = await tool.execute({ action: 'update', taskId: 'no-such', status: 'completed' }, context);
|
|
202
|
-
expect(result.success).toBe(false);
|
|
203
|
-
expect(result.error).toContain('Task not found');
|
|
204
|
-
});
|
|
205
|
-
|
|
206
|
-
test('errors for invalid status', async () => {
|
|
207
|
-
const { tool, agent, context } = createTestSetup();
|
|
208
|
-
await tool.execute({ action: 'create', title: 'T', priority: 'medium' }, context);
|
|
209
|
-
const taskId = agent.taskList.tasks[0].id;
|
|
210
|
-
const result = await tool.execute({ action: 'update', taskId, status: 'badstatus' }, context);
|
|
211
|
-
expect(result.success).toBe(false);
|
|
212
|
-
expect(result.error).toContain('Invalid status');
|
|
213
|
-
});
|
|
214
|
-
});
|
|
215
|
-
|
|
216
|
-
// ── list action ─────────────────────────────────────────────────
|
|
217
|
-
describe('execute - list', () => {
|
|
218
|
-
test('lists all tasks with summary counts', async () => {
|
|
219
|
-
const { tool, context } = createTestSetup();
|
|
220
|
-
await tool.execute({ action: 'create', title: 'T1', priority: 'high' }, context);
|
|
221
|
-
await tool.execute({ action: 'create', title: 'T2', priority: 'low' }, context);
|
|
222
|
-
const result = await tool.execute({ action: 'list' }, context);
|
|
223
|
-
expect(result.success).toBe(true);
|
|
224
|
-
expect(result.result.totalTasks).toBe(2);
|
|
225
|
-
expect(result.result.summary.pending).toBe(2);
|
|
226
|
-
});
|
|
227
|
-
|
|
228
|
-
test('filters by status', async () => {
|
|
229
|
-
const { tool, agent, context } = createTestSetup();
|
|
230
|
-
await tool.execute({ action: 'create', title: 'T1', priority: 'high' }, context);
|
|
231
|
-
await tool.execute({ action: 'create', title: 'T2', priority: 'low' }, context);
|
|
232
|
-
await tool.execute({ action: 'complete', taskId: agent.taskList.tasks[0].id }, context);
|
|
233
|
-
const result = await tool.execute({ action: 'list', status: 'pending' }, context);
|
|
234
|
-
expect(result.result.totalTasks).toBe(1);
|
|
235
|
-
});
|
|
236
|
-
|
|
237
|
-
test('filters by priority', async () => {
|
|
238
|
-
const { tool, context } = createTestSetup();
|
|
239
|
-
await tool.execute({ action: 'create', title: 'T1', priority: 'high' }, context);
|
|
240
|
-
await tool.execute({ action: 'create', title: 'T2', priority: 'low' }, context);
|
|
241
|
-
const result = await tool.execute({ action: 'list', priority: 'high' }, context);
|
|
242
|
-
expect(result.result.totalTasks).toBe(1);
|
|
243
|
-
});
|
|
244
|
-
|
|
245
|
-
test('sorts by priority then creation date', async () => {
|
|
246
|
-
const { tool, context } = createTestSetup();
|
|
247
|
-
await tool.execute({ action: 'create', title: 'Low', priority: 'low' }, context);
|
|
248
|
-
await tool.execute({ action: 'create', title: 'High', priority: 'high' }, context);
|
|
249
|
-
const result = await tool.execute({ action: 'list' }, context);
|
|
250
|
-
expect(result.result.tasks[0].title).toBe('High');
|
|
251
|
-
});
|
|
252
|
-
});
|
|
253
|
-
|
|
254
|
-
// ── complete action ─────────────────────────────────────────────
|
|
255
|
-
describe('execute - complete', () => {
|
|
256
|
-
test('marks task as completed with timestamp', async () => {
|
|
257
|
-
const { tool, agent, context } = createTestSetup();
|
|
258
|
-
await tool.execute({ action: 'create', title: 'Finish', priority: 'medium' }, context);
|
|
259
|
-
const taskId = agent.taskList.tasks[0].id;
|
|
260
|
-
const result = await tool.execute({ action: 'complete', taskId }, context);
|
|
261
|
-
expect(result.success).toBe(true);
|
|
262
|
-
expect(result.result.task.status).toBe('completed');
|
|
263
|
-
expect(result.result.task.completedAt).toBeDefined();
|
|
264
|
-
});
|
|
265
|
-
|
|
266
|
-
test('auto-completes first in-progress task when no taskId', async () => {
|
|
267
|
-
const { tool, agent, context } = createTestSetup();
|
|
268
|
-
await tool.execute({ action: 'create', title: 'Auto', priority: 'medium' }, context);
|
|
269
|
-
agent.taskList.tasks[0].status = 'in_progress';
|
|
270
|
-
const result = await tool.execute({ action: 'complete' }, context);
|
|
271
|
-
expect(result.result.task.status).toBe('completed');
|
|
272
|
-
});
|
|
273
|
-
|
|
274
|
-
test('returns message for already-completed task', async () => {
|
|
275
|
-
const { tool, agent, context } = createTestSetup();
|
|
276
|
-
await tool.execute({ action: 'create', title: 'Done', priority: 'medium' }, context);
|
|
277
|
-
const taskId = agent.taskList.tasks[0].id;
|
|
278
|
-
await tool.execute({ action: 'complete', taskId }, context);
|
|
279
|
-
const result = await tool.execute({ action: 'complete', taskId }, context);
|
|
280
|
-
expect(result.result.message).toContain('already completed');
|
|
281
|
-
});
|
|
282
|
-
|
|
283
|
-
test('sets TTL to 1 when no pending tasks remain', async () => {
|
|
284
|
-
const { tool, agent, context } = createTestSetup();
|
|
285
|
-
await tool.execute({ action: 'create', title: 'Only', priority: 'medium' }, context);
|
|
286
|
-
await tool.execute({ action: 'complete', taskId: agent.taskList.tasks[0].id }, context);
|
|
287
|
-
expect(agent.ttl).toBe(1);
|
|
288
|
-
});
|
|
289
|
-
|
|
290
|
-
test('includes no-tasks hint in summary', async () => {
|
|
291
|
-
const { tool, agent, context } = createTestSetup();
|
|
292
|
-
await tool.execute({ action: 'create', title: 'Only', priority: 'medium' }, context);
|
|
293
|
-
const result = await tool.execute({ action: 'complete', taskId: agent.taskList.tasks[0].id }, context);
|
|
294
|
-
expect(result.summary).toContain('No remaining tasks');
|
|
295
|
-
});
|
|
296
|
-
|
|
297
|
-
test('errors when no taskId and no in-progress tasks', async () => {
|
|
298
|
-
const { tool, context } = createTestSetup();
|
|
299
|
-
const result = await tool.execute({ action: 'complete' }, context);
|
|
300
|
-
expect(result.success).toBe(false);
|
|
301
|
-
expect(result.error).toContain('No task ID provided');
|
|
302
|
-
});
|
|
303
|
-
});
|
|
304
|
-
|
|
305
|
-
// ── cancel action ───────────────────────────────────────────────
|
|
306
|
-
describe('execute - cancel', () => {
|
|
307
|
-
test('cancels a task with reason', async () => {
|
|
308
|
-
const { tool, agent, context } = createTestSetup();
|
|
309
|
-
await tool.execute({ action: 'create', title: 'Cancel me', priority: 'medium' }, context);
|
|
310
|
-
const taskId = agent.taskList.tasks[0].id;
|
|
311
|
-
const result = await tool.execute({ action: 'cancel', taskId, reason: 'Not needed' }, context);
|
|
312
|
-
expect(result.success).toBe(true);
|
|
313
|
-
expect(result.result.task.status).toBe('cancelled');
|
|
314
|
-
expect(result.result.task.cancellationReason).toBe('Not needed');
|
|
315
|
-
});
|
|
316
|
-
|
|
317
|
-
test('errors when taskId is missing', async () => {
|
|
318
|
-
const { tool, context } = createTestSetup();
|
|
319
|
-
const result = await tool.execute({ action: 'cancel' }, context);
|
|
320
|
-
expect(result.success).toBe(false);
|
|
321
|
-
expect(result.error).toContain('Task ID is required');
|
|
322
|
-
});
|
|
323
|
-
});
|
|
324
|
-
|
|
325
|
-
// ── clear action ────────────────────────────────────────────────
|
|
326
|
-
describe('execute - clear', () => {
|
|
327
|
-
test('removes completed and cancelled tasks', async () => {
|
|
328
|
-
const { tool, agent, context } = createTestSetup();
|
|
329
|
-
await tool.execute({ action: 'create', title: 'T1', priority: 'medium' }, context);
|
|
330
|
-
await tool.execute({ action: 'create', title: 'T2', priority: 'medium' }, context);
|
|
331
|
-
await tool.execute({ action: 'create', title: 'T3', priority: 'medium' }, context);
|
|
332
|
-
await tool.execute({ action: 'complete', taskId: agent.taskList.tasks[0].id }, context);
|
|
333
|
-
await tool.execute({ action: 'cancel', taskId: agent.taskList.tasks[1].id }, context);
|
|
334
|
-
const result = await tool.execute({ action: 'clear' }, context);
|
|
335
|
-
expect(result.result.removed).toBe(2);
|
|
336
|
-
expect(agent.taskList.tasks).toHaveLength(1);
|
|
337
|
-
});
|
|
338
|
-
});
|
|
339
|
-
|
|
340
|
-
// ── depend action ───────────────────────────────────────────────
|
|
341
|
-
describe('execute - depend', () => {
|
|
342
|
-
test('creates blocking dependency and sets blocked status', async () => {
|
|
343
|
-
const { tool, agent, context } = createTestSetup();
|
|
344
|
-
await tool.execute({ action: 'create', title: 'T1', priority: 'high' }, context);
|
|
345
|
-
await tool.execute({ action: 'create', title: 'T2', priority: 'medium' }, context);
|
|
346
|
-
const [tA, tB] = agent.taskList.tasks;
|
|
347
|
-
const result = await tool.execute(
|
|
348
|
-
{ action: 'depend', taskId: tB.id, dependsOn: tA.id, dependencyType: 'blocks' }, context
|
|
349
|
-
);
|
|
350
|
-
expect(result.success).toBe(true);
|
|
351
|
-
expect(result.result.dependency.taskId).toBe(tA.id);
|
|
352
|
-
expect(agent.taskList.tasks[1].status).toBe('blocked');
|
|
353
|
-
});
|
|
354
|
-
|
|
355
|
-
test('returns already-exists for duplicate dependency', async () => {
|
|
356
|
-
const { tool, agent, context } = createTestSetup();
|
|
357
|
-
await tool.execute({ action: 'create', title: 'T1', priority: 'high' }, context);
|
|
358
|
-
await tool.execute({ action: 'create', title: 'T2', priority: 'medium' }, context);
|
|
359
|
-
const [tA, tB] = agent.taskList.tasks;
|
|
360
|
-
await tool.execute({ action: 'depend', taskId: tB.id, dependsOn: tA.id }, context);
|
|
361
|
-
const result = await tool.execute({ action: 'depend', taskId: tB.id, dependsOn: tA.id }, context);
|
|
362
|
-
expect(result.result.message).toContain('already exists');
|
|
363
|
-
});
|
|
364
|
-
|
|
365
|
-
test('errors when both params are missing', async () => {
|
|
366
|
-
const { tool, context } = createTestSetup();
|
|
367
|
-
const result = await tool.execute({ action: 'depend', taskId: 'x' }, context);
|
|
368
|
-
expect(result.success).toBe(false);
|
|
369
|
-
});
|
|
370
|
-
|
|
371
|
-
test('errors for invalid dependency type', async () => {
|
|
372
|
-
const { tool, agent, context } = createTestSetup();
|
|
373
|
-
await tool.execute({ action: 'create', title: 'A', priority: 'high' }, context);
|
|
374
|
-
await tool.execute({ action: 'create', title: 'B', priority: 'high' }, context);
|
|
375
|
-
const [a, b] = agent.taskList.tasks;
|
|
376
|
-
const result = await tool.execute(
|
|
377
|
-
{ action: 'depend', taskId: b.id, dependsOn: a.id, dependencyType: 'invalid' }, context
|
|
378
|
-
);
|
|
379
|
-
expect(result.success).toBe(false);
|
|
380
|
-
expect(result.error).toContain('Invalid dependency type');
|
|
381
|
-
});
|
|
382
|
-
});
|
|
383
|
-
|
|
384
|
-
// ── relate action ───────────────────────────────────────────────
|
|
385
|
-
describe('execute - relate', () => {
|
|
386
|
-
test('creates a relates-type dependency', async () => {
|
|
387
|
-
const { tool, agent, context } = createTestSetup();
|
|
388
|
-
await tool.execute({ action: 'create', title: 'T1', priority: 'high' }, context);
|
|
389
|
-
await tool.execute({ action: 'create', title: 'T2', priority: 'medium' }, context);
|
|
390
|
-
const [tA, tB] = agent.taskList.tasks;
|
|
391
|
-
const result = await tool.execute(
|
|
392
|
-
{ action: 'relate', taskId: tB.id, dependsOn: tA.id }, context
|
|
393
|
-
);
|
|
394
|
-
expect(result.result.dependency.type).toBe('relates');
|
|
395
|
-
});
|
|
396
|
-
});
|
|
397
|
-
|
|
398
|
-
// ── subtask action ──────────────────────────────────────────────
|
|
399
|
-
describe('execute - subtask', () => {
|
|
400
|
-
test('creates subtask under parent', async () => {
|
|
401
|
-
const { tool, agent, context } = createTestSetup();
|
|
402
|
-
await tool.execute({ action: 'create', title: 'Parent', priority: 'high' }, context);
|
|
403
|
-
const parentId = agent.taskList.tasks[0].id;
|
|
404
|
-
const result = await tool.execute(
|
|
405
|
-
{ action: 'subtask', parentTaskId: parentId, title: 'Sub 1', priority: 'medium' }, context
|
|
406
|
-
);
|
|
407
|
-
expect(result.success).toBe(true);
|
|
408
|
-
expect(result.result.subtask.isSubtask).toBe(true);
|
|
409
|
-
expect(result.result.subtask.parentTaskId).toBe(parentId);
|
|
410
|
-
expect(agent.taskList.tasks[0].subtasks).toContain(result.result.subtask.id);
|
|
411
|
-
});
|
|
412
|
-
|
|
413
|
-
test('errors when parent not found', async () => {
|
|
414
|
-
const { tool, context } = createTestSetup();
|
|
415
|
-
const result = await tool.execute(
|
|
416
|
-
{ action: 'subtask', parentTaskId: 'no-such', title: 'Sub' }, context
|
|
417
|
-
);
|
|
418
|
-
expect(result.success).toBe(false);
|
|
419
|
-
expect(result.error).toContain('Parent task not found');
|
|
420
|
-
});
|
|
421
|
-
|
|
422
|
-
test('errors when parentTaskId or title missing', async () => {
|
|
423
|
-
const { tool, context } = createTestSetup();
|
|
424
|
-
const result = await tool.execute({ action: 'subtask', title: 'Sub' }, context);
|
|
425
|
-
expect(result.success).toBe(false);
|
|
426
|
-
expect(result.error).toContain('required');
|
|
427
|
-
});
|
|
428
|
-
});
|
|
429
|
-
|
|
430
|
-
// ── sync action ─────────────────────────────────────────────────
|
|
431
|
-
describe('execute - sync', () => {
|
|
432
|
-
test('syncs task list creating new tasks', async () => {
|
|
433
|
-
const { tool, context } = createTestSetup();
|
|
434
|
-
const result = await tool.execute({
|
|
435
|
-
action: 'sync',
|
|
436
|
-
tasks: [
|
|
437
|
-
{ title: 'A', status: 'completed', priority: 'high' },
|
|
438
|
-
{ title: 'B', status: 'in_progress', priority: 'medium' },
|
|
439
|
-
{ title: 'C', status: 'pending', priority: 'low' }
|
|
440
|
-
]
|
|
441
|
-
}, context);
|
|
442
|
-
expect(result.success).toBe(true);
|
|
443
|
-
expect(result.result.summary.total).toBe(3);
|
|
444
|
-
expect(result.result.summary.created).toBe(3);
|
|
445
|
-
});
|
|
446
|
-
|
|
447
|
-
test('updates existing tasks by matching title', async () => {
|
|
448
|
-
const { tool, context } = createTestSetup();
|
|
449
|
-
await tool.execute({ action: 'create', title: 'Existing', priority: 'low' }, context);
|
|
450
|
-
const result = await tool.execute({
|
|
451
|
-
action: 'sync',
|
|
452
|
-
tasks: [{ title: 'Existing', status: 'completed', priority: 'high' }]
|
|
453
|
-
}, context);
|
|
454
|
-
expect(result.result.summary.updated).toBe(1);
|
|
455
|
-
expect(result.result.summary.created).toBe(0);
|
|
456
|
-
});
|
|
457
|
-
|
|
458
|
-
test('parses JSON string tasks', async () => {
|
|
459
|
-
const { tool, context } = createTestSetup();
|
|
460
|
-
const result = await tool.execute({
|
|
461
|
-
action: 'sync',
|
|
462
|
-
tasks: JSON.stringify([{ title: 'JSON', status: 'pending', priority: 'medium' }])
|
|
463
|
-
}, context);
|
|
464
|
-
expect(result.success).toBe(true);
|
|
465
|
-
});
|
|
466
|
-
|
|
467
|
-
test('errors for empty tasks array', async () => {
|
|
468
|
-
const { tool, context } = createTestSetup();
|
|
469
|
-
const result = await tool.execute({ action: 'sync', tasks: [] }, context);
|
|
470
|
-
expect(result.success).toBe(false);
|
|
471
|
-
expect(result.error).toContain('empty');
|
|
472
|
-
});
|
|
473
|
-
|
|
474
|
-
test('errors for invalid status', async () => {
|
|
475
|
-
const { tool, context } = createTestSetup();
|
|
476
|
-
const result = await tool.execute({
|
|
477
|
-
action: 'sync',
|
|
478
|
-
tasks: [{ title: 'Bad', status: 'oops', priority: 'medium' }]
|
|
479
|
-
}, context);
|
|
480
|
-
expect(result.success).toBe(false);
|
|
481
|
-
expect(result.error).toContain('Invalid status');
|
|
482
|
-
});
|
|
483
|
-
|
|
484
|
-
// ── Destructive-sync guardrail ───────────────────────────────────
|
|
485
|
-
// Real production failure: post-compaction, an agent forgot it had
|
|
486
|
-
// a 9-task plan and called sync with 4 unrelated tasks → all 9
|
|
487
|
-
// were silently dropped. These tests pin the guardrail.
|
|
488
|
-
|
|
489
|
-
describe('REGRESSION: destructive-sync guardrail', () => {
|
|
490
|
-
// Helper: seed the agent with two pending + one in_progress task,
|
|
491
|
-
// then attempt to sync with a totally different list.
|
|
492
|
-
async function setupAgentWithPlan() {
|
|
493
|
-
const { tool, context } = createTestSetup();
|
|
494
|
-
await tool.execute({
|
|
495
|
-
action: 'sync',
|
|
496
|
-
tasks: [
|
|
497
|
-
{ title: 'Char select bg', status: 'in_progress', priority: 'high' },
|
|
498
|
-
{ title: 'Board art', status: 'pending', priority: 'high' },
|
|
499
|
-
{ title: 'Dice animation', status: 'pending', priority: 'medium' },
|
|
500
|
-
],
|
|
501
|
-
}, context);
|
|
502
|
-
return { tool, context };
|
|
503
|
-
}
|
|
504
|
-
|
|
505
|
-
test('REFUSES sync that would drop pending/in_progress tasks without confirmReplace', async () => {
|
|
506
|
-
const { tool, context } = await setupAgentWithPlan();
|
|
507
|
-
const result = await tool.execute({
|
|
508
|
-
action: 'sync',
|
|
509
|
-
tasks: [
|
|
510
|
-
{ title: 'Add Settings UI', status: 'pending', priority: 'high' },
|
|
511
|
-
{ title: 'Add Settings logic', status: 'pending', priority: 'high' },
|
|
512
|
-
],
|
|
513
|
-
}, context);
|
|
514
|
-
expect(result.success).toBe(false);
|
|
515
|
-
expect(result.error).toMatch(/Sync would drop 3 non-terminal task/);
|
|
516
|
-
// Must name the at-risk tasks so the agent can see them.
|
|
517
|
-
expect(result.error).toContain('Char select bg');
|
|
518
|
-
expect(result.error).toContain('Board art');
|
|
519
|
-
expect(result.error).toContain('Dice animation');
|
|
520
|
-
// Must explain the escape hatch.
|
|
521
|
-
expect(result.error).toContain('confirmReplace: true');
|
|
522
|
-
});
|
|
523
|
-
|
|
524
|
-
test('PROCEEDS when confirmReplace=true is explicitly set', async () => {
|
|
525
|
-
const { tool, context } = await setupAgentWithPlan();
|
|
526
|
-
const result = await tool.execute({
|
|
527
|
-
action: 'sync',
|
|
528
|
-
confirmReplace: true,
|
|
529
|
-
tasks: [
|
|
530
|
-
{ title: 'Add Settings UI', status: 'pending', priority: 'high' },
|
|
531
|
-
],
|
|
532
|
-
}, context);
|
|
533
|
-
expect(result.success).toBe(true);
|
|
534
|
-
expect(result.result.summary.total).toBe(1);
|
|
535
|
-
expect(result.result.summary.removed).toBe(3);
|
|
536
|
-
});
|
|
537
|
-
|
|
538
|
-
test('does NOT trigger when the incoming list keeps every open task (rename only)', async () => {
|
|
539
|
-
const { tool, context } = await setupAgentWithPlan();
|
|
540
|
-
// Update statuses, but keep all titles. No drops.
|
|
541
|
-
const result = await tool.execute({
|
|
542
|
-
action: 'sync',
|
|
543
|
-
tasks: [
|
|
544
|
-
{ title: 'Char select bg', status: 'completed', priority: 'high' },
|
|
545
|
-
{ title: 'Board art', status: 'in_progress', priority: 'high' },
|
|
546
|
-
{ title: 'Dice animation', status: 'pending', priority: 'medium' },
|
|
547
|
-
],
|
|
548
|
-
}, context);
|
|
549
|
-
expect(result.success).toBe(true);
|
|
550
|
-
expect(result.result.summary.updated).toBe(3);
|
|
551
|
-
expect(result.result.summary.removed).toBe(0);
|
|
552
|
-
});
|
|
553
|
-
|
|
554
|
-
test('does NOT trigger when only completed/cancelled tasks would be dropped', async () => {
|
|
555
|
-
const { tool, context } = createTestSetup();
|
|
556
|
-
// Seed with one done task + one pending.
|
|
557
|
-
await tool.execute({
|
|
558
|
-
action: 'sync',
|
|
559
|
-
tasks: [
|
|
560
|
-
{ title: 'Already done', status: 'completed', priority: 'low' },
|
|
561
|
-
{ title: 'Still going', status: 'pending', priority: 'high' },
|
|
562
|
-
],
|
|
563
|
-
}, context);
|
|
564
|
-
// New sync drops the completed one but keeps the pending one.
|
|
565
|
-
const result = await tool.execute({
|
|
566
|
-
action: 'sync',
|
|
567
|
-
tasks: [{ title: 'Still going', status: 'in_progress', priority: 'high' }],
|
|
568
|
-
}, context);
|
|
569
|
-
// No guardrail trip — completed task is safe to drop.
|
|
570
|
-
expect(result.success).toBe(true);
|
|
571
|
-
});
|
|
572
|
-
|
|
573
|
-
test('error response includes droppedTasks metadata for programmatic recovery', async () => {
|
|
574
|
-
const { tool, context } = await setupAgentWithPlan();
|
|
575
|
-
const result = await tool.execute({
|
|
576
|
-
action: 'sync',
|
|
577
|
-
tasks: [{ title: 'New thing', status: 'pending', priority: 'high' }],
|
|
578
|
-
}, context);
|
|
579
|
-
expect(result.success).toBe(false);
|
|
580
|
-
// BaseTool's execute() catches the thrown Error; the message
|
|
581
|
-
// carries the human-readable hint. We assert on the hint
|
|
582
|
-
// contents — that's what the agent sees.
|
|
583
|
-
expect(result.error).toMatch(/3 non-terminal task/);
|
|
584
|
-
});
|
|
585
|
-
|
|
586
|
-
test('REGRESSION: the exact Talisman failure scenario is now blocked', async () => {
|
|
587
|
-
// Reproduce the production failure: agent has a 9-task plan
|
|
588
|
-
// reflecting the user's UI revision request. Post-compaction,
|
|
589
|
-
// agent loses context and tries to sync a 4-task Settings plan.
|
|
590
|
-
const { tool, context } = createTestSetup();
|
|
591
|
-
await tool.execute({
|
|
592
|
-
action: 'sync',
|
|
593
|
-
tasks: [
|
|
594
|
-
{ title: 'Explore current code and image assets', status: 'in_progress', priority: 'high' },
|
|
595
|
-
{ title: 'Generate character select background', status: 'pending', priority: 'high' },
|
|
596
|
-
{ title: 'Generate board space art for all nodes', status: 'pending', priority: 'high' },
|
|
597
|
-
{ title: 'Generate adventure card art', status: 'pending', priority: 'medium' },
|
|
598
|
-
{ title: 'Fix board to rectangular layout', status: 'pending', priority: 'high' },
|
|
599
|
-
{ title: 'Fix character select sticky buttons', status: 'pending', priority: 'medium' },
|
|
600
|
-
{ title: 'Fix dice animation and combat', status: 'pending', priority: 'high' },
|
|
601
|
-
{ title: 'Remove all emojis from UI', status: 'pending', priority: 'medium' },
|
|
602
|
-
{ title: 'Test and verify all changes', status: 'pending', priority: 'medium' },
|
|
603
|
-
],
|
|
604
|
-
}, context);
|
|
605
|
-
// The agent now (mistakenly) tries to sync a Settings plan.
|
|
606
|
-
const result = await tool.execute({
|
|
607
|
-
action: 'sync',
|
|
608
|
-
tasks: [
|
|
609
|
-
{ title: 'Add Settings UI to index.html', status: 'pending', priority: 'high' },
|
|
610
|
-
{ title: 'Add Settings logic to game.js', status: 'pending', priority: 'high' },
|
|
611
|
-
{ title: 'Wire Settings to title screen', status: 'pending', priority: 'medium' },
|
|
612
|
-
{ title: 'Test Settings persistence', status: 'pending', priority: 'medium' },
|
|
613
|
-
],
|
|
614
|
-
}, context);
|
|
615
|
-
expect(result.success).toBe(false);
|
|
616
|
-
// ALL 9 original tasks must be named so the agent sees them.
|
|
617
|
-
expect(result.error).toContain('Explore current code');
|
|
618
|
-
expect(result.error).toContain('Generate character select background');
|
|
619
|
-
expect(result.error).toContain('Fix dice animation and combat');
|
|
620
|
-
expect(result.error).toContain('Remove all emojis from UI');
|
|
621
|
-
expect(result.error).toMatch(/Sync would drop 9 non-terminal task/);
|
|
622
|
-
});
|
|
623
|
-
});
|
|
624
|
-
|
|
625
|
-
test('enforces only one in_progress task', async () => {
|
|
626
|
-
const { tool, agent, context } = createTestSetup();
|
|
627
|
-
await tool.execute({
|
|
628
|
-
action: 'sync',
|
|
629
|
-
tasks: [
|
|
630
|
-
{ title: 'A', status: 'in_progress', priority: 'high' },
|
|
631
|
-
{ title: 'B', status: 'in_progress', priority: 'medium' }
|
|
632
|
-
]
|
|
633
|
-
}, context);
|
|
634
|
-
const ipCount = agent.taskList.tasks.filter(t => t.status === 'in_progress').length;
|
|
635
|
-
expect(ipCount).toBe(1);
|
|
636
|
-
});
|
|
637
|
-
|
|
638
|
-
test('auto-sets first pending to in_progress when none active', async () => {
|
|
639
|
-
const { tool, agent, context } = createTestSetup();
|
|
640
|
-
await tool.execute({
|
|
641
|
-
action: 'sync',
|
|
642
|
-
tasks: [
|
|
643
|
-
{ title: 'A', status: 'pending', priority: 'high' },
|
|
644
|
-
{ title: 'B', status: 'pending', priority: 'medium' }
|
|
645
|
-
]
|
|
646
|
-
}, context);
|
|
647
|
-
const ipTasks = agent.taskList.tasks.filter(t => t.status === 'in_progress');
|
|
648
|
-
expect(ipTasks).toHaveLength(1);
|
|
649
|
-
});
|
|
650
|
-
});
|
|
651
|
-
|
|
652
|
-
// ── template action ─────────────────────────────────────────────
|
|
653
|
-
describe('execute - template', () => {
|
|
654
|
-
test('lists available templates', async () => {
|
|
655
|
-
const { tool, context } = createTestSetup();
|
|
656
|
-
const result = await tool.execute({ action: 'template', mode: 'list' }, context);
|
|
657
|
-
expect(result.success).toBe(true);
|
|
658
|
-
expect(result.result.builtInTemplates.length).toBeGreaterThan(0);
|
|
659
|
-
});
|
|
660
|
-
|
|
661
|
-
test('applies a built-in template', async () => {
|
|
662
|
-
const { tool, agent, context } = createTestSetup();
|
|
663
|
-
const result = await tool.execute(
|
|
664
|
-
{ action: 'template', mode: 'apply', templateId: 'bug-fix' }, context
|
|
665
|
-
);
|
|
666
|
-
expect(result.success).toBe(true);
|
|
667
|
-
expect(result.result.tasksCreated).toBeGreaterThan(0);
|
|
668
|
-
expect(agent.taskList.tasks.length).toBeGreaterThan(0);
|
|
669
|
-
});
|
|
670
|
-
|
|
671
|
-
test('errors for non-existent template', async () => {
|
|
672
|
-
const { tool, context } = createTestSetup();
|
|
673
|
-
const result = await tool.execute(
|
|
674
|
-
{ action: 'template', mode: 'apply', templateId: 'nope' }, context
|
|
675
|
-
);
|
|
676
|
-
expect(result.success).toBe(false);
|
|
677
|
-
expect(result.error).toContain('Template not found');
|
|
678
|
-
});
|
|
679
|
-
|
|
680
|
-
test('creates a custom template', async () => {
|
|
681
|
-
const { tool, agent, context } = createTestSetup();
|
|
682
|
-
const result = await tool.execute({
|
|
683
|
-
action: 'template',
|
|
684
|
-
mode: 'create',
|
|
685
|
-
customTemplate: {
|
|
686
|
-
name: 'My Workflow',
|
|
687
|
-
description: 'Custom',
|
|
688
|
-
tasks: [{ title: 'Step 1' }, { title: 'Step 2' }]
|
|
689
|
-
}
|
|
690
|
-
}, context);
|
|
691
|
-
expect(result.success).toBe(true);
|
|
692
|
-
expect(agent.customTemplates).toHaveLength(1);
|
|
693
|
-
});
|
|
694
|
-
|
|
695
|
-
test('errors when custom template has no tasks', async () => {
|
|
696
|
-
const { tool, context } = createTestSetup();
|
|
697
|
-
const result = await tool.execute({
|
|
698
|
-
action: 'template',
|
|
699
|
-
mode: 'create',
|
|
700
|
-
customTemplate: { name: 'Empty', tasks: [] }
|
|
701
|
-
}, context);
|
|
702
|
-
expect(result.success).toBe(false);
|
|
703
|
-
expect(result.error).toContain('requires name and at least one task');
|
|
704
|
-
});
|
|
705
|
-
|
|
706
|
-
test('suggests templates based on patterns', async () => {
|
|
707
|
-
const { tool, agent, context } = createTestSetup();
|
|
708
|
-
agent.taskList.tasks.push({
|
|
709
|
-
id: 'bug-task', title: 'Fix login bug', status: 'pending',
|
|
710
|
-
priority: 'high', createdAt: new Date().toISOString()
|
|
711
|
-
});
|
|
712
|
-
const result = await tool.execute({ action: 'template', mode: 'suggest' }, context);
|
|
713
|
-
expect(result.success).toBe(true);
|
|
714
|
-
expect(result.result.suggestions.length).toBeGreaterThan(0);
|
|
715
|
-
});
|
|
716
|
-
|
|
717
|
-
test('errors for invalid template mode', async () => {
|
|
718
|
-
const { tool, context } = createTestSetup();
|
|
719
|
-
const result = await tool.execute({ action: 'template', mode: 'invalid' }, context);
|
|
720
|
-
expect(result.success).toBe(false);
|
|
721
|
-
expect(result.error).toContain('Invalid template mode');
|
|
722
|
-
});
|
|
723
|
-
});
|
|
724
|
-
|
|
725
|
-
// ── progress action ─────────────────────────────────────────────
|
|
726
|
-
describe('execute - progress', () => {
|
|
727
|
-
test('updates task progress with stage and note', async () => {
|
|
728
|
-
const { tool, agent, context } = createTestSetup();
|
|
729
|
-
await tool.execute({ action: 'create', title: 'Dev', priority: 'high' }, context);
|
|
730
|
-
const taskId = agent.taskList.tasks[0].id;
|
|
731
|
-
const result = await tool.execute({
|
|
732
|
-
action: 'progress', mode: 'update', taskId,
|
|
733
|
-
stage: 'in_development', note: 'Started coding'
|
|
734
|
-
}, context);
|
|
735
|
-
expect(result.success).toBe(true);
|
|
736
|
-
expect(result.result.task.progress.stage).toBe('in_development');
|
|
737
|
-
expect(result.result.task.progress.notes).toHaveLength(1);
|
|
738
|
-
});
|
|
739
|
-
|
|
740
|
-
test('setting percentage to 100 completes task', async () => {
|
|
741
|
-
const { tool, agent, context } = createTestSetup();
|
|
742
|
-
await tool.execute({ action: 'create', title: 'Pct', priority: 'high' }, context);
|
|
743
|
-
const taskId = agent.taskList.tasks[0].id;
|
|
744
|
-
await tool.execute({ action: 'progress', mode: 'update', taskId, percentage: 100 }, context);
|
|
745
|
-
expect(agent.taskList.tasks[0].status).toBe('completed');
|
|
746
|
-
expect(agent.taskList.tasks[0].progress.stage).toBe('completed');
|
|
747
|
-
});
|
|
748
|
-
|
|
749
|
-
test('gets progress overview', async () => {
|
|
750
|
-
const { tool, context } = createTestSetup();
|
|
751
|
-
await tool.execute({ action: 'create', title: 'T1', priority: 'high' }, context);
|
|
752
|
-
const result = await tool.execute({ action: 'progress', mode: 'overview' }, context);
|
|
753
|
-
expect(result.success).toBe(true);
|
|
754
|
-
expect(result.result.overview).toBeDefined();
|
|
755
|
-
});
|
|
756
|
-
|
|
757
|
-
test('calculates progress from subtasks', async () => {
|
|
758
|
-
const { tool, agent, context } = createTestSetup();
|
|
759
|
-
await tool.execute({ action: 'create', title: 'Parent', priority: 'high' }, context);
|
|
760
|
-
const parentId = agent.taskList.tasks[0].id;
|
|
761
|
-
await tool.execute({ action: 'subtask', parentTaskId: parentId, title: 'Sub1' }, context);
|
|
762
|
-
const result = await tool.execute({ action: 'progress', mode: 'calculate', taskId: parentId }, context);
|
|
763
|
-
expect(result.result.calculationMethod).toBe('subtasks');
|
|
764
|
-
});
|
|
765
|
-
|
|
766
|
-
test('errors for invalid stage', async () => {
|
|
767
|
-
const { tool, agent, context } = createTestSetup();
|
|
768
|
-
await tool.execute({ action: 'create', title: 'T', priority: 'high' }, context);
|
|
769
|
-
const taskId = agent.taskList.tasks[0].id;
|
|
770
|
-
const result = await tool.execute({ action: 'progress', mode: 'update', taskId, stage: 'nope' }, context);
|
|
771
|
-
expect(result.success).toBe(false);
|
|
772
|
-
expect(result.error).toContain('Invalid progress stage');
|
|
773
|
-
});
|
|
774
|
-
|
|
775
|
-
test('errors for invalid progress mode', async () => {
|
|
776
|
-
const { tool, context } = createTestSetup();
|
|
777
|
-
const result = await tool.execute({ action: 'progress', mode: 'invalid' }, context);
|
|
778
|
-
expect(result.success).toBe(false);
|
|
779
|
-
});
|
|
780
|
-
});
|
|
781
|
-
|
|
782
|
-
// ── prioritize action ───────────────────────────────────────────
|
|
783
|
-
describe('execute - prioritize', () => {
|
|
784
|
-
test('auto-prioritizes tasks', async () => {
|
|
785
|
-
const { tool, context } = createTestSetup();
|
|
786
|
-
await tool.execute({ action: 'create', title: 'T1', priority: 'low' }, context);
|
|
787
|
-
await tool.execute({ action: 'create', title: 'T2', priority: 'medium' }, context);
|
|
788
|
-
const result = await tool.execute({ action: 'prioritize', mode: 'auto' }, context);
|
|
789
|
-
expect(result.success).toBe(true);
|
|
790
|
-
});
|
|
791
|
-
|
|
792
|
-
test('analyzes specific task priority', async () => {
|
|
793
|
-
const { tool, agent, context } = createTestSetup();
|
|
794
|
-
await tool.execute({ action: 'create', title: 'Analyze', priority: 'medium' }, context);
|
|
795
|
-
const taskId = agent.taskList.tasks[0].id;
|
|
796
|
-
const result = await tool.execute({ action: 'prioritize', mode: 'analyze', taskId }, context);
|
|
797
|
-
expect(result.success).toBe(true);
|
|
798
|
-
expect(result.result.task.priorityScore).toBeDefined();
|
|
799
|
-
});
|
|
800
|
-
|
|
801
|
-
test('balance mode without scheduler returns message', async () => {
|
|
802
|
-
const { tool, context } = createTestSetup();
|
|
803
|
-
const result = await tool.execute({ action: 'prioritize', mode: 'balance' }, context);
|
|
804
|
-
expect(result.success).toBe(true);
|
|
805
|
-
expect(result.result.message).toContain('scheduler');
|
|
806
|
-
});
|
|
807
|
-
|
|
808
|
-
test('errors for invalid mode', async () => {
|
|
809
|
-
const { tool, context } = createTestSetup();
|
|
810
|
-
const result = await tool.execute({ action: 'prioritize', mode: 'xyz' }, context);
|
|
811
|
-
expect(result.success).toBe(false);
|
|
812
|
-
});
|
|
813
|
-
});
|
|
814
|
-
|
|
815
|
-
// ── analytics action ────────────────────────────────────────────
|
|
816
|
-
describe('execute - analytics', () => {
|
|
817
|
-
test('generates summary analytics', async () => {
|
|
818
|
-
const { tool, context } = createTestSetup();
|
|
819
|
-
await tool.execute({ action: 'create', title: 'T1', priority: 'high' }, context);
|
|
820
|
-
const result = await tool.execute({ action: 'analytics', mode: 'summary' }, context);
|
|
821
|
-
expect(result.success).toBe(true);
|
|
822
|
-
expect(result.result.generatedAt).toBeDefined();
|
|
823
|
-
});
|
|
824
|
-
|
|
825
|
-
test('errors for invalid analytics mode', async () => {
|
|
826
|
-
const { tool, context } = createTestSetup();
|
|
827
|
-
const result = await tool.execute({ action: 'analytics', mode: 'invalid' }, context);
|
|
828
|
-
expect(result.success).toBe(false);
|
|
829
|
-
expect(result.error).toContain('Invalid analytics mode');
|
|
830
|
-
});
|
|
831
|
-
});
|
|
832
|
-
|
|
833
|
-
// ── error handling ──────────────────────────────────────────────
|
|
834
|
-
describe('execute - error handling', () => {
|
|
835
|
-
test('fails when agentId is missing', async () => {
|
|
836
|
-
const { tool } = createTestSetup();
|
|
837
|
-
const result = await tool.execute({ action: 'list' }, { agentPool: {} });
|
|
838
|
-
expect(result.success).toBe(false);
|
|
839
|
-
expect(result.error).toContain('Agent ID is required');
|
|
840
|
-
});
|
|
841
|
-
|
|
842
|
-
test('fails when agent is not found', async () => {
|
|
843
|
-
const { tool } = createTestSetup();
|
|
844
|
-
const pool = { getAgent: jest.fn().mockResolvedValue(null) };
|
|
845
|
-
const result = await tool.execute({ action: 'list' }, { agentId: 'x', agentPool: pool });
|
|
846
|
-
expect(result.success).toBe(false);
|
|
847
|
-
expect(result.error).toContain('Agent not found');
|
|
848
|
-
});
|
|
849
|
-
|
|
850
|
-
test('fails for unsupported action', async () => {
|
|
851
|
-
const { tool, context } = createTestSetup();
|
|
852
|
-
const result = await tool.execute({ action: 'fly' }, context);
|
|
853
|
-
expect(result.success).toBe(false);
|
|
854
|
-
expect(result.error).toContain('Unsupported action');
|
|
855
|
-
});
|
|
856
|
-
|
|
857
|
-
test('initializes taskList on agent if missing', async () => {
|
|
858
|
-
const { tool, context, agentPool } = createTestSetup();
|
|
859
|
-
const bare = { id: 'agent-1', name: 'Bare' };
|
|
860
|
-
agentPool.getAgent.mockResolvedValue(bare);
|
|
861
|
-
const result = await tool.execute({ action: 'list' }, context);
|
|
862
|
-
expect(result.success).toBe(true);
|
|
863
|
-
expect(bare.taskList).toBeDefined();
|
|
864
|
-
});
|
|
865
|
-
});
|
|
866
|
-
});
|
|
1
|
+
import { jest, describe, test, expect, beforeEach } from '@jest/globals';
|
|
2
|
+
import { createMockLogger } from '../../__test-utils__/mockFactories.js';
|
|
3
|
+
|
|
4
|
+
// Mock uuid
|
|
5
|
+
let uuidCounter = 0;
|
|
6
|
+
jest.unstable_mockModule('uuid', () => ({
|
|
7
|
+
v4: jest.fn(() => `mock-uuid-${++uuidCounter}`)
|
|
8
|
+
}));
|
|
9
|
+
|
|
10
|
+
const { default: TaskManagerTool } = await import('../taskManagerTool.js');
|
|
11
|
+
|
|
12
|
+
/**
|
|
13
|
+
* Helper: create a tool instance, a fake agent, and context.
|
|
14
|
+
*/
|
|
15
|
+
function createTestSetup() {
|
|
16
|
+
const logger = createMockLogger();
|
|
17
|
+
const tool = new TaskManagerTool({ description: 'test task manager' });
|
|
18
|
+
tool.logger = logger;
|
|
19
|
+
|
|
20
|
+
const agent = {
|
|
21
|
+
id: 'agent-1',
|
|
22
|
+
name: 'Test Agent',
|
|
23
|
+
lastActivity: null,
|
|
24
|
+
taskList: {
|
|
25
|
+
tasks: [],
|
|
26
|
+
lastUpdated: new Date().toISOString()
|
|
27
|
+
}
|
|
28
|
+
};
|
|
29
|
+
|
|
30
|
+
const agentPool = {
|
|
31
|
+
getAgent: jest.fn().mockResolvedValue(agent),
|
|
32
|
+
persistAgentState: jest.fn().mockResolvedValue(undefined)
|
|
33
|
+
};
|
|
34
|
+
|
|
35
|
+
const context = {
|
|
36
|
+
agentId: 'agent-1',
|
|
37
|
+
agentName: 'Test Agent',
|
|
38
|
+
agentPool,
|
|
39
|
+
projectDir: '/tmp/test'
|
|
40
|
+
};
|
|
41
|
+
|
|
42
|
+
return { tool, agent, agentPool, context, logger };
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
beforeEach(() => {
|
|
46
|
+
uuidCounter = 0;
|
|
47
|
+
});
|
|
48
|
+
|
|
49
|
+
describe('TaskManagerTool', () => {
|
|
50
|
+
// ── constructor ─────────────────────────────────────────────────
|
|
51
|
+
describe('constructor', () => {
|
|
52
|
+
test('initializes with supported actions, priorities, and statuses', () => {
|
|
53
|
+
const tool = new TaskManagerTool();
|
|
54
|
+
expect(tool.supportedActions).toContain('create');
|
|
55
|
+
expect(tool.supportedActions).toContain('sync');
|
|
56
|
+
expect(tool.supportedActions).toContain('analytics');
|
|
57
|
+
expect(tool.taskPriorities).toEqual(['urgent', 'high', 'medium', 'low']);
|
|
58
|
+
expect(tool.taskStatuses).toContain('pending');
|
|
59
|
+
expect(tool.taskStatuses).toContain('completed');
|
|
60
|
+
expect(tool.taskTemplates).toHaveProperty('bug-fix');
|
|
61
|
+
});
|
|
62
|
+
});
|
|
63
|
+
|
|
64
|
+
// ── getDescription ──────────────────────────────────────────────
|
|
65
|
+
describe('getDescription', () => {
|
|
66
|
+
test('returns a non-empty description string', () => {
|
|
67
|
+
const tool = new TaskManagerTool();
|
|
68
|
+
const desc = tool.getDescription();
|
|
69
|
+
expect(typeof desc).toBe('string');
|
|
70
|
+
expect(desc.length).toBeGreaterThan(50);
|
|
71
|
+
});
|
|
72
|
+
});
|
|
73
|
+
|
|
74
|
+
// ── parseParameters ─────────────────────────────────────────────
|
|
75
|
+
describe('parseParameters', () => {
|
|
76
|
+
test('returns rawContent for string input', () => {
|
|
77
|
+
const tool = new TaskManagerTool();
|
|
78
|
+
expect(tool.parseParameters('hello')).toEqual({ rawContent: 'hello' });
|
|
79
|
+
});
|
|
80
|
+
|
|
81
|
+
test('unwraps tag-parser format objects', () => {
|
|
82
|
+
const tool = new TaskManagerTool();
|
|
83
|
+
const result = tool.parseParameters({
|
|
84
|
+
action: { value: 'create', attributes: {} },
|
|
85
|
+
title: { value: 'My Task', attributes: {} }
|
|
86
|
+
});
|
|
87
|
+
expect(result.action).toBe('create');
|
|
88
|
+
expect(result.title).toBe('My Task');
|
|
89
|
+
});
|
|
90
|
+
|
|
91
|
+
test('passes through plain objects', () => {
|
|
92
|
+
const tool = new TaskManagerTool();
|
|
93
|
+
expect(tool.parseParameters({ action: 'list' })).toEqual({ action: 'list' });
|
|
94
|
+
});
|
|
95
|
+
|
|
96
|
+
test('returns non-object values as-is', () => {
|
|
97
|
+
const tool = new TaskManagerTool();
|
|
98
|
+
expect(tool.parseParameters(null)).toBeNull();
|
|
99
|
+
});
|
|
100
|
+
});
|
|
101
|
+
|
|
102
|
+
// ── create action ───────────────────────────────────────────────
|
|
103
|
+
describe('execute - create', () => {
|
|
104
|
+
test('creates a task with title, description, and priority', async () => {
|
|
105
|
+
const { tool, agent, context } = createTestSetup();
|
|
106
|
+
const result = await tool.execute(
|
|
107
|
+
{ action: 'create', title: 'Build API', description: 'REST endpoints', priority: 'high' },
|
|
108
|
+
context
|
|
109
|
+
);
|
|
110
|
+
expect(result.success).toBe(true);
|
|
111
|
+
expect(result.action).toBe('create');
|
|
112
|
+
expect(result.result.task.title).toBe('Build API');
|
|
113
|
+
expect(result.result.task.priority).toBe('high');
|
|
114
|
+
expect(result.result.task.status).toBe('pending');
|
|
115
|
+
expect(agent.taskList.tasks).toHaveLength(1);
|
|
116
|
+
});
|
|
117
|
+
|
|
118
|
+
test('uses medium as default priority', async () => {
|
|
119
|
+
const { tool, context } = createTestSetup();
|
|
120
|
+
const result = await tool.execute({ action: 'create', title: 'Default' }, context);
|
|
121
|
+
expect(result.result.task.priority).toBe('medium');
|
|
122
|
+
});
|
|
123
|
+
|
|
124
|
+
test('errors when title is missing', async () => {
|
|
125
|
+
const { tool, context } = createTestSetup();
|
|
126
|
+
const result = await tool.execute({ action: 'create', priority: 'low' }, context);
|
|
127
|
+
expect(result.success).toBe(false);
|
|
128
|
+
expect(result.error).toContain('title is required');
|
|
129
|
+
});
|
|
130
|
+
|
|
131
|
+
test('errors for invalid priority', async () => {
|
|
132
|
+
const { tool, context } = createTestSetup();
|
|
133
|
+
const result = await tool.execute(
|
|
134
|
+
{ action: 'create', title: 'Test', priority: 'superurgent' }, context
|
|
135
|
+
);
|
|
136
|
+
expect(result.success).toBe(false);
|
|
137
|
+
expect(result.error).toContain('Invalid priority');
|
|
138
|
+
});
|
|
139
|
+
});
|
|
140
|
+
|
|
141
|
+
// ── actions array format ────────────────────────────────────────
|
|
142
|
+
describe('execute - actions array format', () => {
|
|
143
|
+
test('unwraps first element of actions array', async () => {
|
|
144
|
+
const { tool, context } = createTestSetup();
|
|
145
|
+
const result = await tool.execute(
|
|
146
|
+
{ actions: [{ type: 'create', title: 'From array', priority: 'medium' }] },
|
|
147
|
+
context
|
|
148
|
+
);
|
|
149
|
+
expect(result.success).toBe(true);
|
|
150
|
+
expect(result.result.task.title).toBe('From array');
|
|
151
|
+
});
|
|
152
|
+
|
|
153
|
+
test('deep-unwraps tag-parser wrapped values in actions array', async () => {
|
|
154
|
+
const { tool, context } = createTestSetup();
|
|
155
|
+
const result = await tool.execute({
|
|
156
|
+
actions: [{
|
|
157
|
+
type: { value: 'create', attributes: {} },
|
|
158
|
+
title: { value: 'Wrapped', attributes: {} },
|
|
159
|
+
priority: 'medium'
|
|
160
|
+
}]
|
|
161
|
+
}, context);
|
|
162
|
+
expect(result.success).toBe(true);
|
|
163
|
+
expect(result.result.task.title).toBe('Wrapped');
|
|
164
|
+
});
|
|
165
|
+
});
|
|
166
|
+
|
|
167
|
+
// ── update action ───────────────────────────────────────────────
|
|
168
|
+
describe('execute - update', () => {
|
|
169
|
+
test('updates task status and priority', async () => {
|
|
170
|
+
const { tool, agent, context } = createTestSetup();
|
|
171
|
+
await tool.execute({ action: 'create', title: 'Task A', priority: 'low' }, context);
|
|
172
|
+
const taskId = agent.taskList.tasks[0].id;
|
|
173
|
+
const result = await tool.execute(
|
|
174
|
+
{ action: 'update', taskId, status: 'in_progress', priority: 'high' }, context
|
|
175
|
+
);
|
|
176
|
+
expect(result.success).toBe(true);
|
|
177
|
+
expect(result.result.task.status).toBe('in_progress');
|
|
178
|
+
expect(result.result.task.priority).toBe('high');
|
|
179
|
+
});
|
|
180
|
+
|
|
181
|
+
test('updates title and description', async () => {
|
|
182
|
+
const { tool, agent, context } = createTestSetup();
|
|
183
|
+
await tool.execute({ action: 'create', title: 'Old', priority: 'medium' }, context);
|
|
184
|
+
const taskId = agent.taskList.tasks[0].id;
|
|
185
|
+
const result = await tool.execute(
|
|
186
|
+
{ action: 'update', taskId, title: 'New Title', description: 'Desc' }, context
|
|
187
|
+
);
|
|
188
|
+
expect(result.result.task.title).toBe('New Title');
|
|
189
|
+
expect(result.result.task.description).toBe('Desc');
|
|
190
|
+
});
|
|
191
|
+
|
|
192
|
+
test('errors when taskId is missing', async () => {
|
|
193
|
+
const { tool, context } = createTestSetup();
|
|
194
|
+
const result = await tool.execute({ action: 'update', status: 'completed' }, context);
|
|
195
|
+
expect(result.success).toBe(false);
|
|
196
|
+
expect(result.error).toContain('Task ID is required');
|
|
197
|
+
});
|
|
198
|
+
|
|
199
|
+
test('errors for non-existent task', async () => {
|
|
200
|
+
const { tool, context } = createTestSetup();
|
|
201
|
+
const result = await tool.execute({ action: 'update', taskId: 'no-such', status: 'completed' }, context);
|
|
202
|
+
expect(result.success).toBe(false);
|
|
203
|
+
expect(result.error).toContain('Task not found');
|
|
204
|
+
});
|
|
205
|
+
|
|
206
|
+
test('errors for invalid status', async () => {
|
|
207
|
+
const { tool, agent, context } = createTestSetup();
|
|
208
|
+
await tool.execute({ action: 'create', title: 'T', priority: 'medium' }, context);
|
|
209
|
+
const taskId = agent.taskList.tasks[0].id;
|
|
210
|
+
const result = await tool.execute({ action: 'update', taskId, status: 'badstatus' }, context);
|
|
211
|
+
expect(result.success).toBe(false);
|
|
212
|
+
expect(result.error).toContain('Invalid status');
|
|
213
|
+
});
|
|
214
|
+
});
|
|
215
|
+
|
|
216
|
+
// ── list action ─────────────────────────────────────────────────
|
|
217
|
+
describe('execute - list', () => {
|
|
218
|
+
test('lists all tasks with summary counts', async () => {
|
|
219
|
+
const { tool, context } = createTestSetup();
|
|
220
|
+
await tool.execute({ action: 'create', title: 'T1', priority: 'high' }, context);
|
|
221
|
+
await tool.execute({ action: 'create', title: 'T2', priority: 'low' }, context);
|
|
222
|
+
const result = await tool.execute({ action: 'list' }, context);
|
|
223
|
+
expect(result.success).toBe(true);
|
|
224
|
+
expect(result.result.totalTasks).toBe(2);
|
|
225
|
+
expect(result.result.summary.pending).toBe(2);
|
|
226
|
+
});
|
|
227
|
+
|
|
228
|
+
test('filters by status', async () => {
|
|
229
|
+
const { tool, agent, context } = createTestSetup();
|
|
230
|
+
await tool.execute({ action: 'create', title: 'T1', priority: 'high' }, context);
|
|
231
|
+
await tool.execute({ action: 'create', title: 'T2', priority: 'low' }, context);
|
|
232
|
+
await tool.execute({ action: 'complete', taskId: agent.taskList.tasks[0].id }, context);
|
|
233
|
+
const result = await tool.execute({ action: 'list', status: 'pending' }, context);
|
|
234
|
+
expect(result.result.totalTasks).toBe(1);
|
|
235
|
+
});
|
|
236
|
+
|
|
237
|
+
test('filters by priority', async () => {
|
|
238
|
+
const { tool, context } = createTestSetup();
|
|
239
|
+
await tool.execute({ action: 'create', title: 'T1', priority: 'high' }, context);
|
|
240
|
+
await tool.execute({ action: 'create', title: 'T2', priority: 'low' }, context);
|
|
241
|
+
const result = await tool.execute({ action: 'list', priority: 'high' }, context);
|
|
242
|
+
expect(result.result.totalTasks).toBe(1);
|
|
243
|
+
});
|
|
244
|
+
|
|
245
|
+
test('sorts by priority then creation date', async () => {
|
|
246
|
+
const { tool, context } = createTestSetup();
|
|
247
|
+
await tool.execute({ action: 'create', title: 'Low', priority: 'low' }, context);
|
|
248
|
+
await tool.execute({ action: 'create', title: 'High', priority: 'high' }, context);
|
|
249
|
+
const result = await tool.execute({ action: 'list' }, context);
|
|
250
|
+
expect(result.result.tasks[0].title).toBe('High');
|
|
251
|
+
});
|
|
252
|
+
});
|
|
253
|
+
|
|
254
|
+
// ── complete action ─────────────────────────────────────────────
|
|
255
|
+
describe('execute - complete', () => {
|
|
256
|
+
test('marks task as completed with timestamp', async () => {
|
|
257
|
+
const { tool, agent, context } = createTestSetup();
|
|
258
|
+
await tool.execute({ action: 'create', title: 'Finish', priority: 'medium' }, context);
|
|
259
|
+
const taskId = agent.taskList.tasks[0].id;
|
|
260
|
+
const result = await tool.execute({ action: 'complete', taskId }, context);
|
|
261
|
+
expect(result.success).toBe(true);
|
|
262
|
+
expect(result.result.task.status).toBe('completed');
|
|
263
|
+
expect(result.result.task.completedAt).toBeDefined();
|
|
264
|
+
});
|
|
265
|
+
|
|
266
|
+
test('auto-completes first in-progress task when no taskId', async () => {
|
|
267
|
+
const { tool, agent, context } = createTestSetup();
|
|
268
|
+
await tool.execute({ action: 'create', title: 'Auto', priority: 'medium' }, context);
|
|
269
|
+
agent.taskList.tasks[0].status = 'in_progress';
|
|
270
|
+
const result = await tool.execute({ action: 'complete' }, context);
|
|
271
|
+
expect(result.result.task.status).toBe('completed');
|
|
272
|
+
});
|
|
273
|
+
|
|
274
|
+
test('returns message for already-completed task', async () => {
|
|
275
|
+
const { tool, agent, context } = createTestSetup();
|
|
276
|
+
await tool.execute({ action: 'create', title: 'Done', priority: 'medium' }, context);
|
|
277
|
+
const taskId = agent.taskList.tasks[0].id;
|
|
278
|
+
await tool.execute({ action: 'complete', taskId }, context);
|
|
279
|
+
const result = await tool.execute({ action: 'complete', taskId }, context);
|
|
280
|
+
expect(result.result.message).toContain('already completed');
|
|
281
|
+
});
|
|
282
|
+
|
|
283
|
+
test('sets TTL to 1 when no pending tasks remain', async () => {
|
|
284
|
+
const { tool, agent, context } = createTestSetup();
|
|
285
|
+
await tool.execute({ action: 'create', title: 'Only', priority: 'medium' }, context);
|
|
286
|
+
await tool.execute({ action: 'complete', taskId: agent.taskList.tasks[0].id }, context);
|
|
287
|
+
expect(agent.ttl).toBe(1);
|
|
288
|
+
});
|
|
289
|
+
|
|
290
|
+
test('includes no-tasks hint in summary', async () => {
|
|
291
|
+
const { tool, agent, context } = createTestSetup();
|
|
292
|
+
await tool.execute({ action: 'create', title: 'Only', priority: 'medium' }, context);
|
|
293
|
+
const result = await tool.execute({ action: 'complete', taskId: agent.taskList.tasks[0].id }, context);
|
|
294
|
+
expect(result.summary).toContain('No remaining tasks');
|
|
295
|
+
});
|
|
296
|
+
|
|
297
|
+
test('errors when no taskId and no in-progress tasks', async () => {
|
|
298
|
+
const { tool, context } = createTestSetup();
|
|
299
|
+
const result = await tool.execute({ action: 'complete' }, context);
|
|
300
|
+
expect(result.success).toBe(false);
|
|
301
|
+
expect(result.error).toContain('No task ID provided');
|
|
302
|
+
});
|
|
303
|
+
});
|
|
304
|
+
|
|
305
|
+
// ── cancel action ───────────────────────────────────────────────
|
|
306
|
+
describe('execute - cancel', () => {
|
|
307
|
+
test('cancels a task with reason', async () => {
|
|
308
|
+
const { tool, agent, context } = createTestSetup();
|
|
309
|
+
await tool.execute({ action: 'create', title: 'Cancel me', priority: 'medium' }, context);
|
|
310
|
+
const taskId = agent.taskList.tasks[0].id;
|
|
311
|
+
const result = await tool.execute({ action: 'cancel', taskId, reason: 'Not needed' }, context);
|
|
312
|
+
expect(result.success).toBe(true);
|
|
313
|
+
expect(result.result.task.status).toBe('cancelled');
|
|
314
|
+
expect(result.result.task.cancellationReason).toBe('Not needed');
|
|
315
|
+
});
|
|
316
|
+
|
|
317
|
+
test('errors when taskId is missing', async () => {
|
|
318
|
+
const { tool, context } = createTestSetup();
|
|
319
|
+
const result = await tool.execute({ action: 'cancel' }, context);
|
|
320
|
+
expect(result.success).toBe(false);
|
|
321
|
+
expect(result.error).toContain('Task ID is required');
|
|
322
|
+
});
|
|
323
|
+
});
|
|
324
|
+
|
|
325
|
+
// ── clear action ────────────────────────────────────────────────
|
|
326
|
+
describe('execute - clear', () => {
|
|
327
|
+
test('removes completed and cancelled tasks', async () => {
|
|
328
|
+
const { tool, agent, context } = createTestSetup();
|
|
329
|
+
await tool.execute({ action: 'create', title: 'T1', priority: 'medium' }, context);
|
|
330
|
+
await tool.execute({ action: 'create', title: 'T2', priority: 'medium' }, context);
|
|
331
|
+
await tool.execute({ action: 'create', title: 'T3', priority: 'medium' }, context);
|
|
332
|
+
await tool.execute({ action: 'complete', taskId: agent.taskList.tasks[0].id }, context);
|
|
333
|
+
await tool.execute({ action: 'cancel', taskId: agent.taskList.tasks[1].id }, context);
|
|
334
|
+
const result = await tool.execute({ action: 'clear' }, context);
|
|
335
|
+
expect(result.result.removed).toBe(2);
|
|
336
|
+
expect(agent.taskList.tasks).toHaveLength(1);
|
|
337
|
+
});
|
|
338
|
+
});
|
|
339
|
+
|
|
340
|
+
// ── depend action ───────────────────────────────────────────────
|
|
341
|
+
describe('execute - depend', () => {
|
|
342
|
+
test('creates blocking dependency and sets blocked status', async () => {
|
|
343
|
+
const { tool, agent, context } = createTestSetup();
|
|
344
|
+
await tool.execute({ action: 'create', title: 'T1', priority: 'high' }, context);
|
|
345
|
+
await tool.execute({ action: 'create', title: 'T2', priority: 'medium' }, context);
|
|
346
|
+
const [tA, tB] = agent.taskList.tasks;
|
|
347
|
+
const result = await tool.execute(
|
|
348
|
+
{ action: 'depend', taskId: tB.id, dependsOn: tA.id, dependencyType: 'blocks' }, context
|
|
349
|
+
);
|
|
350
|
+
expect(result.success).toBe(true);
|
|
351
|
+
expect(result.result.dependency.taskId).toBe(tA.id);
|
|
352
|
+
expect(agent.taskList.tasks[1].status).toBe('blocked');
|
|
353
|
+
});
|
|
354
|
+
|
|
355
|
+
test('returns already-exists for duplicate dependency', async () => {
|
|
356
|
+
const { tool, agent, context } = createTestSetup();
|
|
357
|
+
await tool.execute({ action: 'create', title: 'T1', priority: 'high' }, context);
|
|
358
|
+
await tool.execute({ action: 'create', title: 'T2', priority: 'medium' }, context);
|
|
359
|
+
const [tA, tB] = agent.taskList.tasks;
|
|
360
|
+
await tool.execute({ action: 'depend', taskId: tB.id, dependsOn: tA.id }, context);
|
|
361
|
+
const result = await tool.execute({ action: 'depend', taskId: tB.id, dependsOn: tA.id }, context);
|
|
362
|
+
expect(result.result.message).toContain('already exists');
|
|
363
|
+
});
|
|
364
|
+
|
|
365
|
+
test('errors when both params are missing', async () => {
|
|
366
|
+
const { tool, context } = createTestSetup();
|
|
367
|
+
const result = await tool.execute({ action: 'depend', taskId: 'x' }, context);
|
|
368
|
+
expect(result.success).toBe(false);
|
|
369
|
+
});
|
|
370
|
+
|
|
371
|
+
test('errors for invalid dependency type', async () => {
|
|
372
|
+
const { tool, agent, context } = createTestSetup();
|
|
373
|
+
await tool.execute({ action: 'create', title: 'A', priority: 'high' }, context);
|
|
374
|
+
await tool.execute({ action: 'create', title: 'B', priority: 'high' }, context);
|
|
375
|
+
const [a, b] = agent.taskList.tasks;
|
|
376
|
+
const result = await tool.execute(
|
|
377
|
+
{ action: 'depend', taskId: b.id, dependsOn: a.id, dependencyType: 'invalid' }, context
|
|
378
|
+
);
|
|
379
|
+
expect(result.success).toBe(false);
|
|
380
|
+
expect(result.error).toContain('Invalid dependency type');
|
|
381
|
+
});
|
|
382
|
+
});
|
|
383
|
+
|
|
384
|
+
// ── relate action ───────────────────────────────────────────────
|
|
385
|
+
describe('execute - relate', () => {
|
|
386
|
+
test('creates a relates-type dependency', async () => {
|
|
387
|
+
const { tool, agent, context } = createTestSetup();
|
|
388
|
+
await tool.execute({ action: 'create', title: 'T1', priority: 'high' }, context);
|
|
389
|
+
await tool.execute({ action: 'create', title: 'T2', priority: 'medium' }, context);
|
|
390
|
+
const [tA, tB] = agent.taskList.tasks;
|
|
391
|
+
const result = await tool.execute(
|
|
392
|
+
{ action: 'relate', taskId: tB.id, dependsOn: tA.id }, context
|
|
393
|
+
);
|
|
394
|
+
expect(result.result.dependency.type).toBe('relates');
|
|
395
|
+
});
|
|
396
|
+
});
|
|
397
|
+
|
|
398
|
+
// ── subtask action ──────────────────────────────────────────────
|
|
399
|
+
describe('execute - subtask', () => {
|
|
400
|
+
test('creates subtask under parent', async () => {
|
|
401
|
+
const { tool, agent, context } = createTestSetup();
|
|
402
|
+
await tool.execute({ action: 'create', title: 'Parent', priority: 'high' }, context);
|
|
403
|
+
const parentId = agent.taskList.tasks[0].id;
|
|
404
|
+
const result = await tool.execute(
|
|
405
|
+
{ action: 'subtask', parentTaskId: parentId, title: 'Sub 1', priority: 'medium' }, context
|
|
406
|
+
);
|
|
407
|
+
expect(result.success).toBe(true);
|
|
408
|
+
expect(result.result.subtask.isSubtask).toBe(true);
|
|
409
|
+
expect(result.result.subtask.parentTaskId).toBe(parentId);
|
|
410
|
+
expect(agent.taskList.tasks[0].subtasks).toContain(result.result.subtask.id);
|
|
411
|
+
});
|
|
412
|
+
|
|
413
|
+
test('errors when parent not found', async () => {
|
|
414
|
+
const { tool, context } = createTestSetup();
|
|
415
|
+
const result = await tool.execute(
|
|
416
|
+
{ action: 'subtask', parentTaskId: 'no-such', title: 'Sub' }, context
|
|
417
|
+
);
|
|
418
|
+
expect(result.success).toBe(false);
|
|
419
|
+
expect(result.error).toContain('Parent task not found');
|
|
420
|
+
});
|
|
421
|
+
|
|
422
|
+
test('errors when parentTaskId or title missing', async () => {
|
|
423
|
+
const { tool, context } = createTestSetup();
|
|
424
|
+
const result = await tool.execute({ action: 'subtask', title: 'Sub' }, context);
|
|
425
|
+
expect(result.success).toBe(false);
|
|
426
|
+
expect(result.error).toContain('required');
|
|
427
|
+
});
|
|
428
|
+
});
|
|
429
|
+
|
|
430
|
+
// ── sync action ─────────────────────────────────────────────────
|
|
431
|
+
describe('execute - sync', () => {
|
|
432
|
+
test('syncs task list creating new tasks', async () => {
|
|
433
|
+
const { tool, context } = createTestSetup();
|
|
434
|
+
const result = await tool.execute({
|
|
435
|
+
action: 'sync',
|
|
436
|
+
tasks: [
|
|
437
|
+
{ title: 'A', status: 'completed', priority: 'high' },
|
|
438
|
+
{ title: 'B', status: 'in_progress', priority: 'medium' },
|
|
439
|
+
{ title: 'C', status: 'pending', priority: 'low' }
|
|
440
|
+
]
|
|
441
|
+
}, context);
|
|
442
|
+
expect(result.success).toBe(true);
|
|
443
|
+
expect(result.result.summary.total).toBe(3);
|
|
444
|
+
expect(result.result.summary.created).toBe(3);
|
|
445
|
+
});
|
|
446
|
+
|
|
447
|
+
test('updates existing tasks by matching title', async () => {
|
|
448
|
+
const { tool, context } = createTestSetup();
|
|
449
|
+
await tool.execute({ action: 'create', title: 'Existing', priority: 'low' }, context);
|
|
450
|
+
const result = await tool.execute({
|
|
451
|
+
action: 'sync',
|
|
452
|
+
tasks: [{ title: 'Existing', status: 'completed', priority: 'high' }]
|
|
453
|
+
}, context);
|
|
454
|
+
expect(result.result.summary.updated).toBe(1);
|
|
455
|
+
expect(result.result.summary.created).toBe(0);
|
|
456
|
+
});
|
|
457
|
+
|
|
458
|
+
test('parses JSON string tasks', async () => {
|
|
459
|
+
const { tool, context } = createTestSetup();
|
|
460
|
+
const result = await tool.execute({
|
|
461
|
+
action: 'sync',
|
|
462
|
+
tasks: JSON.stringify([{ title: 'JSON', status: 'pending', priority: 'medium' }])
|
|
463
|
+
}, context);
|
|
464
|
+
expect(result.success).toBe(true);
|
|
465
|
+
});
|
|
466
|
+
|
|
467
|
+
test('errors for empty tasks array', async () => {
|
|
468
|
+
const { tool, context } = createTestSetup();
|
|
469
|
+
const result = await tool.execute({ action: 'sync', tasks: [] }, context);
|
|
470
|
+
expect(result.success).toBe(false);
|
|
471
|
+
expect(result.error).toContain('empty');
|
|
472
|
+
});
|
|
473
|
+
|
|
474
|
+
test('errors for invalid status', async () => {
|
|
475
|
+
const { tool, context } = createTestSetup();
|
|
476
|
+
const result = await tool.execute({
|
|
477
|
+
action: 'sync',
|
|
478
|
+
tasks: [{ title: 'Bad', status: 'oops', priority: 'medium' }]
|
|
479
|
+
}, context);
|
|
480
|
+
expect(result.success).toBe(false);
|
|
481
|
+
expect(result.error).toContain('Invalid status');
|
|
482
|
+
});
|
|
483
|
+
|
|
484
|
+
// ── Destructive-sync guardrail ───────────────────────────────────
|
|
485
|
+
// Real production failure: post-compaction, an agent forgot it had
|
|
486
|
+
// a 9-task plan and called sync with 4 unrelated tasks → all 9
|
|
487
|
+
// were silently dropped. These tests pin the guardrail.
|
|
488
|
+
|
|
489
|
+
describe('REGRESSION: destructive-sync guardrail', () => {
|
|
490
|
+
// Helper: seed the agent with two pending + one in_progress task,
|
|
491
|
+
// then attempt to sync with a totally different list.
|
|
492
|
+
async function setupAgentWithPlan() {
|
|
493
|
+
const { tool, context } = createTestSetup();
|
|
494
|
+
await tool.execute({
|
|
495
|
+
action: 'sync',
|
|
496
|
+
tasks: [
|
|
497
|
+
{ title: 'Char select bg', status: 'in_progress', priority: 'high' },
|
|
498
|
+
{ title: 'Board art', status: 'pending', priority: 'high' },
|
|
499
|
+
{ title: 'Dice animation', status: 'pending', priority: 'medium' },
|
|
500
|
+
],
|
|
501
|
+
}, context);
|
|
502
|
+
return { tool, context };
|
|
503
|
+
}
|
|
504
|
+
|
|
505
|
+
test('REFUSES sync that would drop pending/in_progress tasks without confirmReplace', async () => {
|
|
506
|
+
const { tool, context } = await setupAgentWithPlan();
|
|
507
|
+
const result = await tool.execute({
|
|
508
|
+
action: 'sync',
|
|
509
|
+
tasks: [
|
|
510
|
+
{ title: 'Add Settings UI', status: 'pending', priority: 'high' },
|
|
511
|
+
{ title: 'Add Settings logic', status: 'pending', priority: 'high' },
|
|
512
|
+
],
|
|
513
|
+
}, context);
|
|
514
|
+
expect(result.success).toBe(false);
|
|
515
|
+
expect(result.error).toMatch(/Sync would drop 3 non-terminal task/);
|
|
516
|
+
// Must name the at-risk tasks so the agent can see them.
|
|
517
|
+
expect(result.error).toContain('Char select bg');
|
|
518
|
+
expect(result.error).toContain('Board art');
|
|
519
|
+
expect(result.error).toContain('Dice animation');
|
|
520
|
+
// Must explain the escape hatch.
|
|
521
|
+
expect(result.error).toContain('confirmReplace: true');
|
|
522
|
+
});
|
|
523
|
+
|
|
524
|
+
test('PROCEEDS when confirmReplace=true is explicitly set', async () => {
|
|
525
|
+
const { tool, context } = await setupAgentWithPlan();
|
|
526
|
+
const result = await tool.execute({
|
|
527
|
+
action: 'sync',
|
|
528
|
+
confirmReplace: true,
|
|
529
|
+
tasks: [
|
|
530
|
+
{ title: 'Add Settings UI', status: 'pending', priority: 'high' },
|
|
531
|
+
],
|
|
532
|
+
}, context);
|
|
533
|
+
expect(result.success).toBe(true);
|
|
534
|
+
expect(result.result.summary.total).toBe(1);
|
|
535
|
+
expect(result.result.summary.removed).toBe(3);
|
|
536
|
+
});
|
|
537
|
+
|
|
538
|
+
test('does NOT trigger when the incoming list keeps every open task (rename only)', async () => {
|
|
539
|
+
const { tool, context } = await setupAgentWithPlan();
|
|
540
|
+
// Update statuses, but keep all titles. No drops.
|
|
541
|
+
const result = await tool.execute({
|
|
542
|
+
action: 'sync',
|
|
543
|
+
tasks: [
|
|
544
|
+
{ title: 'Char select bg', status: 'completed', priority: 'high' },
|
|
545
|
+
{ title: 'Board art', status: 'in_progress', priority: 'high' },
|
|
546
|
+
{ title: 'Dice animation', status: 'pending', priority: 'medium' },
|
|
547
|
+
],
|
|
548
|
+
}, context);
|
|
549
|
+
expect(result.success).toBe(true);
|
|
550
|
+
expect(result.result.summary.updated).toBe(3);
|
|
551
|
+
expect(result.result.summary.removed).toBe(0);
|
|
552
|
+
});
|
|
553
|
+
|
|
554
|
+
test('does NOT trigger when only completed/cancelled tasks would be dropped', async () => {
|
|
555
|
+
const { tool, context } = createTestSetup();
|
|
556
|
+
// Seed with one done task + one pending.
|
|
557
|
+
await tool.execute({
|
|
558
|
+
action: 'sync',
|
|
559
|
+
tasks: [
|
|
560
|
+
{ title: 'Already done', status: 'completed', priority: 'low' },
|
|
561
|
+
{ title: 'Still going', status: 'pending', priority: 'high' },
|
|
562
|
+
],
|
|
563
|
+
}, context);
|
|
564
|
+
// New sync drops the completed one but keeps the pending one.
|
|
565
|
+
const result = await tool.execute({
|
|
566
|
+
action: 'sync',
|
|
567
|
+
tasks: [{ title: 'Still going', status: 'in_progress', priority: 'high' }],
|
|
568
|
+
}, context);
|
|
569
|
+
// No guardrail trip — completed task is safe to drop.
|
|
570
|
+
expect(result.success).toBe(true);
|
|
571
|
+
});
|
|
572
|
+
|
|
573
|
+
test('error response includes droppedTasks metadata for programmatic recovery', async () => {
|
|
574
|
+
const { tool, context } = await setupAgentWithPlan();
|
|
575
|
+
const result = await tool.execute({
|
|
576
|
+
action: 'sync',
|
|
577
|
+
tasks: [{ title: 'New thing', status: 'pending', priority: 'high' }],
|
|
578
|
+
}, context);
|
|
579
|
+
expect(result.success).toBe(false);
|
|
580
|
+
// BaseTool's execute() catches the thrown Error; the message
|
|
581
|
+
// carries the human-readable hint. We assert on the hint
|
|
582
|
+
// contents — that's what the agent sees.
|
|
583
|
+
expect(result.error).toMatch(/3 non-terminal task/);
|
|
584
|
+
});
|
|
585
|
+
|
|
586
|
+
test('REGRESSION: the exact Talisman failure scenario is now blocked', async () => {
|
|
587
|
+
// Reproduce the production failure: agent has a 9-task plan
|
|
588
|
+
// reflecting the user's UI revision request. Post-compaction,
|
|
589
|
+
// agent loses context and tries to sync a 4-task Settings plan.
|
|
590
|
+
const { tool, context } = createTestSetup();
|
|
591
|
+
await tool.execute({
|
|
592
|
+
action: 'sync',
|
|
593
|
+
tasks: [
|
|
594
|
+
{ title: 'Explore current code and image assets', status: 'in_progress', priority: 'high' },
|
|
595
|
+
{ title: 'Generate character select background', status: 'pending', priority: 'high' },
|
|
596
|
+
{ title: 'Generate board space art for all nodes', status: 'pending', priority: 'high' },
|
|
597
|
+
{ title: 'Generate adventure card art', status: 'pending', priority: 'medium' },
|
|
598
|
+
{ title: 'Fix board to rectangular layout', status: 'pending', priority: 'high' },
|
|
599
|
+
{ title: 'Fix character select sticky buttons', status: 'pending', priority: 'medium' },
|
|
600
|
+
{ title: 'Fix dice animation and combat', status: 'pending', priority: 'high' },
|
|
601
|
+
{ title: 'Remove all emojis from UI', status: 'pending', priority: 'medium' },
|
|
602
|
+
{ title: 'Test and verify all changes', status: 'pending', priority: 'medium' },
|
|
603
|
+
],
|
|
604
|
+
}, context);
|
|
605
|
+
// The agent now (mistakenly) tries to sync a Settings plan.
|
|
606
|
+
const result = await tool.execute({
|
|
607
|
+
action: 'sync',
|
|
608
|
+
tasks: [
|
|
609
|
+
{ title: 'Add Settings UI to index.html', status: 'pending', priority: 'high' },
|
|
610
|
+
{ title: 'Add Settings logic to game.js', status: 'pending', priority: 'high' },
|
|
611
|
+
{ title: 'Wire Settings to title screen', status: 'pending', priority: 'medium' },
|
|
612
|
+
{ title: 'Test Settings persistence', status: 'pending', priority: 'medium' },
|
|
613
|
+
],
|
|
614
|
+
}, context);
|
|
615
|
+
expect(result.success).toBe(false);
|
|
616
|
+
// ALL 9 original tasks must be named so the agent sees them.
|
|
617
|
+
expect(result.error).toContain('Explore current code');
|
|
618
|
+
expect(result.error).toContain('Generate character select background');
|
|
619
|
+
expect(result.error).toContain('Fix dice animation and combat');
|
|
620
|
+
expect(result.error).toContain('Remove all emojis from UI');
|
|
621
|
+
expect(result.error).toMatch(/Sync would drop 9 non-terminal task/);
|
|
622
|
+
});
|
|
623
|
+
});
|
|
624
|
+
|
|
625
|
+
test('enforces only one in_progress task', async () => {
|
|
626
|
+
const { tool, agent, context } = createTestSetup();
|
|
627
|
+
await tool.execute({
|
|
628
|
+
action: 'sync',
|
|
629
|
+
tasks: [
|
|
630
|
+
{ title: 'A', status: 'in_progress', priority: 'high' },
|
|
631
|
+
{ title: 'B', status: 'in_progress', priority: 'medium' }
|
|
632
|
+
]
|
|
633
|
+
}, context);
|
|
634
|
+
const ipCount = agent.taskList.tasks.filter(t => t.status === 'in_progress').length;
|
|
635
|
+
expect(ipCount).toBe(1);
|
|
636
|
+
});
|
|
637
|
+
|
|
638
|
+
test('auto-sets first pending to in_progress when none active', async () => {
|
|
639
|
+
const { tool, agent, context } = createTestSetup();
|
|
640
|
+
await tool.execute({
|
|
641
|
+
action: 'sync',
|
|
642
|
+
tasks: [
|
|
643
|
+
{ title: 'A', status: 'pending', priority: 'high' },
|
|
644
|
+
{ title: 'B', status: 'pending', priority: 'medium' }
|
|
645
|
+
]
|
|
646
|
+
}, context);
|
|
647
|
+
const ipTasks = agent.taskList.tasks.filter(t => t.status === 'in_progress');
|
|
648
|
+
expect(ipTasks).toHaveLength(1);
|
|
649
|
+
});
|
|
650
|
+
});
|
|
651
|
+
|
|
652
|
+
// ── template action ─────────────────────────────────────────────
|
|
653
|
+
describe('execute - template', () => {
|
|
654
|
+
test('lists available templates', async () => {
|
|
655
|
+
const { tool, context } = createTestSetup();
|
|
656
|
+
const result = await tool.execute({ action: 'template', mode: 'list' }, context);
|
|
657
|
+
expect(result.success).toBe(true);
|
|
658
|
+
expect(result.result.builtInTemplates.length).toBeGreaterThan(0);
|
|
659
|
+
});
|
|
660
|
+
|
|
661
|
+
test('applies a built-in template', async () => {
|
|
662
|
+
const { tool, agent, context } = createTestSetup();
|
|
663
|
+
const result = await tool.execute(
|
|
664
|
+
{ action: 'template', mode: 'apply', templateId: 'bug-fix' }, context
|
|
665
|
+
);
|
|
666
|
+
expect(result.success).toBe(true);
|
|
667
|
+
expect(result.result.tasksCreated).toBeGreaterThan(0);
|
|
668
|
+
expect(agent.taskList.tasks.length).toBeGreaterThan(0);
|
|
669
|
+
});
|
|
670
|
+
|
|
671
|
+
test('errors for non-existent template', async () => {
|
|
672
|
+
const { tool, context } = createTestSetup();
|
|
673
|
+
const result = await tool.execute(
|
|
674
|
+
{ action: 'template', mode: 'apply', templateId: 'nope' }, context
|
|
675
|
+
);
|
|
676
|
+
expect(result.success).toBe(false);
|
|
677
|
+
expect(result.error).toContain('Template not found');
|
|
678
|
+
});
|
|
679
|
+
|
|
680
|
+
test('creates a custom template', async () => {
|
|
681
|
+
const { tool, agent, context } = createTestSetup();
|
|
682
|
+
const result = await tool.execute({
|
|
683
|
+
action: 'template',
|
|
684
|
+
mode: 'create',
|
|
685
|
+
customTemplate: {
|
|
686
|
+
name: 'My Workflow',
|
|
687
|
+
description: 'Custom',
|
|
688
|
+
tasks: [{ title: 'Step 1' }, { title: 'Step 2' }]
|
|
689
|
+
}
|
|
690
|
+
}, context);
|
|
691
|
+
expect(result.success).toBe(true);
|
|
692
|
+
expect(agent.customTemplates).toHaveLength(1);
|
|
693
|
+
});
|
|
694
|
+
|
|
695
|
+
test('errors when custom template has no tasks', async () => {
|
|
696
|
+
const { tool, context } = createTestSetup();
|
|
697
|
+
const result = await tool.execute({
|
|
698
|
+
action: 'template',
|
|
699
|
+
mode: 'create',
|
|
700
|
+
customTemplate: { name: 'Empty', tasks: [] }
|
|
701
|
+
}, context);
|
|
702
|
+
expect(result.success).toBe(false);
|
|
703
|
+
expect(result.error).toContain('requires name and at least one task');
|
|
704
|
+
});
|
|
705
|
+
|
|
706
|
+
test('suggests templates based on patterns', async () => {
|
|
707
|
+
const { tool, agent, context } = createTestSetup();
|
|
708
|
+
agent.taskList.tasks.push({
|
|
709
|
+
id: 'bug-task', title: 'Fix login bug', status: 'pending',
|
|
710
|
+
priority: 'high', createdAt: new Date().toISOString()
|
|
711
|
+
});
|
|
712
|
+
const result = await tool.execute({ action: 'template', mode: 'suggest' }, context);
|
|
713
|
+
expect(result.success).toBe(true);
|
|
714
|
+
expect(result.result.suggestions.length).toBeGreaterThan(0);
|
|
715
|
+
});
|
|
716
|
+
|
|
717
|
+
test('errors for invalid template mode', async () => {
|
|
718
|
+
const { tool, context } = createTestSetup();
|
|
719
|
+
const result = await tool.execute({ action: 'template', mode: 'invalid' }, context);
|
|
720
|
+
expect(result.success).toBe(false);
|
|
721
|
+
expect(result.error).toContain('Invalid template mode');
|
|
722
|
+
});
|
|
723
|
+
});
|
|
724
|
+
|
|
725
|
+
// ── progress action ─────────────────────────────────────────────
|
|
726
|
+
describe('execute - progress', () => {
|
|
727
|
+
test('updates task progress with stage and note', async () => {
|
|
728
|
+
const { tool, agent, context } = createTestSetup();
|
|
729
|
+
await tool.execute({ action: 'create', title: 'Dev', priority: 'high' }, context);
|
|
730
|
+
const taskId = agent.taskList.tasks[0].id;
|
|
731
|
+
const result = await tool.execute({
|
|
732
|
+
action: 'progress', mode: 'update', taskId,
|
|
733
|
+
stage: 'in_development', note: 'Started coding'
|
|
734
|
+
}, context);
|
|
735
|
+
expect(result.success).toBe(true);
|
|
736
|
+
expect(result.result.task.progress.stage).toBe('in_development');
|
|
737
|
+
expect(result.result.task.progress.notes).toHaveLength(1);
|
|
738
|
+
});
|
|
739
|
+
|
|
740
|
+
test('setting percentage to 100 completes task', async () => {
|
|
741
|
+
const { tool, agent, context } = createTestSetup();
|
|
742
|
+
await tool.execute({ action: 'create', title: 'Pct', priority: 'high' }, context);
|
|
743
|
+
const taskId = agent.taskList.tasks[0].id;
|
|
744
|
+
await tool.execute({ action: 'progress', mode: 'update', taskId, percentage: 100 }, context);
|
|
745
|
+
expect(agent.taskList.tasks[0].status).toBe('completed');
|
|
746
|
+
expect(agent.taskList.tasks[0].progress.stage).toBe('completed');
|
|
747
|
+
});
|
|
748
|
+
|
|
749
|
+
test('gets progress overview', async () => {
|
|
750
|
+
const { tool, context } = createTestSetup();
|
|
751
|
+
await tool.execute({ action: 'create', title: 'T1', priority: 'high' }, context);
|
|
752
|
+
const result = await tool.execute({ action: 'progress', mode: 'overview' }, context);
|
|
753
|
+
expect(result.success).toBe(true);
|
|
754
|
+
expect(result.result.overview).toBeDefined();
|
|
755
|
+
});
|
|
756
|
+
|
|
757
|
+
test('calculates progress from subtasks', async () => {
|
|
758
|
+
const { tool, agent, context } = createTestSetup();
|
|
759
|
+
await tool.execute({ action: 'create', title: 'Parent', priority: 'high' }, context);
|
|
760
|
+
const parentId = agent.taskList.tasks[0].id;
|
|
761
|
+
await tool.execute({ action: 'subtask', parentTaskId: parentId, title: 'Sub1' }, context);
|
|
762
|
+
const result = await tool.execute({ action: 'progress', mode: 'calculate', taskId: parentId }, context);
|
|
763
|
+
expect(result.result.calculationMethod).toBe('subtasks');
|
|
764
|
+
});
|
|
765
|
+
|
|
766
|
+
test('errors for invalid stage', async () => {
|
|
767
|
+
const { tool, agent, context } = createTestSetup();
|
|
768
|
+
await tool.execute({ action: 'create', title: 'T', priority: 'high' }, context);
|
|
769
|
+
const taskId = agent.taskList.tasks[0].id;
|
|
770
|
+
const result = await tool.execute({ action: 'progress', mode: 'update', taskId, stage: 'nope' }, context);
|
|
771
|
+
expect(result.success).toBe(false);
|
|
772
|
+
expect(result.error).toContain('Invalid progress stage');
|
|
773
|
+
});
|
|
774
|
+
|
|
775
|
+
test('errors for invalid progress mode', async () => {
|
|
776
|
+
const { tool, context } = createTestSetup();
|
|
777
|
+
const result = await tool.execute({ action: 'progress', mode: 'invalid' }, context);
|
|
778
|
+
expect(result.success).toBe(false);
|
|
779
|
+
});
|
|
780
|
+
});
|
|
781
|
+
|
|
782
|
+
// ── prioritize action ───────────────────────────────────────────
|
|
783
|
+
describe('execute - prioritize', () => {
|
|
784
|
+
test('auto-prioritizes tasks', async () => {
|
|
785
|
+
const { tool, context } = createTestSetup();
|
|
786
|
+
await tool.execute({ action: 'create', title: 'T1', priority: 'low' }, context);
|
|
787
|
+
await tool.execute({ action: 'create', title: 'T2', priority: 'medium' }, context);
|
|
788
|
+
const result = await tool.execute({ action: 'prioritize', mode: 'auto' }, context);
|
|
789
|
+
expect(result.success).toBe(true);
|
|
790
|
+
});
|
|
791
|
+
|
|
792
|
+
test('analyzes specific task priority', async () => {
|
|
793
|
+
const { tool, agent, context } = createTestSetup();
|
|
794
|
+
await tool.execute({ action: 'create', title: 'Analyze', priority: 'medium' }, context);
|
|
795
|
+
const taskId = agent.taskList.tasks[0].id;
|
|
796
|
+
const result = await tool.execute({ action: 'prioritize', mode: 'analyze', taskId }, context);
|
|
797
|
+
expect(result.success).toBe(true);
|
|
798
|
+
expect(result.result.task.priorityScore).toBeDefined();
|
|
799
|
+
});
|
|
800
|
+
|
|
801
|
+
test('balance mode without scheduler returns message', async () => {
|
|
802
|
+
const { tool, context } = createTestSetup();
|
|
803
|
+
const result = await tool.execute({ action: 'prioritize', mode: 'balance' }, context);
|
|
804
|
+
expect(result.success).toBe(true);
|
|
805
|
+
expect(result.result.message).toContain('scheduler');
|
|
806
|
+
});
|
|
807
|
+
|
|
808
|
+
test('errors for invalid mode', async () => {
|
|
809
|
+
const { tool, context } = createTestSetup();
|
|
810
|
+
const result = await tool.execute({ action: 'prioritize', mode: 'xyz' }, context);
|
|
811
|
+
expect(result.success).toBe(false);
|
|
812
|
+
});
|
|
813
|
+
});
|
|
814
|
+
|
|
815
|
+
// ── analytics action ────────────────────────────────────────────
|
|
816
|
+
describe('execute - analytics', () => {
|
|
817
|
+
test('generates summary analytics', async () => {
|
|
818
|
+
const { tool, context } = createTestSetup();
|
|
819
|
+
await tool.execute({ action: 'create', title: 'T1', priority: 'high' }, context);
|
|
820
|
+
const result = await tool.execute({ action: 'analytics', mode: 'summary' }, context);
|
|
821
|
+
expect(result.success).toBe(true);
|
|
822
|
+
expect(result.result.generatedAt).toBeDefined();
|
|
823
|
+
});
|
|
824
|
+
|
|
825
|
+
test('errors for invalid analytics mode', async () => {
|
|
826
|
+
const { tool, context } = createTestSetup();
|
|
827
|
+
const result = await tool.execute({ action: 'analytics', mode: 'invalid' }, context);
|
|
828
|
+
expect(result.success).toBe(false);
|
|
829
|
+
expect(result.error).toContain('Invalid analytics mode');
|
|
830
|
+
});
|
|
831
|
+
});
|
|
832
|
+
|
|
833
|
+
// ── error handling ──────────────────────────────────────────────
|
|
834
|
+
describe('execute - error handling', () => {
|
|
835
|
+
test('fails when agentId is missing', async () => {
|
|
836
|
+
const { tool } = createTestSetup();
|
|
837
|
+
const result = await tool.execute({ action: 'list' }, { agentPool: {} });
|
|
838
|
+
expect(result.success).toBe(false);
|
|
839
|
+
expect(result.error).toContain('Agent ID is required');
|
|
840
|
+
});
|
|
841
|
+
|
|
842
|
+
test('fails when agent is not found', async () => {
|
|
843
|
+
const { tool } = createTestSetup();
|
|
844
|
+
const pool = { getAgent: jest.fn().mockResolvedValue(null) };
|
|
845
|
+
const result = await tool.execute({ action: 'list' }, { agentId: 'x', agentPool: pool });
|
|
846
|
+
expect(result.success).toBe(false);
|
|
847
|
+
expect(result.error).toContain('Agent not found');
|
|
848
|
+
});
|
|
849
|
+
|
|
850
|
+
test('fails for unsupported action', async () => {
|
|
851
|
+
const { tool, context } = createTestSetup();
|
|
852
|
+
const result = await tool.execute({ action: 'fly' }, context);
|
|
853
|
+
expect(result.success).toBe(false);
|
|
854
|
+
expect(result.error).toContain('Unsupported action');
|
|
855
|
+
});
|
|
856
|
+
|
|
857
|
+
test('initializes taskList on agent if missing', async () => {
|
|
858
|
+
const { tool, context, agentPool } = createTestSetup();
|
|
859
|
+
const bare = { id: 'agent-1', name: 'Bare' };
|
|
860
|
+
agentPool.getAgent.mockResolvedValue(bare);
|
|
861
|
+
const result = await tool.execute({ action: 'list' }, context);
|
|
862
|
+
expect(result.success).toBe(true);
|
|
863
|
+
expect(bare.taskList).toBeDefined();
|
|
864
|
+
});
|
|
865
|
+
});
|
|
866
|
+
});
|