@yagni-app/code-staging 1.2.2-staging.1530.1 → 1.2.2-staging.1555.1
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.
|
@@ -37,6 +37,8 @@ export type RunStageFn = (stage: PipelineStage, ctx: {
|
|
|
37
37
|
lens?: ReviewLens;
|
|
38
38
|
grounded?: boolean;
|
|
39
39
|
}, deps: RunStageDeps) => Promise<StageResult>;
|
|
40
|
+
/** True when at least {@link MODEL_BOUND_SHARE} of a stage's elapsed time was spent outside tools. */
|
|
41
|
+
export declare function isModelBound(elapsedMs: number, toolMs: number): boolean;
|
|
40
42
|
/** One telemetry record per attempt (routed through the orchestrator logger). */
|
|
41
43
|
export interface ResilienceAttemptRecord {
|
|
42
44
|
stageId: string;
|
|
@@ -54,6 +56,10 @@ export interface ResilienceAttemptRecord {
|
|
|
54
56
|
outcome: "ok" | "transient" | "fatal" | "timeout" | "aborted";
|
|
55
57
|
/** Which timer fired, when the attempt timed out. */
|
|
56
58
|
timeoutKind?: "idle" | "wall";
|
|
59
|
+
/** On a timeout: ms the attempt spent with a tool running (the rest went to the model). */
|
|
60
|
+
toolMs?: number;
|
|
61
|
+
/** On a timeout: whether the model, not tools, held most of the attempt's time. */
|
|
62
|
+
modelBound?: boolean;
|
|
57
63
|
exitCode: number;
|
|
58
64
|
stopReason?: string;
|
|
59
65
|
elapsedMs: number;
|
|
@@ -32,7 +32,34 @@ const WRITE_STAGE_IDS = ["implement", "fix"];
|
|
|
32
32
|
/** Honest message stamped on a stage we aborted for exceeding its time budget. */
|
|
33
33
|
const TIMEOUT_MESSAGE = "stage exceeded its idle or wall-clock timeout";
|
|
34
34
|
const WALL_TIMEOUT_MESSAGE = "stage exceeded its wall-clock timeout";
|
|
35
|
+
/**
|
|
36
|
+
* The wall clock fired while most of the stage's time went to waiting on model
|
|
37
|
+
* responses rather than running tools: a slow model, not a stage too big for one
|
|
38
|
+
* pass. Keeps {@link WALL_TIMEOUT_MESSAGE} as its prefix so a reader that only
|
|
39
|
+
* knows the plain sentence still reads it as a wall-clock stop.
|
|
40
|
+
*/
|
|
41
|
+
const WALL_TIMEOUT_MODEL_MESSAGE = `${WALL_TIMEOUT_MESSAGE} while waiting on the model`;
|
|
42
|
+
/**
|
|
43
|
+
* Share of a timed-out stage's elapsed time outside tools at which the model,
|
|
44
|
+
* not the work, used the clock. The non-tool time also holds the child's
|
|
45
|
+
* startup and its teardown after the abort (seconds), which is noise against a
|
|
46
|
+
* real wall window of many minutes.
|
|
47
|
+
*/
|
|
48
|
+
const MODEL_BOUND_SHARE = 0.6;
|
|
35
49
|
const IDLE_TIMEOUT_MESSAGE = "stage exceeded its idle timeout";
|
|
50
|
+
/** True when at least {@link MODEL_BOUND_SHARE} of a stage's elapsed time was spent outside tools. */
|
|
51
|
+
export function isModelBound(elapsedMs, toolMs) {
|
|
52
|
+
if (elapsedMs <= 0)
|
|
53
|
+
return false;
|
|
54
|
+
return (elapsedMs - toolMs) / elapsedMs >= MODEL_BOUND_SHARE;
|
|
55
|
+
}
|
|
56
|
+
function timeoutMessage(kind, modelBound) {
|
|
57
|
+
if (kind === "wall")
|
|
58
|
+
return modelBound ? WALL_TIMEOUT_MODEL_MESSAGE : WALL_TIMEOUT_MESSAGE;
|
|
59
|
+
if (kind === "idle")
|
|
60
|
+
return IDLE_TIMEOUT_MESSAGE;
|
|
61
|
+
return TIMEOUT_MESSAGE;
|
|
62
|
+
}
|
|
36
63
|
/** stderr fingerprints of a transient transport/provider blip worth retrying. */
|
|
37
64
|
const TRANSIENT_STDERR = /\b429\b|\b5\d\d\b|ECONNRESET|ETIMEDOUT|ENOTFOUND|EAI_AGAIN|socket hang up/i;
|
|
38
65
|
/**
|
|
@@ -117,6 +144,11 @@ export function withResilience(base, policy, opts = {}) {
|
|
|
117
144
|
// and re-arms instead of aborting; the wall-clock timer stays the
|
|
118
145
|
// backstop for a tool that is genuinely hung.
|
|
119
146
|
let inFlightTools = 0;
|
|
147
|
+
// Time spent with at least one tool running. The rest of the stage's
|
|
148
|
+
// elapsed time is the model thinking or streaming, which is what tells a
|
|
149
|
+
// slow model apart from a stage that had too much work to do.
|
|
150
|
+
let toolMs = 0;
|
|
151
|
+
let toolsBusySince;
|
|
120
152
|
let idleTimer;
|
|
121
153
|
const armIdle = () => {
|
|
122
154
|
if (idleTimer)
|
|
@@ -137,10 +169,18 @@ export function withResilience(base, policy, opts = {}) {
|
|
|
137
169
|
const originalOnEvent = deps.onEvent;
|
|
138
170
|
const onEvent = (ev) => {
|
|
139
171
|
sawAnyEvent = true;
|
|
140
|
-
if (ev.type === "tool_execution_start")
|
|
172
|
+
if (ev.type === "tool_execution_start") {
|
|
173
|
+
if (inFlightTools === 0)
|
|
174
|
+
toolsBusySince = now();
|
|
141
175
|
inFlightTools += 1;
|
|
142
|
-
|
|
176
|
+
}
|
|
177
|
+
else if (ev.type === "tool_execution_end") {
|
|
143
178
|
inFlightTools = Math.max(0, inFlightTools - 1);
|
|
179
|
+
if (inFlightTools === 0 && toolsBusySince !== undefined) {
|
|
180
|
+
toolMs += now() - toolsBusySince;
|
|
181
|
+
toolsBusySince = undefined;
|
|
182
|
+
}
|
|
183
|
+
}
|
|
144
184
|
armIdle(); // reset the idle window on every live event
|
|
145
185
|
originalOnEvent?.(ev);
|
|
146
186
|
};
|
|
@@ -154,6 +194,10 @@ export function withResilience(base, policy, opts = {}) {
|
|
|
154
194
|
clearTimeout(idleTimer);
|
|
155
195
|
}
|
|
156
196
|
const elapsedMs = now() - startedAt;
|
|
197
|
+
// A tool still running when the stage ended counts up to now.
|
|
198
|
+
if (toolsBusySince !== undefined)
|
|
199
|
+
toolMs += now() - toolsBusySince;
|
|
200
|
+
const modelBound = isModelBound(elapsedMs, toolMs);
|
|
157
201
|
const callerAborted = callerSignal?.aborted === true;
|
|
158
202
|
const record = (outcome, willRetry) => {
|
|
159
203
|
telemetry?.({
|
|
@@ -164,6 +208,7 @@ export function withResilience(base, policy, opts = {}) {
|
|
|
164
208
|
attempt,
|
|
165
209
|
outcome,
|
|
166
210
|
...(timedOut && timeoutKind ? { timeoutKind } : {}),
|
|
211
|
+
...(timedOut ? { toolMs, modelBound } : {}),
|
|
167
212
|
exitCode: last.exitCode,
|
|
168
213
|
...(last.stopReason ? { stopReason: last.stopReason } : {}),
|
|
169
214
|
elapsedMs,
|
|
@@ -179,9 +224,8 @@ export function withResilience(base, policy, opts = {}) {
|
|
|
179
224
|
record("ok", false);
|
|
180
225
|
return last;
|
|
181
226
|
}
|
|
182
|
-
if (timedOut)
|
|
183
|
-
last.errorMessage = timeoutKind
|
|
184
|
-
}
|
|
227
|
+
if (timedOut)
|
|
228
|
+
last.errorMessage = timeoutMessage(timeoutKind, modelBound);
|
|
185
229
|
const transient = classifyTransient(last, timedOut);
|
|
186
230
|
// A wall-clock exhaustion is a "task too big" signal, not a stall: a
|
|
187
231
|
// lane that opts out (subagents) fails honestly instead of re-burning
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@yagni-app/code-staging",
|
|
3
|
-
"version": "1.2.2-staging.
|
|
3
|
+
"version": "1.2.2-staging.1555.1",
|
|
4
4
|
"description": "YAGNI Code: a terminal coding agent that already knows your company. One YAGNI login routes the model and grounds the agent in your team's context.",
|
|
5
5
|
"license": "SEE LICENSE IN LICENSE.md",
|
|
6
6
|
"author": "YAGNI, Inc. <jack@yagni.app> (https://yagni.app)",
|
|
@@ -58,5 +58,5 @@
|
|
|
58
58
|
"turndown": "^7.2.4",
|
|
59
59
|
"typebox": "^1.3.15"
|
|
60
60
|
},
|
|
61
|
-
"yagniSourceSha": "
|
|
61
|
+
"yagniSourceSha": "8e8cae193cda58ced857dd7c5f832e83d29f3e5b"
|
|
62
62
|
}
|