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
package/src/core/stateManager.js
CHANGED
|
@@ -1,1753 +1,1766 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* StateManager - Handles state persistence, recovery, and project state management
|
|
3
|
-
*
|
|
4
|
-
* Purpose:
|
|
5
|
-
* - Project state persistence and recovery
|
|
6
|
-
* - Agent state management across sessions
|
|
7
|
-
* - Multi-model conversation state handling
|
|
8
|
-
* - Context reference state management
|
|
9
|
-
* - Session recovery and resume functionality
|
|
10
|
-
*
|
|
11
|
-
* IMPORTANT: State is now stored in a platform-appropriate user data directory
|
|
12
|
-
* that persists across npm package updates. See userDataDir.js for details.
|
|
13
|
-
*/
|
|
14
|
-
|
|
15
|
-
import { promises as fs } from 'fs';
|
|
16
|
-
import path from 'path';
|
|
17
|
-
import { getUserDataPaths, ensureUserDataDirs } from '../utilities/userDataDir.js';
|
|
18
|
-
|
|
19
|
-
class StateManager {
|
|
20
|
-
constructor(config, logger) {
|
|
21
|
-
this.config = config;
|
|
22
|
-
this.logger = logger;
|
|
23
|
-
|
|
24
|
-
// UPDATED: Use persistent user data directory instead of relative path
|
|
25
|
-
// This ensures data survives npm package updates
|
|
26
|
-
const userPaths = getUserDataPaths();
|
|
27
|
-
this.stateDirectory = userPaths.state;
|
|
28
|
-
this.userDataPaths = userPaths;
|
|
29
|
-
|
|
30
|
-
// Legacy: Keep for backwards compatibility detection
|
|
31
|
-
this.legacyStateDirectory = config.system?.stateDirectory || '.loxia-state';
|
|
32
|
-
this.stateVersion = '1.0.0';
|
|
33
|
-
|
|
34
|
-
// State file paths
|
|
35
|
-
this.stateFiles = {
|
|
36
|
-
projectState: 'project-state.json',
|
|
37
|
-
agentIndex: 'agent-index.json',
|
|
38
|
-
teamIndex: 'team-index.json',
|
|
39
|
-
flowIndex: 'flow-index.json',
|
|
40
|
-
flowRunIndex: 'flow-run-index.json',
|
|
41
|
-
conversationIndex: 'conversation-index.json',
|
|
42
|
-
lastSession: 'last-session.json',
|
|
43
|
-
contextReferences: 'context-references.json',
|
|
44
|
-
asyncOperations: 'operations/async-operations.json',
|
|
45
|
-
pausedAgents: 'operations/paused-agents.json',
|
|
46
|
-
toolHistory: 'operations/tool-history.json',
|
|
47
|
-
modelRouterCache: 'models/model-router-cache.json',
|
|
48
|
-
errorRecoveryLog: 'models/error-recovery-log.json'
|
|
49
|
-
};
|
|
50
|
-
}
|
|
51
|
-
|
|
52
|
-
/**
|
|
53
|
-
* Get the state directory path
|
|
54
|
-
* UPDATED: Now returns the user data directory (absolute path)
|
|
55
|
-
* The projectDir parameter is kept for API compatibility but is ignored
|
|
56
|
-
* @param {string} projectDir - Ignored, kept for compatibility
|
|
57
|
-
* @returns {string} Absolute path to state directory
|
|
58
|
-
*/
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
*
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
*
|
|
75
|
-
* @
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
*
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
}
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
const
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
*
|
|
175
|
-
* @
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
const
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
this.
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
*
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
*
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
*
|
|
261
|
-
* @
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
const
|
|
267
|
-
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
|
|
271
|
-
|
|
272
|
-
|
|
273
|
-
|
|
274
|
-
|
|
275
|
-
|
|
276
|
-
|
|
277
|
-
*
|
|
278
|
-
|
|
279
|
-
|
|
280
|
-
|
|
281
|
-
|
|
282
|
-
|
|
283
|
-
|
|
284
|
-
|
|
285
|
-
|
|
286
|
-
|
|
287
|
-
|
|
288
|
-
|
|
289
|
-
|
|
290
|
-
|
|
291
|
-
|
|
292
|
-
|
|
293
|
-
*
|
|
294
|
-
|
|
295
|
-
|
|
296
|
-
|
|
297
|
-
|
|
298
|
-
|
|
299
|
-
|
|
300
|
-
|
|
301
|
-
|
|
302
|
-
|
|
303
|
-
|
|
304
|
-
|
|
305
|
-
|
|
306
|
-
|
|
307
|
-
|
|
308
|
-
|
|
309
|
-
|
|
310
|
-
|
|
311
|
-
|
|
312
|
-
|
|
313
|
-
|
|
314
|
-
|
|
315
|
-
|
|
316
|
-
|
|
317
|
-
|
|
318
|
-
|
|
319
|
-
|
|
320
|
-
|
|
321
|
-
|
|
322
|
-
|
|
323
|
-
|
|
324
|
-
|
|
325
|
-
|
|
326
|
-
|
|
327
|
-
|
|
328
|
-
|
|
329
|
-
|
|
330
|
-
|
|
331
|
-
|
|
332
|
-
|
|
333
|
-
|
|
334
|
-
|
|
335
|
-
|
|
336
|
-
|
|
337
|
-
|
|
338
|
-
|
|
339
|
-
|
|
340
|
-
|
|
341
|
-
|
|
342
|
-
|
|
343
|
-
|
|
344
|
-
|
|
345
|
-
|
|
346
|
-
|
|
347
|
-
|
|
348
|
-
|
|
349
|
-
|
|
350
|
-
|
|
351
|
-
|
|
352
|
-
|
|
353
|
-
|
|
354
|
-
|
|
355
|
-
|
|
356
|
-
|
|
357
|
-
|
|
358
|
-
|
|
359
|
-
|
|
360
|
-
|
|
361
|
-
|
|
362
|
-
|
|
363
|
-
|
|
364
|
-
|
|
365
|
-
|
|
366
|
-
|
|
367
|
-
|
|
368
|
-
|
|
369
|
-
|
|
370
|
-
|
|
371
|
-
|
|
372
|
-
|
|
373
|
-
|
|
374
|
-
|
|
375
|
-
|
|
376
|
-
|
|
377
|
-
|
|
378
|
-
|
|
379
|
-
|
|
380
|
-
|
|
381
|
-
|
|
382
|
-
|
|
383
|
-
|
|
384
|
-
|
|
385
|
-
|
|
386
|
-
|
|
387
|
-
|
|
388
|
-
|
|
389
|
-
|
|
390
|
-
|
|
391
|
-
|
|
392
|
-
|
|
393
|
-
|
|
394
|
-
|
|
395
|
-
|
|
396
|
-
|
|
397
|
-
|
|
398
|
-
|
|
399
|
-
|
|
400
|
-
|
|
401
|
-
|
|
402
|
-
|
|
403
|
-
|
|
404
|
-
|
|
405
|
-
|
|
406
|
-
|
|
407
|
-
|
|
408
|
-
|
|
409
|
-
|
|
410
|
-
|
|
411
|
-
|
|
412
|
-
|
|
413
|
-
|
|
414
|
-
|
|
415
|
-
|
|
416
|
-
|
|
417
|
-
|
|
418
|
-
|
|
419
|
-
|
|
420
|
-
|
|
421
|
-
|
|
422
|
-
|
|
423
|
-
|
|
424
|
-
|
|
425
|
-
|
|
426
|
-
|
|
427
|
-
|
|
428
|
-
|
|
429
|
-
|
|
430
|
-
|
|
431
|
-
|
|
432
|
-
|
|
433
|
-
|
|
434
|
-
|
|
435
|
-
|
|
436
|
-
|
|
437
|
-
|
|
438
|
-
|
|
439
|
-
|
|
440
|
-
|
|
441
|
-
|
|
442
|
-
|
|
443
|
-
|
|
444
|
-
|
|
445
|
-
|
|
446
|
-
|
|
447
|
-
|
|
448
|
-
|
|
449
|
-
|
|
450
|
-
|
|
451
|
-
|
|
452
|
-
|
|
453
|
-
|
|
454
|
-
|
|
455
|
-
|
|
456
|
-
|
|
457
|
-
|
|
458
|
-
|
|
459
|
-
|
|
460
|
-
|
|
461
|
-
|
|
462
|
-
|
|
463
|
-
|
|
464
|
-
|
|
465
|
-
|
|
466
|
-
|
|
467
|
-
|
|
468
|
-
|
|
469
|
-
|
|
470
|
-
|
|
471
|
-
|
|
472
|
-
|
|
473
|
-
|
|
474
|
-
|
|
475
|
-
if (!teamIndex[teamId]
|
|
476
|
-
|
|
477
|
-
|
|
478
|
-
|
|
479
|
-
|
|
480
|
-
|
|
481
|
-
|
|
482
|
-
|
|
483
|
-
|
|
484
|
-
|
|
485
|
-
|
|
486
|
-
|
|
487
|
-
|
|
488
|
-
|
|
489
|
-
|
|
490
|
-
|
|
491
|
-
|
|
492
|
-
|
|
493
|
-
|
|
494
|
-
|
|
495
|
-
|
|
496
|
-
|
|
497
|
-
|
|
498
|
-
|
|
499
|
-
|
|
500
|
-
|
|
501
|
-
if (
|
|
502
|
-
|
|
503
|
-
|
|
504
|
-
|
|
505
|
-
|
|
506
|
-
|
|
507
|
-
|
|
508
|
-
|
|
509
|
-
|
|
510
|
-
|
|
511
|
-
|
|
512
|
-
|
|
513
|
-
|
|
514
|
-
|
|
515
|
-
|
|
516
|
-
|
|
517
|
-
|
|
518
|
-
|
|
519
|
-
|
|
520
|
-
|
|
521
|
-
|
|
522
|
-
|
|
523
|
-
|
|
524
|
-
|
|
525
|
-
|
|
526
|
-
|
|
527
|
-
|
|
528
|
-
|
|
529
|
-
|
|
530
|
-
|
|
531
|
-
|
|
532
|
-
|
|
533
|
-
|
|
534
|
-
|
|
535
|
-
|
|
536
|
-
|
|
537
|
-
|
|
538
|
-
|
|
539
|
-
|
|
540
|
-
|
|
541
|
-
|
|
542
|
-
|
|
543
|
-
|
|
544
|
-
|
|
545
|
-
|
|
546
|
-
|
|
547
|
-
|
|
548
|
-
|
|
549
|
-
|
|
550
|
-
|
|
551
|
-
|
|
552
|
-
|
|
553
|
-
|
|
554
|
-
|
|
555
|
-
|
|
556
|
-
|
|
557
|
-
|
|
558
|
-
|
|
559
|
-
|
|
560
|
-
|
|
561
|
-
|
|
562
|
-
|
|
563
|
-
|
|
564
|
-
|
|
565
|
-
|
|
566
|
-
|
|
567
|
-
|
|
568
|
-
|
|
569
|
-
|
|
570
|
-
|
|
571
|
-
|
|
572
|
-
|
|
573
|
-
|
|
574
|
-
|
|
575
|
-
|
|
576
|
-
|
|
577
|
-
|
|
578
|
-
|
|
579
|
-
|
|
580
|
-
|
|
581
|
-
|
|
582
|
-
|
|
583
|
-
|
|
584
|
-
|
|
585
|
-
|
|
586
|
-
|
|
587
|
-
|
|
588
|
-
|
|
589
|
-
|
|
590
|
-
|
|
591
|
-
|
|
592
|
-
|
|
593
|
-
|
|
594
|
-
//
|
|
595
|
-
|
|
596
|
-
|
|
597
|
-
|
|
598
|
-
//
|
|
599
|
-
|
|
600
|
-
|
|
601
|
-
|
|
602
|
-
|
|
603
|
-
|
|
604
|
-
|
|
605
|
-
|
|
606
|
-
|
|
607
|
-
|
|
608
|
-
|
|
609
|
-
|
|
610
|
-
|
|
611
|
-
|
|
612
|
-
|
|
613
|
-
|
|
614
|
-
|
|
615
|
-
|
|
616
|
-
|
|
617
|
-
|
|
618
|
-
|
|
619
|
-
|
|
620
|
-
|
|
621
|
-
|
|
622
|
-
|
|
623
|
-
|
|
624
|
-
|
|
625
|
-
|
|
626
|
-
|
|
627
|
-
|
|
628
|
-
|
|
629
|
-
|
|
630
|
-
|
|
631
|
-
|
|
632
|
-
|
|
633
|
-
|
|
634
|
-
|
|
635
|
-
|
|
636
|
-
|
|
637
|
-
|
|
638
|
-
|
|
639
|
-
|
|
640
|
-
|
|
641
|
-
|
|
642
|
-
|
|
643
|
-
|
|
644
|
-
|
|
645
|
-
|
|
646
|
-
|
|
647
|
-
|
|
648
|
-
|
|
649
|
-
|
|
650
|
-
|
|
651
|
-
|
|
652
|
-
|
|
653
|
-
|
|
654
|
-
|
|
655
|
-
|
|
656
|
-
|
|
657
|
-
|
|
658
|
-
|
|
659
|
-
|
|
660
|
-
|
|
661
|
-
|
|
662
|
-
|
|
663
|
-
|
|
664
|
-
|
|
665
|
-
|
|
666
|
-
|
|
667
|
-
|
|
668
|
-
|
|
669
|
-
const
|
|
670
|
-
|
|
671
|
-
|
|
672
|
-
|
|
673
|
-
|
|
674
|
-
|
|
675
|
-
|
|
676
|
-
|
|
677
|
-
|
|
678
|
-
|
|
679
|
-
|
|
680
|
-
|
|
681
|
-
|
|
682
|
-
|
|
683
|
-
|
|
684
|
-
|
|
685
|
-
|
|
686
|
-
|
|
687
|
-
|
|
688
|
-
|
|
689
|
-
|
|
690
|
-
|
|
691
|
-
|
|
692
|
-
|
|
693
|
-
|
|
694
|
-
|
|
695
|
-
|
|
696
|
-
|
|
697
|
-
|
|
698
|
-
|
|
699
|
-
|
|
700
|
-
|
|
701
|
-
|
|
702
|
-
|
|
703
|
-
|
|
704
|
-
|
|
705
|
-
|
|
706
|
-
|
|
707
|
-
|
|
708
|
-
|
|
709
|
-
|
|
710
|
-
|
|
711
|
-
|
|
712
|
-
|
|
713
|
-
|
|
714
|
-
|
|
715
|
-
|
|
716
|
-
|
|
717
|
-
|
|
718
|
-
|
|
719
|
-
|
|
720
|
-
|
|
721
|
-
|
|
722
|
-
|
|
723
|
-
|
|
724
|
-
|
|
725
|
-
|
|
726
|
-
|
|
727
|
-
|
|
728
|
-
|
|
729
|
-
|
|
730
|
-
|
|
731
|
-
|
|
732
|
-
|
|
733
|
-
|
|
734
|
-
|
|
735
|
-
|
|
736
|
-
|
|
737
|
-
|
|
738
|
-
|
|
739
|
-
|
|
740
|
-
|
|
741
|
-
|
|
742
|
-
|
|
743
|
-
|
|
744
|
-
|
|
745
|
-
|
|
746
|
-
|
|
747
|
-
|
|
748
|
-
|
|
749
|
-
|
|
750
|
-
const
|
|
751
|
-
|
|
752
|
-
|
|
753
|
-
|
|
754
|
-
|
|
755
|
-
|
|
756
|
-
|
|
757
|
-
|
|
758
|
-
|
|
759
|
-
|
|
760
|
-
|
|
761
|
-
|
|
762
|
-
|
|
763
|
-
|
|
764
|
-
|
|
765
|
-
|
|
766
|
-
|
|
767
|
-
|
|
768
|
-
|
|
769
|
-
|
|
770
|
-
|
|
771
|
-
|
|
772
|
-
|
|
773
|
-
|
|
774
|
-
|
|
775
|
-
|
|
776
|
-
|
|
777
|
-
|
|
778
|
-
|
|
779
|
-
|
|
780
|
-
|
|
781
|
-
|
|
782
|
-
|
|
783
|
-
|
|
784
|
-
|
|
785
|
-
|
|
786
|
-
|
|
787
|
-
|
|
788
|
-
|
|
789
|
-
|
|
790
|
-
|
|
791
|
-
|
|
792
|
-
|
|
793
|
-
|
|
794
|
-
|
|
795
|
-
|
|
796
|
-
|
|
797
|
-
|
|
798
|
-
|
|
799
|
-
|
|
800
|
-
|
|
801
|
-
|
|
802
|
-
|
|
803
|
-
|
|
804
|
-
|
|
805
|
-
|
|
806
|
-
|
|
807
|
-
|
|
808
|
-
|
|
809
|
-
|
|
810
|
-
const
|
|
811
|
-
|
|
812
|
-
|
|
813
|
-
|
|
814
|
-
|
|
815
|
-
|
|
816
|
-
|
|
817
|
-
|
|
818
|
-
|
|
819
|
-
|
|
820
|
-
|
|
821
|
-
|
|
822
|
-
|
|
823
|
-
|
|
824
|
-
|
|
825
|
-
|
|
826
|
-
|
|
827
|
-
|
|
828
|
-
|
|
829
|
-
|
|
830
|
-
|
|
831
|
-
|
|
832
|
-
|
|
833
|
-
|
|
834
|
-
|
|
835
|
-
|
|
836
|
-
|
|
837
|
-
|
|
838
|
-
|
|
839
|
-
|
|
840
|
-
|
|
841
|
-
|
|
842
|
-
|
|
843
|
-
|
|
844
|
-
|
|
845
|
-
|
|
846
|
-
|
|
847
|
-
|
|
848
|
-
|
|
849
|
-
|
|
850
|
-
|
|
851
|
-
|
|
852
|
-
|
|
853
|
-
|
|
854
|
-
|
|
855
|
-
const
|
|
856
|
-
|
|
857
|
-
|
|
858
|
-
|
|
859
|
-
|
|
860
|
-
|
|
861
|
-
|
|
862
|
-
|
|
863
|
-
|
|
864
|
-
|
|
865
|
-
|
|
866
|
-
|
|
867
|
-
|
|
868
|
-
|
|
869
|
-
|
|
870
|
-
|
|
871
|
-
|
|
872
|
-
|
|
873
|
-
|
|
874
|
-
|
|
875
|
-
|
|
876
|
-
|
|
877
|
-
|
|
878
|
-
|
|
879
|
-
|
|
880
|
-
|
|
881
|
-
|
|
882
|
-
|
|
883
|
-
}
|
|
884
|
-
|
|
885
|
-
|
|
886
|
-
|
|
887
|
-
|
|
888
|
-
|
|
889
|
-
|
|
890
|
-
}
|
|
891
|
-
|
|
892
|
-
|
|
893
|
-
|
|
894
|
-
|
|
895
|
-
|
|
896
|
-
|
|
897
|
-
|
|
898
|
-
|
|
899
|
-
|
|
900
|
-
|
|
901
|
-
|
|
902
|
-
|
|
903
|
-
|
|
904
|
-
|
|
905
|
-
|
|
906
|
-
|
|
907
|
-
|
|
908
|
-
|
|
909
|
-
|
|
910
|
-
|
|
911
|
-
|
|
912
|
-
|
|
913
|
-
|
|
914
|
-
|
|
915
|
-
|
|
916
|
-
|
|
917
|
-
|
|
918
|
-
|
|
919
|
-
|
|
920
|
-
|
|
921
|
-
|
|
922
|
-
|
|
923
|
-
|
|
924
|
-
|
|
925
|
-
|
|
926
|
-
|
|
927
|
-
|
|
928
|
-
|
|
929
|
-
|
|
930
|
-
|
|
931
|
-
|
|
932
|
-
|
|
933
|
-
|
|
934
|
-
|
|
935
|
-
|
|
936
|
-
|
|
937
|
-
|
|
938
|
-
|
|
939
|
-
for (const agentId of
|
|
940
|
-
const
|
|
941
|
-
|
|
942
|
-
|
|
943
|
-
|
|
944
|
-
|
|
945
|
-
|
|
946
|
-
|
|
947
|
-
|
|
948
|
-
|
|
949
|
-
|
|
950
|
-
|
|
951
|
-
|
|
952
|
-
|
|
953
|
-
|
|
954
|
-
|
|
955
|
-
|
|
956
|
-
|
|
957
|
-
|
|
958
|
-
|
|
959
|
-
|
|
960
|
-
|
|
961
|
-
|
|
962
|
-
|
|
963
|
-
|
|
964
|
-
|
|
965
|
-
|
|
966
|
-
|
|
967
|
-
|
|
968
|
-
|
|
969
|
-
|
|
970
|
-
|
|
971
|
-
|
|
972
|
-
|
|
973
|
-
|
|
974
|
-
|
|
975
|
-
|
|
976
|
-
|
|
977
|
-
|
|
978
|
-
|
|
979
|
-
|
|
980
|
-
|
|
981
|
-
|
|
982
|
-
|
|
983
|
-
|
|
984
|
-
|
|
985
|
-
|
|
986
|
-
|
|
987
|
-
|
|
988
|
-
|
|
989
|
-
|
|
990
|
-
|
|
991
|
-
|
|
992
|
-
|
|
993
|
-
|
|
994
|
-
|
|
995
|
-
|
|
996
|
-
|
|
997
|
-
|
|
998
|
-
|
|
999
|
-
|
|
1000
|
-
|
|
1001
|
-
|
|
1002
|
-
|
|
1003
|
-
|
|
1004
|
-
|
|
1005
|
-
|
|
1006
|
-
|
|
1007
|
-
|
|
1008
|
-
|
|
1009
|
-
|
|
1010
|
-
|
|
1011
|
-
|
|
1012
|
-
|
|
1013
|
-
|
|
1014
|
-
|
|
1015
|
-
|
|
1016
|
-
|
|
1017
|
-
|
|
1018
|
-
|
|
1019
|
-
|
|
1020
|
-
|
|
1021
|
-
|
|
1022
|
-
|
|
1023
|
-
|
|
1024
|
-
|
|
1025
|
-
|
|
1026
|
-
|
|
1027
|
-
|
|
1028
|
-
|
|
1029
|
-
|
|
1030
|
-
|
|
1031
|
-
|
|
1032
|
-
|
|
1033
|
-
|
|
1034
|
-
|
|
1035
|
-
|
|
1036
|
-
|
|
1037
|
-
|
|
1038
|
-
|
|
1039
|
-
|
|
1040
|
-
|
|
1041
|
-
|
|
1042
|
-
|
|
1043
|
-
|
|
1044
|
-
|
|
1045
|
-
|
|
1046
|
-
|
|
1047
|
-
|
|
1048
|
-
|
|
1049
|
-
|
|
1050
|
-
|
|
1051
|
-
|
|
1052
|
-
|
|
1053
|
-
|
|
1054
|
-
|
|
1055
|
-
|
|
1056
|
-
|
|
1057
|
-
|
|
1058
|
-
|
|
1059
|
-
|
|
1060
|
-
|
|
1061
|
-
|
|
1062
|
-
|
|
1063
|
-
|
|
1064
|
-
|
|
1065
|
-
|
|
1066
|
-
|
|
1067
|
-
|
|
1068
|
-
|
|
1069
|
-
|
|
1070
|
-
|
|
1071
|
-
|
|
1072
|
-
|
|
1073
|
-
|
|
1074
|
-
|
|
1075
|
-
|
|
1076
|
-
|
|
1077
|
-
|
|
1078
|
-
|
|
1079
|
-
|
|
1080
|
-
|
|
1081
|
-
|
|
1082
|
-
|
|
1083
|
-
|
|
1084
|
-
|
|
1085
|
-
|
|
1086
|
-
|
|
1087
|
-
|
|
1088
|
-
|
|
1089
|
-
|
|
1090
|
-
|
|
1091
|
-
|
|
1092
|
-
|
|
1093
|
-
|
|
1094
|
-
|
|
1095
|
-
|
|
1096
|
-
|
|
1097
|
-
|
|
1098
|
-
|
|
1099
|
-
|
|
1100
|
-
|
|
1101
|
-
|
|
1102
|
-
|
|
1103
|
-
|
|
1104
|
-
|
|
1105
|
-
|
|
1106
|
-
|
|
1107
|
-
|
|
1108
|
-
|
|
1109
|
-
|
|
1110
|
-
|
|
1111
|
-
|
|
1112
|
-
|
|
1113
|
-
|
|
1114
|
-
|
|
1115
|
-
|
|
1116
|
-
|
|
1117
|
-
|
|
1118
|
-
|
|
1119
|
-
|
|
1120
|
-
|
|
1121
|
-
|
|
1122
|
-
|
|
1123
|
-
|
|
1124
|
-
|
|
1125
|
-
|
|
1126
|
-
|
|
1127
|
-
|
|
1128
|
-
|
|
1129
|
-
|
|
1130
|
-
|
|
1131
|
-
|
|
1132
|
-
|
|
1133
|
-
|
|
1134
|
-
|
|
1135
|
-
|
|
1136
|
-
|
|
1137
|
-
|
|
1138
|
-
|
|
1139
|
-
|
|
1140
|
-
|
|
1141
|
-
|
|
1142
|
-
|
|
1143
|
-
|
|
1144
|
-
|
|
1145
|
-
|
|
1146
|
-
*
|
|
1147
|
-
|
|
1148
|
-
|
|
1149
|
-
|
|
1150
|
-
|
|
1151
|
-
|
|
1152
|
-
|
|
1153
|
-
|
|
1154
|
-
|
|
1155
|
-
|
|
1156
|
-
|
|
1157
|
-
*
|
|
1158
|
-
*
|
|
1159
|
-
|
|
1160
|
-
|
|
1161
|
-
|
|
1162
|
-
|
|
1163
|
-
|
|
1164
|
-
|
|
1165
|
-
|
|
1166
|
-
*
|
|
1167
|
-
*
|
|
1168
|
-
*
|
|
1169
|
-
*
|
|
1170
|
-
*
|
|
1171
|
-
*
|
|
1172
|
-
*
|
|
1173
|
-
*
|
|
1174
|
-
*
|
|
1175
|
-
* `kind='
|
|
1176
|
-
*
|
|
1177
|
-
*
|
|
1178
|
-
*
|
|
1179
|
-
*
|
|
1180
|
-
*
|
|
1181
|
-
*
|
|
1182
|
-
*
|
|
1183
|
-
*
|
|
1184
|
-
*
|
|
1185
|
-
*
|
|
1186
|
-
*
|
|
1187
|
-
*
|
|
1188
|
-
|
|
1189
|
-
|
|
1190
|
-
|
|
1191
|
-
|
|
1192
|
-
|
|
1193
|
-
|
|
1194
|
-
|
|
1195
|
-
|
|
1196
|
-
|
|
1197
|
-
|
|
1198
|
-
|
|
1199
|
-
|
|
1200
|
-
|
|
1201
|
-
|
|
1202
|
-
|
|
1203
|
-
|
|
1204
|
-
|
|
1205
|
-
|
|
1206
|
-
|
|
1207
|
-
|
|
1208
|
-
|
|
1209
|
-
|
|
1210
|
-
|
|
1211
|
-
|
|
1212
|
-
|
|
1213
|
-
|
|
1214
|
-
|
|
1215
|
-
|
|
1216
|
-
|
|
1217
|
-
|
|
1218
|
-
|
|
1219
|
-
|
|
1220
|
-
|
|
1221
|
-
|
|
1222
|
-
|
|
1223
|
-
|
|
1224
|
-
|
|
1225
|
-
|
|
1226
|
-
|
|
1227
|
-
|
|
1228
|
-
|
|
1229
|
-
|
|
1230
|
-
|
|
1231
|
-
|
|
1232
|
-
|
|
1233
|
-
|
|
1234
|
-
|
|
1235
|
-
|
|
1236
|
-
|
|
1237
|
-
|
|
1238
|
-
|
|
1239
|
-
|
|
1240
|
-
|
|
1241
|
-
|
|
1242
|
-
|
|
1243
|
-
|
|
1244
|
-
|
|
1245
|
-
//
|
|
1246
|
-
|
|
1247
|
-
|
|
1248
|
-
|
|
1249
|
-
|
|
1250
|
-
|
|
1251
|
-
|
|
1252
|
-
|
|
1253
|
-
|
|
1254
|
-
|
|
1255
|
-
|
|
1256
|
-
|
|
1257
|
-
|
|
1258
|
-
|
|
1259
|
-
|
|
1260
|
-
|
|
1261
|
-
}
|
|
1262
|
-
|
|
1263
|
-
|
|
1264
|
-
|
|
1265
|
-
|
|
1266
|
-
|
|
1267
|
-
|
|
1268
|
-
|
|
1269
|
-
|
|
1270
|
-
|
|
1271
|
-
|
|
1272
|
-
|
|
1273
|
-
|
|
1274
|
-
|
|
1275
|
-
|
|
1276
|
-
|
|
1277
|
-
|
|
1278
|
-
|
|
1279
|
-
|
|
1280
|
-
|
|
1281
|
-
|
|
1282
|
-
|
|
1283
|
-
|
|
1284
|
-
|
|
1285
|
-
|
|
1286
|
-
|
|
1287
|
-
|
|
1288
|
-
|
|
1289
|
-
|
|
1290
|
-
|
|
1291
|
-
|
|
1292
|
-
|
|
1293
|
-
|
|
1294
|
-
|
|
1295
|
-
|
|
1296
|
-
|
|
1297
|
-
|
|
1298
|
-
|
|
1299
|
-
|
|
1300
|
-
|
|
1301
|
-
|
|
1302
|
-
|
|
1303
|
-
|
|
1304
|
-
|
|
1305
|
-
|
|
1306
|
-
|
|
1307
|
-
|
|
1308
|
-
|
|
1309
|
-
|
|
1310
|
-
|
|
1311
|
-
|
|
1312
|
-
|
|
1313
|
-
|
|
1314
|
-
|
|
1315
|
-
|
|
1316
|
-
|
|
1317
|
-
|
|
1318
|
-
|
|
1319
|
-
|
|
1320
|
-
|
|
1321
|
-
|
|
1322
|
-
|
|
1323
|
-
|
|
1324
|
-
|
|
1325
|
-
|
|
1326
|
-
|
|
1327
|
-
|
|
1328
|
-
|
|
1329
|
-
|
|
1330
|
-
|
|
1331
|
-
|
|
1332
|
-
|
|
1333
|
-
|
|
1334
|
-
|
|
1335
|
-
|
|
1336
|
-
|
|
1337
|
-
|
|
1338
|
-
|
|
1339
|
-
|
|
1340
|
-
|
|
1341
|
-
|
|
1342
|
-
|
|
1343
|
-
|
|
1344
|
-
|
|
1345
|
-
|
|
1346
|
-
|
|
1347
|
-
|
|
1348
|
-
|
|
1349
|
-
|
|
1350
|
-
|
|
1351
|
-
|
|
1352
|
-
|
|
1353
|
-
|
|
1354
|
-
|
|
1355
|
-
|
|
1356
|
-
|
|
1357
|
-
|
|
1358
|
-
|
|
1359
|
-
|
|
1360
|
-
|
|
1361
|
-
|
|
1362
|
-
|
|
1363
|
-
|
|
1364
|
-
|
|
1365
|
-
|
|
1366
|
-
|
|
1367
|
-
|
|
1368
|
-
|
|
1369
|
-
|
|
1370
|
-
|
|
1371
|
-
|
|
1372
|
-
|
|
1373
|
-
|
|
1374
|
-
|
|
1375
|
-
|
|
1376
|
-
|
|
1377
|
-
|
|
1378
|
-
|
|
1379
|
-
|
|
1380
|
-
|
|
1381
|
-
|
|
1382
|
-
|
|
1383
|
-
|
|
1384
|
-
|
|
1385
|
-
|
|
1386
|
-
|
|
1387
|
-
|
|
1388
|
-
|
|
1389
|
-
|
|
1390
|
-
|
|
1391
|
-
|
|
1392
|
-
|
|
1393
|
-
|
|
1394
|
-
|
|
1395
|
-
|
|
1396
|
-
|
|
1397
|
-
|
|
1398
|
-
|
|
1399
|
-
|
|
1400
|
-
|
|
1401
|
-
|
|
1402
|
-
|
|
1403
|
-
|
|
1404
|
-
|
|
1405
|
-
|
|
1406
|
-
|
|
1407
|
-
|
|
1408
|
-
|
|
1409
|
-
|
|
1410
|
-
|
|
1411
|
-
|
|
1412
|
-
|
|
1413
|
-
|
|
1414
|
-
|
|
1415
|
-
|
|
1416
|
-
|
|
1417
|
-
|
|
1418
|
-
|
|
1419
|
-
|
|
1420
|
-
|
|
1421
|
-
|
|
1422
|
-
|
|
1423
|
-
|
|
1424
|
-
|
|
1425
|
-
|
|
1426
|
-
|
|
1427
|
-
|
|
1428
|
-
|
|
1429
|
-
|
|
1430
|
-
|
|
1431
|
-
|
|
1432
|
-
|
|
1433
|
-
|
|
1434
|
-
|
|
1435
|
-
|
|
1436
|
-
|
|
1437
|
-
|
|
1438
|
-
|
|
1439
|
-
|
|
1440
|
-
|
|
1441
|
-
|
|
1442
|
-
|
|
1443
|
-
|
|
1444
|
-
|
|
1445
|
-
|
|
1446
|
-
|
|
1447
|
-
|
|
1448
|
-
|
|
1449
|
-
|
|
1450
|
-
|
|
1451
|
-
|
|
1452
|
-
|
|
1453
|
-
|
|
1454
|
-
|
|
1455
|
-
|
|
1456
|
-
|
|
1457
|
-
|
|
1458
|
-
|
|
1459
|
-
|
|
1460
|
-
|
|
1461
|
-
|
|
1462
|
-
|
|
1463
|
-
|
|
1464
|
-
|
|
1465
|
-
|
|
1466
|
-
|
|
1467
|
-
|
|
1468
|
-
|
|
1469
|
-
|
|
1470
|
-
|
|
1471
|
-
|
|
1472
|
-
|
|
1473
|
-
|
|
1474
|
-
|
|
1475
|
-
|
|
1476
|
-
|
|
1477
|
-
|
|
1478
|
-
|
|
1479
|
-
|
|
1480
|
-
|
|
1481
|
-
|
|
1482
|
-
|
|
1483
|
-
|
|
1484
|
-
|
|
1485
|
-
|
|
1486
|
-
|
|
1487
|
-
|
|
1488
|
-
|
|
1489
|
-
|
|
1490
|
-
|
|
1491
|
-
|
|
1492
|
-
|
|
1493
|
-
|
|
1494
|
-
|
|
1495
|
-
|
|
1496
|
-
|
|
1497
|
-
|
|
1498
|
-
|
|
1499
|
-
|
|
1500
|
-
|
|
1501
|
-
|
|
1502
|
-
|
|
1503
|
-
|
|
1504
|
-
|
|
1505
|
-
|
|
1506
|
-
|
|
1507
|
-
|
|
1508
|
-
|
|
1509
|
-
|
|
1510
|
-
|
|
1511
|
-
|
|
1512
|
-
|
|
1513
|
-
|
|
1514
|
-
|
|
1515
|
-
|
|
1516
|
-
|
|
1517
|
-
|
|
1518
|
-
|
|
1519
|
-
|
|
1520
|
-
|
|
1521
|
-
|
|
1522
|
-
|
|
1523
|
-
|
|
1524
|
-
|
|
1525
|
-
|
|
1526
|
-
|
|
1527
|
-
|
|
1528
|
-
|
|
1529
|
-
|
|
1530
|
-
|
|
1531
|
-
|
|
1532
|
-
|
|
1533
|
-
|
|
1534
|
-
|
|
1535
|
-
|
|
1536
|
-
|
|
1537
|
-
|
|
1538
|
-
|
|
1539
|
-
|
|
1540
|
-
|
|
1541
|
-
|
|
1542
|
-
|
|
1543
|
-
|
|
1544
|
-
|
|
1545
|
-
|
|
1546
|
-
|
|
1547
|
-
|
|
1548
|
-
|
|
1549
|
-
|
|
1550
|
-
|
|
1551
|
-
|
|
1552
|
-
|
|
1553
|
-
|
|
1554
|
-
|
|
1555
|
-
|
|
1556
|
-
|
|
1557
|
-
|
|
1558
|
-
|
|
1559
|
-
|
|
1560
|
-
|
|
1561
|
-
|
|
1562
|
-
|
|
1563
|
-
|
|
1564
|
-
|
|
1565
|
-
|
|
1566
|
-
|
|
1567
|
-
|
|
1568
|
-
|
|
1569
|
-
|
|
1570
|
-
|
|
1571
|
-
|
|
1572
|
-
|
|
1573
|
-
|
|
1574
|
-
|
|
1575
|
-
|
|
1576
|
-
|
|
1577
|
-
|
|
1578
|
-
|
|
1579
|
-
|
|
1580
|
-
|
|
1581
|
-
|
|
1582
|
-
|
|
1583
|
-
|
|
1584
|
-
|
|
1585
|
-
|
|
1586
|
-
|
|
1587
|
-
|
|
1588
|
-
|
|
1589
|
-
|
|
1590
|
-
|
|
1591
|
-
|
|
1592
|
-
|
|
1593
|
-
|
|
1594
|
-
|
|
1595
|
-
|
|
1596
|
-
|
|
1597
|
-
|
|
1598
|
-
|
|
1599
|
-
|
|
1600
|
-
|
|
1601
|
-
|
|
1602
|
-
|
|
1603
|
-
|
|
1604
|
-
|
|
1605
|
-
|
|
1606
|
-
|
|
1607
|
-
|
|
1608
|
-
|
|
1609
|
-
|
|
1610
|
-
|
|
1611
|
-
|
|
1612
|
-
|
|
1613
|
-
|
|
1614
|
-
|
|
1615
|
-
|
|
1616
|
-
|
|
1617
|
-
|
|
1618
|
-
|
|
1619
|
-
|
|
1620
|
-
|
|
1621
|
-
|
|
1622
|
-
|
|
1623
|
-
|
|
1624
|
-
|
|
1625
|
-
|
|
1626
|
-
|
|
1627
|
-
|
|
1628
|
-
|
|
1629
|
-
|
|
1630
|
-
|
|
1631
|
-
|
|
1632
|
-
|
|
1633
|
-
|
|
1634
|
-
|
|
1635
|
-
|
|
1636
|
-
|
|
1637
|
-
|
|
1638
|
-
|
|
1639
|
-
|
|
1640
|
-
|
|
1641
|
-
|
|
1642
|
-
//
|
|
1643
|
-
const
|
|
1644
|
-
const
|
|
1645
|
-
|
|
1646
|
-
if (!
|
|
1647
|
-
throw new Error(`
|
|
1648
|
-
}
|
|
1649
|
-
|
|
1650
|
-
// Load state
|
|
1651
|
-
const
|
|
1652
|
-
const
|
|
1653
|
-
|
|
1654
|
-
|
|
1655
|
-
|
|
1656
|
-
|
|
1657
|
-
|
|
1658
|
-
|
|
1659
|
-
|
|
1660
|
-
|
|
1661
|
-
|
|
1662
|
-
|
|
1663
|
-
|
|
1664
|
-
|
|
1665
|
-
|
|
1666
|
-
|
|
1667
|
-
|
|
1668
|
-
|
|
1669
|
-
|
|
1670
|
-
|
|
1671
|
-
|
|
1672
|
-
|
|
1673
|
-
|
|
1674
|
-
|
|
1675
|
-
|
|
1676
|
-
|
|
1677
|
-
|
|
1678
|
-
|
|
1679
|
-
|
|
1680
|
-
|
|
1681
|
-
|
|
1682
|
-
|
|
1683
|
-
|
|
1684
|
-
|
|
1685
|
-
|
|
1686
|
-
|
|
1687
|
-
|
|
1688
|
-
|
|
1689
|
-
|
|
1690
|
-
|
|
1691
|
-
|
|
1692
|
-
|
|
1693
|
-
|
|
1694
|
-
|
|
1695
|
-
|
|
1696
|
-
|
|
1697
|
-
|
|
1698
|
-
|
|
1699
|
-
|
|
1700
|
-
|
|
1701
|
-
|
|
1702
|
-
|
|
1703
|
-
|
|
1704
|
-
|
|
1705
|
-
|
|
1706
|
-
|
|
1707
|
-
|
|
1708
|
-
|
|
1709
|
-
|
|
1710
|
-
|
|
1711
|
-
|
|
1712
|
-
|
|
1713
|
-
|
|
1714
|
-
|
|
1715
|
-
|
|
1716
|
-
|
|
1717
|
-
|
|
1718
|
-
|
|
1719
|
-
|
|
1720
|
-
|
|
1721
|
-
if (!
|
|
1722
|
-
throw new Error(
|
|
1723
|
-
}
|
|
1724
|
-
|
|
1725
|
-
|
|
1726
|
-
|
|
1727
|
-
|
|
1728
|
-
|
|
1729
|
-
|
|
1730
|
-
//
|
|
1731
|
-
|
|
1732
|
-
|
|
1733
|
-
|
|
1734
|
-
if (
|
|
1735
|
-
|
|
1736
|
-
|
|
1737
|
-
|
|
1738
|
-
}
|
|
1739
|
-
|
|
1740
|
-
//
|
|
1741
|
-
await this.
|
|
1742
|
-
|
|
1743
|
-
|
|
1744
|
-
|
|
1745
|
-
|
|
1746
|
-
|
|
1747
|
-
|
|
1748
|
-
|
|
1749
|
-
|
|
1750
|
-
|
|
1751
|
-
}
|
|
1752
|
-
|
|
1
|
+
/**
|
|
2
|
+
* StateManager - Handles state persistence, recovery, and project state management
|
|
3
|
+
*
|
|
4
|
+
* Purpose:
|
|
5
|
+
* - Project state persistence and recovery
|
|
6
|
+
* - Agent state management across sessions
|
|
7
|
+
* - Multi-model conversation state handling
|
|
8
|
+
* - Context reference state management
|
|
9
|
+
* - Session recovery and resume functionality
|
|
10
|
+
*
|
|
11
|
+
* IMPORTANT: State is now stored in a platform-appropriate user data directory
|
|
12
|
+
* that persists across npm package updates. See userDataDir.js for details.
|
|
13
|
+
*/
|
|
14
|
+
|
|
15
|
+
import { promises as fs } from 'fs';
|
|
16
|
+
import path from 'path';
|
|
17
|
+
import { getUserDataPaths, ensureUserDataDirs } from '../utilities/userDataDir.js';
|
|
18
|
+
|
|
19
|
+
class StateManager {
|
|
20
|
+
constructor(config, logger) {
|
|
21
|
+
this.config = config;
|
|
22
|
+
this.logger = logger;
|
|
23
|
+
|
|
24
|
+
// UPDATED: Use persistent user data directory instead of relative path
|
|
25
|
+
// This ensures data survives npm package updates
|
|
26
|
+
const userPaths = getUserDataPaths();
|
|
27
|
+
this.stateDirectory = userPaths.state;
|
|
28
|
+
this.userDataPaths = userPaths;
|
|
29
|
+
|
|
30
|
+
// Legacy: Keep for backwards compatibility detection
|
|
31
|
+
this.legacyStateDirectory = config.system?.stateDirectory || '.loxia-state';
|
|
32
|
+
this.stateVersion = '1.0.0';
|
|
33
|
+
|
|
34
|
+
// State file paths
|
|
35
|
+
this.stateFiles = {
|
|
36
|
+
projectState: 'project-state.json',
|
|
37
|
+
agentIndex: 'agent-index.json',
|
|
38
|
+
teamIndex: 'team-index.json',
|
|
39
|
+
flowIndex: 'flow-index.json',
|
|
40
|
+
flowRunIndex: 'flow-run-index.json',
|
|
41
|
+
conversationIndex: 'conversation-index.json',
|
|
42
|
+
lastSession: 'last-session.json',
|
|
43
|
+
contextReferences: 'context-references.json',
|
|
44
|
+
asyncOperations: 'operations/async-operations.json',
|
|
45
|
+
pausedAgents: 'operations/paused-agents.json',
|
|
46
|
+
toolHistory: 'operations/tool-history.json',
|
|
47
|
+
modelRouterCache: 'models/model-router-cache.json',
|
|
48
|
+
errorRecoveryLog: 'models/error-recovery-log.json'
|
|
49
|
+
};
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
/**
|
|
53
|
+
* Get the state directory path
|
|
54
|
+
* UPDATED: Now returns the user data directory (absolute path)
|
|
55
|
+
* The projectDir parameter is kept for API compatibility but is ignored
|
|
56
|
+
* @param {string} projectDir - Ignored, kept for compatibility
|
|
57
|
+
* @returns {string} Absolute path to state directory
|
|
58
|
+
*/
|
|
59
|
+
// eslint-disable-next-line no-unused-vars
|
|
60
|
+
getStateDir(projectDir) {
|
|
61
|
+
// Always use the persistent user data directory
|
|
62
|
+
return this.stateDirectory;
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
/**
|
|
66
|
+
* Get the agents subdirectory path
|
|
67
|
+
* @returns {string} Absolute path to agents directory
|
|
68
|
+
*/
|
|
69
|
+
getAgentsDir() {
|
|
70
|
+
return this.userDataPaths.agents;
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
/**
|
|
74
|
+
* Initialize state directory structure
|
|
75
|
+
* @param {string} projectDir - Project directory path (now ignored, uses user data dir)
|
|
76
|
+
* @returns {Promise<void>}
|
|
77
|
+
*/
|
|
78
|
+
// eslint-disable-next-line no-unused-vars
|
|
79
|
+
async initializeStateDirectory(projectDir) {
|
|
80
|
+
// UPDATED: Use persistent user data directory instead of project-relative path
|
|
81
|
+
// The projectDir parameter is kept for API compatibility but now ignored
|
|
82
|
+
try {
|
|
83
|
+
// Use the centralized utility to create all necessary directories
|
|
84
|
+
const paths = await ensureUserDataDirs();
|
|
85
|
+
|
|
86
|
+
this.logger.info(`State directory initialized in user data location`, {
|
|
87
|
+
stateDir: paths.state,
|
|
88
|
+
platform: process.platform
|
|
89
|
+
});
|
|
90
|
+
|
|
91
|
+
} catch (error) {
|
|
92
|
+
this.logger.error(`Failed to initialize state directory: ${error.message}`);
|
|
93
|
+
throw error;
|
|
94
|
+
}
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
/**
|
|
98
|
+
* Resume project from saved state
|
|
99
|
+
* @param {string} projectDir - Project directory path
|
|
100
|
+
* @returns {Promise<Object>} Resumed project state
|
|
101
|
+
*/
|
|
102
|
+
async resumeProject(projectDir) {
|
|
103
|
+
try {
|
|
104
|
+
await this.initializeStateDirectory(projectDir);
|
|
105
|
+
|
|
106
|
+
// Load project state
|
|
107
|
+
const projectState = await this.loadProjectState(projectDir);
|
|
108
|
+
const agentIndex = await this.loadAgentIndex(projectDir);
|
|
109
|
+
|
|
110
|
+
// Restore agents with multi-model conversations
|
|
111
|
+
const restoredAgents = [];
|
|
112
|
+
for (const [agentId, agentInfo] of Object.entries(agentIndex)) {
|
|
113
|
+
try {
|
|
114
|
+
const agent = await this.restoreAgent(agentId, agentInfo, projectDir);
|
|
115
|
+
restoredAgents.push(agent);
|
|
116
|
+
} catch (error) {
|
|
117
|
+
this.logger.warn(`Failed to restore agent: ${agentId}`, error.message);
|
|
118
|
+
}
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
// Restore async operations
|
|
122
|
+
const asyncOperations = await this.restoreAsyncOperations(projectDir);
|
|
123
|
+
|
|
124
|
+
// Restore paused agents
|
|
125
|
+
const pausedAgents = await this.restorePausedAgents(projectDir);
|
|
126
|
+
|
|
127
|
+
// Restore context references
|
|
128
|
+
const contextReferences = await this.restoreContextReferences(projectDir);
|
|
129
|
+
|
|
130
|
+
const resumedState = {
|
|
131
|
+
projectState,
|
|
132
|
+
agents: restoredAgents,
|
|
133
|
+
asyncOperations,
|
|
134
|
+
pausedAgents,
|
|
135
|
+
contextReferences,
|
|
136
|
+
resumedSuccessfully: true,
|
|
137
|
+
resumedAt: new Date().toISOString()
|
|
138
|
+
};
|
|
139
|
+
|
|
140
|
+
// Update last session
|
|
141
|
+
await this.saveLastSession(projectDir, {
|
|
142
|
+
resumedAt: new Date().toISOString(),
|
|
143
|
+
agentCount: restoredAgents.length,
|
|
144
|
+
operationCount: asyncOperations.length
|
|
145
|
+
});
|
|
146
|
+
|
|
147
|
+
this.logger.info(`Project resumed successfully`, {
|
|
148
|
+
projectDir,
|
|
149
|
+
agentCount: restoredAgents.length,
|
|
150
|
+
operationCount: asyncOperations.length
|
|
151
|
+
});
|
|
152
|
+
|
|
153
|
+
return resumedState;
|
|
154
|
+
|
|
155
|
+
} catch (error) {
|
|
156
|
+
this.logger.error(`Project resume failed: ${error.message}`, {
|
|
157
|
+
projectDir,
|
|
158
|
+
error: error.stack
|
|
159
|
+
});
|
|
160
|
+
|
|
161
|
+
return {
|
|
162
|
+
projectState: null,
|
|
163
|
+
agents: [],
|
|
164
|
+
asyncOperations: [],
|
|
165
|
+
pausedAgents: [],
|
|
166
|
+
contextReferences: [],
|
|
167
|
+
resumedSuccessfully: false,
|
|
168
|
+
error: error.message
|
|
169
|
+
};
|
|
170
|
+
}
|
|
171
|
+
}
|
|
172
|
+
|
|
173
|
+
/**
|
|
174
|
+
* Persist agent state to storage
|
|
175
|
+
* @param {Object} agent - Agent object to persist
|
|
176
|
+
* @param {string} projectDir - Project directory path
|
|
177
|
+
* @returns {Promise<void>}
|
|
178
|
+
*/
|
|
179
|
+
async persistAgentState(agent, projectDir = process.cwd()) {
|
|
180
|
+
const stateDir = this.getStateDir(projectDir);
|
|
181
|
+
const agentStateFile = path.join(stateDir, 'agents', `agent-${agent.id}-state.json`);
|
|
182
|
+
const agentConversationsFile = path.join(stateDir, 'agents', `agent-${agent.id}-conversations.json`);
|
|
183
|
+
|
|
184
|
+
try {
|
|
185
|
+
// Separate conversations from main agent state
|
|
186
|
+
const { conversations, ...agentState } = agent;
|
|
187
|
+
|
|
188
|
+
// Save agent state
|
|
189
|
+
await this.saveJSON(agentStateFile, {
|
|
190
|
+
version: this.stateVersion,
|
|
191
|
+
agentId: agent.id,
|
|
192
|
+
state: agentState,
|
|
193
|
+
lastPersisted: new Date().toISOString()
|
|
194
|
+
});
|
|
195
|
+
|
|
196
|
+
// Save conversations separately
|
|
197
|
+
await this.saveJSON(agentConversationsFile, {
|
|
198
|
+
version: this.stateVersion,
|
|
199
|
+
agentId: agent.id,
|
|
200
|
+
conversations,
|
|
201
|
+
lastPersisted: new Date().toISOString()
|
|
202
|
+
});
|
|
203
|
+
|
|
204
|
+
// Update agent index
|
|
205
|
+
await this.updateAgentIndex(agent, projectDir);
|
|
206
|
+
|
|
207
|
+
this.logger.debug(`Agent state persisted: ${agent.id}`);
|
|
208
|
+
|
|
209
|
+
} catch (error) {
|
|
210
|
+
this.logger.error(`Failed to persist agent state: ${error.message}`, {
|
|
211
|
+
agentId: agent.id,
|
|
212
|
+
error: error.stack
|
|
213
|
+
});
|
|
214
|
+
throw error;
|
|
215
|
+
}
|
|
216
|
+
}
|
|
217
|
+
|
|
218
|
+
/**
|
|
219
|
+
* Get project state
|
|
220
|
+
* @param {string} projectDir - Project directory path
|
|
221
|
+
* @returns {Promise<Object>} Project state object
|
|
222
|
+
*/
|
|
223
|
+
async getProjectState(projectDir) {
|
|
224
|
+
return await this.loadProjectState(projectDir);
|
|
225
|
+
}
|
|
226
|
+
|
|
227
|
+
/**
|
|
228
|
+
* Load project state from storage
|
|
229
|
+
* @param {string} projectDir - Project directory path
|
|
230
|
+
* @returns {Promise<Object>} Project state object
|
|
231
|
+
*/
|
|
232
|
+
async loadProjectState(projectDir) {
|
|
233
|
+
const stateFile = path.join(this.stateDirectory, this.stateFiles.projectState);
|
|
234
|
+
|
|
235
|
+
try {
|
|
236
|
+
const data = await this.loadJSON(stateFile);
|
|
237
|
+
return data;
|
|
238
|
+
} catch {
|
|
239
|
+
// Return default project state if file doesn't exist
|
|
240
|
+
const defaultState = {
|
|
241
|
+
version: this.stateVersion,
|
|
242
|
+
projectDir,
|
|
243
|
+
createdAt: new Date().toISOString(),
|
|
244
|
+
lastModified: new Date().toISOString(),
|
|
245
|
+
activeAgents: [],
|
|
246
|
+
lastActiveSession: null,
|
|
247
|
+
configuration: {
|
|
248
|
+
defaultModel: this.config.system?.defaultModel || 'anthropic-sonnet',
|
|
249
|
+
allowedTools: ['terminal', 'filesystem', 'browser'],
|
|
250
|
+
budgetLimit: 100.00
|
|
251
|
+
}
|
|
252
|
+
};
|
|
253
|
+
|
|
254
|
+
await this.saveProjectState(projectDir, defaultState);
|
|
255
|
+
return defaultState;
|
|
256
|
+
}
|
|
257
|
+
}
|
|
258
|
+
|
|
259
|
+
/**
|
|
260
|
+
* Save project state to storage
|
|
261
|
+
* @param {string} projectDir - Project directory path
|
|
262
|
+
* @param {Object} projectState - Project state object
|
|
263
|
+
* @returns {Promise<void>}
|
|
264
|
+
*/
|
|
265
|
+
async saveProjectState(projectDir, projectState) {
|
|
266
|
+
const stateFile = path.join(this.stateDirectory, this.stateFiles.projectState);
|
|
267
|
+
|
|
268
|
+
const stateData = {
|
|
269
|
+
...projectState,
|
|
270
|
+
lastModified: new Date().toISOString()
|
|
271
|
+
};
|
|
272
|
+
|
|
273
|
+
await this.saveJSON(stateFile, stateData);
|
|
274
|
+
}
|
|
275
|
+
|
|
276
|
+
/**
|
|
277
|
+
* Load agent index
|
|
278
|
+
* @param {string} projectDir - Project directory path
|
|
279
|
+
* @returns {Promise<Object>} Agent index object
|
|
280
|
+
*/
|
|
281
|
+
// eslint-disable-next-line no-unused-vars
|
|
282
|
+
async loadAgentIndex(projectDir) {
|
|
283
|
+
const indexFile = path.join(this.stateDirectory, this.stateFiles.agentIndex);
|
|
284
|
+
|
|
285
|
+
try {
|
|
286
|
+
return await this.loadJSON(indexFile);
|
|
287
|
+
} catch {
|
|
288
|
+
return {}; // Return empty index if file doesn't exist
|
|
289
|
+
}
|
|
290
|
+
}
|
|
291
|
+
|
|
292
|
+
/**
|
|
293
|
+
* Update agent index
|
|
294
|
+
* @param {Object} agent - Agent object
|
|
295
|
+
* @param {string} projectDir - Project directory path
|
|
296
|
+
* @returns {Promise<void>}
|
|
297
|
+
*/
|
|
298
|
+
// eslint-disable-next-line no-unused-vars
|
|
299
|
+
async updateAgentIndex(agent, projectDir) {
|
|
300
|
+
const indexFile = path.join(this.stateDirectory, this.stateFiles.agentIndex);
|
|
301
|
+
|
|
302
|
+
let agentIndex;
|
|
303
|
+
try {
|
|
304
|
+
agentIndex = await this.loadJSON(indexFile);
|
|
305
|
+
} catch {
|
|
306
|
+
agentIndex = {};
|
|
307
|
+
}
|
|
308
|
+
|
|
309
|
+
agentIndex[agent.id] = {
|
|
310
|
+
name: agent.name,
|
|
311
|
+
type: agent.type,
|
|
312
|
+
stateFile: `agents/agent-${agent.id}-state.json`,
|
|
313
|
+
conversationsFile: `agents/agent-${agent.id}-conversations.json`,
|
|
314
|
+
lastActivity: agent.lastActivity,
|
|
315
|
+
model: agent.currentModel,
|
|
316
|
+
status: agent.status,
|
|
317
|
+
capabilities: agent.capabilities || []
|
|
318
|
+
};
|
|
319
|
+
|
|
320
|
+
await this.saveJSON(indexFile, agentIndex);
|
|
321
|
+
}
|
|
322
|
+
|
|
323
|
+
// ==================== TEAM INDEX METHODS ====================
|
|
324
|
+
|
|
325
|
+
/**
|
|
326
|
+
* Load team index
|
|
327
|
+
* @param {string} projectDir - Project directory path (ignored, uses user data dir)
|
|
328
|
+
* @returns {Promise<Object>} Team index object
|
|
329
|
+
*/
|
|
330
|
+
// eslint-disable-next-line no-unused-vars
|
|
331
|
+
async loadTeamIndex(projectDir) {
|
|
332
|
+
const indexFile = path.join(this.stateDirectory, this.stateFiles.teamIndex);
|
|
333
|
+
|
|
334
|
+
try {
|
|
335
|
+
return await this.loadJSON(indexFile);
|
|
336
|
+
} catch {
|
|
337
|
+
return {}; // Return empty index if file doesn't exist
|
|
338
|
+
}
|
|
339
|
+
}
|
|
340
|
+
|
|
341
|
+
/**
|
|
342
|
+
* Save team index
|
|
343
|
+
* @param {Object} teamIndex - Team index object to save
|
|
344
|
+
* @returns {Promise<void>}
|
|
345
|
+
*/
|
|
346
|
+
async saveTeamIndex(teamIndex) {
|
|
347
|
+
const indexFile = path.join(this.stateDirectory, this.stateFiles.teamIndex);
|
|
348
|
+
await this.saveJSON(indexFile, teamIndex);
|
|
349
|
+
}
|
|
350
|
+
|
|
351
|
+
/**
|
|
352
|
+
* Get all teams
|
|
353
|
+
* @param {string} projectDir - Project directory path (ignored)
|
|
354
|
+
* @returns {Promise<Array>} Array of team objects
|
|
355
|
+
*/
|
|
356
|
+
async getAllTeams(projectDir) {
|
|
357
|
+
const teamIndex = await this.loadTeamIndex(projectDir);
|
|
358
|
+
return Object.entries(teamIndex).map(([id, team]) => ({
|
|
359
|
+
id,
|
|
360
|
+
...team
|
|
361
|
+
}));
|
|
362
|
+
}
|
|
363
|
+
|
|
364
|
+
/**
|
|
365
|
+
* Get a single team by ID
|
|
366
|
+
* @param {string} teamId - Team identifier
|
|
367
|
+
* @param {string} projectDir - Project directory path (ignored)
|
|
368
|
+
* @returns {Promise<Object|null>} Team object or null if not found
|
|
369
|
+
*/
|
|
370
|
+
async getTeam(teamId, projectDir) {
|
|
371
|
+
const teamIndex = await this.loadTeamIndex(projectDir);
|
|
372
|
+
if (teamIndex[teamId]) {
|
|
373
|
+
return { id: teamId, ...teamIndex[teamId] };
|
|
374
|
+
}
|
|
375
|
+
return null;
|
|
376
|
+
}
|
|
377
|
+
|
|
378
|
+
/**
|
|
379
|
+
* Create a new team
|
|
380
|
+
* @param {Object} teamData - Team data { name, description, color }
|
|
381
|
+
* @param {string} projectDir - Project directory path (ignored)
|
|
382
|
+
* @returns {Promise<Object>} Created team object
|
|
383
|
+
*/
|
|
384
|
+
async createTeam(teamData, projectDir) {
|
|
385
|
+
const teamIndex = await this.loadTeamIndex(projectDir);
|
|
386
|
+
|
|
387
|
+
// Generate team ID
|
|
388
|
+
const safeName = (teamData.name || 'team').toLowerCase().replace(/[^a-z0-9]/g, '-').slice(0, 20);
|
|
389
|
+
const teamId = `team-${safeName}-${Date.now()}`;
|
|
390
|
+
|
|
391
|
+
const team = {
|
|
392
|
+
name: teamData.name,
|
|
393
|
+
description: teamData.description || '',
|
|
394
|
+
memberAgentIds: [],
|
|
395
|
+
color: teamData.color || '#3B82F6', // Default blue
|
|
396
|
+
// Used by platformControlTool's `ownedByMe` team scope. null when
|
|
397
|
+
// the team was created via the UI / by no specific agent. When an
|
|
398
|
+
// agent creates a team via the platformcontrol tool, this carries
|
|
399
|
+
// the caller's id so ownership scope filtering works.
|
|
400
|
+
createdBy: typeof teamData.createdBy === 'string' ? teamData.createdBy : null,
|
|
401
|
+
createdAt: new Date().toISOString(),
|
|
402
|
+
updatedAt: new Date().toISOString()
|
|
403
|
+
};
|
|
404
|
+
|
|
405
|
+
teamIndex[teamId] = team;
|
|
406
|
+
await this.saveTeamIndex(teamIndex);
|
|
407
|
+
|
|
408
|
+
this.logger.info(`Team created: ${teamId}`, { name: team.name });
|
|
409
|
+
|
|
410
|
+
return { id: teamId, ...team };
|
|
411
|
+
}
|
|
412
|
+
|
|
413
|
+
/**
|
|
414
|
+
* Update an existing team
|
|
415
|
+
* @param {string} teamId - Team identifier
|
|
416
|
+
* @param {Object} updates - Fields to update
|
|
417
|
+
* @param {string} projectDir - Project directory path (ignored)
|
|
418
|
+
* @returns {Promise<Object>} Updated team object
|
|
419
|
+
*/
|
|
420
|
+
async updateTeam(teamId, updates, projectDir) {
|
|
421
|
+
const teamIndex = await this.loadTeamIndex(projectDir);
|
|
422
|
+
|
|
423
|
+
if (!teamIndex[teamId]) {
|
|
424
|
+
throw new Error(`Team ${teamId} not found`);
|
|
425
|
+
}
|
|
426
|
+
|
|
427
|
+
// Only allow updating specific fields
|
|
428
|
+
const allowedFields = ['name', 'description', 'color', 'memberAgentIds'];
|
|
429
|
+
for (const field of allowedFields) {
|
|
430
|
+
if (updates[field] !== undefined) {
|
|
431
|
+
teamIndex[teamId][field] = updates[field];
|
|
432
|
+
}
|
|
433
|
+
}
|
|
434
|
+
teamIndex[teamId].updatedAt = new Date().toISOString();
|
|
435
|
+
|
|
436
|
+
await this.saveTeamIndex(teamIndex);
|
|
437
|
+
|
|
438
|
+
this.logger.info(`Team updated: ${teamId}`, { updates: Object.keys(updates) });
|
|
439
|
+
|
|
440
|
+
return { id: teamId, ...teamIndex[teamId] };
|
|
441
|
+
}
|
|
442
|
+
|
|
443
|
+
/**
|
|
444
|
+
* Delete a team
|
|
445
|
+
* @param {string} teamId - Team identifier
|
|
446
|
+
* @param {string} projectDir - Project directory path (ignored)
|
|
447
|
+
* @returns {Promise<boolean>} True if deleted
|
|
448
|
+
*/
|
|
449
|
+
async deleteTeam(teamId, projectDir) {
|
|
450
|
+
const teamIndex = await this.loadTeamIndex(projectDir);
|
|
451
|
+
|
|
452
|
+
if (!teamIndex[teamId]) {
|
|
453
|
+
throw new Error(`Team ${teamId} not found`);
|
|
454
|
+
}
|
|
455
|
+
|
|
456
|
+
const teamName = teamIndex[teamId].name;
|
|
457
|
+
delete teamIndex[teamId];
|
|
458
|
+
await this.saveTeamIndex(teamIndex);
|
|
459
|
+
|
|
460
|
+
this.logger.info(`Team deleted: ${teamId}`, { name: teamName });
|
|
461
|
+
|
|
462
|
+
return true;
|
|
463
|
+
}
|
|
464
|
+
|
|
465
|
+
/**
|
|
466
|
+
* Add an agent to a team
|
|
467
|
+
* @param {string} teamId - Team identifier
|
|
468
|
+
* @param {string} agentId - Agent identifier to add
|
|
469
|
+
* @param {string} projectDir - Project directory path (ignored)
|
|
470
|
+
* @returns {Promise<Object>} Updated team object
|
|
471
|
+
*/
|
|
472
|
+
async addAgentToTeam(teamId, agentId, projectDir) {
|
|
473
|
+
const teamIndex = await this.loadTeamIndex(projectDir);
|
|
474
|
+
|
|
475
|
+
if (!teamIndex[teamId]) {
|
|
476
|
+
throw new Error(`Team ${teamId} not found`);
|
|
477
|
+
}
|
|
478
|
+
|
|
479
|
+
// Check if agent already in team
|
|
480
|
+
if (!teamIndex[teamId].memberAgentIds.includes(agentId)) {
|
|
481
|
+
teamIndex[teamId].memberAgentIds.push(agentId);
|
|
482
|
+
teamIndex[teamId].updatedAt = new Date().toISOString();
|
|
483
|
+
await this.saveTeamIndex(teamIndex);
|
|
484
|
+
|
|
485
|
+
this.logger.info(`Agent added to team`, { teamId, agentId });
|
|
486
|
+
}
|
|
487
|
+
|
|
488
|
+
return { id: teamId, ...teamIndex[teamId] };
|
|
489
|
+
}
|
|
490
|
+
|
|
491
|
+
/**
|
|
492
|
+
* Remove an agent from a team
|
|
493
|
+
* @param {string} teamId - Team identifier
|
|
494
|
+
* @param {string} agentId - Agent identifier to remove
|
|
495
|
+
* @param {string} projectDir - Project directory path (ignored)
|
|
496
|
+
* @returns {Promise<Object>} Updated team object
|
|
497
|
+
*/
|
|
498
|
+
async removeAgentFromTeam(teamId, agentId, projectDir) {
|
|
499
|
+
const teamIndex = await this.loadTeamIndex(projectDir);
|
|
500
|
+
|
|
501
|
+
if (!teamIndex[teamId]) {
|
|
502
|
+
throw new Error(`Team ${teamId} not found`);
|
|
503
|
+
}
|
|
504
|
+
|
|
505
|
+
const index = teamIndex[teamId].memberAgentIds.indexOf(agentId);
|
|
506
|
+
if (index > -1) {
|
|
507
|
+
teamIndex[teamId].memberAgentIds.splice(index, 1);
|
|
508
|
+
teamIndex[teamId].updatedAt = new Date().toISOString();
|
|
509
|
+
await this.saveTeamIndex(teamIndex);
|
|
510
|
+
|
|
511
|
+
this.logger.info(`Agent removed from team`, { teamId, agentId });
|
|
512
|
+
}
|
|
513
|
+
|
|
514
|
+
return { id: teamId, ...teamIndex[teamId] };
|
|
515
|
+
}
|
|
516
|
+
|
|
517
|
+
/**
|
|
518
|
+
* Get all teams that contain a specific agent
|
|
519
|
+
* @param {string} agentId - Agent identifier
|
|
520
|
+
* @param {string} projectDir - Project directory path (ignored)
|
|
521
|
+
* @returns {Promise<Array>} Array of team objects containing the agent
|
|
522
|
+
*/
|
|
523
|
+
async getAgentTeams(agentId, projectDir) {
|
|
524
|
+
const teams = await this.getAllTeams(projectDir);
|
|
525
|
+
return teams.filter(team => team.memberAgentIds.includes(agentId));
|
|
526
|
+
}
|
|
527
|
+
|
|
528
|
+
// ==================== END TEAM INDEX METHODS ====================
|
|
529
|
+
|
|
530
|
+
// ==================== FLOW INDEX METHODS ====================
|
|
531
|
+
|
|
532
|
+
/**
|
|
533
|
+
* Load flow index
|
|
534
|
+
* @param {string} projectDir - Project directory path (ignored, uses user data dir)
|
|
535
|
+
* @returns {Promise<Object>} Flow index object
|
|
536
|
+
*/
|
|
537
|
+
// eslint-disable-next-line no-unused-vars
|
|
538
|
+
async loadFlowIndex(projectDir) {
|
|
539
|
+
const indexFile = path.join(this.stateDirectory, this.stateFiles.flowIndex);
|
|
540
|
+
|
|
541
|
+
try {
|
|
542
|
+
return await this.loadJSON(indexFile);
|
|
543
|
+
} catch {
|
|
544
|
+
return {}; // Return empty index if file doesn't exist
|
|
545
|
+
}
|
|
546
|
+
}
|
|
547
|
+
|
|
548
|
+
/**
|
|
549
|
+
* Save flow index
|
|
550
|
+
* @param {Object} flowIndex - Flow index object to save
|
|
551
|
+
* @returns {Promise<void>}
|
|
552
|
+
*/
|
|
553
|
+
async saveFlowIndex(flowIndex) {
|
|
554
|
+
const indexFile = path.join(this.stateDirectory, this.stateFiles.flowIndex);
|
|
555
|
+
await this.saveJSON(indexFile, flowIndex);
|
|
556
|
+
}
|
|
557
|
+
|
|
558
|
+
/**
|
|
559
|
+
* Get all flows
|
|
560
|
+
* @param {string} projectDir - Project directory path (ignored)
|
|
561
|
+
* @returns {Promise<Array>} Array of flow objects
|
|
562
|
+
*/
|
|
563
|
+
async getAllFlows(projectDir) {
|
|
564
|
+
const flowIndex = await this.loadFlowIndex(projectDir);
|
|
565
|
+
return Object.entries(flowIndex).map(([id, flow]) => ({
|
|
566
|
+
id,
|
|
567
|
+
...flow
|
|
568
|
+
}));
|
|
569
|
+
}
|
|
570
|
+
|
|
571
|
+
/**
|
|
572
|
+
* Get a single flow by ID
|
|
573
|
+
* @param {string} flowId - Flow identifier
|
|
574
|
+
* @param {string} projectDir - Project directory path (ignored)
|
|
575
|
+
* @returns {Promise<Object|null>} Flow object or null if not found
|
|
576
|
+
*/
|
|
577
|
+
async getFlow(flowId, projectDir) {
|
|
578
|
+
const flowIndex = await this.loadFlowIndex(projectDir);
|
|
579
|
+
if (flowIndex[flowId]) {
|
|
580
|
+
return { id: flowId, ...flowIndex[flowId] };
|
|
581
|
+
}
|
|
582
|
+
return null;
|
|
583
|
+
}
|
|
584
|
+
|
|
585
|
+
/**
|
|
586
|
+
* Create a new flow
|
|
587
|
+
* @param {Object} flowData - Flow data { name, description, nodes, edges, variables }
|
|
588
|
+
* @param {string} projectDir - Project directory path (ignored)
|
|
589
|
+
* @returns {Promise<Object>} Created flow object
|
|
590
|
+
*/
|
|
591
|
+
async createFlow(flowData, projectDir) {
|
|
592
|
+
const flowIndex = await this.loadFlowIndex(projectDir);
|
|
593
|
+
|
|
594
|
+
// Generate flow ID
|
|
595
|
+
const safeName = (flowData.name || 'flow').toLowerCase().replace(/[^a-z0-9]/g, '-').slice(0, 20);
|
|
596
|
+
const flowId = `flow-${safeName}-${Date.now()}`;
|
|
597
|
+
|
|
598
|
+
// Stamp edge ids on save. Templates and marketplace-installed flows
|
|
599
|
+
// ship without edge ids; ReactFlow refuses to render edges that
|
|
600
|
+
// don't have a unique `id`, so unstamped edges show up as
|
|
601
|
+
// disconnected dots on the canvas. Doing this here rather than in
|
|
602
|
+
// the editor means the durable shape on disk is always renderable.
|
|
603
|
+
// Helper is shared with FlowEditor's defense-in-depth layer so the
|
|
604
|
+
// id format stays consistent across save + reload.
|
|
605
|
+
const { ensureEdgeIds } = await import('../utilities/flowEdgeIds.js');
|
|
606
|
+
const stampedEdges = ensureEdgeIds(flowData.edges);
|
|
607
|
+
|
|
608
|
+
const flow = {
|
|
609
|
+
name: flowData.name,
|
|
610
|
+
description: flowData.description || '',
|
|
611
|
+
nodes: flowData.nodes || [],
|
|
612
|
+
edges: stampedEdges,
|
|
613
|
+
variables: flowData.variables || {},
|
|
614
|
+
createdAt: new Date().toISOString(),
|
|
615
|
+
updatedAt: new Date().toISOString(),
|
|
616
|
+
// createdBy: only present when the caller stamps it (e.g.
|
|
617
|
+
// platformControlTool's create-flow path). Not required for the
|
|
618
|
+
// happy-path UI flow but durable when set.
|
|
619
|
+
...(flowData.createdBy ? { createdBy: flowData.createdBy } : {})
|
|
620
|
+
};
|
|
621
|
+
|
|
622
|
+
flowIndex[flowId] = flow;
|
|
623
|
+
await this.saveFlowIndex(flowIndex);
|
|
624
|
+
|
|
625
|
+
this.logger.info(`Flow created: ${flowId}`, { name: flow.name });
|
|
626
|
+
|
|
627
|
+
return { id: flowId, ...flow };
|
|
628
|
+
}
|
|
629
|
+
|
|
630
|
+
/**
|
|
631
|
+
* Update an existing flow
|
|
632
|
+
* @param {string} flowId - Flow identifier
|
|
633
|
+
* @param {Object} updates - Fields to update
|
|
634
|
+
* @param {string} projectDir - Project directory path (ignored)
|
|
635
|
+
* @returns {Promise<Object>} Updated flow object
|
|
636
|
+
*/
|
|
637
|
+
async updateFlow(flowId, updates, projectDir) {
|
|
638
|
+
const flowIndex = await this.loadFlowIndex(projectDir);
|
|
639
|
+
|
|
640
|
+
if (!flowIndex[flowId]) {
|
|
641
|
+
throw new Error(`Flow ${flowId} not found`);
|
|
642
|
+
}
|
|
643
|
+
|
|
644
|
+
// Only allow updating specific fields. `version` and `schemaVersion`
|
|
645
|
+
// are added so the Phase 6 version-stamp write-back persists, and
|
|
646
|
+
// so v2 flows can be marked when typed I/O is added.
|
|
647
|
+
const allowedFields = ['name', 'description', 'nodes', 'edges', 'variables', 'version', 'schemaVersion'];
|
|
648
|
+
for (const field of allowedFields) {
|
|
649
|
+
if (updates[field] !== undefined) {
|
|
650
|
+
flowIndex[flowId][field] = updates[field];
|
|
651
|
+
}
|
|
652
|
+
}
|
|
653
|
+
flowIndex[flowId].updatedAt = new Date().toISOString();
|
|
654
|
+
|
|
655
|
+
await this.saveFlowIndex(flowIndex);
|
|
656
|
+
|
|
657
|
+
this.logger.info(`Flow updated: ${flowId}`, { updates: Object.keys(updates) });
|
|
658
|
+
|
|
659
|
+
return { id: flowId, ...flowIndex[flowId] };
|
|
660
|
+
}
|
|
661
|
+
|
|
662
|
+
/**
|
|
663
|
+
* Delete a flow
|
|
664
|
+
* @param {string} flowId - Flow identifier
|
|
665
|
+
* @param {string} projectDir - Project directory path (ignored)
|
|
666
|
+
* @returns {Promise<boolean>} True if deleted
|
|
667
|
+
*/
|
|
668
|
+
async deleteFlow(flowId, projectDir) {
|
|
669
|
+
const flowIndex = await this.loadFlowIndex(projectDir);
|
|
670
|
+
|
|
671
|
+
if (!flowIndex[flowId]) {
|
|
672
|
+
throw new Error(`Flow ${flowId} not found`);
|
|
673
|
+
}
|
|
674
|
+
|
|
675
|
+
const flowName = flowIndex[flowId].name;
|
|
676
|
+
delete flowIndex[flowId];
|
|
677
|
+
await this.saveFlowIndex(flowIndex);
|
|
678
|
+
|
|
679
|
+
this.logger.info(`Flow deleted: ${flowId}`, { name: flowName });
|
|
680
|
+
|
|
681
|
+
return true;
|
|
682
|
+
}
|
|
683
|
+
|
|
684
|
+
// ==================== FLOW RUN METHODS ====================
|
|
685
|
+
|
|
686
|
+
/**
|
|
687
|
+
* Load flow run index
|
|
688
|
+
* @param {string} projectDir - Project directory path (ignored)
|
|
689
|
+
* @returns {Promise<Object>} Flow run index object
|
|
690
|
+
*/
|
|
691
|
+
// eslint-disable-next-line no-unused-vars
|
|
692
|
+
async loadFlowRunIndex(projectDir) {
|
|
693
|
+
const indexFile = path.join(this.stateDirectory, this.stateFiles.flowRunIndex);
|
|
694
|
+
|
|
695
|
+
try {
|
|
696
|
+
return await this.loadJSON(indexFile);
|
|
697
|
+
} catch {
|
|
698
|
+
return {}; // Return empty index if file doesn't exist
|
|
699
|
+
}
|
|
700
|
+
}
|
|
701
|
+
|
|
702
|
+
/**
|
|
703
|
+
* Save flow run index
|
|
704
|
+
* @param {Object} runIndex - Flow run index object to save
|
|
705
|
+
* @returns {Promise<void>}
|
|
706
|
+
*/
|
|
707
|
+
async saveFlowRunIndex(runIndex) {
|
|
708
|
+
const indexFile = path.join(this.stateDirectory, this.stateFiles.flowRunIndex);
|
|
709
|
+
await this.saveJSON(indexFile, runIndex);
|
|
710
|
+
}
|
|
711
|
+
|
|
712
|
+
/**
|
|
713
|
+
* Create a new flow run
|
|
714
|
+
* @param {string} flowId - Flow identifier
|
|
715
|
+
* @param {Object} initialInput - Initial input for the flow
|
|
716
|
+
* @param {string} projectDir - Project directory path (ignored)
|
|
717
|
+
* @returns {Promise<Object>} Created flow run object
|
|
718
|
+
*/
|
|
719
|
+
async createFlowRun(flowId, initialInput, projectDir) {
|
|
720
|
+
const runIndex = await this.loadFlowRunIndex(projectDir);
|
|
721
|
+
|
|
722
|
+
const runId = `run-${Date.now()}-${Math.random().toString(36).slice(2, 8)}`;
|
|
723
|
+
|
|
724
|
+
const run = {
|
|
725
|
+
flowId,
|
|
726
|
+
status: 'pending', // pending, running, completed, failed, stopped
|
|
727
|
+
initialInput,
|
|
728
|
+
nodeStates: {},
|
|
729
|
+
startedAt: new Date().toISOString(),
|
|
730
|
+
completedAt: null,
|
|
731
|
+
error: null
|
|
732
|
+
};
|
|
733
|
+
|
|
734
|
+
runIndex[runId] = run;
|
|
735
|
+
await this.saveFlowRunIndex(runIndex);
|
|
736
|
+
|
|
737
|
+
this.logger.info(`Flow run created: ${runId}`, { flowId });
|
|
738
|
+
|
|
739
|
+
return { id: runId, ...run };
|
|
740
|
+
}
|
|
741
|
+
|
|
742
|
+
/**
|
|
743
|
+
* Update a flow run
|
|
744
|
+
* @param {string} runId - Run identifier
|
|
745
|
+
* @param {Object} updates - Fields to update
|
|
746
|
+
* @param {string} projectDir - Project directory path (ignored)
|
|
747
|
+
* @returns {Promise<Object>} Updated flow run object
|
|
748
|
+
*/
|
|
749
|
+
async updateFlowRun(runId, updates, projectDir) {
|
|
750
|
+
const runIndex = await this.loadFlowRunIndex(projectDir);
|
|
751
|
+
|
|
752
|
+
if (!runIndex[runId]) {
|
|
753
|
+
throw new Error(`Flow run ${runId} not found`);
|
|
754
|
+
}
|
|
755
|
+
|
|
756
|
+
// Only allow updating specific fields
|
|
757
|
+
const allowedFields = ['status', 'nodeStates', 'completedAt', 'error', 'output'];
|
|
758
|
+
for (const field of allowedFields) {
|
|
759
|
+
if (updates[field] !== undefined) {
|
|
760
|
+
runIndex[runId][field] = updates[field];
|
|
761
|
+
}
|
|
762
|
+
}
|
|
763
|
+
|
|
764
|
+
await this.saveFlowRunIndex(runIndex);
|
|
765
|
+
|
|
766
|
+
this.logger.info(`Flow run updated: ${runId}`, { status: updates.status });
|
|
767
|
+
|
|
768
|
+
return { id: runId, ...runIndex[runId] };
|
|
769
|
+
}
|
|
770
|
+
|
|
771
|
+
/**
|
|
772
|
+
* Get a flow run by ID
|
|
773
|
+
* @param {string} runId - Run identifier
|
|
774
|
+
* @param {string} projectDir - Project directory path (ignored)
|
|
775
|
+
* @returns {Promise<Object|null>} Flow run object or null if not found
|
|
776
|
+
*/
|
|
777
|
+
async getFlowRun(runId, projectDir) {
|
|
778
|
+
const runIndex = await this.loadFlowRunIndex(projectDir);
|
|
779
|
+
if (runIndex[runId]) {
|
|
780
|
+
return { id: runId, ...runIndex[runId] };
|
|
781
|
+
}
|
|
782
|
+
return null;
|
|
783
|
+
}
|
|
784
|
+
|
|
785
|
+
/**
|
|
786
|
+
* Get all runs for a specific flow
|
|
787
|
+
* @param {string} flowId - Flow identifier
|
|
788
|
+
* @param {string} projectDir - Project directory path (ignored)
|
|
789
|
+
* @returns {Promise<Array>} Array of flow run objects
|
|
790
|
+
*/
|
|
791
|
+
async getFlowRuns(flowId, projectDir) {
|
|
792
|
+
const runIndex = await this.loadFlowRunIndex(projectDir);
|
|
793
|
+
return Object.entries(runIndex)
|
|
794
|
+
.filter(([, run]) => run.flowId === flowId)
|
|
795
|
+
.map(([id, run]) => ({ id, ...run }))
|
|
796
|
+
.sort((a, b) => new Date(b.startedAt) - new Date(a.startedAt));
|
|
797
|
+
}
|
|
798
|
+
|
|
799
|
+
// ==================== END FLOW INDEX METHODS ====================
|
|
800
|
+
|
|
801
|
+
/**
|
|
802
|
+
* Restore agent from saved state
|
|
803
|
+
* @param {string} agentId - Agent identifier
|
|
804
|
+
* @param {Object} agentInfo - Agent info from index
|
|
805
|
+
* @param {string} projectDir - Project directory path
|
|
806
|
+
* @returns {Promise<Object>} Restored agent object
|
|
807
|
+
*/
|
|
808
|
+
async restoreAgent(agentId, agentInfo, projectDir) {
|
|
809
|
+
const stateDir = this.getStateDir(projectDir);
|
|
810
|
+
const stateFile = path.join(stateDir, agentInfo.stateFile);
|
|
811
|
+
const conversationsFile = path.join(stateDir, agentInfo.conversationsFile);
|
|
812
|
+
|
|
813
|
+
// Skeleton defaults the resilient loader falls back to when a state
|
|
814
|
+
// file is missing, empty, or unrepairable. The state skeleton derives
|
|
815
|
+
// the agent name from the index entry so even a total wipe leaves
|
|
816
|
+
// the agent identifiable in the UI rather than ending up nameless.
|
|
817
|
+
const stateDefault = () => ({
|
|
818
|
+
version: 2,
|
|
819
|
+
agentId,
|
|
820
|
+
state: {
|
|
821
|
+
id: agentId,
|
|
822
|
+
name: agentInfo.name || agentId,
|
|
823
|
+
type: agentInfo.type || 'user-created',
|
|
824
|
+
status: 'active',
|
|
825
|
+
mode: 'chat',
|
|
826
|
+
currentModel: agentInfo.preferredModel || agentInfo.model || null,
|
|
827
|
+
preferredModel: agentInfo.preferredModel || agentInfo.model || null,
|
|
828
|
+
systemPrompt: '',
|
|
829
|
+
capabilities: agentInfo.capabilities || [],
|
|
830
|
+
taskList: { tasks: [], lastUpdated: new Date().toISOString() },
|
|
831
|
+
messageQueues: { userMessages: [], interAgentMessages: [], toolResults: [] },
|
|
832
|
+
interAgentTracking: {},
|
|
833
|
+
},
|
|
834
|
+
lastPersisted: new Date().toISOString(),
|
|
835
|
+
});
|
|
836
|
+
const conversationsDefault = () => ({
|
|
837
|
+
version: 2,
|
|
838
|
+
agentId,
|
|
839
|
+
conversations: {
|
|
840
|
+
// Empty `full` conversation — required by validateModelConversations
|
|
841
|
+
// and the agent loop's history reader. New per-model entries are
|
|
842
|
+
// added on demand at first use.
|
|
843
|
+
full: { messages: [], lastUpdated: new Date().toISOString() },
|
|
844
|
+
},
|
|
845
|
+
lastPersisted: new Date().toISOString(),
|
|
846
|
+
});
|
|
847
|
+
|
|
848
|
+
// Recovery reports for any auto-fixed files. Attached to the
|
|
849
|
+
// returned agent so the caller (orchestrator → webServer) can
|
|
850
|
+
// broadcast each one as a toast in the UI. Empty array on the
|
|
851
|
+
// happy path; harmless presence otherwise.
|
|
852
|
+
const recoveries = [];
|
|
853
|
+
|
|
854
|
+
try {
|
|
855
|
+
const stateRes = await this.loadJSONResilient(stateFile, stateDefault, {
|
|
856
|
+
label: `agent state (${agentInfo.name || agentId})`,
|
|
857
|
+
});
|
|
858
|
+
const conversationsRes = await this.loadJSONResilient(conversationsFile, conversationsDefault, {
|
|
859
|
+
label: `agent conversations (${agentInfo.name || agentId})`,
|
|
860
|
+
});
|
|
861
|
+
const stateData = stateRes.data;
|
|
862
|
+
const conversationsData = conversationsRes.data;
|
|
863
|
+
if (stateRes.recovery) recoveries.push(stateRes.recovery);
|
|
864
|
+
if (conversationsRes.recovery) recoveries.push(conversationsRes.recovery);
|
|
865
|
+
|
|
866
|
+
// Validate model conversations integrity
|
|
867
|
+
await this.validateModelConversations(conversationsData.conversations);
|
|
868
|
+
|
|
869
|
+
// Check if agent is paused
|
|
870
|
+
const pauseStatus = await this.checkAgentPauseStatus(agentId, projectDir);
|
|
871
|
+
|
|
872
|
+
const restoredAgent = {
|
|
873
|
+
...stateData.state,
|
|
874
|
+
conversations: conversationsData.conversations,
|
|
875
|
+
isPaused: pauseStatus.isPaused,
|
|
876
|
+
pausedUntil: pauseStatus.pausedUntil,
|
|
877
|
+
isRestored: true,
|
|
878
|
+
restoredAt: new Date().toISOString(),
|
|
879
|
+
// Non-enumerable would be cleaner, but agentPool serializes the
|
|
880
|
+
// whole object — keeping it as a plain field. The orchestrator
|
|
881
|
+
// strips it before persisting (see import handler).
|
|
882
|
+
_restoreRecoveries: recoveries,
|
|
883
|
+
};
|
|
884
|
+
|
|
885
|
+
// CRITICAL: Restore interAgentTracking as a Map (it comes as plain object from JSON)
|
|
886
|
+
if (!restoredAgent.interAgentTracking || typeof restoredAgent.interAgentTracking !== 'object') {
|
|
887
|
+
restoredAgent.interAgentTracking = new Map();
|
|
888
|
+
} else if (!(restoredAgent.interAgentTracking instanceof Map)) {
|
|
889
|
+
restoredAgent.interAgentTracking = new Map(Object.entries(restoredAgent.interAgentTracking));
|
|
890
|
+
}
|
|
891
|
+
|
|
892
|
+
this.logger.info(`Agent restored: ${agentId}`, {
|
|
893
|
+
name: restoredAgent.name,
|
|
894
|
+
status: restoredAgent.status,
|
|
895
|
+
messageCount: restoredAgent.conversations?.full?.messages?.length || 0,
|
|
896
|
+
recoveryCount: recoveries.length,
|
|
897
|
+
});
|
|
898
|
+
|
|
899
|
+
return restoredAgent;
|
|
900
|
+
|
|
901
|
+
} catch (error) {
|
|
902
|
+
this.logger.error(`Agent restoration failed: ${agentId}`, error.message);
|
|
903
|
+
throw error;
|
|
904
|
+
}
|
|
905
|
+
}
|
|
906
|
+
|
|
907
|
+
/**
|
|
908
|
+
* Restore async operations
|
|
909
|
+
* @param {string} projectDir - Project directory path
|
|
910
|
+
* @returns {Promise<Array>} Array of active async operations
|
|
911
|
+
*/
|
|
912
|
+
// eslint-disable-next-line no-unused-vars
|
|
913
|
+
async restoreAsyncOperations(projectDir) {
|
|
914
|
+
const operationsFile = path.join(this.stateDirectory, this.stateFiles.asyncOperations);
|
|
915
|
+
|
|
916
|
+
try {
|
|
917
|
+
const data = await this.loadJSON(operationsFile);
|
|
918
|
+
return data.operations || [];
|
|
919
|
+
} catch {
|
|
920
|
+
return [];
|
|
921
|
+
}
|
|
922
|
+
}
|
|
923
|
+
|
|
924
|
+
/**
|
|
925
|
+
* Restore paused agents
|
|
926
|
+
* @param {string} projectDir - Project directory path
|
|
927
|
+
* @returns {Promise<Object>} Paused agents data
|
|
928
|
+
*/
|
|
929
|
+
// eslint-disable-next-line no-unused-vars
|
|
930
|
+
async restorePausedAgents(projectDir) {
|
|
931
|
+
const pausedFile = path.join(this.stateDirectory, this.stateFiles.pausedAgents);
|
|
932
|
+
|
|
933
|
+
try {
|
|
934
|
+
const data = await this.loadJSON(pausedFile);
|
|
935
|
+
const now = Date.now();
|
|
936
|
+
|
|
937
|
+
// Check which agents should be resumed
|
|
938
|
+
const toResume = [];
|
|
939
|
+
for (const [agentId, pauseInfo] of Object.entries(data.pausedAgents || {})) {
|
|
940
|
+
const pausedUntil = new Date(pauseInfo.pausedUntil).getTime();
|
|
941
|
+
|
|
942
|
+
if (now >= pausedUntil) {
|
|
943
|
+
toResume.push(agentId);
|
|
944
|
+
}
|
|
945
|
+
}
|
|
946
|
+
|
|
947
|
+
// Move expired pauses to history
|
|
948
|
+
for (const agentId of toResume) {
|
|
949
|
+
const pauseInfo = data.pausedAgents[agentId];
|
|
950
|
+
delete data.pausedAgents[agentId];
|
|
951
|
+
|
|
952
|
+
data.pauseHistory = data.pauseHistory || [];
|
|
953
|
+
data.pauseHistory.push({
|
|
954
|
+
agentId,
|
|
955
|
+
pausedAt: pauseInfo.pausedAt,
|
|
956
|
+
resumedAt: new Date().toISOString(),
|
|
957
|
+
reason: pauseInfo.reason,
|
|
958
|
+
actualDuration: Math.round((now - new Date(pauseInfo.pausedAt).getTime()) / 1000)
|
|
959
|
+
});
|
|
960
|
+
}
|
|
961
|
+
|
|
962
|
+
// Save updated data
|
|
963
|
+
await this.saveJSON(pausedFile, data);
|
|
964
|
+
|
|
965
|
+
return data;
|
|
966
|
+
|
|
967
|
+
} catch {
|
|
968
|
+
return {
|
|
969
|
+
pausedAgents: {},
|
|
970
|
+
pauseHistory: []
|
|
971
|
+
};
|
|
972
|
+
}
|
|
973
|
+
}
|
|
974
|
+
|
|
975
|
+
/**
|
|
976
|
+
* Restore context references
|
|
977
|
+
* @param {string} projectDir - Project directory path
|
|
978
|
+
* @returns {Promise<Object>} Context references data
|
|
979
|
+
*/
|
|
980
|
+
// eslint-disable-next-line no-unused-vars
|
|
981
|
+
async restoreContextReferences(projectDir) {
|
|
982
|
+
const contextFile = path.join(this.stateDirectory, this.stateFiles.contextReferences);
|
|
983
|
+
|
|
984
|
+
try {
|
|
985
|
+
const data = await this.loadJSON(contextFile);
|
|
986
|
+
|
|
987
|
+
// Validate context references (implementation would validate file existence, etc.)
|
|
988
|
+
const validatedReferences = [];
|
|
989
|
+
for (const reference of data.references || []) {
|
|
990
|
+
// Add validation logic here
|
|
991
|
+
reference.isValid = true; // Placeholder
|
|
992
|
+
reference.lastValidated = new Date().toISOString();
|
|
993
|
+
validatedReferences.push(reference);
|
|
994
|
+
}
|
|
995
|
+
|
|
996
|
+
data.references = validatedReferences;
|
|
997
|
+
await this.saveJSON(contextFile, data);
|
|
998
|
+
|
|
999
|
+
return data;
|
|
1000
|
+
|
|
1001
|
+
} catch {
|
|
1002
|
+
return {
|
|
1003
|
+
references: [],
|
|
1004
|
+
lastCleanup: new Date().toISOString()
|
|
1005
|
+
};
|
|
1006
|
+
}
|
|
1007
|
+
}
|
|
1008
|
+
|
|
1009
|
+
/**
|
|
1010
|
+
* Save last session data
|
|
1011
|
+
* @param {string} projectDir - Project directory path
|
|
1012
|
+
* @param {Object} sessionData - Session data to save
|
|
1013
|
+
* @returns {Promise<void>}
|
|
1014
|
+
*/
|
|
1015
|
+
async saveLastSession(projectDir, sessionData) {
|
|
1016
|
+
const sessionFile = path.join(this.stateDirectory, this.stateFiles.lastSession);
|
|
1017
|
+
|
|
1018
|
+
const data = {
|
|
1019
|
+
...sessionData,
|
|
1020
|
+
savedAt: new Date().toISOString(),
|
|
1021
|
+
projectDir
|
|
1022
|
+
};
|
|
1023
|
+
|
|
1024
|
+
await this.saveJSON(sessionFile, data);
|
|
1025
|
+
}
|
|
1026
|
+
|
|
1027
|
+
/**
|
|
1028
|
+
* Load last session data
|
|
1029
|
+
* @param {string} projectDir - Project directory path
|
|
1030
|
+
* @returns {Promise<Object>} Last session data
|
|
1031
|
+
*/
|
|
1032
|
+
// eslint-disable-next-line no-unused-vars
|
|
1033
|
+
async loadLastSession(projectDir) {
|
|
1034
|
+
const sessionFile = path.join(this.stateDirectory, this.stateFiles.lastSession);
|
|
1035
|
+
|
|
1036
|
+
try {
|
|
1037
|
+
return await this.loadJSON(sessionFile);
|
|
1038
|
+
} catch {
|
|
1039
|
+
return null;
|
|
1040
|
+
}
|
|
1041
|
+
}
|
|
1042
|
+
|
|
1043
|
+
/**
|
|
1044
|
+
* Save paused agent data
|
|
1045
|
+
* @param {string} projectDir - Project directory path
|
|
1046
|
+
* @param {string} agentId - Agent identifier
|
|
1047
|
+
* @param {Object} pauseData - Pause information
|
|
1048
|
+
* @returns {Promise<void>}
|
|
1049
|
+
*/
|
|
1050
|
+
async savePausedAgent(projectDir, agentId, pauseData) {
|
|
1051
|
+
const pausedFile = path.join(this.stateDirectory, this.stateFiles.pausedAgents);
|
|
1052
|
+
|
|
1053
|
+
let data;
|
|
1054
|
+
try {
|
|
1055
|
+
data = await this.loadJSON(pausedFile);
|
|
1056
|
+
} catch {
|
|
1057
|
+
data = { pausedAgents: {}, pauseHistory: [] };
|
|
1058
|
+
}
|
|
1059
|
+
|
|
1060
|
+
data.pausedAgents[agentId] = pauseData;
|
|
1061
|
+
await this.saveJSON(pausedFile, data);
|
|
1062
|
+
}
|
|
1063
|
+
|
|
1064
|
+
/**
|
|
1065
|
+
* Remove paused agent data
|
|
1066
|
+
* @param {string} projectDir - Project directory path
|
|
1067
|
+
* @param {string} agentId - Agent identifier
|
|
1068
|
+
* @returns {Promise<void>}
|
|
1069
|
+
*/
|
|
1070
|
+
async removePausedAgent(projectDir, agentId) {
|
|
1071
|
+
const pausedFile = path.join(this.stateDirectory, this.stateFiles.pausedAgents);
|
|
1072
|
+
|
|
1073
|
+
try {
|
|
1074
|
+
const data = await this.loadJSON(pausedFile);
|
|
1075
|
+
delete data.pausedAgents[agentId];
|
|
1076
|
+
await this.saveJSON(pausedFile, data);
|
|
1077
|
+
} catch {
|
|
1078
|
+
// File doesn't exist, nothing to remove
|
|
1079
|
+
}
|
|
1080
|
+
}
|
|
1081
|
+
|
|
1082
|
+
/**
|
|
1083
|
+
* Check agent pause status
|
|
1084
|
+
* @param {string} agentId - Agent identifier
|
|
1085
|
+
* @param {string} projectDir - Project directory path
|
|
1086
|
+
* @returns {Promise<Object>} Pause status
|
|
1087
|
+
*/
|
|
1088
|
+
// eslint-disable-next-line no-unused-vars
|
|
1089
|
+
async checkAgentPauseStatus(agentId, projectDir) {
|
|
1090
|
+
const pausedFile = path.join(this.stateDirectory, this.stateFiles.pausedAgents);
|
|
1091
|
+
|
|
1092
|
+
try {
|
|
1093
|
+
const data = await this.loadJSON(pausedFile);
|
|
1094
|
+
const pauseInfo = data.pausedAgents[agentId];
|
|
1095
|
+
|
|
1096
|
+
if (!pauseInfo) {
|
|
1097
|
+
return { isPaused: false, pausedUntil: null };
|
|
1098
|
+
}
|
|
1099
|
+
|
|
1100
|
+
const now = Date.now();
|
|
1101
|
+
const pausedUntil = new Date(pauseInfo.pausedUntil).getTime();
|
|
1102
|
+
|
|
1103
|
+
return {
|
|
1104
|
+
isPaused: now < pausedUntil,
|
|
1105
|
+
pausedUntil: pauseInfo.pausedUntil,
|
|
1106
|
+
reason: pauseInfo.reason
|
|
1107
|
+
};
|
|
1108
|
+
|
|
1109
|
+
} catch {
|
|
1110
|
+
return { isPaused: false, pausedUntil: null };
|
|
1111
|
+
}
|
|
1112
|
+
}
|
|
1113
|
+
|
|
1114
|
+
/**
|
|
1115
|
+
* Validate model conversations integrity
|
|
1116
|
+
* @param {Object} conversations - Conversations object
|
|
1117
|
+
* @returns {Promise<void>}
|
|
1118
|
+
*/
|
|
1119
|
+
async validateModelConversations(conversations) {
|
|
1120
|
+
if (!conversations || !conversations.full) {
|
|
1121
|
+
throw new Error('Invalid conversations structure - missing full conversation');
|
|
1122
|
+
}
|
|
1123
|
+
|
|
1124
|
+
const fullLastUpdated = new Date(conversations.full.lastUpdated);
|
|
1125
|
+
|
|
1126
|
+
// Validate each model conversation against full conversation
|
|
1127
|
+
for (const [modelName, modelConv] of Object.entries(conversations)) {
|
|
1128
|
+
if (modelName === 'full') continue;
|
|
1129
|
+
|
|
1130
|
+
if (!modelConv.messages) {
|
|
1131
|
+
this.logger.warn(`Model conversation ${modelName} missing messages array`);
|
|
1132
|
+
continue;
|
|
1133
|
+
}
|
|
1134
|
+
|
|
1135
|
+
const modelLastUpdated = new Date(modelConv.lastUpdated);
|
|
1136
|
+
|
|
1137
|
+
if (fullLastUpdated > modelLastUpdated) {
|
|
1138
|
+
this.logger.warn(`Model conversation ${modelName} is outdated, will sync on next use`);
|
|
1139
|
+
modelConv.needsSync = true;
|
|
1140
|
+
}
|
|
1141
|
+
}
|
|
1142
|
+
}
|
|
1143
|
+
|
|
1144
|
+
/**
|
|
1145
|
+
* Save JSON data to file
|
|
1146
|
+
* @private
|
|
1147
|
+
*/
|
|
1148
|
+
async saveJSON(filePath, data) {
|
|
1149
|
+
const dir = path.dirname(filePath);
|
|
1150
|
+
await fs.mkdir(dir, { recursive: true });
|
|
1151
|
+
|
|
1152
|
+
const jsonData = JSON.stringify(data, null, 2);
|
|
1153
|
+
await fs.writeFile(filePath, jsonData, 'utf8');
|
|
1154
|
+
}
|
|
1155
|
+
|
|
1156
|
+
/**
|
|
1157
|
+
* Load JSON data from file (strict — throws on missing or corrupt).
|
|
1158
|
+
* @private
|
|
1159
|
+
*/
|
|
1160
|
+
async loadJSON(filePath) {
|
|
1161
|
+
const data = await fs.readFile(filePath, 'utf8');
|
|
1162
|
+
return JSON.parse(data);
|
|
1163
|
+
}
|
|
1164
|
+
|
|
1165
|
+
/**
|
|
1166
|
+
* Load JSON with best-effort recovery from common breakage modes.
|
|
1167
|
+
* Designed so a single bad state file doesn't take down the entire
|
|
1168
|
+
* agent-load flow — instead the caller gets back a usable object plus
|
|
1169
|
+
* a `recovery` report that callers can surface as a toast.
|
|
1170
|
+
*
|
|
1171
|
+
* Recovery ladder (first match wins):
|
|
1172
|
+
* 1. Missing file (ENOENT) → recreate with `defaultValue`,
|
|
1173
|
+
* report `kind='not-found'`
|
|
1174
|
+
* 2. Empty / whitespace-only file → overwrite with `defaultValue`,
|
|
1175
|
+
* report `kind='empty-recreated'`
|
|
1176
|
+
* 3. Common syntax issues → repaired in memory and saved
|
|
1177
|
+
* (trailing comma, BOM, unclosed back to disk; report
|
|
1178
|
+
* braces/brackets, stray garbage `kind='repaired'`
|
|
1179
|
+
* after the JSON object)
|
|
1180
|
+
* 4. Salvageable first JSON block → use the salvaged object,
|
|
1181
|
+
* (anywhere in the buffer) archive the original to
|
|
1182
|
+
* `<file>.corrupt-<ts>.json`,
|
|
1183
|
+
* report `kind='partial'`
|
|
1184
|
+
* 5. Nothing usable → use `defaultValue`, archive
|
|
1185
|
+
* original (if any), report
|
|
1186
|
+
* `kind='unrepairable'`
|
|
1187
|
+
*
|
|
1188
|
+
* @param {string} filePath - Absolute path to the JSON file
|
|
1189
|
+
* @param {Object|Array|null|Function} defaultValue - Value to use when
|
|
1190
|
+
* the file is missing/empty/unrepairable. If a function is provided
|
|
1191
|
+
* it's called with the file path and its return value is used.
|
|
1192
|
+
* @param {Object} [options]
|
|
1193
|
+
* @param {boolean} [options.persistRecreated=true] - Whether to write
|
|
1194
|
+
* the default back to disk on missing/empty/unrepairable cases.
|
|
1195
|
+
* @param {string} [options.label] - Human-readable label used in toast
|
|
1196
|
+
* messages (e.g. 'agent state', 'agent conversations'). Falls back
|
|
1197
|
+
* to the file's basename.
|
|
1198
|
+
* @returns {Promise<{data: any, recovery: ?Object}>}
|
|
1199
|
+
*/
|
|
1200
|
+
async loadJSONResilient(filePath, defaultValue, options = {}) {
|
|
1201
|
+
const { persistRecreated = true } = options;
|
|
1202
|
+
const label = options.label || path.basename(filePath);
|
|
1203
|
+
const defaultOf = () =>
|
|
1204
|
+
typeof defaultValue === 'function' ? defaultValue(filePath) : defaultValue;
|
|
1205
|
+
|
|
1206
|
+
// 1. Missing file → recreate with default
|
|
1207
|
+
let raw;
|
|
1208
|
+
try {
|
|
1209
|
+
raw = await fs.readFile(filePath, 'utf8');
|
|
1210
|
+
} catch (err) {
|
|
1211
|
+
if (err.code === 'ENOENT') {
|
|
1212
|
+
const data = defaultOf();
|
|
1213
|
+
if (persistRecreated && data !== undefined) {
|
|
1214
|
+
try { await this.saveJSON(filePath, data); } catch { /* tolerate read-only mounts */ }
|
|
1215
|
+
}
|
|
1216
|
+
const recovery = {
|
|
1217
|
+
kind: 'not-found', filePath, label,
|
|
1218
|
+
message: `${label} file was missing — created an empty one.`,
|
|
1219
|
+
};
|
|
1220
|
+
this.logger?.info(`[stateManager] ${recovery.message}`, { filePath });
|
|
1221
|
+
return { data, recovery };
|
|
1222
|
+
}
|
|
1223
|
+
throw err; // unexpected read error (permissions, etc) — let caller handle
|
|
1224
|
+
}
|
|
1225
|
+
|
|
1226
|
+
// 2. Empty or whitespace-only → recreate
|
|
1227
|
+
if (!raw || raw.trim().length === 0) {
|
|
1228
|
+
const data = defaultOf();
|
|
1229
|
+
if (persistRecreated && data !== undefined) {
|
|
1230
|
+
try { await this.saveJSON(filePath, data); } catch { /* ok */ }
|
|
1231
|
+
}
|
|
1232
|
+
const recovery = {
|
|
1233
|
+
kind: 'empty-recreated', filePath, label,
|
|
1234
|
+
message: `${label} file was empty — recreated with a default skeleton.`,
|
|
1235
|
+
};
|
|
1236
|
+
this.logger?.warn(`[stateManager] ${recovery.message}`, { filePath });
|
|
1237
|
+
return { data, recovery };
|
|
1238
|
+
}
|
|
1239
|
+
|
|
1240
|
+
// 3. Strict parse — happy path
|
|
1241
|
+
try {
|
|
1242
|
+
return { data: JSON.parse(raw), recovery: null };
|
|
1243
|
+
} catch (firstErr) {
|
|
1244
|
+
// 3a. Quick repairs: BOM, trailing comma before close, junk after
|
|
1245
|
+
// the closing brace, smart quotes that occasionally creep in via
|
|
1246
|
+
// copy-paste. None of these is exotic — they're the top-3 reasons
|
|
1247
|
+
// hand-edited or partially-flushed files break.
|
|
1248
|
+
let repaired = raw;
|
|
1249
|
+
if (repaired.charCodeAt(0) === 0xFEFF) repaired = repaired.slice(1);
|
|
1250
|
+
// Strip ASCII control chars except tab/newline/CR
|
|
1251
|
+
// eslint-disable-next-line no-control-regex
|
|
1252
|
+
repaired = repaired.replace(/[\x00-\x08\x0b\x0c\x0e-\x1f]/g, '');
|
|
1253
|
+
// Trim whitespace
|
|
1254
|
+
repaired = repaired.trim();
|
|
1255
|
+
// Drop trailing commas before a closing } or ]
|
|
1256
|
+
repaired = repaired.replace(/,(\s*[}\]])/g, '$1');
|
|
1257
|
+
// Cut anything after the last balanced } / ] (junk after JSON).
|
|
1258
|
+
const lastClose = Math.max(repaired.lastIndexOf('}'), repaired.lastIndexOf(']'));
|
|
1259
|
+
if (lastClose > 0 && lastClose < repaired.length - 1) {
|
|
1260
|
+
repaired = repaired.slice(0, lastClose + 1);
|
|
1261
|
+
}
|
|
1262
|
+
try {
|
|
1263
|
+
const data = JSON.parse(repaired);
|
|
1264
|
+
if (persistRecreated && repaired !== raw) {
|
|
1265
|
+
try { await this.saveJSON(filePath, data); } catch { /* ok */ }
|
|
1266
|
+
}
|
|
1267
|
+
const recovery = {
|
|
1268
|
+
kind: 'repaired', filePath, label,
|
|
1269
|
+
message: `${label} file had a minor syntax issue (${firstErr.message}) — auto-repaired.`,
|
|
1270
|
+
};
|
|
1271
|
+
this.logger?.warn(`[stateManager] ${recovery.message}`, { filePath });
|
|
1272
|
+
return { data, recovery };
|
|
1273
|
+
} catch { /* fall through */ }
|
|
1274
|
+
|
|
1275
|
+
// 3b. Salvage: walk the buffer for the first balanced { ... } block
|
|
1276
|
+
// and try to parse just that. Catches truncated-mid-file cases.
|
|
1277
|
+
const salvaged = this._extractFirstJsonBlock(raw);
|
|
1278
|
+
if (salvaged) {
|
|
1279
|
+
try {
|
|
1280
|
+
const data = JSON.parse(salvaged);
|
|
1281
|
+
const archivePath = `${filePath}.corrupt-${Date.now()}.json`;
|
|
1282
|
+
try { await fs.writeFile(archivePath, raw, 'utf8'); } catch { /* ok */ }
|
|
1283
|
+
if (persistRecreated) {
|
|
1284
|
+
try { await this.saveJSON(filePath, data); } catch { /* ok */ }
|
|
1285
|
+
}
|
|
1286
|
+
const recovery = {
|
|
1287
|
+
kind: 'partial', filePath, label,
|
|
1288
|
+
message: `${label} file was partially corrupt — recovered the first valid section. Original archived to ${path.basename(archivePath)}.`,
|
|
1289
|
+
archivePath,
|
|
1290
|
+
};
|
|
1291
|
+
this.logger?.warn(`[stateManager] ${recovery.message}`, { filePath, archivePath });
|
|
1292
|
+
return { data, recovery };
|
|
1293
|
+
} catch { /* fall through */ }
|
|
1294
|
+
}
|
|
1295
|
+
|
|
1296
|
+
// 4. Nothing parseable — archive the corrupt file and use default
|
|
1297
|
+
const data = defaultOf();
|
|
1298
|
+
const archivePath = `${filePath}.corrupt-${Date.now()}.json`;
|
|
1299
|
+
try { await fs.writeFile(archivePath, raw, 'utf8'); } catch { /* ok */ }
|
|
1300
|
+
if (persistRecreated && data !== undefined) {
|
|
1301
|
+
try { await this.saveJSON(filePath, data); } catch { /* ok */ }
|
|
1302
|
+
}
|
|
1303
|
+
const recovery = {
|
|
1304
|
+
kind: 'unrepairable', filePath, label,
|
|
1305
|
+
message: `${label} file is corrupt and could not be repaired — restored a default skeleton. Original archived to ${path.basename(archivePath)}.`,
|
|
1306
|
+
archivePath,
|
|
1307
|
+
originalError: firstErr.message,
|
|
1308
|
+
};
|
|
1309
|
+
this.logger?.error(`[stateManager] ${recovery.message}`, { filePath, archivePath, error: firstErr.message });
|
|
1310
|
+
return { data, recovery };
|
|
1311
|
+
}
|
|
1312
|
+
}
|
|
1313
|
+
|
|
1314
|
+
/**
|
|
1315
|
+
* Walk a string and return the substring of the first balanced
|
|
1316
|
+
* `{ ... }` block, respecting string literals so braces inside strings
|
|
1317
|
+
* don't confuse the count. Returns null if no complete block exists.
|
|
1318
|
+
* @private
|
|
1319
|
+
*/
|
|
1320
|
+
_extractFirstJsonBlock(s) {
|
|
1321
|
+
if (typeof s !== 'string') return null;
|
|
1322
|
+
const start = s.indexOf('{');
|
|
1323
|
+
if (start < 0) return null;
|
|
1324
|
+
let depth = 0;
|
|
1325
|
+
let inStr = false;
|
|
1326
|
+
let escape = false;
|
|
1327
|
+
for (let i = start; i < s.length; i++) {
|
|
1328
|
+
const ch = s[i];
|
|
1329
|
+
if (inStr) {
|
|
1330
|
+
if (escape) { escape = false; continue; }
|
|
1331
|
+
if (ch === '\\') { escape = true; continue; }
|
|
1332
|
+
if (ch === '"') { inStr = false; }
|
|
1333
|
+
continue;
|
|
1334
|
+
}
|
|
1335
|
+
if (ch === '"') { inStr = true; continue; }
|
|
1336
|
+
if (ch === '{') depth++;
|
|
1337
|
+
else if (ch === '}') {
|
|
1338
|
+
depth--;
|
|
1339
|
+
if (depth === 0) return s.slice(start, i + 1);
|
|
1340
|
+
}
|
|
1341
|
+
}
|
|
1342
|
+
return null;
|
|
1343
|
+
}
|
|
1344
|
+
|
|
1345
|
+
/**
|
|
1346
|
+
* Delete agent state from storage
|
|
1347
|
+
* @param {string} agentId - Agent identifier
|
|
1348
|
+
* @param {string} projectDir - Project directory path
|
|
1349
|
+
* @returns {Promise<void>}
|
|
1350
|
+
*/
|
|
1351
|
+
async deleteAgentState(agentId, projectDir = process.cwd()) {
|
|
1352
|
+
const stateDir = this.getStateDir(projectDir);
|
|
1353
|
+
const agentStateFile = path.join(stateDir, 'agents', `agent-${agentId}-state.json`);
|
|
1354
|
+
const agentConversationsFile = path.join(stateDir, 'agents', `agent-${agentId}-conversations.json`);
|
|
1355
|
+
|
|
1356
|
+
try {
|
|
1357
|
+
// Delete agent state file
|
|
1358
|
+
try {
|
|
1359
|
+
await fs.unlink(agentStateFile);
|
|
1360
|
+
this.logger.debug(`Deleted agent state file: ${agentId}`);
|
|
1361
|
+
} catch (error) {
|
|
1362
|
+
if (error.code !== 'ENOENT') {
|
|
1363
|
+
this.logger.warn(`Failed to delete agent state file: ${error.message}`, { agentId });
|
|
1364
|
+
}
|
|
1365
|
+
}
|
|
1366
|
+
|
|
1367
|
+
// Delete agent conversations file
|
|
1368
|
+
try {
|
|
1369
|
+
await fs.unlink(agentConversationsFile);
|
|
1370
|
+
this.logger.debug(`Deleted agent conversations file: ${agentId}`);
|
|
1371
|
+
} catch (error) {
|
|
1372
|
+
if (error.code !== 'ENOENT') {
|
|
1373
|
+
this.logger.warn(`Failed to delete agent conversations file: ${error.message}`, { agentId });
|
|
1374
|
+
}
|
|
1375
|
+
}
|
|
1376
|
+
|
|
1377
|
+
// Remove from agent index
|
|
1378
|
+
await this.removeFromAgentIndex(agentId, projectDir);
|
|
1379
|
+
|
|
1380
|
+
this.logger.info(`Agent state deleted: ${agentId}`);
|
|
1381
|
+
|
|
1382
|
+
} catch (error) {
|
|
1383
|
+
this.logger.error(`Failed to delete agent state: ${error.message}`, {
|
|
1384
|
+
agentId,
|
|
1385
|
+
error: error.stack
|
|
1386
|
+
});
|
|
1387
|
+
throw error;
|
|
1388
|
+
}
|
|
1389
|
+
}
|
|
1390
|
+
|
|
1391
|
+
/**
|
|
1392
|
+
* Remove agent from agent index
|
|
1393
|
+
* @param {string} agentId - Agent identifier
|
|
1394
|
+
* @param {string} projectDir - Project directory path
|
|
1395
|
+
* @returns {Promise<void>}
|
|
1396
|
+
*/
|
|
1397
|
+
// eslint-disable-next-line no-unused-vars
|
|
1398
|
+
async removeFromAgentIndex(agentId, projectDir) {
|
|
1399
|
+
const indexFile = path.join(this.stateDirectory, this.stateFiles.agentIndex);
|
|
1400
|
+
|
|
1401
|
+
try {
|
|
1402
|
+
const agentIndex = await this.loadJSON(indexFile);
|
|
1403
|
+
delete agentIndex[agentId];
|
|
1404
|
+
await this.saveJSON(indexFile, agentIndex);
|
|
1405
|
+
this.logger.debug(`Removed agent from index: ${agentId}`);
|
|
1406
|
+
} catch (error) {
|
|
1407
|
+
// If index doesn't exist or can't be updated, log but don't throw
|
|
1408
|
+
this.logger.warn(`Failed to remove agent from index: ${error.message}`, { agentId });
|
|
1409
|
+
}
|
|
1410
|
+
}
|
|
1411
|
+
|
|
1412
|
+
/**
|
|
1413
|
+
* Check if state directory exists
|
|
1414
|
+
* @param {string} projectDir - Project directory path
|
|
1415
|
+
* @returns {Promise<boolean>} True if state directory exists
|
|
1416
|
+
*/
|
|
1417
|
+
async stateDirectoryExists(projectDir) {
|
|
1418
|
+
const stateDir = this.getStateDir(projectDir);
|
|
1419
|
+
|
|
1420
|
+
try {
|
|
1421
|
+
const stats = await fs.stat(stateDir);
|
|
1422
|
+
return stats.isDirectory();
|
|
1423
|
+
} catch {
|
|
1424
|
+
return false;
|
|
1425
|
+
}
|
|
1426
|
+
}
|
|
1427
|
+
|
|
1428
|
+
/**
|
|
1429
|
+
* Clean up old state files
|
|
1430
|
+
* @param {string} projectDir - Project directory path
|
|
1431
|
+
* @param {number} maxAge - Maximum age in days
|
|
1432
|
+
* @returns {Promise<void>}
|
|
1433
|
+
*/
|
|
1434
|
+
async cleanupOldState(projectDir, maxAge = 30) {
|
|
1435
|
+
const stateDir = this.getStateDir(projectDir);
|
|
1436
|
+
const cutoffDate = Date.now() - (maxAge * 24 * 60 * 60 * 1000);
|
|
1437
|
+
|
|
1438
|
+
try {
|
|
1439
|
+
const agentsDir = path.join(stateDir, 'agents');
|
|
1440
|
+
const files = await fs.readdir(agentsDir);
|
|
1441
|
+
|
|
1442
|
+
for (const file of files) {
|
|
1443
|
+
const filePath = path.join(agentsDir, file);
|
|
1444
|
+
const stats = await fs.stat(filePath);
|
|
1445
|
+
|
|
1446
|
+
if (stats.mtime.getTime() < cutoffDate) {
|
|
1447
|
+
await fs.unlink(filePath);
|
|
1448
|
+
this.logger.info(`Cleaned up old state file: ${file}`);
|
|
1449
|
+
}
|
|
1450
|
+
}
|
|
1451
|
+
|
|
1452
|
+
} catch (error) {
|
|
1453
|
+
this.logger.warn(`State cleanup failed: ${error.message}`);
|
|
1454
|
+
}
|
|
1455
|
+
}
|
|
1456
|
+
|
|
1457
|
+
/**
|
|
1458
|
+
* Get all available agents (active + archived) from filesystem
|
|
1459
|
+
* Scans both the index AND the agents directory to find all agents,
|
|
1460
|
+
* including those that may be missing from the index.
|
|
1461
|
+
* @param {string} projectDir - Project directory path
|
|
1462
|
+
* @param {Object} agentPool - Agent pool instance to check active agents
|
|
1463
|
+
* @returns {Promise<Array>} List of all agents with metadata
|
|
1464
|
+
*/
|
|
1465
|
+
async getAllAvailableAgents(projectDir, agentPool) {
|
|
1466
|
+
try {
|
|
1467
|
+
const agentIndex = await this.loadAgentIndex(projectDir);
|
|
1468
|
+
const activeAgentIds = agentPool ? (await agentPool.getAllAgents()).map(a => a.id) : [];
|
|
1469
|
+
const agentsDir = this.getAgentsDir();
|
|
1470
|
+
|
|
1471
|
+
// Track which agent IDs we've already processed
|
|
1472
|
+
const processedAgentIds = new Set();
|
|
1473
|
+
const agents = [];
|
|
1474
|
+
|
|
1475
|
+
// First, process agents from the index
|
|
1476
|
+
for (const [agentId, info] of Object.entries(agentIndex)) {
|
|
1477
|
+
// Skip invalid or undefined entries
|
|
1478
|
+
if (!info || !info.name) {
|
|
1479
|
+
continue;
|
|
1480
|
+
}
|
|
1481
|
+
|
|
1482
|
+
processedAgentIds.add(agentId);
|
|
1483
|
+
agents.push({
|
|
1484
|
+
agentId,
|
|
1485
|
+
name: info.name,
|
|
1486
|
+
type: info.type,
|
|
1487
|
+
model: info.model,
|
|
1488
|
+
lastActivity: info.lastActivity,
|
|
1489
|
+
status: info.status,
|
|
1490
|
+
stateFile: info.stateFile,
|
|
1491
|
+
conversationsFile: info.conversationsFile,
|
|
1492
|
+
capabilities: info.capabilities || [],
|
|
1493
|
+
isLoaded: activeAgentIds.includes(agentId),
|
|
1494
|
+
canImport: !activeAgentIds.includes(agentId)
|
|
1495
|
+
});
|
|
1496
|
+
}
|
|
1497
|
+
|
|
1498
|
+
// Second, scan the agents directory for any agents not in the index
|
|
1499
|
+
try {
|
|
1500
|
+
const files = await fs.readdir(agentsDir);
|
|
1501
|
+
const stateFiles = files.filter(f => f.endsWith('-state.json'));
|
|
1502
|
+
|
|
1503
|
+
let indexUpdated = false;
|
|
1504
|
+
|
|
1505
|
+
for (const stateFile of stateFiles) {
|
|
1506
|
+
// Extract agent ID from filename: agent-{agentId}-state.json
|
|
1507
|
+
const match = stateFile.match(/^agent-(.+)-state\.json$/);
|
|
1508
|
+
if (!match) continue;
|
|
1509
|
+
|
|
1510
|
+
const agentId = match[1];
|
|
1511
|
+
if (processedAgentIds.has(agentId)) continue;
|
|
1512
|
+
|
|
1513
|
+
// Found an agent not in the index - load its state
|
|
1514
|
+
try {
|
|
1515
|
+
const statePath = path.join(agentsDir, stateFile);
|
|
1516
|
+
const stateData = await this.loadJSON(statePath);
|
|
1517
|
+
const state = stateData.state || stateData;
|
|
1518
|
+
|
|
1519
|
+
// Build agent info from state file
|
|
1520
|
+
const agentInfo = {
|
|
1521
|
+
agentId,
|
|
1522
|
+
name: state.name || stateData.name || `Recovered Agent ${agentId.slice(-8)}`,
|
|
1523
|
+
type: state.type || 'user-created',
|
|
1524
|
+
model: state.currentModel || state.preferredModel || 'unknown',
|
|
1525
|
+
lastActivity: state.lastActivity || stateData.timestamp || null,
|
|
1526
|
+
status: state.status || 'idle',
|
|
1527
|
+
stateFile: `agents/${stateFile}`,
|
|
1528
|
+
conversationsFile: `agents/agent-${agentId}-conversations.json`,
|
|
1529
|
+
capabilities: state.capabilities || [],
|
|
1530
|
+
isLoaded: activeAgentIds.includes(agentId),
|
|
1531
|
+
canImport: !activeAgentIds.includes(agentId)
|
|
1532
|
+
};
|
|
1533
|
+
|
|
1534
|
+
agents.push(agentInfo);
|
|
1535
|
+
processedAgentIds.add(agentId);
|
|
1536
|
+
|
|
1537
|
+
// Update the index with this recovered agent
|
|
1538
|
+
agentIndex[agentId] = {
|
|
1539
|
+
name: agentInfo.name,
|
|
1540
|
+
type: agentInfo.type,
|
|
1541
|
+
stateFile: agentInfo.stateFile,
|
|
1542
|
+
conversationsFile: agentInfo.conversationsFile,
|
|
1543
|
+
lastActivity: agentInfo.lastActivity,
|
|
1544
|
+
model: agentInfo.model,
|
|
1545
|
+
status: agentInfo.status,
|
|
1546
|
+
capabilities: agentInfo.capabilities
|
|
1547
|
+
};
|
|
1548
|
+
indexUpdated = true;
|
|
1549
|
+
|
|
1550
|
+
this.logger.info(`Recovered agent from disk: ${agentInfo.name} (${agentId})`);
|
|
1551
|
+
} catch (err) {
|
|
1552
|
+
this.logger.warn(`Failed to recover agent from ${stateFile}: ${err.message}`);
|
|
1553
|
+
}
|
|
1554
|
+
}
|
|
1555
|
+
|
|
1556
|
+
// Save the updated index if we found missing agents
|
|
1557
|
+
if (indexUpdated) {
|
|
1558
|
+
const indexFile = path.join(this.stateDirectory, this.stateFiles.agentIndex);
|
|
1559
|
+
await this.saveJSON(indexFile, agentIndex);
|
|
1560
|
+
this.logger.info(`Updated agent index with ${agents.length - Object.keys(agentIndex).length + Object.keys(processedAgentIds).size} recovered agents`);
|
|
1561
|
+
}
|
|
1562
|
+
} catch (err) {
|
|
1563
|
+
this.logger.warn(`Failed to scan agents directory: ${err.message}`);
|
|
1564
|
+
}
|
|
1565
|
+
|
|
1566
|
+
// Sort by last activity (most recent first)
|
|
1567
|
+
agents.sort((a, b) => {
|
|
1568
|
+
const dateA = a.lastActivity ? new Date(a.lastActivity) : new Date(0);
|
|
1569
|
+
const dateB = b.lastActivity ? new Date(b.lastActivity) : new Date(0);
|
|
1570
|
+
return dateB - dateA;
|
|
1571
|
+
});
|
|
1572
|
+
|
|
1573
|
+
// Enrich agents with firstUserMessage snippet
|
|
1574
|
+
await this._enrichAgentsWithSnippets(agents, agentPool);
|
|
1575
|
+
|
|
1576
|
+
this.logger.info(`Found ${agents.length} available agents (${agents.filter(a => a.isLoaded).length} active, ${agents.filter(a => !a.isLoaded).length} archived)`);
|
|
1577
|
+
|
|
1578
|
+
return agents;
|
|
1579
|
+
} catch (error) {
|
|
1580
|
+
this.logger.error(`Failed to get available agents: ${error.message}`);
|
|
1581
|
+
throw error;
|
|
1582
|
+
}
|
|
1583
|
+
}
|
|
1584
|
+
|
|
1585
|
+
/**
|
|
1586
|
+
* Enrich agent list with firstUserMessage snippets.
|
|
1587
|
+
* For loaded agents, reads from agentPool. For archived agents, peeks into conversations file.
|
|
1588
|
+
*/
|
|
1589
|
+
async _enrichAgentsWithSnippets(agents, agentPool) {
|
|
1590
|
+
const extractSnippet = (messages) => {
|
|
1591
|
+
if (!messages || messages.length === 0) return null;
|
|
1592
|
+
const firstUser = messages.find(m =>
|
|
1593
|
+
m.role === 'user' && m.content && m.type !== 'task-boundary'
|
|
1594
|
+
);
|
|
1595
|
+
if (!firstUser) return null;
|
|
1596
|
+
const text = typeof firstUser.content === 'string'
|
|
1597
|
+
? firstUser.content
|
|
1598
|
+
: Array.isArray(firstUser.content)
|
|
1599
|
+
? firstUser.content.filter(b => b.type === 'text').map(b => b.text).join('\n')
|
|
1600
|
+
: null;
|
|
1601
|
+
if (!text) return null;
|
|
1602
|
+
const lines = text.split('\n').filter(l => l.trim());
|
|
1603
|
+
const snippet = lines.slice(0, 2).join('\n');
|
|
1604
|
+
return snippet.length > 120 ? snippet.slice(0, 117) + '...' : snippet;
|
|
1605
|
+
};
|
|
1606
|
+
|
|
1607
|
+
for (const agent of agents) {
|
|
1608
|
+
try {
|
|
1609
|
+
if (agent.isLoaded && agentPool) {
|
|
1610
|
+
// For loaded agents, get from the in-memory agent
|
|
1611
|
+
const liveAgent = await agentPool.getAgent(agent.agentId);
|
|
1612
|
+
if (liveAgent) {
|
|
1613
|
+
agent.firstUserMessage = extractSnippet(liveAgent.conversations?.full?.messages);
|
|
1614
|
+
continue;
|
|
1615
|
+
}
|
|
1616
|
+
}
|
|
1617
|
+
// For archived agents, peek into the conversations file on disk
|
|
1618
|
+
if (agent.conversationsFile) {
|
|
1619
|
+
const convPath = path.join(this.stateDirectory, agent.conversationsFile);
|
|
1620
|
+
try {
|
|
1621
|
+
const convData = await this.loadJSON(convPath);
|
|
1622
|
+
const messages = convData?.conversations?.full?.messages || convData?.full?.messages;
|
|
1623
|
+
agent.firstUserMessage = extractSnippet(messages);
|
|
1624
|
+
} catch {
|
|
1625
|
+
// File may not exist — that's fine
|
|
1626
|
+
}
|
|
1627
|
+
}
|
|
1628
|
+
} catch {
|
|
1629
|
+
// Non-critical — just skip this agent's snippet
|
|
1630
|
+
}
|
|
1631
|
+
}
|
|
1632
|
+
}
|
|
1633
|
+
|
|
1634
|
+
/**
|
|
1635
|
+
* Get agent metadata without full restoration (lightweight preview)
|
|
1636
|
+
* @param {string} agentId - Agent ID
|
|
1637
|
+
* @param {string} projectDir - Project directory path
|
|
1638
|
+
* @returns {Promise<Object>} Agent metadata for preview
|
|
1639
|
+
*/
|
|
1640
|
+
async getAgentMetadata(agentId, projectDir) {
|
|
1641
|
+
try {
|
|
1642
|
+
// Load agent index
|
|
1643
|
+
const agentIndex = await this.loadAgentIndex(projectDir);
|
|
1644
|
+
const agentInfo = agentIndex[agentId];
|
|
1645
|
+
|
|
1646
|
+
if (!agentInfo) {
|
|
1647
|
+
throw new Error(`Agent ${agentId} not found in index`);
|
|
1648
|
+
}
|
|
1649
|
+
|
|
1650
|
+
// Load just the state file (lightweight)
|
|
1651
|
+
const stateDir = this.getStateDir(projectDir);
|
|
1652
|
+
const stateFile = path.join(stateDir, agentInfo.stateFile);
|
|
1653
|
+
const conversationsFile = path.join(stateDir, agentInfo.conversationsFile);
|
|
1654
|
+
|
|
1655
|
+
// Check if files exist
|
|
1656
|
+
const stateExists = await fs.access(stateFile).then(() => true).catch(() => false);
|
|
1657
|
+
const conversationsExist = await fs.access(conversationsFile).then(() => true).catch(() => false);
|
|
1658
|
+
|
|
1659
|
+
if (!stateExists) {
|
|
1660
|
+
throw new Error(`State file not found for agent ${agentId}`);
|
|
1661
|
+
}
|
|
1662
|
+
|
|
1663
|
+
// Load state
|
|
1664
|
+
const stateData = await this.loadJSON(stateFile);
|
|
1665
|
+
const state = stateData.state || {};
|
|
1666
|
+
|
|
1667
|
+
// Load conversation count without loading full messages (for performance)
|
|
1668
|
+
let messageCount = 0;
|
|
1669
|
+
let lastMessage = null;
|
|
1670
|
+
if (conversationsExist) {
|
|
1671
|
+
try {
|
|
1672
|
+
const conversations = await this.loadJSON(conversationsFile);
|
|
1673
|
+
messageCount = Array.isArray(conversations) ? conversations.length : 0;
|
|
1674
|
+
|
|
1675
|
+
// Get last message for preview
|
|
1676
|
+
if (messageCount > 0) {
|
|
1677
|
+
const lastMsg = conversations[conversations.length - 1];
|
|
1678
|
+
lastMessage = lastMsg?.content?.substring(0, 100) || null;
|
|
1679
|
+
}
|
|
1680
|
+
} catch (error) {
|
|
1681
|
+
this.logger.warn(`Failed to load conversations for ${agentId}: ${error.message}`);
|
|
1682
|
+
}
|
|
1683
|
+
}
|
|
1684
|
+
|
|
1685
|
+
const metadata = {
|
|
1686
|
+
agentId,
|
|
1687
|
+
name: agentInfo.name || state.name,
|
|
1688
|
+
model: agentInfo.model || state.preferredModel || state.currentModel,
|
|
1689
|
+
lastActivity: agentInfo.lastActivity,
|
|
1690
|
+
status: agentInfo.status,
|
|
1691
|
+
capabilities: state.capabilities || [],
|
|
1692
|
+
messageCount,
|
|
1693
|
+
lastMessage,
|
|
1694
|
+
taskCount: state.taskList?.tasks?.length || 0,
|
|
1695
|
+
createdAt: state.createdAt,
|
|
1696
|
+
workingDirectory: state.directoryAccess?.workingDirectory,
|
|
1697
|
+
mode: state.mode,
|
|
1698
|
+
systemPrompt: state.originalSystemPrompt
|
|
1699
|
+
};
|
|
1700
|
+
|
|
1701
|
+
this.logger.info(`Loaded metadata for agent ${agentId}: ${metadata.messageCount} messages, ${metadata.taskCount} tasks`);
|
|
1702
|
+
|
|
1703
|
+
return metadata;
|
|
1704
|
+
} catch (error) {
|
|
1705
|
+
this.logger.error(`Failed to get agent metadata for ${agentId}: ${error.message}`);
|
|
1706
|
+
throw error;
|
|
1707
|
+
}
|
|
1708
|
+
}
|
|
1709
|
+
|
|
1710
|
+
/**
|
|
1711
|
+
* Import archived agent from filesystem and add to agent pool
|
|
1712
|
+
* @param {string} agentId - Agent ID to import
|
|
1713
|
+
* @param {string} projectDir - Project directory path
|
|
1714
|
+
* @param {Object} agentPool - Agent pool instance
|
|
1715
|
+
* @returns {Promise<Object>} Imported agent object
|
|
1716
|
+
*/
|
|
1717
|
+
async importArchivedAgent(agentId, projectDir, agentPool) {
|
|
1718
|
+
try {
|
|
1719
|
+
// Validate agent ID format for security
|
|
1720
|
+
const AGENT_ID_REGEX = /^agent-[a-z0-9-]+-\d+$/;
|
|
1721
|
+
if (!AGENT_ID_REGEX.test(agentId)) {
|
|
1722
|
+
throw new Error('Invalid agent ID format');
|
|
1723
|
+
}
|
|
1724
|
+
|
|
1725
|
+
// Check if already loaded in agent pool
|
|
1726
|
+
if (agentPool && await agentPool.getAgent(agentId)) {
|
|
1727
|
+
throw new Error(`Agent ${agentId} is already loaded. Use switchAgent() instead.`);
|
|
1728
|
+
}
|
|
1729
|
+
|
|
1730
|
+
// Load from agent index
|
|
1731
|
+
const agentIndex = await this.loadAgentIndex(projectDir);
|
|
1732
|
+
const agentInfo = agentIndex[agentId];
|
|
1733
|
+
|
|
1734
|
+
if (!agentInfo) {
|
|
1735
|
+
throw new Error(`Agent ${agentId} not found in index`);
|
|
1736
|
+
}
|
|
1737
|
+
|
|
1738
|
+
this.logger.info(`Importing archived agent: ${agentId} (${agentInfo.name})`);
|
|
1739
|
+
|
|
1740
|
+
// Restore agent using existing restore logic
|
|
1741
|
+
const agent = await this.restoreAgent(agentId, agentInfo, projectDir);
|
|
1742
|
+
|
|
1743
|
+
// Update agent's last activity
|
|
1744
|
+
agent.lastActivity = new Date().toISOString();
|
|
1745
|
+
|
|
1746
|
+
// Add to agent pool if provided
|
|
1747
|
+
if (agentPool) {
|
|
1748
|
+
agentPool.agents.set(agent.id, agent);
|
|
1749
|
+
agentPool._updateAgentDirectory(agent);
|
|
1750
|
+
this.logger.info(`Agent ${agentId} added to agent pool`);
|
|
1751
|
+
}
|
|
1752
|
+
|
|
1753
|
+
// Update agent index with new last activity
|
|
1754
|
+
await this.updateAgentIndex(agent, projectDir);
|
|
1755
|
+
|
|
1756
|
+
this.logger.info(`Successfully imported agent ${agentId}: ${agent.name}`);
|
|
1757
|
+
|
|
1758
|
+
return agent;
|
|
1759
|
+
} catch (error) {
|
|
1760
|
+
this.logger.error(`Failed to import agent ${agentId}: ${error.message}`);
|
|
1761
|
+
throw error;
|
|
1762
|
+
}
|
|
1763
|
+
}
|
|
1764
|
+
}
|
|
1765
|
+
|
|
1753
1766
|
export default StateManager;
|