march-cli 0.1.41 → 0.1.42
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/package.json
CHANGED
package/src/agent/runner.mjs
CHANGED
|
@@ -18,7 +18,7 @@ import { resolveRunnerSessionOptions } from "./session/session-options.mjs";
|
|
|
18
18
|
import { createSessionBinding } from "./session/session-binding.mjs";
|
|
19
19
|
import { maybeAutoNameSession } from "./session/session-auto-name.mjs";
|
|
20
20
|
import { MARCH_BASE_TOOL_NAMES } from "./tool-names.mjs";
|
|
21
|
-
import { runRunnerTurn } from "./turn/turn-runner.mjs";
|
|
21
|
+
import { MODEL_STREAM_IDLE_TIMEOUT_CODE, runRunnerTurn } from "./turn/turn-runner.mjs";
|
|
22
22
|
import { beginLoggedTurn } from "./turn/turn-logging.mjs";
|
|
23
23
|
import { appendFastVariants, createFastModelEntry, fromFastEntryModel, isFastProvider } from "./runner/fast-model.mjs";
|
|
24
24
|
import { registerSuperGrokProvider } from "../supergrok/provider.mjs";
|
|
@@ -142,6 +142,7 @@ export async function createRunner({ cwd, modelId = null, provider = null, provi
|
|
|
142
142
|
turnLog.endSuccess(result);
|
|
143
143
|
return result;
|
|
144
144
|
} catch (err) {
|
|
145
|
+
if (err?.code === MODEL_STREAM_IDLE_TIMEOUT_CODE) nextTurnContextMode = "continueExistingPiTranscript";
|
|
145
146
|
notifyTurnEndDetached(turnNotifier, {
|
|
146
147
|
status: "error",
|
|
147
148
|
sessionName: engine.sessionName,
|
|
@@ -12,6 +12,8 @@ export function createTurnEventState() {
|
|
|
12
12
|
assistantContextParts: [],
|
|
13
13
|
activeToolContextPart: null,
|
|
14
14
|
toolCalls: [],
|
|
15
|
+
lastAssistantStopReason: null,
|
|
16
|
+
lastAssistantErrorMessage: null,
|
|
15
17
|
};
|
|
16
18
|
}
|
|
17
19
|
|
|
@@ -19,6 +21,10 @@ export function handleRunnerSessionEvent(event, { ui, engine, state }) {
|
|
|
19
21
|
if (event.type === "message_update" && event.assistantMessageEvent) {
|
|
20
22
|
handleAssistantMessageEvent(event.assistantMessageEvent, { ui, state });
|
|
21
23
|
}
|
|
24
|
+
if (event.type === "message_end" && event.message?.role === "assistant") {
|
|
25
|
+
state.lastAssistantStopReason = event.message.stopReason ?? null;
|
|
26
|
+
state.lastAssistantErrorMessage = event.message.errorMessage ?? null;
|
|
27
|
+
}
|
|
22
28
|
if (event.type === "tool_execution_start") {
|
|
23
29
|
closeAssistantReply({ ui, state });
|
|
24
30
|
appendToolStartContext(state, event.toolName, event.args);
|
|
@@ -2,6 +2,8 @@ import { formatRecallHints } from "../../memory/markdown-store.mjs";
|
|
|
2
2
|
import { resolveImageAttachmentReferences } from "../../session/attachment-references.mjs";
|
|
3
3
|
import { closeAssistantReply, compactAssistantContext, createTurnEventState, handleRunnerSessionEvent } from "./turn-events.mjs";
|
|
4
4
|
|
|
5
|
+
export const MODEL_STREAM_IDLE_TIMEOUT_CODE = "MODEL_STREAM_IDLE_TIMEOUT";
|
|
6
|
+
|
|
5
7
|
export async function runRunnerTurn({
|
|
6
8
|
prompt,
|
|
7
9
|
userMessage,
|
|
@@ -26,17 +28,33 @@ export async function runRunnerTurn({
|
|
|
26
28
|
const activeSession = sessionBinding.get();
|
|
27
29
|
const turnState = createTurnEventState();
|
|
28
30
|
const midTurnRecallHints = [];
|
|
31
|
+
const idleWatchdog = createModelStreamIdleWatchdog({ session: activeSession, logger, setPhase });
|
|
29
32
|
ui.turnStart();
|
|
30
33
|
setPhase?.("subscribed");
|
|
31
34
|
logger?.event("turn.ui.start");
|
|
32
35
|
|
|
33
36
|
const unsubscribe = activeSession.subscribe((event) => {
|
|
34
37
|
logSessionEvent(logger, event);
|
|
35
|
-
if (event.type === "tool_execution_start")
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
if (event.type === "
|
|
38
|
+
if (event.type === "tool_execution_start") {
|
|
39
|
+
idleWatchdog.pause();
|
|
40
|
+
setPhase?.(`tool_running:${event.toolName ?? "unknown"}`);
|
|
41
|
+
}
|
|
42
|
+
if (event.type === "tool_execution_end") {
|
|
43
|
+
setPhase?.("model_streaming");
|
|
44
|
+
idleWatchdog.arm("tool_execution_end");
|
|
45
|
+
}
|
|
46
|
+
if (event.type === "auto_retry_start") {
|
|
47
|
+
idleWatchdog.pause();
|
|
48
|
+
setPhase?.("retry_wait");
|
|
49
|
+
}
|
|
50
|
+
if (event.type === "auto_retry_end") {
|
|
51
|
+
setPhase?.("model_streaming");
|
|
52
|
+
idleWatchdog.arm("auto_retry_end");
|
|
53
|
+
}
|
|
54
|
+
if (event.type === "message_update") {
|
|
55
|
+
setPhase?.("model_streaming");
|
|
56
|
+
idleWatchdog.arm("message_update");
|
|
57
|
+
}
|
|
40
58
|
handleRunnerSessionEvent(event, { ui, engine, state: turnState });
|
|
41
59
|
if (event.type === "tool_execution_start") {
|
|
42
60
|
const hints = flushAssistantRecall({ memoryStore, engine, turnState, currentProject });
|
|
@@ -59,11 +77,14 @@ export async function runRunnerTurn({
|
|
|
59
77
|
logger?.event("model.prompt.start", { contextMode });
|
|
60
78
|
try {
|
|
61
79
|
if (contextMode === "rebuild") resetPiMessageHistory(activeSession);
|
|
62
|
-
|
|
80
|
+
idleWatchdog.arm("model_request");
|
|
81
|
+
await idleWatchdog.watch(activeSession.prompt(
|
|
63
82
|
contextMode === "continueExistingPiTranscript" ? (userMessage ?? prompt) : prompt,
|
|
64
83
|
attachmentReferences.images.length > 0 ? { images: attachmentReferences.images } : undefined,
|
|
65
|
-
);
|
|
84
|
+
));
|
|
85
|
+
throwIfAssistantEndedWithError(turnState);
|
|
66
86
|
} finally {
|
|
87
|
+
idleWatchdog.clear();
|
|
67
88
|
setModelCallKind("model");
|
|
68
89
|
logger?.event("model.prompt.end");
|
|
69
90
|
}
|
|
@@ -86,11 +107,70 @@ export async function runRunnerTurn({
|
|
|
86
107
|
return { draft: turnState.draft };
|
|
87
108
|
} finally {
|
|
88
109
|
logger?.event("turn.ui.end");
|
|
110
|
+
idleWatchdog.clear();
|
|
89
111
|
ui.turnEnd();
|
|
90
112
|
unsubscribe();
|
|
91
113
|
}
|
|
92
114
|
}
|
|
93
115
|
|
|
116
|
+
function createModelStreamIdleWatchdog({ session, logger, setPhase }) {
|
|
117
|
+
const timeoutMs = getModelStreamIdleTimeoutMs();
|
|
118
|
+
let timer = null;
|
|
119
|
+
let timedOut = false;
|
|
120
|
+
let rejectIdle = null;
|
|
121
|
+
const idlePromise = new Promise((_, reject) => {
|
|
122
|
+
rejectIdle = reject;
|
|
123
|
+
});
|
|
124
|
+
|
|
125
|
+
return {
|
|
126
|
+
arm(reason) {
|
|
127
|
+
if (timeoutMs <= 0 || timedOut) return;
|
|
128
|
+
clearTimer();
|
|
129
|
+
timer = setTimeout(() => {
|
|
130
|
+
timedOut = true;
|
|
131
|
+
setPhase?.("model_idle_timeout");
|
|
132
|
+
logger?.event("model.stream.idle_timeout", { timeoutMs, reason });
|
|
133
|
+
try { session.abortRetry?.(); } catch {}
|
|
134
|
+
try { session.abort?.(); } catch {}
|
|
135
|
+
rejectIdle(createModelStreamIdleTimeoutError(timeoutMs, reason));
|
|
136
|
+
}, timeoutMs);
|
|
137
|
+
},
|
|
138
|
+
pause: clearTimer,
|
|
139
|
+
clear: clearTimer,
|
|
140
|
+
async watch(promise) {
|
|
141
|
+
const guarded = Promise.resolve(promise);
|
|
142
|
+
guarded.catch(() => {});
|
|
143
|
+
return await Promise.race([guarded, idlePromise]);
|
|
144
|
+
},
|
|
145
|
+
};
|
|
146
|
+
|
|
147
|
+
function clearTimer() {
|
|
148
|
+
if (!timer) return;
|
|
149
|
+
clearTimeout(timer);
|
|
150
|
+
timer = null;
|
|
151
|
+
}
|
|
152
|
+
}
|
|
153
|
+
|
|
154
|
+
function createModelStreamIdleTimeoutError(timeoutMs, reason) {
|
|
155
|
+
const error = new Error(`Model stream idle timeout after ${timeoutMs}ms (${reason}); aborted the current turn`);
|
|
156
|
+
error.code = MODEL_STREAM_IDLE_TIMEOUT_CODE;
|
|
157
|
+
return error;
|
|
158
|
+
}
|
|
159
|
+
|
|
160
|
+
function getModelStreamIdleTimeoutMs() {
|
|
161
|
+
const raw = process.env.MARCH_MODEL_STREAM_IDLE_TIMEOUT_MS;
|
|
162
|
+
if (raw === "0" || raw === "false" || raw === "no") return 0;
|
|
163
|
+
const parsed = raw ? Number.parseInt(raw, 10) : 18000;
|
|
164
|
+
return Number.isFinite(parsed) && parsed > 0 ? parsed : 18000;
|
|
165
|
+
}
|
|
166
|
+
|
|
167
|
+
function throwIfAssistantEndedWithError(turnState) {
|
|
168
|
+
if (turnState.lastAssistantStopReason !== "error") return;
|
|
169
|
+
const error = new Error(turnState.lastAssistantErrorMessage || "Model provider returned an error");
|
|
170
|
+
error.code = "MODEL_PROVIDER_ERROR";
|
|
171
|
+
throw error;
|
|
172
|
+
}
|
|
173
|
+
|
|
94
174
|
function queueMidTurnRecallHints(session, hints, logger) {
|
|
95
175
|
const content = formatRecallHints("assistant", hints);
|
|
96
176
|
if (!content) return;
|