mstro-app 0.4.51 → 0.5.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +10 -5
- package/bin/mstro.js +1 -1
- package/dist/server/cli/headless/claude-invoker-stall.d.ts.map +1 -1
- package/dist/server/cli/headless/claude-invoker-stall.js +7 -2
- package/dist/server/cli/headless/claude-invoker-stall.js.map +1 -1
- package/dist/server/cli/headless/claude-invoker.js +1 -1
- package/dist/server/cli/headless/claude-invoker.js.map +1 -1
- package/dist/server/cli/headless/runner.d.ts.map +1 -1
- package/dist/server/cli/headless/runner.js +63 -67
- package/dist/server/cli/headless/runner.js.map +1 -1
- package/dist/server/cli/headless/stall-assessor.d.ts.map +1 -1
- package/dist/server/cli/headless/stall-assessor.js +9 -4
- package/dist/server/cli/headless/stall-assessor.js.map +1 -1
- package/dist/server/cli/improvisation-history-store.d.ts +16 -0
- package/dist/server/cli/improvisation-history-store.d.ts.map +1 -0
- package/dist/server/cli/improvisation-history-store.js +52 -0
- package/dist/server/cli/improvisation-history-store.js.map +1 -0
- package/dist/server/cli/improvisation-movements.d.ts +31 -0
- package/dist/server/cli/improvisation-movements.d.ts.map +1 -0
- package/dist/server/cli/improvisation-movements.js +93 -0
- package/dist/server/cli/improvisation-movements.js.map +1 -0
- package/dist/server/cli/improvisation-output-queue.d.ts +13 -0
- package/dist/server/cli/improvisation-output-queue.d.ts.map +1 -0
- package/dist/server/cli/improvisation-output-queue.js +40 -0
- package/dist/server/cli/improvisation-output-queue.js.map +1 -0
- package/dist/server/cli/improvisation-retry.d.ts +21 -51
- package/dist/server/cli/improvisation-retry.d.ts.map +1 -1
- package/dist/server/cli/improvisation-retry.js +18 -433
- package/dist/server/cli/improvisation-retry.js.map +1 -1
- package/dist/server/cli/improvisation-session-manager.d.ts +10 -8
- package/dist/server/cli/improvisation-session-manager.d.ts.map +1 -1
- package/dist/server/cli/improvisation-session-manager.js +53 -148
- package/dist/server/cli/improvisation-session-manager.js.map +1 -1
- package/dist/server/cli/retry/retry-best-result.d.ts +4 -0
- package/dist/server/cli/retry/retry-best-result.d.ts.map +1 -0
- package/dist/server/cli/retry/retry-best-result.js +61 -0
- package/dist/server/cli/retry/retry-best-result.js.map +1 -0
- package/dist/server/cli/retry/retry-context-loss.d.ts +6 -0
- package/dist/server/cli/retry/retry-context-loss.d.ts.map +1 -0
- package/dist/server/cli/retry/retry-context-loss.js +68 -0
- package/dist/server/cli/retry/retry-context-loss.js.map +1 -0
- package/dist/server/cli/retry/retry-premature-completion.d.ts +5 -0
- package/dist/server/cli/retry/retry-premature-completion.d.ts.map +1 -0
- package/dist/server/cli/retry/retry-premature-completion.js +81 -0
- package/dist/server/cli/retry/retry-premature-completion.js.map +1 -0
- package/dist/server/cli/retry/retry-recovery-strategies.d.ts +13 -0
- package/dist/server/cli/retry/retry-recovery-strategies.d.ts.map +1 -0
- package/dist/server/cli/retry/retry-recovery-strategies.js +166 -0
- package/dist/server/cli/retry/retry-recovery-strategies.js.map +1 -0
- package/dist/server/cli/retry/retry-resume-strategy.d.ts +12 -0
- package/dist/server/cli/retry/retry-resume-strategy.d.ts.map +1 -0
- package/dist/server/cli/retry/retry-resume-strategy.js +22 -0
- package/dist/server/cli/retry/retry-resume-strategy.js.map +1 -0
- package/dist/server/cli/retry/retry-runner-factory.d.ts +11 -0
- package/dist/server/cli/retry/retry-runner-factory.d.ts.map +1 -0
- package/dist/server/cli/retry/retry-runner-factory.js +60 -0
- package/dist/server/cli/retry/retry-runner-factory.js.map +1 -0
- package/dist/server/cli/retry/retry-tool-results.d.ts +9 -0
- package/dist/server/cli/retry/retry-tool-results.d.ts.map +1 -0
- package/dist/server/cli/retry/retry-tool-results.js +24 -0
- package/dist/server/cli/retry/retry-tool-results.js.map +1 -0
- package/dist/server/cli/retry/retry-types.d.ts +30 -0
- package/dist/server/cli/retry/retry-types.d.ts.map +1 -0
- package/dist/server/cli/retry/retry-types.js +4 -0
- package/dist/server/cli/retry/retry-types.js.map +1 -0
- package/dist/server/index.js +21 -109
- package/dist/server/index.js.map +1 -1
- package/dist/server/server-setup.d.ts +16 -1
- package/dist/server/server-setup.d.ts.map +1 -1
- package/dist/server/server-setup.js +107 -0
- package/dist/server/server-setup.js.map +1 -1
- package/dist/server/services/plan/board-config.d.ts +21 -0
- package/dist/server/services/plan/board-config.d.ts.map +1 -0
- package/dist/server/services/plan/board-config.js +112 -0
- package/dist/server/services/plan/board-config.js.map +1 -0
- 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 +7 -5
- package/dist/server/services/plan/composer.js.map +1 -1
- package/dist/server/services/plan/executor.d.ts +48 -48
- package/dist/server/services/plan/executor.d.ts.map +1 -1
- package/dist/server/services/plan/executor.js +157 -455
- package/dist/server/services/plan/executor.js.map +1 -1
- package/dist/server/services/plan/issue-loader.d.ts +16 -0
- package/dist/server/services/plan/issue-loader.d.ts.map +1 -0
- package/dist/server/services/plan/issue-loader.js +46 -0
- package/dist/server/services/plan/issue-loader.js.map +1 -0
- package/dist/server/services/plan/issue-writer.d.ts +34 -0
- package/dist/server/services/plan/issue-writer.d.ts.map +1 -0
- package/dist/server/services/plan/issue-writer.js +110 -0
- package/dist/server/services/plan/issue-writer.js.map +1 -0
- package/dist/server/services/plan/output-manager.d.ts.map +1 -1
- package/dist/server/services/plan/output-manager.js +2 -1
- package/dist/server/services/plan/output-manager.js.map +1 -1
- package/dist/server/services/plan/progress-log.d.ts +11 -0
- package/dist/server/services/plan/progress-log.d.ts.map +1 -0
- package/dist/server/services/plan/progress-log.js +81 -0
- package/dist/server/services/plan/progress-log.js.map +1 -0
- package/dist/server/services/plan/prompt-builder.d.ts.map +1 -1
- package/dist/server/services/plan/prompt-builder.js +48 -31
- package/dist/server/services/plan/prompt-builder.js.map +1 -1
- package/dist/server/services/plan/readiness-planner.d.ts +15 -0
- package/dist/server/services/plan/readiness-planner.d.ts.map +1 -0
- package/dist/server/services/plan/readiness-planner.js +41 -0
- package/dist/server/services/plan/readiness-planner.js.map +1 -0
- package/dist/server/services/plan/review-gate.d.ts +31 -0
- package/dist/server/services/plan/review-gate.d.ts.map +1 -1
- package/dist/server/services/plan/review-gate.js +52 -2
- package/dist/server/services/plan/review-gate.js.map +1 -1
- package/dist/server/services/platform.d.ts +56 -0
- package/dist/server/services/platform.d.ts.map +1 -1
- package/dist/server/services/platform.js +154 -52
- package/dist/server/services/platform.js.map +1 -1
- package/dist/server/services/websocket/file-download-handler.d.ts +17 -0
- package/dist/server/services/websocket/file-download-handler.d.ts.map +1 -0
- package/dist/server/services/websocket/file-download-handler.js +165 -0
- package/dist/server/services/websocket/file-download-handler.js.map +1 -0
- package/dist/server/services/websocket/git-branch-handlers.d.ts +1 -1
- package/dist/server/services/websocket/git-branch-handlers.d.ts.map +1 -1
- package/dist/server/services/websocket/git-branch-handlers.js +21 -1
- package/dist/server/services/websocket/git-branch-handlers.js.map +1 -1
- package/dist/server/services/websocket/git-handlers.js +1 -1
- package/dist/server/services/websocket/git-handlers.js.map +1 -1
- package/dist/server/services/websocket/git-worktree-handlers.d.ts +2 -0
- package/dist/server/services/websocket/git-worktree-handlers.d.ts.map +1 -1
- package/dist/server/services/websocket/git-worktree-handlers.js +30 -4
- package/dist/server/services/websocket/git-worktree-handlers.js.map +1 -1
- package/dist/server/services/websocket/handler-context.d.ts +15 -0
- package/dist/server/services/websocket/handler-context.d.ts.map +1 -1
- package/dist/server/services/websocket/handler.d.ts +7 -0
- package/dist/server/services/websocket/handler.d.ts.map +1 -1
- package/dist/server/services/websocket/handler.js +73 -11
- package/dist/server/services/websocket/handler.js.map +1 -1
- package/dist/server/services/websocket/msg-id-tracker.d.ts +21 -0
- package/dist/server/services/websocket/msg-id-tracker.d.ts.map +1 -0
- package/dist/server/services/websocket/msg-id-tracker.js +77 -0
- package/dist/server/services/websocket/msg-id-tracker.js.map +1 -0
- package/dist/server/services/websocket/quality-handlers.js +15 -3
- package/dist/server/services/websocket/quality-handlers.js.map +1 -1
- package/dist/server/services/websocket/quality-review-agent.js +2 -2
- package/dist/server/services/websocket/session-handlers.d.ts +48 -2
- package/dist/server/services/websocket/session-handlers.d.ts.map +1 -1
- package/dist/server/services/websocket/session-handlers.js +204 -65
- package/dist/server/services/websocket/session-handlers.js.map +1 -1
- package/dist/server/services/websocket/session-initialization.d.ts +2 -2
- package/dist/server/services/websocket/session-initialization.d.ts.map +1 -1
- package/dist/server/services/websocket/session-initialization.js +75 -17
- package/dist/server/services/websocket/session-initialization.js.map +1 -1
- package/dist/server/services/websocket/session-registry.d.ts +29 -1
- package/dist/server/services/websocket/session-registry.d.ts.map +1 -1
- package/dist/server/services/websocket/session-registry.js +53 -4
- package/dist/server/services/websocket/session-registry.js.map +1 -1
- package/dist/server/services/websocket/tab-broadcast.d.ts +24 -0
- package/dist/server/services/websocket/tab-broadcast.d.ts.map +1 -0
- package/dist/server/services/websocket/tab-broadcast.js +13 -0
- package/dist/server/services/websocket/tab-broadcast.js.map +1 -0
- package/dist/server/services/websocket/tab-event-buffer.d.ts +103 -0
- package/dist/server/services/websocket/tab-event-buffer.d.ts.map +1 -0
- package/dist/server/services/websocket/tab-event-buffer.js +107 -0
- package/dist/server/services/websocket/tab-event-buffer.js.map +1 -0
- package/dist/server/services/websocket/tab-event-replay.d.ts +20 -0
- package/dist/server/services/websocket/tab-event-replay.d.ts.map +1 -0
- package/dist/server/services/websocket/tab-event-replay.js +21 -0
- package/dist/server/services/websocket/tab-event-replay.js.map +1 -0
- package/dist/server/services/websocket/tab-handlers.d.ts +0 -1
- package/dist/server/services/websocket/tab-handlers.d.ts.map +1 -1
- package/dist/server/services/websocket/tab-handlers.js +2 -9
- package/dist/server/services/websocket/tab-handlers.js.map +1 -1
- package/dist/server/services/websocket/types.d.ts +15 -6
- package/dist/server/services/websocket/types.d.ts.map +1 -1
- package/dist/server/services/websocket/types.js +6 -4
- package/dist/server/services/websocket/types.js.map +1 -1
- package/package.json +1 -1
- package/server/README.md +1 -1
- package/server/cli/headless/claude-invoker-stall.ts +7 -2
- package/server/cli/headless/claude-invoker.ts +1 -1
- package/server/cli/headless/runner.ts +67 -72
- package/server/cli/headless/stall-assessor.ts +9 -4
- package/server/cli/headless/types.ts +1 -1
- package/server/cli/improvisation-history-store.ts +62 -0
- package/server/cli/improvisation-movements.ts +120 -0
- package/server/cli/improvisation-output-queue.ts +42 -0
- package/server/cli/improvisation-retry.ts +25 -600
- package/server/cli/improvisation-session-manager.ts +74 -160
- package/server/cli/retry/retry-best-result.ts +70 -0
- package/server/cli/retry/retry-context-loss.ts +87 -0
- package/server/cli/retry/retry-premature-completion.ts +113 -0
- package/server/cli/retry/retry-recovery-strategies.ts +247 -0
- package/server/cli/retry/retry-resume-strategy.ts +33 -0
- package/server/cli/retry/retry-runner-factory.ts +70 -0
- package/server/cli/retry/retry-tool-results.ts +31 -0
- package/server/cli/retry/retry-types.ts +32 -0
- package/server/index.ts +37 -123
- package/server/server-setup.ts +126 -1
- package/server/services/plan/agents/assess-stall.md +11 -4
- package/server/services/plan/board-config.ts +122 -0
- package/server/services/plan/composer.ts +7 -5
- package/server/services/plan/executor.ts +214 -467
- package/server/services/plan/issue-loader.ts +64 -0
- package/server/services/plan/issue-writer.ts +137 -0
- package/server/services/plan/output-manager.ts +2 -1
- package/server/services/plan/progress-log.ts +92 -0
- package/server/services/plan/prompt-builder.ts +73 -35
- package/server/services/plan/readiness-planner.ts +50 -0
- package/server/services/plan/review-gate.ts +102 -2
- package/server/services/platform.ts +163 -58
- package/server/services/websocket/file-download-handler.ts +191 -0
- package/server/services/websocket/git-branch-handlers.ts +28 -1
- package/server/services/websocket/git-handlers.ts +1 -1
- package/server/services/websocket/git-worktree-handlers.ts +31 -4
- package/server/services/websocket/handler-context.ts +15 -0
- package/server/services/websocket/handler.ts +76 -12
- package/server/services/websocket/msg-id-tracker.ts +84 -0
- package/server/services/websocket/quality-handlers.ts +16 -3
- package/server/services/websocket/quality-review-agent.ts +2 -2
- package/server/services/websocket/session-handlers.ts +213 -68
- package/server/services/websocket/session-initialization.ts +83 -19
- package/server/services/websocket/session-registry.ts +61 -4
- package/server/services/websocket/tab-broadcast.ts +38 -0
- package/server/services/websocket/tab-event-buffer.ts +159 -0
- package/server/services/websocket/tab-event-replay.ts +42 -0
- package/server/services/websocket/tab-handlers.ts +2 -9
- package/server/services/websocket/types.ts +17 -4
|
@@ -2,605 +2,30 @@
|
|
|
2
2
|
// Licensed under the MIT License. See LICENSE file for details.
|
|
3
3
|
|
|
4
4
|
/**
|
|
5
|
-
* Retry and recovery logic for improvisation sessions.
|
|
6
|
-
*
|
|
5
|
+
* Retry and recovery logic for improvisation sessions — barrel re-export.
|
|
6
|
+
*
|
|
7
|
+
* Implementation lives in focused modules under ./retry/:
|
|
8
|
+
* - retry-types.ts — RetryCallbacks, RetrySessionState
|
|
9
|
+
* - retry-resume-strategy.ts — whether/how to --resume
|
|
10
|
+
* - retry-runner-factory.ts — HeadlessRunner factory for a single iteration
|
|
11
|
+
* - retry-context-loss.ts — Path 1 + Path 2 context loss detection
|
|
12
|
+
* - retry-tool-results.ts — accumulate tool results across retries
|
|
13
|
+
* - retry-recovery-strategies.ts — context-loss, tool-timeout, signal-crash retries
|
|
14
|
+
* - retry-premature-completion.ts — detect + recover from unfinished end_turn
|
|
15
|
+
* - retry-best-result.ts — pick the best result across retries
|
|
7
16
|
*/
|
|
8
17
|
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
buildRetryPrompt,
|
|
24
|
-
buildSignalCrashRecoveryPrompt,
|
|
25
|
-
extractHistoricalToolResults,
|
|
26
|
-
} from './prompt-builders.js';
|
|
27
|
-
|
|
28
|
-
/** Callbacks the retry logic needs from the session manager */
|
|
29
|
-
export interface RetryCallbacks {
|
|
30
|
-
isCancelled: () => boolean;
|
|
31
|
-
queueOutput: (text: string) => void;
|
|
32
|
-
flushOutputQueue: () => void;
|
|
33
|
-
emit: (event: string, ...args: unknown[]) => void;
|
|
34
|
-
addEventLog: (entry: { type: string; data: unknown; timestamp: number }) => void;
|
|
35
|
-
setRunner: (runner: HeadlessRunner | null) => void;
|
|
36
|
-
}
|
|
37
|
-
|
|
38
|
-
/** Session state the retry logic reads/writes */
|
|
39
|
-
export interface RetrySessionState {
|
|
40
|
-
options: ImprovisationOptions;
|
|
41
|
-
claudeSessionId: string | undefined;
|
|
42
|
-
isFirstPrompt: boolean;
|
|
43
|
-
isResumedSession: boolean;
|
|
44
|
-
history: SessionHistory;
|
|
45
|
-
executionStartTimestamp: number | undefined;
|
|
46
|
-
}
|
|
47
|
-
|
|
48
|
-
// ========== Resume Strategy ==========
|
|
49
|
-
|
|
50
|
-
/** Determine whether to use --resume and which session ID */
|
|
51
|
-
export function determineResumeStrategy(
|
|
52
|
-
state: RetryLoopState,
|
|
53
|
-
session: RetrySessionState,
|
|
54
|
-
): { useResume: boolean; resumeSessionId: string | undefined } {
|
|
55
|
-
if (state.freshRecoveryMode) {
|
|
56
|
-
state.freshRecoveryMode = false;
|
|
57
|
-
return { useResume: false, resumeSessionId: undefined };
|
|
58
|
-
}
|
|
59
|
-
if (state.contextRecoverySessionId) {
|
|
60
|
-
const id = state.contextRecoverySessionId;
|
|
61
|
-
state.contextRecoverySessionId = undefined;
|
|
62
|
-
return { useResume: true, resumeSessionId: id };
|
|
63
|
-
}
|
|
64
|
-
if (state.retryNumber === 0) {
|
|
65
|
-
return { useResume: !session.isFirstPrompt, resumeSessionId: session.claudeSessionId };
|
|
66
|
-
}
|
|
67
|
-
if (state.lastWatchdogCheckpoint?.inProgressTools.length === 0 && state.lastWatchdogCheckpoint.claudeSessionId) {
|
|
68
|
-
return { useResume: true, resumeSessionId: state.lastWatchdogCheckpoint.claudeSessionId };
|
|
69
|
-
}
|
|
70
|
-
return { useResume: false, resumeSessionId: undefined };
|
|
71
|
-
}
|
|
72
|
-
|
|
73
|
-
// ========== Runner Creation ==========
|
|
74
|
-
|
|
75
|
-
/** Create HeadlessRunner for one retry iteration */
|
|
76
|
-
export function createExecutionRunner(
|
|
77
|
-
state: RetryLoopState,
|
|
78
|
-
session: RetrySessionState,
|
|
79
|
-
callbacks: RetryCallbacks,
|
|
80
|
-
sequenceNumber: number,
|
|
81
|
-
useResume: boolean,
|
|
82
|
-
resumeSessionId: string | undefined,
|
|
83
|
-
imageAttachments: FileAttachment[] | undefined,
|
|
84
|
-
workingDirOverride?: string,
|
|
85
|
-
): HeadlessRunner {
|
|
86
|
-
return new HeadlessRunner({
|
|
87
|
-
workingDir: workingDirOverride || session.options.workingDir,
|
|
88
|
-
tokenBudgetThreshold: session.options.tokenBudgetThreshold,
|
|
89
|
-
maxSessions: session.options.maxSessions,
|
|
90
|
-
verbose: session.options.verbose,
|
|
91
|
-
noColor: session.options.noColor,
|
|
92
|
-
model: session.options.model,
|
|
93
|
-
effortLevel: session.options.effortLevel,
|
|
94
|
-
improvisationMode: true,
|
|
95
|
-
movementNumber: sequenceNumber,
|
|
96
|
-
continueSession: useResume,
|
|
97
|
-
claudeSessionId: resumeSessionId,
|
|
98
|
-
outputCallback: (text: string) => {
|
|
99
|
-
if (callbacks.isCancelled()) return;
|
|
100
|
-
callbacks.addEventLog({ type: 'output', data: { text, timestamp: Date.now() }, timestamp: Date.now() });
|
|
101
|
-
callbacks.queueOutput(text);
|
|
102
|
-
callbacks.flushOutputQueue();
|
|
103
|
-
},
|
|
104
|
-
thinkingCallback: (text: string) => {
|
|
105
|
-
if (callbacks.isCancelled()) return;
|
|
106
|
-
callbacks.addEventLog({ type: 'thinking', data: { text }, timestamp: Date.now() });
|
|
107
|
-
callbacks.emit('onThinking', text);
|
|
108
|
-
callbacks.flushOutputQueue();
|
|
109
|
-
},
|
|
110
|
-
toolUseCallback: (event) => {
|
|
111
|
-
if (callbacks.isCancelled()) return;
|
|
112
|
-
callbacks.addEventLog({ type: 'toolUse', data: { ...event, timestamp: Date.now() }, timestamp: Date.now() });
|
|
113
|
-
callbacks.emit('onToolUse', event);
|
|
114
|
-
callbacks.flushOutputQueue();
|
|
115
|
-
},
|
|
116
|
-
tokenUsageCallback: (usage) => {
|
|
117
|
-
if (callbacks.isCancelled()) return;
|
|
118
|
-
callbacks.emit('onTokenUsage', usage);
|
|
119
|
-
},
|
|
120
|
-
directPrompt: state.currentPrompt,
|
|
121
|
-
imageAttachments,
|
|
122
|
-
promptContext: (state.retryNumber === 0 && session.isResumedSession && session.isFirstPrompt)
|
|
123
|
-
? { accumulatedKnowledge: buildHistoricalContext(session.history.movements), filesModified: [] }
|
|
124
|
-
: undefined,
|
|
125
|
-
onToolTimeout: (checkpoint: ExecutionCheckpoint) => {
|
|
126
|
-
state.checkpointRef.value = checkpoint;
|
|
127
|
-
},
|
|
128
|
-
});
|
|
129
|
-
}
|
|
130
|
-
|
|
131
|
-
// ========== Context Loss Detection ==========
|
|
132
|
-
|
|
133
|
-
/** Detect resume context loss (Path 1): session expired on --resume */
|
|
134
|
-
export function detectResumeContextLoss(
|
|
135
|
-
result: HeadlessRunResult,
|
|
136
|
-
state: RetryLoopState,
|
|
137
|
-
useResume: boolean,
|
|
138
|
-
maxRetries: number,
|
|
139
|
-
nativeTimeouts: number,
|
|
140
|
-
verbose: boolean,
|
|
141
|
-
): void {
|
|
142
|
-
if (!useResume || state.checkpointRef.value || state.retryNumber >= maxRetries || nativeTimeouts > 0) {
|
|
143
|
-
return;
|
|
144
|
-
}
|
|
145
|
-
if (!result.assistantResponse || result.assistantResponse.trim().length === 0) {
|
|
146
|
-
state.contextLost = true;
|
|
147
|
-
if (verbose) hlog('[CONTEXT-RECOVERY] Resume context loss: null/empty response');
|
|
148
|
-
} else if (result.resumeBufferedOutput !== undefined) {
|
|
149
|
-
state.contextLost = true;
|
|
150
|
-
if (verbose) hlog('[CONTEXT-RECOVERY] Resume context loss: buffer never flushed (no thinking/tools)');
|
|
151
|
-
} else if (
|
|
152
|
-
(!result.toolUseHistory || result.toolUseHistory.length === 0) &&
|
|
153
|
-
!result.thinkingOutput &&
|
|
154
|
-
result.assistantResponse.length < 500
|
|
155
|
-
) {
|
|
156
|
-
state.contextLost = true;
|
|
157
|
-
if (verbose) hlog('[CONTEXT-RECOVERY] Resume context loss: no tools, no thinking, short response');
|
|
158
|
-
}
|
|
159
|
-
}
|
|
160
|
-
|
|
161
|
-
/** Detect native timeout context loss (Path 2): tool timeouts caused confusion */
|
|
162
|
-
export async function detectNativeTimeoutContextLoss(
|
|
163
|
-
result: HeadlessRunResult,
|
|
164
|
-
state: RetryLoopState,
|
|
165
|
-
maxRetries: number,
|
|
166
|
-
nativeTimeouts: number,
|
|
167
|
-
verbose: boolean,
|
|
168
|
-
): Promise<void> {
|
|
169
|
-
if (state.contextLost) return;
|
|
170
|
-
|
|
171
|
-
const succeededIds = new Set<string>();
|
|
172
|
-
const allIds = new Set<string>();
|
|
173
|
-
for (const t of result.toolUseHistory ?? []) {
|
|
174
|
-
allIds.add(t.toolId);
|
|
175
|
-
if (t.result !== undefined) succeededIds.add(t.toolId);
|
|
176
|
-
}
|
|
177
|
-
const toolsWithoutResult = [...allIds].filter(id => !succeededIds.has(id)).length;
|
|
178
|
-
const effectiveTimeouts = Math.max(nativeTimeouts, toolsWithoutResult);
|
|
179
|
-
|
|
180
|
-
if (effectiveTimeouts === 0 || !result.assistantResponse || state.checkpointRef.value || state.retryNumber >= maxRetries) {
|
|
181
|
-
return;
|
|
182
|
-
}
|
|
183
|
-
|
|
184
|
-
const writeToolNames = new Set(['Edit', 'Write', 'MultiEdit', 'NotebookEdit']);
|
|
185
|
-
const contextLossCtx: ContextLossContext = {
|
|
186
|
-
assistantResponse: result.assistantResponse,
|
|
187
|
-
effectiveTimeouts,
|
|
188
|
-
nativeTimeoutCount: nativeTimeouts,
|
|
189
|
-
successfulToolCalls: result.toolUseHistory?.filter(t => t.result !== undefined && !t.isError).length ?? 0,
|
|
190
|
-
thinkingOutputLength: result.thinkingOutput?.length ?? 0,
|
|
191
|
-
hasSuccessfulWrite: result.toolUseHistory?.some(
|
|
192
|
-
t => writeToolNames.has(t.toolName) && t.result !== undefined && !t.isError
|
|
193
|
-
) ?? false,
|
|
194
|
-
};
|
|
195
|
-
|
|
196
|
-
const claudeCmd = process.env.CLAUDE_COMMAND || 'claude';
|
|
197
|
-
const verdict = await assessContextLoss(contextLossCtx, claudeCmd, verbose);
|
|
198
|
-
state.contextLost = verdict.contextLost;
|
|
199
|
-
if (verbose) {
|
|
200
|
-
hlog(`[CONTEXT-RECOVERY] Haiku verdict: ${state.contextLost ? 'LOST' : 'OK'} — ${verdict.reason}`);
|
|
201
|
-
}
|
|
202
|
-
}
|
|
203
|
-
|
|
204
|
-
// ========== Tool Result Accumulation ==========
|
|
205
|
-
|
|
206
|
-
const MAX_ACCUMULATED_RESULTS = 50;
|
|
207
|
-
|
|
208
|
-
/** Accumulate completed tool results from a run into the retry state */
|
|
209
|
-
export function accumulateToolResults(result: HeadlessRunResult, state: RetryLoopState): void {
|
|
210
|
-
if (!result.toolUseHistory) return;
|
|
211
|
-
for (const t of result.toolUseHistory) {
|
|
212
|
-
if (t.result !== undefined) {
|
|
213
|
-
state.accumulatedToolResults.push({
|
|
214
|
-
toolName: t.toolName,
|
|
215
|
-
toolId: t.toolId,
|
|
216
|
-
toolInput: t.toolInput,
|
|
217
|
-
result: t.result,
|
|
218
|
-
isError: t.isError,
|
|
219
|
-
duration: t.duration,
|
|
220
|
-
});
|
|
221
|
-
}
|
|
222
|
-
}
|
|
223
|
-
if (state.accumulatedToolResults.length > MAX_ACCUMULATED_RESULTS) {
|
|
224
|
-
state.accumulatedToolResults = state.accumulatedToolResults.slice(-MAX_ACCUMULATED_RESULTS);
|
|
225
|
-
}
|
|
226
|
-
}
|
|
227
|
-
|
|
228
|
-
// ========== Recovery Strategies ==========
|
|
229
|
-
|
|
230
|
-
/** Handle inter-movement context loss recovery (resume session expired) */
|
|
231
|
-
export function applyInterMovementRecovery(
|
|
232
|
-
state: RetryLoopState,
|
|
233
|
-
promptWithAttachments: string,
|
|
234
|
-
history: MovementRecord[],
|
|
235
|
-
callbacks: RetryCallbacks,
|
|
236
|
-
): void {
|
|
237
|
-
const historicalResults = extractHistoricalToolResults(history);
|
|
238
|
-
const allResults = [...historicalResults, ...state.accumulatedToolResults];
|
|
239
|
-
|
|
240
|
-
callbacks.emit('onAutoRetry', {
|
|
241
|
-
retryNumber: state.retryNumber,
|
|
242
|
-
maxRetries: 3,
|
|
243
|
-
toolName: 'InterMovementRecovery',
|
|
244
|
-
completedCount: allResults.length,
|
|
245
|
-
});
|
|
246
|
-
callbacks.queueOutput(
|
|
247
|
-
`\n[[MSTRO_CONTEXT_RECOVERY]] Session context expired — continuing with ${allResults.length} preserved results from prior work (retry ${state.retryNumber}/3).\n`
|
|
248
|
-
);
|
|
249
|
-
callbacks.flushOutputQueue();
|
|
250
|
-
|
|
251
|
-
state.freshRecoveryMode = true;
|
|
252
|
-
state.currentPrompt = buildInterMovementRecoveryPrompt(promptWithAttachments, allResults, history);
|
|
253
|
-
}
|
|
254
|
-
|
|
255
|
-
/** Handle native-timeout context loss recovery (tool timeouts caused confusion) */
|
|
256
|
-
export function applyNativeTimeoutRecovery(
|
|
257
|
-
result: HeadlessRunResult,
|
|
258
|
-
state: RetryLoopState,
|
|
259
|
-
promptWithAttachments: string,
|
|
260
|
-
session: RetrySessionState,
|
|
261
|
-
callbacks: RetryCallbacks,
|
|
262
|
-
): void {
|
|
263
|
-
const completedCount = state.accumulatedToolResults.length;
|
|
264
|
-
|
|
265
|
-
callbacks.emit('onAutoRetry', {
|
|
266
|
-
retryNumber: state.retryNumber,
|
|
267
|
-
maxRetries: 3,
|
|
268
|
-
toolName: 'ContextRecovery',
|
|
269
|
-
completedCount,
|
|
270
|
-
});
|
|
271
|
-
|
|
272
|
-
if (result.claudeSessionId && state.retryNumber === 1) {
|
|
273
|
-
callbacks.queueOutput(
|
|
274
|
-
`\n[[MSTRO_CONTEXT_RECOVERY]] Context loss detected — resuming session with ${completedCount} preserved results (retry ${state.retryNumber}/3).\n`
|
|
275
|
-
);
|
|
276
|
-
callbacks.flushOutputQueue();
|
|
277
|
-
state.contextRecoverySessionId = result.claudeSessionId;
|
|
278
|
-
session.claudeSessionId = result.claudeSessionId;
|
|
279
|
-
state.currentPrompt = buildContextRecoveryPrompt(promptWithAttachments);
|
|
280
|
-
} else {
|
|
281
|
-
callbacks.queueOutput(
|
|
282
|
-
`\n[[MSTRO_CONTEXT_RECOVERY]] Continuing with fresh context — ${completedCount} preserved results injected (retry ${state.retryNumber}/3).\n`
|
|
283
|
-
);
|
|
284
|
-
callbacks.flushOutputQueue();
|
|
285
|
-
state.freshRecoveryMode = true;
|
|
286
|
-
state.currentPrompt = buildFreshRecoveryPrompt(promptWithAttachments, state.accumulatedToolResults, state.timedOutTools);
|
|
287
|
-
}
|
|
288
|
-
}
|
|
289
|
-
|
|
290
|
-
/** Check if context loss recovery should trigger. Returns true if loop should continue. */
|
|
291
|
-
export function shouldRetryContextLoss(
|
|
292
|
-
result: HeadlessRunResult,
|
|
293
|
-
state: RetryLoopState,
|
|
294
|
-
session: RetrySessionState,
|
|
295
|
-
useResume: boolean,
|
|
296
|
-
nativeTimeouts: number,
|
|
297
|
-
maxRetries: number,
|
|
298
|
-
promptWithAttachments: string,
|
|
299
|
-
callbacks: RetryCallbacks,
|
|
300
|
-
): boolean {
|
|
301
|
-
if (state.checkpointRef.value || state.retryNumber >= maxRetries || !state.contextLost) {
|
|
302
|
-
return false;
|
|
303
|
-
}
|
|
304
|
-
accumulateToolResults(result, state);
|
|
305
|
-
state.retryNumber++;
|
|
306
|
-
const path = (useResume && nativeTimeouts === 0) ? 'InterMovementRecovery' : 'NativeTimeoutRecovery';
|
|
307
|
-
state.retryLog.push({
|
|
308
|
-
retryNumber: state.retryNumber,
|
|
309
|
-
path,
|
|
310
|
-
reason: `Context lost (${nativeTimeouts} timeouts, ${state.accumulatedToolResults.length} tools preserved)`,
|
|
311
|
-
timestamp: Date.now(),
|
|
312
|
-
});
|
|
313
|
-
if (useResume && nativeTimeouts === 0) {
|
|
314
|
-
applyInterMovementRecovery(state, promptWithAttachments, session.history.movements, callbacks);
|
|
315
|
-
} else {
|
|
316
|
-
applyNativeTimeoutRecovery(result, state, promptWithAttachments, session, callbacks);
|
|
317
|
-
}
|
|
318
|
-
return true;
|
|
319
|
-
}
|
|
320
|
-
|
|
321
|
-
/** Handle tool timeout checkpoint. Returns true if loop should continue. */
|
|
322
|
-
export function applyToolTimeoutRetry(
|
|
323
|
-
state: RetryLoopState,
|
|
324
|
-
maxRetries: number,
|
|
325
|
-
promptWithAttachments: string,
|
|
326
|
-
callbacks: RetryCallbacks,
|
|
327
|
-
model: string | undefined,
|
|
328
|
-
effortLevel: string | undefined,
|
|
329
|
-
): boolean {
|
|
330
|
-
if (!state.checkpointRef.value || state.retryNumber >= maxRetries) {
|
|
331
|
-
return false;
|
|
332
|
-
}
|
|
333
|
-
|
|
334
|
-
const cp: ExecutionCheckpoint = state.checkpointRef.value;
|
|
335
|
-
state.retryNumber++;
|
|
336
|
-
|
|
337
|
-
state.timedOutTools.push({
|
|
338
|
-
toolName: cp.hungTool.toolName,
|
|
339
|
-
input: cp.hungTool.input ?? {},
|
|
340
|
-
timeoutMs: cp.hungTool.timeoutMs,
|
|
341
|
-
});
|
|
342
|
-
|
|
343
|
-
const canResumeSession = cp.inProgressTools.length === 0 && !!cp.claudeSessionId;
|
|
344
|
-
state.retryLog.push({
|
|
345
|
-
retryNumber: state.retryNumber,
|
|
346
|
-
path: 'ToolTimeout',
|
|
347
|
-
reason: `${cp.hungTool.toolName} timed out after ${cp.hungTool.timeoutMs}ms, ${cp.completedTools.length} tools completed, ${canResumeSession ? 'resuming' : 'fresh start'}`,
|
|
348
|
-
timestamp: Date.now(),
|
|
349
|
-
});
|
|
350
|
-
callbacks.emit('onAutoRetry', {
|
|
351
|
-
retryNumber: state.retryNumber,
|
|
352
|
-
maxRetries,
|
|
353
|
-
toolName: cp.hungTool.toolName,
|
|
354
|
-
url: cp.hungTool.url,
|
|
355
|
-
completedCount: cp.completedTools.length,
|
|
356
|
-
});
|
|
357
|
-
|
|
358
|
-
trackEvent(AnalyticsEvents.IMPROVISE_AUTO_RETRY, {
|
|
359
|
-
retry_number: state.retryNumber,
|
|
360
|
-
hung_tool: cp.hungTool.toolName,
|
|
361
|
-
hung_url: cp.hungTool.url?.slice(0, 200),
|
|
362
|
-
completed_tools: cp.completedTools.length,
|
|
363
|
-
elapsed_ms: cp.elapsedMs,
|
|
364
|
-
resume_attempted: canResumeSession,
|
|
365
|
-
model: model || 'default',
|
|
366
|
-
effort_level: effortLevel || 'auto',
|
|
367
|
-
});
|
|
368
|
-
|
|
369
|
-
state.currentPrompt = canResumeSession
|
|
370
|
-
? buildResumeRetryPrompt(cp, state.timedOutTools)
|
|
371
|
-
: buildRetryPrompt(cp, promptWithAttachments, state.timedOutTools);
|
|
372
|
-
|
|
373
|
-
callbacks.queueOutput(
|
|
374
|
-
`\n[[MSTRO_AUTO_RETRY]] Auto-retry ${state.retryNumber}/${maxRetries}: ${canResumeSession ? 'Resuming session' : 'Continuing'} with ${cp.completedTools.length} successful results, skipping failed ${cp.hungTool.toolName}.\n`
|
|
375
|
-
);
|
|
376
|
-
callbacks.flushOutputQueue();
|
|
377
|
-
|
|
378
|
-
return true;
|
|
379
|
-
}
|
|
380
|
-
|
|
381
|
-
/** Detect and retry after a signal crash. Returns true if loop should continue. */
|
|
382
|
-
export function shouldRetrySignalCrash(
|
|
383
|
-
result: HeadlessRunResult,
|
|
384
|
-
state: RetryLoopState,
|
|
385
|
-
session: RetrySessionState,
|
|
386
|
-
maxRetries: number,
|
|
387
|
-
promptWithAttachments: string,
|
|
388
|
-
callbacks: RetryCallbacks,
|
|
389
|
-
): boolean {
|
|
390
|
-
const isSignalCrash = !!result.signalName;
|
|
391
|
-
const exitCodeSignal = !result.completed && !result.signalName && result.error?.match(/exited with code (1[2-9]\d|[2-9]\d{2})/);
|
|
392
|
-
if ((!isSignalCrash && !exitCodeSignal) || state.retryNumber >= maxRetries) {
|
|
393
|
-
return false;
|
|
394
|
-
}
|
|
395
|
-
if (state.checkpointRef.value) {
|
|
396
|
-
return false;
|
|
397
|
-
}
|
|
398
|
-
|
|
399
|
-
accumulateToolResults(result, state);
|
|
400
|
-
state.retryNumber++;
|
|
401
|
-
|
|
402
|
-
const completedCount = state.accumulatedToolResults.length;
|
|
403
|
-
const signalInfo = result.signalName || 'unknown signal';
|
|
404
|
-
const useResume = !!result.claudeSessionId && state.retryNumber === 1;
|
|
405
|
-
|
|
406
|
-
state.retryLog.push({
|
|
407
|
-
retryNumber: state.retryNumber,
|
|
408
|
-
path: 'SignalCrash',
|
|
409
|
-
reason: `Process killed (${signalInfo}), ${completedCount} tools preserved, ${useResume ? 'resuming' : 'fresh start'}`,
|
|
410
|
-
timestamp: Date.now(),
|
|
411
|
-
});
|
|
412
|
-
|
|
413
|
-
callbacks.emit('onAutoRetry', {
|
|
414
|
-
retryNumber: state.retryNumber,
|
|
415
|
-
maxRetries,
|
|
416
|
-
toolName: `SignalCrash(${signalInfo})`,
|
|
417
|
-
completedCount,
|
|
418
|
-
});
|
|
419
|
-
|
|
420
|
-
trackEvent(AnalyticsEvents.IMPROVISE_AUTO_RETRY, {
|
|
421
|
-
retry_number: state.retryNumber,
|
|
422
|
-
hung_tool: `signal_crash:${signalInfo}`,
|
|
423
|
-
completed_tools: completedCount,
|
|
424
|
-
resume_attempted: useResume,
|
|
425
|
-
});
|
|
426
|
-
|
|
427
|
-
if (useResume) {
|
|
428
|
-
callbacks.queueOutput(
|
|
429
|
-
`\n[[MSTRO_SIGNAL_RECOVERY]] Process killed (${signalInfo}) — resuming session with ${completedCount} preserved results (retry ${state.retryNumber}/${maxRetries}).\n`
|
|
430
|
-
);
|
|
431
|
-
callbacks.flushOutputQueue();
|
|
432
|
-
state.contextRecoverySessionId = result.claudeSessionId;
|
|
433
|
-
session.claudeSessionId = result.claudeSessionId;
|
|
434
|
-
state.currentPrompt = buildSignalCrashRecoveryPrompt(promptWithAttachments, true);
|
|
435
|
-
} else {
|
|
436
|
-
callbacks.queueOutput(
|
|
437
|
-
`\n[[MSTRO_SIGNAL_RECOVERY]] Process killed (${signalInfo}) — restarting with ${completedCount} preserved results (retry ${state.retryNumber}/${maxRetries}).\n`
|
|
438
|
-
);
|
|
439
|
-
callbacks.flushOutputQueue();
|
|
440
|
-
state.freshRecoveryMode = true;
|
|
441
|
-
const allResults = [...extractHistoricalToolResults(session.history.movements), ...state.accumulatedToolResults];
|
|
442
|
-
state.currentPrompt = buildSignalCrashRecoveryPrompt(promptWithAttachments, false, allResults);
|
|
443
|
-
}
|
|
444
|
-
|
|
445
|
-
return true;
|
|
446
|
-
}
|
|
447
|
-
|
|
448
|
-
// ========== Premature Completion ==========
|
|
449
|
-
|
|
450
|
-
/** Guard checks for premature completion */
|
|
451
|
-
function isPrematureCompletionCandidate(
|
|
452
|
-
result: HeadlessRunResult,
|
|
453
|
-
state: RetryLoopState,
|
|
454
|
-
maxRetries: number,
|
|
455
|
-
): boolean {
|
|
456
|
-
if (!result.completed || result.signalName || state.retryNumber >= maxRetries) return false;
|
|
457
|
-
if (state.checkpointRef.value || state.contextLost) return false;
|
|
458
|
-
if (!result.claudeSessionId || !result.stopReason) return false;
|
|
459
|
-
return result.stopReason === 'max_tokens' || result.stopReason === 'end_turn';
|
|
460
|
-
}
|
|
461
|
-
|
|
462
|
-
/** Use Haiku to assess whether an end_turn response is genuinely complete */
|
|
463
|
-
async function assessEndTurnCompletion(result: HeadlessRunResult, verbose: boolean): Promise<boolean> {
|
|
464
|
-
if (!result.assistantResponse) return false;
|
|
465
|
-
|
|
466
|
-
const successfulToolCalls = result.toolUseHistory?.filter(t => t.result !== undefined && !t.isError).length ?? 0;
|
|
467
|
-
const claudeCmd = process.env.CLAUDE_COMMAND || 'claude';
|
|
468
|
-
const verdict = await assessPrematureCompletion({
|
|
469
|
-
responseTail: extractFinalTextBlock(result.assistantResponse, 800),
|
|
470
|
-
successfulToolCalls,
|
|
471
|
-
hasThinking: !!result.thinkingOutput,
|
|
472
|
-
responseLength: result.assistantResponse.length,
|
|
473
|
-
}, claudeCmd, verbose);
|
|
474
|
-
|
|
475
|
-
if (verbose) {
|
|
476
|
-
hlog(`[PREMATURE-COMPLETION] Haiku verdict: ${verdict.isIncomplete ? 'INCOMPLETE' : 'COMPLETE'} — ${verdict.reason}`);
|
|
477
|
-
}
|
|
478
|
-
return verdict.isIncomplete;
|
|
479
|
-
}
|
|
480
|
-
|
|
481
|
-
/** Apply premature completion retry */
|
|
482
|
-
function applyPrematureCompletionRetry(
|
|
483
|
-
result: HeadlessRunResult,
|
|
484
|
-
state: RetryLoopState,
|
|
485
|
-
session: RetrySessionState,
|
|
486
|
-
maxRetries: number,
|
|
487
|
-
stopReason: string,
|
|
488
|
-
isMaxTokens: boolean,
|
|
489
|
-
callbacks: RetryCallbacks,
|
|
490
|
-
): void {
|
|
491
|
-
state.retryNumber++;
|
|
492
|
-
const reason = isMaxTokens ? 'Output limit reached' : 'Task appears unfinished (AI assessment)';
|
|
493
|
-
|
|
494
|
-
state.retryLog.push({
|
|
495
|
-
retryNumber: state.retryNumber,
|
|
496
|
-
path: 'PrematureCompletion',
|
|
497
|
-
reason,
|
|
498
|
-
timestamp: Date.now(),
|
|
499
|
-
});
|
|
500
|
-
|
|
501
|
-
callbacks.emit('onAutoRetry', {
|
|
502
|
-
retryNumber: state.retryNumber,
|
|
503
|
-
maxRetries,
|
|
504
|
-
toolName: `PrematureCompletion(${stopReason})`,
|
|
505
|
-
completedCount: result.toolUseHistory?.length ?? 0,
|
|
506
|
-
});
|
|
507
|
-
|
|
508
|
-
trackEvent(AnalyticsEvents.IMPROVISE_AUTO_RETRY, {
|
|
509
|
-
retry_number: state.retryNumber,
|
|
510
|
-
hung_tool: `premature_completion:${stopReason}`,
|
|
511
|
-
completed_tools: result.toolUseHistory?.length ?? 0,
|
|
512
|
-
resume_attempted: true,
|
|
513
|
-
});
|
|
514
|
-
|
|
515
|
-
callbacks.queueOutput(
|
|
516
|
-
`\n${reason} — resuming session (retry ${state.retryNumber}/${maxRetries}).\n`
|
|
517
|
-
);
|
|
518
|
-
callbacks.flushOutputQueue();
|
|
519
|
-
|
|
520
|
-
state.contextRecoverySessionId = result.claudeSessionId;
|
|
521
|
-
session.claudeSessionId = result.claudeSessionId;
|
|
522
|
-
state.currentPrompt = 'continue';
|
|
523
|
-
}
|
|
524
|
-
|
|
525
|
-
/** Detect and retry premature completion. Returns true if loop should continue. */
|
|
526
|
-
export async function shouldRetryPrematureCompletion(
|
|
527
|
-
result: HeadlessRunResult,
|
|
528
|
-
state: RetryLoopState,
|
|
529
|
-
session: RetrySessionState,
|
|
530
|
-
maxRetries: number,
|
|
531
|
-
callbacks: RetryCallbacks,
|
|
532
|
-
): Promise<boolean> {
|
|
533
|
-
if (!isPrematureCompletionCandidate(result, state, maxRetries)) {
|
|
534
|
-
return false;
|
|
535
|
-
}
|
|
536
|
-
|
|
537
|
-
const stopReason = result.stopReason!;
|
|
538
|
-
const isMaxTokens = stopReason === 'max_tokens';
|
|
539
|
-
const abandoned = isResponseAbandoned(result);
|
|
540
|
-
const isIncomplete = isMaxTokens || abandoned || await assessEndTurnCompletion(result, session.options.verbose);
|
|
541
|
-
|
|
542
|
-
if (!isIncomplete) return false;
|
|
543
|
-
|
|
544
|
-
applyPrematureCompletionRetry(result, state, session, maxRetries, stopReason, isMaxTokens, callbacks);
|
|
545
|
-
return true;
|
|
546
|
-
}
|
|
547
|
-
|
|
548
|
-
// ========== Best Result Selection ==========
|
|
549
|
-
|
|
550
|
-
/** Select the best result across retries using Haiku assessment */
|
|
551
|
-
export async function selectBestResult(
|
|
552
|
-
state: RetryLoopState,
|
|
553
|
-
result: HeadlessRunResult,
|
|
554
|
-
userPrompt: string,
|
|
555
|
-
verbose: boolean,
|
|
556
|
-
): Promise<HeadlessRunResult> {
|
|
557
|
-
if (!state.bestResult || state.bestResult === result || state.retryNumber === 0) {
|
|
558
|
-
return result;
|
|
559
|
-
}
|
|
560
|
-
|
|
561
|
-
const claudeCmd = process.env.CLAUDE_COMMAND || 'claude';
|
|
562
|
-
const bestToolCount = state.bestResult.toolUseHistory?.filter(t => t.result !== undefined && !t.isError).length ?? 0;
|
|
563
|
-
const currentToolCount = result.toolUseHistory?.filter(t => t.result !== undefined && !t.isError).length ?? 0;
|
|
564
|
-
|
|
565
|
-
try {
|
|
566
|
-
const verdict = await assessBestResult({
|
|
567
|
-
originalPrompt: userPrompt,
|
|
568
|
-
resultA: {
|
|
569
|
-
successfulToolCalls: bestToolCount,
|
|
570
|
-
responseLength: state.bestResult.assistantResponse?.length ?? 0,
|
|
571
|
-
hasThinking: !!state.bestResult.thinkingOutput,
|
|
572
|
-
responseTail: (state.bestResult.assistantResponse ?? '').slice(-500),
|
|
573
|
-
},
|
|
574
|
-
resultB: {
|
|
575
|
-
successfulToolCalls: currentToolCount,
|
|
576
|
-
responseLength: result.assistantResponse?.length ?? 0,
|
|
577
|
-
hasThinking: !!result.thinkingOutput,
|
|
578
|
-
responseTail: (result.assistantResponse ?? '').slice(-500),
|
|
579
|
-
},
|
|
580
|
-
}, claudeCmd, verbose);
|
|
581
|
-
|
|
582
|
-
if (verdict.winner === 'A') {
|
|
583
|
-
if (verbose) hlog(`[BEST-RESULT] Haiku picked earlier attempt: ${verdict.reason}`);
|
|
584
|
-
return mergeResultSessionId(state.bestResult, result.claudeSessionId);
|
|
585
|
-
}
|
|
586
|
-
if (verbose) hlog(`[BEST-RESULT] Haiku picked final attempt: ${verdict.reason}`);
|
|
587
|
-
return result;
|
|
588
|
-
} catch {
|
|
589
|
-
return fallbackBestResult(state.bestResult, result, verbose);
|
|
590
|
-
}
|
|
591
|
-
}
|
|
592
|
-
|
|
593
|
-
function mergeResultSessionId(result: HeadlessRunResult, sessionId: string | undefined): HeadlessRunResult {
|
|
594
|
-
if (sessionId) return { ...result, claudeSessionId: sessionId };
|
|
595
|
-
return result;
|
|
596
|
-
}
|
|
597
|
-
|
|
598
|
-
function fallbackBestResult(bestResult: HeadlessRunResult, result: HeadlessRunResult, verbose: boolean): HeadlessRunResult {
|
|
599
|
-
if (scoreRunResult(bestResult) > scoreRunResult(result)) {
|
|
600
|
-
if (verbose) {
|
|
601
|
-
hlog(`[BEST-RESULT] Haiku unavailable, numeric fallback: earlier attempt (score ${scoreRunResult(bestResult)} vs ${scoreRunResult(result)})`);
|
|
602
|
-
}
|
|
603
|
-
return mergeResultSessionId(bestResult, result.claudeSessionId);
|
|
604
|
-
}
|
|
605
|
-
return result;
|
|
606
|
-
}
|
|
18
|
+
export { selectBestResult } from './retry/retry-best-result.js';
|
|
19
|
+
export { detectNativeTimeoutContextLoss, detectResumeContextLoss } from './retry/retry-context-loss.js';
|
|
20
|
+
export { shouldRetryPrematureCompletion } from './retry/retry-premature-completion.js';
|
|
21
|
+
export {
|
|
22
|
+
applyInterMovementRecovery,
|
|
23
|
+
applyNativeTimeoutRecovery,
|
|
24
|
+
applyToolTimeoutRetry,
|
|
25
|
+
shouldRetryContextLoss,
|
|
26
|
+
shouldRetrySignalCrash,
|
|
27
|
+
} from './retry/retry-recovery-strategies.js';
|
|
28
|
+
export { determineResumeStrategy } from './retry/retry-resume-strategy.js';
|
|
29
|
+
export { createExecutionRunner } from './retry/retry-runner-factory.js';
|
|
30
|
+
export { accumulateToolResults } from './retry/retry-tool-results.js';
|
|
31
|
+
export type { RetryCallbacks, RetrySessionState } from './retry/retry-types.js';
|