mstro-app 0.4.2 → 0.4.4
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/bin/mstro.js +119 -40
- package/dist/server/cli/headless/claude-invoker-process.d.ts +11 -0
- package/dist/server/cli/headless/claude-invoker-process.d.ts.map +1 -0
- package/dist/server/cli/headless/claude-invoker-process.js +140 -0
- package/dist/server/cli/headless/claude-invoker-process.js.map +1 -0
- package/dist/server/cli/headless/claude-invoker-stall.d.ts +40 -0
- package/dist/server/cli/headless/claude-invoker-stall.d.ts.map +1 -0
- package/dist/server/cli/headless/claude-invoker-stall.js +98 -0
- package/dist/server/cli/headless/claude-invoker-stall.js.map +1 -0
- package/dist/server/cli/headless/claude-invoker-stream.d.ts +44 -0
- package/dist/server/cli/headless/claude-invoker-stream.d.ts.map +1 -0
- package/dist/server/cli/headless/claude-invoker-stream.js +276 -0
- package/dist/server/cli/headless/claude-invoker-stream.js.map +1 -0
- package/dist/server/cli/headless/claude-invoker-tools.d.ts +21 -0
- package/dist/server/cli/headless/claude-invoker-tools.d.ts.map +1 -0
- package/dist/server/cli/headless/claude-invoker-tools.js +137 -0
- package/dist/server/cli/headless/claude-invoker-tools.js.map +1 -0
- package/dist/server/cli/headless/claude-invoker.d.ts +6 -4
- package/dist/server/cli/headless/claude-invoker.d.ts.map +1 -1
- package/dist/server/cli/headless/claude-invoker.js +10 -804
- package/dist/server/cli/headless/claude-invoker.js.map +1 -1
- package/dist/server/cli/headless/haiku-assessments.d.ts +62 -0
- package/dist/server/cli/headless/haiku-assessments.d.ts.map +1 -0
- package/dist/server/cli/headless/haiku-assessments.js +281 -0
- package/dist/server/cli/headless/haiku-assessments.js.map +1 -0
- package/dist/server/cli/headless/headless-logger.d.ts +3 -2
- package/dist/server/cli/headless/headless-logger.d.ts.map +1 -1
- package/dist/server/cli/headless/headless-logger.js +28 -5
- package/dist/server/cli/headless/headless-logger.js.map +1 -1
- package/dist/server/cli/headless/native-timeout-detector.d.ts +44 -0
- package/dist/server/cli/headless/native-timeout-detector.d.ts.map +1 -0
- package/dist/server/cli/headless/native-timeout-detector.js +99 -0
- package/dist/server/cli/headless/native-timeout-detector.js.map +1 -0
- package/dist/server/cli/headless/stall-assessor.d.ts +2 -110
- package/dist/server/cli/headless/stall-assessor.d.ts.map +1 -1
- package/dist/server/cli/headless/stall-assessor.js +65 -457
- package/dist/server/cli/headless/stall-assessor.js.map +1 -1
- package/dist/server/cli/headless/types.d.ts +4 -1
- package/dist/server/cli/headless/types.d.ts.map +1 -1
- package/dist/server/cli/improvisation-attachments.d.ts +21 -0
- package/dist/server/cli/improvisation-attachments.d.ts.map +1 -0
- package/dist/server/cli/improvisation-attachments.js +116 -0
- package/dist/server/cli/improvisation-attachments.js.map +1 -0
- package/dist/server/cli/improvisation-retry.d.ts +52 -0
- package/dist/server/cli/improvisation-retry.d.ts.map +1 -0
- package/dist/server/cli/improvisation-retry.js +434 -0
- package/dist/server/cli/improvisation-retry.js.map +1 -0
- package/dist/server/cli/improvisation-session-manager.d.ts +10 -266
- package/dist/server/cli/improvisation-session-manager.d.ts.map +1 -1
- package/dist/server/cli/improvisation-session-manager.js +117 -1079
- package/dist/server/cli/improvisation-session-manager.js.map +1 -1
- package/dist/server/cli/improvisation-types.d.ts +86 -0
- package/dist/server/cli/improvisation-types.d.ts.map +1 -0
- package/dist/server/cli/improvisation-types.js +10 -0
- package/dist/server/cli/improvisation-types.js.map +1 -0
- package/dist/server/cli/prompt-builders.d.ts +68 -0
- package/dist/server/cli/prompt-builders.d.ts.map +1 -0
- package/dist/server/cli/prompt-builders.js +312 -0
- package/dist/server/cli/prompt-builders.js.map +1 -0
- package/dist/server/index.js +33 -212
- package/dist/server/index.js.map +1 -1
- package/dist/server/mcp/bouncer-haiku.d.ts +10 -0
- package/dist/server/mcp/bouncer-haiku.d.ts.map +1 -0
- package/dist/server/mcp/bouncer-haiku.js +152 -0
- package/dist/server/mcp/bouncer-haiku.js.map +1 -0
- package/dist/server/mcp/bouncer-integration.d.ts +3 -4
- package/dist/server/mcp/bouncer-integration.d.ts.map +1 -1
- package/dist/server/mcp/bouncer-integration.js +50 -196
- package/dist/server/mcp/bouncer-integration.js.map +1 -1
- package/dist/server/mcp/security-analysis.d.ts +38 -0
- package/dist/server/mcp/security-analysis.d.ts.map +1 -0
- package/dist/server/mcp/security-analysis.js +183 -0
- package/dist/server/mcp/security-analysis.js.map +1 -0
- package/dist/server/mcp/security-audit.d.ts +1 -1
- package/dist/server/mcp/security-audit.d.ts.map +1 -1
- package/dist/server/mcp/security-patterns.d.ts +1 -25
- package/dist/server/mcp/security-patterns.d.ts.map +1 -1
- package/dist/server/mcp/security-patterns.js +55 -260
- package/dist/server/mcp/security-patterns.js.map +1 -1
- package/dist/server/server-setup.d.ts +22 -0
- package/dist/server/server-setup.d.ts.map +1 -0
- package/dist/server/server-setup.js +101 -0
- package/dist/server/server-setup.js.map +1 -0
- package/dist/server/services/file-explorer-ops.d.ts +24 -0
- package/dist/server/services/file-explorer-ops.d.ts.map +1 -0
- package/dist/server/services/file-explorer-ops.js +211 -0
- package/dist/server/services/file-explorer-ops.js.map +1 -0
- package/dist/server/services/files.d.ts +2 -85
- package/dist/server/services/files.d.ts.map +1 -1
- package/dist/server/services/files.js +7 -427
- package/dist/server/services/files.js.map +1 -1
- package/dist/server/services/plan/composer.d.ts +1 -1
- package/dist/server/services/plan/composer.d.ts.map +1 -1
- package/dist/server/services/plan/composer.js +118 -32
- package/dist/server/services/plan/composer.js.map +1 -1
- package/dist/server/services/plan/config-installer.d.ts +25 -0
- package/dist/server/services/plan/config-installer.d.ts.map +1 -0
- package/dist/server/services/plan/config-installer.js +182 -0
- package/dist/server/services/plan/config-installer.js.map +1 -0
- package/dist/server/services/plan/dependency-resolver.d.ts +1 -1
- package/dist/server/services/plan/dependency-resolver.d.ts.map +1 -1
- package/dist/server/services/plan/dependency-resolver.js +4 -1
- package/dist/server/services/plan/dependency-resolver.js.map +1 -1
- package/dist/server/services/plan/executor.d.ts +38 -74
- package/dist/server/services/plan/executor.d.ts.map +1 -1
- package/dist/server/services/plan/executor.js +274 -460
- package/dist/server/services/plan/executor.js.map +1 -1
- package/dist/server/services/plan/front-matter.d.ts +18 -0
- package/dist/server/services/plan/front-matter.d.ts.map +1 -0
- package/dist/server/services/plan/front-matter.js +44 -0
- package/dist/server/services/plan/front-matter.js.map +1 -0
- package/dist/server/services/plan/output-manager.d.ts +22 -0
- package/dist/server/services/plan/output-manager.d.ts.map +1 -0
- package/dist/server/services/plan/output-manager.js +97 -0
- package/dist/server/services/plan/output-manager.js.map +1 -0
- package/dist/server/services/plan/parser-core.d.ts +20 -0
- package/dist/server/services/plan/parser-core.d.ts.map +1 -0
- package/dist/server/services/plan/parser-core.js +350 -0
- package/dist/server/services/plan/parser-core.js.map +1 -0
- package/dist/server/services/plan/parser-migration.d.ts +5 -0
- package/dist/server/services/plan/parser-migration.d.ts.map +1 -0
- package/dist/server/services/plan/parser-migration.js +124 -0
- package/dist/server/services/plan/parser-migration.js.map +1 -0
- package/dist/server/services/plan/parser.d.ts +11 -3
- package/dist/server/services/plan/parser.d.ts.map +1 -1
- package/dist/server/services/plan/parser.js +184 -369
- package/dist/server/services/plan/parser.js.map +1 -1
- package/dist/server/services/plan/prompt-builder.d.ts +17 -0
- package/dist/server/services/plan/prompt-builder.d.ts.map +1 -0
- package/dist/server/services/plan/prompt-builder.js +137 -0
- package/dist/server/services/plan/prompt-builder.js.map +1 -0
- package/dist/server/services/plan/review-gate.d.ts +28 -0
- package/dist/server/services/plan/review-gate.d.ts.map +1 -0
- package/dist/server/services/plan/review-gate.js +191 -0
- package/dist/server/services/plan/review-gate.js.map +1 -0
- package/dist/server/services/plan/state-reconciler.d.ts +1 -1
- package/dist/server/services/plan/state-reconciler.d.ts.map +1 -1
- package/dist/server/services/plan/state-reconciler.js +59 -7
- package/dist/server/services/plan/state-reconciler.js.map +1 -1
- package/dist/server/services/plan/types.d.ts +68 -0
- package/dist/server/services/plan/types.d.ts.map +1 -1
- package/dist/server/services/platform-credentials.d.ts +24 -0
- package/dist/server/services/platform-credentials.d.ts.map +1 -0
- package/dist/server/services/platform-credentials.js +68 -0
- package/dist/server/services/platform-credentials.js.map +1 -0
- package/dist/server/services/platform.d.ts +1 -31
- package/dist/server/services/platform.d.ts.map +1 -1
- package/dist/server/services/platform.js +11 -109
- package/dist/server/services/platform.js.map +1 -1
- package/dist/server/services/terminal/pty-manager.d.ts +7 -97
- package/dist/server/services/terminal/pty-manager.d.ts.map +1 -1
- package/dist/server/services/terminal/pty-manager.js +53 -266
- package/dist/server/services/terminal/pty-manager.js.map +1 -1
- package/dist/server/services/terminal/pty-utils.d.ts +57 -0
- package/dist/server/services/terminal/pty-utils.d.ts.map +1 -0
- package/dist/server/services/terminal/pty-utils.js +141 -0
- package/dist/server/services/terminal/pty-utils.js.map +1 -0
- package/dist/server/services/websocket/file-definition-handlers.d.ts +4 -0
- package/dist/server/services/websocket/file-definition-handlers.d.ts.map +1 -0
- package/dist/server/services/websocket/file-definition-handlers.js +153 -0
- package/dist/server/services/websocket/file-definition-handlers.js.map +1 -0
- package/dist/server/services/websocket/file-explorer-handlers.d.ts.map +1 -1
- package/dist/server/services/websocket/file-explorer-handlers.js +52 -391
- package/dist/server/services/websocket/file-explorer-handlers.js.map +1 -1
- package/dist/server/services/websocket/file-search-handlers.d.ts +5 -0
- package/dist/server/services/websocket/file-search-handlers.d.ts.map +1 -0
- package/dist/server/services/websocket/file-search-handlers.js +238 -0
- package/dist/server/services/websocket/file-search-handlers.js.map +1 -0
- package/dist/server/services/websocket/file-utils.js +3 -3
- package/dist/server/services/websocket/file-utils.js.map +1 -1
- package/dist/server/services/websocket/git-branch-handlers.d.ts +7 -0
- package/dist/server/services/websocket/git-branch-handlers.d.ts.map +1 -0
- package/dist/server/services/websocket/git-branch-handlers.js +110 -0
- package/dist/server/services/websocket/git-branch-handlers.js.map +1 -0
- package/dist/server/services/websocket/git-diff-handlers.d.ts +6 -0
- package/dist/server/services/websocket/git-diff-handlers.d.ts.map +1 -0
- package/dist/server/services/websocket/git-diff-handlers.js +123 -0
- package/dist/server/services/websocket/git-diff-handlers.js.map +1 -0
- package/dist/server/services/websocket/git-handlers.d.ts +2 -31
- package/dist/server/services/websocket/git-handlers.d.ts.map +1 -1
- package/dist/server/services/websocket/git-handlers.js +35 -541
- package/dist/server/services/websocket/git-handlers.js.map +1 -1
- package/dist/server/services/websocket/git-log-handlers.d.ts +6 -0
- package/dist/server/services/websocket/git-log-handlers.d.ts.map +1 -0
- package/dist/server/services/websocket/git-log-handlers.js +128 -0
- package/dist/server/services/websocket/git-log-handlers.js.map +1 -0
- package/dist/server/services/websocket/git-pr-handlers.d.ts.map +1 -1
- package/dist/server/services/websocket/git-pr-handlers.js +13 -53
- package/dist/server/services/websocket/git-pr-handlers.js.map +1 -1
- package/dist/server/services/websocket/git-tag-handlers.d.ts +6 -0
- package/dist/server/services/websocket/git-tag-handlers.d.ts.map +1 -0
- package/dist/server/services/websocket/git-tag-handlers.js +76 -0
- package/dist/server/services/websocket/git-tag-handlers.js.map +1 -0
- package/dist/server/services/websocket/git-utils.d.ts +43 -0
- package/dist/server/services/websocket/git-utils.d.ts.map +1 -0
- package/dist/server/services/websocket/git-utils.js +201 -0
- package/dist/server/services/websocket/git-utils.js.map +1 -0
- package/dist/server/services/websocket/handler.d.ts +2 -0
- package/dist/server/services/websocket/handler.d.ts.map +1 -1
- package/dist/server/services/websocket/handler.js +37 -112
- package/dist/server/services/websocket/handler.js.map +1 -1
- package/dist/server/services/websocket/plan-board-handlers.d.ts +11 -0
- package/dist/server/services/websocket/plan-board-handlers.d.ts.map +1 -0
- package/dist/server/services/websocket/plan-board-handlers.js +218 -0
- package/dist/server/services/websocket/plan-board-handlers.js.map +1 -0
- package/dist/server/services/websocket/plan-execution-handlers.d.ts +9 -0
- package/dist/server/services/websocket/plan-execution-handlers.d.ts.map +1 -0
- package/dist/server/services/websocket/plan-execution-handlers.js +142 -0
- package/dist/server/services/websocket/plan-execution-handlers.js.map +1 -0
- package/dist/server/services/websocket/plan-handlers.d.ts +7 -2
- package/dist/server/services/websocket/plan-handlers.d.ts.map +1 -1
- package/dist/server/services/websocket/plan-handlers.js +21 -462
- package/dist/server/services/websocket/plan-handlers.js.map +1 -1
- package/dist/server/services/websocket/plan-helpers.d.ts +19 -0
- package/dist/server/services/websocket/plan-helpers.d.ts.map +1 -0
- package/dist/server/services/websocket/plan-helpers.js +199 -0
- package/dist/server/services/websocket/plan-helpers.js.map +1 -0
- package/dist/server/services/websocket/plan-issue-handlers.d.ts +12 -0
- package/dist/server/services/websocket/plan-issue-handlers.d.ts.map +1 -0
- package/dist/server/services/websocket/plan-issue-handlers.js +162 -0
- package/dist/server/services/websocket/plan-issue-handlers.js.map +1 -0
- package/dist/server/services/websocket/plan-sprint-handlers.d.ts +7 -0
- package/dist/server/services/websocket/plan-sprint-handlers.d.ts.map +1 -0
- package/dist/server/services/websocket/plan-sprint-handlers.js +206 -0
- package/dist/server/services/websocket/plan-sprint-handlers.js.map +1 -0
- package/dist/server/services/websocket/quality-complexity.d.ts +14 -0
- package/dist/server/services/websocket/quality-complexity.d.ts.map +1 -0
- package/dist/server/services/websocket/quality-complexity.js +262 -0
- package/dist/server/services/websocket/quality-complexity.js.map +1 -0
- package/dist/server/services/websocket/quality-fix-agent.d.ts +16 -0
- package/dist/server/services/websocket/quality-fix-agent.d.ts.map +1 -0
- package/dist/server/services/websocket/quality-fix-agent.js +140 -0
- package/dist/server/services/websocket/quality-fix-agent.js.map +1 -0
- package/dist/server/services/websocket/quality-handlers.d.ts.map +1 -1
- package/dist/server/services/websocket/quality-handlers.js +34 -346
- package/dist/server/services/websocket/quality-handlers.js.map +1 -1
- package/dist/server/services/websocket/quality-linting.d.ts +9 -0
- package/dist/server/services/websocket/quality-linting.d.ts.map +1 -0
- package/dist/server/services/websocket/quality-linting.js +178 -0
- package/dist/server/services/websocket/quality-linting.js.map +1 -0
- package/dist/server/services/websocket/quality-review-agent.d.ts +19 -0
- package/dist/server/services/websocket/quality-review-agent.d.ts.map +1 -0
- package/dist/server/services/websocket/quality-review-agent.js +206 -0
- package/dist/server/services/websocket/quality-review-agent.js.map +1 -0
- package/dist/server/services/websocket/quality-service.d.ts +3 -51
- package/dist/server/services/websocket/quality-service.d.ts.map +1 -1
- package/dist/server/services/websocket/quality-service.js +9 -651
- package/dist/server/services/websocket/quality-service.js.map +1 -1
- package/dist/server/services/websocket/quality-tools.d.ts +23 -0
- package/dist/server/services/websocket/quality-tools.d.ts.map +1 -0
- package/dist/server/services/websocket/quality-tools.js +208 -0
- package/dist/server/services/websocket/quality-tools.js.map +1 -0
- package/dist/server/services/websocket/quality-types.d.ts +59 -0
- package/dist/server/services/websocket/quality-types.d.ts.map +1 -0
- package/dist/server/services/websocket/quality-types.js +101 -0
- package/dist/server/services/websocket/quality-types.js.map +1 -0
- package/dist/server/services/websocket/session-handlers.d.ts +3 -4
- package/dist/server/services/websocket/session-handlers.d.ts.map +1 -1
- package/dist/server/services/websocket/session-handlers.js +3 -378
- package/dist/server/services/websocket/session-handlers.js.map +1 -1
- package/dist/server/services/websocket/session-history.d.ts +4 -0
- package/dist/server/services/websocket/session-history.d.ts.map +1 -0
- package/dist/server/services/websocket/session-history.js +208 -0
- package/dist/server/services/websocket/session-history.js.map +1 -0
- package/dist/server/services/websocket/session-initialization.d.ts +5 -0
- package/dist/server/services/websocket/session-initialization.d.ts.map +1 -0
- package/dist/server/services/websocket/session-initialization.js +163 -0
- package/dist/server/services/websocket/session-initialization.js.map +1 -0
- package/dist/server/services/websocket/types.d.ts +12 -2
- package/dist/server/services/websocket/types.d.ts.map +1 -1
- package/package.json +1 -2
- package/server/cli/headless/claude-invoker-process.ts +204 -0
- package/server/cli/headless/claude-invoker-stall.ts +164 -0
- package/server/cli/headless/claude-invoker-stream.ts +353 -0
- package/server/cli/headless/claude-invoker-tools.ts +187 -0
- package/server/cli/headless/claude-invoker.ts +15 -1092
- package/server/cli/headless/haiku-assessments.ts +365 -0
- package/server/cli/headless/headless-logger.ts +26 -5
- package/server/cli/headless/native-timeout-detector.ts +117 -0
- package/server/cli/headless/stall-assessor.ts +65 -618
- package/server/cli/headless/types.ts +4 -1
- package/server/cli/improvisation-attachments.ts +148 -0
- package/server/cli/improvisation-retry.ts +602 -0
- package/server/cli/improvisation-session-manager.ts +140 -1349
- package/server/cli/improvisation-types.ts +98 -0
- package/server/cli/prompt-builders.ts +370 -0
- package/server/index.ts +35 -246
- package/server/mcp/bouncer-haiku.ts +182 -0
- package/server/mcp/bouncer-integration.ts +87 -248
- package/server/mcp/security-analysis.ts +217 -0
- package/server/mcp/security-audit.ts +1 -1
- package/server/mcp/security-patterns.ts +60 -283
- package/server/server-setup.ts +114 -0
- package/server/services/file-explorer-ops.ts +293 -0
- package/server/services/files.ts +20 -532
- package/server/services/plan/composer.ts +140 -35
- package/server/services/plan/config-installer.ts +187 -0
- package/server/services/plan/dependency-resolver.ts +4 -1
- package/server/services/plan/executor.ts +281 -488
- package/server/services/plan/front-matter.ts +48 -0
- package/server/services/plan/output-manager.ts +113 -0
- package/server/services/plan/parser-core.ts +406 -0
- package/server/services/plan/parser-migration.ts +128 -0
- package/server/services/plan/parser.ts +188 -394
- package/server/services/plan/prompt-builder.ts +161 -0
- package/server/services/plan/review-gate.ts +212 -0
- package/server/services/plan/state-reconciler.ts +68 -7
- package/server/services/plan/types.ts +101 -1
- package/server/services/platform-credentials.ts +83 -0
- package/server/services/platform.ts +16 -131
- package/server/services/terminal/pty-manager.ts +66 -313
- package/server/services/terminal/pty-utils.ts +176 -0
- package/server/services/websocket/file-definition-handlers.ts +165 -0
- package/server/services/websocket/file-explorer-handlers.ts +37 -452
- package/server/services/websocket/file-search-handlers.ts +291 -0
- package/server/services/websocket/file-utils.ts +3 -3
- package/server/services/websocket/git-branch-handlers.ts +130 -0
- package/server/services/websocket/git-diff-handlers.ts +140 -0
- package/server/services/websocket/git-handlers.ts +40 -625
- package/server/services/websocket/git-log-handlers.ts +149 -0
- package/server/services/websocket/git-pr-handlers.ts +17 -62
- package/server/services/websocket/git-tag-handlers.ts +91 -0
- package/server/services/websocket/git-utils.ts +230 -0
- package/server/services/websocket/handler.ts +39 -112
- package/server/services/websocket/plan-board-handlers.ts +277 -0
- package/server/services/websocket/plan-execution-handlers.ts +184 -0
- package/server/services/websocket/plan-handlers.ts +23 -544
- package/server/services/websocket/plan-helpers.ts +215 -0
- package/server/services/websocket/plan-issue-handlers.ts +204 -0
- package/server/services/websocket/plan-sprint-handlers.ts +252 -0
- package/server/services/websocket/quality-complexity.ts +294 -0
- package/server/services/websocket/quality-fix-agent.ts +181 -0
- package/server/services/websocket/quality-handlers.ts +36 -404
- package/server/services/websocket/quality-linting.ts +187 -0
- package/server/services/websocket/quality-review-agent.ts +246 -0
- package/server/services/websocket/quality-service.ts +11 -762
- package/server/services/websocket/quality-tools.ts +209 -0
- package/server/services/websocket/quality-types.ts +169 -0
- package/server/services/websocket/session-handlers.ts +5 -437
- package/server/services/websocket/session-history.ts +222 -0
- package/server/services/websocket/session-initialization.ts +209 -0
- package/server/services/websocket/types.ts +46 -2
|
@@ -2,68 +2,48 @@
|
|
|
2
2
|
// Licensed under the MIT License. See LICENSE file for details.
|
|
3
3
|
|
|
4
4
|
/**
|
|
5
|
-
* Stall Assessor
|
|
5
|
+
* Stall Assessor — Two-layer stall detection (heuristic + Haiku AI).
|
|
6
6
|
*
|
|
7
|
-
* Provides Haiku-based intelligent assessment for:
|
|
8
|
-
* - Stall detection (is a silent process working or hung?)
|
|
9
|
-
* - Context loss detection (did Claude lose context after timeouts?)
|
|
10
|
-
* - Approval prompt classification (is a user message an approval or new task?)
|
|
11
|
-
* - Best result comparison (which retry attempt produced better work?)
|
|
12
|
-
* - Error classification (what kind of error is in stderr?)
|
|
13
|
-
*
|
|
14
|
-
* Stall detection uses a two-layer approach:
|
|
15
7
|
* 1. Fast heuristic: known long-running patterns get automatic extensions.
|
|
16
8
|
* 2. Haiku assessment: ambiguous cases get a quick AI evaluation.
|
|
9
|
+
*
|
|
10
|
+
* Other Haiku-based assessments (context loss, approval, premature completion,
|
|
11
|
+
* best result, error classification) live in haiku-assessments.ts.
|
|
17
12
|
*/
|
|
18
13
|
|
|
19
|
-
import {
|
|
14
|
+
import { spawnHaikuRaw } from './haiku-assessments.js';
|
|
20
15
|
import { hlog } from './headless-logger.js';
|
|
21
16
|
|
|
17
|
+
export type { ApprovalVerdict, BestResultContext, BestResultVerdict, ContextLossContext, ContextLossVerdict, ErrorClassification, PrematureCompletionContext, PrematureCompletionVerdict } from './haiku-assessments.js';
|
|
18
|
+
// Re-export assessment functions for backward compatibility
|
|
19
|
+
export { assessApproval, assessBestResult, assessContextLoss, assessPrematureCompletion, classifyError } from './haiku-assessments.js';
|
|
20
|
+
|
|
22
21
|
export interface StallContext {
|
|
23
|
-
/** The original user prompt being executed */
|
|
24
22
|
originalPrompt: string;
|
|
25
|
-
/** How long the process has been silent (ms) */
|
|
26
23
|
silenceMs: number;
|
|
27
|
-
/** Name of the last tool that started executing */
|
|
28
24
|
lastToolName?: string;
|
|
29
|
-
/** Summarized input of the last tool call */
|
|
30
25
|
lastToolInputSummary?: string;
|
|
31
|
-
/** Number of tool calls started but not yet returned */
|
|
32
26
|
pendingToolCount: number;
|
|
33
|
-
/** Names of all currently pending tools (toolId -> toolName) */
|
|
34
27
|
pendingToolNames?: Set<string>;
|
|
35
|
-
/** Total tool calls made so far this session */
|
|
36
28
|
totalToolCalls: number;
|
|
37
|
-
/** Total wall-clock time since process started (ms) */
|
|
38
29
|
elapsedTotalMs: number;
|
|
39
|
-
/** Time since the last token usage event (ms). Undefined if no token events yet. */
|
|
40
30
|
tokenSilenceMs?: number;
|
|
41
31
|
}
|
|
42
32
|
|
|
43
33
|
export interface StallVerdict {
|
|
44
|
-
/** Whether to extend the deadline or proceed with kill */
|
|
45
34
|
action: 'extend' | 'kill';
|
|
46
|
-
/** Additional time to grant (ms), only meaningful when action is 'extend' */
|
|
47
35
|
extensionMs: number;
|
|
48
|
-
/** Human-readable reason for the verdict */
|
|
49
36
|
reason: string;
|
|
50
37
|
}
|
|
51
38
|
|
|
52
|
-
|
|
39
|
+
// ========== Fast Heuristic ==========
|
|
40
|
+
|
|
53
41
|
function hasSubagentPending(pendingNames: Set<string>, lastToolName: string | undefined, hasPendingTools: boolean): boolean {
|
|
54
42
|
return pendingNames.has('Task') || pendingNames.has('Agent')
|
|
55
43
|
|| ((lastToolName === 'Task' || lastToolName === 'Agent') && hasPendingTools);
|
|
56
44
|
}
|
|
57
45
|
|
|
58
|
-
/**
|
|
59
|
-
* Check if an Agent Teams lead is idle-waiting for teammate notifications.
|
|
60
|
-
* After spawning teammates (Agent tool calls complete), the lead has no pending
|
|
61
|
-
* tools but is legitimately waiting for teammate idle events.
|
|
62
|
-
*/
|
|
63
46
|
function checkAgentTeamsWaiting(ctx: StallContext, hasPendingTools: boolean): StallVerdict | null {
|
|
64
|
-
// The lead may use any tool while waiting (Glob to verify outputs, Bash to
|
|
65
|
-
// check disk, ToolSearch, etc.), so don't gate on lastToolName. The key
|
|
66
|
-
// signal is: prompt contains team_name, tools were called, nothing pending.
|
|
67
47
|
if (
|
|
68
48
|
!hasPendingTools &&
|
|
69
49
|
ctx.totalToolCalls > 0 &&
|
|
@@ -78,20 +58,10 @@ function checkAgentTeamsWaiting(ctx: StallContext, hasPendingTools: boolean): St
|
|
|
78
58
|
return null;
|
|
79
59
|
}
|
|
80
60
|
|
|
81
|
-
/**
|
|
82
|
-
* Fast heuristic for known long-running patterns.
|
|
83
|
-
* Returns a verdict immediately if the pattern is recognized, null otherwise.
|
|
84
|
-
* When toolWatchdogActive is true, defers entirely to the watchdog for any
|
|
85
|
-
* pending tool calls — the watchdog has per-tool adaptive timeouts that are
|
|
86
|
-
* more precise than the stall detector's silence-based approach.
|
|
87
|
-
*/
|
|
88
61
|
function quickHeuristic(ctx: StallContext, toolWatchdogActive = false): StallVerdict | null {
|
|
89
62
|
const pendingNames = ctx.pendingToolNames ?? new Set<string>();
|
|
90
63
|
const hasPendingTools = ctx.pendingToolCount > 0;
|
|
91
64
|
|
|
92
|
-
// Tokens still flowing = process is alive and actively processing.
|
|
93
|
-
// Extend generously when token activity is recent (< 60s), regardless
|
|
94
|
-
// of stdout silence. This covers silent thinking and tool result processing.
|
|
95
65
|
if (ctx.tokenSilenceMs !== undefined && ctx.tokenSilenceMs < 60_000) {
|
|
96
66
|
return {
|
|
97
67
|
action: 'extend',
|
|
@@ -100,9 +70,6 @@ function quickHeuristic(ctx: StallContext, toolWatchdogActive = false): StallVer
|
|
|
100
70
|
};
|
|
101
71
|
}
|
|
102
72
|
|
|
103
|
-
// When the watchdog is active and tools are pending, always defer.
|
|
104
|
-
// The watchdog manages per-tool timeouts; the stall detector should only
|
|
105
|
-
// fire when no tools are running and there's genuine silence.
|
|
106
73
|
if (toolWatchdogActive && hasPendingTools) {
|
|
107
74
|
const toolList = pendingNames.size > 0
|
|
108
75
|
? Array.from(pendingNames).join(', ')
|
|
@@ -114,8 +81,6 @@ function quickHeuristic(ctx: StallContext, toolWatchdogActive = false): StallVer
|
|
|
114
81
|
};
|
|
115
82
|
}
|
|
116
83
|
|
|
117
|
-
// Task/subagent launches are known to produce long silence periods.
|
|
118
|
-
// The parent Claude process emits nothing while waiting for subagent results.
|
|
119
84
|
if (hasSubagentPending(pendingNames, ctx.lastToolName, hasPendingTools)) {
|
|
120
85
|
const extensionMin = Math.min(30, 10 + ctx.pendingToolCount * 5);
|
|
121
86
|
return {
|
|
@@ -125,11 +90,9 @@ function quickHeuristic(ctx: StallContext, toolWatchdogActive = false): StallVer
|
|
|
125
90
|
};
|
|
126
91
|
}
|
|
127
92
|
|
|
128
|
-
// Agent Teams lead waiting for teammate idle notifications (extracted for complexity)
|
|
129
93
|
const agentTeamsVerdict = checkAgentTeamsWaiting(ctx, hasPendingTools);
|
|
130
94
|
if (agentTeamsVerdict) return agentTeamsVerdict;
|
|
131
95
|
|
|
132
|
-
// Multiple parallel tool calls (e.g., parallel Bash, parallel Read/Grep)
|
|
133
96
|
if (ctx.pendingToolCount >= 3) {
|
|
134
97
|
return {
|
|
135
98
|
action: 'extend',
|
|
@@ -138,11 +101,7 @@ function quickHeuristic(ctx: StallContext, toolWatchdogActive = false): StallVer
|
|
|
138
101
|
};
|
|
139
102
|
}
|
|
140
103
|
|
|
141
|
-
|
|
142
|
-
if (
|
|
143
|
-
!toolWatchdogActive &&
|
|
144
|
-
(ctx.lastToolName === 'WebSearch' || ctx.lastToolName === 'WebFetch')
|
|
145
|
-
) {
|
|
104
|
+
if (!toolWatchdogActive && (ctx.lastToolName === 'WebSearch' || ctx.lastToolName === 'WebFetch')) {
|
|
146
105
|
return {
|
|
147
106
|
action: 'extend',
|
|
148
107
|
extensionMs: 5 * 60_000,
|
|
@@ -153,211 +112,14 @@ function quickHeuristic(ctx: StallContext, toolWatchdogActive = false): StallVer
|
|
|
153
112
|
return null;
|
|
154
113
|
}
|
|
155
114
|
|
|
156
|
-
|
|
157
|
-
* Main assessment entry point. Tries the fast heuristic first,
|
|
158
|
-
* falls back to a Haiku model call for ambiguous cases.
|
|
159
|
-
*/
|
|
160
|
-
export async function assessStall(
|
|
161
|
-
ctx: StallContext,
|
|
162
|
-
claudeCommand: string,
|
|
163
|
-
verbose: boolean,
|
|
164
|
-
toolWatchdogActive = false,
|
|
165
|
-
): Promise<StallVerdict> {
|
|
166
|
-
// Layer 1: fast heuristic
|
|
167
|
-
const quick = quickHeuristic(ctx, toolWatchdogActive);
|
|
168
|
-
if (quick) {
|
|
169
|
-
if (verbose) {
|
|
170
|
-
hlog(`[STALL-ASSESS] Heuristic verdict: ${quick.reason}`);
|
|
171
|
-
}
|
|
172
|
-
return quick;
|
|
173
|
-
}
|
|
174
|
-
|
|
175
|
-
// Layer 2: Haiku assessment
|
|
176
|
-
try {
|
|
177
|
-
if (verbose) {
|
|
178
|
-
hlog('[STALL-ASSESS] Running Haiku assessment...');
|
|
179
|
-
}
|
|
180
|
-
return await runHaikuAssessment(ctx, claudeCommand, verbose);
|
|
181
|
-
} catch (err) {
|
|
182
|
-
if (verbose) {
|
|
183
|
-
hlog(`[STALL-ASSESS] Haiku assessment failed: ${err}`);
|
|
184
|
-
}
|
|
185
|
-
// If Haiku fails (timeout, auth issue, etc.), extend cautiously
|
|
186
|
-
return {
|
|
187
|
-
action: 'extend',
|
|
188
|
-
extensionMs: 10 * 60_000,
|
|
189
|
-
reason: 'Stall assessment unavailable — extending 10 min as precaution',
|
|
190
|
-
};
|
|
191
|
-
}
|
|
192
|
-
}
|
|
193
|
-
|
|
194
|
-
/**
|
|
195
|
-
* Assess a specific tool timeout using Haiku.
|
|
196
|
-
* Used by ToolWatchdog as a tiebreaker before killing a tool.
|
|
197
|
-
*/
|
|
198
|
-
export async function assessToolTimeout(
|
|
199
|
-
toolName: string,
|
|
200
|
-
toolInput: Record<string, unknown>,
|
|
201
|
-
elapsedMs: number,
|
|
202
|
-
claudeCommand: string,
|
|
203
|
-
verbose: boolean,
|
|
204
|
-
tokenSilenceMs?: number,
|
|
205
|
-
): Promise<StallVerdict> {
|
|
206
|
-
const elapsedSec = Math.round(elapsedMs / 1000);
|
|
207
|
-
|
|
208
|
-
// Summarize what the tool is doing
|
|
209
|
-
let inputSummary = '';
|
|
210
|
-
if (toolInput.url) {
|
|
211
|
-
inputSummary = `URL: ${String(toolInput.url).slice(0, 200)}`;
|
|
212
|
-
} else if (toolInput.query) {
|
|
213
|
-
inputSummary = `Query: ${String(toolInput.query).slice(0, 200)}`;
|
|
214
|
-
} else if (toolInput.command) {
|
|
215
|
-
inputSummary = `Command: ${String(toolInput.command).slice(0, 200)}`;
|
|
216
|
-
} else if (toolInput.prompt) {
|
|
217
|
-
inputSummary = `Prompt: ${String(toolInput.prompt).slice(0, 200)}`;
|
|
218
|
-
} else {
|
|
219
|
-
inputSummary = JSON.stringify(toolInput).slice(0, 200);
|
|
220
|
-
}
|
|
221
|
-
|
|
222
|
-
const toolDescriptions: Record<string, string> = {
|
|
223
|
-
WebFetch: 'fetches a URL, converts HTML to markdown, and runs a Haiku summarization pass',
|
|
224
|
-
WebSearch: 'performs a web search and returns results',
|
|
225
|
-
Task: 'spawns a subagent that runs autonomously with its own tools',
|
|
226
|
-
Agent: 'spawns a subagent that runs autonomously with its own tools',
|
|
227
|
-
Bash: 'executes a shell command',
|
|
228
|
-
};
|
|
229
|
-
const toolDesc = toolDescriptions[toolName] || `executes the ${toolName} tool`;
|
|
230
|
-
|
|
231
|
-
const tokenLine = tokenSilenceMs !== undefined
|
|
232
|
-
? `Token activity: last token event ${Math.round(tokenSilenceMs / 1000)}s ago (recent tokens = process is alive and processing)`
|
|
233
|
-
: 'Token activity: no token events observed';
|
|
234
|
-
|
|
235
|
-
const prompt = [
|
|
236
|
-
`You are a process health monitor. A ${toolName} tool call has been running for ${elapsedSec}s.`,
|
|
237
|
-
`${toolName} ${toolDesc}.`,
|
|
238
|
-
`Tool input: ${inputSummary}`,
|
|
239
|
-
tokenLine,
|
|
240
|
-
'',
|
|
241
|
-
`Is this tool call likely still working, or is it hung/frozen?`,
|
|
242
|
-
'Consider: network latency, server response times, anti-bot protections, large page sizes, complex operations.',
|
|
243
|
-
'IMPORTANT: If tokens were active recently (< 60s ago), the process is likely still alive and processing — strongly favor WORKING.',
|
|
244
|
-
'',
|
|
245
|
-
'Respond in EXACTLY this format (3 lines, no extra text):',
|
|
246
|
-
'VERDICT: WORKING or STALLED',
|
|
247
|
-
'MINUTES: <number 1-10, only if WORKING, how many more minutes to allow>',
|
|
248
|
-
'REASON: <brief one-line explanation>',
|
|
249
|
-
].join('\n');
|
|
250
|
-
|
|
251
|
-
try {
|
|
252
|
-
if (verbose) {
|
|
253
|
-
hlog(`[TOOL-ASSESS] Running Haiku assessment for ${toolName} (${elapsedSec}s elapsed)...`);
|
|
254
|
-
}
|
|
255
|
-
|
|
256
|
-
return await spawnHaikuVerdict(prompt, claudeCommand, verbose, 'TOOL-ASSESS');
|
|
257
|
-
} catch (err) {
|
|
258
|
-
if (verbose) {
|
|
259
|
-
hlog(`[TOOL-ASSESS] Haiku assessment failed: ${err}`);
|
|
260
|
-
}
|
|
261
|
-
// On failure, default to kill (the tool has already exceeded its timeout)
|
|
262
|
-
return {
|
|
263
|
-
action: 'kill',
|
|
264
|
-
extensionMs: 0,
|
|
265
|
-
reason: `Tool timeout assessment failed: ${err}`,
|
|
266
|
-
};
|
|
267
|
-
}
|
|
268
|
-
}
|
|
269
|
-
|
|
270
|
-
// ========== Context Loss Assessment ==========
|
|
271
|
-
|
|
272
|
-
export interface ContextLossVerdict {
|
|
273
|
-
/** Whether the agent lost context and needs recovery */
|
|
274
|
-
contextLost: boolean;
|
|
275
|
-
/** Human-readable reason for the verdict */
|
|
276
|
-
reason: string;
|
|
277
|
-
}
|
|
278
|
-
|
|
279
|
-
/** Enriched context for Haiku-based context loss assessment */
|
|
280
|
-
export interface ContextLossContext {
|
|
281
|
-
assistantResponse: string;
|
|
282
|
-
effectiveTimeouts: number;
|
|
283
|
-
nativeTimeoutCount: number;
|
|
284
|
-
successfulToolCalls: number;
|
|
285
|
-
thinkingOutputLength: number;
|
|
286
|
-
hasSuccessfulWrite: boolean;
|
|
287
|
-
}
|
|
288
|
-
|
|
289
|
-
/**
|
|
290
|
-
* Assess whether a Claude Code session lost context after tool timeouts.
|
|
291
|
-
* Uses Haiku with enriched context signals — replaces brittle hardcoded
|
|
292
|
-
* thresholds (200 chars thinking, 2x ratio, 500 chars response) with
|
|
293
|
-
* a single LLM call that sees the full picture.
|
|
294
|
-
*
|
|
295
|
-
* Only call this when effectiveTimeouts > 0.
|
|
296
|
-
*/
|
|
297
|
-
export async function assessContextLoss(
|
|
298
|
-
ctx: ContextLossContext,
|
|
299
|
-
claudeCommand: string,
|
|
300
|
-
verbose: boolean,
|
|
301
|
-
): Promise<ContextLossVerdict> {
|
|
302
|
-
const tail = ctx.assistantResponse.slice(-800);
|
|
303
|
-
|
|
304
|
-
const prompt = [
|
|
305
|
-
'You are analyzing a Claude Code agent session that experienced tool timeouts.',
|
|
306
|
-
'Determine whether the agent lost context (needs recovery) or is still productively working.',
|
|
307
|
-
'',
|
|
308
|
-
'Session signals:',
|
|
309
|
-
`- ${ctx.effectiveTimeouts} tools timed out (${ctx.nativeTimeoutCount} detected in text stream, ${ctx.effectiveTimeouts - ctx.nativeTimeoutCount} detected structurally)`,
|
|
310
|
-
`- ${ctx.successfulToolCalls} tools completed successfully`,
|
|
311
|
-
`- Thinking output: ${ctx.thinkingOutputLength} characters`,
|
|
312
|
-
`- Response length: ${ctx.assistantResponse.length} characters`,
|
|
313
|
-
`- Successful file writes (Edit/Write/MultiEdit): ${ctx.hasSuccessfulWrite ? 'YES' : 'NO'}`,
|
|
314
|
-
'',
|
|
315
|
-
`Final response (last ${tail.length} chars):`,
|
|
316
|
-
tail,
|
|
317
|
-
'',
|
|
318
|
-
'WORKING signals: continued tool calls after timeouts, substantial thinking about the task, producing code/analysis, writing files, referencing the original task.',
|
|
319
|
-
'STALLED signals: asking "how can I help?", starting fresh, offering generic help, not referencing the original task, very short response with no substance, task abandoned mid-research.',
|
|
320
|
-
'',
|
|
321
|
-
'Respond in EXACTLY this format (2 lines, no extra text):',
|
|
322
|
-
'VERDICT: WORKING or STALLED',
|
|
323
|
-
'REASON: <brief one-line explanation>',
|
|
324
|
-
].join('\n');
|
|
325
|
-
|
|
326
|
-
try {
|
|
327
|
-
if (verbose) {
|
|
328
|
-
hlog(`[CONTEXT-ASSESS] Running Haiku assessment (${ctx.effectiveTimeouts} timeouts, ${ctx.successfulToolCalls} successes, ${ctx.thinkingOutputLength} thinking chars)...`);
|
|
329
|
-
}
|
|
330
|
-
|
|
331
|
-
const raw = await spawnHaikuRaw(prompt, claudeCommand, verbose, 'CONTEXT-ASSESS');
|
|
332
|
-
const parsed = parseVerdictResponse(raw);
|
|
333
|
-
const contextLost = parsed.verdict === 'STALLED';
|
|
334
|
-
|
|
335
|
-
if (verbose) {
|
|
336
|
-
hlog(`[CONTEXT-ASSESS] Verdict: ${contextLost ? 'LOST' : 'CONTINUED'} — ${parsed.reason}`);
|
|
337
|
-
}
|
|
338
|
-
|
|
339
|
-
return { contextLost, reason: parsed.reason };
|
|
340
|
-
} catch (err) {
|
|
341
|
-
if (verbose) {
|
|
342
|
-
hlog(`[CONTEXT-ASSESS] Haiku assessment failed: ${err}`);
|
|
343
|
-
}
|
|
344
|
-
// On failure, assume context was lost (safer to retry than to show a confused response)
|
|
345
|
-
return {
|
|
346
|
-
contextLost: true,
|
|
347
|
-
reason: `Context loss assessment failed: ${err}`,
|
|
348
|
-
};
|
|
349
|
-
}
|
|
350
|
-
}
|
|
115
|
+
// ========== Haiku Stall Assessment ==========
|
|
351
116
|
|
|
352
117
|
function buildAssessmentPrompt(ctx: StallContext): string {
|
|
353
118
|
const silenceMin = Math.round(ctx.silenceMs / 60_000);
|
|
354
119
|
const totalMin = Math.round(ctx.elapsedTotalMs / 60_000);
|
|
355
|
-
|
|
356
|
-
// Truncate prompt to avoid huge payloads
|
|
357
120
|
const promptPreview = ctx.originalPrompt.length > 500
|
|
358
121
|
? `${ctx.originalPrompt.slice(0, 500)}...`
|
|
359
122
|
: ctx.originalPrompt;
|
|
360
|
-
|
|
361
123
|
const tokenLine = ctx.tokenSilenceMs !== undefined
|
|
362
124
|
? `Token activity: last token event ${Math.round(ctx.tokenSilenceMs / 1000)}s ago (tokens flowing = process alive)`
|
|
363
125
|
: 'Token activity: no token events observed';
|
|
@@ -402,102 +164,11 @@ function parseAssessmentResponse(output: string): StallVerdict {
|
|
|
402
164
|
}
|
|
403
165
|
|
|
404
166
|
if (verdict.includes('WORKING')) {
|
|
405
|
-
return {
|
|
406
|
-
action: 'extend',
|
|
407
|
-
extensionMs: minutes * 60_000,
|
|
408
|
-
reason,
|
|
409
|
-
};
|
|
167
|
+
return { action: 'extend', extensionMs: minutes * 60_000, reason };
|
|
410
168
|
}
|
|
411
|
-
|
|
412
|
-
return {
|
|
413
|
-
action: 'kill',
|
|
414
|
-
extensionMs: 0,
|
|
415
|
-
reason,
|
|
416
|
-
};
|
|
169
|
+
return { action: 'kill', extensionMs: 0, reason };
|
|
417
170
|
}
|
|
418
171
|
|
|
419
|
-
const HAIKU_TIMEOUT_MS = 30_000;
|
|
420
|
-
|
|
421
|
-
/** Low-level Haiku spawner: runs a prompt through `claude --print --model haiku` and returns raw text */
|
|
422
|
-
function spawnHaikuRaw(
|
|
423
|
-
prompt: string,
|
|
424
|
-
claudeCommand: string,
|
|
425
|
-
verbose: boolean,
|
|
426
|
-
label: string,
|
|
427
|
-
): Promise<string> {
|
|
428
|
-
return new Promise((resolve, reject) => {
|
|
429
|
-
let stdout = '';
|
|
430
|
-
let settled = false;
|
|
431
|
-
|
|
432
|
-
const proc: ChildProcess = spawn(
|
|
433
|
-
claudeCommand,
|
|
434
|
-
['--print', '--model', 'haiku', prompt],
|
|
435
|
-
{ stdio: ['ignore', 'pipe', 'pipe'] },
|
|
436
|
-
);
|
|
437
|
-
|
|
438
|
-
const timer = setTimeout(() => {
|
|
439
|
-
if (!settled) {
|
|
440
|
-
settled = true;
|
|
441
|
-
proc.kill('SIGTERM');
|
|
442
|
-
reject(new Error('Haiku assessment timed out'));
|
|
443
|
-
}
|
|
444
|
-
}, HAIKU_TIMEOUT_MS);
|
|
445
|
-
|
|
446
|
-
proc.stdout!.on('data', (data) => {
|
|
447
|
-
stdout += data.toString();
|
|
448
|
-
});
|
|
449
|
-
|
|
450
|
-
proc.stderr!.on('data', (data) => {
|
|
451
|
-
if (verbose) {
|
|
452
|
-
hlog(`[${label}] haiku stderr: ${data.toString().trim()}`);
|
|
453
|
-
}
|
|
454
|
-
});
|
|
455
|
-
|
|
456
|
-
proc.on('close', (code) => {
|
|
457
|
-
clearTimeout(timer);
|
|
458
|
-
if (settled) return;
|
|
459
|
-
settled = true;
|
|
460
|
-
|
|
461
|
-
if (code !== 0 || !stdout.trim()) {
|
|
462
|
-
reject(new Error(`Haiku exited with code ${code}, output: ${stdout.trim()}`));
|
|
463
|
-
return;
|
|
464
|
-
}
|
|
465
|
-
|
|
466
|
-
if (verbose) {
|
|
467
|
-
hlog(`[${label}] Haiku response: ${stdout.trim()}`);
|
|
468
|
-
}
|
|
469
|
-
|
|
470
|
-
resolve(stdout.trim());
|
|
471
|
-
});
|
|
472
|
-
|
|
473
|
-
proc.on('error', (err) => {
|
|
474
|
-
clearTimeout(timer);
|
|
475
|
-
if (settled) return;
|
|
476
|
-
settled = true;
|
|
477
|
-
reject(err);
|
|
478
|
-
});
|
|
479
|
-
});
|
|
480
|
-
}
|
|
481
|
-
|
|
482
|
-
/** Parse VERDICT/REASON format from Haiku response */
|
|
483
|
-
function parseVerdictResponse(raw: string): { verdict: string; reason: string } {
|
|
484
|
-
const lines = raw.split('\n');
|
|
485
|
-
let verdict = 'STALLED';
|
|
486
|
-
let reason = 'Assessment inconclusive';
|
|
487
|
-
|
|
488
|
-
for (const line of lines) {
|
|
489
|
-
const trimmed = line.trim();
|
|
490
|
-
if (trimmed.startsWith('VERDICT:')) {
|
|
491
|
-
verdict = trimmed.slice('VERDICT:'.length).trim().toUpperCase();
|
|
492
|
-
} else if (trimmed.startsWith('REASON:')) {
|
|
493
|
-
reason = trimmed.slice('REASON:'.length).trim();
|
|
494
|
-
}
|
|
495
|
-
}
|
|
496
|
-
|
|
497
|
-
return { verdict, reason };
|
|
498
|
-
}
|
|
499
|
-
|
|
500
|
-
/** Haiku spawner that returns a parsed StallVerdict (for stall assessment) */
|
|
501
172
|
async function spawnHaikuVerdict(
|
|
502
173
|
prompt: string,
|
|
503
174
|
claudeCommand: string,
|
|
@@ -508,304 +179,80 @@ async function spawnHaikuVerdict(
|
|
|
508
179
|
return parseAssessmentResponse(raw);
|
|
509
180
|
}
|
|
510
181
|
|
|
511
|
-
|
|
182
|
+
// ========== Public API ==========
|
|
183
|
+
|
|
184
|
+
export async function assessStall(
|
|
512
185
|
ctx: StallContext,
|
|
513
186
|
claudeCommand: string,
|
|
514
187
|
verbose: boolean,
|
|
188
|
+
toolWatchdogActive = false,
|
|
515
189
|
): Promise<StallVerdict> {
|
|
516
|
-
|
|
517
|
-
|
|
518
|
-
|
|
519
|
-
|
|
520
|
-
|
|
521
|
-
export interface ApprovalVerdict {
|
|
522
|
-
isApproval: boolean;
|
|
523
|
-
reason: string;
|
|
524
|
-
}
|
|
525
|
-
|
|
526
|
-
/**
|
|
527
|
-
* Assess whether a user message is an approval/continuation or a new task.
|
|
528
|
-
* Uses Haiku to classify intent — handles natural language variations that
|
|
529
|
-
* regex patterns miss ("sounds good", "yep do it", "option 2", etc.).
|
|
530
|
-
*/
|
|
531
|
-
export async function assessApproval(
|
|
532
|
-
userMessage: string,
|
|
533
|
-
claudeCommand: string,
|
|
534
|
-
verbose: boolean,
|
|
535
|
-
): Promise<ApprovalVerdict> {
|
|
536
|
-
const prompt = [
|
|
537
|
-
'You are classifying a user message in a multi-turn conversation with a coding assistant.',
|
|
538
|
-
'The assistant previously proposed a plan or asked a question, and the user is now responding.',
|
|
539
|
-
'',
|
|
540
|
-
`User's message: "${userMessage}"`,
|
|
541
|
-
'',
|
|
542
|
-
'Is this an approval/continuation (user agrees, says yes, wants to proceed) or a new task/question?',
|
|
543
|
-
'',
|
|
544
|
-
'APPROVAL signs: "yes", "sure", "go ahead", "sounds good", "do it", "yep", "option 2", "the first one", "proceed", references to previous proposal, short affirmative with modifications ("yes but use TypeScript").',
|
|
545
|
-
'NEW_TASK signs: asks a different question, gives new detailed instructions, changes topic, provides new requirements unrelated to any proposal.',
|
|
546
|
-
'',
|
|
547
|
-
'Respond in EXACTLY this format (2 lines, no extra text):',
|
|
548
|
-
'VERDICT: APPROVAL or NEW_TASK',
|
|
549
|
-
'REASON: <brief one-line explanation>',
|
|
550
|
-
].join('\n');
|
|
190
|
+
const quick = quickHeuristic(ctx, toolWatchdogActive);
|
|
191
|
+
if (quick) {
|
|
192
|
+
if (verbose) hlog(`[STALL-ASSESS] Heuristic verdict: ${quick.reason}`);
|
|
193
|
+
return quick;
|
|
194
|
+
}
|
|
551
195
|
|
|
552
196
|
try {
|
|
553
|
-
if (verbose)
|
|
554
|
-
|
|
555
|
-
}
|
|
556
|
-
|
|
557
|
-
const raw = await spawnHaikuRaw(prompt, claudeCommand, verbose, 'APPROVAL-ASSESS');
|
|
558
|
-
const parsed = parseVerdictResponse(raw);
|
|
559
|
-
const isApproval = parsed.verdict.includes('APPROVAL');
|
|
560
|
-
|
|
561
|
-
if (verbose) {
|
|
562
|
-
hlog(`[APPROVAL-ASSESS] Verdict: ${isApproval ? 'APPROVAL' : 'NEW_TASK'} — ${parsed.reason}`);
|
|
563
|
-
}
|
|
564
|
-
|
|
565
|
-
return { isApproval, reason: parsed.reason };
|
|
197
|
+
if (verbose) hlog('[STALL-ASSESS] Running Haiku assessment...');
|
|
198
|
+
return await spawnHaikuVerdict(buildAssessmentPrompt(ctx), claudeCommand, verbose);
|
|
566
199
|
} catch (err) {
|
|
567
|
-
if (verbose) {
|
|
568
|
-
|
|
569
|
-
}
|
|
570
|
-
// On failure, assume not an approval (safer to treat as new task)
|
|
571
|
-
return { isApproval: false, reason: `Assessment failed: ${err}` };
|
|
200
|
+
if (verbose) hlog(`[STALL-ASSESS] Haiku assessment failed: ${err}`);
|
|
201
|
+
return { action: 'extend', extensionMs: 10 * 60_000, reason: 'Stall assessment unavailable — extending 10 min as precaution' };
|
|
572
202
|
}
|
|
573
203
|
}
|
|
574
204
|
|
|
575
|
-
|
|
576
|
-
|
|
577
|
-
|
|
578
|
-
|
|
579
|
-
responseTail: string;
|
|
580
|
-
/** Total number of successful tool calls in this execution */
|
|
581
|
-
successfulToolCalls: number;
|
|
582
|
-
/** Whether extended thinking output was produced */
|
|
583
|
-
hasThinking: boolean;
|
|
584
|
-
/** Total response length */
|
|
585
|
-
responseLength: number;
|
|
586
|
-
}
|
|
587
|
-
|
|
588
|
-
export interface PrematureCompletionVerdict {
|
|
589
|
-
/** True if the task appears incomplete and should be auto-continued */
|
|
590
|
-
isIncomplete: boolean;
|
|
591
|
-
reason: string;
|
|
592
|
-
}
|
|
593
|
-
|
|
594
|
-
/**
|
|
595
|
-
* Assess whether a completed Claude execution ended prematurely.
|
|
596
|
-
* Called when stop_reason is 'end_turn' but the task may not be finished.
|
|
597
|
-
* Haiku determines if the trailing response text indicates planned-but-unexecuted work.
|
|
598
|
-
*/
|
|
599
|
-
export async function assessPrematureCompletion(
|
|
600
|
-
ctx: PrematureCompletionContext,
|
|
205
|
+
export async function assessToolTimeout(
|
|
206
|
+
toolName: string,
|
|
207
|
+
toolInput: Record<string, unknown>,
|
|
208
|
+
elapsedMs: number,
|
|
601
209
|
claudeCommand: string,
|
|
602
210
|
verbose: boolean,
|
|
603
|
-
|
|
604
|
-
|
|
605
|
-
|
|
606
|
-
'Determine whether the agent finished its task or stopped prematurely mid-work.',
|
|
607
|
-
'',
|
|
608
|
-
'Session signals:',
|
|
609
|
-
`- ${ctx.successfulToolCalls} tool calls completed successfully`,
|
|
610
|
-
`- Response length: ${ctx.responseLength} characters`,
|
|
611
|
-
`- Extended thinking: ${ctx.hasThinking ? 'YES' : 'NO'}`,
|
|
612
|
-
'',
|
|
613
|
-
`Final response text (last ${ctx.responseTail.length} chars):`,
|
|
614
|
-
ctx.responseTail,
|
|
615
|
-
'',
|
|
616
|
-
'INCOMPLETE signals: "Now I\'ll...", "Let me fix...", "Next I\'ll...", "Moving on to...",',
|
|
617
|
-
'"I\'ll continue with...", announcing next steps that were never executed,',
|
|
618
|
-
'describing work that will happen next but no tool call followed.',
|
|
619
|
-
'',
|
|
620
|
-
'COMPLETE signals: summarizing what was done, confirming changes, reporting results,',
|
|
621
|
-
'asking the user a question, past-tense descriptions of completed work,',
|
|
622
|
-
'"all done", "changes applied", referencing finished state.',
|
|
623
|
-
'',
|
|
624
|
-
'Respond in EXACTLY this format (2 lines, no extra text):',
|
|
625
|
-
'VERDICT: COMPLETE or INCOMPLETE',
|
|
626
|
-
'REASON: <brief one-line explanation>',
|
|
627
|
-
].join('\n');
|
|
628
|
-
|
|
629
|
-
try {
|
|
630
|
-
if (verbose) {
|
|
631
|
-
hlog(`[PREMATURE-ASSESS] Running Haiku assessment (${ctx.successfulToolCalls} tools, ${ctx.responseLength} chars)...`);
|
|
632
|
-
}
|
|
633
|
-
|
|
634
|
-
const raw = await spawnHaikuRaw(prompt, claudeCommand, verbose, 'PREMATURE-ASSESS');
|
|
635
|
-
const parsed = parseVerdictResponse(raw);
|
|
636
|
-
const isIncomplete = parsed.verdict.includes('INCOMPLETE');
|
|
637
|
-
|
|
638
|
-
if (verbose) {
|
|
639
|
-
hlog(`[PREMATURE-ASSESS] Verdict: ${isIncomplete ? 'INCOMPLETE' : 'COMPLETE'} — ${parsed.reason}`);
|
|
640
|
-
}
|
|
641
|
-
|
|
642
|
-
return { isIncomplete, reason: parsed.reason };
|
|
643
|
-
} catch (err) {
|
|
644
|
-
if (verbose) {
|
|
645
|
-
hlog(`[PREMATURE-ASSESS] Haiku assessment failed: ${err}`);
|
|
646
|
-
}
|
|
647
|
-
// On failure, don't retry — safer to let the user decide than to auto-continue incorrectly
|
|
648
|
-
return { isIncomplete: false, reason: `Assessment failed: ${err}` };
|
|
649
|
-
}
|
|
650
|
-
}
|
|
211
|
+
tokenSilenceMs?: number,
|
|
212
|
+
): Promise<StallVerdict> {
|
|
213
|
+
const elapsedSec = Math.round(elapsedMs / 1000);
|
|
651
214
|
|
|
652
|
-
|
|
215
|
+
let inputSummary = '';
|
|
216
|
+
if (toolInput.url) inputSummary = `URL: ${String(toolInput.url).slice(0, 200)}`;
|
|
217
|
+
else if (toolInput.query) inputSummary = `Query: ${String(toolInput.query).slice(0, 200)}`;
|
|
218
|
+
else if (toolInput.command) inputSummary = `Command: ${String(toolInput.command).slice(0, 200)}`;
|
|
219
|
+
else if (toolInput.prompt) inputSummary = `Prompt: ${String(toolInput.prompt).slice(0, 200)}`;
|
|
220
|
+
else inputSummary = JSON.stringify(toolInput).slice(0, 200);
|
|
653
221
|
|
|
654
|
-
|
|
655
|
-
|
|
656
|
-
|
|
657
|
-
|
|
658
|
-
|
|
659
|
-
|
|
660
|
-
responseTail: string;
|
|
661
|
-
};
|
|
662
|
-
resultB: {
|
|
663
|
-
successfulToolCalls: number;
|
|
664
|
-
responseLength: number;
|
|
665
|
-
hasThinking: boolean;
|
|
666
|
-
responseTail: string;
|
|
222
|
+
const toolDescriptions: Record<string, string> = {
|
|
223
|
+
WebFetch: 'fetches a URL, converts HTML to markdown, and runs a Haiku summarization pass',
|
|
224
|
+
WebSearch: 'performs a web search and returns results',
|
|
225
|
+
Task: 'spawns a subagent that runs autonomously with its own tools',
|
|
226
|
+
Agent: 'spawns a subagent that runs autonomously with its own tools',
|
|
227
|
+
Bash: 'executes a shell command',
|
|
667
228
|
};
|
|
668
|
-
}
|
|
669
|
-
|
|
670
|
-
export interface BestResultVerdict {
|
|
671
|
-
winner: 'A' | 'B';
|
|
672
|
-
reason: string;
|
|
673
|
-
}
|
|
229
|
+
const toolDesc = toolDescriptions[toolName] || `executes the ${toolName} tool`;
|
|
674
230
|
|
|
675
|
-
|
|
676
|
-
|
|
677
|
-
|
|
678
|
-
* (tool count * 10 + response length / 50 + thinking bonus).
|
|
679
|
-
*/
|
|
680
|
-
export async function assessBestResult(
|
|
681
|
-
ctx: BestResultContext,
|
|
682
|
-
claudeCommand: string,
|
|
683
|
-
verbose: boolean,
|
|
684
|
-
): Promise<BestResultVerdict> {
|
|
685
|
-
const promptPreview = ctx.originalPrompt.length > 300
|
|
686
|
-
? `${ctx.originalPrompt.slice(0, 300)}...`
|
|
687
|
-
: ctx.originalPrompt;
|
|
231
|
+
const tokenLine = tokenSilenceMs !== undefined
|
|
232
|
+
? `Token activity: last token event ${Math.round(tokenSilenceMs / 1000)}s ago (recent tokens = process is alive and processing)`
|
|
233
|
+
: 'Token activity: no token events observed';
|
|
688
234
|
|
|
689
235
|
const prompt = [
|
|
690
|
-
|
|
691
|
-
|
|
692
|
-
`
|
|
693
|
-
|
|
694
|
-
`Response A: ${ctx.resultA.successfulToolCalls} successful tool calls, ${ctx.resultA.responseLength} chars, ${ctx.resultA.hasThinking ? 'has' : 'no'} thinking output`,
|
|
695
|
-
`Last 500 chars of A: ${ctx.resultA.responseTail}`,
|
|
696
|
-
'',
|
|
697
|
-
`Response B: ${ctx.resultB.successfulToolCalls} successful tool calls, ${ctx.resultB.responseLength} chars, ${ctx.resultB.hasThinking ? 'has' : 'no'} thinking output`,
|
|
698
|
-
`Last 500 chars of B: ${ctx.resultB.responseTail}`,
|
|
236
|
+
`You are a process health monitor. A ${toolName} tool call has been running for ${elapsedSec}s.`,
|
|
237
|
+
`${toolName} ${toolDesc}.`,
|
|
238
|
+
`Tool input: ${inputSummary}`,
|
|
239
|
+
tokenLine,
|
|
699
240
|
'',
|
|
700
|
-
|
|
701
|
-
'
|
|
702
|
-
'
|
|
703
|
-
'- Quality of analysis and output, not just quantity.',
|
|
241
|
+
`Is this tool call likely still working, or is it hung/frozen?`,
|
|
242
|
+
'Consider: network latency, server response times, anti-bot protections, large page sizes, complex operations.',
|
|
243
|
+
'IMPORTANT: If tokens were active recently (< 60s ago), the process is likely still alive and processing — strongly favor WORKING.',
|
|
704
244
|
'',
|
|
705
|
-
'Respond in EXACTLY this format (
|
|
706
|
-
'VERDICT:
|
|
245
|
+
'Respond in EXACTLY this format (3 lines, no extra text):',
|
|
246
|
+
'VERDICT: WORKING or STALLED',
|
|
247
|
+
'MINUTES: <number 1-10, only if WORKING, how many more minutes to allow>',
|
|
707
248
|
'REASON: <brief one-line explanation>',
|
|
708
249
|
].join('\n');
|
|
709
250
|
|
|
710
251
|
try {
|
|
711
|
-
if (verbose) {
|
|
712
|
-
|
|
713
|
-
}
|
|
714
|
-
|
|
715
|
-
const raw = await spawnHaikuRaw(prompt, claudeCommand, verbose, 'BEST-RESULT');
|
|
716
|
-
const parsed = parseVerdictResponse(raw);
|
|
717
|
-
const winner: 'A' | 'B' = parsed.verdict.includes('B') ? 'B' : 'A';
|
|
718
|
-
|
|
719
|
-
if (verbose) {
|
|
720
|
-
hlog(`[BEST-RESULT] Verdict: ${winner} — ${parsed.reason}`);
|
|
721
|
-
}
|
|
722
|
-
|
|
723
|
-
return { winner, reason: parsed.reason };
|
|
724
|
-
} catch (err) {
|
|
725
|
-
if (verbose) {
|
|
726
|
-
hlog(`[BEST-RESULT] Haiku assessment failed: ${err}`);
|
|
727
|
-
}
|
|
728
|
-
// On failure, prefer A (the previously-tracked best result)
|
|
729
|
-
return { winner: 'A', reason: `Assessment failed: ${err}` };
|
|
730
|
-
}
|
|
731
|
-
}
|
|
732
|
-
|
|
733
|
-
// ========== Error Classification ==========
|
|
734
|
-
|
|
735
|
-
export interface ErrorClassification {
|
|
736
|
-
errorCode: string;
|
|
737
|
-
message: string;
|
|
738
|
-
}
|
|
739
|
-
|
|
740
|
-
/**
|
|
741
|
-
* Classify an unrecognized error from stderr using Haiku.
|
|
742
|
-
* Called as a fallback when regex patterns in output-utils.ts don't match.
|
|
743
|
-
* Returns null if the stderr content isn't a real error (just warnings/debug info).
|
|
744
|
-
*/
|
|
745
|
-
export async function classifyError(
|
|
746
|
-
stderrContent: string,
|
|
747
|
-
claudeCommand: string,
|
|
748
|
-
verbose: boolean,
|
|
749
|
-
): Promise<ErrorClassification | null> {
|
|
750
|
-
const tail = stderrContent.slice(-500);
|
|
751
|
-
if (!tail.trim()) return null;
|
|
752
|
-
|
|
753
|
-
const prompt = [
|
|
754
|
-
'You are classifying an error message from the Claude Code CLI that did not match known patterns.',
|
|
755
|
-
'',
|
|
756
|
-
`stderr (last ${tail.length} chars):`,
|
|
757
|
-
tail,
|
|
758
|
-
'',
|
|
759
|
-
'Classify into one of these categories:',
|
|
760
|
-
'- AUTH_REQUIRED: Authentication/login issues',
|
|
761
|
-
'- API_KEY_INVALID: API key problems',
|
|
762
|
-
'- QUOTA_EXCEEDED: Usage limits, billing, subscription',
|
|
763
|
-
'- RATE_LIMITED: Too many requests, throttling',
|
|
764
|
-
'- NETWORK_ERROR: Connection, DNS, timeout issues',
|
|
765
|
-
'- SSL_ERROR: Certificate/TLS problems',
|
|
766
|
-
'- SERVICE_UNAVAILABLE: Backend down (502/503/504)',
|
|
767
|
-
'- INTERNAL_ERROR: Server errors (500)',
|
|
768
|
-
'- CONTEXT_TOO_LONG: Token/context limit exceeded',
|
|
769
|
-
'- SESSION_NOT_FOUND: Invalid/expired session',
|
|
770
|
-
'- UNKNOWN: Cannot determine, not a real error, or just warnings/debug output',
|
|
771
|
-
'',
|
|
772
|
-
'If the stderr content is just warnings, debug info, or not an actual error, use UNKNOWN.',
|
|
773
|
-
'',
|
|
774
|
-
'Respond in EXACTLY this format (2 lines, no extra text):',
|
|
775
|
-
'CATEGORY: <one of the above>',
|
|
776
|
-
'MESSAGE: <brief user-friendly description of the error>',
|
|
777
|
-
].join('\n');
|
|
778
|
-
|
|
779
|
-
try {
|
|
780
|
-
if (verbose) {
|
|
781
|
-
hlog('[ERROR-CLASSIFY] Running Haiku assessment...');
|
|
782
|
-
}
|
|
783
|
-
|
|
784
|
-
const raw = await spawnHaikuRaw(prompt, claudeCommand, verbose, 'ERROR-CLASSIFY');
|
|
785
|
-
const lines = raw.split('\n');
|
|
786
|
-
let category = 'UNKNOWN';
|
|
787
|
-
let message = '';
|
|
788
|
-
|
|
789
|
-
for (const line of lines) {
|
|
790
|
-
const trimmed = line.trim();
|
|
791
|
-
if (trimmed.startsWith('CATEGORY:')) {
|
|
792
|
-
category = trimmed.slice('CATEGORY:'.length).trim().toUpperCase();
|
|
793
|
-
} else if (trimmed.startsWith('MESSAGE:')) {
|
|
794
|
-
message = trimmed.slice('MESSAGE:'.length).trim();
|
|
795
|
-
}
|
|
796
|
-
}
|
|
797
|
-
|
|
798
|
-
if (category === 'UNKNOWN' || !message) return null;
|
|
799
|
-
|
|
800
|
-
if (verbose) {
|
|
801
|
-
hlog(`[ERROR-CLASSIFY] Verdict: ${category} — ${message}`);
|
|
802
|
-
}
|
|
803
|
-
|
|
804
|
-
return { errorCode: category, message };
|
|
252
|
+
if (verbose) hlog(`[TOOL-ASSESS] Running Haiku assessment for ${toolName} (${elapsedSec}s elapsed)...`);
|
|
253
|
+
return await spawnHaikuVerdict(prompt, claudeCommand, verbose, 'TOOL-ASSESS');
|
|
805
254
|
} catch (err) {
|
|
806
|
-
if (verbose) {
|
|
807
|
-
|
|
808
|
-
}
|
|
809
|
-
return null;
|
|
255
|
+
if (verbose) hlog(`[TOOL-ASSESS] Haiku assessment failed: ${err}`);
|
|
256
|
+
return { action: 'kill', extensionMs: 0, reason: `Tool timeout assessment failed: ${err}` };
|
|
810
257
|
}
|
|
811
258
|
}
|