flavor-code 1.4.0-beta.3 → 1.4.0-beta.5
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/agent/loop.d.ts +4 -2
- package/dist/{app-H7RYDMD3.js → app-SXNJDSI2.js} +3 -3
- package/dist/{chunk-NIKCPCJG.js → chunk-J7TLT6LR.js} +1098 -366
- package/dist/{chunk-3BHKW5A7.js → chunk-JZVC4477.js} +8 -2
- package/dist/{chunk-KBHWS7M6.js → chunk-KQMKY7C5.js} +4 -0
- package/dist/chunk-N5MXHJKD.js +107 -0
- package/dist/cli-main.js +20 -13
- package/dist/cli.js +51 -10
- package/dist/config/schema.d.ts +4 -0
- package/dist/context/manager.d.ts +9 -0
- package/dist/desktop/main.js +1442 -625
- package/dist/desktop-renderer/assets/index-nR67Z0XT.js +154 -0
- package/dist/desktop-renderer/assets/{interactive-terminal-j52Ty3QO.js → interactive-terminal-D2J4feTJ.js} +1 -1
- package/dist/desktop-renderer/index.html +1 -1
- package/dist/goal/classifier.d.ts +2 -0
- package/dist/goal/orchestrator.d.ts +23 -2
- package/dist/goal/planner.d.ts +2 -0
- package/dist/goal/store.d.ts +2 -0
- package/dist/goal/types.d.ts +14 -0
- package/dist/harness/journal.d.ts +2 -0
- package/dist/jobs/registry.d.ts +6 -0
- package/dist/{load-SLSL3IWR.js → load-F4AYLCF6.js} +1 -1
- package/dist/loop/orchestrator.d.ts +20 -0
- package/dist/loop/types.d.ts +7 -0
- package/dist/loop/verifier.d.ts +2 -0
- package/dist/production.d.ts +17 -0
- package/dist/sdk/index.js +4 -4
- package/dist/session/tree.d.ts +2 -0
- package/dist/ui/session.d.ts +11 -0
- package/dist/ui/transcript.d.ts +9 -0
- package/dist/utils/abort.d.ts +16 -0
- package/dist/utils/heap.d.ts +58 -0
- package/dist/utils/memory-restart.d.ts +42 -3
- package/dist/utils/rotation-census.d.ts +36 -0
- package/package.json +1 -1
- package/dist/chunk-CIVPKHF4.js +0 -53
- package/dist/desktop-renderer/assets/index-CYd5JkTR.js +0 -154
|
@@ -2,10 +2,15 @@
|
|
|
2
2
|
import { createRequire } from "node:module"; const require = createRequire(import.meta.url);
|
|
3
3
|
import {
|
|
4
4
|
MEMORY_RESTART_EXIT_CODE,
|
|
5
|
+
consumedMemoryRestartMarker,
|
|
6
|
+
markMemoryRotation,
|
|
5
7
|
memoryRestartMarkerPath,
|
|
8
|
+
memoryRotationActive,
|
|
6
9
|
nextMemoryRestartMarker,
|
|
7
|
-
parseMemoryRestartMarker
|
|
8
|
-
|
|
10
|
+
parseMemoryRestartMarker,
|
|
11
|
+
pendingContinuation,
|
|
12
|
+
rotationCooldownActive
|
|
13
|
+
} from "./chunk-N5MXHJKD.js";
|
|
9
14
|
import {
|
|
10
15
|
PERMISSION_MODES,
|
|
11
16
|
PermissionModeSchema,
|
|
@@ -18,7 +23,7 @@ import {
|
|
|
18
23
|
redactConfig,
|
|
19
24
|
setProjectMcpServerDisabled,
|
|
20
25
|
updateProtectedFile
|
|
21
|
-
} from "./chunk-
|
|
26
|
+
} from "./chunk-KQMKY7C5.js";
|
|
22
27
|
import {
|
|
23
28
|
AuditLogger,
|
|
24
29
|
appendUsageLog,
|
|
@@ -1296,6 +1301,11 @@ function appendThinkingText(previous, delta) {
|
|
|
1296
1301
|
// src/ui/transcript.ts
|
|
1297
1302
|
var MAX_TRANSCRIPT_TURNS = 200;
|
|
1298
1303
|
var MAX_TRANSCRIPT_CHARS = 2e6;
|
|
1304
|
+
var MAX_ACTIVE_TRANSCRIPT_BLOCKS = 160;
|
|
1305
|
+
var MAX_ACTIVE_ASSISTANT_CHARS = 64e3;
|
|
1306
|
+
var MAX_TRANSCRIPT_BLOCK_TEXT_CHARS = 16e3;
|
|
1307
|
+
var MAX_TRANSCRIPT_TOOL_VALUE_CHARS = 16e3;
|
|
1308
|
+
var MAX_TRANSCRIPT_PRESENTATION_CHARS = 32e3;
|
|
1299
1309
|
function createTranscriptState() {
|
|
1300
1310
|
return { completed: [], nextId: 1 };
|
|
1301
1311
|
}
|
|
@@ -1656,11 +1666,9 @@ function applyTaskSnapshot(turn, snapshot4, includeTerminal = true) {
|
|
|
1656
1666
|
else blocks[index] = block;
|
|
1657
1667
|
}
|
|
1658
1668
|
return {
|
|
1659
|
-
...turn,
|
|
1669
|
+
...withBlocks(turn, blocks),
|
|
1660
1670
|
taskSnapshot: snapshot4,
|
|
1661
|
-
suppressedTaskIds: [...suppressed]
|
|
1662
|
-
blocks,
|
|
1663
|
-
statusLines: blocks.filter((block) => block.kind === "status").map((block) => block.text)
|
|
1671
|
+
suppressedTaskIds: [...suppressed]
|
|
1664
1672
|
};
|
|
1665
1673
|
}
|
|
1666
1674
|
function terminalTaskIds(snapshot4) {
|
|
@@ -1674,13 +1682,10 @@ function upsertStatus(state, block) {
|
|
|
1674
1682
|
if (state.active === void 0) return state;
|
|
1675
1683
|
const blocks = [...state.active.blocks];
|
|
1676
1684
|
const index = blocks.findIndex((item) => item.kind === "status" && item.id === block.id);
|
|
1677
|
-
|
|
1678
|
-
|
|
1679
|
-
|
|
1680
|
-
|
|
1681
|
-
statusLines: blocks.filter((item) => item.kind === "status").map((item) => item.text),
|
|
1682
|
-
blocks
|
|
1683
|
-
} };
|
|
1685
|
+
const bounded2 = boundedTranscriptBlock(block);
|
|
1686
|
+
if (index < 0) blocks.push(bounded2);
|
|
1687
|
+
else blocks[index] = bounded2;
|
|
1688
|
+
return { ...state, active: withBlocks(state.active, blocks) };
|
|
1684
1689
|
}
|
|
1685
1690
|
function finishActive(state) {
|
|
1686
1691
|
if (state.active === void 0) return state;
|
|
@@ -1704,16 +1709,12 @@ function attachThinking(turn, text) {
|
|
|
1704
1709
|
...block,
|
|
1705
1710
|
thinkingText: appendThinkingText(block.thinkingText, text)
|
|
1706
1711
|
};
|
|
1707
|
-
return
|
|
1712
|
+
return withBlocks(turn, blocks);
|
|
1708
1713
|
}
|
|
1709
1714
|
function withoutModelActivity(turn, id) {
|
|
1710
1715
|
const blocks = turn.blocks.filter((block) => block.kind !== "status" || block.activity !== "model" || id !== void 0 && block.id !== id);
|
|
1711
1716
|
if (blocks.length === turn.blocks.length) return turn;
|
|
1712
|
-
return
|
|
1713
|
-
...turn,
|
|
1714
|
-
blocks,
|
|
1715
|
-
statusLines: blocks.filter((block) => block.kind === "status").map((block) => block.text)
|
|
1716
|
-
};
|
|
1717
|
+
return withBlocks(turn, blocks);
|
|
1717
1718
|
}
|
|
1718
1719
|
function restoreTranscriptState(state) {
|
|
1719
1720
|
const completed = state.completed.map(cloneTurn);
|
|
@@ -1815,15 +1816,18 @@ function cloneTurn(turn) {
|
|
|
1815
1816
|
function upsertTurnStatus(turn, block) {
|
|
1816
1817
|
const blocks = [...turn.blocks];
|
|
1817
1818
|
const index = blocks.findIndex((item) => item.kind === "status" && item.id === block.id);
|
|
1818
|
-
|
|
1819
|
-
|
|
1819
|
+
const bounded2 = boundedTranscriptBlock(block);
|
|
1820
|
+
if (index < 0) blocks.push(bounded2);
|
|
1821
|
+
else blocks[index] = bounded2;
|
|
1820
1822
|
return withBlocks(turn, blocks);
|
|
1821
1823
|
}
|
|
1822
1824
|
function withBlocks(turn, blocks) {
|
|
1825
|
+
const bounded2 = blocks.length <= MAX_ACTIVE_TRANSCRIPT_BLOCKS ? blocks : blocks.slice(-MAX_ACTIVE_TRANSCRIPT_BLOCKS);
|
|
1823
1826
|
return {
|
|
1824
1827
|
...turn,
|
|
1825
|
-
|
|
1826
|
-
|
|
1828
|
+
assistantText: tailText(turn.assistantText, MAX_ACTIVE_ASSISTANT_CHARS),
|
|
1829
|
+
blocks: bounded2,
|
|
1830
|
+
statusLines: bounded2.filter((block) => block.kind === "status").map((block) => block.text)
|
|
1827
1831
|
};
|
|
1828
1832
|
}
|
|
1829
1833
|
function appendLine(text, line) {
|
|
@@ -1837,19 +1841,95 @@ function stripRetryBlocks(turn) {
|
|
|
1837
1841
|
if (block.id.startsWith("structured-retry:")) return false;
|
|
1838
1842
|
return true;
|
|
1839
1843
|
});
|
|
1840
|
-
return
|
|
1841
|
-
...turn,
|
|
1842
|
-
blocks,
|
|
1843
|
-
statusLines: blocks.filter((item) => item.kind === "status").map((item) => item.text)
|
|
1844
|
-
};
|
|
1844
|
+
return withBlocks(turn, blocks);
|
|
1845
1845
|
}
|
|
1846
1846
|
function addText(turn, text, onNewLine = false) {
|
|
1847
|
-
const assistantText =
|
|
1847
|
+
const assistantText = tailText(
|
|
1848
|
+
onNewLine ? appendLine(turn.assistantText, text) : turn.assistantText + text,
|
|
1849
|
+
MAX_ACTIVE_ASSISTANT_CHARS
|
|
1850
|
+
);
|
|
1848
1851
|
const blocks = [...turn.blocks];
|
|
1849
1852
|
const last = blocks[blocks.length - 1];
|
|
1850
|
-
if (!onNewLine && last?.kind === "text")
|
|
1851
|
-
|
|
1852
|
-
|
|
1853
|
+
if (!onNewLine && last?.kind === "text") {
|
|
1854
|
+
blocks[blocks.length - 1] = { kind: "text", text: tailText(last.text + text, MAX_TRANSCRIPT_BLOCK_TEXT_CHARS) };
|
|
1855
|
+
} else {
|
|
1856
|
+
blocks.push({ kind: "text", text: tailText(text, MAX_TRANSCRIPT_BLOCK_TEXT_CHARS) });
|
|
1857
|
+
}
|
|
1858
|
+
return withBlocks({ ...turn, assistantText }, blocks);
|
|
1859
|
+
}
|
|
1860
|
+
function boundedTranscriptBlock(block) {
|
|
1861
|
+
const presentation = block.presentation === void 0 ? void 0 : boundedTranscriptPresentation(block.presentation);
|
|
1862
|
+
const tool = block.tool === void 0 ? void 0 : {
|
|
1863
|
+
name: block.tool.name,
|
|
1864
|
+
input: boundedTranscriptValue(block.tool.input),
|
|
1865
|
+
...block.tool.result === void 0 ? {} : {
|
|
1866
|
+
result: {
|
|
1867
|
+
ok: block.tool.result.ok,
|
|
1868
|
+
...block.tool.result.output === void 0 ? {} : { output: boundedTranscriptValue(block.tool.result.output) },
|
|
1869
|
+
...block.tool.result.content === void 0 ? {} : {
|
|
1870
|
+
content: tailText(block.tool.result.content, MAX_TRANSCRIPT_TOOL_VALUE_CHARS)
|
|
1871
|
+
},
|
|
1872
|
+
...block.tool.result.presentation === void 0 ? {} : {
|
|
1873
|
+
presentation: boundedTranscriptPresentation(block.tool.result.presentation)
|
|
1874
|
+
},
|
|
1875
|
+
...block.tool.result.additionalContext === void 0 ? {} : {
|
|
1876
|
+
additionalContext: block.tool.result.additionalContext.slice(-20).map((item) => tailText(item, MAX_TRANSCRIPT_TOOL_VALUE_CHARS))
|
|
1877
|
+
},
|
|
1878
|
+
...block.tool.result.error === void 0 ? {} : { error: {
|
|
1879
|
+
code: block.tool.result.error.code,
|
|
1880
|
+
message: tailText(block.tool.result.error.message, MAX_TRANSCRIPT_BLOCK_TEXT_CHARS)
|
|
1881
|
+
} }
|
|
1882
|
+
}
|
|
1883
|
+
}
|
|
1884
|
+
};
|
|
1885
|
+
return {
|
|
1886
|
+
...block,
|
|
1887
|
+
text: tailText(block.text, MAX_TRANSCRIPT_BLOCK_TEXT_CHARS),
|
|
1888
|
+
...block.hint === void 0 ? {} : { hint: tailText(block.hint, MAX_TRANSCRIPT_BLOCK_TEXT_CHARS) },
|
|
1889
|
+
...block.details === void 0 ? {} : { details: tailText(block.details, MAX_TRANSCRIPT_BLOCK_TEXT_CHARS) },
|
|
1890
|
+
...presentation === void 0 ? {} : { presentation },
|
|
1891
|
+
...tool === void 0 ? {} : { tool }
|
|
1892
|
+
};
|
|
1893
|
+
}
|
|
1894
|
+
function boundedTranscriptPresentation(presentation) {
|
|
1895
|
+
return jsonSize(presentation) <= MAX_TRANSCRIPT_PRESENTATION_CHARS ? presentation : {
|
|
1896
|
+
kind: "generic",
|
|
1897
|
+
title: "Tool output",
|
|
1898
|
+
summary: "Large presentation omitted from the live transcript; the model-facing result remains available in context."
|
|
1899
|
+
};
|
|
1900
|
+
}
|
|
1901
|
+
function boundedTranscriptValue(value) {
|
|
1902
|
+
const chars = jsonSize(value);
|
|
1903
|
+
if (chars <= MAX_TRANSCRIPT_TOOL_VALUE_CHARS) return value;
|
|
1904
|
+
const record = typeof value === "object" && value !== null && !Array.isArray(value) ? value : void 0;
|
|
1905
|
+
const summary2 = { truncated: true, originalChars: chars };
|
|
1906
|
+
for (const key of ["path", "command", "id", "name", "exitCode", "bytes", "replacements"]) {
|
|
1907
|
+
const item = record?.[key];
|
|
1908
|
+
if (typeof item === "string") summary2[key] = tailText(item, 1e3);
|
|
1909
|
+
else if (typeof item === "number" || typeof item === "boolean" || item === null) summary2[key] = item;
|
|
1910
|
+
}
|
|
1911
|
+
return summary2;
|
|
1912
|
+
}
|
|
1913
|
+
function jsonSize(value) {
|
|
1914
|
+
try {
|
|
1915
|
+
return JSON.stringify(value)?.length ?? 0;
|
|
1916
|
+
} catch {
|
|
1917
|
+
return Number.POSITIVE_INFINITY;
|
|
1918
|
+
}
|
|
1919
|
+
}
|
|
1920
|
+
function tailText(text, limit) {
|
|
1921
|
+
if (text.length <= limit) return text;
|
|
1922
|
+
const marker = `\u2026 ${text.length - limit} earlier characters omitted \u2026
|
|
1923
|
+
`;
|
|
1924
|
+
return marker.length >= limit ? text.slice(-limit) : marker + text.slice(-(limit - marker.length));
|
|
1925
|
+
}
|
|
1926
|
+
function transcriptCensus(state) {
|
|
1927
|
+
const turns = [...state.completed, ...state.active === void 0 ? [] : [state.active]];
|
|
1928
|
+
return {
|
|
1929
|
+
turns: turns.length,
|
|
1930
|
+
blocks: turns.reduce((total, turn) => total + turn.blocks.length, 0),
|
|
1931
|
+
chars: jsonSize(state)
|
|
1932
|
+
};
|
|
1853
1933
|
}
|
|
1854
1934
|
|
|
1855
1935
|
// src/session/checkpoint.ts
|
|
@@ -2061,6 +2141,10 @@ var SessionHistory = class _SessionHistory {
|
|
|
2061
2141
|
get leafId() {
|
|
2062
2142
|
return this.#leafId;
|
|
2063
2143
|
}
|
|
2144
|
+
/** Cheap retained-node count for the rotation census (no cloning). */
|
|
2145
|
+
get nodeCount() {
|
|
2146
|
+
return this.#nodes.length;
|
|
2147
|
+
}
|
|
2064
2148
|
tree() {
|
|
2065
2149
|
return this.#nodes.map((node) => structuredClone(node));
|
|
2066
2150
|
}
|
|
@@ -3612,6 +3696,11 @@ var HELP = [
|
|
|
3612
3696
|
"/co-work <pal> <goal> | status [id] | cancel <id> [reason]"
|
|
3613
3697
|
].join("\n");
|
|
3614
3698
|
var MAX_STOP_DENIALS = 2;
|
|
3699
|
+
var RESUMABLE_TURN_BOUNDARIES = /* @__PURE__ */ new Set([
|
|
3700
|
+
"iteration_limit",
|
|
3701
|
+
"output_limit",
|
|
3702
|
+
"context_overflow"
|
|
3703
|
+
]);
|
|
3615
3704
|
var FlavorSession = class {
|
|
3616
3705
|
#services;
|
|
3617
3706
|
#active;
|
|
@@ -3635,6 +3724,7 @@ var FlavorSession = class {
|
|
|
3635
3724
|
#pendingCoWorkStarts = [];
|
|
3636
3725
|
#activeSteeringDurableIds = /* @__PURE__ */ new Set();
|
|
3637
3726
|
#durableResumeStarted = false;
|
|
3727
|
+
#continuationResumeStarted = false;
|
|
3638
3728
|
#permissionBaseline;
|
|
3639
3729
|
#activeSubmission;
|
|
3640
3730
|
constructor(services) {
|
|
@@ -3655,6 +3745,10 @@ var FlavorSession = class {
|
|
|
3655
3745
|
queueSnapshot() {
|
|
3656
3746
|
return this.#queue.snapshot();
|
|
3657
3747
|
}
|
|
3748
|
+
/** True when start() admitted a persisted /loop or /goal continuation. */
|
|
3749
|
+
get rotationContinuationResumed() {
|
|
3750
|
+
return this.#continuationResumeStarted;
|
|
3751
|
+
}
|
|
3658
3752
|
clearQueue() {
|
|
3659
3753
|
const snapshot4 = this.#queue.clear();
|
|
3660
3754
|
for (const kind of ["steer", "followUp"]) {
|
|
@@ -3804,6 +3898,7 @@ var FlavorSession = class {
|
|
|
3804
3898
|
if (decision.additionalContext !== void 0) await this.#services.addContext?.(decision.additionalContext);
|
|
3805
3899
|
this.#started = true;
|
|
3806
3900
|
this.#resumeDurableQueue();
|
|
3901
|
+
this.#resumeRotationContinuation();
|
|
3807
3902
|
});
|
|
3808
3903
|
return this.#startPromise;
|
|
3809
3904
|
}
|
|
@@ -3837,6 +3932,7 @@ var FlavorSession = class {
|
|
|
3837
3932
|
if (!initial) this.#outputQueuedSubmission(submission);
|
|
3838
3933
|
initial = false;
|
|
3839
3934
|
await this.#runSubmission(submission);
|
|
3935
|
+
if (this.#services.rotationPending?.() === true) return;
|
|
3840
3936
|
pending.push(...this.#drainMessages("steer"), ...this.#drainMessages("followUp"));
|
|
3841
3937
|
}
|
|
3842
3938
|
}
|
|
@@ -3860,6 +3956,7 @@ var FlavorSession = class {
|
|
|
3860
3956
|
this.#activeSubmission = submission;
|
|
3861
3957
|
this.#interrupted = false;
|
|
3862
3958
|
let outcome = "completed";
|
|
3959
|
+
let resumableBoundary;
|
|
3863
3960
|
let assistantSummary = "";
|
|
3864
3961
|
let deliverables = [];
|
|
3865
3962
|
try {
|
|
@@ -3886,7 +3983,8 @@ var FlavorSession = class {
|
|
|
3886
3983
|
skillNames,
|
|
3887
3984
|
this.#services.managedToolCommands().map(({ name }) => name)
|
|
3888
3985
|
);
|
|
3889
|
-
if (
|
|
3986
|
+
if (submission.continuation !== void 0) await this.#runContinuation(submission.continuation, controller.signal);
|
|
3987
|
+
else if (command !== null) await this.#dispatch(command, controller.signal);
|
|
3890
3988
|
else for await (const event of this.#services.run(prompt, controller.signal, {
|
|
3891
3989
|
getSteeringMessages: () => this.#drainMessages("steer", true).map((item) => item.text),
|
|
3892
3990
|
...decision.additionalContext === void 0 ? {} : { additionalContext: decision.additionalContext },
|
|
@@ -3895,7 +3993,10 @@ var FlavorSession = class {
|
|
|
3895
3993
|
this.#services.output(event);
|
|
3896
3994
|
if (event.type === "text") assistantSummary = `${assistantSummary}${event.text}`.slice(-2e3);
|
|
3897
3995
|
if (event.type === "deliverables") deliverables = event.files.slice(0, 100);
|
|
3898
|
-
if (event.type === "error")
|
|
3996
|
+
if (event.type === "error") {
|
|
3997
|
+
outcome = "failed";
|
|
3998
|
+
if (RESUMABLE_TURN_BOUNDARIES.has(event.error.code)) resumableBoundary = event.error.code;
|
|
3999
|
+
}
|
|
3899
4000
|
}
|
|
3900
4001
|
if (controller.signal.aborted) outcome = "cancelled";
|
|
3901
4002
|
} catch (error) {
|
|
@@ -3936,6 +4037,13 @@ var FlavorSession = class {
|
|
|
3936
4037
|
}
|
|
3937
4038
|
}
|
|
3938
4039
|
try {
|
|
4040
|
+
if (resumableBoundary !== void 0 && !this.#closed && !controller.signal.aborted) {
|
|
4041
|
+
const attempt = (submission.iterationContinuation ?? 0) + 1;
|
|
4042
|
+
const text = `[automatic continuation after ${resumableBoundary} boundary; segment ${attempt}]
|
|
4043
|
+
Continue the unfinished task from the current conversation and workspace state. Do not repeat completed operations. Inspect existing results, finish the remaining work, and verify it before stopping.`;
|
|
4044
|
+
this.#enqueueMessage("followUp", { text, displayText: text, iterationContinuation: attempt });
|
|
4045
|
+
this.#notice(`Agent ${resumableBoundary} boundary reached; durable continuation ${attempt} queued.`);
|
|
4046
|
+
}
|
|
3939
4047
|
if (stopDecision?.decision === "deny" && typeof stopDecision.reason === "string" && stopDecision.reason.length > 0 && !this.#closed && outcome !== "cancelled" && outcome !== "denied") {
|
|
3940
4048
|
if (this.#stopDenials < MAX_STOP_DENIALS) {
|
|
3941
4049
|
this.#stopDenials += 1;
|
|
@@ -4132,7 +4240,7 @@ Address this now (for example run the required verification or fix), then finish
|
|
|
4132
4240
|
});
|
|
4133
4241
|
}
|
|
4134
4242
|
#resumeDurableQueue() {
|
|
4135
|
-
if (this.#durableResumeStarted || this.#closed || this.active || this.#pendingSubmissions > 0) return;
|
|
4243
|
+
if (this.#durableResumeStarted || this.#closed || this.active || this.#pendingSubmissions > 0 || this.#services.rotationPending?.() === true) return;
|
|
4136
4244
|
if (!this.#queue.hasPending) return;
|
|
4137
4245
|
this.#durableResumeStarted = true;
|
|
4138
4246
|
const submissions = [...this.#drainMessages("steer"), ...this.#drainMessages("followUp")];
|
|
@@ -4144,6 +4252,33 @@ Address this now (for example run the required verification or fix), then finish
|
|
|
4144
4252
|
});
|
|
4145
4253
|
}
|
|
4146
4254
|
}
|
|
4255
|
+
/**
|
|
4256
|
+
* After a heap rotation the relaunched process inherits the same session and
|
|
4257
|
+
* a marker continuation naming the /loop or /goal that was in flight. Resume
|
|
4258
|
+
* it once here so a multi-day autonomous run survives the rotation instead of
|
|
4259
|
+
* silently stopping at the boundary.
|
|
4260
|
+
*/
|
|
4261
|
+
#resumeRotationContinuation() {
|
|
4262
|
+
if (this.#continuationResumeStarted || this.#closed) return;
|
|
4263
|
+
const continuation = this.#services.continuation?.();
|
|
4264
|
+
if (continuation === void 0) return;
|
|
4265
|
+
const supported = continuation.kind === "loop" ? this.#services.resumeLoop !== void 0 : this.#services.resumeGoal !== void 0;
|
|
4266
|
+
if (!supported) return;
|
|
4267
|
+
this.#continuationResumeStarted = true;
|
|
4268
|
+
const displayText = `Resuming the interrupted ${continuation.kind} ${continuation.id} after a heap rotation.`;
|
|
4269
|
+
this.#notice(displayText);
|
|
4270
|
+
this.#enqueueNormalizedSubmission({ text: displayText, displayText, continuation }).catch((error) => {
|
|
4271
|
+
this.#services.output({ type: "error", error: { code: "unknown", message: message(error) } });
|
|
4272
|
+
});
|
|
4273
|
+
}
|
|
4274
|
+
async #runContinuation(continuation, signal) {
|
|
4275
|
+
const stream = continuation.kind === "loop" ? this.#services.resumeLoop?.(continuation.id, signal) : this.#services.resumeGoal?.(continuation.id, signal);
|
|
4276
|
+
if (stream === void 0) {
|
|
4277
|
+
this.#notice(`The runtime cannot resume the interrupted ${continuation.kind} ${continuation.id}; start it again with /${continuation.kind}.`);
|
|
4278
|
+
return;
|
|
4279
|
+
}
|
|
4280
|
+
for await (const event of stream) this.#services.output(event);
|
|
4281
|
+
}
|
|
4147
4282
|
#ackDurable(submission) {
|
|
4148
4283
|
if (submission.durableId === void 0) return;
|
|
4149
4284
|
this.#services.durableQueue?.ack(submission.durableId);
|
|
@@ -4256,6 +4391,7 @@ function recoveredSubmission(value) {
|
|
|
4256
4391
|
if (typeof input.text !== "string" || input.text.trim().length === 0) return void 0;
|
|
4257
4392
|
if (typeof input.displayText !== "string") return void 0;
|
|
4258
4393
|
if (input.controlOnly !== void 0 && typeof input.controlOnly !== "boolean") return void 0;
|
|
4394
|
+
if (input.iterationContinuation !== void 0 && (!Number.isSafeInteger(input.iterationContinuation) || input.iterationContinuation < 1)) return void 0;
|
|
4259
4395
|
if (input.coWorkPlanningKey !== void 0 && typeof input.coWorkPlanningKey !== "string") return void 0;
|
|
4260
4396
|
if (input.remoteOrigin !== void 0) {
|
|
4261
4397
|
const origin = input.remoteOrigin;
|
|
@@ -5973,8 +6109,8 @@ var PalBrokerServer = class {
|
|
|
5973
6109
|
});
|
|
5974
6110
|
if (this.endpointLock === void 0) throw new Error(`Pals endpoint '${this.address}' is active or owned`);
|
|
5975
6111
|
try {
|
|
5976
|
-
const
|
|
5977
|
-
if (!
|
|
6112
|
+
const stat12 = await lstat4(this.address);
|
|
6113
|
+
if (!stat12.isSocket()) throw new Error(`Refusing to unlink '${this.address}' because it is not a socket`);
|
|
5978
6114
|
await unlink3(this.address);
|
|
5979
6115
|
} catch (error) {
|
|
5980
6116
|
if (error.code !== "ENOENT") throw error;
|
|
@@ -5987,8 +6123,8 @@ var PalBrokerServer = class {
|
|
|
5987
6123
|
}
|
|
5988
6124
|
async unlinkKnownSocket() {
|
|
5989
6125
|
try {
|
|
5990
|
-
const
|
|
5991
|
-
if (
|
|
6126
|
+
const stat12 = await lstat4(this.address);
|
|
6127
|
+
if (stat12.isSocket()) await unlink3(this.address);
|
|
5992
6128
|
} catch (error) {
|
|
5993
6129
|
if (error.code !== "ENOENT") throw error;
|
|
5994
6130
|
}
|
|
@@ -6715,11 +6851,11 @@ async function ensureOnce(options) {
|
|
|
6715
6851
|
}
|
|
6716
6852
|
|
|
6717
6853
|
// src/production.ts
|
|
6718
|
-
import { readFile as
|
|
6719
|
-
import { mkdirSync as
|
|
6854
|
+
import { readFile as readFile24 } from "fs/promises";
|
|
6855
|
+
import { mkdirSync as mkdirSync4, readFileSync as readFileSync3, renameSync as renameSync2, unlinkSync as unlinkSync3, writeFileSync as writeFileSync3 } from "fs";
|
|
6720
6856
|
import { createHash as createHash18, randomUUID as randomUUID20 } from "crypto";
|
|
6721
6857
|
import { homedir as homedir3, release as osRelease, tmpdir as tmpdir4, version as osVersion } from "os";
|
|
6722
|
-
import { basename as basename7, dirname as dirname17, join as
|
|
6858
|
+
import { basename as basename7, dirname as dirname17, join as join30, relative as relative22, resolve as resolve31, sep as sep22 } from "path";
|
|
6723
6859
|
|
|
6724
6860
|
// src/agent/subagents.ts
|
|
6725
6861
|
import { z as z9 } from "zod";
|
|
@@ -7677,6 +7813,18 @@ var ContextManager = class _ContextManager {
|
|
|
7677
7813
|
estimatedTokens() {
|
|
7678
7814
|
return estimateMessageTokens(this.messagesForModel());
|
|
7679
7815
|
}
|
|
7816
|
+
/** Retained sizes for the rotation census: what this context holds right now. */
|
|
7817
|
+
census() {
|
|
7818
|
+
const chars = (messages) => messages.reduce((sum, entry2) => sum + modelContentText(entry2.content).length, 0);
|
|
7819
|
+
return {
|
|
7820
|
+
messages: this.#messages.length,
|
|
7821
|
+
messageChars: chars(this.#messages),
|
|
7822
|
+
visibilityEntries: this.#visibilityLog.length,
|
|
7823
|
+
visibilityChars: this.#visibilityLog.reduce((sum, record) => sum + record.content.length, 0),
|
|
7824
|
+
transientEntries: this.#activeTransientSystem.size,
|
|
7825
|
+
transientChars: chars([...this.#activeTransientSystem.values()])
|
|
7826
|
+
};
|
|
7827
|
+
}
|
|
7680
7828
|
needsCompaction() {
|
|
7681
7829
|
if (modelVisibleText(this.messagesForModel()).length >= this.#compactAtChars) return true;
|
|
7682
7830
|
return calculateContextPressure(this.#currentTokenUsage(), this.#compaction).shouldAutoCompact;
|
|
@@ -7717,10 +7865,6 @@ var ContextManager = class _ContextManager {
|
|
|
7717
7865
|
}
|
|
7718
7866
|
return microcompact.changed;
|
|
7719
7867
|
}
|
|
7720
|
-
if (this.#consecutiveAutoCompactFailures >= 3) {
|
|
7721
|
-
rollbackMicrocompact();
|
|
7722
|
-
return false;
|
|
7723
|
-
}
|
|
7724
7868
|
try {
|
|
7725
7869
|
const compacted = await this.#compactConversation(signal);
|
|
7726
7870
|
if (compacted) {
|
|
@@ -8184,10 +8328,10 @@ function within(root, candidate) {
|
|
|
8184
8328
|
|
|
8185
8329
|
// src/context/summarizer.ts
|
|
8186
8330
|
async function summarizeWithModel(options) {
|
|
8187
|
-
const maxAttempts = options.maxPromptTooLongAttempts ?? 3;
|
|
8188
|
-
if (!Number.isInteger(maxAttempts) || maxAttempts <= 0) throw new Error("maxPromptTooLongAttempts must be positive");
|
|
8189
8331
|
options.signal?.throwIfAborted();
|
|
8190
8332
|
let groups = groupMessagesByApiRound(options.messages);
|
|
8333
|
+
const maxAttempts = options.maxPromptTooLongAttempts ?? Math.max(1, Math.ceil(Math.log2(Math.max(1, groups.length))) + 1);
|
|
8334
|
+
if (!Number.isInteger(maxAttempts) || maxAttempts <= 0) throw new Error("maxPromptTooLongAttempts must be positive");
|
|
8191
8335
|
let lastOverflow;
|
|
8192
8336
|
let progress = 20;
|
|
8193
8337
|
reportProgress(options.onProgress, progress);
|
|
@@ -8228,7 +8372,7 @@ async function summarizeWithModel(options) {
|
|
|
8228
8372
|
if (terminalError.code !== "context_overflow") throw terminalError;
|
|
8229
8373
|
lastOverflow = terminalError;
|
|
8230
8374
|
if (attempt >= maxAttempts || groups.length <= 1) throw terminalError;
|
|
8231
|
-
groups = groups.slice(1);
|
|
8375
|
+
groups = groups.slice(Math.max(1, Math.floor(groups.length / 2)));
|
|
8232
8376
|
}
|
|
8233
8377
|
throw lastOverflow ?? new Error("Compact summary failed");
|
|
8234
8378
|
}
|
|
@@ -8245,8 +8389,75 @@ function reportProgress(callback, percentage) {
|
|
|
8245
8389
|
}
|
|
8246
8390
|
}
|
|
8247
8391
|
|
|
8248
|
-
// src/
|
|
8392
|
+
// src/utils/heap.ts
|
|
8249
8393
|
import { getHeapStatistics } from "v8";
|
|
8394
|
+
import { totalmem } from "os";
|
|
8395
|
+
var MEMORY_PRESSURE_HEAP_RATIO = 0.8;
|
|
8396
|
+
var MEMORY_ROTATION_HEAP_RATIO = 0.6;
|
|
8397
|
+
var MEMORY_PRESSURE_RSS_RATIO = 0.75;
|
|
8398
|
+
var MEMORY_ROTATION_RSS_RATIO = 0.6;
|
|
8399
|
+
var ROTATION_PRECHECK_RATIO = 0.45;
|
|
8400
|
+
function readHeap() {
|
|
8401
|
+
const heapLimit = getHeapStatistics().heap_size_limit;
|
|
8402
|
+
const heapUsed = process.memoryUsage().heapUsed;
|
|
8403
|
+
return { heapUsed, heapLimit, ratio: heapLimit > 0 ? heapUsed / heapLimit : 0 };
|
|
8404
|
+
}
|
|
8405
|
+
function forceFullGc() {
|
|
8406
|
+
const gc = globalThis.gc;
|
|
8407
|
+
if (typeof gc !== "function") return false;
|
|
8408
|
+
gc();
|
|
8409
|
+
gc();
|
|
8410
|
+
return true;
|
|
8411
|
+
}
|
|
8412
|
+
function ratioOf(heapLimit, heapUsed) {
|
|
8413
|
+
return heapLimit > 0 ? heapUsed / heapLimit : 0;
|
|
8414
|
+
}
|
|
8415
|
+
function effectiveMemoryLimit() {
|
|
8416
|
+
const physical = totalmem();
|
|
8417
|
+
const constrained = process.constrainedMemory?.() ?? 0;
|
|
8418
|
+
return constrained > 0 ? Math.min(physical, constrained) : physical;
|
|
8419
|
+
}
|
|
8420
|
+
function withRss(reading, gcVerified) {
|
|
8421
|
+
const usage = process.memoryUsage();
|
|
8422
|
+
const memoryLimit = effectiveMemoryLimit();
|
|
8423
|
+
return {
|
|
8424
|
+
...reading,
|
|
8425
|
+
gcVerified,
|
|
8426
|
+
rss: usage.rss,
|
|
8427
|
+
memoryLimit,
|
|
8428
|
+
rssRatio: ratioOf(memoryLimit, usage.rss),
|
|
8429
|
+
external: usage.external,
|
|
8430
|
+
arrayBuffers: usage.arrayBuffers
|
|
8431
|
+
};
|
|
8432
|
+
}
|
|
8433
|
+
function verifiedHeapPressure(threshold) {
|
|
8434
|
+
const raw = readHeap();
|
|
8435
|
+
const before = withRss(raw, false);
|
|
8436
|
+
const rssThreshold = threshold >= MEMORY_PRESSURE_HEAP_RATIO ? MEMORY_PRESSURE_RSS_RATIO : threshold;
|
|
8437
|
+
if ((raw.heapLimit <= 0 || raw.ratio < threshold) && (before.rssRatio ?? 0) < rssThreshold) return void 0;
|
|
8438
|
+
if (!forceFullGc()) return before;
|
|
8439
|
+
const heapUsed = process.memoryUsage().heapUsed;
|
|
8440
|
+
const ratio = ratioOf(raw.heapLimit, heapUsed);
|
|
8441
|
+
const verified = withRss({ heapUsed, heapLimit: raw.heapLimit, ratio, verifiedRatio: ratio }, true);
|
|
8442
|
+
if (ratio < threshold && (verified.rssRatio ?? 0) < rssThreshold) return void 0;
|
|
8443
|
+
return verified;
|
|
8444
|
+
}
|
|
8445
|
+
function heapRotationNeeded() {
|
|
8446
|
+
const raw = readHeap();
|
|
8447
|
+
const before = withRss(raw, false);
|
|
8448
|
+
if ((raw.heapLimit <= 0 || raw.ratio < ROTATION_PRECHECK_RATIO) && (before.rssRatio ?? 0) < ROTATION_PRECHECK_RATIO) return void 0;
|
|
8449
|
+
if (!forceFullGc()) {
|
|
8450
|
+
return raw.ratio >= MEMORY_PRESSURE_HEAP_RATIO || (before.rssRatio ?? 0) >= MEMORY_PRESSURE_RSS_RATIO ? before : void 0;
|
|
8451
|
+
}
|
|
8452
|
+
const heapUsed = process.memoryUsage().heapUsed;
|
|
8453
|
+
const ratio = ratioOf(raw.heapLimit, heapUsed);
|
|
8454
|
+
const verified = withRss({ heapUsed, heapLimit: raw.heapLimit, ratio, verifiedRatio: ratio }, true);
|
|
8455
|
+
if (ratio < MEMORY_ROTATION_HEAP_RATIO && (verified.rssRatio ?? 0) < MEMORY_ROTATION_RSS_RATIO) return void 0;
|
|
8456
|
+
return verified;
|
|
8457
|
+
}
|
|
8458
|
+
function heapSnapshotWarranted(_reading) {
|
|
8459
|
+
return process.env.FLAVOR_HEAP_SNAPSHOT_ON_ROTATE === "1";
|
|
8460
|
+
}
|
|
8250
8461
|
|
|
8251
8462
|
// src/models/structured.ts
|
|
8252
8463
|
import { z as z11 } from "zod";
|
|
@@ -8665,7 +8876,6 @@ function envMaxIterations() {
|
|
|
8665
8876
|
if (!Number.isInteger(parsed) || parsed <= 0) return void 0;
|
|
8666
8877
|
return parsed;
|
|
8667
8878
|
}
|
|
8668
|
-
var MEMORY_PRESSURE_HEAP_RATIO = 0.8;
|
|
8669
8879
|
function memoryPressureError(heapUsed, heapLimit) {
|
|
8670
8880
|
if (heapLimit <= 0 || heapUsed < heapLimit * MEMORY_PRESSURE_HEAP_RATIO) return void 0;
|
|
8671
8881
|
const mb = (bytes) => Math.round(bytes / 1048576);
|
|
@@ -8675,7 +8885,12 @@ function memoryPressureError(heapUsed, heapLimit) {
|
|
|
8675
8885
|
};
|
|
8676
8886
|
}
|
|
8677
8887
|
function currentMemoryPressure() {
|
|
8678
|
-
|
|
8888
|
+
const reading = verifiedHeapPressure(MEMORY_PRESSURE_HEAP_RATIO);
|
|
8889
|
+
if (reading === void 0) return void 0;
|
|
8890
|
+
return memoryPressureError(reading.heapUsed, reading.heapLimit) ?? {
|
|
8891
|
+
code: "memory_pressure",
|
|
8892
|
+
message: `Process RSS reached ${Math.round((reading.rssRatio ?? 0) * 100)}% of the available memory limit; stopping before system OOM`
|
|
8893
|
+
};
|
|
8679
8894
|
}
|
|
8680
8895
|
var AgentLoop = class {
|
|
8681
8896
|
#options;
|
|
@@ -10053,6 +10268,10 @@ var HarnessJournal = class {
|
|
|
10053
10268
|
get records() {
|
|
10054
10269
|
return this.#records.map((record) => structuredClone(record));
|
|
10055
10270
|
}
|
|
10271
|
+
/** Retained record count for the rotation census. */
|
|
10272
|
+
get recordCount() {
|
|
10273
|
+
return this.#records.length;
|
|
10274
|
+
}
|
|
10056
10275
|
admitQueue(kind, payload) {
|
|
10057
10276
|
QueueKindSchema.parse(kind);
|
|
10058
10277
|
const id = randomUUID8();
|
|
@@ -10329,19 +10548,38 @@ function jsonSafeRecord(value) {
|
|
|
10329
10548
|
return JSON.parse(json);
|
|
10330
10549
|
}
|
|
10331
10550
|
function hashJson(value) {
|
|
10332
|
-
|
|
10333
|
-
|
|
10334
|
-
|
|
10335
|
-
const { hash: _hash, ...unsigned } = record;
|
|
10336
|
-
return hashJson(unsigned);
|
|
10551
|
+
const hash = createHash6("sha256");
|
|
10552
|
+
stableHashInto(hash, value);
|
|
10553
|
+
return hash.digest("hex");
|
|
10337
10554
|
}
|
|
10338
|
-
function
|
|
10339
|
-
if (Array.isArray(value))
|
|
10555
|
+
function stableHashInto(hash, value) {
|
|
10556
|
+
if (Array.isArray(value)) {
|
|
10557
|
+
hash.update("[");
|
|
10558
|
+
for (let index = 0; index < value.length; index += 1) {
|
|
10559
|
+
if (index > 0) hash.update(",");
|
|
10560
|
+
stableHashInto(hash, value[index]);
|
|
10561
|
+
}
|
|
10562
|
+
hash.update("]");
|
|
10563
|
+
return;
|
|
10564
|
+
}
|
|
10340
10565
|
if (typeof value === "object" && value !== null) {
|
|
10341
10566
|
const object = value;
|
|
10342
|
-
|
|
10567
|
+
hash.update("{");
|
|
10568
|
+
const keys = Object.keys(object).sort();
|
|
10569
|
+
for (let index = 0; index < keys.length; index += 1) {
|
|
10570
|
+
const key = keys[index];
|
|
10571
|
+
if (index > 0) hash.update(",");
|
|
10572
|
+
hash.update(`${JSON.stringify(key)}:`);
|
|
10573
|
+
stableHashInto(hash, object[key]);
|
|
10574
|
+
}
|
|
10575
|
+
hash.update("}");
|
|
10576
|
+
return;
|
|
10343
10577
|
}
|
|
10344
|
-
|
|
10578
|
+
hash.update(JSON.stringify(value) ?? String(value));
|
|
10579
|
+
}
|
|
10580
|
+
function recordHash(record) {
|
|
10581
|
+
const { hash: _hash, ...unsigned } = record;
|
|
10582
|
+
return hashJson(unsigned);
|
|
10345
10583
|
}
|
|
10346
10584
|
function assertSessionId(value) {
|
|
10347
10585
|
if (!/^[A-Za-z0-9][A-Za-z0-9._-]{0,127}$/.test(value)) throw new Error("Invalid session id");
|
|
@@ -12457,6 +12695,14 @@ var LoopStateSchema = z17.object({
|
|
|
12457
12695
|
approvals: z17.array(LoopApprovalSchema)
|
|
12458
12696
|
}).strict(),
|
|
12459
12697
|
cycles: z17.array(LoopCycleEvidenceSchema),
|
|
12698
|
+
/** Completed worker awaiting host verification; avoids replaying tool work after rotation. */
|
|
12699
|
+
pendingCycle: z17.object({
|
|
12700
|
+
cycle: z17.number().int().positive(),
|
|
12701
|
+
startedAt: z17.string().datetime({ offset: true }),
|
|
12702
|
+
inputTokens: z17.number().int().nonnegative(),
|
|
12703
|
+
outputTokens: z17.number().int().nonnegative(),
|
|
12704
|
+
workerText: z17.string().max(16e3)
|
|
12705
|
+
}).strict().nullable().optional(),
|
|
12460
12706
|
terminalReason: z17.string().optional()
|
|
12461
12707
|
}).strict();
|
|
12462
12708
|
var LoopEventSchema = z17.object({
|
|
@@ -12506,7 +12752,7 @@ function extendBudget(input, dimensions, approvedAt) {
|
|
|
12506
12752
|
return LoopStateSchema.parse({
|
|
12507
12753
|
...state,
|
|
12508
12754
|
updatedAt: approvedAt,
|
|
12509
|
-
budget: { ...state.budget, cycleCheckpoint, tokenCheckpoint, approvals }
|
|
12755
|
+
budget: { ...state.budget, cycleCheckpoint, tokenCheckpoint, approvals: approvals.slice(-64) }
|
|
12510
12756
|
});
|
|
12511
12757
|
}
|
|
12512
12758
|
function rejectBudget(input, terminalReason) {
|
|
@@ -12545,15 +12791,45 @@ var LoopOrchestrator = class {
|
|
|
12545
12791
|
tokenCheckpoint: this.#options.config.maxTokens,
|
|
12546
12792
|
approvals: []
|
|
12547
12793
|
},
|
|
12548
|
-
cycles: []
|
|
12794
|
+
cycles: [],
|
|
12795
|
+
pendingCycle: null
|
|
12549
12796
|
});
|
|
12550
12797
|
await this.#options.persistence.create(state);
|
|
12798
|
+
yield* this.#execute(state, request.goal, request.signal);
|
|
12799
|
+
}
|
|
12800
|
+
/**
|
|
12801
|
+
* Continue a loop whose process rotated onto a fresh heap. The persisted
|
|
12802
|
+
* state decides where to pick up; an interrupted cycle simply runs again.
|
|
12803
|
+
*/
|
|
12804
|
+
async *resume(request) {
|
|
12805
|
+
const persistence = this.#options.persistence;
|
|
12806
|
+
if (persistence.load === void 0) throw new Error("Loop persistence does not support resume");
|
|
12807
|
+
let state;
|
|
12551
12808
|
try {
|
|
12809
|
+
state = await persistence.load(request.loopId);
|
|
12810
|
+
} catch (error) {
|
|
12811
|
+
yield { type: "loop-terminal", loopId: request.loopId, status: "failed", reason: message(error) };
|
|
12812
|
+
return;
|
|
12813
|
+
}
|
|
12814
|
+
if (state.status !== "running") {
|
|
12815
|
+
const status = state.status;
|
|
12816
|
+
yield { type: "loop-terminal", loopId: state.loopId, status, reason: state.terminalReason ?? "The loop is not running." };
|
|
12817
|
+
return;
|
|
12818
|
+
}
|
|
12819
|
+
yield* this.#execute(state, state.goal, request.signal);
|
|
12820
|
+
}
|
|
12821
|
+
async *#execute(initialState, goal, signal) {
|
|
12822
|
+
const now = this.#options.now ?? (() => (/* @__PURE__ */ new Date()).toISOString());
|
|
12823
|
+
const loopId = initialState.loopId;
|
|
12824
|
+
let state = initialState;
|
|
12825
|
+
try {
|
|
12826
|
+
await this.#options.onMemoryCheckpoint?.(loopId);
|
|
12827
|
+
if (memoryRotationActive()) return;
|
|
12552
12828
|
const workspaceResolution = await this.#options.prepareWorkspace({
|
|
12553
12829
|
root: this.#options.workspace,
|
|
12554
12830
|
loopId,
|
|
12555
|
-
goal
|
|
12556
|
-
signal
|
|
12831
|
+
goal,
|
|
12832
|
+
signal
|
|
12557
12833
|
});
|
|
12558
12834
|
if (workspaceResolution.kind === "needs_human") {
|
|
12559
12835
|
state = await this.#terminal(state, "needs_human", workspaceResolution.reason, now());
|
|
@@ -12571,67 +12847,90 @@ var LoopOrchestrator = class {
|
|
|
12571
12847
|
cycleCheckpoint: state.budget.cycleCheckpoint,
|
|
12572
12848
|
tokenCheckpoint: state.budget.tokenCheckpoint
|
|
12573
12849
|
};
|
|
12574
|
-
|
|
12850
|
+
const lastCompletedCycle = state.cycles.at(-1);
|
|
12851
|
+
let previousVerification = lastCompletedCycle?.verification ?? (plan.commands.length === 0 ? {
|
|
12575
12852
|
passed: false,
|
|
12576
12853
|
commands: [],
|
|
12577
12854
|
summary: `${plan.needsHumanReason ?? "No deterministic verification command was found."} Inspect the project and establish a meaningful project-native deterministic check; never add a trivial pass-through verifier.`
|
|
12578
|
-
} : void 0;
|
|
12579
|
-
let previousFailureSignature
|
|
12580
|
-
|
|
12855
|
+
} : void 0);
|
|
12856
|
+
let previousFailureSignature = lastCompletedCycle === void 0 ? void 0 : `${lastCompletedCycle.workspaceFingerprint}
|
|
12857
|
+
${lastCompletedCycle.verification.summary}`;
|
|
12858
|
+
let repeatedFailures = previousFailureSignature === void 0 ? 0 : countTrailingFailureSignature(state.cycles, previousFailureSignature);
|
|
12581
12859
|
while (state.status === "running") {
|
|
12582
|
-
|
|
12583
|
-
|
|
12584
|
-
|
|
12585
|
-
|
|
12586
|
-
|
|
12587
|
-
const
|
|
12588
|
-
|
|
12589
|
-
|
|
12590
|
-
|
|
12591
|
-
|
|
12592
|
-
|
|
12593
|
-
}
|
|
12594
|
-
|
|
12595
|
-
|
|
12596
|
-
|
|
12597
|
-
|
|
12598
|
-
|
|
12599
|
-
|
|
12600
|
-
goal: request.goal,
|
|
12601
|
-
cycle,
|
|
12602
|
-
workspace: executionWorkspace.root,
|
|
12603
|
-
prompt,
|
|
12604
|
-
signal: request.signal
|
|
12605
|
-
})) {
|
|
12606
|
-
if (event.type === "usage") {
|
|
12607
|
-
cycleInputTokens += event.inputTokens;
|
|
12608
|
-
cycleOutputTokens += event.outputTokens;
|
|
12609
|
-
}
|
|
12610
|
-
if (event.type === "error") workerError = event.error.message;
|
|
12611
|
-
if (event.type === "text") workerText += event.text;
|
|
12612
|
-
if (this.#options.hallucinationGuard !== void 0) {
|
|
12613
|
-
if (event.type === "tool-start") {
|
|
12614
|
-
this.#options.hallucinationGuard.recordToolCall(event.name, event.input, event.id);
|
|
12615
|
-
} else if (event.type === "tool-end") {
|
|
12616
|
-
this.#options.hallucinationGuard.recordToolResult(event.name, event.result, event.id);
|
|
12860
|
+
signal.throwIfAborted();
|
|
12861
|
+
await this.#options.onCycleBoundary?.(loopId);
|
|
12862
|
+
if (memoryRotationActive()) return;
|
|
12863
|
+
const pendingCycle = state.pendingCycle;
|
|
12864
|
+
const cycle = pendingCycle?.cycle ?? state.budget.cyclesUsed + 1;
|
|
12865
|
+
const startedAt = pendingCycle?.startedAt ?? now();
|
|
12866
|
+
let cycleInputTokens = pendingCycle?.inputTokens ?? 0;
|
|
12867
|
+
let cycleOutputTokens = pendingCycle?.outputTokens ?? 0;
|
|
12868
|
+
let workerText = pendingCycle?.workerText ?? "";
|
|
12869
|
+
if (pendingCycle === void 0 || pendingCycle === null) {
|
|
12870
|
+
await this.#options.persistence.append({ version: 1, type: "cycle_started", timestamp: startedAt, loopId, payload: { cycle } });
|
|
12871
|
+
yield { type: "loop-cycle-start", loopId, cycle };
|
|
12872
|
+
const prompt = buildLoopCyclePrompt({
|
|
12873
|
+
goal,
|
|
12874
|
+
cycle,
|
|
12875
|
+
...previousVerification === void 0 ? {} : {
|
|
12876
|
+
memory: `Previous host verification: ${previousVerification.summary}`,
|
|
12877
|
+
verification: previousVerification
|
|
12617
12878
|
}
|
|
12879
|
+
});
|
|
12880
|
+
let workerError;
|
|
12881
|
+
let workerMemoryPressure = false;
|
|
12882
|
+
let workerIterationLimit = false;
|
|
12883
|
+
for await (const event of this.#options.runWorker({
|
|
12884
|
+
goal,
|
|
12885
|
+
cycle,
|
|
12886
|
+
workspace: executionWorkspace.root,
|
|
12887
|
+
prompt,
|
|
12888
|
+
signal
|
|
12889
|
+
})) {
|
|
12890
|
+
if (event.type === "usage") {
|
|
12891
|
+
cycleInputTokens += event.inputTokens;
|
|
12892
|
+
cycleOutputTokens += event.outputTokens;
|
|
12893
|
+
}
|
|
12894
|
+
if (event.type === "error") {
|
|
12895
|
+
workerError = event.error;
|
|
12896
|
+
if (event.error.code === "memory_pressure") workerMemoryPressure = true;
|
|
12897
|
+
if (event.error.code === "iteration_limit") workerIterationLimit = true;
|
|
12898
|
+
}
|
|
12899
|
+
if (event.type === "text") workerText = `${workerText}${event.text}`.slice(-16e3);
|
|
12900
|
+
if (this.#options.hallucinationGuard !== void 0) {
|
|
12901
|
+
if (event.type === "tool-start") {
|
|
12902
|
+
this.#options.hallucinationGuard.recordToolCall(event.name, event.input, event.id);
|
|
12903
|
+
} else if (event.type === "tool-end") {
|
|
12904
|
+
this.#options.hallucinationGuard.recordToolResult(event.name, event.result, event.id);
|
|
12905
|
+
}
|
|
12906
|
+
}
|
|
12907
|
+
yield { type: "worker-event", event };
|
|
12908
|
+
}
|
|
12909
|
+
if (workerMemoryPressure) return;
|
|
12910
|
+
if (workerError !== void 0 && !workerIterationLimit && !isResumableWorkerError(workerError)) {
|
|
12911
|
+
const status = workerError.code === "cancelled" ? "cancelled" : "failed";
|
|
12912
|
+
state = LoopStateSchema.parse({
|
|
12913
|
+
...state,
|
|
12914
|
+
budget: {
|
|
12915
|
+
...state.budget,
|
|
12916
|
+
cyclesUsed: cycle,
|
|
12917
|
+
inputTokens: state.budget.inputTokens + cycleInputTokens,
|
|
12918
|
+
outputTokens: state.budget.outputTokens + cycleOutputTokens
|
|
12919
|
+
}
|
|
12920
|
+
});
|
|
12921
|
+
state = await this.#terminal(state, status, workerError.message, now());
|
|
12922
|
+
yield { type: "loop-terminal", loopId, status, reason: workerError.message };
|
|
12923
|
+
return;
|
|
12618
12924
|
}
|
|
12619
|
-
yield { type: "worker-event", event };
|
|
12620
|
-
}
|
|
12621
|
-
if (workerError !== void 0) {
|
|
12622
12925
|
state = LoopStateSchema.parse({
|
|
12623
12926
|
...state,
|
|
12624
|
-
|
|
12625
|
-
|
|
12626
|
-
cyclesUsed: cycle,
|
|
12627
|
-
inputTokens: state.budget.inputTokens + cycleInputTokens,
|
|
12628
|
-
outputTokens: state.budget.outputTokens + cycleOutputTokens
|
|
12629
|
-
}
|
|
12927
|
+
pendingCycle: { cycle, startedAt, inputTokens: cycleInputTokens, outputTokens: cycleOutputTokens, workerText },
|
|
12928
|
+
updatedAt: now()
|
|
12630
12929
|
});
|
|
12631
|
-
|
|
12632
|
-
yield { type: "loop-terminal", loopId, status: "failed", reason: workerError };
|
|
12633
|
-
return;
|
|
12930
|
+
await this.#options.persistence.save(state);
|
|
12634
12931
|
}
|
|
12932
|
+
await this.#options.onMemoryCheckpoint?.(loopId);
|
|
12933
|
+
if (memoryRotationActive()) return;
|
|
12635
12934
|
if (plan.commands.length === 0) {
|
|
12636
12935
|
plan = await this.#options.inferVerification(executionWorkspace.root);
|
|
12637
12936
|
if (plan.commands.length > 0) {
|
|
@@ -12651,8 +12950,22 @@ var LoopOrchestrator = class {
|
|
|
12651
12950
|
passed: false,
|
|
12652
12951
|
commands: [],
|
|
12653
12952
|
summary: plan.needsHumanReason ?? "No deterministic verification command was found after the discovery cycle."
|
|
12654
|
-
} : await this.#options.runVerifier(plan, executionWorkspace.root,
|
|
12953
|
+
} : await this.#options.runVerifier(plan, executionWorkspace.root, signal);
|
|
12954
|
+
await this.#options.onMemoryCheckpoint?.(loopId);
|
|
12955
|
+
if (memoryRotationActive()) return;
|
|
12655
12956
|
const fingerprint2 = await this.#options.fingerprint(executionWorkspace.root);
|
|
12957
|
+
let guardWarnings = [];
|
|
12958
|
+
if (evidence.passed && this.#options.hallucinationGuard !== void 0) {
|
|
12959
|
+
try {
|
|
12960
|
+
await this.#options.onMemoryCheckpoint?.(loopId);
|
|
12961
|
+
if (memoryRotationActive()) return;
|
|
12962
|
+
const report = await this.#options.hallucinationGuard.evaluate(goal, workerText);
|
|
12963
|
+
await this.#options.onMemoryCheckpoint?.(loopId);
|
|
12964
|
+
if (memoryRotationActive()) return;
|
|
12965
|
+
guardWarnings = [...report.blockingReasons, ...report.warnings];
|
|
12966
|
+
} catch {
|
|
12967
|
+
}
|
|
12968
|
+
}
|
|
12656
12969
|
const completedAt = now();
|
|
12657
12970
|
state = LoopStateSchema.parse({
|
|
12658
12971
|
...state,
|
|
@@ -12671,7 +12984,8 @@ var LoopOrchestrator = class {
|
|
|
12671
12984
|
outputTokens: cycleOutputTokens,
|
|
12672
12985
|
workspaceFingerprint: fingerprint2,
|
|
12673
12986
|
verification: evidence
|
|
12674
|
-
}]
|
|
12987
|
+
}].slice(-64),
|
|
12988
|
+
pendingCycle: null
|
|
12675
12989
|
});
|
|
12676
12990
|
await this.#options.persistence.save(state);
|
|
12677
12991
|
await this.#options.persistence.append({
|
|
@@ -12689,20 +13003,6 @@ var LoopOrchestrator = class {
|
|
|
12689
13003
|
return;
|
|
12690
13004
|
}
|
|
12691
13005
|
if (evidence.passed) {
|
|
12692
|
-
let guardWarnings = [];
|
|
12693
|
-
if (this.#options.hallucinationGuard !== void 0) {
|
|
12694
|
-
try {
|
|
12695
|
-
const report = await this.#options.hallucinationGuard.evaluate(request.goal, workerText);
|
|
12696
|
-
if (!report.passed) {
|
|
12697
|
-
const guardReason = report.blockingReasons.join("; ") || "Hallucination guard blocked completion.";
|
|
12698
|
-
state = await this.#terminal(state, "failed", guardReason, now());
|
|
12699
|
-
yield { type: "loop-terminal", loopId, status: "failed", reason: guardReason };
|
|
12700
|
-
return;
|
|
12701
|
-
}
|
|
12702
|
-
guardWarnings = report.warnings;
|
|
12703
|
-
} catch {
|
|
12704
|
-
}
|
|
12705
|
-
}
|
|
12706
13006
|
const reason = [evidence.summary, ...guardWarnings].filter(Boolean).join("; ");
|
|
12707
13007
|
state = await this.#terminal(state, "succeeded", reason, now());
|
|
12708
13008
|
yield { type: "loop-terminal", loopId, status: "succeeded", reason };
|
|
@@ -12713,8 +13013,8 @@ ${evidence.summary}`;
|
|
|
12713
13013
|
repeatedFailures = failureSignature === previousFailureSignature ? repeatedFailures + 1 : 1;
|
|
12714
13014
|
previousFailureSignature = failureSignature;
|
|
12715
13015
|
previousVerification = evidence;
|
|
12716
|
-
if (repeatedFailures >=
|
|
12717
|
-
const reason =
|
|
13016
|
+
if (this.#options.maxNoProgressCycles !== void 0 && repeatedFailures >= this.#options.maxNoProgressCycles) {
|
|
13017
|
+
const reason = `The same verification failure repeated ${this.#options.maxNoProgressCycles} times without material workspace progress.`;
|
|
12718
13018
|
state = await this.#terminal(state, "no_progress", reason, now());
|
|
12719
13019
|
yield { type: "loop-terminal", loopId, status: "no_progress", reason };
|
|
12720
13020
|
return;
|
|
@@ -12728,7 +13028,7 @@ ${evidence.summary}`;
|
|
|
12728
13028
|
cycleCheckpoint: state.budget.cycleCheckpoint,
|
|
12729
13029
|
tokenCheckpoint: state.budget.tokenCheckpoint
|
|
12730
13030
|
};
|
|
12731
|
-
const confirmation = await this.#options.confirmBudget(state, decision.dimensions,
|
|
13031
|
+
const confirmation = await this.#options.confirmBudget(state, decision.dimensions, signal);
|
|
12732
13032
|
if (confirmation === "unavailable") {
|
|
12733
13033
|
const reason = "Loop budget extension requires user confirmation.";
|
|
12734
13034
|
state = await this.#terminal(state, "needs_human", reason, now());
|
|
@@ -12755,8 +13055,9 @@ ${evidence.summary}`;
|
|
|
12755
13055
|
}
|
|
12756
13056
|
}
|
|
12757
13057
|
} catch (error) {
|
|
12758
|
-
|
|
12759
|
-
const
|
|
13058
|
+
if (memoryRotationActive()) return;
|
|
13059
|
+
const status = signal.aborted ? "cancelled" : "failed";
|
|
13060
|
+
const reason = signal.aborted ? "Loop cancelled by user." : message(error);
|
|
12760
13061
|
state = await this.#terminal(state, status, reason, now());
|
|
12761
13062
|
yield { type: "loop-terminal", loopId, status, reason };
|
|
12762
13063
|
}
|
|
@@ -12774,6 +13075,19 @@ ${evidence.summary}`;
|
|
|
12774
13075
|
return state;
|
|
12775
13076
|
}
|
|
12776
13077
|
};
|
|
13078
|
+
function isResumableWorkerError(error) {
|
|
13079
|
+
return error.code !== "authentication" && error.code !== "model_not_found" && error.code !== "cancelled" && error.code !== "memory_pressure";
|
|
13080
|
+
}
|
|
13081
|
+
function countTrailingFailureSignature(cycles, signature) {
|
|
13082
|
+
let count = 0;
|
|
13083
|
+
for (let index = cycles.length - 1; index >= 0; index -= 1) {
|
|
13084
|
+
const cycle = cycles[index];
|
|
13085
|
+
if (`${cycle.workspaceFingerprint}
|
|
13086
|
+
${cycle.verification.summary}` !== signature) break;
|
|
13087
|
+
count += 1;
|
|
13088
|
+
}
|
|
13089
|
+
return count;
|
|
13090
|
+
}
|
|
12777
13091
|
|
|
12778
13092
|
// src/goal/orchestrator.ts
|
|
12779
13093
|
import { createHash as createHash11 } from "crypto";
|
|
@@ -12841,6 +13155,11 @@ var GoalStateSchema = z18.object({
|
|
|
12841
13155
|
planPath: z18.string().nullable(),
|
|
12842
13156
|
verifyRounds: z18.number().int().min(0),
|
|
12843
13157
|
workerRounds: z18.number().int().min(0),
|
|
13158
|
+
/** Worker output checkpoint used to resume verification without rerunning tools. */
|
|
13159
|
+
pendingVerification: z18.object({
|
|
13160
|
+
round: z18.number().int().positive(),
|
|
13161
|
+
finalResponse: z18.string().max(16e3)
|
|
13162
|
+
}).strict().nullable().optional(),
|
|
12844
13163
|
lastGaps: z18.array(GapSchema),
|
|
12845
13164
|
gapFingerprint: z18.string(),
|
|
12846
13165
|
stallStreak: z18.number().int().min(0),
|
|
@@ -12886,6 +13205,7 @@ ${objective}`
|
|
|
12886
13205
|
].join("\n");
|
|
12887
13206
|
}
|
|
12888
13207
|
async function runPlanner(options) {
|
|
13208
|
+
await options.onProgress?.();
|
|
12889
13209
|
const { adapter, model } = options.registry.get(options.modelId);
|
|
12890
13210
|
const prompt = buildPlannerPrompt(options.objective);
|
|
12891
13211
|
const messages = [{ role: "user", content: prompt }];
|
|
@@ -12897,6 +13217,7 @@ async function runPlanner(options) {
|
|
|
12897
13217
|
tools: [],
|
|
12898
13218
|
...options.signal === void 0 ? {} : { signal: options.signal }
|
|
12899
13219
|
})) {
|
|
13220
|
+
await options.onProgress?.();
|
|
12900
13221
|
if (event.type === "text") text += event.text;
|
|
12901
13222
|
else if (event.type === "error") throw event.error;
|
|
12902
13223
|
else if (event.type === "done") {
|
|
@@ -13003,7 +13324,7 @@ async function runClassifier(evidence, plan, options) {
|
|
|
13003
13324
|
const skepticResults = await Promise.all(
|
|
13004
13325
|
Array.from(
|
|
13005
13326
|
{ length: n },
|
|
13006
|
-
(_, i) => runOneSkeptic(evidence, plan, i + 1, adapter, model, options.signal)
|
|
13327
|
+
(_, i) => runOneSkeptic(evidence, plan, i + 1, adapter, model, options.signal, options.onProgress)
|
|
13007
13328
|
)
|
|
13008
13329
|
);
|
|
13009
13330
|
const verdicts = [];
|
|
@@ -13020,7 +13341,8 @@ async function runClassifier(evidence, plan, options) {
|
|
|
13020
13341
|
}
|
|
13021
13342
|
return aggregate(verdicts);
|
|
13022
13343
|
}
|
|
13023
|
-
async function runOneSkeptic(evidence, plan, index, adapter, model, signal) {
|
|
13344
|
+
async function runOneSkeptic(evidence, plan, index, adapter, model, signal, onProgress) {
|
|
13345
|
+
await onProgress?.();
|
|
13024
13346
|
const prompt = buildSkepticPrompt(evidence, plan, index);
|
|
13025
13347
|
const messages = [{ role: "user", content: prompt }];
|
|
13026
13348
|
let text = "";
|
|
@@ -13030,6 +13352,7 @@ async function runOneSkeptic(evidence, plan, index, adapter, model, signal) {
|
|
|
13030
13352
|
tools: [],
|
|
13031
13353
|
...signal === void 0 ? {} : { signal }
|
|
13032
13354
|
})) {
|
|
13355
|
+
await onProgress?.();
|
|
13033
13356
|
if (event.type === "text") text += event.text;
|
|
13034
13357
|
else if (event.type === "error") throw event.error;
|
|
13035
13358
|
else if (event.type === "done") break;
|
|
@@ -13184,7 +13507,7 @@ var GoalOrchestrator = class {
|
|
|
13184
13507
|
async *run(request) {
|
|
13185
13508
|
const timestamp2 = () => this.#options.now?.() ?? (/* @__PURE__ */ new Date()).toISOString();
|
|
13186
13509
|
const createdAt = timestamp2();
|
|
13187
|
-
|
|
13510
|
+
const state = {
|
|
13188
13511
|
id: this.#options.idFactory?.() ?? `goal-${Date.now().toString(36)}`,
|
|
13189
13512
|
objective: request.goal,
|
|
13190
13513
|
phase: "planning",
|
|
@@ -13193,6 +13516,7 @@ var GoalOrchestrator = class {
|
|
|
13193
13516
|
planPath: null,
|
|
13194
13517
|
verifyRounds: 0,
|
|
13195
13518
|
workerRounds: 0,
|
|
13519
|
+
pendingVerification: null,
|
|
13196
13520
|
lastGaps: [],
|
|
13197
13521
|
gapFingerprint: "",
|
|
13198
13522
|
stallStreak: 0,
|
|
@@ -13201,91 +13525,164 @@ var GoalOrchestrator = class {
|
|
|
13201
13525
|
createdAt,
|
|
13202
13526
|
updatedAt: createdAt
|
|
13203
13527
|
};
|
|
13528
|
+
yield* this.#execute(state, request.signal);
|
|
13529
|
+
}
|
|
13530
|
+
/**
|
|
13531
|
+
* Continue a goal whose process rotated onto a fresh heap. The persisted
|
|
13532
|
+
* state decides where to pick up; an interrupted round simply runs again.
|
|
13533
|
+
*/
|
|
13534
|
+
async *resume(request) {
|
|
13535
|
+
const persistence = this.#options.persistence;
|
|
13536
|
+
if (persistence === void 0 || persistence.load === void 0) {
|
|
13537
|
+
throw new Error("Goal persistence does not support resume");
|
|
13538
|
+
}
|
|
13539
|
+
let state;
|
|
13540
|
+
try {
|
|
13541
|
+
state = await persistence.load(request.goalId);
|
|
13542
|
+
} catch (error) {
|
|
13543
|
+
yield { type: "goal-failed", reason: message(error) };
|
|
13544
|
+
return;
|
|
13545
|
+
}
|
|
13546
|
+
if (state.status !== "active" && state.status !== "not_achieved") {
|
|
13547
|
+
yield { type: "goal-failed", reason: `Goal "${state.id}" is ${state.status}; it cannot be resumed.` };
|
|
13548
|
+
return;
|
|
13549
|
+
}
|
|
13550
|
+
yield { type: "goal-resumed", goalId: state.id, round: resumeStartRound(state) };
|
|
13551
|
+
yield* this.#execute(state, request.signal);
|
|
13552
|
+
}
|
|
13553
|
+
async *#execute(initialState, signal) {
|
|
13554
|
+
const timestamp2 = () => this.#options.now?.() ?? (/* @__PURE__ */ new Date()).toISOString();
|
|
13555
|
+
let state = initialState;
|
|
13204
13556
|
const persistState = async (patch) => {
|
|
13205
13557
|
state = { ...state, ...patch, updatedAt: timestamp2() };
|
|
13206
13558
|
await this.#options.persistence?.save(state);
|
|
13207
13559
|
};
|
|
13560
|
+
const memoryCheckpoint = async () => {
|
|
13561
|
+
await this.#options.onMemoryCheckpoint?.(state.id);
|
|
13562
|
+
if (memoryRotationActive()) throw new Error("Heap rotation requested");
|
|
13563
|
+
};
|
|
13208
13564
|
await persistState();
|
|
13209
13565
|
const workspace = this.#options.workspace;
|
|
13210
13566
|
let plan;
|
|
13211
13567
|
let planPath;
|
|
13212
|
-
|
|
13213
|
-
|
|
13214
|
-
|
|
13215
|
-
|
|
13216
|
-
|
|
13217
|
-
|
|
13218
|
-
|
|
13219
|
-
|
|
13220
|
-
|
|
13221
|
-
|
|
13222
|
-
|
|
13223
|
-
|
|
13224
|
-
|
|
13225
|
-
|
|
13226
|
-
|
|
13227
|
-
|
|
13228
|
-
|
|
13229
|
-
|
|
13230
|
-
|
|
13231
|
-
|
|
13232
|
-
|
|
13233
|
-
|
|
13234
|
-
|
|
13235
|
-
|
|
13236
|
-
|
|
13237
|
-
|
|
13238
|
-
status: "active",
|
|
13239
|
-
workerRounds: round,
|
|
13240
|
-
lastGaps: priorGaps,
|
|
13241
|
-
gapFingerprint: priorFingerprint,
|
|
13242
|
-
stallStreak
|
|
13243
|
-
});
|
|
13244
|
-
yield { type: "goal-worker-start", round };
|
|
13245
|
-
let finalResponse = "";
|
|
13246
|
-
let workerError;
|
|
13247
|
-
try {
|
|
13248
|
-
for await (const event of this.#options.runWorker({
|
|
13249
|
-
goal: request.goal,
|
|
13250
|
-
round,
|
|
13251
|
-
workspace,
|
|
13252
|
-
prompt: workerPrompt,
|
|
13253
|
-
priorGaps: formatPriorGaps(priorGaps),
|
|
13254
|
-
signal: request.signal
|
|
13255
|
-
})) {
|
|
13256
|
-
yield { type: "goal-worker-event", round, event };
|
|
13257
|
-
if (event.type === "text") finalResponse += event.text;
|
|
13258
|
-
if (event.type === "error") {
|
|
13259
|
-
workerError = event.error.message;
|
|
13260
|
-
break;
|
|
13568
|
+
if (state.plan === null) {
|
|
13569
|
+
let planningAttempt = 0;
|
|
13570
|
+
while (true) {
|
|
13571
|
+
try {
|
|
13572
|
+
signal.throwIfAborted();
|
|
13573
|
+
await memoryCheckpoint();
|
|
13574
|
+
plan = await runPlanner({
|
|
13575
|
+
registry: this.#options.registry,
|
|
13576
|
+
modelId: this.#options.plannerModelId,
|
|
13577
|
+
objective: state.objective,
|
|
13578
|
+
signal,
|
|
13579
|
+
onProgress: memoryCheckpoint
|
|
13580
|
+
});
|
|
13581
|
+
planPath = await writePlanFile(workspace, plan);
|
|
13582
|
+
await persistState({ phase: "executing", plan, planPath, contractHash: contractHash(state.objective, plan) });
|
|
13583
|
+
yield { type: "goal-plan-created", plan, planPath };
|
|
13584
|
+
break;
|
|
13585
|
+
} catch (error) {
|
|
13586
|
+
if (memoryRotationActive()) return;
|
|
13587
|
+
const normalized = normalizeProviderError(error);
|
|
13588
|
+
if (!isResumableStageError(normalized)) {
|
|
13589
|
+
const reason = `Goal planning failed: ${normalized.message}`;
|
|
13590
|
+
await persistState({ phase: "complete", status: "failed" });
|
|
13591
|
+
yield { type: "goal-plan-failed", reason };
|
|
13592
|
+
yield { type: "goal-failed", reason };
|
|
13593
|
+
return;
|
|
13261
13594
|
}
|
|
13595
|
+
planningAttempt += 1;
|
|
13596
|
+
const delayMs = stageRetryDelay(this.#options, planningAttempt);
|
|
13597
|
+
yield { type: "goal-stage-retry", stage: "planning", attempt: planningAttempt, reason: normalized.message, delayMs };
|
|
13598
|
+
await waitForStageRetry(delayMs, signal);
|
|
13262
13599
|
}
|
|
13263
|
-
} catch (error) {
|
|
13264
|
-
workerError = message(error);
|
|
13265
13600
|
}
|
|
13266
|
-
|
|
13267
|
-
|
|
13268
|
-
|
|
13269
|
-
|
|
13270
|
-
|
|
13601
|
+
} else {
|
|
13602
|
+
plan = state.plan;
|
|
13603
|
+
planPath = state.planPath ?? await writePlanFile(workspace, plan);
|
|
13604
|
+
await persistState({ phase: "executing", status: "active", ...state.planPath === null ? { planPath } : {} });
|
|
13605
|
+
}
|
|
13606
|
+
let priorGaps = state.lastGaps;
|
|
13607
|
+
let priorFingerprint = state.gapFingerprint;
|
|
13608
|
+
let stallStreak = state.stallStreak;
|
|
13609
|
+
const startRound = resumeStartRound(state);
|
|
13610
|
+
for (let round = startRound; this.#options.maxRounds === void 0 || round <= this.#options.maxRounds; round++) {
|
|
13611
|
+
signal.throwIfAborted();
|
|
13612
|
+
await this.#options.onRoundBoundary?.(state.id);
|
|
13613
|
+
if (memoryRotationActive()) return;
|
|
13614
|
+
const pendingVerification = state.pendingVerification?.round === round ? state.pendingVerification : void 0;
|
|
13615
|
+
let finalResponse = pendingVerification?.finalResponse ?? "";
|
|
13616
|
+
if (pendingVerification === void 0) {
|
|
13617
|
+
const workerPrompt = buildWorkerPrompt(state.objective, plan, priorGaps, round);
|
|
13618
|
+
await persistState({
|
|
13619
|
+
phase: "executing",
|
|
13620
|
+
status: "active",
|
|
13621
|
+
workerRounds: round,
|
|
13622
|
+
pendingVerification: null,
|
|
13623
|
+
lastGaps: priorGaps,
|
|
13624
|
+
gapFingerprint: priorFingerprint,
|
|
13625
|
+
stallStreak
|
|
13626
|
+
});
|
|
13627
|
+
yield { type: "goal-worker-start", round };
|
|
13628
|
+
let workerError;
|
|
13629
|
+
let workerMemoryPressure = false;
|
|
13630
|
+
let workerIterationLimit = false;
|
|
13631
|
+
try {
|
|
13632
|
+
for await (const event of this.#options.runWorker({
|
|
13633
|
+
goal: state.objective,
|
|
13634
|
+
round,
|
|
13635
|
+
workspace,
|
|
13636
|
+
prompt: workerPrompt,
|
|
13637
|
+
priorGaps: formatPriorGaps(priorGaps),
|
|
13638
|
+
signal
|
|
13639
|
+
})) {
|
|
13640
|
+
yield { type: "goal-worker-event", round, event };
|
|
13641
|
+
if (event.type === "text") finalResponse = `${finalResponse}${event.text}`.slice(-16e3);
|
|
13642
|
+
if (event.type === "error") {
|
|
13643
|
+
workerError = event.error;
|
|
13644
|
+
if (event.error.code === "memory_pressure") workerMemoryPressure = true;
|
|
13645
|
+
if (event.error.code === "iteration_limit") workerIterationLimit = true;
|
|
13646
|
+
break;
|
|
13647
|
+
}
|
|
13648
|
+
}
|
|
13649
|
+
} catch (error) {
|
|
13650
|
+
if (memoryRotationActive()) return;
|
|
13651
|
+
workerError = { code: "unknown", message: message(error) };
|
|
13652
|
+
}
|
|
13653
|
+
if (workerMemoryPressure) return;
|
|
13654
|
+
if (workerError !== void 0 && !workerIterationLimit && !isResumableWorkerError2(workerError)) {
|
|
13655
|
+
const reason = `Worker error in round ${round}: ${workerError.message}`;
|
|
13656
|
+
await persistState({ phase: "complete", status: "failed" });
|
|
13657
|
+
yield { type: "goal-failed", reason };
|
|
13658
|
+
return;
|
|
13659
|
+
}
|
|
13660
|
+
await persistState({
|
|
13661
|
+
phase: "verifying",
|
|
13662
|
+
status: "active",
|
|
13663
|
+
pendingVerification: { round, finalResponse }
|
|
13664
|
+
});
|
|
13271
13665
|
}
|
|
13272
|
-
await persistState({ phase: "verifying", status: "active", verifyRounds: round });
|
|
13273
13666
|
yield { type: "goal-verification-start", round };
|
|
13274
|
-
|
|
13667
|
+
await this.#options.onMemoryCheckpoint?.(state.id);
|
|
13668
|
+
if (memoryRotationActive()) return;
|
|
13669
|
+
const hostVerification = await this.#options.verifyHost?.(signal);
|
|
13670
|
+
await this.#options.onMemoryCheckpoint?.(state.id);
|
|
13671
|
+
if (memoryRotationActive()) return;
|
|
13275
13672
|
const evidence = await collectEvidence(
|
|
13276
13673
|
workspace,
|
|
13277
|
-
|
|
13674
|
+
state.objective,
|
|
13278
13675
|
finalResponse.slice(-4e3),
|
|
13279
13676
|
// Last 4000 chars of response
|
|
13280
13677
|
formatPriorGaps(priorGaps),
|
|
13281
13678
|
state.contractHash,
|
|
13282
13679
|
hostVerification
|
|
13283
13680
|
);
|
|
13284
|
-
await persistState({ evidenceRounds: [...state.evidenceRounds, {
|
|
13681
|
+
await persistState({ evidenceRounds: [...state.evidenceRounds.filter((item) => item.round !== round), {
|
|
13285
13682
|
round,
|
|
13286
13683
|
workspaceDiffHash: evidence.workspaceDiffHash,
|
|
13287
13684
|
...hostVerification === void 0 ? {} : { hostVerification }
|
|
13288
|
-
}] });
|
|
13685
|
+
}].slice(-64) });
|
|
13289
13686
|
let outcome;
|
|
13290
13687
|
if (plan.kind === "code-change" && hostVerification !== void 0 && !hostVerification.passed) {
|
|
13291
13688
|
if (hostVerification.commands.length === 0) {
|
|
@@ -13299,20 +13696,34 @@ var GoalOrchestrator = class {
|
|
|
13299
13696
|
fingerprint: createHash11("sha256").update(hostVerification.summary).digest("hex").slice(0, 16)
|
|
13300
13697
|
};
|
|
13301
13698
|
}
|
|
13302
|
-
} else
|
|
13303
|
-
|
|
13304
|
-
|
|
13305
|
-
|
|
13306
|
-
|
|
13307
|
-
|
|
13308
|
-
|
|
13309
|
-
|
|
13310
|
-
|
|
13311
|
-
|
|
13312
|
-
|
|
13313
|
-
|
|
13314
|
-
|
|
13699
|
+
} else {
|
|
13700
|
+
let classifierAttempt = 0;
|
|
13701
|
+
while (true) {
|
|
13702
|
+
try {
|
|
13703
|
+
outcome = await runClassifier(evidence, plan, {
|
|
13704
|
+
registry: this.#options.registry,
|
|
13705
|
+
modelId: this.#options.classifierModelId,
|
|
13706
|
+
skepticCount: this.#options.skepticCount,
|
|
13707
|
+
workspace,
|
|
13708
|
+
signal,
|
|
13709
|
+
onProgress: memoryCheckpoint
|
|
13710
|
+
});
|
|
13711
|
+
break;
|
|
13712
|
+
} catch (error) {
|
|
13713
|
+
if (memoryRotationActive()) return;
|
|
13714
|
+
const normalized = normalizeProviderError(error);
|
|
13715
|
+
if (!isResumableStageError(normalized)) {
|
|
13716
|
+
outcome = { type: "blocked", reason: `Classifier infrastructure error: ${normalized.message}` };
|
|
13717
|
+
break;
|
|
13718
|
+
}
|
|
13719
|
+
classifierAttempt += 1;
|
|
13720
|
+
const delayMs = stageRetryDelay(this.#options, classifierAttempt);
|
|
13721
|
+
yield { type: "goal-stage-retry", stage: "classification", attempt: classifierAttempt, reason: normalized.message, delayMs };
|
|
13722
|
+
await waitForStageRetry(delayMs, signal);
|
|
13723
|
+
}
|
|
13724
|
+
}
|
|
13315
13725
|
}
|
|
13726
|
+
await persistState({ verifyRounds: round, pendingVerification: null });
|
|
13316
13727
|
yield {
|
|
13317
13728
|
type: "goal-verdict",
|
|
13318
13729
|
round,
|
|
@@ -13332,7 +13743,7 @@ var GoalOrchestrator = class {
|
|
|
13332
13743
|
}
|
|
13333
13744
|
if (outcome.fingerprint === priorFingerprint) {
|
|
13334
13745
|
stallStreak++;
|
|
13335
|
-
if (stallStreak >= this.#options.maxStallStreak) {
|
|
13746
|
+
if (this.#options.maxStallStreak !== void 0 && stallStreak >= this.#options.maxStallStreak) {
|
|
13336
13747
|
await persistState({
|
|
13337
13748
|
phase: "complete",
|
|
13338
13749
|
status: "failed",
|
|
@@ -13372,6 +13783,37 @@ var GoalOrchestrator = class {
|
|
|
13372
13783
|
function contractHash(objective, plan) {
|
|
13373
13784
|
return createHash11("sha256").update(JSON.stringify({ objective, plan })).digest("hex");
|
|
13374
13785
|
}
|
|
13786
|
+
function resumeStartRound(state) {
|
|
13787
|
+
if (state.pendingVerification !== void 0 && state.pendingVerification !== null) {
|
|
13788
|
+
return state.pendingVerification.round;
|
|
13789
|
+
}
|
|
13790
|
+
return state.verifyRounds >= state.workerRounds ? state.workerRounds + 1 : Math.max(state.workerRounds, 1);
|
|
13791
|
+
}
|
|
13792
|
+
function isResumableWorkerError2(error) {
|
|
13793
|
+
return error.code !== "authentication" && error.code !== "model_not_found" && error.code !== "cancelled" && error.code !== "memory_pressure";
|
|
13794
|
+
}
|
|
13795
|
+
function isResumableStageError(error) {
|
|
13796
|
+
return error.code !== "authentication" && error.code !== "model_not_found" && error.code !== "cancelled" && error.code !== "memory_pressure";
|
|
13797
|
+
}
|
|
13798
|
+
function stageRetryDelay(options, attempt) {
|
|
13799
|
+
const delayMs = options.stageRetryDelayMs?.(attempt) ?? Math.min(6e4, 1e3 * 2 ** Math.min(attempt - 1, 6));
|
|
13800
|
+
return Number.isSafeInteger(delayMs) && delayMs >= 0 ? delayMs : 1e3;
|
|
13801
|
+
}
|
|
13802
|
+
function waitForStageRetry(delayMs, signal) {
|
|
13803
|
+
signal.throwIfAborted();
|
|
13804
|
+
if (delayMs === 0) return Promise.resolve();
|
|
13805
|
+
return new Promise((resolvePromise, reject) => {
|
|
13806
|
+
const onAbort = () => {
|
|
13807
|
+
clearTimeout(timer);
|
|
13808
|
+
reject(signal.reason instanceof Error ? signal.reason : new Error("Goal cancelled"));
|
|
13809
|
+
};
|
|
13810
|
+
const timer = setTimeout(() => {
|
|
13811
|
+
signal.removeEventListener("abort", onAbort);
|
|
13812
|
+
resolvePromise();
|
|
13813
|
+
}, delayMs);
|
|
13814
|
+
signal.addEventListener("abort", onAbort, { once: true });
|
|
13815
|
+
});
|
|
13816
|
+
}
|
|
13375
13817
|
async function writePlanFile(workspace, plan) {
|
|
13376
13818
|
const dir = join17(workspace, ".flavor");
|
|
13377
13819
|
const planPath = join17(dir, "goal-plan.md");
|
|
@@ -13451,7 +13893,7 @@ function formatPriorGaps(gaps) {
|
|
|
13451
13893
|
// src/goal/store.ts
|
|
13452
13894
|
import { constants as constants4 } from "fs";
|
|
13453
13895
|
import { randomUUID as randomUUID11 } from "crypto";
|
|
13454
|
-
import { lstat as lstat7, mkdir as mkdir11, open as open6, rename as rename4, rm as rm4 } from "fs/promises";
|
|
13896
|
+
import { lstat as lstat7, mkdir as mkdir11, open as open6, readFile as readFile11, rename as rename4, rm as rm4, stat as stat6 } from "fs/promises";
|
|
13455
13897
|
import { dirname as dirname10, join as join18, relative as relative11, resolve as resolve14, sep as sep11 } from "path";
|
|
13456
13898
|
var DEFAULT_MAX_BYTES = 1024 * 1024;
|
|
13457
13899
|
var GoalStore = class {
|
|
@@ -13488,6 +13930,28 @@ var GoalStore = class {
|
|
|
13488
13930
|
await rm4(temporary, { force: true }).catch(() => void 0);
|
|
13489
13931
|
}
|
|
13490
13932
|
}
|
|
13933
|
+
/** Reload a persisted goal so a rotated process can resume it. */
|
|
13934
|
+
async load(goalId) {
|
|
13935
|
+
if (!/^[A-Za-z0-9][A-Za-z0-9._-]{0,127}$/.test(goalId)) {
|
|
13936
|
+
throw new Error(`Goal "${goalId}" was not found`);
|
|
13937
|
+
}
|
|
13938
|
+
await assertNoSymlink(this.#workspace, dirname10(this.#root));
|
|
13939
|
+
await assertNoSymlink(this.#workspace, this.#root);
|
|
13940
|
+
const target = join18(this.#root, `${goalId}.json`);
|
|
13941
|
+
try {
|
|
13942
|
+
const metadata = await stat6(target);
|
|
13943
|
+
if (!metadata.isFile() || metadata.size > this.#maxBytes) {
|
|
13944
|
+
throw new Error("Goal state is not a bounded regular file");
|
|
13945
|
+
}
|
|
13946
|
+
const raw = await readFile11(target, "utf8");
|
|
13947
|
+
return GoalStateSchema.parse(JSON.parse(raw));
|
|
13948
|
+
} catch (error) {
|
|
13949
|
+
if (isCode3(error, "ENOENT")) throw new Error(`Goal "${goalId}" was not found`);
|
|
13950
|
+
const quarantine = `${target}.corrupt-${Date.now()}-${randomUUID11().slice(0, 8)}`;
|
|
13951
|
+
await rename4(target, quarantine).catch(() => void 0);
|
|
13952
|
+
throw new Error(`Goal "${goalId}" is corrupt and was quarantined`);
|
|
13953
|
+
}
|
|
13954
|
+
}
|
|
13491
13955
|
};
|
|
13492
13956
|
async function assertNoSymlink(root, target) {
|
|
13493
13957
|
let cursor = resolve14(target);
|
|
@@ -13514,7 +13978,7 @@ function isCode3(error, code) {
|
|
|
13514
13978
|
}
|
|
13515
13979
|
|
|
13516
13980
|
// src/loop/isolation.ts
|
|
13517
|
-
import { appendFile as appendFile2, mkdir as mkdir12, readFile as
|
|
13981
|
+
import { appendFile as appendFile2, mkdir as mkdir12, readFile as readFile12 } from "fs/promises";
|
|
13518
13982
|
import { join as join19, resolve as resolve15 } from "path";
|
|
13519
13983
|
var CODE_CHANGE = /\b(?:fix|implement|add|update|change|modify|refactor|build|create|remove|delete|write|migrate)\b|修复|实现|添加|更新|修改|重构|创建|删除|迁移/i;
|
|
13520
13984
|
var READ_ONLY = /\b(?:review|analy[sz]e|inspect|explain|research|investigate|audit|summari[sz]e|report)\b|调研|分析|审查|检查|解释|研究|总结|报告/i;
|
|
@@ -13574,7 +14038,7 @@ async function ensureWorktreeDirectoryIgnored(root) {
|
|
|
13574
14038
|
await mkdir12(infoDirectory, { recursive: true });
|
|
13575
14039
|
let existing = "";
|
|
13576
14040
|
try {
|
|
13577
|
-
existing = await
|
|
14041
|
+
existing = await readFile12(excludePath, "utf8");
|
|
13578
14042
|
} catch (error) {
|
|
13579
14043
|
if (!(typeof error === "object" && error !== null && "code" in error && error.code === "ENOENT")) throw error;
|
|
13580
14044
|
}
|
|
@@ -13585,7 +14049,7 @@ async function ensureWorktreeDirectoryIgnored(root) {
|
|
|
13585
14049
|
|
|
13586
14050
|
// src/loop/store.ts
|
|
13587
14051
|
import { constants as constants5 } from "fs";
|
|
13588
|
-
import { lstat as lstat8, mkdir as mkdir13, open as open7, readFile as
|
|
14052
|
+
import { lstat as lstat8, mkdir as mkdir13, open as open7, readFile as readFile13, realpath as realpath5, rename as rename5, rm as rm5, stat as stat7 } from "fs/promises";
|
|
13589
14053
|
import { dirname as dirname11, join as join20, relative as relative12, resolve as resolve16, sep as sep12 } from "path";
|
|
13590
14054
|
import { randomUUID as randomUUID12 } from "crypto";
|
|
13591
14055
|
var LOOP_ID = /^[A-Za-z0-9][A-Za-z0-9._-]{0,127}$/;
|
|
@@ -13636,9 +14100,9 @@ var LoopStore = class {
|
|
|
13636
14100
|
const directory = await this.#safeExisting(loopId);
|
|
13637
14101
|
const target = join20(directory, "state.json");
|
|
13638
14102
|
try {
|
|
13639
|
-
const metadata = await
|
|
14103
|
+
const metadata = await stat7(target);
|
|
13640
14104
|
if (!metadata.isFile() || metadata.size > this.#maxBytes) throw new Error("Loop state is not a bounded regular file");
|
|
13641
|
-
const raw = await
|
|
14105
|
+
const raw = await readFile13(target, "utf8");
|
|
13642
14106
|
const state = LoopStateSchema.parse(JSON.parse(raw));
|
|
13643
14107
|
await this.#assertWorkspace(state.workspace);
|
|
13644
14108
|
return state;
|
|
@@ -13727,9 +14191,10 @@ function isCode4(error, code) {
|
|
|
13727
14191
|
}
|
|
13728
14192
|
|
|
13729
14193
|
// src/loop/verifier.ts
|
|
13730
|
-
import { readFile as
|
|
14194
|
+
import { readFile as readFile14 } from "fs/promises";
|
|
13731
14195
|
import { join as join21 } from "path";
|
|
13732
14196
|
var SCRIPT_PRIORITY = ["test", "typecheck", "lint", "build", "smoke:install"];
|
|
14197
|
+
var MAX_PERSISTED_VERIFICATION_STREAM_BYTES = 1024;
|
|
13733
14198
|
async function inferVerificationPlan(workspace) {
|
|
13734
14199
|
const commands = [];
|
|
13735
14200
|
const seen = /* @__PURE__ */ new Set();
|
|
@@ -13765,9 +14230,9 @@ async function runVerificationPlan(plan, workspace, signal, executionEnvironment
|
|
|
13765
14230
|
command: item.command,
|
|
13766
14231
|
args: item.args,
|
|
13767
14232
|
exitCode: result.exitCode,
|
|
13768
|
-
stdout: result.stdout,
|
|
13769
|
-
stderr: result.stderr,
|
|
13770
|
-
truncated: result.truncated
|
|
14233
|
+
stdout: boundedEvidenceTail(result.stdout),
|
|
14234
|
+
stderr: boundedEvidenceTail(result.stderr),
|
|
14235
|
+
truncated: result.truncated || Buffer.byteLength(result.stdout, "utf8") > MAX_PERSISTED_VERIFICATION_STREAM_BYTES || Buffer.byteLength(result.stderr, "utf8") > MAX_PERSISTED_VERIFICATION_STREAM_BYTES
|
|
13771
14236
|
});
|
|
13772
14237
|
if (result.exitCode !== 0) {
|
|
13773
14238
|
return { passed: false, commands, summary: `${item.label} failed with exit code ${String(result.exitCode)}.` };
|
|
@@ -13775,6 +14240,14 @@ async function runVerificationPlan(plan, workspace, signal, executionEnvironment
|
|
|
13775
14240
|
}
|
|
13776
14241
|
return { passed: true, commands, summary: `Passed ${commands.length} deterministic verification command(s).` };
|
|
13777
14242
|
}
|
|
14243
|
+
function boundedEvidenceTail(value) {
|
|
14244
|
+
const encoded = Buffer.from(value, "utf8");
|
|
14245
|
+
if (encoded.byteLength <= MAX_PERSISTED_VERIFICATION_STREAM_BYTES) return value;
|
|
14246
|
+
const prefix = Buffer.from("\u2026\n", "utf8");
|
|
14247
|
+
let start = encoded.byteLength - (MAX_PERSISTED_VERIFICATION_STREAM_BYTES - prefix.byteLength);
|
|
14248
|
+
while (start < encoded.byteLength && (encoded[start] & 192) === 128) start += 1;
|
|
14249
|
+
return `${prefix.toString("utf8")}${encoded.subarray(start).toString("utf8")}`;
|
|
14250
|
+
}
|
|
13778
14251
|
function npmScript(name) {
|
|
13779
14252
|
return name === "test" ? { label: name, command: "npm", args: ["test"] } : { label: name, command: "npm", args: ["run", name] };
|
|
13780
14253
|
}
|
|
@@ -13806,7 +14279,7 @@ function isUnconditionalPassThrough(script) {
|
|
|
13806
14279
|
}
|
|
13807
14280
|
async function optionalText(path) {
|
|
13808
14281
|
try {
|
|
13809
|
-
return await
|
|
14282
|
+
return await readFile14(path, "utf8");
|
|
13810
14283
|
} catch (error) {
|
|
13811
14284
|
if (isCode5(error, "ENOENT")) return void 0;
|
|
13812
14285
|
throw error;
|
|
@@ -13827,7 +14300,7 @@ function isCode5(error, code) {
|
|
|
13827
14300
|
|
|
13828
14301
|
// src/models/anthropic.ts
|
|
13829
14302
|
import Anthropic from "@anthropic-ai/sdk";
|
|
13830
|
-
import { readFile as
|
|
14303
|
+
import { readFile as readFile15 } from "fs/promises";
|
|
13831
14304
|
|
|
13832
14305
|
// src/utils/json.ts
|
|
13833
14306
|
function safeParseJson(input) {
|
|
@@ -13868,6 +14341,35 @@ function normalizeToolCallInput(input, maxDepth = 3) {
|
|
|
13868
14341
|
throw new Error(`Tool-call input must decode to an object; received ${kind}`);
|
|
13869
14342
|
}
|
|
13870
14343
|
|
|
14344
|
+
// src/utils/abort.ts
|
|
14345
|
+
function waitWithSignal(operation, signal) {
|
|
14346
|
+
if (signal === void 0) return operation;
|
|
14347
|
+
return new Promise((resolve32, reject) => {
|
|
14348
|
+
const abort2 = () => reject(signal.reason);
|
|
14349
|
+
if (signal.aborted) abort2();
|
|
14350
|
+
else signal.addEventListener("abort", abort2, { once: true });
|
|
14351
|
+
operation.then(resolve32, reject).finally(() => signal.removeEventListener("abort", abort2));
|
|
14352
|
+
});
|
|
14353
|
+
}
|
|
14354
|
+
function createScopedAbortSignal(parent) {
|
|
14355
|
+
const controller = new AbortController();
|
|
14356
|
+
let disposed = false;
|
|
14357
|
+
const abort2 = () => {
|
|
14358
|
+
if (!controller.signal.aborted) controller.abort(parent?.reason);
|
|
14359
|
+
};
|
|
14360
|
+
if (parent?.aborted) abort2();
|
|
14361
|
+
else parent?.addEventListener("abort", abort2, { once: true });
|
|
14362
|
+
return {
|
|
14363
|
+
signal: controller.signal,
|
|
14364
|
+
dispose: () => {
|
|
14365
|
+
if (disposed) return;
|
|
14366
|
+
disposed = true;
|
|
14367
|
+
parent?.removeEventListener("abort", abort2);
|
|
14368
|
+
abort2();
|
|
14369
|
+
}
|
|
14370
|
+
};
|
|
14371
|
+
}
|
|
14372
|
+
|
|
13871
14373
|
// src/models/anthropic.ts
|
|
13872
14374
|
var CLAUDE_CLIENT_HEADERS = {
|
|
13873
14375
|
"User-Agent": "claude-cli/2.1.0 (external, cli)",
|
|
@@ -13990,6 +14492,7 @@ var AnthropicModelAdapter = class {
|
|
|
13990
14492
|
void appendUsageLog(line);
|
|
13991
14493
|
}
|
|
13992
14494
|
async *stream(request) {
|
|
14495
|
+
const requestAbort = createScopedAbortSignal(request.signal);
|
|
13993
14496
|
let inputTokens = 0;
|
|
13994
14497
|
let outputTokens = 0;
|
|
13995
14498
|
let hasUsage = false;
|
|
@@ -14064,7 +14567,7 @@ var AnthropicModelAdapter = class {
|
|
|
14064
14567
|
source: {
|
|
14065
14568
|
type: "base64",
|
|
14066
14569
|
media_type: block.mediaType,
|
|
14067
|
-
data: (await
|
|
14570
|
+
data: (await readFile15(block.source.path)).toString("base64")
|
|
14068
14571
|
}
|
|
14069
14572
|
}));
|
|
14070
14573
|
if (message2.cacheBreakpoint && Array.isArray(content) && content.length > 0) {
|
|
@@ -14113,13 +14616,13 @@ var AnthropicModelAdapter = class {
|
|
|
14113
14616
|
};
|
|
14114
14617
|
let stream;
|
|
14115
14618
|
try {
|
|
14116
|
-
stream = await this.client.messages.create(body, { signal:
|
|
14619
|
+
stream = await this.client.messages.create(body, { signal: requestAbort.signal });
|
|
14117
14620
|
} catch (error) {
|
|
14118
14621
|
if (this.thinkingBudget > 0 && !this.#thinkingRejected && isThinkingParamRejected(error)) {
|
|
14119
14622
|
this.#thinkingRejected = true;
|
|
14120
14623
|
delete body.thinking;
|
|
14121
14624
|
stripThinkingBlocks(body.messages);
|
|
14122
|
-
stream = await this.client.messages.create(body, { signal:
|
|
14625
|
+
stream = await this.client.messages.create(body, { signal: requestAbort.signal });
|
|
14123
14626
|
} else {
|
|
14124
14627
|
throw error;
|
|
14125
14628
|
}
|
|
@@ -14231,6 +14734,8 @@ var AnthropicModelAdapter = class {
|
|
|
14231
14734
|
};
|
|
14232
14735
|
}
|
|
14233
14736
|
yield { type: "error", error: normalizeProviderError(error) };
|
|
14737
|
+
} finally {
|
|
14738
|
+
requestAbort.dispose();
|
|
14234
14739
|
}
|
|
14235
14740
|
}
|
|
14236
14741
|
};
|
|
@@ -14284,7 +14789,7 @@ function resolveCacheProfile(input) {
|
|
|
14284
14789
|
|
|
14285
14790
|
// src/models/openai.ts
|
|
14286
14791
|
import OpenAI from "openai";
|
|
14287
|
-
import { readFile as
|
|
14792
|
+
import { readFile as readFile16 } from "fs/promises";
|
|
14288
14793
|
async function toInput(message2) {
|
|
14289
14794
|
if (message2.role === "tool") {
|
|
14290
14795
|
if (!message2.toolCallId) throw new Error("Tool messages require toolCallId");
|
|
@@ -14296,7 +14801,7 @@ async function toInput(message2) {
|
|
|
14296
14801
|
}
|
|
14297
14802
|
const content = typeof message2.content === "string" ? message2.content : await Promise.all(message2.content.map(async (block) => block.type === "text" ? { type: "input_text", text: block.text } : {
|
|
14298
14803
|
type: "input_image",
|
|
14299
|
-
image_url: `data:${block.mediaType};base64,${(await
|
|
14804
|
+
image_url: `data:${block.mediaType};base64,${(await readFile16(block.source.path)).toString("base64")}`,
|
|
14300
14805
|
detail: "auto"
|
|
14301
14806
|
}));
|
|
14302
14807
|
return [
|
|
@@ -14375,6 +14880,7 @@ var OpenAIModelAdapter = class {
|
|
|
14375
14880
|
void appendUsageLog(line);
|
|
14376
14881
|
}
|
|
14377
14882
|
async *stream(request) {
|
|
14883
|
+
const requestAbort = createScopedAbortSignal(request.signal);
|
|
14378
14884
|
const callIds = /* @__PURE__ */ new Map();
|
|
14379
14885
|
const pendingCalls = /* @__PURE__ */ new Map();
|
|
14380
14886
|
const emittedCalls = /* @__PURE__ */ new Set();
|
|
@@ -14391,7 +14897,7 @@ var OpenAIModelAdapter = class {
|
|
|
14391
14897
|
strict: tool.strict ?? true
|
|
14392
14898
|
}))
|
|
14393
14899
|
};
|
|
14394
|
-
const stream = this.client.responses.stream(body, { signal:
|
|
14900
|
+
const stream = this.client.responses.stream(body, { signal: requestAbort.signal });
|
|
14395
14901
|
for await (const event of stream) {
|
|
14396
14902
|
if (event.type === "response.output_item.added" && event.item?.type === "function_call" && event.output_index !== void 0 && event.item.call_id) {
|
|
14397
14903
|
callIds.set(event.output_index, event.item.call_id);
|
|
@@ -14485,6 +14991,8 @@ var OpenAIModelAdapter = class {
|
|
|
14485
14991
|
}
|
|
14486
14992
|
} catch (error) {
|
|
14487
14993
|
yield { type: "error", error: normalizeProviderError(error) };
|
|
14994
|
+
} finally {
|
|
14995
|
+
requestAbort.dispose();
|
|
14488
14996
|
}
|
|
14489
14997
|
}
|
|
14490
14998
|
};
|
|
@@ -15127,7 +15635,7 @@ function isStoredToken(value) {
|
|
|
15127
15635
|
}
|
|
15128
15636
|
|
|
15129
15637
|
// src/permissions/policy.ts
|
|
15130
|
-
import { readFile as
|
|
15638
|
+
import { readFile as readFile17 } from "fs/promises";
|
|
15131
15639
|
import { join as join22, resolve as resolve18 } from "path";
|
|
15132
15640
|
import { z as z20 } from "zod";
|
|
15133
15641
|
var DecisionSchema2 = z20.enum(["allow", "ask", "deny"]);
|
|
@@ -15173,7 +15681,7 @@ async function loadPermissionPolicy(options) {
|
|
|
15173
15681
|
for (const source of sources) {
|
|
15174
15682
|
let raw;
|
|
15175
15683
|
try {
|
|
15176
|
-
raw = await
|
|
15684
|
+
raw = await readFile17(source.path, "utf8");
|
|
15177
15685
|
} catch (error) {
|
|
15178
15686
|
if (isCode6(error, "ENOENT")) continue;
|
|
15179
15687
|
throw error;
|
|
@@ -15939,7 +16447,7 @@ function createSkillResourceTool(registry) {
|
|
|
15939
16447
|
|
|
15940
16448
|
// src/session/store.ts
|
|
15941
16449
|
import { constants as constants7 } from "fs";
|
|
15942
|
-
import { lstat as lstat10, mkdir as mkdir14, open as open8, readFile as
|
|
16450
|
+
import { lstat as lstat10, mkdir as mkdir14, open as open8, readFile as readFile18, readdir as readdir7, realpath as realpath7, rename as rename6, rm as rm6, stat as stat8 } from "fs/promises";
|
|
15943
16451
|
import { basename as basename4, dirname as dirname13, join as join23, relative as relative14, resolve as resolve20, sep as sep14 } from "path";
|
|
15944
16452
|
import { randomUUID as randomUUID13 } from "crypto";
|
|
15945
16453
|
import { z as z22 } from "zod";
|
|
@@ -16137,7 +16645,7 @@ var SessionStore = class {
|
|
|
16137
16645
|
release: async () => {
|
|
16138
16646
|
if (released) return;
|
|
16139
16647
|
try {
|
|
16140
|
-
if (await
|
|
16648
|
+
if (await readFile18(path, "utf8") === token) await rm6(path, { force: true });
|
|
16141
16649
|
} catch (error) {
|
|
16142
16650
|
if (!isCode7(error, "ENOENT")) throw error;
|
|
16143
16651
|
}
|
|
@@ -16331,14 +16839,14 @@ function storedSessionVersion(raw) {
|
|
|
16331
16839
|
}
|
|
16332
16840
|
async function reclaimStaleSessionLock(path) {
|
|
16333
16841
|
try {
|
|
16334
|
-
const token = await
|
|
16335
|
-
const metadata = await
|
|
16842
|
+
const token = await readFile18(path, "utf8");
|
|
16843
|
+
const metadata = await stat8(path);
|
|
16336
16844
|
const owner2 = parseSessionLockOwner(token);
|
|
16337
16845
|
if (owner2 !== void 0 && isProcessAlive(owner2)) return owner2;
|
|
16338
16846
|
if (owner2 === void 0 && Date.now() - metadata.mtimeMs <= INVALID_SESSION_LOCK_STALE_MS) {
|
|
16339
16847
|
throw new Error(`Session writer lock ${path} is incomplete or invalid; retry after the owning process exits`);
|
|
16340
16848
|
}
|
|
16341
|
-
if (await
|
|
16849
|
+
if (await readFile18(path, "utf8") !== token) return void 0;
|
|
16342
16850
|
const stale = `${path}.stale-${process.pid}-${randomUUID13()}`;
|
|
16343
16851
|
try {
|
|
16344
16852
|
await rename6(path, stale);
|
|
@@ -16371,10 +16879,10 @@ function isProcessAlive(pid) {
|
|
|
16371
16879
|
}
|
|
16372
16880
|
}
|
|
16373
16881
|
async function boundedRead(path, maxBytes) {
|
|
16374
|
-
const metadata = await
|
|
16882
|
+
const metadata = await stat8(path);
|
|
16375
16883
|
if (!metadata.isFile()) throw new Error("Session path is not a regular file");
|
|
16376
16884
|
if (metadata.size > maxBytes) throw new Error(`Session exceeds maximum read size of ${maxBytes} bytes`);
|
|
16377
|
-
const value = await
|
|
16885
|
+
const value = await readFile18(path);
|
|
16378
16886
|
if (value.byteLength > maxBytes) throw new Error(`Session exceeds maximum read size of ${maxBytes} bytes`);
|
|
16379
16887
|
return value.toString("utf8");
|
|
16380
16888
|
}
|
|
@@ -16564,11 +17072,70 @@ function sanitize2(input, seen = /* @__PURE__ */ new WeakSet()) {
|
|
|
16564
17072
|
return output;
|
|
16565
17073
|
}
|
|
16566
17074
|
|
|
17075
|
+
// src/utils/rotation-census.ts
|
|
17076
|
+
import { mkdirSync as mkdirSync3, readdirSync, unlinkSync as unlinkSync2, writeFileSync as writeFileSync2 } from "fs";
|
|
17077
|
+
import { join as join24 } from "path";
|
|
17078
|
+
import { writeHeapSnapshot } from "v8";
|
|
17079
|
+
var CENSUS_PREFIX = "rotation-census-";
|
|
17080
|
+
var SNAPSHOT_PREFIX = "heap-";
|
|
17081
|
+
var KEEP_CENSUS_FILES = 5;
|
|
17082
|
+
var KEEP_SNAPSHOT_FILES = 2;
|
|
17083
|
+
function censusHeapBlock(reading) {
|
|
17084
|
+
const usage = process.memoryUsage();
|
|
17085
|
+
const mb = (bytes) => Math.round(bytes / 1048576);
|
|
17086
|
+
return {
|
|
17087
|
+
heapUsedMb: mb(reading.heapUsed),
|
|
17088
|
+
heapLimitMb: mb(reading.heapLimit),
|
|
17089
|
+
rawRatio: Number(reading.ratio.toFixed(3)),
|
|
17090
|
+
...reading.verifiedRatio === void 0 ? {} : { verifiedRatio: Number(reading.verifiedRatio.toFixed(3)) },
|
|
17091
|
+
gcVerified: reading.gcVerified,
|
|
17092
|
+
rssMb: mb(usage.rss),
|
|
17093
|
+
externalMb: mb(usage.external),
|
|
17094
|
+
arrayBuffersMb: mb(usage.arrayBuffers),
|
|
17095
|
+
...reading.memoryLimit === void 0 ? {} : { memoryLimitMb: mb(reading.memoryLimit) },
|
|
17096
|
+
...reading.rssRatio === void 0 ? {} : { rssRatio: Number(reading.rssRatio.toFixed(3)) }
|
|
17097
|
+
};
|
|
17098
|
+
}
|
|
17099
|
+
function writeRotationCensus(workspace, census) {
|
|
17100
|
+
try {
|
|
17101
|
+
const directory = join24(workspace, ".flavor", "tmp");
|
|
17102
|
+
mkdirSync3(directory, { recursive: true });
|
|
17103
|
+
const path = join24(directory, `${CENSUS_PREFIX}${Date.now()}.json`);
|
|
17104
|
+
writeFileSync2(path, `${JSON.stringify(census, null, 2)}
|
|
17105
|
+
`, { encoding: "utf8", mode: 384 });
|
|
17106
|
+
prune(directory, CENSUS_PREFIX, ".json", KEEP_CENSUS_FILES);
|
|
17107
|
+
return path;
|
|
17108
|
+
} catch {
|
|
17109
|
+
return void 0;
|
|
17110
|
+
}
|
|
17111
|
+
}
|
|
17112
|
+
function writeRotationHeapSnapshot(workspace) {
|
|
17113
|
+
try {
|
|
17114
|
+
const directory = join24(workspace, ".flavor", "tmp");
|
|
17115
|
+
mkdirSync3(directory, { recursive: true });
|
|
17116
|
+
const path = join24(directory, `${SNAPSHOT_PREFIX}${Date.now()}.heapsnapshot`);
|
|
17117
|
+
writeHeapSnapshot(path);
|
|
17118
|
+
prune(directory, SNAPSHOT_PREFIX, ".heapsnapshot", KEEP_SNAPSHOT_FILES);
|
|
17119
|
+
return path;
|
|
17120
|
+
} catch {
|
|
17121
|
+
return void 0;
|
|
17122
|
+
}
|
|
17123
|
+
}
|
|
17124
|
+
function prune(directory, prefix, suffix, keep) {
|
|
17125
|
+
const stale = readdirSync(directory).filter((name) => name.startsWith(prefix) && name.endsWith(suffix)).sort().slice(0, -keep);
|
|
17126
|
+
for (const name of stale) {
|
|
17127
|
+
try {
|
|
17128
|
+
unlinkSync2(join24(directory, name));
|
|
17129
|
+
} catch {
|
|
17130
|
+
}
|
|
17131
|
+
}
|
|
17132
|
+
}
|
|
17133
|
+
|
|
16567
17134
|
// src/sleep/organizer.ts
|
|
16568
17135
|
import { constants as constants8 } from "fs";
|
|
16569
17136
|
import { randomUUID as randomUUID14 } from "crypto";
|
|
16570
17137
|
import { lstat as lstat11, mkdir as mkdir15, open as open9, readdir as readdir8, realpath as realpath8, rename as rename7, rm as rm7 } from "fs/promises";
|
|
16571
|
-
import { isAbsolute as isAbsolute10, join as
|
|
17138
|
+
import { isAbsolute as isAbsolute10, join as join25, relative as relative15, resolve as resolve21, sep as sep15 } from "path";
|
|
16572
17139
|
import { z as z23 } from "zod";
|
|
16573
17140
|
var DateKeySchema = z23.string().regex(/^\d{4}-\d{2}-\d{2}$/);
|
|
16574
17141
|
var ReviewSchema = z23.object({
|
|
@@ -16623,7 +17190,7 @@ var ProjectSleepOrganizer = class {
|
|
|
16623
17190
|
#generate;
|
|
16624
17191
|
constructor(options) {
|
|
16625
17192
|
this.#workspace = resolve21(options.workspace);
|
|
16626
|
-
this.#sleepDirectory =
|
|
17193
|
+
this.#sleepDirectory = join25(this.#workspace, ".flavor", "sleep");
|
|
16627
17194
|
this.#sessions = options.sessions ?? new SessionStore({ workspace: this.#workspace });
|
|
16628
17195
|
this.#generate = options.generate;
|
|
16629
17196
|
}
|
|
@@ -16632,7 +17199,7 @@ var ProjectSleepOrganizer = class {
|
|
|
16632
17199
|
signal.throwIfAborted();
|
|
16633
17200
|
await this.#prepareDirectory();
|
|
16634
17201
|
if (await this.#hasReport(date)) return { status: "exists", date };
|
|
16635
|
-
const lockPath =
|
|
17202
|
+
const lockPath = join25(this.#sleepDirectory, `.${date}.lock`);
|
|
16636
17203
|
let lock;
|
|
16637
17204
|
try {
|
|
16638
17205
|
lock = await open9(lockPath, constants8.O_CREAT | constants8.O_EXCL | constants8.O_WRONLY, 384);
|
|
@@ -16667,7 +17234,7 @@ Previous output was invalid. Repair it and return strict JSON only.`;
|
|
|
16667
17234
|
}
|
|
16668
17235
|
if (review === void 0) throw lastError;
|
|
16669
17236
|
const filename2 = `${date}-${filenameSummary(review.title)}.md`;
|
|
16670
|
-
const path =
|
|
17237
|
+
const path = join25(this.#sleepDirectory, filename2);
|
|
16671
17238
|
const markdown = renderSleepReport(date, review, documents);
|
|
16672
17239
|
await this.#atomicWrite(path, markdown);
|
|
16673
17240
|
return { status: "written", date, path, sessionCount: documents.length };
|
|
@@ -16709,7 +17276,7 @@ Previous output was invalid. Repair it and return strict JSON only.`;
|
|
|
16709
17276
|
});
|
|
16710
17277
|
}
|
|
16711
17278
|
async #prepareDirectory() {
|
|
16712
|
-
const flavorDirectory =
|
|
17279
|
+
const flavorDirectory = join25(this.#workspace, ".flavor");
|
|
16713
17280
|
await assertNotSymlink(flavorDirectory);
|
|
16714
17281
|
await mkdir15(flavorDirectory, { recursive: true, mode: 448 });
|
|
16715
17282
|
await assertNotSymlink(this.#sleepDirectory);
|
|
@@ -16723,7 +17290,7 @@ Previous output was invalid. Repair it and return strict JSON only.`;
|
|
|
16723
17290
|
return (await readdir8(this.#sleepDirectory, { withFileTypes: true })).some((entry2) => entry2.isFile() && entry2.name.startsWith(prefix) && entry2.name.endsWith(".md"));
|
|
16724
17291
|
}
|
|
16725
17292
|
async #atomicWrite(path, content) {
|
|
16726
|
-
const temporary =
|
|
17293
|
+
const temporary = join25(this.#sleepDirectory, `.${randomUUID14()}.tmp`);
|
|
16727
17294
|
let handle;
|
|
16728
17295
|
try {
|
|
16729
17296
|
handle = await open9(temporary, constants8.O_CREAT | constants8.O_EXCL | constants8.O_WRONLY, 384);
|
|
@@ -17097,7 +17664,7 @@ function computeSessionStats(documents) {
|
|
|
17097
17664
|
|
|
17098
17665
|
// src/tools/files.ts
|
|
17099
17666
|
import { constants as constants9 } from "fs";
|
|
17100
|
-
import { mkdir as mkdir16, open as open10, readFile as
|
|
17667
|
+
import { mkdir as mkdir16, open as open10, readFile as readFile19, realpath as realpath9, rename as rename8, rm as rm8, stat as stat9 } from "fs/promises";
|
|
17101
17668
|
import { dirname as dirname14, isAbsolute as isAbsolute11, relative as relative16, resolve as resolve22, sep as sep16 } from "path";
|
|
17102
17669
|
import { randomUUID as randomUUID15 } from "crypto";
|
|
17103
17670
|
import { z as z24 } from "zod";
|
|
@@ -17250,7 +17817,7 @@ function createReadTool(workspace, options = {}) {
|
|
|
17250
17817
|
execute: async (input, signal) => {
|
|
17251
17818
|
abortIfNeeded(signal);
|
|
17252
17819
|
const path = await guard.existing(input.path);
|
|
17253
|
-
const info = await
|
|
17820
|
+
const info = await stat9(path);
|
|
17254
17821
|
const maxBytes = input.maxBytes ?? MAX_READ_BYTES;
|
|
17255
17822
|
if (!Number.isSafeInteger(maxBytes) || maxBytes <= 0 || maxBytes > MAX_READ_BYTES) {
|
|
17256
17823
|
throw new Error(`maxBytes must be a positive integer no greater than ${MAX_READ_BYTES}`);
|
|
@@ -17459,7 +18026,7 @@ async function atomicWrite(path, content, signal, expectedVersion) {
|
|
|
17459
18026
|
const temporary = `${path}.${randomUUID15()}.tmp`;
|
|
17460
18027
|
let mode;
|
|
17461
18028
|
try {
|
|
17462
|
-
mode = (await
|
|
18029
|
+
mode = (await stat9(path)).mode & 511;
|
|
17463
18030
|
} catch (error) {
|
|
17464
18031
|
if (!isMissing3(error)) throw error;
|
|
17465
18032
|
}
|
|
@@ -17482,7 +18049,7 @@ async function atomicWrite(path, content, signal, expectedVersion) {
|
|
|
17482
18049
|
}
|
|
17483
18050
|
}
|
|
17484
18051
|
async function readText(path) {
|
|
17485
|
-
const contents = await
|
|
18052
|
+
const contents = await readFile19(path);
|
|
17486
18053
|
if (isBinary(contents)) throw new Error("Cannot edit binary file as text");
|
|
17487
18054
|
return decodeFileText(contents);
|
|
17488
18055
|
}
|
|
@@ -17508,7 +18075,7 @@ async function assertMutationVersion(path, expected, observations) {
|
|
|
17508
18075
|
}
|
|
17509
18076
|
async function fileVersion(path) {
|
|
17510
18077
|
try {
|
|
17511
|
-
return versionOfStat(await
|
|
18078
|
+
return versionOfStat(await stat9(path));
|
|
17512
18079
|
} catch (error) {
|
|
17513
18080
|
if (isMissing3(error)) return ABSENT_VERSION;
|
|
17514
18081
|
throw error;
|
|
@@ -17519,7 +18086,7 @@ function versionOfStat(info) {
|
|
|
17519
18086
|
}
|
|
17520
18087
|
async function readOptionalPresentationText(path) {
|
|
17521
18088
|
try {
|
|
17522
|
-
const contents = await
|
|
18089
|
+
const contents = await readFile19(path);
|
|
17523
18090
|
return isBinary(contents) ? { exists: true } : { exists: true, text: contents.toString("utf8") };
|
|
17524
18091
|
} catch (error) {
|
|
17525
18092
|
return isMissing3(error) ? { exists: false } : { exists: true };
|
|
@@ -17834,19 +18401,6 @@ import { isIP } from "net";
|
|
|
17834
18401
|
import { request as httpRequest } from "http";
|
|
17835
18402
|
import { request as httpsRequest } from "https";
|
|
17836
18403
|
import { z as z25 } from "zod";
|
|
17837
|
-
|
|
17838
|
-
// src/utils/abort.ts
|
|
17839
|
-
function waitWithSignal(operation, signal) {
|
|
17840
|
-
if (signal === void 0) return operation;
|
|
17841
|
-
return new Promise((resolve32, reject) => {
|
|
17842
|
-
const abort2 = () => reject(signal.reason);
|
|
17843
|
-
if (signal.aborted) abort2();
|
|
17844
|
-
else signal.addEventListener("abort", abort2, { once: true });
|
|
17845
|
-
operation.then(resolve32, reject).finally(() => signal.removeEventListener("abort", abort2));
|
|
17846
|
-
});
|
|
17847
|
-
}
|
|
17848
|
-
|
|
17849
|
-
// src/tools/web.ts
|
|
17850
18404
|
var WebFetchInput = z25.object({
|
|
17851
18405
|
url: z25.string().url().max(8192),
|
|
17852
18406
|
timeoutMs: z25.coerce.number().int().positive().max(12e4).optional(),
|
|
@@ -18171,7 +18725,7 @@ function charset(contentType) {
|
|
|
18171
18725
|
// src/tools/lsp.ts
|
|
18172
18726
|
import { spawn as spawn8 } from "child_process";
|
|
18173
18727
|
import { existsSync as existsSync5 } from "fs";
|
|
18174
|
-
import { access as access2, constants as constants10, readFile as
|
|
18728
|
+
import { access as access2, constants as constants10, readFile as readFile20 } from "fs/promises";
|
|
18175
18729
|
import { basename as basename5, dirname as dirname15, extname as extname2, isAbsolute as isAbsolute12, relative as relative17, resolve as resolve23, sep as sep17 } from "path";
|
|
18176
18730
|
import { fileURLToPath as fileURLToPath2, pathToFileURL as pathToFileURL2 } from "url";
|
|
18177
18731
|
import { z as z26 } from "zod";
|
|
@@ -18365,7 +18919,7 @@ var LspConnection = class {
|
|
|
18365
18919
|
const url = new URL(uri);
|
|
18366
18920
|
if (url.protocol !== "file:") return;
|
|
18367
18921
|
const filePath = fileURLToPath2(url);
|
|
18368
|
-
const text = await
|
|
18922
|
+
const text = await readFile20(filePath, "utf8");
|
|
18369
18923
|
const current = this.#documents.get(uri);
|
|
18370
18924
|
if (current === void 0) {
|
|
18371
18925
|
this.#documents.set(uri, { text, version: 1 });
|
|
@@ -18731,19 +19285,19 @@ async function changeSummary(workspace) {
|
|
|
18731
19285
|
};
|
|
18732
19286
|
}
|
|
18733
19287
|
async function stagedDiff(workspace, maxChars = 96e3) {
|
|
18734
|
-
const
|
|
19288
|
+
const stat12 = await git2(workspace, ["diff", "--staged", "--stat"]);
|
|
18735
19289
|
const diff = await git2(workspace, ["diff", "--staged", "--no-ext-diff", "--no-color"]);
|
|
18736
19290
|
if (!diff.ok) throw gitFailure(diff, "diff --staged");
|
|
18737
19291
|
const text = diff.stdout;
|
|
18738
19292
|
return {
|
|
18739
19293
|
diff: text.length > maxChars ? `${text.slice(0, maxChars)}
|
|
18740
19294
|
\u2026[diff truncated]` : text,
|
|
18741
|
-
stat:
|
|
19295
|
+
stat: stat12.stdout.trim(),
|
|
18742
19296
|
truncated: text.length > maxChars
|
|
18743
19297
|
};
|
|
18744
19298
|
}
|
|
18745
19299
|
async function uncommittedDiff(workspace, maxChars = 96e3) {
|
|
18746
|
-
const
|
|
19300
|
+
const stat12 = await git2(workspace, ["diff", "HEAD", "--stat"]);
|
|
18747
19301
|
const diff = await git2(workspace, ["diff", "HEAD", "--no-ext-diff", "--no-color"]);
|
|
18748
19302
|
if (!diff.ok && !diff.stderr.includes("unknown revision")) throw gitFailure(diff, "diff HEAD");
|
|
18749
19303
|
const summary2 = await changeSummary(workspace);
|
|
@@ -18751,7 +19305,7 @@ async function uncommittedDiff(workspace, maxChars = 96e3) {
|
|
|
18751
19305
|
return {
|
|
18752
19306
|
diff: text.length > maxChars ? `${text.slice(0, maxChars)}
|
|
18753
19307
|
\u2026[diff truncated]` : text,
|
|
18754
|
-
stat:
|
|
19308
|
+
stat: stat12.stdout.trim(),
|
|
18755
19309
|
untracked: summary2.untrackedFiles
|
|
18756
19310
|
};
|
|
18757
19311
|
}
|
|
@@ -19048,17 +19602,17 @@ ${explanation}` : "";
|
|
|
19048
19602
|
}
|
|
19049
19603
|
|
|
19050
19604
|
// src/desktop/astgraph-service.ts
|
|
19051
|
-
import { stat as
|
|
19605
|
+
import { stat as stat10 } from "fs/promises";
|
|
19052
19606
|
import { createRequire as nodeCreateRequire } from "module";
|
|
19053
|
-
import { join as
|
|
19607
|
+
import { join as join26, resolve as resolve24 } from "path";
|
|
19054
19608
|
var FIELDS = "id, kind, name, qualified_name, file_path, language, start_line, end_line, signature";
|
|
19055
19609
|
var DesktopAstGraphService = class {
|
|
19056
19610
|
#path;
|
|
19057
19611
|
constructor(workspace) {
|
|
19058
|
-
this.#path =
|
|
19612
|
+
this.#path = join26(resolve24(workspace), ".flavor", "astgraph", "index.db");
|
|
19059
19613
|
}
|
|
19060
19614
|
async status() {
|
|
19061
|
-
if ((await
|
|
19615
|
+
if ((await stat10(this.#path).catch(() => void 0))?.isFile() !== true) {
|
|
19062
19616
|
return { available: false, path: this.#path, files: 0, nodes: 0, edges: 0 };
|
|
19063
19617
|
}
|
|
19064
19618
|
return this.#read((db) => {
|
|
@@ -19338,8 +19892,8 @@ function createTodoWriteTool() {
|
|
|
19338
19892
|
|
|
19339
19893
|
// src/tools/managed.ts
|
|
19340
19894
|
import { randomUUID as randomUUID16 } from "crypto";
|
|
19341
|
-
import { link as link2, lstat as lstat12, mkdir as mkdir17, readFile as
|
|
19342
|
-
import { dirname as dirname16, join as
|
|
19895
|
+
import { link as link2, lstat as lstat12, mkdir as mkdir17, readFile as readFile21, readdir as readdir9, unlink as unlink5, writeFile as writeFile10 } from "fs/promises";
|
|
19896
|
+
import { dirname as dirname16, join as join27, resolve as resolve26 } from "path";
|
|
19343
19897
|
import { z as z31 } from "zod";
|
|
19344
19898
|
var MANAGED_TOOL_VERSION = 1;
|
|
19345
19899
|
var MAX_RECORD_BYTES = 256 * 1024;
|
|
@@ -19382,8 +19936,8 @@ var ManagedToolStore = class {
|
|
|
19382
19936
|
#diagnostics = [];
|
|
19383
19937
|
constructor(options) {
|
|
19384
19938
|
this.#workspace = resolve26(options.workspace);
|
|
19385
|
-
this.#projectRoot =
|
|
19386
|
-
this.#globalRoot =
|
|
19939
|
+
this.#projectRoot = join27(this.#workspace, ".flavor", "tools");
|
|
19940
|
+
this.#globalRoot = join27(resolve26(options.home), ".flavor-code", "tools");
|
|
19387
19941
|
this.#now = options.now ?? (() => (/* @__PURE__ */ new Date()).toISOString());
|
|
19388
19942
|
}
|
|
19389
19943
|
get diagnostics() {
|
|
@@ -19430,7 +19984,7 @@ var ManagedToolStore = class {
|
|
|
19430
19984
|
version: MANAGED_TOOL_VERSION,
|
|
19431
19985
|
createdAt: this.#now()
|
|
19432
19986
|
});
|
|
19433
|
-
const path =
|
|
19987
|
+
const path = join27(this.directory(input.scope), filename(input.name));
|
|
19434
19988
|
const definition = compileDefinition(record, input.scope, this.#workspace);
|
|
19435
19989
|
await writeExclusive(path, `${JSON.stringify(record, void 0, 2)}
|
|
19436
19990
|
`);
|
|
@@ -19455,7 +20009,7 @@ var ManagedToolStore = class {
|
|
|
19455
20009
|
if (!parsed.success) return [];
|
|
19456
20010
|
const matches = this.#matching(parsed.data.name).filter((entry2) => parsed.data.scope === void 0 || entry2.scope === parsed.data.scope);
|
|
19457
20011
|
if (matches.length > 0) return matches.map((entry2) => entry2.path);
|
|
19458
|
-
return [
|
|
20012
|
+
return [join27(this.directory(parsed.data.scope ?? "project"), filename(parsed.data.name))];
|
|
19459
20013
|
}
|
|
19460
20014
|
async #loadDirectory(scope2) {
|
|
19461
20015
|
const root = this.directory(scope2);
|
|
@@ -19468,12 +20022,12 @@ var ManagedToolStore = class {
|
|
|
19468
20022
|
return;
|
|
19469
20023
|
}
|
|
19470
20024
|
for (const name of names) {
|
|
19471
|
-
const path =
|
|
20025
|
+
const path = join27(root, name);
|
|
19472
20026
|
try {
|
|
19473
20027
|
const info = await lstat12(path);
|
|
19474
20028
|
if (!info.isFile() || info.isSymbolicLink()) throw new Error("Managed tool record must be a regular file");
|
|
19475
20029
|
if (info.size > MAX_RECORD_BYTES) throw new Error(`Managed tool record exceeds ${MAX_RECORD_BYTES} bytes`);
|
|
19476
|
-
const record = ManagedToolRecordSchema.parse(JSON.parse(await
|
|
20030
|
+
const record = ManagedToolRecordSchema.parse(JSON.parse(await readFile21(path, "utf8")));
|
|
19477
20031
|
if (record.scope !== scope2) throw new Error(`Managed tool record scope must match its ${scope2} directory`);
|
|
19478
20032
|
const key = scopedKey(scope2, record.name);
|
|
19479
20033
|
if (this.#entries.has(key)) throw new Error(`Duplicate managed tool name in ${scope2} scope: ${record.name}`);
|
|
@@ -20386,8 +20940,8 @@ ${total}`;
|
|
|
20386
20940
|
|
|
20387
20941
|
// src/memory/store.ts
|
|
20388
20942
|
import { createHash as createHash16 } from "crypto";
|
|
20389
|
-
import { readFile as
|
|
20390
|
-
import { resolve as resolve27, join as
|
|
20943
|
+
import { readFile as readFile22, rm as rm9 } from "fs/promises";
|
|
20944
|
+
import { resolve as resolve27, join as join28, relative as relative19, sep as sep19 } from "path";
|
|
20391
20945
|
|
|
20392
20946
|
// src/memory/similarity.ts
|
|
20393
20947
|
function normalizeForSimilarity(value) {
|
|
@@ -20490,9 +21044,9 @@ var MemoryStore = class {
|
|
|
20490
21044
|
if (!Number.isSafeInteger(options.maxEntries) || options.maxEntries < 1) throw new Error("maxEntries must be an integer of at least 1");
|
|
20491
21045
|
if (!Number.isSafeInteger(options.maxEntryChars) || options.maxEntryChars < 1) throw new Error("maxEntryChars must be an integer of at least 1");
|
|
20492
21046
|
this.workspace = resolve27(options.workspace);
|
|
20493
|
-
this.#memoryRoot =
|
|
20494
|
-
this.path =
|
|
20495
|
-
this.#behaviorPath =
|
|
21047
|
+
this.#memoryRoot = join28(this.workspace, ".flavor", "memory");
|
|
21048
|
+
this.path = join28(this.#memoryRoot, "MEMORY.md");
|
|
21049
|
+
this.#behaviorPath = join28(this.#memoryRoot, "behavior.json");
|
|
20496
21050
|
this.#maxEntries = options.maxEntries;
|
|
20497
21051
|
this.#maxEntryChars = options.maxEntryChars;
|
|
20498
21052
|
}
|
|
@@ -20710,7 +21264,7 @@ var MemoryStore = class {
|
|
|
20710
21264
|
});
|
|
20711
21265
|
}
|
|
20712
21266
|
async #readTaskItem(reference) {
|
|
20713
|
-
const document = decodeTask(await
|
|
21267
|
+
const document = decodeTask(await readFile22(this.#resolveContentPath(reference.contentPath), "utf8"));
|
|
20714
21268
|
return document.items.find((item) => item.id === reference.id)?.content ?? reference.summary;
|
|
20715
21269
|
}
|
|
20716
21270
|
#resolveContentPath(contentPath) {
|
|
@@ -21186,8 +21740,8 @@ function createExecutionEnvironment(workspace, config) {
|
|
|
21186
21740
|
}
|
|
21187
21741
|
|
|
21188
21742
|
// src/ide/client.ts
|
|
21189
|
-
import { readFile as
|
|
21190
|
-
import { basename as basename6, join as
|
|
21743
|
+
import { readFile as readFile23, readdir as readdir10, stat as stat11 } from "fs/promises";
|
|
21744
|
+
import { basename as basename6, join as join29, relative as relative20, resolve as resolve28, sep as sep20 } from "path";
|
|
21191
21745
|
var FlavorIdeClient = class {
|
|
21192
21746
|
#workspace;
|
|
21193
21747
|
#home;
|
|
@@ -21283,7 +21837,7 @@ var FlavorIdeClient = class {
|
|
|
21283
21837
|
};
|
|
21284
21838
|
if (await this.#healthy(configured)) return configured;
|
|
21285
21839
|
}
|
|
21286
|
-
const directory =
|
|
21840
|
+
const directory = join29(this.#home, ".flavor-code", "ide");
|
|
21287
21841
|
let entries;
|
|
21288
21842
|
try {
|
|
21289
21843
|
entries = (await readdir10(directory)).filter((name) => name.endsWith(".lock"));
|
|
@@ -21291,9 +21845,9 @@ var FlavorIdeClient = class {
|
|
|
21291
21845
|
return void 0;
|
|
21292
21846
|
}
|
|
21293
21847
|
const candidates = (await Promise.all(entries.map(async (name) => {
|
|
21294
|
-
const path =
|
|
21848
|
+
const path = join29(directory, name);
|
|
21295
21849
|
try {
|
|
21296
|
-
const [contents, info] = await Promise.all([
|
|
21850
|
+
const [contents, info] = await Promise.all([readFile23(path, "utf8"), stat11(path)]);
|
|
21297
21851
|
const lock = parseLockfile(contents);
|
|
21298
21852
|
return lock === void 0 ? void 0 : { lock, modified: info.mtimeMs };
|
|
21299
21853
|
} catch {
|
|
@@ -21412,6 +21966,7 @@ function relativeDisplay(workspace, filePath) {
|
|
|
21412
21966
|
|
|
21413
21967
|
// src/jobs/registry.ts
|
|
21414
21968
|
import { randomUUID as randomUUID17 } from "crypto";
|
|
21969
|
+
var OUTPUT_SLICE_SLACK_CHARS = 64 * 1024;
|
|
21415
21970
|
var JobRegistry = class {
|
|
21416
21971
|
#jobs = /* @__PURE__ */ new Map();
|
|
21417
21972
|
#maxOutputChars;
|
|
@@ -21440,7 +21995,8 @@ var JobRegistry = class {
|
|
|
21440
21995
|
updatedAt: now,
|
|
21441
21996
|
outputChars: 0,
|
|
21442
21997
|
truncated: false,
|
|
21443
|
-
|
|
21998
|
+
chunks: [],
|
|
21999
|
+
windowChars: 0,
|
|
21444
22000
|
outputStart: 0,
|
|
21445
22001
|
settle,
|
|
21446
22002
|
done
|
|
@@ -21451,11 +22007,16 @@ var JobRegistry = class {
|
|
|
21451
22007
|
id,
|
|
21452
22008
|
append: (text) => {
|
|
21453
22009
|
if (record.state !== "running" || text === "") return;
|
|
21454
|
-
record.
|
|
22010
|
+
record.chunks.push(text);
|
|
22011
|
+
record.windowChars += text.length;
|
|
21455
22012
|
record.outputChars += text.length;
|
|
21456
|
-
if (record.
|
|
21457
|
-
|
|
21458
|
-
|
|
22013
|
+
if (record.outputChars > this.#maxOutputChars) record.truncated = true;
|
|
22014
|
+
if (record.windowChars > this.#maxOutputChars + OUTPUT_SLICE_SLACK_CHARS) {
|
|
22015
|
+
const joined = record.chunks.join("");
|
|
22016
|
+
const kept = joined.slice(joined.length - this.#maxOutputChars);
|
|
22017
|
+
const removed = joined.length - kept.length;
|
|
22018
|
+
record.chunks = [kept];
|
|
22019
|
+
record.windowChars = kept.length;
|
|
21459
22020
|
record.outputStart += removed;
|
|
21460
22021
|
record.truncated = true;
|
|
21461
22022
|
}
|
|
@@ -21476,8 +22037,20 @@ var JobRegistry = class {
|
|
|
21476
22037
|
}
|
|
21477
22038
|
read(id, owner2, cursor = 0) {
|
|
21478
22039
|
const job = this.#owned(id, owner2);
|
|
21479
|
-
const
|
|
21480
|
-
|
|
22040
|
+
const window = job.chunks.join("");
|
|
22041
|
+
const dropped = Math.max(0, window.length - this.#maxOutputChars);
|
|
22042
|
+
const effective = Math.max(cursor, job.outputStart + dropped);
|
|
22043
|
+
return { ...snapshot2(job), output: window.slice(effective - job.outputStart), cursor: job.outputStart + window.length };
|
|
22044
|
+
}
|
|
22045
|
+
/** Retained sizes for the rotation census. */
|
|
22046
|
+
census() {
|
|
22047
|
+
let windowChars = 0;
|
|
22048
|
+
let running = 0;
|
|
22049
|
+
for (const job of this.#jobs.values()) {
|
|
22050
|
+
windowChars += job.windowChars;
|
|
22051
|
+
if (job.state === "running") running += 1;
|
|
22052
|
+
}
|
|
22053
|
+
return { jobs: this.#jobs.size, running, windowChars };
|
|
21481
22054
|
}
|
|
21482
22055
|
async wait(id, owner2, signal) {
|
|
21483
22056
|
signal?.throwIfAborted();
|
|
@@ -22705,8 +23278,8 @@ async function createProductionRuntime(options) {
|
|
|
22705
23278
|
const registeredProviders = registration.registered;
|
|
22706
23279
|
let effectiveLlm = registration.effectiveLlm;
|
|
22707
23280
|
const pluginHost = new PluginHost({
|
|
22708
|
-
globalPluginDirs: [
|
|
22709
|
-
projectPluginDirs: [
|
|
23281
|
+
globalPluginDirs: [join30(home, ".flavor-code", "plugins")],
|
|
23282
|
+
projectPluginDirs: [join30(workspace, ".flavor", "plugins")],
|
|
22710
23283
|
config,
|
|
22711
23284
|
// Existing project plugins (including astgraph and superharness) use Node.js
|
|
22712
23285
|
// built-ins. The Worker/vm sandbox deliberately blocks those imports, so it
|
|
@@ -22815,7 +23388,7 @@ async function createProductionRuntime(options) {
|
|
|
22815
23388
|
const address = palSocketAddress({
|
|
22816
23389
|
platform: process.platform,
|
|
22817
23390
|
userScope: home,
|
|
22818
|
-
runtimeDir:
|
|
23391
|
+
runtimeDir: join30(tmpdir4(), `flavor-code-pals-${createHash18("sha256").update(home).digest("hex").slice(0, 16)}`)
|
|
22819
23392
|
});
|
|
22820
23393
|
const baseCollaborationClient = collaboration.client ?? collaboration.createClient?.({
|
|
22821
23394
|
instanceId: collaboration.instanceId,
|
|
@@ -22926,8 +23499,8 @@ async function createProductionRuntime(options) {
|
|
|
22926
23499
|
diagnostics.push(`MCP servers could not start: ${message(error)}`);
|
|
22927
23500
|
});
|
|
22928
23501
|
const skills = new SkillRegistry({
|
|
22929
|
-
globalRoots: [
|
|
22930
|
-
projectRoots: [
|
|
23502
|
+
globalRoots: [join30(home, ".flavor-code", "skills")],
|
|
23503
|
+
projectRoots: [join30(workspace, ".flavor", "skills"), ...pluginSkillRoots],
|
|
22931
23504
|
authorizeResource: async () => true,
|
|
22932
23505
|
disabledNames: config.skills.disabled
|
|
22933
23506
|
});
|
|
@@ -22935,7 +23508,7 @@ async function createProductionRuntime(options) {
|
|
|
22935
23508
|
skillsReady.catch(() => void 0);
|
|
22936
23509
|
tools.push(createSkillTool(skills), createSkillResourceTool(skills));
|
|
22937
23510
|
if (options.extraTools !== void 0) tools.push(...options.extraTools);
|
|
22938
|
-
const flavor = await optionalText2(
|
|
23511
|
+
const flavor = await optionalText2(join30(workspace, "FLAVOR.md"));
|
|
22939
23512
|
const instructionBaseline = await workspaceInstructions.baseline();
|
|
22940
23513
|
let memoryContext;
|
|
22941
23514
|
if (memoryStore !== void 0) {
|
|
@@ -23520,7 +24093,7 @@ async function createProductionRuntime(options) {
|
|
|
23520
24093
|
evolveService.toolDefinition(),
|
|
23521
24094
|
...mcpTools
|
|
23522
24095
|
];
|
|
23523
|
-
const loopFlavor = await optionalText2(
|
|
24096
|
+
const loopFlavor = await optionalText2(join30(input.workspace, "FLAVOR.md"));
|
|
23524
24097
|
const loopInstructionBaseline = await loopInstructions.baseline();
|
|
23525
24098
|
const loopEnvironment = createPromptEnvironment({
|
|
23526
24099
|
now: /* @__PURE__ */ new Date(),
|
|
@@ -23628,12 +24201,89 @@ async function createProductionRuntime(options) {
|
|
|
23628
24201
|
await loopExecutionEnvironment?.dispose();
|
|
23629
24202
|
}
|
|
23630
24203
|
};
|
|
24204
|
+
let activeLongTask;
|
|
24205
|
+
const readMemoryRestartMarker = () => {
|
|
24206
|
+
try {
|
|
24207
|
+
return parseMemoryRestartMarker(readFileSync3(memoryRestartMarkerPath(workspace), "utf8"));
|
|
24208
|
+
} catch {
|
|
24209
|
+
return void 0;
|
|
24210
|
+
}
|
|
24211
|
+
};
|
|
24212
|
+
const currentPressureReading = () => verifiedHeapPressure(MEMORY_PRESSURE_HEAP_RATIO) ?? { ...readHeap(), gcVerified: false };
|
|
24213
|
+
const collectRotationHolders = () => {
|
|
24214
|
+
const holders = {};
|
|
24215
|
+
try {
|
|
24216
|
+
const context = harness.main.context.census();
|
|
24217
|
+
holders.context = { entries: context.messages, chars: context.messageChars };
|
|
24218
|
+
holders.contextVisibility = { entries: context.visibilityEntries, chars: context.visibilityChars };
|
|
24219
|
+
holders.contextTransient = { entries: context.transientEntries, chars: context.transientChars };
|
|
24220
|
+
} catch {
|
|
24221
|
+
}
|
|
24222
|
+
try {
|
|
24223
|
+
holders.journal = { entries: harnessJournal.recordCount };
|
|
24224
|
+
} catch {
|
|
24225
|
+
}
|
|
24226
|
+
try {
|
|
24227
|
+
const jobCensus = jobs.census();
|
|
24228
|
+
holders.jobs = { entries: jobCensus.jobs, chars: jobCensus.windowChars };
|
|
24229
|
+
} catch {
|
|
24230
|
+
}
|
|
24231
|
+
try {
|
|
24232
|
+
holders.sessionTree = { entries: sessionHistory.nodeCount };
|
|
24233
|
+
} catch {
|
|
24234
|
+
}
|
|
24235
|
+
try {
|
|
24236
|
+
const transcript = transcriptCensus(timelineState);
|
|
24237
|
+
holders.transcript = { entries: transcript.blocks, chars: transcript.chars };
|
|
24238
|
+
holders.transcriptTurns = { entries: transcript.turns };
|
|
24239
|
+
} catch {
|
|
24240
|
+
}
|
|
24241
|
+
return holders;
|
|
24242
|
+
};
|
|
24243
|
+
const rotateForHeap = (continuation, reading) => {
|
|
24244
|
+
requestMemoryRestart(workspace, sessionId, emitOutput, {
|
|
24245
|
+
...continuation === void 0 ? {} : { continuation },
|
|
24246
|
+
...reading === void 0 ? {} : { reading },
|
|
24247
|
+
holders: collectRotationHolders()
|
|
24248
|
+
}, options.onMemoryRestartRequested);
|
|
24249
|
+
};
|
|
24250
|
+
const maybeRotateAtBoundary = (continuation) => {
|
|
24251
|
+
activeLongTask = continuation;
|
|
24252
|
+
const pressure = verifiedHeapPressure(MEMORY_PRESSURE_HEAP_RATIO);
|
|
24253
|
+
if (pressure !== void 0) {
|
|
24254
|
+
rotateForHeap(continuation, pressure);
|
|
24255
|
+
return;
|
|
24256
|
+
}
|
|
24257
|
+
const reading = heapRotationNeeded();
|
|
24258
|
+
if (reading === void 0) return;
|
|
24259
|
+
if (rotationCooldownActive(readMemoryRestartMarker(), sessionId, /* @__PURE__ */ new Date())) return;
|
|
24260
|
+
rotateForHeap(continuation, reading);
|
|
24261
|
+
};
|
|
24262
|
+
const maybeRotateAtTurnBoundary = () => {
|
|
24263
|
+
const reading = heapRotationNeeded();
|
|
24264
|
+
if (reading === void 0) return;
|
|
24265
|
+
if (rotationCooldownActive(readMemoryRestartMarker(), sessionId, /* @__PURE__ */ new Date())) return;
|
|
24266
|
+
rotateForHeap(void 0, reading);
|
|
24267
|
+
};
|
|
24268
|
+
const consumePendingContinuation = () => {
|
|
24269
|
+
const marker = readMemoryRestartMarker();
|
|
24270
|
+
const continuation = pendingContinuation(marker, sessionId, /* @__PURE__ */ new Date());
|
|
24271
|
+
if (continuation === void 0 || marker === void 0) return void 0;
|
|
24272
|
+
try {
|
|
24273
|
+
const markerPath = memoryRestartMarkerPath(workspace);
|
|
24274
|
+
writeMarkerAtomically(markerPath, consumedMemoryRestartMarker(marker));
|
|
24275
|
+
} catch {
|
|
24276
|
+
}
|
|
24277
|
+
return continuation;
|
|
24278
|
+
};
|
|
23631
24279
|
const loopStore = new LoopStore({ workspace });
|
|
23632
24280
|
const loopOrchestrator = new LoopOrchestrator({
|
|
23633
24281
|
workspace,
|
|
23634
24282
|
config: config.loop,
|
|
23635
24283
|
persistence: loopStore,
|
|
23636
24284
|
hallucinationGuard,
|
|
24285
|
+
onCycleBoundary: (loopId) => maybeRotateAtBoundary({ kind: "loop", id: loopId }),
|
|
24286
|
+
onMemoryCheckpoint: (loopId) => maybeRotateAtBoundary({ kind: "loop", id: loopId }),
|
|
23637
24287
|
prepareWorkspace: (input) => prepareLoopWorkspace(input),
|
|
23638
24288
|
inferVerification: inferVerificationPlan,
|
|
23639
24289
|
runWorker: ({ workspace: executionWorkspace, prompt, signal }) => runLoopWorker({ workspace: executionWorkspace, prompt, signal }),
|
|
@@ -23645,21 +24295,9 @@ async function createProductionRuntime(options) {
|
|
|
23645
24295
|
await verifierEnvironment?.dispose();
|
|
23646
24296
|
}
|
|
23647
24297
|
},
|
|
23648
|
-
|
|
23649
|
-
|
|
23650
|
-
|
|
23651
|
-
const next = dimensions.map((dimension) => dimension === "cycles" ? `${state.budget.cycleCheckpoint + state.config.cycleStep} cycles` : `${state.budget.tokenCheckpoint + state.config.tokenStep} tokens`).join(" and ");
|
|
23652
|
-
const latestVerification = state.cycles.at(-1)?.verification.summary ?? "No host verification evidence yet.";
|
|
23653
|
-
const answers = await questions.ask([{
|
|
23654
|
-
header: "Loop budget",
|
|
23655
|
-
question: `Loop ${state.loopId} reached ${reached}. Latest verification: ${latestVerification} Continue until the next checkpoint (${next})?`,
|
|
23656
|
-
options: [
|
|
23657
|
-
{ label: "Continue", description: "Extend only the reached budget tranche and keep looping." },
|
|
23658
|
-
{ label: "Stop", description: "End this loop as budget exhausted." }
|
|
23659
|
-
]
|
|
23660
|
-
}], signal);
|
|
23661
|
-
return answers[0] === "Continue" ? "approved" : "rejected";
|
|
23662
|
-
},
|
|
24298
|
+
// Cycle/token values are telemetry checkpoint cadence, not an unattended
|
|
24299
|
+
// execution cap. The user can still cancel explicitly at any time.
|
|
24300
|
+
confirmBudget: async () => "approved",
|
|
23663
24301
|
fingerprint: workspaceFingerprint,
|
|
23664
24302
|
idFactory: () => `loop-${(/* @__PURE__ */ new Date()).toISOString().replace(/[^0-9]/g, "").slice(0, 17)}-${randomUUID20().slice(0, 8)}`
|
|
23665
24303
|
});
|
|
@@ -23670,9 +24308,12 @@ async function createProductionRuntime(options) {
|
|
|
23670
24308
|
plannerModelId: mainModel,
|
|
23671
24309
|
classifierModelId: mainModel,
|
|
23672
24310
|
skepticCount: 3,
|
|
23673
|
-
|
|
23674
|
-
|
|
24311
|
+
// Production goals run until verified, explicitly cancelled, or blocked
|
|
24312
|
+
// by a non-recoverable external condition. Numeric config values remain
|
|
24313
|
+
// accepted for compatibility but no longer terminate autonomous work.
|
|
23675
24314
|
persistence: goalStore,
|
|
24315
|
+
onRoundBoundary: (goalId) => maybeRotateAtBoundary({ kind: "goal", id: goalId }),
|
|
24316
|
+
onMemoryCheckpoint: (goalId) => maybeRotateAtBoundary({ kind: "goal", id: goalId }),
|
|
23676
24317
|
verifyHost: async (signal) => runVerificationPlan(
|
|
23677
24318
|
await inferVerificationPlan(workspace),
|
|
23678
24319
|
workspace,
|
|
@@ -23767,30 +24408,50 @@ async function createProductionRuntime(options) {
|
|
|
23767
24408
|
contextEpoch: harness.main.context.snapshot().epoch?.id ?? "legacy"
|
|
23768
24409
|
});
|
|
23769
24410
|
const turnId = harnessJournal.startTurn(turnConfig, { prompt, initialUserMessage: runOptions?.initialUserMessage });
|
|
23770
|
-
return
|
|
23771
|
-
|
|
23772
|
-
|
|
23773
|
-
|
|
23774
|
-
|
|
23775
|
-
|
|
23776
|
-
|
|
23777
|
-
|
|
23778
|
-
|
|
23779
|
-
|
|
23780
|
-
|
|
23781
|
-
|
|
23782
|
-
|
|
23783
|
-
|
|
23784
|
-
|
|
23785
|
-
|
|
23786
|
-
|
|
24411
|
+
return monitorTurnMemory(
|
|
24412
|
+
durableTurn(persistAfter(runMain(
|
|
24413
|
+
harness,
|
|
24414
|
+
skills,
|
|
24415
|
+
prompt,
|
|
24416
|
+
signal,
|
|
24417
|
+
selectedModels.mainError,
|
|
24418
|
+
memoryStore === void 0 || !memoryHasRoutableEntries && userMemoryContext === void 0 ? void 0 : {
|
|
24419
|
+
store: memoryStore,
|
|
24420
|
+
taskId: memoryLifecycle.taskId ?? sessionId,
|
|
24421
|
+
topK: config.memory.retrievalTopK,
|
|
24422
|
+
maxChars: config.memory.maxPromptChars
|
|
24423
|
+
},
|
|
24424
|
+
runOptions?.getSteeringMessages,
|
|
24425
|
+
runOptions?.initialUserMessage,
|
|
24426
|
+
runOptions?.additionalContext,
|
|
24427
|
+
ide
|
|
24428
|
+
), persist), harnessJournal, turnId, turnConfig),
|
|
24429
|
+
() => rotateForHeap(void 0, currentPressureReading()),
|
|
24430
|
+
() => maybeRotateAtTurnBoundary()
|
|
24431
|
+
);
|
|
23787
24432
|
},
|
|
23788
24433
|
runSkill: (skill, prompt, signal) => persistAfter(
|
|
23789
24434
|
runExplicitSkill(harness, skills, skill, prompt, signal, selectedModels.mainError),
|
|
23790
24435
|
persist
|
|
23791
24436
|
),
|
|
23792
|
-
runLoop: (goal, signal) =>
|
|
23793
|
-
|
|
24437
|
+
runLoop: (goal, signal) => monitorLongTaskRestart(
|
|
24438
|
+
runLoopSession(loopOrchestrator, hooks, goal, signal),
|
|
24439
|
+
() => rotateForHeap(activeLongTask, currentPressureReading())
|
|
24440
|
+
),
|
|
24441
|
+
runGoal: (goal, signal) => monitorLongTaskRestart(
|
|
24442
|
+
persistEach(runGoalSession(goalOrchestrator, goal, signal), persist),
|
|
24443
|
+
() => rotateForHeap(activeLongTask, currentPressureReading())
|
|
24444
|
+
),
|
|
24445
|
+
resumeLoop: (loopId, signal) => monitorLongTaskRestart(
|
|
24446
|
+
runResumeLoopSession(loopOrchestrator, hooks, loopId, signal),
|
|
24447
|
+
() => rotateForHeap(activeLongTask ?? { kind: "loop", id: loopId }, currentPressureReading())
|
|
24448
|
+
),
|
|
24449
|
+
resumeGoal: (goalId, signal) => monitorLongTaskRestart(
|
|
24450
|
+
persistEach(runResumeGoalSession(goalOrchestrator, goalId, signal), persist),
|
|
24451
|
+
() => rotateForHeap(activeLongTask ?? { kind: "goal", id: goalId }, currentPressureReading())
|
|
24452
|
+
),
|
|
24453
|
+
continuation: () => consumePendingContinuation(),
|
|
24454
|
+
rotationPending: () => memoryRotationActive(),
|
|
23794
24455
|
mcp: async (command, signal) => {
|
|
23795
24456
|
signal.throwIfAborted();
|
|
23796
24457
|
const manager = await mcpReady;
|
|
@@ -23884,7 +24545,7 @@ async function createProductionRuntime(options) {
|
|
|
23884
24545
|
const root = resolve31(workspace);
|
|
23885
24546
|
const abs = resolve31(root, path);
|
|
23886
24547
|
if (abs !== root && !abs.startsWith(root + sep22)) throw new Error(`path outside workspace: ${path}`);
|
|
23887
|
-
return
|
|
24548
|
+
return readFile24(abs, "utf8");
|
|
23888
24549
|
},
|
|
23889
24550
|
history: async (path, limit) => fileHistory(workspace, path, limit),
|
|
23890
24551
|
questions,
|
|
@@ -23896,7 +24557,7 @@ async function createProductionRuntime(options) {
|
|
|
23896
24557
|
}, query, focus, signal),
|
|
23897
24558
|
audit: async (toolFilter) => {
|
|
23898
24559
|
try {
|
|
23899
|
-
const raw = await
|
|
24560
|
+
const raw = await readFile24(auditLogger.path, "utf8");
|
|
23900
24561
|
const lines = raw.trim().split("\n").filter((line) => line.length > 0);
|
|
23901
24562
|
const entries = lines.map((line) => {
|
|
23902
24563
|
try {
|
|
@@ -23936,7 +24597,7 @@ ${summary2}`;
|
|
|
23936
24597
|
doctor: async () => formatDoctorReport(await runDoctor({ workspace, home, environment })),
|
|
23937
24598
|
usage: async () => {
|
|
23938
24599
|
try {
|
|
23939
|
-
const raw = await
|
|
24600
|
+
const raw = await readFile24(usageLogPath(), "utf8");
|
|
23940
24601
|
return formatUsageSummary(summarizeUsage(parseUsageEntries(raw)));
|
|
23941
24602
|
} catch (error) {
|
|
23942
24603
|
if (typeof error === "object" && error !== null && "code" in error && error.code === "ENOENT") {
|
|
@@ -24089,7 +24750,7 @@ ${renderMemoryDocument(entries)}`;
|
|
|
24089
24750
|
return `Provider "${providerName}" is missing authorizationUrl, tokenUrl, or clientId.`;
|
|
24090
24751
|
}
|
|
24091
24752
|
try {
|
|
24092
|
-
const tokenStore = createFileTokenStore(
|
|
24753
|
+
const tokenStore = createFileTokenStore(join30(home, ".flavor-code", "auth.json"));
|
|
24093
24754
|
const credentialId = oauthCredentialId(oauthConfig.tokenUrl, oauthConfig.clientId);
|
|
24094
24755
|
await migrateLegacyOAuthToken(tokenStore, providerName, credentialId);
|
|
24095
24756
|
const oauth = new OAuthCallbackAuthProvider({
|
|
@@ -24136,7 +24797,7 @@ ${renderMemoryDocument(entries)}`;
|
|
|
24136
24797
|
}
|
|
24137
24798
|
},
|
|
24138
24799
|
async logout() {
|
|
24139
|
-
const tokenStore = createFileTokenStore(
|
|
24800
|
+
const tokenStore = createFileTokenStore(join30(home, ".flavor-code", "auth.json"));
|
|
24140
24801
|
const tokens = await tokenStore.load();
|
|
24141
24802
|
const hadCredentials = Object.keys(tokens).length > 0 || effectiveLlm !== void 0;
|
|
24142
24803
|
if (!hadCredentials) {
|
|
@@ -24329,10 +24990,16 @@ function formatMcpReconnect(server) {
|
|
|
24329
24990
|
return `Failed to reconnect MCP server "${server.name}": ${server.error ?? "unknown error"}`;
|
|
24330
24991
|
}
|
|
24331
24992
|
async function* runLoopSession(orchestrator, hooks, goal, signal) {
|
|
24993
|
+
yield* loopSessionEvents(orchestrator.run({ goal, signal }), hooks);
|
|
24994
|
+
}
|
|
24995
|
+
async function* runResumeLoopSession(orchestrator, hooks, loopId, signal) {
|
|
24996
|
+
yield* loopSessionEvents(orchestrator.resume({ loopId, signal }), hooks);
|
|
24997
|
+
}
|
|
24998
|
+
async function* loopSessionEvents(events, hooks) {
|
|
24332
24999
|
let totalInputTokens = 0;
|
|
24333
25000
|
let totalOutputTokens = 0;
|
|
24334
25001
|
let latestVerification;
|
|
24335
|
-
for await (const event of
|
|
25002
|
+
for await (const event of events) {
|
|
24336
25003
|
if (event.type === "worker-event") {
|
|
24337
25004
|
if (event.event.type === "usage") {
|
|
24338
25005
|
totalInputTokens += event.event.inputTokens;
|
|
@@ -24342,6 +25009,8 @@ async function* runLoopSession(orchestrator, hooks, goal, signal) {
|
|
|
24342
25009
|
totalInputTokens,
|
|
24343
25010
|
totalOutputTokens
|
|
24344
25011
|
};
|
|
25012
|
+
} else if (event.event.type === "error" && isResumableLongTaskError(event.event.error.code)) {
|
|
25013
|
+
yield { type: "warning", message: `Loop worker segment ended at ${event.event.error.code}; checkpointing and verifying before continuing.` };
|
|
24345
25014
|
} else if (event.event.type !== "done") yield event.event;
|
|
24346
25015
|
continue;
|
|
24347
25016
|
}
|
|
@@ -24396,7 +25065,7 @@ function loopProgressEvent(event) {
|
|
|
24396
25065
|
loopId: event.loopId,
|
|
24397
25066
|
phase: "budget",
|
|
24398
25067
|
state: "info",
|
|
24399
|
-
message:
|
|
25068
|
+
message: `${event.dimensions.join(" and ")} checkpoint reached; continuing automatically.`
|
|
24400
25069
|
};
|
|
24401
25070
|
}
|
|
24402
25071
|
return {
|
|
@@ -24413,6 +25082,9 @@ function terminalProgressState(status) {
|
|
|
24413
25082
|
if (status === "needs_human") return "info";
|
|
24414
25083
|
return "failed";
|
|
24415
25084
|
}
|
|
25085
|
+
function isResumableLongTaskError(code) {
|
|
25086
|
+
return code !== "authentication" && code !== "model_not_found" && code !== "cancelled" && code !== "memory_pressure";
|
|
25087
|
+
}
|
|
24416
25088
|
async function workspaceFingerprint(workspace) {
|
|
24417
25089
|
const hash = createHash18("sha256");
|
|
24418
25090
|
const diff = await execFileNoThrow(
|
|
@@ -24436,7 +25108,7 @@ async function workspaceFingerprint(workspace) {
|
|
|
24436
25108
|
hash.update(name);
|
|
24437
25109
|
if (remaining <= 0) continue;
|
|
24438
25110
|
try {
|
|
24439
|
-
const content = await
|
|
25111
|
+
const content = await readFile24(path);
|
|
24440
25112
|
const slice = content.subarray(0, remaining);
|
|
24441
25113
|
hash.update(slice);
|
|
24442
25114
|
remaining -= slice.length;
|
|
@@ -24548,7 +25220,16 @@ async function* durableTurn(source, journal, turnId, config) {
|
|
|
24548
25220
|
}
|
|
24549
25221
|
}
|
|
24550
25222
|
}
|
|
24551
|
-
async function*
|
|
25223
|
+
async function* monitorTurnMemory(source, onPressure, onBoundary) {
|
|
25224
|
+
let pressure = false;
|
|
25225
|
+
for await (const event of source) {
|
|
25226
|
+
if (event.type === "error" && event.error.code === "memory_pressure") pressure = true;
|
|
25227
|
+
yield event;
|
|
25228
|
+
}
|
|
25229
|
+
if (pressure) onPressure();
|
|
25230
|
+
else onBoundary();
|
|
25231
|
+
}
|
|
25232
|
+
async function* monitorLongTaskRestart(source, onRestartNeeded) {
|
|
24552
25233
|
let pressure = false;
|
|
24553
25234
|
for await (const event of source) {
|
|
24554
25235
|
if (event.type === "error" && event.error.code === "memory_pressure") pressure = true;
|
|
@@ -24556,7 +25237,10 @@ async function* monitorMemoryRestart(source, onRestartNeeded) {
|
|
|
24556
25237
|
}
|
|
24557
25238
|
if (pressure) onRestartNeeded();
|
|
24558
25239
|
}
|
|
24559
|
-
function requestMemoryRestart(workspace, sessionId, emitOutput) {
|
|
25240
|
+
function requestMemoryRestart(workspace, sessionId, emitOutput, rotation, onRestartRequested) {
|
|
25241
|
+
if (memoryRotationActive()) return;
|
|
25242
|
+
markMemoryRotation();
|
|
25243
|
+
const continuation = rotation?.continuation;
|
|
24560
25244
|
try {
|
|
24561
25245
|
const markerPath = memoryRestartMarkerPath(workspace);
|
|
24562
25246
|
let previous;
|
|
@@ -24564,14 +25248,43 @@ function requestMemoryRestart(workspace, sessionId, emitOutput) {
|
|
|
24564
25248
|
previous = parseMemoryRestartMarker(readFileSync3(markerPath, "utf8"));
|
|
24565
25249
|
} catch {
|
|
24566
25250
|
}
|
|
24567
|
-
|
|
24568
|
-
writeFileSync2(markerPath, `${JSON.stringify(nextMemoryRestartMarker(previous, sessionId, /* @__PURE__ */ new Date()))}
|
|
24569
|
-
`, "utf8");
|
|
25251
|
+
writeMarkerAtomically(markerPath, nextMemoryRestartMarker(previous, sessionId, /* @__PURE__ */ new Date(), continuation));
|
|
24570
25252
|
} catch {
|
|
24571
25253
|
}
|
|
24572
|
-
|
|
24573
|
-
|
|
24574
|
-
|
|
25254
|
+
if (rotation?.reading !== void 0) {
|
|
25255
|
+
const census = {
|
|
25256
|
+
at: (/* @__PURE__ */ new Date()).toISOString(),
|
|
25257
|
+
reason: continuation === void 0 ? "memory-pressure" : `heap-rotation:${continuation.kind}`,
|
|
25258
|
+
sessionId,
|
|
25259
|
+
heap: censusHeapBlock(rotation.reading),
|
|
25260
|
+
holders: rotation.holders ?? {}
|
|
25261
|
+
};
|
|
25262
|
+
writeRotationCensus(workspace, census);
|
|
25263
|
+
if (heapSnapshotWarranted(rotation.reading)) writeRotationHeapSnapshot(workspace);
|
|
25264
|
+
}
|
|
25265
|
+
emitOutput({
|
|
25266
|
+
type: "notice",
|
|
25267
|
+
message: continuation === void 0 ? "Memory watermark reached; the session is saved and Flavor is restarting with a fresh heap." : `Memory watermark reached; the session and the running ${continuation.kind} are saved, and Flavor is restarting with a fresh heap to continue it.`
|
|
25268
|
+
});
|
|
25269
|
+
if (onRestartRequested !== void 0) onRestartRequested(sessionId);
|
|
25270
|
+
else {
|
|
25271
|
+
process.exitCode = MEMORY_RESTART_EXIT_CODE;
|
|
25272
|
+
emitOutput({ type: "exit" });
|
|
25273
|
+
}
|
|
25274
|
+
}
|
|
25275
|
+
function writeMarkerAtomically(path, marker) {
|
|
25276
|
+
mkdirSync4(dirname17(path), { recursive: true });
|
|
25277
|
+
const temporary = `${path}.${process.pid}.${randomUUID20()}.tmp`;
|
|
25278
|
+
try {
|
|
25279
|
+
writeFileSync3(temporary, `${JSON.stringify(marker)}
|
|
25280
|
+
`, { encoding: "utf8", mode: 384 });
|
|
25281
|
+
renameSync2(temporary, path);
|
|
25282
|
+
} finally {
|
|
25283
|
+
try {
|
|
25284
|
+
unlinkSync3(temporary);
|
|
25285
|
+
} catch {
|
|
25286
|
+
}
|
|
25287
|
+
}
|
|
24575
25288
|
}
|
|
24576
25289
|
async function runGitCommit(deps, hint, signal) {
|
|
24577
25290
|
if (!await isGitRepository(deps.workspace)) return "Not a git repository \u2014 /commit needs git.";
|
|
@@ -24666,7 +25379,7 @@ async function registerConfiguredAdapters(providers, registry, environment, diag
|
|
|
24666
25379
|
const configured = { ...providers };
|
|
24667
25380
|
if (configured.openai === void 0 && environment.OPENAI_API_KEY) configured.openai = { type: "openai", apiKey: environment.OPENAI_API_KEY };
|
|
24668
25381
|
if (configured.anthropic === void 0 && environment.ANTHROPIC_API_KEY) configured.anthropic = { type: "anthropic", apiKey: environment.ANTHROPIC_API_KEY };
|
|
24669
|
-
const oauthTokenStore = createFileTokenStore(
|
|
25382
|
+
const oauthTokenStore = createFileTokenStore(join30(home, ".flavor-code", "auth.json"));
|
|
24670
25383
|
const registered = [];
|
|
24671
25384
|
let effectiveLlm;
|
|
24672
25385
|
for (const [name, provider] of Object.entries(configured)) {
|
|
@@ -24935,7 +25648,7 @@ async function generateSleepReview(registry, modelId, prompt, signal) {
|
|
|
24935
25648
|
}
|
|
24936
25649
|
async function optionalText2(path) {
|
|
24937
25650
|
try {
|
|
24938
|
-
return await
|
|
25651
|
+
return await readFile24(path, "utf8");
|
|
24939
25652
|
} catch (error) {
|
|
24940
25653
|
if (typeof error === "object" && error !== null && "code" in error && error.code === "ENOENT") return void 0;
|
|
24941
25654
|
throw error;
|
|
@@ -25020,7 +25733,24 @@ function attachCleanupError(primary, cleanup) {
|
|
|
25020
25733
|
}
|
|
25021
25734
|
}
|
|
25022
25735
|
async function* runGoalSession(orchestrator, goal, signal) {
|
|
25023
|
-
|
|
25736
|
+
yield* goalSessionEvents(orchestrator.run({ goal, signal }));
|
|
25737
|
+
}
|
|
25738
|
+
async function* runResumeGoalSession(orchestrator, goalId, signal) {
|
|
25739
|
+
yield* goalSessionEvents(orchestrator.resume({ goalId, signal }));
|
|
25740
|
+
}
|
|
25741
|
+
async function* goalSessionEvents(events) {
|
|
25742
|
+
for await (const event of events) {
|
|
25743
|
+
if (event.type === "goal-resumed") {
|
|
25744
|
+
yield { type: "notice", message: `Resuming goal ${event.goalId} at round ${event.round} after a heap rotation.` };
|
|
25745
|
+
continue;
|
|
25746
|
+
}
|
|
25747
|
+
if (event.type === "goal-stage-retry") {
|
|
25748
|
+
yield {
|
|
25749
|
+
type: "warning",
|
|
25750
|
+
message: `Goal ${event.stage} attempt ${event.attempt} hit a recoverable boundary (${event.reason}); retrying in ${event.delayMs}ms.`
|
|
25751
|
+
};
|
|
25752
|
+
continue;
|
|
25753
|
+
}
|
|
25024
25754
|
if (event.type === "goal-plan-created") {
|
|
25025
25755
|
yield { type: "notice", message: `Goal plan created (${event.plan.kind}) with ${event.plan.criteria.length} acceptance criteria.` };
|
|
25026
25756
|
yield { type: "notice", message: `Plan file: ${event.planPath}` };
|
|
@@ -25047,6 +25777,8 @@ async function* runGoalSession(orchestrator, goal, signal) {
|
|
|
25047
25777
|
totalInputTokens: event.event.usage.inputTokens,
|
|
25048
25778
|
totalOutputTokens: event.event.usage.outputTokens
|
|
25049
25779
|
};
|
|
25780
|
+
} else if (event.event.type === "error" && isResumableLongTaskError(event.event.error.code)) {
|
|
25781
|
+
yield { type: "warning", message: `Goal worker segment ended at ${event.event.error.code}; checkpointing and verifying before continuing.` };
|
|
25050
25782
|
} else {
|
|
25051
25783
|
yield event.event;
|
|
25052
25784
|
}
|