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
|
@@ -0,0 +1,365 @@
|
|
|
1
|
+
// Copyright (c) 2025-present Mstro, Inc. All rights reserved.
|
|
2
|
+
// Licensed under the MIT License. See LICENSE file for details.
|
|
3
|
+
|
|
4
|
+
/**
|
|
5
|
+
* Haiku-based AI assessments for the headless runner.
|
|
6
|
+
*
|
|
7
|
+
* Provides context loss detection, approval classification, premature completion detection,
|
|
8
|
+
* best result comparison, and error classification via lightweight Haiku calls.
|
|
9
|
+
*
|
|
10
|
+
* The low-level Haiku spawner is also exported for use by stall-assessor.ts.
|
|
11
|
+
*/
|
|
12
|
+
|
|
13
|
+
import { type ChildProcess, spawn } from 'node:child_process';
|
|
14
|
+
import { hlog } from './headless-logger.js';
|
|
15
|
+
|
|
16
|
+
// ========== Haiku Infrastructure ==========
|
|
17
|
+
|
|
18
|
+
const HAIKU_TIMEOUT_MS = 30_000;
|
|
19
|
+
|
|
20
|
+
/** Low-level Haiku spawner: runs a prompt through `claude --print --model haiku` and returns raw text */
|
|
21
|
+
export function spawnHaikuRaw(
|
|
22
|
+
prompt: string,
|
|
23
|
+
claudeCommand: string,
|
|
24
|
+
verbose: boolean,
|
|
25
|
+
label: string,
|
|
26
|
+
): Promise<string> {
|
|
27
|
+
return new Promise((resolve, reject) => {
|
|
28
|
+
let stdout = '';
|
|
29
|
+
let settled = false;
|
|
30
|
+
|
|
31
|
+
const proc: ChildProcess = spawn(
|
|
32
|
+
claudeCommand,
|
|
33
|
+
['--print', '--model', 'haiku', prompt],
|
|
34
|
+
{ stdio: ['ignore', 'pipe', 'pipe'] },
|
|
35
|
+
);
|
|
36
|
+
|
|
37
|
+
const timer = setTimeout(() => {
|
|
38
|
+
if (!settled) {
|
|
39
|
+
settled = true;
|
|
40
|
+
proc.kill('SIGTERM');
|
|
41
|
+
reject(new Error('Haiku assessment timed out'));
|
|
42
|
+
}
|
|
43
|
+
}, HAIKU_TIMEOUT_MS);
|
|
44
|
+
|
|
45
|
+
proc.stdout!.on('data', (data) => { stdout += data.toString(); });
|
|
46
|
+
proc.stderr!.on('data', (data) => {
|
|
47
|
+
if (verbose) hlog(`[${label}] haiku stderr: ${data.toString().trim()}`);
|
|
48
|
+
});
|
|
49
|
+
|
|
50
|
+
proc.on('close', (code) => {
|
|
51
|
+
clearTimeout(timer);
|
|
52
|
+
if (settled) return;
|
|
53
|
+
settled = true;
|
|
54
|
+
if (code !== 0 || !stdout.trim()) {
|
|
55
|
+
reject(new Error(`Haiku exited with code ${code}, output: ${stdout.trim()}`));
|
|
56
|
+
return;
|
|
57
|
+
}
|
|
58
|
+
if (verbose) hlog(`[${label}] Haiku response: ${stdout.trim()}`);
|
|
59
|
+
resolve(stdout.trim());
|
|
60
|
+
});
|
|
61
|
+
|
|
62
|
+
proc.on('error', (err) => {
|
|
63
|
+
clearTimeout(timer);
|
|
64
|
+
if (settled) return;
|
|
65
|
+
settled = true;
|
|
66
|
+
reject(err);
|
|
67
|
+
});
|
|
68
|
+
});
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
/** Parse VERDICT/REASON format from Haiku response */
|
|
72
|
+
export function parseVerdictResponse(raw: string): { verdict: string; reason: string } {
|
|
73
|
+
const lines = raw.split('\n');
|
|
74
|
+
let verdict = 'STALLED';
|
|
75
|
+
let reason = 'Assessment inconclusive';
|
|
76
|
+
|
|
77
|
+
for (const line of lines) {
|
|
78
|
+
const trimmed = line.trim();
|
|
79
|
+
if (trimmed.startsWith('VERDICT:')) {
|
|
80
|
+
verdict = trimmed.slice('VERDICT:'.length).trim().toUpperCase();
|
|
81
|
+
} else if (trimmed.startsWith('REASON:')) {
|
|
82
|
+
reason = trimmed.slice('REASON:'.length).trim();
|
|
83
|
+
}
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
return { verdict, reason };
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
// ========== Context Loss Assessment ==========
|
|
90
|
+
|
|
91
|
+
export interface ContextLossVerdict {
|
|
92
|
+
contextLost: boolean;
|
|
93
|
+
reason: string;
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
export interface ContextLossContext {
|
|
97
|
+
assistantResponse: string;
|
|
98
|
+
effectiveTimeouts: number;
|
|
99
|
+
nativeTimeoutCount: number;
|
|
100
|
+
successfulToolCalls: number;
|
|
101
|
+
thinkingOutputLength: number;
|
|
102
|
+
hasSuccessfulWrite: boolean;
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
export async function assessContextLoss(
|
|
106
|
+
ctx: ContextLossContext,
|
|
107
|
+
claudeCommand: string,
|
|
108
|
+
verbose: boolean,
|
|
109
|
+
): Promise<ContextLossVerdict> {
|
|
110
|
+
const prompt = [
|
|
111
|
+
'You are analyzing whether a Claude Code agent lost context after experiencing tool timeouts.',
|
|
112
|
+
'',
|
|
113
|
+
'Session signals:',
|
|
114
|
+
`- ${ctx.effectiveTimeouts} tool(s) timed out (${ctx.nativeTimeoutCount} native timeouts)`,
|
|
115
|
+
`- ${ctx.successfulToolCalls} tool calls completed successfully`,
|
|
116
|
+
`- ${ctx.thinkingOutputLength > 0 ? 'Extended thinking was active' : 'No extended thinking'}`,
|
|
117
|
+
`- ${ctx.hasSuccessfulWrite ? 'At least one file write succeeded' : 'No file writes succeeded'}`,
|
|
118
|
+
'',
|
|
119
|
+
`Final response text (last 500 chars):`,
|
|
120
|
+
ctx.assistantResponse.slice(-500),
|
|
121
|
+
'',
|
|
122
|
+
'CONTEXT_LOST signs: "How can I help you?", generic greeting, no reference to the task,',
|
|
123
|
+
'confusion about what to do, asking for task description, repeating the same action.',
|
|
124
|
+
'',
|
|
125
|
+
'CONTEXT_OK signs: references specific files/code, describes completed work, plans next steps,',
|
|
126
|
+
'summarizes results, mentions the timeout and adjusts approach.',
|
|
127
|
+
'',
|
|
128
|
+
'IMPORTANT: If successful file writes happened AND the response references specific work,',
|
|
129
|
+
'the agent likely recovered — favor CONTEXT_OK.',
|
|
130
|
+
'',
|
|
131
|
+
'Respond in EXACTLY this format (2 lines, no extra text):',
|
|
132
|
+
'VERDICT: CONTEXT_LOST or CONTEXT_OK',
|
|
133
|
+
'REASON: <brief one-line explanation>',
|
|
134
|
+
].join('\n');
|
|
135
|
+
|
|
136
|
+
try {
|
|
137
|
+
if (verbose) hlog('[CONTEXT-ASSESS] Running Haiku assessment...');
|
|
138
|
+
const raw = await spawnHaikuRaw(prompt, claudeCommand, verbose, 'CONTEXT-ASSESS');
|
|
139
|
+
const parsed = parseVerdictResponse(raw);
|
|
140
|
+
const contextLost = parsed.verdict.includes('LOST');
|
|
141
|
+
if (verbose) hlog(`[CONTEXT-ASSESS] Verdict: ${contextLost ? 'LOST' : 'OK'} — ${parsed.reason}`);
|
|
142
|
+
return { contextLost, reason: parsed.reason };
|
|
143
|
+
} catch (err) {
|
|
144
|
+
if (verbose) hlog(`[CONTEXT-ASSESS] Haiku assessment failed: ${err}`);
|
|
145
|
+
return { contextLost: false, reason: `Assessment failed: ${err}` };
|
|
146
|
+
}
|
|
147
|
+
}
|
|
148
|
+
|
|
149
|
+
// ========== Approval Prompt Assessment ==========
|
|
150
|
+
|
|
151
|
+
export interface ApprovalVerdict {
|
|
152
|
+
isApproval: boolean;
|
|
153
|
+
reason: string;
|
|
154
|
+
}
|
|
155
|
+
|
|
156
|
+
export async function assessApproval(
|
|
157
|
+
userMessage: string,
|
|
158
|
+
claudeCommand: string,
|
|
159
|
+
verbose: boolean,
|
|
160
|
+
): Promise<ApprovalVerdict> {
|
|
161
|
+
const prompt = [
|
|
162
|
+
'You are classifying a user message in a multi-turn conversation with a coding assistant.',
|
|
163
|
+
'The assistant previously proposed a plan or asked a question, and the user is now responding.',
|
|
164
|
+
'',
|
|
165
|
+
`User's message: "${userMessage}"`,
|
|
166
|
+
'',
|
|
167
|
+
'Is this an approval/continuation (user agrees, says yes, wants to proceed) or a new task/question?',
|
|
168
|
+
'',
|
|
169
|
+
'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").',
|
|
170
|
+
'NEW_TASK signs: asks a different question, gives new detailed instructions, changes topic, provides new requirements unrelated to any proposal.',
|
|
171
|
+
'',
|
|
172
|
+
'Respond in EXACTLY this format (2 lines, no extra text):',
|
|
173
|
+
'VERDICT: APPROVAL or NEW_TASK',
|
|
174
|
+
'REASON: <brief one-line explanation>',
|
|
175
|
+
].join('\n');
|
|
176
|
+
|
|
177
|
+
try {
|
|
178
|
+
if (verbose) hlog('[APPROVAL-ASSESS] Running Haiku assessment...');
|
|
179
|
+
const raw = await spawnHaikuRaw(prompt, claudeCommand, verbose, 'APPROVAL-ASSESS');
|
|
180
|
+
const parsed = parseVerdictResponse(raw);
|
|
181
|
+
const isApproval = parsed.verdict.includes('APPROVAL');
|
|
182
|
+
if (verbose) hlog(`[APPROVAL-ASSESS] Verdict: ${isApproval ? 'APPROVAL' : 'NEW_TASK'} — ${parsed.reason}`);
|
|
183
|
+
return { isApproval, reason: parsed.reason };
|
|
184
|
+
} catch (err) {
|
|
185
|
+
if (verbose) hlog(`[APPROVAL-ASSESS] Haiku assessment failed: ${err}`);
|
|
186
|
+
return { isApproval: false, reason: `Assessment failed: ${err}` };
|
|
187
|
+
}
|
|
188
|
+
}
|
|
189
|
+
|
|
190
|
+
// ========== Premature Completion Assessment ==========
|
|
191
|
+
|
|
192
|
+
export interface PrematureCompletionContext {
|
|
193
|
+
responseTail: string;
|
|
194
|
+
successfulToolCalls: number;
|
|
195
|
+
hasThinking: boolean;
|
|
196
|
+
responseLength: number;
|
|
197
|
+
}
|
|
198
|
+
|
|
199
|
+
export interface PrematureCompletionVerdict {
|
|
200
|
+
isIncomplete: boolean;
|
|
201
|
+
reason: string;
|
|
202
|
+
}
|
|
203
|
+
|
|
204
|
+
export async function assessPrematureCompletion(
|
|
205
|
+
ctx: PrematureCompletionContext,
|
|
206
|
+
claudeCommand: string,
|
|
207
|
+
verbose: boolean,
|
|
208
|
+
): Promise<PrematureCompletionVerdict> {
|
|
209
|
+
const prompt = [
|
|
210
|
+
'You are analyzing the FINAL output of a Claude Code agent that just exited normally.',
|
|
211
|
+
'Determine whether the agent finished its task or stopped prematurely mid-work.',
|
|
212
|
+
'',
|
|
213
|
+
'Session signals:',
|
|
214
|
+
`- ${ctx.successfulToolCalls} tool calls completed successfully`,
|
|
215
|
+
`- Response length: ${ctx.responseLength} characters`,
|
|
216
|
+
`- Extended thinking: ${ctx.hasThinking ? 'YES' : 'NO'}`,
|
|
217
|
+
'',
|
|
218
|
+
`Final response text (last ${ctx.responseTail.length} chars):`,
|
|
219
|
+
ctx.responseTail,
|
|
220
|
+
'',
|
|
221
|
+
'INCOMPLETE signals: "Now I\'ll...", "Let me fix...", "Next I\'ll...", "Moving on to...",',
|
|
222
|
+
'"I\'ll continue with...", announcing next steps that were never executed,',
|
|
223
|
+
'describing work that will happen next but no tool call followed.',
|
|
224
|
+
'',
|
|
225
|
+
'COMPLETE signals: summarizing what was done, confirming changes, reporting results,',
|
|
226
|
+
'asking the user a question, past-tense descriptions of completed work,',
|
|
227
|
+
'"all done", "changes applied", referencing finished state.',
|
|
228
|
+
'',
|
|
229
|
+
'Respond in EXACTLY this format (2 lines, no extra text):',
|
|
230
|
+
'VERDICT: COMPLETE or INCOMPLETE',
|
|
231
|
+
'REASON: <brief one-line explanation>',
|
|
232
|
+
].join('\n');
|
|
233
|
+
|
|
234
|
+
try {
|
|
235
|
+
if (verbose) hlog(`[PREMATURE-ASSESS] Running Haiku assessment (${ctx.successfulToolCalls} tools, ${ctx.responseLength} chars)...`);
|
|
236
|
+
const raw = await spawnHaikuRaw(prompt, claudeCommand, verbose, 'PREMATURE-ASSESS');
|
|
237
|
+
const parsed = parseVerdictResponse(raw);
|
|
238
|
+
const isIncomplete = parsed.verdict.includes('INCOMPLETE');
|
|
239
|
+
if (verbose) hlog(`[PREMATURE-ASSESS] Verdict: ${isIncomplete ? 'INCOMPLETE' : 'COMPLETE'} — ${parsed.reason}`);
|
|
240
|
+
return { isIncomplete, reason: parsed.reason };
|
|
241
|
+
} catch (err) {
|
|
242
|
+
if (verbose) hlog(`[PREMATURE-ASSESS] Haiku assessment failed: ${err}`);
|
|
243
|
+
return { isIncomplete: false, reason: `Assessment failed: ${err}` };
|
|
244
|
+
}
|
|
245
|
+
}
|
|
246
|
+
|
|
247
|
+
// ========== Best Result Comparison ==========
|
|
248
|
+
|
|
249
|
+
export interface BestResultContext {
|
|
250
|
+
originalPrompt: string;
|
|
251
|
+
resultA: { successfulToolCalls: number; responseLength: number; hasThinking: boolean; responseTail: string };
|
|
252
|
+
resultB: { successfulToolCalls: number; responseLength: number; hasThinking: boolean; responseTail: string };
|
|
253
|
+
}
|
|
254
|
+
|
|
255
|
+
export interface BestResultVerdict {
|
|
256
|
+
winner: 'A' | 'B';
|
|
257
|
+
reason: string;
|
|
258
|
+
}
|
|
259
|
+
|
|
260
|
+
export async function assessBestResult(
|
|
261
|
+
ctx: BestResultContext,
|
|
262
|
+
claudeCommand: string,
|
|
263
|
+
verbose: boolean,
|
|
264
|
+
): Promise<BestResultVerdict> {
|
|
265
|
+
const promptPreview = ctx.originalPrompt.length > 300 ? `${ctx.originalPrompt.slice(0, 300)}...` : ctx.originalPrompt;
|
|
266
|
+
|
|
267
|
+
const prompt = [
|
|
268
|
+
'You are comparing two AI assistant responses from retry attempts to determine which made more meaningful progress on the user\'s task.',
|
|
269
|
+
'',
|
|
270
|
+
`Original task: ${promptPreview}`,
|
|
271
|
+
'',
|
|
272
|
+
`Response A: ${ctx.resultA.successfulToolCalls} successful tool calls, ${ctx.resultA.responseLength} chars, ${ctx.resultA.hasThinking ? 'has' : 'no'} thinking output`,
|
|
273
|
+
`Last 500 chars of A: ${ctx.resultA.responseTail}`,
|
|
274
|
+
'',
|
|
275
|
+
`Response B: ${ctx.resultB.successfulToolCalls} successful tool calls, ${ctx.resultB.responseLength} chars, ${ctx.resultB.hasThinking ? 'has' : 'no'} thinking output`,
|
|
276
|
+
`Last 500 chars of B: ${ctx.resultB.responseTail}`,
|
|
277
|
+
'',
|
|
278
|
+
'Which response made more meaningful progress? Consider:',
|
|
279
|
+
'- Did it actually work on the task (tool calls, code changes) vs just talking about it?',
|
|
280
|
+
'- Is it confused/lost context ("How can I help?") vs engaged with the original task?',
|
|
281
|
+
'- Quality of analysis and output, not just quantity.',
|
|
282
|
+
'',
|
|
283
|
+
'Respond in EXACTLY this format (2 lines, no extra text):',
|
|
284
|
+
'VERDICT: A or B',
|
|
285
|
+
'REASON: <brief one-line explanation>',
|
|
286
|
+
].join('\n');
|
|
287
|
+
|
|
288
|
+
try {
|
|
289
|
+
if (verbose) hlog('[BEST-RESULT] Running Haiku assessment...');
|
|
290
|
+
const raw = await spawnHaikuRaw(prompt, claudeCommand, verbose, 'BEST-RESULT');
|
|
291
|
+
const parsed = parseVerdictResponse(raw);
|
|
292
|
+
const winner: 'A' | 'B' = parsed.verdict.includes('B') ? 'B' : 'A';
|
|
293
|
+
if (verbose) hlog(`[BEST-RESULT] Verdict: ${winner} — ${parsed.reason}`);
|
|
294
|
+
return { winner, reason: parsed.reason };
|
|
295
|
+
} catch (err) {
|
|
296
|
+
if (verbose) hlog(`[BEST-RESULT] Haiku assessment failed: ${err}`);
|
|
297
|
+
return { winner: 'A', reason: `Assessment failed: ${err}` };
|
|
298
|
+
}
|
|
299
|
+
}
|
|
300
|
+
|
|
301
|
+
// ========== Error Classification ==========
|
|
302
|
+
|
|
303
|
+
export interface ErrorClassification {
|
|
304
|
+
errorCode: string;
|
|
305
|
+
message: string;
|
|
306
|
+
}
|
|
307
|
+
|
|
308
|
+
export async function classifyError(
|
|
309
|
+
stderrContent: string,
|
|
310
|
+
claudeCommand: string,
|
|
311
|
+
verbose: boolean,
|
|
312
|
+
): Promise<ErrorClassification | null> {
|
|
313
|
+
const tail = stderrContent.slice(-500);
|
|
314
|
+
if (!tail.trim()) return null;
|
|
315
|
+
|
|
316
|
+
const prompt = [
|
|
317
|
+
'You are classifying an error message from the Claude Code CLI that did not match known patterns.',
|
|
318
|
+
'',
|
|
319
|
+
`stderr (last ${tail.length} chars):`,
|
|
320
|
+
tail,
|
|
321
|
+
'',
|
|
322
|
+
'Classify into one of these categories:',
|
|
323
|
+
'- AUTH_REQUIRED: Authentication/login issues',
|
|
324
|
+
'- API_KEY_INVALID: API key problems',
|
|
325
|
+
'- QUOTA_EXCEEDED: Usage limits, billing, subscription',
|
|
326
|
+
'- RATE_LIMITED: Too many requests, throttling',
|
|
327
|
+
'- NETWORK_ERROR: Connection, DNS, timeout issues',
|
|
328
|
+
'- SSL_ERROR: Certificate/TLS problems',
|
|
329
|
+
'- SERVICE_UNAVAILABLE: Backend down (502/503/504)',
|
|
330
|
+
'- INTERNAL_ERROR: Server errors (500)',
|
|
331
|
+
'- CONTEXT_TOO_LONG: Token/context limit exceeded',
|
|
332
|
+
'- SESSION_NOT_FOUND: Invalid/expired session',
|
|
333
|
+
'- UNKNOWN: Cannot determine, not a real error, or just warnings/debug output',
|
|
334
|
+
'',
|
|
335
|
+
'If the stderr content is just warnings, debug info, or not an actual error, use UNKNOWN.',
|
|
336
|
+
'',
|
|
337
|
+
'Respond in EXACTLY this format (2 lines, no extra text):',
|
|
338
|
+
'CATEGORY: <one of the above>',
|
|
339
|
+
'MESSAGE: <brief user-friendly description of the error>',
|
|
340
|
+
].join('\n');
|
|
341
|
+
|
|
342
|
+
try {
|
|
343
|
+
if (verbose) hlog('[ERROR-CLASSIFY] Running Haiku assessment...');
|
|
344
|
+
const raw = await spawnHaikuRaw(prompt, claudeCommand, verbose, 'ERROR-CLASSIFY');
|
|
345
|
+
const lines = raw.split('\n');
|
|
346
|
+
let category = 'UNKNOWN';
|
|
347
|
+
let message = '';
|
|
348
|
+
|
|
349
|
+
for (const line of lines) {
|
|
350
|
+
const trimmed = line.trim();
|
|
351
|
+
if (trimmed.startsWith('CATEGORY:')) {
|
|
352
|
+
category = trimmed.slice('CATEGORY:'.length).trim().toUpperCase();
|
|
353
|
+
} else if (trimmed.startsWith('MESSAGE:')) {
|
|
354
|
+
message = trimmed.slice('MESSAGE:'.length).trim();
|
|
355
|
+
}
|
|
356
|
+
}
|
|
357
|
+
|
|
358
|
+
if (category === 'UNKNOWN' || !message) return null;
|
|
359
|
+
if (verbose) hlog(`[ERROR-CLASSIFY] Verdict: ${category} — ${message}`);
|
|
360
|
+
return { errorCode: category, message };
|
|
361
|
+
} catch (err) {
|
|
362
|
+
if (verbose) hlog(`[ERROR-CLASSIFY] Haiku assessment failed: ${err}`);
|
|
363
|
+
return null;
|
|
364
|
+
}
|
|
365
|
+
}
|
|
@@ -12,7 +12,7 @@
|
|
|
12
12
|
|
|
13
13
|
import { AsyncLocalStorage } from 'node:async_hooks';
|
|
14
14
|
import type { WriteStream } from 'node:fs';
|
|
15
|
-
import { createWriteStream, mkdirSync } from 'node:fs';
|
|
15
|
+
import { createWriteStream, mkdirSync, readdirSync, statSync, unlinkSync } from 'node:fs';
|
|
16
16
|
import { homedir } from 'node:os';
|
|
17
17
|
import { join } from 'node:path';
|
|
18
18
|
|
|
@@ -49,14 +49,35 @@ export function herror(...args: unknown[]): void {
|
|
|
49
49
|
|
|
50
50
|
const LOG_DIR = join(homedir(), '.mstro', 'logs', 'headless');
|
|
51
51
|
|
|
52
|
+
/** Delete headless log files older than 7 days. Runs best-effort on startup. */
|
|
53
|
+
function rotateHeadlessLogs(): void {
|
|
54
|
+
try {
|
|
55
|
+
const MAX_AGE_MS = 7 * 24 * 60 * 60 * 1000;
|
|
56
|
+
const now = Date.now();
|
|
57
|
+
for (const file of readdirSync(LOG_DIR)) {
|
|
58
|
+
if (!file.endsWith('.log')) continue;
|
|
59
|
+
const filePath = join(LOG_DIR, file);
|
|
60
|
+
try {
|
|
61
|
+
const stat = statSync(filePath);
|
|
62
|
+
if (now - stat.mtimeMs > MAX_AGE_MS || stat.size === 0) {
|
|
63
|
+
unlinkSync(filePath);
|
|
64
|
+
}
|
|
65
|
+
} catch { /* ignore per-file errors */ }
|
|
66
|
+
}
|
|
67
|
+
} catch { /* ignore if dir doesn't exist yet */ }
|
|
68
|
+
}
|
|
69
|
+
|
|
52
70
|
/**
|
|
53
71
|
* Run an async function with all hlog/herror output redirected to a log file.
|
|
54
|
-
* The log file is created at ~/.mstro/logs/headless/{label}-{timestamp}.log
|
|
72
|
+
* The log file is created at ~/.mstro/logs/headless/{label}-{timestamp}.log,
|
|
73
|
+
* or in logDir if provided (e.g. board-scoped logs/).
|
|
55
74
|
*/
|
|
56
|
-
export async function runWithFileLogger<T>(label: string, fn: () => Promise<T
|
|
57
|
-
|
|
75
|
+
export async function runWithFileLogger<T>(label: string, fn: () => Promise<T>, logDir?: string): Promise<T> {
|
|
76
|
+
const dir = logDir ?? LOG_DIR;
|
|
77
|
+
mkdirSync(dir, { recursive: true });
|
|
78
|
+
rotateHeadlessLogs();
|
|
58
79
|
const timestamp = new Date().toISOString().replace(/[:.]/g, '-');
|
|
59
|
-
const logPath = join(
|
|
80
|
+
const logPath = join(dir, `${label}-${timestamp}.log`);
|
|
60
81
|
const stream: WriteStream = createWriteStream(logPath, { flags: 'a' });
|
|
61
82
|
|
|
62
83
|
const target: LogTarget = {
|
|
@@ -0,0 +1,117 @@
|
|
|
1
|
+
// Copyright (c) 2025-present Mstro, Inc. All rights reserved.
|
|
2
|
+
// Licensed under the MIT License. See LICENSE file for details.
|
|
3
|
+
|
|
4
|
+
/** Regex matching Claude Code's internal tool timeout messages */
|
|
5
|
+
export const NATIVE_TIMEOUT_PATTERN = /^(\w+) timed out — (continuing|retrying) with (\d+) results? preserved$/;
|
|
6
|
+
|
|
7
|
+
/** Quick prefix check: does incomplete text look like it might be a timeout?
|
|
8
|
+
* Matches any capitalized tool name followed by " timed" — no hardcoded set
|
|
9
|
+
* needed because the full NATIVE_TIMEOUT_PATTERN validates on the next chunk. */
|
|
10
|
+
export const TIMEOUT_PREFIX_PATTERN = /^[A-Z]\w* timed/;
|
|
11
|
+
|
|
12
|
+
export interface NativeTimeoutEvent {
|
|
13
|
+
toolName: string;
|
|
14
|
+
action: 'continuing' | 'retrying';
|
|
15
|
+
preservedCount: number;
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
/**
|
|
19
|
+
* Detects Claude Code's internal tool timeout messages in the text stream.
|
|
20
|
+
*
|
|
21
|
+
* Buffers text at newline boundaries to detect complete timeout lines.
|
|
22
|
+
* Non-matching text is forwarded immediately to minimize streaming latency.
|
|
23
|
+
*/
|
|
24
|
+
export class NativeTimeoutDetector {
|
|
25
|
+
private lineBuffer = '';
|
|
26
|
+
private detectedTimeouts: NativeTimeoutEvent[] = [];
|
|
27
|
+
/** Text buffered after native timeouts — held back from streaming until context is assessed */
|
|
28
|
+
private postTimeoutBuffer = '';
|
|
29
|
+
|
|
30
|
+
/**
|
|
31
|
+
* Process a text_delta chunk.
|
|
32
|
+
* Returns passthrough text (for outputCallback) and any detected timeouts.
|
|
33
|
+
*
|
|
34
|
+
* After the first native timeout is detected, subsequent passthrough text
|
|
35
|
+
* is held in postTimeoutBuffer instead of returned as passthrough. This
|
|
36
|
+
* prevents confused "What were you working on?" responses from streaming
|
|
37
|
+
* to the user before context loss can be assessed.
|
|
38
|
+
*/
|
|
39
|
+
processChunk(text: string): { passthrough: string; timeouts: NativeTimeoutEvent[] } {
|
|
40
|
+
const timeouts: NativeTimeoutEvent[] = [];
|
|
41
|
+
let passthrough = '';
|
|
42
|
+
|
|
43
|
+
this.lineBuffer += text;
|
|
44
|
+
|
|
45
|
+
const lines = this.lineBuffer.split('\n');
|
|
46
|
+
const incomplete = lines.pop() ?? '';
|
|
47
|
+
|
|
48
|
+
for (const line of lines) {
|
|
49
|
+
const trimmed = line.trim();
|
|
50
|
+
const match = trimmed.match(NATIVE_TIMEOUT_PATTERN);
|
|
51
|
+
|
|
52
|
+
if (match) {
|
|
53
|
+
const event: NativeTimeoutEvent = {
|
|
54
|
+
toolName: match[1],
|
|
55
|
+
action: match[2] as 'continuing' | 'retrying',
|
|
56
|
+
preservedCount: parseInt(match[3], 10),
|
|
57
|
+
};
|
|
58
|
+
timeouts.push(event);
|
|
59
|
+
this.detectedTimeouts.push(event);
|
|
60
|
+
} else {
|
|
61
|
+
passthrough += `${line}\n`;
|
|
62
|
+
}
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
// Handle incomplete trailing text
|
|
66
|
+
if (incomplete) {
|
|
67
|
+
if (TIMEOUT_PREFIX_PATTERN.test(incomplete)) {
|
|
68
|
+
this.lineBuffer = incomplete;
|
|
69
|
+
} else {
|
|
70
|
+
passthrough += incomplete;
|
|
71
|
+
this.lineBuffer = '';
|
|
72
|
+
}
|
|
73
|
+
} else {
|
|
74
|
+
this.lineBuffer = '';
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
// After native timeouts, buffer passthrough text instead of returning it.
|
|
78
|
+
if (this.detectedTimeouts.length > 0 && passthrough) {
|
|
79
|
+
this.postTimeoutBuffer += passthrough;
|
|
80
|
+
passthrough = '';
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
return { passthrough, timeouts };
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
/** Flush any held buffer (call on stream end).
|
|
87
|
+
* Also checks remaining buffer for timeout patterns so the last
|
|
88
|
+
* timeout message (without trailing newline) is always counted.
|
|
89
|
+
*/
|
|
90
|
+
flush(): string {
|
|
91
|
+
const remaining = this.lineBuffer;
|
|
92
|
+
this.lineBuffer = '';
|
|
93
|
+
|
|
94
|
+
if (remaining) {
|
|
95
|
+
const trimmed = remaining.trim();
|
|
96
|
+
const match = trimmed.match(NATIVE_TIMEOUT_PATTERN);
|
|
97
|
+
if (match) {
|
|
98
|
+
this.detectedTimeouts.push({
|
|
99
|
+
toolName: match[1],
|
|
100
|
+
action: match[2] as 'continuing' | 'retrying',
|
|
101
|
+
preservedCount: parseInt(match[3], 10),
|
|
102
|
+
});
|
|
103
|
+
return '';
|
|
104
|
+
}
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
return remaining;
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
get timeoutCount(): number {
|
|
111
|
+
return this.detectedTimeouts.length;
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
get bufferedPostTimeoutOutput(): string {
|
|
115
|
+
return this.postTimeoutBuffer;
|
|
116
|
+
}
|
|
117
|
+
}
|