hammoc 1.5.0 → 1.7.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +12 -4
- package/package.json +2 -2
- package/packages/client/dist/assets/{agentExampleHighlight-BgwTm15v.js → agentExampleHighlight-7Olvqdpf.js} +1 -1
- package/packages/client/dist/assets/{commandTokenHighlight-BljHwnrK.js → commandTokenHighlight-DtKwh4lx.js} +1 -1
- package/packages/client/dist/assets/index-B09doO8H.js +139 -0
- package/packages/client/dist/assets/index-Bee52CKG.js +1569 -0
- package/packages/client/dist/assets/index-CVy582lU.css +32 -0
- package/packages/client/dist/assets/{index-D3LxqW3f.js → index-DLHBUw8c.js} +1 -1
- package/packages/client/dist/assets/{snippetTokenHighlight-DWsaQXX0.js → snippetTokenHighlight-C8O688f4.js} +1 -1
- package/packages/client/dist/index.html +2 -2
- package/packages/client/dist/sw.js +2 -2
- package/packages/server/dist/_dbg_agent_rows.d.ts +2 -0
- package/packages/server/dist/_dbg_agent_rows.d.ts.map +1 -0
- package/packages/server/dist/_dbg_agent_rows.js +60 -0
- package/packages/server/dist/_dbg_agent_rows.js.map +1 -0
- package/packages/server/dist/_dbg_flicker.d.ts +2 -0
- package/packages/server/dist/_dbg_flicker.d.ts.map +1 -0
- package/packages/server/dist/_dbg_flicker.js +38 -0
- package/packages/server/dist/_dbg_flicker.js.map +1 -0
- package/packages/server/dist/_harness_cli.d.ts +2 -0
- package/packages/server/dist/_harness_cli.d.ts.map +1 -0
- package/packages/server/dist/_harness_cli.js +81 -0
- package/packages/server/dist/_harness_cli.js.map +1 -0
- package/packages/server/dist/_harness_provcard.d.ts +2 -0
- package/packages/server/dist/_harness_provcard.d.ts.map +1 -0
- package/packages/server/dist/_harness_provcard.js +67 -0
- package/packages/server/dist/_harness_provcard.js.map +1 -0
- package/packages/server/dist/_replay_dump.d.ts +2 -0
- package/packages/server/dist/_replay_dump.d.ts.map +1 -0
- package/packages/server/dist/_replay_dump.js +62 -0
- package/packages/server/dist/_replay_dump.js.map +1 -0
- package/packages/server/dist/app.d.ts.map +1 -1
- package/packages/server/dist/app.js +6 -0
- package/packages/server/dist/app.js.map +1 -1
- package/packages/server/dist/config/index.d.ts.map +1 -1
- package/packages/server/dist/config/index.js.map +1 -1
- package/packages/server/dist/controllers/bmadCoreConfigController.d.ts +41 -0
- package/packages/server/dist/controllers/bmadCoreConfigController.d.ts.map +1 -0
- package/packages/server/dist/controllers/bmadCoreConfigController.js +172 -0
- package/packages/server/dist/controllers/bmadCoreConfigController.js.map +1 -0
- package/packages/server/dist/controllers/contextBuilderController.d.ts +43 -0
- package/packages/server/dist/controllers/contextBuilderController.d.ts.map +1 -0
- package/packages/server/dist/controllers/contextBuilderController.js +159 -0
- package/packages/server/dist/controllers/contextBuilderController.js.map +1 -0
- package/packages/server/dist/controllers/harnessAgentController.d.ts +7 -0
- package/packages/server/dist/controllers/harnessAgentController.d.ts.map +1 -1
- package/packages/server/dist/controllers/harnessAgentController.js +33 -0
- package/packages/server/dist/controllers/harnessAgentController.js.map +1 -1
- package/packages/server/dist/controllers/harnessBundleController.d.ts +37 -0
- package/packages/server/dist/controllers/harnessBundleController.d.ts.map +1 -0
- package/packages/server/dist/controllers/harnessBundleController.js +312 -0
- package/packages/server/dist/controllers/harnessBundleController.js.map +1 -0
- package/packages/server/dist/controllers/harnessCommandController.d.ts +7 -0
- package/packages/server/dist/controllers/harnessCommandController.d.ts.map +1 -1
- package/packages/server/dist/controllers/harnessCommandController.js +33 -0
- package/packages/server/dist/controllers/harnessCommandController.js.map +1 -1
- package/packages/server/dist/controllers/harnessHookController.d.ts.map +1 -1
- package/packages/server/dist/controllers/harnessHookController.js +44 -1
- package/packages/server/dist/controllers/harnessHookController.js.map +1 -1
- package/packages/server/dist/controllers/harnessMcpController.d.ts.map +1 -1
- package/packages/server/dist/controllers/harnessMcpController.js +62 -1
- package/packages/server/dist/controllers/harnessMcpController.js.map +1 -1
- package/packages/server/dist/controllers/harnessShareScopeController.d.ts +9 -0
- package/packages/server/dist/controllers/harnessShareScopeController.d.ts.map +1 -1
- package/packages/server/dist/controllers/harnessShareScopeController.js +48 -1
- package/packages/server/dist/controllers/harnessShareScopeController.js.map +1 -1
- package/packages/server/dist/controllers/marketplaceController.d.ts +19 -0
- package/packages/server/dist/controllers/marketplaceController.d.ts.map +1 -0
- package/packages/server/dist/controllers/marketplaceController.js +74 -0
- package/packages/server/dist/controllers/marketplaceController.js.map +1 -0
- package/packages/server/dist/controllers/observabilityController.d.ts +32 -0
- package/packages/server/dist/controllers/observabilityController.d.ts.map +1 -0
- package/packages/server/dist/controllers/observabilityController.js +148 -0
- package/packages/server/dist/controllers/observabilityController.js.map +1 -0
- package/packages/server/dist/controllers/projectController.d.ts.map +1 -1
- package/packages/server/dist/controllers/projectController.js +7 -1
- package/packages/server/dist/controllers/projectController.js.map +1 -1
- package/packages/server/dist/controllers/sessionController.d.ts.map +1 -1
- package/packages/server/dist/controllers/sessionController.js +16 -4
- package/packages/server/dist/controllers/sessionController.js.map +1 -1
- package/packages/server/dist/controllers/systemBrowseController.d.ts +33 -0
- package/packages/server/dist/controllers/systemBrowseController.d.ts.map +1 -0
- package/packages/server/dist/controllers/systemBrowseController.js +105 -0
- package/packages/server/dist/controllers/systemBrowseController.js.map +1 -0
- package/packages/server/dist/handlers/streamCallbacks.d.ts +10 -0
- package/packages/server/dist/handlers/streamCallbacks.d.ts.map +1 -1
- package/packages/server/dist/handlers/streamCallbacks.js +25 -4
- package/packages/server/dist/handlers/streamCallbacks.js.map +1 -1
- package/packages/server/dist/handlers/websocket.d.ts +43 -3
- package/packages/server/dist/handlers/websocket.d.ts.map +1 -1
- package/packages/server/dist/handlers/websocket.js +385 -93
- package/packages/server/dist/handlers/websocket.js.map +1 -1
- package/packages/server/dist/index.js +2 -0
- package/packages/server/dist/index.js.map +1 -1
- package/packages/server/dist/locales/en/server.json +17 -0
- package/packages/server/dist/locales/es/server.json +17 -0
- package/packages/server/dist/locales/ja/server.json +17 -0
- package/packages/server/dist/locales/ko/server.json +17 -0
- package/packages/server/dist/locales/pt/server.json +17 -0
- package/packages/server/dist/locales/zh-CN/server.json +17 -0
- package/packages/server/dist/routes/harness.d.ts.map +1 -1
- package/packages/server/dist/routes/harness.js +58 -0
- package/packages/server/dist/routes/harness.js.map +1 -1
- package/packages/server/dist/routes/preferences.d.ts.map +1 -1
- package/packages/server/dist/routes/preferences.js +12 -2
- package/packages/server/dist/routes/preferences.js.map +1 -1
- package/packages/server/dist/routes/systemBrowse.d.ts +17 -0
- package/packages/server/dist/routes/systemBrowse.d.ts.map +1 -0
- package/packages/server/dist/routes/systemBrowse.js +22 -0
- package/packages/server/dist/routes/systemBrowse.js.map +1 -0
- package/packages/server/dist/services/bmadCoreConfigService.d.ts +86 -0
- package/packages/server/dist/services/bmadCoreConfigService.d.ts.map +1 -0
- package/packages/server/dist/services/bmadCoreConfigService.js +175 -0
- package/packages/server/dist/services/bmadCoreConfigService.js.map +1 -0
- package/packages/server/dist/services/bmadStatusService.d.ts +9 -0
- package/packages/server/dist/services/bmadStatusService.d.ts.map +1 -1
- package/packages/server/dist/services/bmadStatusService.js +77 -13
- package/packages/server/dist/services/bmadStatusService.js.map +1 -1
- package/packages/server/dist/services/chatEngine.d.ts +81 -0
- package/packages/server/dist/services/chatEngine.d.ts.map +1 -0
- package/packages/server/dist/services/chatEngine.js +2 -0
- package/packages/server/dist/services/chatEngine.js.map +1 -0
- package/packages/server/dist/services/chatEngineFactory.d.ts +15 -0
- package/packages/server/dist/services/chatEngineFactory.d.ts.map +1 -0
- package/packages/server/dist/services/chatEngineFactory.js +28 -0
- package/packages/server/dist/services/chatEngineFactory.js.map +1 -0
- package/packages/server/dist/services/chatService.d.ts +34 -26
- package/packages/server/dist/services/chatService.d.ts.map +1 -1
- package/packages/server/dist/services/chatService.js +44 -104
- package/packages/server/dist/services/chatService.js.map +1 -1
- package/packages/server/dist/services/cliChatEngine.d.ts +198 -0
- package/packages/server/dist/services/cliChatEngine.d.ts.map +1 -0
- package/packages/server/dist/services/cliChatEngine.js +2977 -0
- package/packages/server/dist/services/cliChatEngine.js.map +1 -0
- package/packages/server/dist/services/cliGridCards.d.ts +87 -0
- package/packages/server/dist/services/cliGridCards.d.ts.map +1 -0
- package/packages/server/dist/services/cliGridCards.js +298 -0
- package/packages/server/dist/services/cliGridCards.js.map +1 -0
- package/packages/server/dist/services/cliGridRegion.d.ts +44 -0
- package/packages/server/dist/services/cliGridRegion.d.ts.map +1 -0
- package/packages/server/dist/services/cliGridRegion.js +94 -0
- package/packages/server/dist/services/cliGridRegion.js.map +1 -0
- package/packages/server/dist/services/cliModalDetect.d.ts +299 -0
- package/packages/server/dist/services/cliModalDetect.d.ts.map +1 -0
- package/packages/server/dist/services/cliModalDetect.js +583 -0
- package/packages/server/dist/services/cliModalDetect.js.map +1 -0
- package/packages/server/dist/services/cliScreenCache.d.ts +33 -0
- package/packages/server/dist/services/cliScreenCache.d.ts.map +1 -0
- package/packages/server/dist/services/cliScreenCache.js +48 -0
- package/packages/server/dist/services/cliScreenCache.js.map +1 -0
- package/packages/server/dist/services/cliScreenModel.d.ts +141 -0
- package/packages/server/dist/services/cliScreenModel.d.ts.map +1 -0
- package/packages/server/dist/services/cliScreenModel.js +196 -0
- package/packages/server/dist/services/cliScreenModel.js.map +1 -0
- package/packages/server/dist/services/cliScreenStallWatchdog.d.ts +41 -0
- package/packages/server/dist/services/cliScreenStallWatchdog.d.ts.map +1 -0
- package/packages/server/dist/services/cliScreenStallWatchdog.js +45 -0
- package/packages/server/dist/services/cliScreenStallWatchdog.js.map +1 -0
- package/packages/server/dist/services/cliSessionPool.d.ts +97 -0
- package/packages/server/dist/services/cliSessionPool.d.ts.map +1 -0
- package/packages/server/dist/services/cliSessionPool.js +278 -0
- package/packages/server/dist/services/cliSessionPool.js.map +1 -0
- package/packages/server/dist/services/cliSpinnerProgress.d.ts +41 -0
- package/packages/server/dist/services/cliSpinnerProgress.d.ts.map +1 -0
- package/packages/server/dist/services/cliSpinnerProgress.js +131 -0
- package/packages/server/dist/services/cliSpinnerProgress.js.map +1 -0
- package/packages/server/dist/services/contextBuilderScriptTemplate.d.ts +24 -0
- package/packages/server/dist/services/contextBuilderScriptTemplate.d.ts.map +1 -0
- package/packages/server/dist/services/contextBuilderScriptTemplate.js +181 -0
- package/packages/server/dist/services/contextBuilderScriptTemplate.js.map +1 -0
- package/packages/server/dist/services/contextBuilderService.d.ts +68 -0
- package/packages/server/dist/services/contextBuilderService.d.ts.map +1 -0
- package/packages/server/dist/services/contextBuilderService.js +345 -0
- package/packages/server/dist/services/contextBuilderService.js.map +1 -0
- package/packages/server/dist/services/fileRewind.d.ts +46 -0
- package/packages/server/dist/services/fileRewind.d.ts.map +1 -0
- package/packages/server/dist/services/fileRewind.js +77 -0
- package/packages/server/dist/services/fileRewind.js.map +1 -0
- package/packages/server/dist/services/fileWatcherService.d.ts.map +1 -1
- package/packages/server/dist/services/fileWatcherService.js +40 -0
- package/packages/server/dist/services/fileWatcherService.js.map +1 -1
- package/packages/server/dist/services/harnessAgentService.d.ts +18 -0
- package/packages/server/dist/services/harnessAgentService.d.ts.map +1 -1
- package/packages/server/dist/services/harnessAgentService.js +55 -0
- package/packages/server/dist/services/harnessAgentService.js.map +1 -1
- package/packages/server/dist/services/harnessBundleService.d.ts +145 -0
- package/packages/server/dist/services/harnessBundleService.d.ts.map +1 -0
- package/packages/server/dist/services/harnessBundleService.js +1318 -0
- package/packages/server/dist/services/harnessBundleService.js.map +1 -0
- package/packages/server/dist/services/harnessCommandService.d.ts +21 -0
- package/packages/server/dist/services/harnessCommandService.d.ts.map +1 -1
- package/packages/server/dist/services/harnessCommandService.js +64 -0
- package/packages/server/dist/services/harnessCommandService.js.map +1 -1
- package/packages/server/dist/services/harnessHookService.d.ts +27 -0
- package/packages/server/dist/services/harnessHookService.d.ts.map +1 -1
- package/packages/server/dist/services/harnessHookService.js +52 -0
- package/packages/server/dist/services/harnessHookService.js.map +1 -1
- package/packages/server/dist/services/harnessMcpService.d.ts +24 -1
- package/packages/server/dist/services/harnessMcpService.d.ts.map +1 -1
- package/packages/server/dist/services/harnessMcpService.js +70 -0
- package/packages/server/dist/services/harnessMcpService.js.map +1 -1
- package/packages/server/dist/services/harnessShareScopeService.d.ts +19 -0
- package/packages/server/dist/services/harnessShareScopeService.d.ts.map +1 -1
- package/packages/server/dist/services/harnessShareScopeService.js +65 -0
- package/packages/server/dist/services/harnessShareScopeService.js.map +1 -1
- package/packages/server/dist/services/historyParser.d.ts +11 -0
- package/packages/server/dist/services/historyParser.d.ts.map +1 -1
- package/packages/server/dist/services/historyParser.js +10 -8
- package/packages/server/dist/services/historyParser.js.map +1 -1
- package/packages/server/dist/services/imageStorageService.d.ts +12 -0
- package/packages/server/dist/services/imageStorageService.d.ts.map +1 -1
- package/packages/server/dist/services/imageStorageService.js +22 -0
- package/packages/server/dist/services/imageStorageService.js.map +1 -1
- package/packages/server/dist/services/issueService.d.ts.map +1 -1
- package/packages/server/dist/services/issueService.js +1 -0
- package/packages/server/dist/services/issueService.js.map +1 -1
- package/packages/server/dist/services/marketplaceService.d.ts +50 -0
- package/packages/server/dist/services/marketplaceService.d.ts.map +1 -0
- package/packages/server/dist/services/marketplaceService.js +326 -0
- package/packages/server/dist/services/marketplaceService.js.map +1 -0
- package/packages/server/dist/services/observabilityService.d.ts +87 -0
- package/packages/server/dist/services/observabilityService.d.ts.map +1 -0
- package/packages/server/dist/services/observabilityService.js +0 -0
- package/packages/server/dist/services/observabilityService.js.map +1 -0
- package/packages/server/dist/services/projectService.d.ts +13 -1
- package/packages/server/dist/services/projectService.d.ts.map +1 -1
- package/packages/server/dist/services/projectService.js +32 -0
- package/packages/server/dist/services/projectService.js.map +1 -1
- package/packages/server/dist/services/queueService.d.ts.map +1 -1
- package/packages/server/dist/services/queueService.js +12 -3
- package/packages/server/dist/services/queueService.js.map +1 -1
- package/packages/server/dist/services/rateLimitProbeService.d.ts +15 -0
- package/packages/server/dist/services/rateLimitProbeService.d.ts.map +1 -1
- package/packages/server/dist/services/rateLimitProbeService.js +48 -19
- package/packages/server/dist/services/rateLimitProbeService.js.map +1 -1
- package/packages/server/dist/services/sessionService.d.ts +16 -0
- package/packages/server/dist/services/sessionService.d.ts.map +1 -1
- package/packages/server/dist/services/sessionService.js +125 -0
- package/packages/server/dist/services/sessionService.js.map +1 -1
- package/packages/server/dist/services/streamHandler.d.ts +8 -1
- package/packages/server/dist/services/streamHandler.d.ts.map +1 -1
- package/packages/server/dist/services/streamHandler.js +12 -5
- package/packages/server/dist/services/streamHandler.js.map +1 -1
- package/packages/server/dist/services/systemBrowseService.d.ts +91 -0
- package/packages/server/dist/services/systemBrowseService.d.ts.map +1 -0
- package/packages/server/dist/services/systemBrowseService.js +372 -0
- package/packages/server/dist/services/systemBrowseService.js.map +1 -0
- package/packages/server/dist/services/tokenCountService.d.ts +71 -0
- package/packages/server/dist/services/tokenCountService.d.ts.map +1 -0
- package/packages/server/dist/services/tokenCountService.js +313 -0
- package/packages/server/dist/services/tokenCountService.js.map +1 -0
- package/packages/server/dist/services/workspaceContext.d.ts +24 -0
- package/packages/server/dist/services/workspaceContext.d.ts.map +1 -0
- package/packages/server/dist/services/workspaceContext.js +109 -0
- package/packages/server/dist/services/workspaceContext.js.map +1 -0
- package/packages/server/dist/snippets/apply-qa-fixes +7 -5
- package/packages/server/dist/snippets/qa-review +7 -1
- package/packages/server/dist/utils/assertSafeBundlePath.d.ts +29 -0
- package/packages/server/dist/utils/assertSafeBundlePath.d.ts.map +1 -0
- package/packages/server/dist/utils/assertSafeBundlePath.js +53 -0
- package/packages/server/dist/utils/assertSafeBundlePath.js.map +1 -0
- package/packages/server/dist/utils/backgroundBlock.d.ts +29 -0
- package/packages/server/dist/utils/backgroundBlock.d.ts.map +1 -0
- package/packages/server/dist/utils/backgroundBlock.js +33 -0
- package/packages/server/dist/utils/backgroundBlock.js.map +1 -0
- package/packages/server/dist/utils/backgroundTaskTracker.d.ts +20 -0
- package/packages/server/dist/utils/backgroundTaskTracker.d.ts.map +1 -0
- package/packages/server/dist/utils/backgroundTaskTracker.js +40 -0
- package/packages/server/dist/utils/backgroundTaskTracker.js.map +1 -0
- package/packages/server/dist/utils/bundledBinaryModelSupport.d.ts +14 -0
- package/packages/server/dist/utils/bundledBinaryModelSupport.d.ts.map +1 -0
- package/packages/server/dist/utils/bundledBinaryModelSupport.js +112 -0
- package/packages/server/dist/utils/bundledBinaryModelSupport.js.map +1 -0
- package/packages/server/dist/utils/cliDebugLog.d.ts +38 -0
- package/packages/server/dist/utils/cliDebugLog.d.ts.map +1 -0
- package/packages/server/dist/utils/cliDebugLog.js +65 -0
- package/packages/server/dist/utils/cliDebugLog.js.map +1 -0
- package/packages/server/dist/utils/cliEngineUtils.d.ts +35 -0
- package/packages/server/dist/utils/cliEngineUtils.d.ts.map +1 -0
- package/packages/server/dist/utils/cliEngineUtils.js +38 -0
- package/packages/server/dist/utils/cliEngineUtils.js.map +1 -0
- package/packages/server/dist/utils/effortUtils.d.ts +2 -2
- package/packages/server/dist/utils/effortUtils.d.ts.map +1 -1
- package/packages/server/dist/utils/effortUtils.js +7 -5
- package/packages/server/dist/utils/effortUtils.js.map +1 -1
- package/packages/server/dist/utils/errors.d.ts +1 -0
- package/packages/server/dist/utils/errors.d.ts.map +1 -1
- package/packages/server/dist/utils/errors.js +17 -0
- package/packages/server/dist/utils/errors.js.map +1 -1
- package/packages/server/dist/utils/harnessBundleSchema.d.ts +14 -12
- package/packages/server/dist/utils/harnessBundleSchema.d.ts.map +1 -1
- package/packages/server/dist/utils/harnessBundleSchema.js +11 -1
- package/packages/server/dist/utils/harnessBundleSchema.js.map +1 -1
- package/packages/server/dist/utils/harnessPaths.d.ts +40 -0
- package/packages/server/dist/utils/harnessPaths.d.ts.map +1 -1
- package/packages/server/dist/utils/harnessPaths.js +123 -0
- package/packages/server/dist/utils/harnessPaths.js.map +1 -1
- package/packages/server/dist/utils/trailingThrottle.d.ts +21 -0
- package/packages/server/dist/utils/trailingThrottle.d.ts.map +1 -0
- package/packages/server/dist/utils/trailingThrottle.js +36 -0
- package/packages/server/dist/utils/trailingThrottle.js.map +1 -0
- package/packages/server/package.json +61 -58
- package/packages/server/resources/internals/INDEX.md +3 -1
- package/packages/server/resources/internals/bmad-qa-fix-marker.md +32 -0
- package/packages/server/resources/internals/harness-files.md +22 -0
- package/packages/server/resources/internals/image-storage.md +9 -0
- package/packages/server/resources/internals/observability-storage.md +23 -0
- package/packages/server/resources/manual/02-chat.md +23 -4
- package/packages/server/resources/manual/03-sessions.md +1 -0
- package/packages/server/resources/manual/05-projects.md +15 -2
- package/packages/server/resources/manual/10-project-board.md +4 -3
- package/packages/server/resources/manual/11-bmad-method-integration.md +12 -8
- package/packages/server/resources/manual/12-harness-workbench.md +82 -1
- package/packages/server/resources/manual/13-settings.md +61 -9
- package/packages/shared/dist/index.d.ts +7 -0
- package/packages/shared/dist/index.d.ts.map +1 -1
- package/packages/shared/dist/index.js +14 -0
- package/packages/shared/dist/index.js.map +1 -1
- package/packages/shared/dist/types/bmadCoreConfig.d.ts +71 -0
- package/packages/shared/dist/types/bmadCoreConfig.d.ts.map +1 -0
- package/packages/shared/dist/types/bmadCoreConfig.js +30 -0
- package/packages/shared/dist/types/bmadCoreConfig.js.map +1 -0
- package/packages/shared/dist/types/bmadStatus.d.ts +26 -0
- package/packages/shared/dist/types/bmadStatus.d.ts.map +1 -1
- package/packages/shared/dist/types/bmadStatus.js.map +1 -1
- package/packages/shared/dist/types/board.d.ts +6 -0
- package/packages/shared/dist/types/board.d.ts.map +1 -1
- package/packages/shared/dist/types/contextBuilder.d.ts +102 -0
- package/packages/shared/dist/types/contextBuilder.d.ts.map +1 -0
- package/packages/shared/dist/types/contextBuilder.js +55 -0
- package/packages/shared/dist/types/contextBuilder.js.map +1 -0
- package/packages/shared/dist/types/harnessBundle.d.ts +35 -0
- package/packages/shared/dist/types/harnessBundle.d.ts.map +1 -1
- package/packages/shared/dist/types/marketplace.d.ts +83 -0
- package/packages/shared/dist/types/marketplace.d.ts.map +1 -0
- package/packages/shared/dist/types/marketplace.js +18 -0
- package/packages/shared/dist/types/marketplace.js.map +1 -0
- package/packages/shared/dist/types/observability.d.ts +148 -0
- package/packages/shared/dist/types/observability.d.ts.map +1 -0
- package/packages/shared/dist/types/observability.js +24 -0
- package/packages/shared/dist/types/observability.js.map +1 -0
- package/packages/shared/dist/types/preferences.d.ts +16 -2
- package/packages/shared/dist/types/preferences.d.ts.map +1 -1
- package/packages/shared/dist/types/preferences.js +1 -0
- package/packages/shared/dist/types/preferences.js.map +1 -1
- package/packages/shared/dist/types/project.d.ts +7 -1
- package/packages/shared/dist/types/project.d.ts.map +1 -1
- package/packages/shared/dist/types/project.js.map +1 -1
- package/packages/shared/dist/types/sdk.d.ts +124 -12
- package/packages/shared/dist/types/sdk.d.ts.map +1 -1
- package/packages/shared/dist/types/sdk.js +64 -19
- package/packages/shared/dist/types/sdk.js.map +1 -1
- package/packages/shared/dist/types/streaming.d.ts +2 -2
- package/packages/shared/dist/types/streaming.d.ts.map +1 -1
- package/packages/shared/dist/types/streaming.js.map +1 -1
- package/packages/shared/dist/types/systemBrowse.d.ts +120 -0
- package/packages/shared/dist/types/systemBrowse.d.ts.map +1 -0
- package/packages/shared/dist/types/systemBrowse.js +57 -0
- package/packages/shared/dist/types/systemBrowse.js.map +1 -0
- package/packages/shared/dist/types/websocket.d.ts +38 -0
- package/packages/shared/dist/types/websocket.d.ts.map +1 -1
- package/packages/shared/dist/utils/interruptFiller.d.ts +30 -0
- package/packages/shared/dist/utils/interruptFiller.d.ts.map +1 -0
- package/packages/shared/dist/utils/interruptFiller.js +38 -0
- package/packages/shared/dist/utils/interruptFiller.js.map +1 -0
- package/packages/shared/dist/utils/toolResultContent.d.ts +22 -0
- package/packages/shared/dist/utils/toolResultContent.d.ts.map +1 -0
- package/packages/shared/dist/utils/toolResultContent.js +29 -0
- package/packages/shared/dist/utils/toolResultContent.js.map +1 -0
- package/scripts/run-integration-test.mjs +362 -362
- package/packages/client/dist/assets/index-CjyjnXB8.css +0 -32
- package/packages/client/dist/assets/index-NqJdhlek.js +0 -1498
|
@@ -7,13 +7,12 @@
|
|
|
7
7
|
import { Server as SocketIOServer } from 'socket.io';
|
|
8
8
|
import { existsSync, unlinkSync, readFileSync, readdirSync, statSync } from 'fs';
|
|
9
9
|
import { randomUUID } from 'crypto';
|
|
10
|
-
import { ERROR_CODES, IMAGE_CONSTRAINTS, parseQueueScript, TERMINAL_ERRORS, ROOT_BRANCH_KEY } from '@hammoc/shared';
|
|
10
|
+
import { ERROR_CODES, IMAGE_CONSTRAINTS, parseQueueScript, TERMINAL_ERRORS, ROOT_BRANCH_KEY, effectiveModelIs1M } from '@hammoc/shared';
|
|
11
11
|
import i18next from '../i18n.js';
|
|
12
12
|
import { SUPPORTED_LANGUAGES } from '@hammoc/shared';
|
|
13
13
|
import { isLocalIP, extractClientIP } from '../utils/networkUtils.js';
|
|
14
|
-
import {
|
|
15
|
-
import {
|
|
16
|
-
import { SessionService } from '../services/sessionService.js';
|
|
14
|
+
import { createChatEngine } from '../services/chatEngineFactory.js';
|
|
15
|
+
import { SessionService, sessionService } from '../services/sessionService.js';
|
|
17
16
|
import { parseSDKError, AbortedError, SDKErrorCode } from '../utils/errors.js';
|
|
18
17
|
import { createSessionMiddleware } from '../middleware/session.js';
|
|
19
18
|
import { config } from '../config/index.js';
|
|
@@ -22,7 +21,13 @@ import { preferencesService } from '../services/preferencesService.js';
|
|
|
22
21
|
import { getOrCreateQueueService, getQueueInstances } from '../controllers/queueController.js';
|
|
23
22
|
import { createLogger } from '../utils/logger.js';
|
|
24
23
|
import { clampEffortForModel, supportsAdaptiveThinking } from '../utils/effortUtils.js';
|
|
24
|
+
import { shouldForwardCliProgress, shouldForwardCliPtyMirror } from '../utils/cliEngineUtils.js';
|
|
25
|
+
import { getCliScreen, getCliScreenStall, setCliScreenStall, deleteCliScreen } from '../services/cliScreenCache.js';
|
|
26
|
+
import { createScreenStallWatchdog } from '../services/cliScreenStallWatchdog.js';
|
|
27
|
+
import { modelMissingNative1MSupport } from '../utils/bundledBinaryModelSupport.js';
|
|
25
28
|
import { buildStreamCallbacks } from './streamCallbacks.js';
|
|
29
|
+
import { BackgroundTaskTracker } from '../utils/backgroundTaskTracker.js';
|
|
30
|
+
import { createMcpCallRecorder } from '../services/observabilityService.js';
|
|
26
31
|
import { rateLimitProbeService } from '../services/rateLimitProbeService.js';
|
|
27
32
|
import { ptyService } from '../services/ptyService.js';
|
|
28
33
|
import { projectService } from '../services/projectService.js';
|
|
@@ -37,6 +42,8 @@ import { isSnippetRef, resolveSnippet, listSnippets, SnippetError } from '../uti
|
|
|
37
42
|
const log = createLogger('websocket');
|
|
38
43
|
// Alias for concise usage in guards
|
|
39
44
|
const queueInstances = getQueueInstances;
|
|
45
|
+
// Defense B: models already warned about a stale-binary 1M fallback (warn once per model per process)
|
|
46
|
+
const warnedMissing1M = new Set();
|
|
40
47
|
// Socket-to-terminal mapping: socket.id → Set of terminalIds (Story 17.1)
|
|
41
48
|
const socketTerminals = new Map();
|
|
42
49
|
// Story 20.1: Session-to-project mapping for dashboard triggers
|
|
@@ -412,6 +419,25 @@ export function getActiveStreamSessionIds() {
|
|
|
412
419
|
.filter(([, stream]) => stream.status === 'running')
|
|
413
420
|
.map(([key]) => key);
|
|
414
421
|
}
|
|
422
|
+
/**
|
|
423
|
+
* Running streams for a project, with their captured first prompt. Used by the
|
|
424
|
+
* session list to surface a session whose JSONL file does not exist yet (the CLI
|
|
425
|
+
* boot window) and which the client may have already navigated away from — so it
|
|
426
|
+
* is no longer in getJoinedSessionIdsByProject. Prefers the synchronously-captured
|
|
427
|
+
* stream.projectSlug, falling back to the async-populated sessionProjectMap.
|
|
428
|
+
*/
|
|
429
|
+
export function getActiveStreamMetaByProject(projectSlug) {
|
|
430
|
+
const out = [];
|
|
431
|
+
for (const [sessionId, stream] of activeStreams.entries()) {
|
|
432
|
+
if (stream.status !== 'running')
|
|
433
|
+
continue;
|
|
434
|
+
const slug = stream.projectSlug ?? sessionProjectMap.get(sessionId);
|
|
435
|
+
if (slug !== projectSlug)
|
|
436
|
+
continue;
|
|
437
|
+
out.push({ sessionId, firstPrompt: stream.firstPrompt ?? '' });
|
|
438
|
+
}
|
|
439
|
+
return out;
|
|
440
|
+
}
|
|
415
441
|
/** Get active (running) session counts grouped by project slug (in-memory, no I/O) */
|
|
416
442
|
export function getActiveSessionCountsByProject() {
|
|
417
443
|
const counts = new Map();
|
|
@@ -918,6 +944,13 @@ export async function initializeWebSocket(httpServer) {
|
|
|
918
944
|
resumeSessionAt: data.resumeSessionAt,
|
|
919
945
|
expectedBranchTotal: data.expectedBranchTotal,
|
|
920
946
|
isFork: !!data.forkSession,
|
|
947
|
+
// Captured up front so a file-less waiting session (esp. the CLI boot window)
|
|
948
|
+
// can be listed for its project with a real preview even if the client has
|
|
949
|
+
// already left the session view (joinedIds would no longer include it).
|
|
950
|
+
projectSlug: data.workingDirectory
|
|
951
|
+
? sessionService.encodeProjectPath(data.workingDirectory)
|
|
952
|
+
: undefined,
|
|
953
|
+
firstPrompt: data.content || undefined,
|
|
921
954
|
};
|
|
922
955
|
activeStreams.set(streamKey, stream);
|
|
923
956
|
// Bump drain generation so any pending scheduled drain is invalidated
|
|
@@ -1064,31 +1097,43 @@ export async function initializeWebSocket(httpServer) {
|
|
|
1064
1097
|
return;
|
|
1065
1098
|
const { mode, projectSlug } = data;
|
|
1066
1099
|
const stream = activeStreams.get(sessionId);
|
|
1067
|
-
//
|
|
1100
|
+
// Story 37.5: the CLI engine's setPermissionMode runs a live Shift+Tab closed loop and may
|
|
1101
|
+
// LAND on a different mode than requested (fail-safe / off-cycle). setPermissionMode returns
|
|
1102
|
+
// void (signature unchanged), so we read the *verified* applied mode back via
|
|
1103
|
+
// getPermissionMode() and broadcast/persist THAT — not the inbound request value. Without the
|
|
1104
|
+
// read-back the requested mode would be broadcast even on a fail-safe (the live screen never
|
|
1105
|
+
// reached it), silently violating AC3 ("verified, not assumed"). SDK mode: getPermissionMode()
|
|
1106
|
+
// == requested, so the read-back is a harmless no-op there.
|
|
1107
|
+
let effectiveMode = mode;
|
|
1108
|
+
let modeDiverged = false; // verified mode differed from the request (CLI fail-safe / off-cycle)
|
|
1109
|
+
// 1) Update the engine's permission mode — only when stream is actively running
|
|
1068
1110
|
if (stream?.chatService && stream.status === 'running') {
|
|
1069
1111
|
try {
|
|
1070
1112
|
await stream.chatService.setPermissionMode(mode);
|
|
1071
|
-
|
|
1113
|
+
effectiveMode = stream.chatService.getPermissionMode(); // verified (closed-loop) mode
|
|
1114
|
+
modeDiverged = effectiveMode !== mode;
|
|
1115
|
+
log.debug(`Permission mode requested "${mode}", applied "${effectiveMode}" for session ${sessionId}`);
|
|
1072
1116
|
}
|
|
1073
1117
|
catch (err) {
|
|
1074
1118
|
log.error('Failed to change permission mode:', err);
|
|
1075
1119
|
return; // Don't persist or broadcast a mode that failed to apply
|
|
1076
1120
|
}
|
|
1077
1121
|
}
|
|
1078
|
-
// Update pending chain items so the next drain uses the new mode.
|
|
1079
|
-
//
|
|
1122
|
+
// Update pending chain items so the next drain uses the new mode. Use the verified mode so a
|
|
1123
|
+
// CLI fail-safe also corrects the next-spawn posture (== request on every non-CLI/non-diverged
|
|
1124
|
+
// path). Outside the running-stream block because mode can change between turns
|
|
1080
1125
|
// (e.g., during the 1s chain drain delay when no stream is active).
|
|
1081
1126
|
const chainItems = chainState.get(sessionId);
|
|
1082
1127
|
if (chainItems) {
|
|
1083
1128
|
for (const item of chainItems) {
|
|
1084
1129
|
if (item.status === 'pending' || item.status === 'sending') {
|
|
1085
|
-
item.permissionMode =
|
|
1130
|
+
item.permissionMode = effectiveMode;
|
|
1086
1131
|
}
|
|
1087
1132
|
}
|
|
1088
1133
|
}
|
|
1089
1134
|
// 2) Always persist per-session permission mode (read only when policy is 'always')
|
|
1090
1135
|
// Use projectSlug from client as fallback when sessionProjectMap entry is gone (stream ended)
|
|
1091
|
-
await persistSessionPermissionMode(sessionId,
|
|
1136
|
+
await persistSessionPermissionMode(sessionId, effectiveMode, projectSlug);
|
|
1092
1137
|
// 3) Broadcast to other viewers based on sync policy
|
|
1093
1138
|
let syncPolicy = 'always';
|
|
1094
1139
|
try {
|
|
@@ -1100,8 +1145,16 @@ export async function initializeWebSocket(httpServer) {
|
|
|
1100
1145
|
}
|
|
1101
1146
|
if (syncPolicy === 'streaming' && stream?.status !== 'running')
|
|
1102
1147
|
return;
|
|
1103
|
-
// 'always' or ('streaming' + running) → broadcast via Socket.io room
|
|
1104
|
-
socket.to(`session:${sessionId}`).emit('permission:mode-change', { mode });
|
|
1148
|
+
// 'always' or ('streaming' + running) → broadcast the VERIFIED mode via Socket.io room.
|
|
1149
|
+
socket.to(`session:${sessionId}`).emit('permission:mode-change', { mode: effectiveMode });
|
|
1150
|
+
// Story 37.5: originator convergence. The broadcast above excludes the sending socket
|
|
1151
|
+
// (socket.to), so on a fail-safe (verified ≠ requested) the SENDER's selector would stay on
|
|
1152
|
+
// the wrong requested value. Echo the verified mode back to the sender ONLY in that case; the
|
|
1153
|
+
// normal path (verified == requested) leaves the sender's optimistic value correct, so no
|
|
1154
|
+
// redundant self-echo is sent.
|
|
1155
|
+
if (modeDiverged) {
|
|
1156
|
+
socket.emit('permission:mode-change', { mode: effectiveMode });
|
|
1157
|
+
}
|
|
1105
1158
|
});
|
|
1106
1159
|
// Handle session:join event — attach socket to active running stream (broadcast)
|
|
1107
1160
|
// Also joins a persistent Socket.io room so future streams auto-include this socket.
|
|
@@ -1185,6 +1238,26 @@ export async function initializeWebSocket(httpServer) {
|
|
|
1185
1238
|
if (alreadyJoinedSame) {
|
|
1186
1239
|
return;
|
|
1187
1240
|
}
|
|
1241
|
+
// Story 37.8: CLI mirror late-join / refresh sync. If this session has a cached screen
|
|
1242
|
+
// frame (refreshed each ~100ms during a turn and at teardown), push it once to THIS
|
|
1243
|
+
// socket so a newly-connected or refreshed browser initializes to the CURRENT claude
|
|
1244
|
+
// screen (color intact), then follows live cli:screen-frame updates. Room-wide broadcast
|
|
1245
|
+
// is unnecessary (sockets already in the room are live-current). A browser refresh
|
|
1246
|
+
// reconnects with a NEW socket id, so it is past the alreadyJoinedSame dedup above and
|
|
1247
|
+
// receives the frame — only a same-socket re-join is deduped. Cache miss (e.g. SDK mode,
|
|
1248
|
+
// or before the first turn ends) → no emit, blank-screen fallback, unchanged from before.
|
|
1249
|
+
const cachedScreen = getCliScreen(sessionId);
|
|
1250
|
+
if (cachedScreen) {
|
|
1251
|
+
socket.emit('cli:screen-frame', { sessionId, frame: cachedScreen });
|
|
1252
|
+
}
|
|
1253
|
+
// Resync the soft screen-stall flag (CLI mode). cli:screen-stall is emitted only on a
|
|
1254
|
+
// stalled↔live transition, so a socket that joined / reconnected AFTER the stall began
|
|
1255
|
+
// (tab switch, mobile sleep/wake) never saw it and would otherwise show a stale card.
|
|
1256
|
+
// Re-send the current flag once so the stall notice matches reality. Only when stalled:
|
|
1257
|
+
// the client resets the flag to false on stream restore, so a false needs no wire traffic.
|
|
1258
|
+
if (getCliScreenStall(sessionId)) {
|
|
1259
|
+
socket.emit('cli:screen-stall', { sessionId, stalled: true });
|
|
1260
|
+
}
|
|
1188
1261
|
if (!stream || stream.status !== 'running') {
|
|
1189
1262
|
// Story 27.1: Deliver buffer messages via stream:history (no HTTP fetch needed).
|
|
1190
1263
|
// Wrapped in a helper that re-checks activeStreams because the async
|
|
@@ -1299,6 +1372,32 @@ export async function initializeWebSocket(httpServer) {
|
|
|
1299
1372
|
// NOW add to broadcast set — live events flow from here
|
|
1300
1373
|
stream.sockets.add(socket);
|
|
1301
1374
|
});
|
|
1375
|
+
// Story 37.8: CLI mirror collapse/expand + remount restore. The mirror panel emits this
|
|
1376
|
+
// (no payload) whenever it (re)mounts; resolve the socket's current session and push a
|
|
1377
|
+
// one-time cli:screen-frame from the screen cache. A collapse/expand or panel remount does
|
|
1378
|
+
// NOT re-fire session:join, so this control channel is how the panel pulls the current
|
|
1379
|
+
// screen. Cache miss (SDK mode / before first turn / no session) → no-op.
|
|
1380
|
+
socket.on('cli:request-screen-frame', () => {
|
|
1381
|
+
const sid = socketToSession.get(socket.id) ?? socketSessionRoom.get(socket.id);
|
|
1382
|
+
if (!sid)
|
|
1383
|
+
return;
|
|
1384
|
+
const cachedFrame = getCliScreen(sid);
|
|
1385
|
+
if (cachedFrame) {
|
|
1386
|
+
socket.emit('cli:screen-frame', { sessionId: sid, frame: cachedFrame });
|
|
1387
|
+
}
|
|
1388
|
+
});
|
|
1389
|
+
// CLI debug log relay — client sends decision entries, server writes them to the
|
|
1390
|
+
// per-session CLI debug log file alongside server-side entries.
|
|
1391
|
+
socket.on('cli:debug-log', (data) => {
|
|
1392
|
+
const sid = socketToSession.get(socket.id) ?? socketSessionRoom.get(socket.id);
|
|
1393
|
+
if (!sid)
|
|
1394
|
+
return;
|
|
1395
|
+
const stream = activeStreams.get(sid);
|
|
1396
|
+
const engine = stream?.chatService;
|
|
1397
|
+
if (engine && 'currentDebugLog' in engine) {
|
|
1398
|
+
engine.currentDebugLog?.clientWithTs(data.ts, data.ev, data.d);
|
|
1399
|
+
}
|
|
1400
|
+
});
|
|
1302
1401
|
// Handle session:leave event — detach socket from current stream and session room
|
|
1303
1402
|
// (client navigating away from a session while streaming continues in background)
|
|
1304
1403
|
socket.on('session:leave', (sessionId) => {
|
|
@@ -1562,50 +1661,27 @@ export async function initializeWebSocket(httpServer) {
|
|
|
1562
1661
|
return;
|
|
1563
1662
|
log.info(`session:rewind-files sessionId=${sessionId}, messageUuid=${messageUuid}, dryRun=${!!dryRun}`);
|
|
1564
1663
|
try {
|
|
1565
|
-
|
|
1566
|
-
|
|
1567
|
-
|
|
1568
|
-
|
|
1569
|
-
|
|
1570
|
-
|
|
1571
|
-
|
|
1572
|
-
|
|
1573
|
-
|
|
1574
|
-
|
|
1575
|
-
|
|
1576
|
-
|
|
1577
|
-
|
|
1578
|
-
|
|
1579
|
-
const rewindResult = await rewindQuery.rewindFiles(messageUuid, { dryRun: !!dryRun });
|
|
1580
|
-
log.info(`rewindFiles result: canRewind=${rewindResult.canRewind}, filesChanged=${rewindResult.filesChanged?.length ?? 0}, insertions=${rewindResult.insertions ?? 0}, deletions=${rewindResult.deletions ?? 0}`);
|
|
1581
|
-
if (rewindResult.canRewind) {
|
|
1582
|
-
socket.emit('session:rewind-result', {
|
|
1583
|
-
success: true,
|
|
1584
|
-
dryRun: !!dryRun,
|
|
1585
|
-
filesChanged: rewindResult.filesChanged,
|
|
1586
|
-
insertions: rewindResult.insertions,
|
|
1587
|
-
deletions: rewindResult.deletions,
|
|
1588
|
-
});
|
|
1589
|
-
}
|
|
1590
|
-
else {
|
|
1591
|
-
socket.emit('session:rewind-result', {
|
|
1592
|
-
success: false,
|
|
1593
|
-
dryRun: !!dryRun,
|
|
1594
|
-
error: rewindResult.error,
|
|
1595
|
-
});
|
|
1596
|
-
}
|
|
1664
|
+
// Standalone rewind is now a ChatEngine operation (Story 32.3) — the SDK
|
|
1665
|
+
// mechanism (throwaway resume query + markRewindDirty + close) lives
|
|
1666
|
+
// behind the seam in ChatService.rewindFiles. The handler keeps only the
|
|
1667
|
+
// transport concerns: result → session:rewind-result mapping.
|
|
1668
|
+
const engine = createChatEngine(await projectService.getEffectiveEngineMode(workingDirectory), { workingDirectory });
|
|
1669
|
+
const result = await engine.rewindFiles({ sessionId, messageUuid, dryRun: !!dryRun });
|
|
1670
|
+
if (result.canRewind) {
|
|
1671
|
+
socket.emit('session:rewind-result', {
|
|
1672
|
+
success: true,
|
|
1673
|
+
dryRun: !!dryRun,
|
|
1674
|
+
filesChanged: result.filesChanged,
|
|
1675
|
+
insertions: result.insertions,
|
|
1676
|
+
deletions: result.deletions,
|
|
1677
|
+
});
|
|
1597
1678
|
}
|
|
1598
|
-
|
|
1599
|
-
|
|
1600
|
-
|
|
1601
|
-
|
|
1602
|
-
|
|
1603
|
-
|
|
1604
|
-
// Clean up the query object
|
|
1605
|
-
try {
|
|
1606
|
-
rewindQuery.close();
|
|
1607
|
-
}
|
|
1608
|
-
catch { /* best-effort */ }
|
|
1679
|
+
else {
|
|
1680
|
+
socket.emit('session:rewind-result', {
|
|
1681
|
+
success: false,
|
|
1682
|
+
dryRun: !!dryRun,
|
|
1683
|
+
error: result.error,
|
|
1684
|
+
});
|
|
1609
1685
|
}
|
|
1610
1686
|
}
|
|
1611
1687
|
catch (err) {
|
|
@@ -2090,6 +2166,9 @@ export async function initializeWebSocket(httpServer) {
|
|
|
2090
2166
|
// Only destroy if no active stream (streaming continues in background)
|
|
2091
2167
|
if (!activeStreams.has(disconnectSessionId)) {
|
|
2092
2168
|
sessionBufferManager.destroy(disconnectSessionId);
|
|
2169
|
+
// Story 37.7: drop the CLI mirror screen cache on the same room-empty seam
|
|
2170
|
+
// (no observer left to sync) so the session-lifetime Map does not leak.
|
|
2171
|
+
deleteCliScreen(disconnectSessionId);
|
|
2093
2172
|
}
|
|
2094
2173
|
// Notify session list viewers when no sockets remain for this session
|
|
2095
2174
|
const slug = socketProjectRoom.get(socket.id) || sessionProjectMap.get(disconnectSessionId);
|
|
@@ -2183,6 +2262,45 @@ export async function broadcastSnippetList(workingDirectory, originSocketId) {
|
|
|
2183
2262
|
log.warn(`broadcastSnippetList failed for socket ${originSocketId}: ${String(err)}`);
|
|
2184
2263
|
}
|
|
2185
2264
|
}
|
|
2265
|
+
/**
|
|
2266
|
+
* Broadcast a global-preferences change to every OTHER connected browser
|
|
2267
|
+
* (multi-device settings sync). The originating socket is excluded so it never
|
|
2268
|
+
* receives its own echo — it already applied the change optimistically. With no
|
|
2269
|
+
* origin id (e.g. a non-browser caller) the change goes to everyone.
|
|
2270
|
+
*/
|
|
2271
|
+
export function broadcastPreferencesChange(preferences, originSocketId) {
|
|
2272
|
+
try {
|
|
2273
|
+
// Strip credential-bearing fields — telegram/webPush are managed via
|
|
2274
|
+
// dedicated (masked) endpoints and must not be broadcast in plaintext to
|
|
2275
|
+
// other browsers. Clients keep their existing values for these (merge).
|
|
2276
|
+
const safe = { ...preferences };
|
|
2277
|
+
delete safe.telegram;
|
|
2278
|
+
delete safe.webPush;
|
|
2279
|
+
// theme & diffLayout are device-local (each browser's localStorage, not the server) —
|
|
2280
|
+
// broadcasting them would yank another device's theme / diff layout. Never sync them.
|
|
2281
|
+
delete safe.theme;
|
|
2282
|
+
delete safe.diffLayout;
|
|
2283
|
+
const target = originSocketId ? io.except(originSocketId) : io;
|
|
2284
|
+
target.emit('preferences:changed', { preferences: safe });
|
|
2285
|
+
}
|
|
2286
|
+
catch (err) {
|
|
2287
|
+
log.warn(`broadcastPreferencesChange failed: ${String(err)}`);
|
|
2288
|
+
}
|
|
2289
|
+
}
|
|
2290
|
+
/**
|
|
2291
|
+
* Broadcast a project-settings change to OTHER browsers (multi-device sync).
|
|
2292
|
+
* Sent to everyone except the origin socket; the payload is keyed by projectSlug
|
|
2293
|
+
* so each client applies it only while viewing that project's settings.
|
|
2294
|
+
*/
|
|
2295
|
+
export function broadcastProjectSettingsChange(projectSlug, settings, originSocketId) {
|
|
2296
|
+
try {
|
|
2297
|
+
const target = originSocketId ? io.except(originSocketId) : io;
|
|
2298
|
+
target.emit('project:settings-changed', { projectSlug, settings });
|
|
2299
|
+
}
|
|
2300
|
+
catch (err) {
|
|
2301
|
+
log.warn(`broadcastProjectSettingsChange failed: ${String(err)}`);
|
|
2302
|
+
}
|
|
2303
|
+
}
|
|
2186
2304
|
/**
|
|
2187
2305
|
* Get the current number of connected clients
|
|
2188
2306
|
* @returns Number of connected clients
|
|
@@ -2294,10 +2412,14 @@ async function handleChatSend(stream, data, abortController, lang) {
|
|
|
2294
2412
|
const sessionService = new SessionService();
|
|
2295
2413
|
const projectSlug = sessionService.encodeProjectPath(workingDirectory);
|
|
2296
2414
|
// Story 27.2: Store images as files, emit URL-based ImageRef[] instead of base64.
|
|
2415
|
+
// CLI engine (image support): also resolve the on-disk absolute paths so the CLI path
|
|
2416
|
+
// can reference attachments by --add-dir + prompt (it can't embed base64 over the PTY).
|
|
2417
|
+
let attachedImagePaths = [];
|
|
2297
2418
|
if (!forkSession) {
|
|
2298
2419
|
let imageRefs;
|
|
2299
2420
|
if (images && images.length > 0) {
|
|
2300
2421
|
imageRefs = await imageStorageService.storeImages(projectSlug, sessionId || '', images);
|
|
2422
|
+
attachedImagePaths = imageStorageService.resolveImagePaths(projectSlug, sessionId || '', images);
|
|
2301
2423
|
}
|
|
2302
2424
|
emit('user:message', {
|
|
2303
2425
|
content,
|
|
@@ -2364,6 +2486,14 @@ async function handleChatSend(stream, data, abortController, lang) {
|
|
|
2364
2486
|
}
|
|
2365
2487
|
}
|
|
2366
2488
|
let timeoutId = null;
|
|
2489
|
+
// CLI screen-stall watchdog — assigned once prefs/engineMode are known (below). Declared at the
|
|
2490
|
+
// function scope so the finally can dispose() it (a try-block `const` wouldn't be in scope there).
|
|
2491
|
+
let stallWatchdog = null;
|
|
2492
|
+
// Story 35.1: when true the inactivity timer is paused because we're awaiting
|
|
2493
|
+
// user input (permission approval / AskUserQuestion selection). resetTimeout()
|
|
2494
|
+
// short-circuits while this is set so no activity can re-arm the timer, and
|
|
2495
|
+
// canUseTool clears it + re-arms once the user responds.
|
|
2496
|
+
let permissionWaiting = false;
|
|
2367
2497
|
// Snapshot JSONL files before SDK query to detect phantom checkpoint files.
|
|
2368
2498
|
// The SDK's file checkpointing creates separate JSONL files (new UUIDs) with
|
|
2369
2499
|
// only file-history-snapshot entries alongside the real session file. These
|
|
@@ -2380,12 +2510,20 @@ async function handleChatSend(stream, data, abortController, lang) {
|
|
|
2380
2510
|
// Non-critical — cleanup will be skipped if snapshot fails
|
|
2381
2511
|
}
|
|
2382
2512
|
try {
|
|
2383
|
-
const chatService = new ChatService({ workingDirectory, permissionMode });
|
|
2384
|
-
stream.chatService = chatService;
|
|
2385
2513
|
// Load preferences early for advanced settings + timeout
|
|
2386
2514
|
const effectivePrefs = await preferencesService.getEffectivePreferences();
|
|
2515
|
+
// Story 33.3: resolve the effective engine mode (override > global > 'sdk' default).
|
|
2516
|
+
// Captured once into a local so the progress-callback gating below reuses the same
|
|
2517
|
+
// decision (no second resolve, and `engineMode === 'cli'` stays consistent).
|
|
2518
|
+
const engineMode = await projectService.getEffectiveEngineMode(workingDirectory);
|
|
2519
|
+
// Auto-compaction master switch (both engines). Default ON. Gates the engine-internal
|
|
2520
|
+
// auto-compact (forwarded into each engine's settings) AND Hammoc's overflow-triggered
|
|
2521
|
+
// /compact recovery below, so one toggle controls all auto-compaction.
|
|
2522
|
+
const autoCompactEnabled = effectivePrefs.autoCompactEnabled ?? true;
|
|
2523
|
+
const chatService = createChatEngine(engineMode, { workingDirectory, permissionMode, cliBinaryPath: effectivePrefs.cliBinaryPath, cliShowThinkingSummaries: effectivePrefs.cliShowThinkingSummaries, cliResumeChoice: effectivePrefs.cliResumeChoice, autoCompactEnabled, planModeBypassBehavior: effectivePrefs.planModeBypassBehavior });
|
|
2524
|
+
stream.chatService = chatService;
|
|
2387
2525
|
const effectiveEffort = clampEffortForModel(effort ?? effectivePrefs.defaultEffort, model);
|
|
2388
|
-
// Opus 4.7 flipped `thinking.display` default to 'omitted' — ThinkingBlock UI stays blank
|
|
2526
|
+
// Opus 4.7+ flipped `thinking.display` default to 'omitted' — ThinkingBlock UI stays blank
|
|
2389
2527
|
// unless we explicitly opt in. For adaptive-thinking models, forward an explicit `thinking`
|
|
2390
2528
|
// config based on the user's showThinkingBlocks preference (Hammoc default: true).
|
|
2391
2529
|
// Legacy models (Sonnet 4.5, Opus 4.5, Haiku, Sonnet 4) keep the `maxThinkingTokens` path
|
|
@@ -2399,11 +2537,17 @@ async function handleChatSend(stream, data, abortController, lang) {
|
|
|
2399
2537
|
...(isResuming ? { resume: sessionId } : { sessionId }),
|
|
2400
2538
|
abortController,
|
|
2401
2539
|
model,
|
|
2402
|
-
|
|
2540
|
+
// SDK mode embeds base64 image blocks directly; CLI mode can't (the PTY is
|
|
2541
|
+
// text-only), so it instead receives the on-disk paths resolved above and
|
|
2542
|
+
// references them via --add-dir + an explicit prompt instruction. Each engine
|
|
2543
|
+
// ignores the other's field, so we only send the one that applies.
|
|
2544
|
+
...(engineMode === 'cli'
|
|
2545
|
+
? (attachedImagePaths.length > 0 ? { attachedImagePaths } : {})
|
|
2546
|
+
: { images }),
|
|
2403
2547
|
// Advanced settings from preferences
|
|
2404
2548
|
customSystemPrompt: effectivePrefs.customSystemPrompt,
|
|
2405
2549
|
// maxThinkingTokens: legacy path; SDK docs say it takes precedence for backward-compat,
|
|
2406
|
-
// which conflicts with adaptive mode on Opus 4.7 (rejects enabled+budget). Skip on
|
|
2550
|
+
// which conflicts with adaptive mode on Opus 4.7+ (rejects enabled+budget). Skip on
|
|
2407
2551
|
// adaptive-capable models so the explicit `thinking` field governs.
|
|
2408
2552
|
...(!adaptiveCapable && { maxThinkingTokens: effectivePrefs.maxThinkingTokens }),
|
|
2409
2553
|
...(thinkingConfig && { thinking: thinkingConfig }),
|
|
@@ -2449,6 +2593,10 @@ async function handleChatSend(stream, data, abortController, lang) {
|
|
|
2449
2593
|
sessionId: sessionIdRef.current || '',
|
|
2450
2594
|
toolCall: { id: requestId, name: toolName, input },
|
|
2451
2595
|
requiresApproval: true,
|
|
2596
|
+
// CLI engine emits no tool:call, so a normal-tool permission has no tool card to
|
|
2597
|
+
// attach to — tell the client to render it as an independent card. AskUserQuestion
|
|
2598
|
+
// already renders independently (client keys off the tool name), so exclude it.
|
|
2599
|
+
standalone: engineMode === 'cli' && !isAskUserQuestion,
|
|
2452
2600
|
});
|
|
2453
2601
|
// Notify via Telegram if no socket connected (or alwaysNotify enabled)
|
|
2454
2602
|
if (notificationService.shouldNotify(stream.sockets.size)) {
|
|
@@ -2457,38 +2605,59 @@ async function handleChatSend(stream, data, abortController, lang) {
|
|
|
2457
2605
|
: `${toolName}`;
|
|
2458
2606
|
notificationService.notifyInputRequired(stream.sessionId, toolName, prompt);
|
|
2459
2607
|
}
|
|
2460
|
-
//
|
|
2461
|
-
|
|
2462
|
-
|
|
2463
|
-
|
|
2464
|
-
|
|
2608
|
+
// Story 35.1: pause the inactivity timer for the input-wait. Waiting on a human
|
|
2609
|
+
// (permission approval / AskUserQuestion selection) is by-design indefinite —
|
|
2610
|
+
// "respond when you can" — so the activity-based timeout must not fire here. Stop
|
|
2611
|
+
// it on the way in; the finally below resumes it on EVERY exit path. Generation-hang
|
|
2612
|
+
// detection is unaffected: the timer still governs all activity outside this wait.
|
|
2613
|
+
permissionWaiting = true;
|
|
2614
|
+
if (timeoutId) {
|
|
2615
|
+
clearTimeout(timeoutId);
|
|
2616
|
+
timeoutId = null;
|
|
2617
|
+
}
|
|
2618
|
+
try {
|
|
2619
|
+
// Wait for user response — Promise stays pending if no socket connected
|
|
2620
|
+
const userResponse = await new Promise((resolve) => {
|
|
2621
|
+
stream.pendingPermissions.set(requestId, {
|
|
2622
|
+
resolve,
|
|
2623
|
+
interactionType: isAskUserQuestion ? 'question' : 'permission',
|
|
2624
|
+
});
|
|
2465
2625
|
});
|
|
2466
|
-
|
|
2467
|
-
|
|
2468
|
-
|
|
2469
|
-
|
|
2470
|
-
|
|
2471
|
-
|
|
2626
|
+
if (isAskUserQuestion) {
|
|
2627
|
+
const questions = input.questions;
|
|
2628
|
+
let answers;
|
|
2629
|
+
if (typeof userResponse.response === 'object' && !Array.isArray(userResponse.response) && userResponse.response !== null) {
|
|
2630
|
+
answers = userResponse.response;
|
|
2631
|
+
}
|
|
2632
|
+
else {
|
|
2633
|
+
const answer = typeof userResponse.response === 'string'
|
|
2634
|
+
? userResponse.response
|
|
2635
|
+
: Array.isArray(userResponse.response) ? userResponse.response.join(', ') : '';
|
|
2636
|
+
answers = { [questions[0].question]: answer };
|
|
2637
|
+
}
|
|
2638
|
+
log.info(`[canUseTool] AskUserQuestion result: questionCount=${questions.length}, answerCount=${Object.keys(answers).length}, responseType=${typeof userResponse.response}, isArray=${Array.isArray(userResponse.response)}, answerKeys=${JSON.stringify(Object.keys(answers))}`);
|
|
2639
|
+
return {
|
|
2640
|
+
behavior: 'allow',
|
|
2641
|
+
updatedInput: {
|
|
2642
|
+
questions,
|
|
2643
|
+
answers,
|
|
2644
|
+
},
|
|
2645
|
+
};
|
|
2646
|
+
}
|
|
2647
|
+
if (userResponse.approved) {
|
|
2648
|
+
return { behavior: 'allow', updatedInput: input };
|
|
2472
2649
|
}
|
|
2473
2650
|
else {
|
|
2474
|
-
|
|
2475
|
-
|
|
2476
|
-
: Array.isArray(userResponse.response) ? userResponse.response.join(', ') : '';
|
|
2477
|
-
answers = { [questions[0].question]: answer };
|
|
2478
|
-
}
|
|
2479
|
-
return {
|
|
2480
|
-
behavior: 'allow',
|
|
2481
|
-
updatedInput: {
|
|
2482
|
-
questions,
|
|
2483
|
-
answers,
|
|
2484
|
-
},
|
|
2485
|
-
};
|
|
2486
|
-
}
|
|
2487
|
-
if (userResponse.approved) {
|
|
2488
|
-
return { behavior: 'allow', updatedInput: input };
|
|
2651
|
+
return { behavior: 'deny', message: 'User denied permission', interrupt: true };
|
|
2652
|
+
}
|
|
2489
2653
|
}
|
|
2490
|
-
|
|
2491
|
-
|
|
2654
|
+
finally {
|
|
2655
|
+
// Story 35.1: resume on EVERY exit path (resolve / throw / early return). Clear
|
|
2656
|
+
// the pause FIRST so resetTimeout's guard lets this call re-arm the timer; later
|
|
2657
|
+
// SDK activity then resets it normally. Skipping this would leave hang-detection
|
|
2658
|
+
// disabled for the rest of the turn.
|
|
2659
|
+
permissionWaiting = false;
|
|
2660
|
+
resetTimeout('permission-resolved');
|
|
2492
2661
|
}
|
|
2493
2662
|
};
|
|
2494
2663
|
// Activity-based timeout: resets on every SDK callback event
|
|
@@ -2496,8 +2665,24 @@ async function handleChatSend(stream, data, abortController, lang) {
|
|
|
2496
2665
|
// Timeout value from preferences (with env var override), clamped to 5s–30min range
|
|
2497
2666
|
const rawTimeoutMs = effectivePrefs.chatTimeoutMs ?? config.chat.timeoutMs;
|
|
2498
2667
|
const timeoutMs = (rawTimeoutMs >= 5000 && rawTimeoutMs <= 1800000) ? rawTimeoutMs : 300000;
|
|
2668
|
+
// CLI mode has NO inactivity timeout. The activity-based timeout is an SDK-mode safeguard
|
|
2669
|
+
// against a silently-stalled query; in CLI mode it does more harm than good — a turn can be
|
|
2670
|
+
// legitimately quiet (deep thinking, a long tool run) yet the spinner stops repainting, so the
|
|
2671
|
+
// timer would abort healthy work. The genuine stuck case it used to (slowly, confusingly) catch
|
|
2672
|
+
// is now handled directly: the usage-limit notice is detected on the PTY and fails the turn
|
|
2673
|
+
// immediately (cliChatEngine.detectUsageLimit), and a crashed REPL surfaces via pty.onExit.
|
|
2674
|
+
// Anything else ends via end_turn or the user's Stop button. So CLI never arms the timer.
|
|
2675
|
+
const isCliEngine = engineMode === 'cli';
|
|
2499
2676
|
let lastResetSource = 'initial';
|
|
2500
2677
|
const resetTimeout = (source) => {
|
|
2678
|
+
if (isCliEngine)
|
|
2679
|
+
return; // CLI mode: no inactivity timeout (see above)
|
|
2680
|
+
// Story 35.1: while awaiting user input (permission / AskUserQuestion) the timer
|
|
2681
|
+
// is paused — ignore every activity-driven reset (onActivity, raw PTY frames,
|
|
2682
|
+
// compact/retry) so the wait stays indefinite. canUseTool clears the pause and
|
|
2683
|
+
// calls this once to re-arm when the user responds.
|
|
2684
|
+
if (permissionWaiting)
|
|
2685
|
+
return;
|
|
2501
2686
|
if (source)
|
|
2502
2687
|
lastResetSource = source;
|
|
2503
2688
|
if (timeoutId)
|
|
@@ -2508,6 +2693,71 @@ async function handleChatSend(stream, data, abortController, lang) {
|
|
|
2508
2693
|
}, timeoutMs);
|
|
2509
2694
|
};
|
|
2510
2695
|
resetTimeout('initial');
|
|
2696
|
+
// CLI screen-stall watchdog (soft, advisory — see cliScreenStallWatchdog). The reconstructed
|
|
2697
|
+
// screen changes ~1×/second while claude is healthy (spinner glyph + ticking clock), so a long
|
|
2698
|
+
// flat-line of NO change is a reliable "claude froze" signal — unlike the removed inactivity
|
|
2699
|
+
// timeout, which watched JSONL activity that legitimately pauses during thinking. We do NOT
|
|
2700
|
+
// auto-abort: emit a soft `cli:screen-stall` so the UI can offer a Stop. CLI mode only, driven by
|
|
2701
|
+
// the mirror frames (so it follows the mirror gate), clamped to 5s–10min (0 = off). Fires only on
|
|
2702
|
+
// a still-running, non-modal turn; disposed at turn end so a stale timer can't leak into the next.
|
|
2703
|
+
const rawStallMs = effectivePrefs.cliScreenStallMs ?? 20000;
|
|
2704
|
+
const screenStallMs = (engineMode !== 'cli' || rawStallMs <= 0)
|
|
2705
|
+
? 0
|
|
2706
|
+
: Math.min(Math.max(rawStallMs, 5000), 600000);
|
|
2707
|
+
stallWatchdog = createScreenStallWatchdog({
|
|
2708
|
+
stallMs: screenStallMs,
|
|
2709
|
+
isActive: () => stream.status === 'running' && !permissionWaiting,
|
|
2710
|
+
onStallChange: (stalled) => {
|
|
2711
|
+
// Mirror onto the session screen cache so a late-join / reconnect can resync this
|
|
2712
|
+
// transition-only signal (see session:join above). sessionId is set by the time a turn
|
|
2713
|
+
// streams, but it's typed optional at this scope — guard so the cache key is never empty.
|
|
2714
|
+
if (sessionId)
|
|
2715
|
+
setCliScreenStall(sessionId, stalled);
|
|
2716
|
+
emit('cli:screen-stall', { sessionId, stalled });
|
|
2717
|
+
},
|
|
2718
|
+
});
|
|
2719
|
+
// Story 32.7: forward the CLI engine's transient generation-progress counter
|
|
2720
|
+
// ("↓ N tokens · Ns" parsed from the spinner) to the browser. Live-only — the
|
|
2721
|
+
// client skips it on buffer replay (mirrors tool:progress). The SDK engine never
|
|
2722
|
+
// calls this (real token streaming makes it unnecessary). Defined once and passed
|
|
2723
|
+
// as the 6th arg to every sendMessageWithCallbacks below; the queue path omits it
|
|
2724
|
+
// (no live progress UI there — the same asymmetry as onRawMessage).
|
|
2725
|
+
const onGenerationProgress = (progress) => {
|
|
2726
|
+
emit('generation:progress', progress);
|
|
2727
|
+
};
|
|
2728
|
+
// Story 36.2: CLI pre-generation phase (launching/submitting/waiting/null). Same
|
|
2729
|
+
// transient/live-only semantics and the SAME gating as the progress counter below.
|
|
2730
|
+
const onPhase = (phase) => {
|
|
2731
|
+
emit('cli:phase', { phase });
|
|
2732
|
+
};
|
|
2733
|
+
// Story 37.8: full-screen mirror frame. The CLI engine serializes the headless emulator's
|
|
2734
|
+
// CURRENT screen (color intact) and paces it to ~100ms; forward each frame to the room so
|
|
2735
|
+
// the read-only mirror renders the actual claude screen. Self-contained (a whole screen,
|
|
2736
|
+
// not a delta), so the same frame restores a late-join / refresh / collapse-expand. The
|
|
2737
|
+
// session:join + cli:request-screen-frame paths reuse this same event from the cache.
|
|
2738
|
+
const onScreenFrame = (frame) => {
|
|
2739
|
+
stallWatchdog?.noteFrame(frame); // soft screen-stall watchdog (CLI mode; no-op when disabled)
|
|
2740
|
+
emit('cli:screen-frame', { sessionId, frame });
|
|
2741
|
+
};
|
|
2742
|
+
// Story 33.3: gate the progress counter on CLI mode + the user's preference (default
|
|
2743
|
+
// ON, Story 33.2). In SDK mode the engine ignores the callback anyway; passing
|
|
2744
|
+
// `undefined` makes the gate explicit so the 'cli' + pref-OFF case emits nothing.
|
|
2745
|
+
const cliProgress = shouldForwardCliProgress(engineMode, effectivePrefs.cliShowGenerationProgress);
|
|
2746
|
+
const generationProgressCb = cliProgress ? onGenerationProgress : undefined;
|
|
2747
|
+
const cliPhaseCb = cliProgress ? onPhase : undefined;
|
|
2748
|
+
// Mirror gate — a SEPARATE preference (default ON, opt-out), independent of the progress
|
|
2749
|
+
// gate. Forwarded unless the user explicitly turned the mirror off.
|
|
2750
|
+
const screenFrameCb = shouldForwardCliPtyMirror(engineMode, effectivePrefs.cliPtyMirror) ? onScreenFrame : undefined;
|
|
2751
|
+
// Story 37.8: mirror refresh interval (ms) — the user's preference, applied as the engine's
|
|
2752
|
+
// trailing-throttle interval. undefined → engine default (200ms). Harmless when the mirror is off.
|
|
2753
|
+
const mirrorThrottleMs = effectivePrefs.cliMirrorThrottleMs;
|
|
2754
|
+
const backgroundTracker = new BackgroundTaskTracker((state) => {
|
|
2755
|
+
emit('background:waiting', {
|
|
2756
|
+
sessionId: sessionIdRef.current ?? sessionId,
|
|
2757
|
+
waiting: state.waiting,
|
|
2758
|
+
pendingCount: state.pending,
|
|
2759
|
+
});
|
|
2760
|
+
});
|
|
2511
2761
|
// Build shared callbacks (common logic for browser & queue paths)
|
|
2512
2762
|
const { callbacks, sessionIdRef } = buildStreamCallbacks({
|
|
2513
2763
|
emit,
|
|
@@ -2518,6 +2768,11 @@ async function handleChatSend(stream, data, abortController, lang) {
|
|
|
2518
2768
|
rekeyStream: (sid) => rekeyStream(stream, sid),
|
|
2519
2769
|
broadcastStreamChange,
|
|
2520
2770
|
notificationService,
|
|
2771
|
+
// Story 31.3 — browser path resolves projectSlug from the working dir
|
|
2772
|
+
// (async; memoized by the recorder). append is read-only so the lazy
|
|
2773
|
+
// slug resolution cannot disrupt the stream.
|
|
2774
|
+
mcpRecorder: createMcpCallRecorder(() => projectService.findProjectByPath(workingDirectory).then((p) => p?.projectSlug)),
|
|
2775
|
+
backgroundTracker,
|
|
2521
2776
|
}, {
|
|
2522
2777
|
onCallbackActivity: (source) => resetTimeout(source),
|
|
2523
2778
|
onSessionIdResolved: (sid) => {
|
|
@@ -2611,9 +2866,28 @@ async function handleChatSend(stream, data, abortController, lang) {
|
|
|
2611
2866
|
origOnError?.(error);
|
|
2612
2867
|
};
|
|
2613
2868
|
}
|
|
2869
|
+
// Defense B: if the bundled engine doesn't recognize this native-1M model, its
|
|
2870
|
+
// 1M context silently falls back to ~200K (long sessions then compact early and
|
|
2871
|
+
// can break on resume). Warn once per model so the fallback isn't invisible.
|
|
2872
|
+
// Non-blocking: the binary scan must not delay the query.
|
|
2873
|
+
// Only warn when 1M is actually intended (Opus auto, or an explicit `[1m]`
|
|
2874
|
+
// opt-in). A bare Sonnet now runs at 200K by design — warning there would be
|
|
2875
|
+
// a false alarm about a window the user never asked for.
|
|
2876
|
+
const warnModel = model;
|
|
2877
|
+
if (warnModel && effectiveModelIs1M(warnModel) && !warnedMissing1M.has(warnModel)) {
|
|
2878
|
+
void modelMissingNative1MSupport(warnModel).then((missing) => {
|
|
2879
|
+
if (missing && !warnedMissing1M.has(warnModel)) {
|
|
2880
|
+
warnedMissing1M.add(warnModel);
|
|
2881
|
+
log.warn(`Bundled Claude Code engine does not recognize model "${warnModel}" — native 1M context will not apply (falls back to ~200K). Update @anthropic-ai/claude-agent-sdk.`);
|
|
2882
|
+
emit('system:info', { message: `⚠️ "${warnModel}" 1M context is not active — the bundled engine doesn't recognize this model yet, so it's limited to ~200K. Update @anthropic-ai/claude-agent-sdk to enable the full 1M window.` });
|
|
2883
|
+
}
|
|
2884
|
+
}).catch(() => { });
|
|
2885
|
+
}
|
|
2614
2886
|
try {
|
|
2615
2887
|
const sendResult = await chatService.sendMessageWithCallbacks(content, callbacks, chatOptions, canUseTool, (messageType) => {
|
|
2616
2888
|
resetTimeout(`raw:${messageType}`);
|
|
2889
|
+
}, generationProgressCb, cliPhaseCb, screenFrameCb, mirrorThrottleMs, backgroundTracker, (mode) => {
|
|
2890
|
+
emit('permission:mode-change', { mode });
|
|
2617
2891
|
});
|
|
2618
2892
|
// SDK may return "No conversation found" as an error result (not a thrown exception).
|
|
2619
2893
|
// Convert to a thrown error so the retry logic below can handle it.
|
|
@@ -2638,6 +2912,7 @@ async function handleChatSend(stream, data, abortController, lang) {
|
|
|
2638
2912
|
// /compact is a CLI-level command processed before context evaluation,
|
|
2639
2913
|
// so it works even when context is overflowing.
|
|
2640
2914
|
if (isResumeAttempt
|
|
2915
|
+
&& autoCompactEnabled
|
|
2641
2916
|
&& parsedError.code === SDKErrorCode.CONTEXT_OVERFLOW
|
|
2642
2917
|
&& !abortController.signal.aborted
|
|
2643
2918
|
&& !hasEmittedOutput) {
|
|
@@ -2650,12 +2925,16 @@ async function handleChatSend(stream, data, abortController, lang) {
|
|
|
2650
2925
|
// Run /compact to shrink context (preserves session file)
|
|
2651
2926
|
await chatService.sendMessageWithCallbacks('/compact', callbacks, chatOptions, canUseTool, (messageType) => {
|
|
2652
2927
|
resetTimeout(`compact:${messageType}`);
|
|
2928
|
+
}, generationProgressCb, cliPhaseCb, screenFrameCb, mirrorThrottleMs, backgroundTracker, (mode) => {
|
|
2929
|
+
emit('permission:mode-change', { mode });
|
|
2653
2930
|
});
|
|
2654
2931
|
// Retry the original message after compaction
|
|
2655
2932
|
log.info(`[AUTO-COMPACT] compaction done, retrying original message: sessionId=${sessionId}`);
|
|
2656
2933
|
resetTimeout('auto-compact-retry');
|
|
2657
2934
|
await chatService.sendMessageWithCallbacks(content, callbacks, chatOptions, canUseTool, (messageType) => {
|
|
2658
2935
|
resetTimeout(`retry:${messageType}`);
|
|
2936
|
+
}, generationProgressCb, cliPhaseCb, screenFrameCb, mirrorThrottleMs, backgroundTracker, (mode) => {
|
|
2937
|
+
emit('permission:mode-change', { mode });
|
|
2659
2938
|
});
|
|
2660
2939
|
// Resume failed for other unknown reasons — retry without resume (fresh session).
|
|
2661
2940
|
// Guards:
|
|
@@ -2694,6 +2973,8 @@ async function handleChatSend(stream, data, abortController, lang) {
|
|
|
2694
2973
|
resetTimeout('resume-retry');
|
|
2695
2974
|
await chatService.sendMessageWithCallbacks(content, callbacks, retryOptions, canUseTool, (messageType) => {
|
|
2696
2975
|
resetTimeout(`raw:${messageType}`);
|
|
2976
|
+
}, generationProgressCb, cliPhaseCb, screenFrameCb, mirrorThrottleMs, backgroundTracker, (mode) => {
|
|
2977
|
+
emit('permission:mode-change', { mode });
|
|
2697
2978
|
});
|
|
2698
2979
|
}
|
|
2699
2980
|
else {
|
|
@@ -2712,13 +2993,20 @@ async function handleChatSend(stream, data, abortController, lang) {
|
|
|
2712
2993
|
const sdkError = parseSDKError(error, lang);
|
|
2713
2994
|
log.info(`[CHAIN-DRAIN] handleChatSend catch: sessionId=${stream.sessionId}, aborted=${abortController.signal.aborted}, reason=${abortController.signal.reason}, error=${sdkError.message.slice(0, 120)}`);
|
|
2714
2995
|
if (sdkError instanceof AbortedError || abortController.signal.aborted) {
|
|
2715
|
-
|
|
2996
|
+
const abortReason = abortController.signal.reason;
|
|
2997
|
+
if (abortReason === 'user-abort' || abortReason === 'another-client') {
|
|
2716
2998
|
return false;
|
|
2717
2999
|
}
|
|
2718
|
-
|
|
2719
|
-
|
|
2720
|
-
|
|
2721
|
-
|
|
3000
|
+
// Only a genuine inactivity timeout (abortController.abort('timeout'), SDK mode) is a "timeout".
|
|
3001
|
+
// Any OTHER abort/interruption surfaces its real message instead of a misleading "응답 시간 초과"
|
|
3002
|
+
// — that catch-all previously masked e.g. a CLI boot-readiness failure across several debugging
|
|
3003
|
+
// cycles (the user saw "timeout" for what was actually a resume-classify abort).
|
|
3004
|
+
if (abortReason === 'timeout') {
|
|
3005
|
+
emit('error', { code: ERROR_CODES.TIMEOUT_ERROR, message: t('ws.error.timeout') });
|
|
3006
|
+
}
|
|
3007
|
+
else {
|
|
3008
|
+
emit('error', { code: ERROR_CODES.CHAT_ERROR, message: sdkError.message });
|
|
3009
|
+
}
|
|
2722
3010
|
return false;
|
|
2723
3011
|
}
|
|
2724
3012
|
emit('error', {
|
|
@@ -2731,6 +3019,10 @@ async function handleChatSend(stream, data, abortController, lang) {
|
|
|
2731
3019
|
if (timeoutId) {
|
|
2732
3020
|
clearTimeout(timeoutId);
|
|
2733
3021
|
}
|
|
3022
|
+
// Screen-stall watchdog teardown: clear the pending timer so a stale fire can't leak into the
|
|
3023
|
+
// NEXT turn (the stream object is reused and its status flips back to 'running'), and drop any
|
|
3024
|
+
// active stall so every viewer's "looks stuck?" affordance clears when the turn ends.
|
|
3025
|
+
stallWatchdog?.dispose();
|
|
2734
3026
|
// Delete phantom checkpoint files created by SDK file checkpointing.
|
|
2735
3027
|
// These are separate JSONL files (new UUIDs) containing only
|
|
2736
3028
|
// file-history-snapshot entries. The same snapshot data already exists
|