@yagni-app/code-staging 1.2.2-staging.1537.1 → 1.2.2-staging.1563.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.
- package/dist/extension/pipeline/headlessGo.d.ts +3 -0
- package/dist/extension/pipeline/headlessGo.js +11 -0
- package/dist/extension/pipeline/resilience.d.ts +6 -0
- package/dist/extension/pipeline/resilience.js +49 -5
- package/dist/extension/pipeline/treeSnapshot.d.ts +51 -0
- package/dist/extension/pipeline/treeSnapshot.js +139 -0
- package/package.json +2 -2
|
@@ -50,6 +50,7 @@
|
|
|
50
50
|
* anchored to this process's clock at start: the run then stops `budget_exceeded` before a review round or fix it
|
|
51
51
|
* cannot finish, rather than being killed partway through one (see budget.ts).
|
|
52
52
|
*/
|
|
53
|
+
import { type TreeSnapshotDeps } from "./treeSnapshot.js";
|
|
53
54
|
import { runPipeline as defaultRunPipeline } from "./orchestrator.js";
|
|
54
55
|
import { type RunBudget } from "./budget.js";
|
|
55
56
|
import type { Finding, JsonEvent, ModelTier, PipelineStage, StageId, StageTag, StageUsage, StopReason } from "./types.js";
|
|
@@ -151,6 +152,8 @@ export interface HeadlessGoDeps {
|
|
|
151
152
|
onEvent?: (ev: JsonEvent, tag: StageTag) => void;
|
|
152
153
|
/** Additive tap on the pipeline's own log events. */
|
|
153
154
|
logger?: (event: string, data?: unknown) => void;
|
|
155
|
+
/** Mission runs' working-tree snapshots (see treeSnapshot.ts); injected in tests. */
|
|
156
|
+
startTreeSnapshots?: (cwd: string, snapshotDeps: TreeSnapshotDeps) => () => Promise<void>;
|
|
154
157
|
}
|
|
155
158
|
/**
|
|
156
159
|
* Parse the argv remainder after `go`. Accepts both `--flag value` and
|
|
@@ -54,6 +54,7 @@ import { readFileSync } from "node:fs";
|
|
|
54
54
|
import { resolveFanoutMode } from "./fanout.js";
|
|
55
55
|
import { makeFanoutBeats } from "./fanoutBeats.js";
|
|
56
56
|
import { normalizeMission } from "./mission.js";
|
|
57
|
+
import { startTreeSnapshots } from "./treeSnapshot.js";
|
|
57
58
|
import { runPipeline as defaultRunPipeline } from "./orchestrator.js";
|
|
58
59
|
import { loadGroundingEnabled } from "../grounding.js";
|
|
59
60
|
import { codeStateHome } from "../stateHome.js";
|
|
@@ -333,6 +334,13 @@ export async function runHeadlessGo(argv, deps = {}) {
|
|
|
333
334
|
emit({ type: "pipeline_log", event, ...(data === undefined ? {} : { data }) });
|
|
334
335
|
},
|
|
335
336
|
};
|
|
337
|
+
// A mission's tree is the only copy of its work until the host commits it,
|
|
338
|
+
// so it is snapshotted for the host to recover from (treeSnapshot.ts).
|
|
339
|
+
const stopSnapshots = mission
|
|
340
|
+
? (deps.startTreeSnapshots ?? startTreeSnapshots)(cwd, {
|
|
341
|
+
onError: (message) => writeErr(`Working-tree checkpoint failed (the run continues without it): ${message}`),
|
|
342
|
+
})
|
|
343
|
+
: null;
|
|
336
344
|
try {
|
|
337
345
|
const result = await runPipeline(files.ticket, pipelineDeps);
|
|
338
346
|
const headless = buildResult(result, {
|
|
@@ -353,5 +361,8 @@ export async function runHeadlessGo(argv, deps = {}) {
|
|
|
353
361
|
writeErr(message);
|
|
354
362
|
return { exitCode: HEADLESS_GO_EXIT.unverified, error: message };
|
|
355
363
|
}
|
|
364
|
+
finally {
|
|
365
|
+
await stopSnapshots?.();
|
|
366
|
+
}
|
|
356
367
|
}
|
|
357
368
|
//# sourceMappingURL=headlessGo.js.map
|
|
@@ -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
|
|
@@ -0,0 +1,51 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Working-tree snapshots for a headless mission run.
|
|
3
|
+
*
|
|
4
|
+
* In mission mode the pipeline's FINISH stage never runs: the host turns
|
|
5
|
+
* whatever is in the tree when the harness ends into the one candidate commit.
|
|
6
|
+
* That makes the tree the only copy of the work, and a model can empty it in
|
|
7
|
+
* the last minute of a stage (a `git stash` to see whether a test failed before
|
|
8
|
+
* its change, a `git checkout .` to retry an approach) and then be cut off by
|
|
9
|
+
* the wall clock before putting it back. The host then finds the tree at the
|
|
10
|
+
* approved base and the paid build is gone.
|
|
11
|
+
*
|
|
12
|
+
* So, on an interval and once at the end, the run records the tree as a commit
|
|
13
|
+
* under {@link CHECKPOINT_REF}: a side ref the host reads only to recover from
|
|
14
|
+
* exactly that, never a commit on the branch. It goes through a throwaway
|
|
15
|
+
* index (`GIT_INDEX_FILE`), so HEAD, the real index and the files are never
|
|
16
|
+
* touched and the model's own git view is unchanged; `add -A` against that
|
|
17
|
+
* index still picks up new files and still honours `.gitignore`. A clean tree
|
|
18
|
+
* never overwrites the last real snapshot. Hooks are off for every call: the
|
|
19
|
+
* repository is the model's to edit, and a hook it installed must not run.
|
|
20
|
+
*
|
|
21
|
+
* Fail-soft throughout: a snapshot that cannot be taken is just not taken.
|
|
22
|
+
*/
|
|
23
|
+
/** The side ref a snapshot lands on. Never pushed: the candidate bundle carries HEAD only. */
|
|
24
|
+
export declare const CHECKPOINT_REF = "refs/yagni/checkpoint";
|
|
25
|
+
/** How often a mission run records its tree. */
|
|
26
|
+
export declare const SNAPSHOT_INTERVAL_MS = 60000;
|
|
27
|
+
/**
|
|
28
|
+
* Record the working tree under {@link CHECKPOINT_REF}. Answers the snapshot
|
|
29
|
+
* commit, or undefined when the tree matches HEAD or anything failed.
|
|
30
|
+
*/
|
|
31
|
+
export declare function snapshotWorkingTree(cwd: string, onError?: (message: string) => void): Promise<string | undefined>;
|
|
32
|
+
export interface TreeSnapshotDeps {
|
|
33
|
+
intervalMs?: number;
|
|
34
|
+
/** Bound on the stop-time snapshot; defaults to {@link STOP_SNAPSHOT_BOUND_MS}. */
|
|
35
|
+
stopBoundMs?: number;
|
|
36
|
+
/**
|
|
37
|
+
* Told when snapshots start failing (once per run of failures: a success
|
|
38
|
+
* re-arms it), and always about the final snapshot failing or being cut
|
|
39
|
+
* off, so a recovery that never arms or a stale checkpoint is visible.
|
|
40
|
+
*/
|
|
41
|
+
onError?: (message: string) => void;
|
|
42
|
+
snapshot?: (cwd: string, onError?: (message: string) => void) => Promise<string | undefined>;
|
|
43
|
+
setInterval?: (fn: () => void, ms: number) => NodeJS.Timeout;
|
|
44
|
+
clearInterval?: (timer: NodeJS.Timeout) => void;
|
|
45
|
+
}
|
|
46
|
+
/**
|
|
47
|
+
* Snapshot `cwd` every {@link SNAPSHOT_INTERVAL_MS} until the returned stop
|
|
48
|
+
* runs, which takes one last snapshot. Snapshots never overlap.
|
|
49
|
+
*/
|
|
50
|
+
export declare function startTreeSnapshots(cwd: string, deps?: TreeSnapshotDeps): () => Promise<void>;
|
|
51
|
+
//# sourceMappingURL=treeSnapshot.d.ts.map
|
|
@@ -0,0 +1,139 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Working-tree snapshots for a headless mission run.
|
|
3
|
+
*
|
|
4
|
+
* In mission mode the pipeline's FINISH stage never runs: the host turns
|
|
5
|
+
* whatever is in the tree when the harness ends into the one candidate commit.
|
|
6
|
+
* That makes the tree the only copy of the work, and a model can empty it in
|
|
7
|
+
* the last minute of a stage (a `git stash` to see whether a test failed before
|
|
8
|
+
* its change, a `git checkout .` to retry an approach) and then be cut off by
|
|
9
|
+
* the wall clock before putting it back. The host then finds the tree at the
|
|
10
|
+
* approved base and the paid build is gone.
|
|
11
|
+
*
|
|
12
|
+
* So, on an interval and once at the end, the run records the tree as a commit
|
|
13
|
+
* under {@link CHECKPOINT_REF}: a side ref the host reads only to recover from
|
|
14
|
+
* exactly that, never a commit on the branch. It goes through a throwaway
|
|
15
|
+
* index (`GIT_INDEX_FILE`), so HEAD, the real index and the files are never
|
|
16
|
+
* touched and the model's own git view is unchanged; `add -A` against that
|
|
17
|
+
* index still picks up new files and still honours `.gitignore`. A clean tree
|
|
18
|
+
* never overwrites the last real snapshot. Hooks are off for every call: the
|
|
19
|
+
* repository is the model's to edit, and a hook it installed must not run.
|
|
20
|
+
*
|
|
21
|
+
* Fail-soft throughout: a snapshot that cannot be taken is just not taken.
|
|
22
|
+
*/
|
|
23
|
+
import { execFile } from "node:child_process";
|
|
24
|
+
import { copyFileSync, mkdtempSync, rmSync } from "node:fs";
|
|
25
|
+
import { tmpdir } from "node:os";
|
|
26
|
+
import { join, resolve as resolvePath } from "node:path";
|
|
27
|
+
/** The side ref a snapshot lands on. Never pushed: the candidate bundle carries HEAD only. */
|
|
28
|
+
export const CHECKPOINT_REF = "refs/yagni/checkpoint";
|
|
29
|
+
/** How often a mission run records its tree. */
|
|
30
|
+
export const SNAPSHOT_INTERVAL_MS = 60_000;
|
|
31
|
+
const GIT_BASE_ARGS = ["-c", "core.hooksPath=/dev/null", "-c", "user.name=YAGNI", "-c", "user.email=worker@yagni.local"];
|
|
32
|
+
function gitOut(args, cwd, env) {
|
|
33
|
+
return new Promise((resolve, reject) => {
|
|
34
|
+
execFile("git", [...GIT_BASE_ARGS, ...args], { cwd, env: { ...process.env, ...env, GIT_TERMINAL_PROMPT: "0" }, maxBuffer: 4 * 1024 * 1024, timeout: 60_000 }, (err, stdout) => (err ? reject(err) : resolve(String(stdout).trim())));
|
|
35
|
+
});
|
|
36
|
+
}
|
|
37
|
+
/**
|
|
38
|
+
* Record the working tree under {@link CHECKPOINT_REF}. Answers the snapshot
|
|
39
|
+
* commit, or undefined when the tree matches HEAD or anything failed.
|
|
40
|
+
*/
|
|
41
|
+
export async function snapshotWorkingTree(cwd, onError) {
|
|
42
|
+
let scratch;
|
|
43
|
+
try {
|
|
44
|
+
scratch = mkdtempSync(join(tmpdir(), "yagni-snapshot-"));
|
|
45
|
+
const index = join(scratch, "index");
|
|
46
|
+
const env = { GIT_INDEX_FILE: index };
|
|
47
|
+
// Seed the throwaway index from the real one: its stat cache lets `add -A`
|
|
48
|
+
// skip every unchanged file instead of re-hashing the whole tree each
|
|
49
|
+
// minute. The result is the same tree either way (`add -A` makes the index
|
|
50
|
+
// match the files); a repo with no index yet starts from HEAD.
|
|
51
|
+
const realIndex = resolvePath(cwd, await gitOut(["rev-parse", "--git-path", "index"], cwd));
|
|
52
|
+
try {
|
|
53
|
+
copyFileSync(realIndex, index);
|
|
54
|
+
}
|
|
55
|
+
catch {
|
|
56
|
+
await gitOut(["read-tree", "HEAD"], cwd, env);
|
|
57
|
+
}
|
|
58
|
+
await gitOut(["add", "-A"], cwd, env);
|
|
59
|
+
const tree = await gitOut(["write-tree"], cwd, env);
|
|
60
|
+
const headTree = await gitOut(["rev-parse", "HEAD^{tree}"], cwd);
|
|
61
|
+
if (!tree || tree === headTree)
|
|
62
|
+
return undefined;
|
|
63
|
+
const commit = await gitOut(["commit-tree", tree, "-p", "HEAD", "-m", "yagni: working tree checkpoint"], cwd);
|
|
64
|
+
if (!commit)
|
|
65
|
+
return undefined;
|
|
66
|
+
await gitOut(["update-ref", CHECKPOINT_REF, commit], cwd);
|
|
67
|
+
return commit;
|
|
68
|
+
}
|
|
69
|
+
catch (err) {
|
|
70
|
+
onError?.(err instanceof Error ? err.message : String(err));
|
|
71
|
+
return undefined;
|
|
72
|
+
}
|
|
73
|
+
finally {
|
|
74
|
+
if (scratch)
|
|
75
|
+
rmSync(scratch, { recursive: true, force: true });
|
|
76
|
+
}
|
|
77
|
+
}
|
|
78
|
+
/** The most the final snapshot may hold up the harness's exit. */
|
|
79
|
+
const STOP_SNAPSHOT_BOUND_MS = 15_000;
|
|
80
|
+
/**
|
|
81
|
+
* Snapshot `cwd` every {@link SNAPSHOT_INTERVAL_MS} until the returned stop
|
|
82
|
+
* runs, which takes one last snapshot. Snapshots never overlap.
|
|
83
|
+
*/
|
|
84
|
+
export function startTreeSnapshots(cwd, deps = {}) {
|
|
85
|
+
const snapshot = deps.snapshot ?? snapshotWorkingTree;
|
|
86
|
+
let reported = false;
|
|
87
|
+
let running = null;
|
|
88
|
+
const take = (final = false) => {
|
|
89
|
+
if (!running) {
|
|
90
|
+
let failed = false;
|
|
91
|
+
const onError = (message) => {
|
|
92
|
+
failed = true;
|
|
93
|
+
if (final)
|
|
94
|
+
deps.onError?.(`final snapshot: ${message}`);
|
|
95
|
+
else if (!reported)
|
|
96
|
+
deps.onError?.(message);
|
|
97
|
+
reported = true;
|
|
98
|
+
};
|
|
99
|
+
running = snapshot(cwd, onError)
|
|
100
|
+
.catch((err) => onError(err instanceof Error ? err.message : String(err)))
|
|
101
|
+
.finally(() => {
|
|
102
|
+
// A snapshot that worked re-arms the report for the next failure.
|
|
103
|
+
if (!failed)
|
|
104
|
+
reported = false;
|
|
105
|
+
running = null;
|
|
106
|
+
});
|
|
107
|
+
}
|
|
108
|
+
return running;
|
|
109
|
+
};
|
|
110
|
+
const schedule = deps.setInterval ?? ((fn, ms) => globalThis.setInterval(fn, ms));
|
|
111
|
+
const cancel = deps.clearInterval ?? ((handle) => globalThis.clearInterval(handle));
|
|
112
|
+
const timer = schedule(() => {
|
|
113
|
+
void take();
|
|
114
|
+
}, deps.intervalMs ?? SNAPSHOT_INTERVAL_MS);
|
|
115
|
+
timer.unref?.();
|
|
116
|
+
return async () => {
|
|
117
|
+
cancel(timer);
|
|
118
|
+
// The result is already known by now; a wedged filesystem must not hold
|
|
119
|
+
// the harness's exit for minutes. The last interval snapshot still stands.
|
|
120
|
+
const bound = deps.stopBoundMs ?? STOP_SNAPSHOT_BOUND_MS;
|
|
121
|
+
let timeout;
|
|
122
|
+
const limit = new Promise((resolve) => {
|
|
123
|
+
timeout = globalThis.setTimeout(() => resolve("cut_off"), bound);
|
|
124
|
+
});
|
|
125
|
+
const final = (async () => {
|
|
126
|
+
if (running)
|
|
127
|
+
await running;
|
|
128
|
+
await take(true);
|
|
129
|
+
return "taken";
|
|
130
|
+
})();
|
|
131
|
+
const outcome = await Promise.race([final, limit]);
|
|
132
|
+
if (timeout !== undefined)
|
|
133
|
+
globalThis.clearTimeout(timeout);
|
|
134
|
+
if (outcome === "cut_off") {
|
|
135
|
+
deps.onError?.(`final snapshot did not finish within ${bound} ms; the last interval snapshot stands`);
|
|
136
|
+
}
|
|
137
|
+
};
|
|
138
|
+
}
|
|
139
|
+
//# sourceMappingURL=treeSnapshot.js.map
|
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.1563.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": "a55a45e07d5aa97ca696dcf692ec7c6507882a81"
|
|
62
62
|
}
|