flavor-code 1.4.0-beta.3 → 1.4.0-beta.4
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-UWIEIWBQ.js} +3 -3
- package/dist/{chunk-3BHKW5A7.js → chunk-4RR5VVX4.js} +8 -2
- package/dist/{chunk-NIKCPCJG.js → chunk-DCNUBQCH.js} +826 -273
- package/dist/{chunk-KBHWS7M6.js → chunk-KQMKY7C5.js} +4 -0
- package/dist/chunk-LKQTBL3R.js +107 -0
- package/dist/cli-main.js +20 -13
- package/dist/cli.js +24 -5
- package/dist/config/schema.d.ts +4 -0
- package/dist/context/manager.d.ts +9 -0
- package/dist/desktop/main.js +1170 -532
- package/dist/desktop-renderer/assets/{index-CYd5JkTR.js → index-C0t1nGih.js} +2 -2
- package/dist/desktop-renderer/assets/{interactive-terminal-j52Ty3QO.js → interactive-terminal-BJqe96mZ.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 +18 -0
- package/dist/goal/planner.d.ts +2 -0
- package/dist/goal/store.d.ts +2 -0
- package/dist/goal/types.d.ts +8 -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 +18 -0
- package/dist/loop/types.d.ts +7 -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 +9 -0
- package/dist/utils/heap.d.ts +58 -0
- package/dist/utils/memory-restart.d.ts +38 -3
- package/dist/utils/rotation-census.d.ts +36 -0
- package/package.json +1 -1
- package/dist/chunk-CIVPKHF4.js +0 -53
|
@@ -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-LKQTBL3R.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,
|
|
@@ -2061,6 +2066,10 @@ var SessionHistory = class _SessionHistory {
|
|
|
2061
2066
|
get leafId() {
|
|
2062
2067
|
return this.#leafId;
|
|
2063
2068
|
}
|
|
2069
|
+
/** Cheap retained-node count for the rotation census (no cloning). */
|
|
2070
|
+
get nodeCount() {
|
|
2071
|
+
return this.#nodes.length;
|
|
2072
|
+
}
|
|
2064
2073
|
tree() {
|
|
2065
2074
|
return this.#nodes.map((node) => structuredClone(node));
|
|
2066
2075
|
}
|
|
@@ -3635,6 +3644,7 @@ var FlavorSession = class {
|
|
|
3635
3644
|
#pendingCoWorkStarts = [];
|
|
3636
3645
|
#activeSteeringDurableIds = /* @__PURE__ */ new Set();
|
|
3637
3646
|
#durableResumeStarted = false;
|
|
3647
|
+
#continuationResumeStarted = false;
|
|
3638
3648
|
#permissionBaseline;
|
|
3639
3649
|
#activeSubmission;
|
|
3640
3650
|
constructor(services) {
|
|
@@ -3655,6 +3665,10 @@ var FlavorSession = class {
|
|
|
3655
3665
|
queueSnapshot() {
|
|
3656
3666
|
return this.#queue.snapshot();
|
|
3657
3667
|
}
|
|
3668
|
+
/** True when start() admitted a persisted /loop or /goal continuation. */
|
|
3669
|
+
get rotationContinuationResumed() {
|
|
3670
|
+
return this.#continuationResumeStarted;
|
|
3671
|
+
}
|
|
3658
3672
|
clearQueue() {
|
|
3659
3673
|
const snapshot4 = this.#queue.clear();
|
|
3660
3674
|
for (const kind of ["steer", "followUp"]) {
|
|
@@ -3804,6 +3818,7 @@ var FlavorSession = class {
|
|
|
3804
3818
|
if (decision.additionalContext !== void 0) await this.#services.addContext?.(decision.additionalContext);
|
|
3805
3819
|
this.#started = true;
|
|
3806
3820
|
this.#resumeDurableQueue();
|
|
3821
|
+
this.#resumeRotationContinuation();
|
|
3807
3822
|
});
|
|
3808
3823
|
return this.#startPromise;
|
|
3809
3824
|
}
|
|
@@ -3886,7 +3901,8 @@ var FlavorSession = class {
|
|
|
3886
3901
|
skillNames,
|
|
3887
3902
|
this.#services.managedToolCommands().map(({ name }) => name)
|
|
3888
3903
|
);
|
|
3889
|
-
if (
|
|
3904
|
+
if (submission.continuation !== void 0) await this.#runContinuation(submission.continuation, controller.signal);
|
|
3905
|
+
else if (command !== null) await this.#dispatch(command, controller.signal);
|
|
3890
3906
|
else for await (const event of this.#services.run(prompt, controller.signal, {
|
|
3891
3907
|
getSteeringMessages: () => this.#drainMessages("steer", true).map((item) => item.text),
|
|
3892
3908
|
...decision.additionalContext === void 0 ? {} : { additionalContext: decision.additionalContext },
|
|
@@ -4144,6 +4160,33 @@ Address this now (for example run the required verification or fix), then finish
|
|
|
4144
4160
|
});
|
|
4145
4161
|
}
|
|
4146
4162
|
}
|
|
4163
|
+
/**
|
|
4164
|
+
* After a heap rotation the relaunched process inherits the same session and
|
|
4165
|
+
* a marker continuation naming the /loop or /goal that was in flight. Resume
|
|
4166
|
+
* it once here so a multi-day autonomous run survives the rotation instead of
|
|
4167
|
+
* silently stopping at the boundary.
|
|
4168
|
+
*/
|
|
4169
|
+
#resumeRotationContinuation() {
|
|
4170
|
+
if (this.#continuationResumeStarted || this.#closed) return;
|
|
4171
|
+
const continuation = this.#services.continuation?.();
|
|
4172
|
+
if (continuation === void 0) return;
|
|
4173
|
+
const supported = continuation.kind === "loop" ? this.#services.resumeLoop !== void 0 : this.#services.resumeGoal !== void 0;
|
|
4174
|
+
if (!supported) return;
|
|
4175
|
+
this.#continuationResumeStarted = true;
|
|
4176
|
+
const displayText = `Resuming the interrupted ${continuation.kind} ${continuation.id} after a heap rotation.`;
|
|
4177
|
+
this.#notice(displayText);
|
|
4178
|
+
this.#enqueueNormalizedSubmission({ text: displayText, displayText, continuation }).catch((error) => {
|
|
4179
|
+
this.#services.output({ type: "error", error: { code: "unknown", message: message(error) } });
|
|
4180
|
+
});
|
|
4181
|
+
}
|
|
4182
|
+
async #runContinuation(continuation, signal) {
|
|
4183
|
+
const stream = continuation.kind === "loop" ? this.#services.resumeLoop?.(continuation.id, signal) : this.#services.resumeGoal?.(continuation.id, signal);
|
|
4184
|
+
if (stream === void 0) {
|
|
4185
|
+
this.#notice(`The runtime cannot resume the interrupted ${continuation.kind} ${continuation.id}; start it again with /${continuation.kind}.`);
|
|
4186
|
+
return;
|
|
4187
|
+
}
|
|
4188
|
+
for await (const event of stream) this.#services.output(event);
|
|
4189
|
+
}
|
|
4147
4190
|
#ackDurable(submission) {
|
|
4148
4191
|
if (submission.durableId === void 0) return;
|
|
4149
4192
|
this.#services.durableQueue?.ack(submission.durableId);
|
|
@@ -5973,8 +6016,8 @@ var PalBrokerServer = class {
|
|
|
5973
6016
|
});
|
|
5974
6017
|
if (this.endpointLock === void 0) throw new Error(`Pals endpoint '${this.address}' is active or owned`);
|
|
5975
6018
|
try {
|
|
5976
|
-
const
|
|
5977
|
-
if (!
|
|
6019
|
+
const stat12 = await lstat4(this.address);
|
|
6020
|
+
if (!stat12.isSocket()) throw new Error(`Refusing to unlink '${this.address}' because it is not a socket`);
|
|
5978
6021
|
await unlink3(this.address);
|
|
5979
6022
|
} catch (error) {
|
|
5980
6023
|
if (error.code !== "ENOENT") throw error;
|
|
@@ -5987,8 +6030,8 @@ var PalBrokerServer = class {
|
|
|
5987
6030
|
}
|
|
5988
6031
|
async unlinkKnownSocket() {
|
|
5989
6032
|
try {
|
|
5990
|
-
const
|
|
5991
|
-
if (
|
|
6033
|
+
const stat12 = await lstat4(this.address);
|
|
6034
|
+
if (stat12.isSocket()) await unlink3(this.address);
|
|
5992
6035
|
} catch (error) {
|
|
5993
6036
|
if (error.code !== "ENOENT") throw error;
|
|
5994
6037
|
}
|
|
@@ -6715,11 +6758,11 @@ async function ensureOnce(options) {
|
|
|
6715
6758
|
}
|
|
6716
6759
|
|
|
6717
6760
|
// src/production.ts
|
|
6718
|
-
import { readFile as
|
|
6719
|
-
import { mkdirSync as
|
|
6761
|
+
import { readFile as readFile24 } from "fs/promises";
|
|
6762
|
+
import { mkdirSync as mkdirSync4, readFileSync as readFileSync3, renameSync as renameSync2, unlinkSync as unlinkSync3, writeFileSync as writeFileSync3 } from "fs";
|
|
6720
6763
|
import { createHash as createHash18, randomUUID as randomUUID20 } from "crypto";
|
|
6721
6764
|
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
|
|
6765
|
+
import { basename as basename7, dirname as dirname17, join as join30, relative as relative22, resolve as resolve31, sep as sep22 } from "path";
|
|
6723
6766
|
|
|
6724
6767
|
// src/agent/subagents.ts
|
|
6725
6768
|
import { z as z9 } from "zod";
|
|
@@ -7677,6 +7720,18 @@ var ContextManager = class _ContextManager {
|
|
|
7677
7720
|
estimatedTokens() {
|
|
7678
7721
|
return estimateMessageTokens(this.messagesForModel());
|
|
7679
7722
|
}
|
|
7723
|
+
/** Retained sizes for the rotation census: what this context holds right now. */
|
|
7724
|
+
census() {
|
|
7725
|
+
const chars = (messages) => messages.reduce((sum, entry2) => sum + modelContentText(entry2.content).length, 0);
|
|
7726
|
+
return {
|
|
7727
|
+
messages: this.#messages.length,
|
|
7728
|
+
messageChars: chars(this.#messages),
|
|
7729
|
+
visibilityEntries: this.#visibilityLog.length,
|
|
7730
|
+
visibilityChars: this.#visibilityLog.reduce((sum, record) => sum + record.content.length, 0),
|
|
7731
|
+
transientEntries: this.#activeTransientSystem.size,
|
|
7732
|
+
transientChars: chars([...this.#activeTransientSystem.values()])
|
|
7733
|
+
};
|
|
7734
|
+
}
|
|
7680
7735
|
needsCompaction() {
|
|
7681
7736
|
if (modelVisibleText(this.messagesForModel()).length >= this.#compactAtChars) return true;
|
|
7682
7737
|
return calculateContextPressure(this.#currentTokenUsage(), this.#compaction).shouldAutoCompact;
|
|
@@ -8245,8 +8300,75 @@ function reportProgress(callback, percentage) {
|
|
|
8245
8300
|
}
|
|
8246
8301
|
}
|
|
8247
8302
|
|
|
8248
|
-
// src/
|
|
8303
|
+
// src/utils/heap.ts
|
|
8249
8304
|
import { getHeapStatistics } from "v8";
|
|
8305
|
+
import { totalmem } from "os";
|
|
8306
|
+
var MEMORY_PRESSURE_HEAP_RATIO = 0.8;
|
|
8307
|
+
var MEMORY_ROTATION_HEAP_RATIO = 0.6;
|
|
8308
|
+
var MEMORY_PRESSURE_RSS_RATIO = 0.75;
|
|
8309
|
+
var MEMORY_ROTATION_RSS_RATIO = 0.6;
|
|
8310
|
+
var ROTATION_PRECHECK_RATIO = 0.45;
|
|
8311
|
+
function readHeap() {
|
|
8312
|
+
const heapLimit = getHeapStatistics().heap_size_limit;
|
|
8313
|
+
const heapUsed = process.memoryUsage().heapUsed;
|
|
8314
|
+
return { heapUsed, heapLimit, ratio: heapLimit > 0 ? heapUsed / heapLimit : 0 };
|
|
8315
|
+
}
|
|
8316
|
+
function forceFullGc() {
|
|
8317
|
+
const gc = globalThis.gc;
|
|
8318
|
+
if (typeof gc !== "function") return false;
|
|
8319
|
+
gc();
|
|
8320
|
+
gc();
|
|
8321
|
+
return true;
|
|
8322
|
+
}
|
|
8323
|
+
function ratioOf(heapLimit, heapUsed) {
|
|
8324
|
+
return heapLimit > 0 ? heapUsed / heapLimit : 0;
|
|
8325
|
+
}
|
|
8326
|
+
function effectiveMemoryLimit() {
|
|
8327
|
+
const physical = totalmem();
|
|
8328
|
+
const constrained = process.constrainedMemory?.() ?? 0;
|
|
8329
|
+
return constrained > 0 ? Math.min(physical, constrained) : physical;
|
|
8330
|
+
}
|
|
8331
|
+
function withRss(reading, gcVerified) {
|
|
8332
|
+
const usage = process.memoryUsage();
|
|
8333
|
+
const memoryLimit = effectiveMemoryLimit();
|
|
8334
|
+
return {
|
|
8335
|
+
...reading,
|
|
8336
|
+
gcVerified,
|
|
8337
|
+
rss: usage.rss,
|
|
8338
|
+
memoryLimit,
|
|
8339
|
+
rssRatio: ratioOf(memoryLimit, usage.rss),
|
|
8340
|
+
external: usage.external,
|
|
8341
|
+
arrayBuffers: usage.arrayBuffers
|
|
8342
|
+
};
|
|
8343
|
+
}
|
|
8344
|
+
function verifiedHeapPressure(threshold) {
|
|
8345
|
+
const raw = readHeap();
|
|
8346
|
+
const before = withRss(raw, false);
|
|
8347
|
+
const rssThreshold = threshold >= MEMORY_PRESSURE_HEAP_RATIO ? MEMORY_PRESSURE_RSS_RATIO : threshold;
|
|
8348
|
+
if ((raw.heapLimit <= 0 || raw.ratio < threshold) && (before.rssRatio ?? 0) < rssThreshold) return void 0;
|
|
8349
|
+
if (!forceFullGc()) return before;
|
|
8350
|
+
const heapUsed = process.memoryUsage().heapUsed;
|
|
8351
|
+
const ratio = ratioOf(raw.heapLimit, heapUsed);
|
|
8352
|
+
const verified = withRss({ heapUsed, heapLimit: raw.heapLimit, ratio, verifiedRatio: ratio }, true);
|
|
8353
|
+
if (ratio < threshold && (verified.rssRatio ?? 0) < rssThreshold) return void 0;
|
|
8354
|
+
return verified;
|
|
8355
|
+
}
|
|
8356
|
+
function heapRotationNeeded() {
|
|
8357
|
+
const raw = readHeap();
|
|
8358
|
+
const before = withRss(raw, false);
|
|
8359
|
+
if ((raw.heapLimit <= 0 || raw.ratio < ROTATION_PRECHECK_RATIO) && (before.rssRatio ?? 0) < ROTATION_PRECHECK_RATIO) return void 0;
|
|
8360
|
+
if (!forceFullGc()) {
|
|
8361
|
+
return raw.ratio >= MEMORY_PRESSURE_HEAP_RATIO || (before.rssRatio ?? 0) >= MEMORY_PRESSURE_RSS_RATIO ? before : void 0;
|
|
8362
|
+
}
|
|
8363
|
+
const heapUsed = process.memoryUsage().heapUsed;
|
|
8364
|
+
const ratio = ratioOf(raw.heapLimit, heapUsed);
|
|
8365
|
+
const verified = withRss({ heapUsed, heapLimit: raw.heapLimit, ratio, verifiedRatio: ratio }, true);
|
|
8366
|
+
if (ratio < MEMORY_ROTATION_HEAP_RATIO && (verified.rssRatio ?? 0) < MEMORY_ROTATION_RSS_RATIO) return void 0;
|
|
8367
|
+
return verified;
|
|
8368
|
+
}
|
|
8369
|
+
function heapSnapshotWarranted(_reading) {
|
|
8370
|
+
return process.env.FLAVOR_HEAP_SNAPSHOT_ON_ROTATE === "1";
|
|
8371
|
+
}
|
|
8250
8372
|
|
|
8251
8373
|
// src/models/structured.ts
|
|
8252
8374
|
import { z as z11 } from "zod";
|
|
@@ -8665,7 +8787,6 @@ function envMaxIterations() {
|
|
|
8665
8787
|
if (!Number.isInteger(parsed) || parsed <= 0) return void 0;
|
|
8666
8788
|
return parsed;
|
|
8667
8789
|
}
|
|
8668
|
-
var MEMORY_PRESSURE_HEAP_RATIO = 0.8;
|
|
8669
8790
|
function memoryPressureError(heapUsed, heapLimit) {
|
|
8670
8791
|
if (heapLimit <= 0 || heapUsed < heapLimit * MEMORY_PRESSURE_HEAP_RATIO) return void 0;
|
|
8671
8792
|
const mb = (bytes) => Math.round(bytes / 1048576);
|
|
@@ -8675,7 +8796,12 @@ function memoryPressureError(heapUsed, heapLimit) {
|
|
|
8675
8796
|
};
|
|
8676
8797
|
}
|
|
8677
8798
|
function currentMemoryPressure() {
|
|
8678
|
-
|
|
8799
|
+
const reading = verifiedHeapPressure(MEMORY_PRESSURE_HEAP_RATIO);
|
|
8800
|
+
if (reading === void 0) return void 0;
|
|
8801
|
+
return memoryPressureError(reading.heapUsed, reading.heapLimit) ?? {
|
|
8802
|
+
code: "memory_pressure",
|
|
8803
|
+
message: `Process RSS reached ${Math.round((reading.rssRatio ?? 0) * 100)}% of the available memory limit; stopping before system OOM`
|
|
8804
|
+
};
|
|
8679
8805
|
}
|
|
8680
8806
|
var AgentLoop = class {
|
|
8681
8807
|
#options;
|
|
@@ -10053,6 +10179,10 @@ var HarnessJournal = class {
|
|
|
10053
10179
|
get records() {
|
|
10054
10180
|
return this.#records.map((record) => structuredClone(record));
|
|
10055
10181
|
}
|
|
10182
|
+
/** Retained record count for the rotation census. */
|
|
10183
|
+
get recordCount() {
|
|
10184
|
+
return this.#records.length;
|
|
10185
|
+
}
|
|
10056
10186
|
admitQueue(kind, payload) {
|
|
10057
10187
|
QueueKindSchema.parse(kind);
|
|
10058
10188
|
const id = randomUUID8();
|
|
@@ -10329,19 +10459,38 @@ function jsonSafeRecord(value) {
|
|
|
10329
10459
|
return JSON.parse(json);
|
|
10330
10460
|
}
|
|
10331
10461
|
function hashJson(value) {
|
|
10332
|
-
|
|
10333
|
-
|
|
10334
|
-
|
|
10335
|
-
const { hash: _hash, ...unsigned } = record;
|
|
10336
|
-
return hashJson(unsigned);
|
|
10462
|
+
const hash = createHash6("sha256");
|
|
10463
|
+
stableHashInto(hash, value);
|
|
10464
|
+
return hash.digest("hex");
|
|
10337
10465
|
}
|
|
10338
|
-
function
|
|
10339
|
-
if (Array.isArray(value))
|
|
10466
|
+
function stableHashInto(hash, value) {
|
|
10467
|
+
if (Array.isArray(value)) {
|
|
10468
|
+
hash.update("[");
|
|
10469
|
+
for (let index = 0; index < value.length; index += 1) {
|
|
10470
|
+
if (index > 0) hash.update(",");
|
|
10471
|
+
stableHashInto(hash, value[index]);
|
|
10472
|
+
}
|
|
10473
|
+
hash.update("]");
|
|
10474
|
+
return;
|
|
10475
|
+
}
|
|
10340
10476
|
if (typeof value === "object" && value !== null) {
|
|
10341
10477
|
const object = value;
|
|
10342
|
-
|
|
10478
|
+
hash.update("{");
|
|
10479
|
+
const keys = Object.keys(object).sort();
|
|
10480
|
+
for (let index = 0; index < keys.length; index += 1) {
|
|
10481
|
+
const key = keys[index];
|
|
10482
|
+
if (index > 0) hash.update(",");
|
|
10483
|
+
hash.update(`${JSON.stringify(key)}:`);
|
|
10484
|
+
stableHashInto(hash, object[key]);
|
|
10485
|
+
}
|
|
10486
|
+
hash.update("}");
|
|
10487
|
+
return;
|
|
10343
10488
|
}
|
|
10344
|
-
|
|
10489
|
+
hash.update(JSON.stringify(value) ?? String(value));
|
|
10490
|
+
}
|
|
10491
|
+
function recordHash(record) {
|
|
10492
|
+
const { hash: _hash, ...unsigned } = record;
|
|
10493
|
+
return hashJson(unsigned);
|
|
10345
10494
|
}
|
|
10346
10495
|
function assertSessionId(value) {
|
|
10347
10496
|
if (!/^[A-Za-z0-9][A-Za-z0-9._-]{0,127}$/.test(value)) throw new Error("Invalid session id");
|
|
@@ -12457,6 +12606,14 @@ var LoopStateSchema = z17.object({
|
|
|
12457
12606
|
approvals: z17.array(LoopApprovalSchema)
|
|
12458
12607
|
}).strict(),
|
|
12459
12608
|
cycles: z17.array(LoopCycleEvidenceSchema),
|
|
12609
|
+
/** Completed worker awaiting host verification; avoids replaying tool work after rotation. */
|
|
12610
|
+
pendingCycle: z17.object({
|
|
12611
|
+
cycle: z17.number().int().positive(),
|
|
12612
|
+
startedAt: z17.string().datetime({ offset: true }),
|
|
12613
|
+
inputTokens: z17.number().int().nonnegative(),
|
|
12614
|
+
outputTokens: z17.number().int().nonnegative(),
|
|
12615
|
+
workerText: z17.string().max(16e3)
|
|
12616
|
+
}).strict().nullable().optional(),
|
|
12460
12617
|
terminalReason: z17.string().optional()
|
|
12461
12618
|
}).strict();
|
|
12462
12619
|
var LoopEventSchema = z17.object({
|
|
@@ -12545,15 +12702,45 @@ var LoopOrchestrator = class {
|
|
|
12545
12702
|
tokenCheckpoint: this.#options.config.maxTokens,
|
|
12546
12703
|
approvals: []
|
|
12547
12704
|
},
|
|
12548
|
-
cycles: []
|
|
12705
|
+
cycles: [],
|
|
12706
|
+
pendingCycle: null
|
|
12549
12707
|
});
|
|
12550
12708
|
await this.#options.persistence.create(state);
|
|
12709
|
+
yield* this.#execute(state, request.goal, request.signal);
|
|
12710
|
+
}
|
|
12711
|
+
/**
|
|
12712
|
+
* Continue a loop whose process rotated onto a fresh heap. The persisted
|
|
12713
|
+
* state decides where to pick up; an interrupted cycle simply runs again.
|
|
12714
|
+
*/
|
|
12715
|
+
async *resume(request) {
|
|
12716
|
+
const persistence = this.#options.persistence;
|
|
12717
|
+
if (persistence.load === void 0) throw new Error("Loop persistence does not support resume");
|
|
12718
|
+
let state;
|
|
12551
12719
|
try {
|
|
12720
|
+
state = await persistence.load(request.loopId);
|
|
12721
|
+
} catch (error) {
|
|
12722
|
+
yield { type: "loop-terminal", loopId: request.loopId, status: "failed", reason: message(error) };
|
|
12723
|
+
return;
|
|
12724
|
+
}
|
|
12725
|
+
if (state.status !== "running") {
|
|
12726
|
+
const status = state.status;
|
|
12727
|
+
yield { type: "loop-terminal", loopId: state.loopId, status, reason: state.terminalReason ?? "The loop is not running." };
|
|
12728
|
+
return;
|
|
12729
|
+
}
|
|
12730
|
+
yield* this.#execute(state, state.goal, request.signal);
|
|
12731
|
+
}
|
|
12732
|
+
async *#execute(initialState, goal, signal) {
|
|
12733
|
+
const now = this.#options.now ?? (() => (/* @__PURE__ */ new Date()).toISOString());
|
|
12734
|
+
const loopId = initialState.loopId;
|
|
12735
|
+
let state = initialState;
|
|
12736
|
+
try {
|
|
12737
|
+
await this.#options.onMemoryCheckpoint?.(loopId);
|
|
12738
|
+
if (memoryRotationActive()) return;
|
|
12552
12739
|
const workspaceResolution = await this.#options.prepareWorkspace({
|
|
12553
12740
|
root: this.#options.workspace,
|
|
12554
12741
|
loopId,
|
|
12555
|
-
goal
|
|
12556
|
-
signal
|
|
12742
|
+
goal,
|
|
12743
|
+
signal
|
|
12557
12744
|
});
|
|
12558
12745
|
if (workspaceResolution.kind === "needs_human") {
|
|
12559
12746
|
state = await this.#terminal(state, "needs_human", workspaceResolution.reason, now());
|
|
@@ -12571,67 +12758,87 @@ var LoopOrchestrator = class {
|
|
|
12571
12758
|
cycleCheckpoint: state.budget.cycleCheckpoint,
|
|
12572
12759
|
tokenCheckpoint: state.budget.tokenCheckpoint
|
|
12573
12760
|
};
|
|
12574
|
-
|
|
12761
|
+
const lastCompletedCycle = state.cycles.at(-1);
|
|
12762
|
+
let previousVerification = lastCompletedCycle?.verification ?? (plan.commands.length === 0 ? {
|
|
12575
12763
|
passed: false,
|
|
12576
12764
|
commands: [],
|
|
12577
12765
|
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
|
-
|
|
12766
|
+
} : void 0);
|
|
12767
|
+
let previousFailureSignature = lastCompletedCycle === void 0 ? void 0 : `${lastCompletedCycle.workspaceFingerprint}
|
|
12768
|
+
${lastCompletedCycle.verification.summary}`;
|
|
12769
|
+
let repeatedFailures = previousFailureSignature === void 0 ? 0 : countTrailingFailureSignature(state.cycles, previousFailureSignature);
|
|
12581
12770
|
while (state.status === "running") {
|
|
12582
|
-
|
|
12583
|
-
|
|
12584
|
-
|
|
12585
|
-
|
|
12586
|
-
|
|
12587
|
-
const
|
|
12588
|
-
|
|
12589
|
-
|
|
12590
|
-
|
|
12591
|
-
|
|
12592
|
-
|
|
12593
|
-
}
|
|
12594
|
-
|
|
12595
|
-
|
|
12596
|
-
|
|
12597
|
-
|
|
12598
|
-
|
|
12599
|
-
|
|
12600
|
-
|
|
12601
|
-
|
|
12602
|
-
|
|
12603
|
-
|
|
12604
|
-
|
|
12605
|
-
|
|
12606
|
-
|
|
12607
|
-
|
|
12608
|
-
|
|
12609
|
-
|
|
12610
|
-
|
|
12611
|
-
|
|
12612
|
-
|
|
12613
|
-
|
|
12614
|
-
|
|
12615
|
-
|
|
12616
|
-
|
|
12771
|
+
signal.throwIfAborted();
|
|
12772
|
+
await this.#options.onCycleBoundary?.(loopId);
|
|
12773
|
+
if (memoryRotationActive()) return;
|
|
12774
|
+
const pendingCycle = state.pendingCycle;
|
|
12775
|
+
const cycle = pendingCycle?.cycle ?? state.budget.cyclesUsed + 1;
|
|
12776
|
+
const startedAt = pendingCycle?.startedAt ?? now();
|
|
12777
|
+
let cycleInputTokens = pendingCycle?.inputTokens ?? 0;
|
|
12778
|
+
let cycleOutputTokens = pendingCycle?.outputTokens ?? 0;
|
|
12779
|
+
let workerText = pendingCycle?.workerText ?? "";
|
|
12780
|
+
if (pendingCycle === void 0 || pendingCycle === null) {
|
|
12781
|
+
await this.#options.persistence.append({ version: 1, type: "cycle_started", timestamp: startedAt, loopId, payload: { cycle } });
|
|
12782
|
+
yield { type: "loop-cycle-start", loopId, cycle };
|
|
12783
|
+
const prompt = buildLoopCyclePrompt({
|
|
12784
|
+
goal,
|
|
12785
|
+
cycle,
|
|
12786
|
+
...previousVerification === void 0 ? {} : {
|
|
12787
|
+
memory: `Previous host verification: ${previousVerification.summary}`,
|
|
12788
|
+
verification: previousVerification
|
|
12789
|
+
}
|
|
12790
|
+
});
|
|
12791
|
+
let workerError;
|
|
12792
|
+
let workerMemoryPressure = false;
|
|
12793
|
+
for await (const event of this.#options.runWorker({
|
|
12794
|
+
goal,
|
|
12795
|
+
cycle,
|
|
12796
|
+
workspace: executionWorkspace.root,
|
|
12797
|
+
prompt,
|
|
12798
|
+
signal
|
|
12799
|
+
})) {
|
|
12800
|
+
if (event.type === "usage") {
|
|
12801
|
+
cycleInputTokens += event.inputTokens;
|
|
12802
|
+
cycleOutputTokens += event.outputTokens;
|
|
12803
|
+
}
|
|
12804
|
+
if (event.type === "error") {
|
|
12805
|
+
workerError = event.error.message;
|
|
12806
|
+
if (event.error.code === "memory_pressure") workerMemoryPressure = true;
|
|
12807
|
+
}
|
|
12808
|
+
if (event.type === "text") workerText = `${workerText}${event.text}`.slice(-16e3);
|
|
12809
|
+
if (this.#options.hallucinationGuard !== void 0) {
|
|
12810
|
+
if (event.type === "tool-start") {
|
|
12811
|
+
this.#options.hallucinationGuard.recordToolCall(event.name, event.input, event.id);
|
|
12812
|
+
} else if (event.type === "tool-end") {
|
|
12813
|
+
this.#options.hallucinationGuard.recordToolResult(event.name, event.result, event.id);
|
|
12814
|
+
}
|
|
12617
12815
|
}
|
|
12816
|
+
yield { type: "worker-event", event };
|
|
12817
|
+
}
|
|
12818
|
+
if (workerMemoryPressure) return;
|
|
12819
|
+
if (workerError !== void 0) {
|
|
12820
|
+
state = LoopStateSchema.parse({
|
|
12821
|
+
...state,
|
|
12822
|
+
budget: {
|
|
12823
|
+
...state.budget,
|
|
12824
|
+
cyclesUsed: cycle,
|
|
12825
|
+
inputTokens: state.budget.inputTokens + cycleInputTokens,
|
|
12826
|
+
outputTokens: state.budget.outputTokens + cycleOutputTokens
|
|
12827
|
+
}
|
|
12828
|
+
});
|
|
12829
|
+
state = await this.#terminal(state, "failed", workerError, now());
|
|
12830
|
+
yield { type: "loop-terminal", loopId, status: "failed", reason: workerError };
|
|
12831
|
+
return;
|
|
12618
12832
|
}
|
|
12619
|
-
yield { type: "worker-event", event };
|
|
12620
|
-
}
|
|
12621
|
-
if (workerError !== void 0) {
|
|
12622
12833
|
state = LoopStateSchema.parse({
|
|
12623
12834
|
...state,
|
|
12624
|
-
|
|
12625
|
-
|
|
12626
|
-
cyclesUsed: cycle,
|
|
12627
|
-
inputTokens: state.budget.inputTokens + cycleInputTokens,
|
|
12628
|
-
outputTokens: state.budget.outputTokens + cycleOutputTokens
|
|
12629
|
-
}
|
|
12835
|
+
pendingCycle: { cycle, startedAt, inputTokens: cycleInputTokens, outputTokens: cycleOutputTokens, workerText },
|
|
12836
|
+
updatedAt: now()
|
|
12630
12837
|
});
|
|
12631
|
-
|
|
12632
|
-
yield { type: "loop-terminal", loopId, status: "failed", reason: workerError };
|
|
12633
|
-
return;
|
|
12838
|
+
await this.#options.persistence.save(state);
|
|
12634
12839
|
}
|
|
12840
|
+
await this.#options.onMemoryCheckpoint?.(loopId);
|
|
12841
|
+
if (memoryRotationActive()) return;
|
|
12635
12842
|
if (plan.commands.length === 0) {
|
|
12636
12843
|
plan = await this.#options.inferVerification(executionWorkspace.root);
|
|
12637
12844
|
if (plan.commands.length > 0) {
|
|
@@ -12651,8 +12858,28 @@ var LoopOrchestrator = class {
|
|
|
12651
12858
|
passed: false,
|
|
12652
12859
|
commands: [],
|
|
12653
12860
|
summary: plan.needsHumanReason ?? "No deterministic verification command was found after the discovery cycle."
|
|
12654
|
-
} : await this.#options.runVerifier(plan, executionWorkspace.root,
|
|
12861
|
+
} : await this.#options.runVerifier(plan, executionWorkspace.root, signal);
|
|
12862
|
+
await this.#options.onMemoryCheckpoint?.(loopId);
|
|
12863
|
+
if (memoryRotationActive()) return;
|
|
12655
12864
|
const fingerprint2 = await this.#options.fingerprint(executionWorkspace.root);
|
|
12865
|
+
let guardWarnings = [];
|
|
12866
|
+
if (evidence.passed && this.#options.hallucinationGuard !== void 0) {
|
|
12867
|
+
try {
|
|
12868
|
+
await this.#options.onMemoryCheckpoint?.(loopId);
|
|
12869
|
+
if (memoryRotationActive()) return;
|
|
12870
|
+
const report = await this.#options.hallucinationGuard.evaluate(goal, workerText);
|
|
12871
|
+
await this.#options.onMemoryCheckpoint?.(loopId);
|
|
12872
|
+
if (memoryRotationActive()) return;
|
|
12873
|
+
if (!report.passed) {
|
|
12874
|
+
const guardReason = report.blockingReasons.join("; ") || "Hallucination guard blocked completion.";
|
|
12875
|
+
state = await this.#terminal(state, "failed", guardReason, now());
|
|
12876
|
+
yield { type: "loop-terminal", loopId, status: "failed", reason: guardReason };
|
|
12877
|
+
return;
|
|
12878
|
+
}
|
|
12879
|
+
guardWarnings = report.warnings;
|
|
12880
|
+
} catch {
|
|
12881
|
+
}
|
|
12882
|
+
}
|
|
12656
12883
|
const completedAt = now();
|
|
12657
12884
|
state = LoopStateSchema.parse({
|
|
12658
12885
|
...state,
|
|
@@ -12671,7 +12898,8 @@ var LoopOrchestrator = class {
|
|
|
12671
12898
|
outputTokens: cycleOutputTokens,
|
|
12672
12899
|
workspaceFingerprint: fingerprint2,
|
|
12673
12900
|
verification: evidence
|
|
12674
|
-
}]
|
|
12901
|
+
}],
|
|
12902
|
+
pendingCycle: null
|
|
12675
12903
|
});
|
|
12676
12904
|
await this.#options.persistence.save(state);
|
|
12677
12905
|
await this.#options.persistence.append({
|
|
@@ -12689,20 +12917,6 @@ var LoopOrchestrator = class {
|
|
|
12689
12917
|
return;
|
|
12690
12918
|
}
|
|
12691
12919
|
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
12920
|
const reason = [evidence.summary, ...guardWarnings].filter(Boolean).join("; ");
|
|
12707
12921
|
state = await this.#terminal(state, "succeeded", reason, now());
|
|
12708
12922
|
yield { type: "loop-terminal", loopId, status: "succeeded", reason };
|
|
@@ -12728,7 +12942,7 @@ ${evidence.summary}`;
|
|
|
12728
12942
|
cycleCheckpoint: state.budget.cycleCheckpoint,
|
|
12729
12943
|
tokenCheckpoint: state.budget.tokenCheckpoint
|
|
12730
12944
|
};
|
|
12731
|
-
const confirmation = await this.#options.confirmBudget(state, decision.dimensions,
|
|
12945
|
+
const confirmation = await this.#options.confirmBudget(state, decision.dimensions, signal);
|
|
12732
12946
|
if (confirmation === "unavailable") {
|
|
12733
12947
|
const reason = "Loop budget extension requires user confirmation.";
|
|
12734
12948
|
state = await this.#terminal(state, "needs_human", reason, now());
|
|
@@ -12755,8 +12969,9 @@ ${evidence.summary}`;
|
|
|
12755
12969
|
}
|
|
12756
12970
|
}
|
|
12757
12971
|
} catch (error) {
|
|
12758
|
-
|
|
12759
|
-
const
|
|
12972
|
+
if (memoryRotationActive()) return;
|
|
12973
|
+
const status = signal.aborted ? "cancelled" : "failed";
|
|
12974
|
+
const reason = signal.aborted ? "Loop cancelled by user." : message(error);
|
|
12760
12975
|
state = await this.#terminal(state, status, reason, now());
|
|
12761
12976
|
yield { type: "loop-terminal", loopId, status, reason };
|
|
12762
12977
|
}
|
|
@@ -12774,6 +12989,16 @@ ${evidence.summary}`;
|
|
|
12774
12989
|
return state;
|
|
12775
12990
|
}
|
|
12776
12991
|
};
|
|
12992
|
+
function countTrailingFailureSignature(cycles, signature) {
|
|
12993
|
+
let count = 0;
|
|
12994
|
+
for (let index = cycles.length - 1; index >= 0; index -= 1) {
|
|
12995
|
+
const cycle = cycles[index];
|
|
12996
|
+
if (`${cycle.workspaceFingerprint}
|
|
12997
|
+
${cycle.verification.summary}` !== signature) break;
|
|
12998
|
+
count += 1;
|
|
12999
|
+
}
|
|
13000
|
+
return count;
|
|
13001
|
+
}
|
|
12777
13002
|
|
|
12778
13003
|
// src/goal/orchestrator.ts
|
|
12779
13004
|
import { createHash as createHash11 } from "crypto";
|
|
@@ -12841,6 +13066,11 @@ var GoalStateSchema = z18.object({
|
|
|
12841
13066
|
planPath: z18.string().nullable(),
|
|
12842
13067
|
verifyRounds: z18.number().int().min(0),
|
|
12843
13068
|
workerRounds: z18.number().int().min(0),
|
|
13069
|
+
/** Worker output checkpoint used to resume verification without rerunning tools. */
|
|
13070
|
+
pendingVerification: z18.object({
|
|
13071
|
+
round: z18.number().int().positive(),
|
|
13072
|
+
finalResponse: z18.string().max(16e3)
|
|
13073
|
+
}).strict().nullable().optional(),
|
|
12844
13074
|
lastGaps: z18.array(GapSchema),
|
|
12845
13075
|
gapFingerprint: z18.string(),
|
|
12846
13076
|
stallStreak: z18.number().int().min(0),
|
|
@@ -12886,6 +13116,7 @@ ${objective}`
|
|
|
12886
13116
|
].join("\n");
|
|
12887
13117
|
}
|
|
12888
13118
|
async function runPlanner(options) {
|
|
13119
|
+
await options.onProgress?.();
|
|
12889
13120
|
const { adapter, model } = options.registry.get(options.modelId);
|
|
12890
13121
|
const prompt = buildPlannerPrompt(options.objective);
|
|
12891
13122
|
const messages = [{ role: "user", content: prompt }];
|
|
@@ -12897,6 +13128,7 @@ async function runPlanner(options) {
|
|
|
12897
13128
|
tools: [],
|
|
12898
13129
|
...options.signal === void 0 ? {} : { signal: options.signal }
|
|
12899
13130
|
})) {
|
|
13131
|
+
await options.onProgress?.();
|
|
12900
13132
|
if (event.type === "text") text += event.text;
|
|
12901
13133
|
else if (event.type === "error") throw event.error;
|
|
12902
13134
|
else if (event.type === "done") {
|
|
@@ -13003,7 +13235,7 @@ async function runClassifier(evidence, plan, options) {
|
|
|
13003
13235
|
const skepticResults = await Promise.all(
|
|
13004
13236
|
Array.from(
|
|
13005
13237
|
{ length: n },
|
|
13006
|
-
(_, i) => runOneSkeptic(evidence, plan, i + 1, adapter, model, options.signal)
|
|
13238
|
+
(_, i) => runOneSkeptic(evidence, plan, i + 1, adapter, model, options.signal, options.onProgress)
|
|
13007
13239
|
)
|
|
13008
13240
|
);
|
|
13009
13241
|
const verdicts = [];
|
|
@@ -13020,7 +13252,8 @@ async function runClassifier(evidence, plan, options) {
|
|
|
13020
13252
|
}
|
|
13021
13253
|
return aggregate(verdicts);
|
|
13022
13254
|
}
|
|
13023
|
-
async function runOneSkeptic(evidence, plan, index, adapter, model, signal) {
|
|
13255
|
+
async function runOneSkeptic(evidence, plan, index, adapter, model, signal, onProgress) {
|
|
13256
|
+
await onProgress?.();
|
|
13024
13257
|
const prompt = buildSkepticPrompt(evidence, plan, index);
|
|
13025
13258
|
const messages = [{ role: "user", content: prompt }];
|
|
13026
13259
|
let text = "";
|
|
@@ -13030,6 +13263,7 @@ async function runOneSkeptic(evidence, plan, index, adapter, model, signal) {
|
|
|
13030
13263
|
tools: [],
|
|
13031
13264
|
...signal === void 0 ? {} : { signal }
|
|
13032
13265
|
})) {
|
|
13266
|
+
await onProgress?.();
|
|
13033
13267
|
if (event.type === "text") text += event.text;
|
|
13034
13268
|
else if (event.type === "error") throw event.error;
|
|
13035
13269
|
else if (event.type === "done") break;
|
|
@@ -13184,7 +13418,7 @@ var GoalOrchestrator = class {
|
|
|
13184
13418
|
async *run(request) {
|
|
13185
13419
|
const timestamp2 = () => this.#options.now?.() ?? (/* @__PURE__ */ new Date()).toISOString();
|
|
13186
13420
|
const createdAt = timestamp2();
|
|
13187
|
-
|
|
13421
|
+
const state = {
|
|
13188
13422
|
id: this.#options.idFactory?.() ?? `goal-${Date.now().toString(36)}`,
|
|
13189
13423
|
objective: request.goal,
|
|
13190
13424
|
phase: "planning",
|
|
@@ -13193,6 +13427,7 @@ var GoalOrchestrator = class {
|
|
|
13193
13427
|
planPath: null,
|
|
13194
13428
|
verifyRounds: 0,
|
|
13195
13429
|
workerRounds: 0,
|
|
13430
|
+
pendingVerification: null,
|
|
13196
13431
|
lastGaps: [],
|
|
13197
13432
|
gapFingerprint: "",
|
|
13198
13433
|
stallStreak: 0,
|
|
@@ -13201,87 +13436,147 @@ var GoalOrchestrator = class {
|
|
|
13201
13436
|
createdAt,
|
|
13202
13437
|
updatedAt: createdAt
|
|
13203
13438
|
};
|
|
13439
|
+
yield* this.#execute(state, request.signal);
|
|
13440
|
+
}
|
|
13441
|
+
/**
|
|
13442
|
+
* Continue a goal whose process rotated onto a fresh heap. The persisted
|
|
13443
|
+
* state decides where to pick up; an interrupted round simply runs again.
|
|
13444
|
+
*/
|
|
13445
|
+
async *resume(request) {
|
|
13446
|
+
const persistence = this.#options.persistence;
|
|
13447
|
+
if (persistence === void 0 || persistence.load === void 0) {
|
|
13448
|
+
throw new Error("Goal persistence does not support resume");
|
|
13449
|
+
}
|
|
13450
|
+
let state;
|
|
13451
|
+
try {
|
|
13452
|
+
state = await persistence.load(request.goalId);
|
|
13453
|
+
} catch (error) {
|
|
13454
|
+
yield { type: "goal-failed", reason: message(error) };
|
|
13455
|
+
return;
|
|
13456
|
+
}
|
|
13457
|
+
if (state.status !== "active" && state.status !== "not_achieved") {
|
|
13458
|
+
yield { type: "goal-failed", reason: `Goal "${state.id}" is ${state.status}; it cannot be resumed.` };
|
|
13459
|
+
return;
|
|
13460
|
+
}
|
|
13461
|
+
yield { type: "goal-resumed", goalId: state.id, round: resumeStartRound(state) };
|
|
13462
|
+
yield* this.#execute(state, request.signal);
|
|
13463
|
+
}
|
|
13464
|
+
async *#execute(initialState, signal) {
|
|
13465
|
+
const timestamp2 = () => this.#options.now?.() ?? (/* @__PURE__ */ new Date()).toISOString();
|
|
13466
|
+
let state = initialState;
|
|
13204
13467
|
const persistState = async (patch) => {
|
|
13205
13468
|
state = { ...state, ...patch, updatedAt: timestamp2() };
|
|
13206
13469
|
await this.#options.persistence?.save(state);
|
|
13207
13470
|
};
|
|
13471
|
+
const memoryCheckpoint = async () => {
|
|
13472
|
+
await this.#options.onMemoryCheckpoint?.(state.id);
|
|
13473
|
+
if (memoryRotationActive()) throw new Error("Heap rotation requested");
|
|
13474
|
+
};
|
|
13208
13475
|
await persistState();
|
|
13209
13476
|
const workspace = this.#options.workspace;
|
|
13210
13477
|
let plan;
|
|
13211
13478
|
let planPath;
|
|
13212
|
-
|
|
13213
|
-
request.signal.throwIfAborted();
|
|
13214
|
-
plan = await runPlanner({
|
|
13215
|
-
registry: this.#options.registry,
|
|
13216
|
-
modelId: this.#options.plannerModelId,
|
|
13217
|
-
objective: request.goal,
|
|
13218
|
-
signal: request.signal
|
|
13219
|
-
});
|
|
13220
|
-
planPath = await writePlanFile(workspace, plan);
|
|
13221
|
-
await persistState({ plan, planPath, contractHash: contractHash(request.goal, plan) });
|
|
13222
|
-
yield { type: "goal-plan-created", plan, planPath };
|
|
13223
|
-
} catch (error) {
|
|
13224
|
-
const reason = `Goal planning failed: ${message(error)}`;
|
|
13225
|
-
await persistState({ phase: "complete", status: "failed" });
|
|
13226
|
-
yield { type: "goal-plan-failed", reason };
|
|
13227
|
-
yield { type: "goal-failed", reason };
|
|
13228
|
-
return;
|
|
13229
|
-
}
|
|
13230
|
-
let priorGaps = [];
|
|
13231
|
-
let priorFingerprint = "";
|
|
13232
|
-
let stallStreak = 0;
|
|
13233
|
-
for (let round = 1; round <= this.#options.maxRounds; round++) {
|
|
13234
|
-
request.signal.throwIfAborted();
|
|
13235
|
-
const workerPrompt = buildWorkerPrompt(request.goal, plan, priorGaps, round);
|
|
13236
|
-
await persistState({
|
|
13237
|
-
phase: "executing",
|
|
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;
|
|
13479
|
+
if (state.plan === null) {
|
|
13247
13480
|
try {
|
|
13248
|
-
|
|
13249
|
-
|
|
13250
|
-
|
|
13251
|
-
|
|
13252
|
-
|
|
13253
|
-
|
|
13254
|
-
signal
|
|
13255
|
-
|
|
13256
|
-
|
|
13257
|
-
|
|
13258
|
-
|
|
13259
|
-
|
|
13260
|
-
break;
|
|
13261
|
-
}
|
|
13262
|
-
}
|
|
13481
|
+
signal.throwIfAborted();
|
|
13482
|
+
await memoryCheckpoint();
|
|
13483
|
+
plan = await runPlanner({
|
|
13484
|
+
registry: this.#options.registry,
|
|
13485
|
+
modelId: this.#options.plannerModelId,
|
|
13486
|
+
objective: state.objective,
|
|
13487
|
+
signal,
|
|
13488
|
+
onProgress: memoryCheckpoint
|
|
13489
|
+
});
|
|
13490
|
+
planPath = await writePlanFile(workspace, plan);
|
|
13491
|
+
await persistState({ phase: "executing", plan, planPath, contractHash: contractHash(state.objective, plan) });
|
|
13492
|
+
yield { type: "goal-plan-created", plan, planPath };
|
|
13263
13493
|
} catch (error) {
|
|
13264
|
-
|
|
13265
|
-
|
|
13266
|
-
if (workerError !== void 0) {
|
|
13267
|
-
const reason = `Worker error in round ${round}: ${workerError}`;
|
|
13494
|
+
if (memoryRotationActive()) return;
|
|
13495
|
+
const reason = `Goal planning failed: ${message(error)}`;
|
|
13268
13496
|
await persistState({ phase: "complete", status: "failed" });
|
|
13497
|
+
yield { type: "goal-plan-failed", reason };
|
|
13269
13498
|
yield { type: "goal-failed", reason };
|
|
13270
13499
|
return;
|
|
13271
13500
|
}
|
|
13272
|
-
|
|
13501
|
+
} else {
|
|
13502
|
+
plan = state.plan;
|
|
13503
|
+
planPath = state.planPath ?? await writePlanFile(workspace, plan);
|
|
13504
|
+
await persistState({ phase: "executing", status: "active", ...state.planPath === null ? { planPath } : {} });
|
|
13505
|
+
}
|
|
13506
|
+
let priorGaps = state.lastGaps;
|
|
13507
|
+
let priorFingerprint = state.gapFingerprint;
|
|
13508
|
+
let stallStreak = state.stallStreak;
|
|
13509
|
+
const startRound = resumeStartRound(state);
|
|
13510
|
+
for (let round = startRound; round <= this.#options.maxRounds; round++) {
|
|
13511
|
+
signal.throwIfAborted();
|
|
13512
|
+
await this.#options.onRoundBoundary?.(state.id);
|
|
13513
|
+
if (memoryRotationActive()) return;
|
|
13514
|
+
const pendingVerification = state.pendingVerification?.round === round ? state.pendingVerification : void 0;
|
|
13515
|
+
let finalResponse = pendingVerification?.finalResponse ?? "";
|
|
13516
|
+
if (pendingVerification === void 0) {
|
|
13517
|
+
const workerPrompt = buildWorkerPrompt(state.objective, plan, priorGaps, round);
|
|
13518
|
+
await persistState({
|
|
13519
|
+
phase: "executing",
|
|
13520
|
+
status: "active",
|
|
13521
|
+
workerRounds: round,
|
|
13522
|
+
pendingVerification: null,
|
|
13523
|
+
lastGaps: priorGaps,
|
|
13524
|
+
gapFingerprint: priorFingerprint,
|
|
13525
|
+
stallStreak
|
|
13526
|
+
});
|
|
13527
|
+
yield { type: "goal-worker-start", round };
|
|
13528
|
+
let workerError;
|
|
13529
|
+
let workerMemoryPressure = false;
|
|
13530
|
+
try {
|
|
13531
|
+
for await (const event of this.#options.runWorker({
|
|
13532
|
+
goal: state.objective,
|
|
13533
|
+
round,
|
|
13534
|
+
workspace,
|
|
13535
|
+
prompt: workerPrompt,
|
|
13536
|
+
priorGaps: formatPriorGaps(priorGaps),
|
|
13537
|
+
signal
|
|
13538
|
+
})) {
|
|
13539
|
+
yield { type: "goal-worker-event", round, event };
|
|
13540
|
+
if (event.type === "text") finalResponse = `${finalResponse}${event.text}`.slice(-16e3);
|
|
13541
|
+
if (event.type === "error") {
|
|
13542
|
+
workerError = event.error.message;
|
|
13543
|
+
if (event.error.code === "memory_pressure") workerMemoryPressure = true;
|
|
13544
|
+
break;
|
|
13545
|
+
}
|
|
13546
|
+
}
|
|
13547
|
+
} catch (error) {
|
|
13548
|
+
if (memoryRotationActive()) return;
|
|
13549
|
+
workerError = message(error);
|
|
13550
|
+
}
|
|
13551
|
+
if (workerMemoryPressure) return;
|
|
13552
|
+
if (workerError !== void 0) {
|
|
13553
|
+
const reason = `Worker error in round ${round}: ${workerError}`;
|
|
13554
|
+
await persistState({ phase: "complete", status: "failed" });
|
|
13555
|
+
yield { type: "goal-failed", reason };
|
|
13556
|
+
return;
|
|
13557
|
+
}
|
|
13558
|
+
await persistState({
|
|
13559
|
+
phase: "verifying",
|
|
13560
|
+
status: "active",
|
|
13561
|
+
pendingVerification: { round, finalResponse }
|
|
13562
|
+
});
|
|
13563
|
+
}
|
|
13273
13564
|
yield { type: "goal-verification-start", round };
|
|
13274
|
-
|
|
13565
|
+
await this.#options.onMemoryCheckpoint?.(state.id);
|
|
13566
|
+
if (memoryRotationActive()) return;
|
|
13567
|
+
const hostVerification = await this.#options.verifyHost?.(signal);
|
|
13568
|
+
await this.#options.onMemoryCheckpoint?.(state.id);
|
|
13569
|
+
if (memoryRotationActive()) return;
|
|
13275
13570
|
const evidence = await collectEvidence(
|
|
13276
13571
|
workspace,
|
|
13277
|
-
|
|
13572
|
+
state.objective,
|
|
13278
13573
|
finalResponse.slice(-4e3),
|
|
13279
13574
|
// Last 4000 chars of response
|
|
13280
13575
|
formatPriorGaps(priorGaps),
|
|
13281
13576
|
state.contractHash,
|
|
13282
13577
|
hostVerification
|
|
13283
13578
|
);
|
|
13284
|
-
await persistState({ evidenceRounds: [...state.evidenceRounds, {
|
|
13579
|
+
await persistState({ evidenceRounds: [...state.evidenceRounds.filter((item) => item.round !== round), {
|
|
13285
13580
|
round,
|
|
13286
13581
|
workspaceDiffHash: evidence.workspaceDiffHash,
|
|
13287
13582
|
...hostVerification === void 0 ? {} : { hostVerification }
|
|
@@ -13305,14 +13600,17 @@ var GoalOrchestrator = class {
|
|
|
13305
13600
|
modelId: this.#options.classifierModelId,
|
|
13306
13601
|
skepticCount: this.#options.skepticCount,
|
|
13307
13602
|
workspace,
|
|
13308
|
-
signal
|
|
13603
|
+
signal,
|
|
13604
|
+
onProgress: memoryCheckpoint
|
|
13309
13605
|
});
|
|
13310
13606
|
} catch (error) {
|
|
13607
|
+
if (memoryRotationActive()) return;
|
|
13311
13608
|
outcome = {
|
|
13312
13609
|
type: "blocked",
|
|
13313
13610
|
reason: `Classifier infrastructure error: ${message(error)}`
|
|
13314
13611
|
};
|
|
13315
13612
|
}
|
|
13613
|
+
await persistState({ verifyRounds: round, pendingVerification: null });
|
|
13316
13614
|
yield {
|
|
13317
13615
|
type: "goal-verdict",
|
|
13318
13616
|
round,
|
|
@@ -13372,6 +13670,12 @@ var GoalOrchestrator = class {
|
|
|
13372
13670
|
function contractHash(objective, plan) {
|
|
13373
13671
|
return createHash11("sha256").update(JSON.stringify({ objective, plan })).digest("hex");
|
|
13374
13672
|
}
|
|
13673
|
+
function resumeStartRound(state) {
|
|
13674
|
+
if (state.pendingVerification !== void 0 && state.pendingVerification !== null) {
|
|
13675
|
+
return state.pendingVerification.round;
|
|
13676
|
+
}
|
|
13677
|
+
return state.verifyRounds >= state.workerRounds ? state.workerRounds + 1 : Math.max(state.workerRounds, 1);
|
|
13678
|
+
}
|
|
13375
13679
|
async function writePlanFile(workspace, plan) {
|
|
13376
13680
|
const dir = join17(workspace, ".flavor");
|
|
13377
13681
|
const planPath = join17(dir, "goal-plan.md");
|
|
@@ -13451,7 +13755,7 @@ function formatPriorGaps(gaps) {
|
|
|
13451
13755
|
// src/goal/store.ts
|
|
13452
13756
|
import { constants as constants4 } from "fs";
|
|
13453
13757
|
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";
|
|
13758
|
+
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
13759
|
import { dirname as dirname10, join as join18, relative as relative11, resolve as resolve14, sep as sep11 } from "path";
|
|
13456
13760
|
var DEFAULT_MAX_BYTES = 1024 * 1024;
|
|
13457
13761
|
var GoalStore = class {
|
|
@@ -13488,6 +13792,28 @@ var GoalStore = class {
|
|
|
13488
13792
|
await rm4(temporary, { force: true }).catch(() => void 0);
|
|
13489
13793
|
}
|
|
13490
13794
|
}
|
|
13795
|
+
/** Reload a persisted goal so a rotated process can resume it. */
|
|
13796
|
+
async load(goalId) {
|
|
13797
|
+
if (!/^[A-Za-z0-9][A-Za-z0-9._-]{0,127}$/.test(goalId)) {
|
|
13798
|
+
throw new Error(`Goal "${goalId}" was not found`);
|
|
13799
|
+
}
|
|
13800
|
+
await assertNoSymlink(this.#workspace, dirname10(this.#root));
|
|
13801
|
+
await assertNoSymlink(this.#workspace, this.#root);
|
|
13802
|
+
const target = join18(this.#root, `${goalId}.json`);
|
|
13803
|
+
try {
|
|
13804
|
+
const metadata = await stat6(target);
|
|
13805
|
+
if (!metadata.isFile() || metadata.size > this.#maxBytes) {
|
|
13806
|
+
throw new Error("Goal state is not a bounded regular file");
|
|
13807
|
+
}
|
|
13808
|
+
const raw = await readFile11(target, "utf8");
|
|
13809
|
+
return GoalStateSchema.parse(JSON.parse(raw));
|
|
13810
|
+
} catch (error) {
|
|
13811
|
+
if (isCode3(error, "ENOENT")) throw new Error(`Goal "${goalId}" was not found`);
|
|
13812
|
+
const quarantine = `${target}.corrupt-${Date.now()}-${randomUUID11().slice(0, 8)}`;
|
|
13813
|
+
await rename4(target, quarantine).catch(() => void 0);
|
|
13814
|
+
throw new Error(`Goal "${goalId}" is corrupt and was quarantined`);
|
|
13815
|
+
}
|
|
13816
|
+
}
|
|
13491
13817
|
};
|
|
13492
13818
|
async function assertNoSymlink(root, target) {
|
|
13493
13819
|
let cursor = resolve14(target);
|
|
@@ -13514,7 +13840,7 @@ function isCode3(error, code) {
|
|
|
13514
13840
|
}
|
|
13515
13841
|
|
|
13516
13842
|
// src/loop/isolation.ts
|
|
13517
|
-
import { appendFile as appendFile2, mkdir as mkdir12, readFile as
|
|
13843
|
+
import { appendFile as appendFile2, mkdir as mkdir12, readFile as readFile12 } from "fs/promises";
|
|
13518
13844
|
import { join as join19, resolve as resolve15 } from "path";
|
|
13519
13845
|
var CODE_CHANGE = /\b(?:fix|implement|add|update|change|modify|refactor|build|create|remove|delete|write|migrate)\b|修复|实现|添加|更新|修改|重构|创建|删除|迁移/i;
|
|
13520
13846
|
var READ_ONLY = /\b(?:review|analy[sz]e|inspect|explain|research|investigate|audit|summari[sz]e|report)\b|调研|分析|审查|检查|解释|研究|总结|报告/i;
|
|
@@ -13574,7 +13900,7 @@ async function ensureWorktreeDirectoryIgnored(root) {
|
|
|
13574
13900
|
await mkdir12(infoDirectory, { recursive: true });
|
|
13575
13901
|
let existing = "";
|
|
13576
13902
|
try {
|
|
13577
|
-
existing = await
|
|
13903
|
+
existing = await readFile12(excludePath, "utf8");
|
|
13578
13904
|
} catch (error) {
|
|
13579
13905
|
if (!(typeof error === "object" && error !== null && "code" in error && error.code === "ENOENT")) throw error;
|
|
13580
13906
|
}
|
|
@@ -13585,7 +13911,7 @@ async function ensureWorktreeDirectoryIgnored(root) {
|
|
|
13585
13911
|
|
|
13586
13912
|
// src/loop/store.ts
|
|
13587
13913
|
import { constants as constants5 } from "fs";
|
|
13588
|
-
import { lstat as lstat8, mkdir as mkdir13, open as open7, readFile as
|
|
13914
|
+
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
13915
|
import { dirname as dirname11, join as join20, relative as relative12, resolve as resolve16, sep as sep12 } from "path";
|
|
13590
13916
|
import { randomUUID as randomUUID12 } from "crypto";
|
|
13591
13917
|
var LOOP_ID = /^[A-Za-z0-9][A-Za-z0-9._-]{0,127}$/;
|
|
@@ -13636,9 +13962,9 @@ var LoopStore = class {
|
|
|
13636
13962
|
const directory = await this.#safeExisting(loopId);
|
|
13637
13963
|
const target = join20(directory, "state.json");
|
|
13638
13964
|
try {
|
|
13639
|
-
const metadata = await
|
|
13965
|
+
const metadata = await stat7(target);
|
|
13640
13966
|
if (!metadata.isFile() || metadata.size > this.#maxBytes) throw new Error("Loop state is not a bounded regular file");
|
|
13641
|
-
const raw = await
|
|
13967
|
+
const raw = await readFile13(target, "utf8");
|
|
13642
13968
|
const state = LoopStateSchema.parse(JSON.parse(raw));
|
|
13643
13969
|
await this.#assertWorkspace(state.workspace);
|
|
13644
13970
|
return state;
|
|
@@ -13727,7 +14053,7 @@ function isCode4(error, code) {
|
|
|
13727
14053
|
}
|
|
13728
14054
|
|
|
13729
14055
|
// src/loop/verifier.ts
|
|
13730
|
-
import { readFile as
|
|
14056
|
+
import { readFile as readFile14 } from "fs/promises";
|
|
13731
14057
|
import { join as join21 } from "path";
|
|
13732
14058
|
var SCRIPT_PRIORITY = ["test", "typecheck", "lint", "build", "smoke:install"];
|
|
13733
14059
|
async function inferVerificationPlan(workspace) {
|
|
@@ -13806,7 +14132,7 @@ function isUnconditionalPassThrough(script) {
|
|
|
13806
14132
|
}
|
|
13807
14133
|
async function optionalText(path) {
|
|
13808
14134
|
try {
|
|
13809
|
-
return await
|
|
14135
|
+
return await readFile14(path, "utf8");
|
|
13810
14136
|
} catch (error) {
|
|
13811
14137
|
if (isCode5(error, "ENOENT")) return void 0;
|
|
13812
14138
|
throw error;
|
|
@@ -13827,7 +14153,7 @@ function isCode5(error, code) {
|
|
|
13827
14153
|
|
|
13828
14154
|
// src/models/anthropic.ts
|
|
13829
14155
|
import Anthropic from "@anthropic-ai/sdk";
|
|
13830
|
-
import { readFile as
|
|
14156
|
+
import { readFile as readFile15 } from "fs/promises";
|
|
13831
14157
|
|
|
13832
14158
|
// src/utils/json.ts
|
|
13833
14159
|
function safeParseJson(input) {
|
|
@@ -14064,7 +14390,7 @@ var AnthropicModelAdapter = class {
|
|
|
14064
14390
|
source: {
|
|
14065
14391
|
type: "base64",
|
|
14066
14392
|
media_type: block.mediaType,
|
|
14067
|
-
data: (await
|
|
14393
|
+
data: (await readFile15(block.source.path)).toString("base64")
|
|
14068
14394
|
}
|
|
14069
14395
|
}));
|
|
14070
14396
|
if (message2.cacheBreakpoint && Array.isArray(content) && content.length > 0) {
|
|
@@ -14284,7 +14610,7 @@ function resolveCacheProfile(input) {
|
|
|
14284
14610
|
|
|
14285
14611
|
// src/models/openai.ts
|
|
14286
14612
|
import OpenAI from "openai";
|
|
14287
|
-
import { readFile as
|
|
14613
|
+
import { readFile as readFile16 } from "fs/promises";
|
|
14288
14614
|
async function toInput(message2) {
|
|
14289
14615
|
if (message2.role === "tool") {
|
|
14290
14616
|
if (!message2.toolCallId) throw new Error("Tool messages require toolCallId");
|
|
@@ -14296,7 +14622,7 @@ async function toInput(message2) {
|
|
|
14296
14622
|
}
|
|
14297
14623
|
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
14624
|
type: "input_image",
|
|
14299
|
-
image_url: `data:${block.mediaType};base64,${(await
|
|
14625
|
+
image_url: `data:${block.mediaType};base64,${(await readFile16(block.source.path)).toString("base64")}`,
|
|
14300
14626
|
detail: "auto"
|
|
14301
14627
|
}));
|
|
14302
14628
|
return [
|
|
@@ -15127,7 +15453,7 @@ function isStoredToken(value) {
|
|
|
15127
15453
|
}
|
|
15128
15454
|
|
|
15129
15455
|
// src/permissions/policy.ts
|
|
15130
|
-
import { readFile as
|
|
15456
|
+
import { readFile as readFile17 } from "fs/promises";
|
|
15131
15457
|
import { join as join22, resolve as resolve18 } from "path";
|
|
15132
15458
|
import { z as z20 } from "zod";
|
|
15133
15459
|
var DecisionSchema2 = z20.enum(["allow", "ask", "deny"]);
|
|
@@ -15173,7 +15499,7 @@ async function loadPermissionPolicy(options) {
|
|
|
15173
15499
|
for (const source of sources) {
|
|
15174
15500
|
let raw;
|
|
15175
15501
|
try {
|
|
15176
|
-
raw = await
|
|
15502
|
+
raw = await readFile17(source.path, "utf8");
|
|
15177
15503
|
} catch (error) {
|
|
15178
15504
|
if (isCode6(error, "ENOENT")) continue;
|
|
15179
15505
|
throw error;
|
|
@@ -15939,7 +16265,7 @@ function createSkillResourceTool(registry) {
|
|
|
15939
16265
|
|
|
15940
16266
|
// src/session/store.ts
|
|
15941
16267
|
import { constants as constants7 } from "fs";
|
|
15942
|
-
import { lstat as lstat10, mkdir as mkdir14, open as open8, readFile as
|
|
16268
|
+
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
16269
|
import { basename as basename4, dirname as dirname13, join as join23, relative as relative14, resolve as resolve20, sep as sep14 } from "path";
|
|
15944
16270
|
import { randomUUID as randomUUID13 } from "crypto";
|
|
15945
16271
|
import { z as z22 } from "zod";
|
|
@@ -16137,7 +16463,7 @@ var SessionStore = class {
|
|
|
16137
16463
|
release: async () => {
|
|
16138
16464
|
if (released) return;
|
|
16139
16465
|
try {
|
|
16140
|
-
if (await
|
|
16466
|
+
if (await readFile18(path, "utf8") === token) await rm6(path, { force: true });
|
|
16141
16467
|
} catch (error) {
|
|
16142
16468
|
if (!isCode7(error, "ENOENT")) throw error;
|
|
16143
16469
|
}
|
|
@@ -16331,14 +16657,14 @@ function storedSessionVersion(raw) {
|
|
|
16331
16657
|
}
|
|
16332
16658
|
async function reclaimStaleSessionLock(path) {
|
|
16333
16659
|
try {
|
|
16334
|
-
const token = await
|
|
16335
|
-
const metadata = await
|
|
16660
|
+
const token = await readFile18(path, "utf8");
|
|
16661
|
+
const metadata = await stat8(path);
|
|
16336
16662
|
const owner2 = parseSessionLockOwner(token);
|
|
16337
16663
|
if (owner2 !== void 0 && isProcessAlive(owner2)) return owner2;
|
|
16338
16664
|
if (owner2 === void 0 && Date.now() - metadata.mtimeMs <= INVALID_SESSION_LOCK_STALE_MS) {
|
|
16339
16665
|
throw new Error(`Session writer lock ${path} is incomplete or invalid; retry after the owning process exits`);
|
|
16340
16666
|
}
|
|
16341
|
-
if (await
|
|
16667
|
+
if (await readFile18(path, "utf8") !== token) return void 0;
|
|
16342
16668
|
const stale = `${path}.stale-${process.pid}-${randomUUID13()}`;
|
|
16343
16669
|
try {
|
|
16344
16670
|
await rename6(path, stale);
|
|
@@ -16371,10 +16697,10 @@ function isProcessAlive(pid) {
|
|
|
16371
16697
|
}
|
|
16372
16698
|
}
|
|
16373
16699
|
async function boundedRead(path, maxBytes) {
|
|
16374
|
-
const metadata = await
|
|
16700
|
+
const metadata = await stat8(path);
|
|
16375
16701
|
if (!metadata.isFile()) throw new Error("Session path is not a regular file");
|
|
16376
16702
|
if (metadata.size > maxBytes) throw new Error(`Session exceeds maximum read size of ${maxBytes} bytes`);
|
|
16377
|
-
const value = await
|
|
16703
|
+
const value = await readFile18(path);
|
|
16378
16704
|
if (value.byteLength > maxBytes) throw new Error(`Session exceeds maximum read size of ${maxBytes} bytes`);
|
|
16379
16705
|
return value.toString("utf8");
|
|
16380
16706
|
}
|
|
@@ -16564,11 +16890,70 @@ function sanitize2(input, seen = /* @__PURE__ */ new WeakSet()) {
|
|
|
16564
16890
|
return output;
|
|
16565
16891
|
}
|
|
16566
16892
|
|
|
16893
|
+
// src/utils/rotation-census.ts
|
|
16894
|
+
import { mkdirSync as mkdirSync3, readdirSync, unlinkSync as unlinkSync2, writeFileSync as writeFileSync2 } from "fs";
|
|
16895
|
+
import { join as join24 } from "path";
|
|
16896
|
+
import { writeHeapSnapshot } from "v8";
|
|
16897
|
+
var CENSUS_PREFIX = "rotation-census-";
|
|
16898
|
+
var SNAPSHOT_PREFIX = "heap-";
|
|
16899
|
+
var KEEP_CENSUS_FILES = 5;
|
|
16900
|
+
var KEEP_SNAPSHOT_FILES = 2;
|
|
16901
|
+
function censusHeapBlock(reading) {
|
|
16902
|
+
const usage = process.memoryUsage();
|
|
16903
|
+
const mb = (bytes) => Math.round(bytes / 1048576);
|
|
16904
|
+
return {
|
|
16905
|
+
heapUsedMb: mb(reading.heapUsed),
|
|
16906
|
+
heapLimitMb: mb(reading.heapLimit),
|
|
16907
|
+
rawRatio: Number(reading.ratio.toFixed(3)),
|
|
16908
|
+
...reading.verifiedRatio === void 0 ? {} : { verifiedRatio: Number(reading.verifiedRatio.toFixed(3)) },
|
|
16909
|
+
gcVerified: reading.gcVerified,
|
|
16910
|
+
rssMb: mb(usage.rss),
|
|
16911
|
+
externalMb: mb(usage.external),
|
|
16912
|
+
arrayBuffersMb: mb(usage.arrayBuffers),
|
|
16913
|
+
...reading.memoryLimit === void 0 ? {} : { memoryLimitMb: mb(reading.memoryLimit) },
|
|
16914
|
+
...reading.rssRatio === void 0 ? {} : { rssRatio: Number(reading.rssRatio.toFixed(3)) }
|
|
16915
|
+
};
|
|
16916
|
+
}
|
|
16917
|
+
function writeRotationCensus(workspace, census) {
|
|
16918
|
+
try {
|
|
16919
|
+
const directory = join24(workspace, ".flavor", "tmp");
|
|
16920
|
+
mkdirSync3(directory, { recursive: true });
|
|
16921
|
+
const path = join24(directory, `${CENSUS_PREFIX}${Date.now()}.json`);
|
|
16922
|
+
writeFileSync2(path, `${JSON.stringify(census, null, 2)}
|
|
16923
|
+
`, { encoding: "utf8", mode: 384 });
|
|
16924
|
+
prune(directory, CENSUS_PREFIX, ".json", KEEP_CENSUS_FILES);
|
|
16925
|
+
return path;
|
|
16926
|
+
} catch {
|
|
16927
|
+
return void 0;
|
|
16928
|
+
}
|
|
16929
|
+
}
|
|
16930
|
+
function writeRotationHeapSnapshot(workspace) {
|
|
16931
|
+
try {
|
|
16932
|
+
const directory = join24(workspace, ".flavor", "tmp");
|
|
16933
|
+
mkdirSync3(directory, { recursive: true });
|
|
16934
|
+
const path = join24(directory, `${SNAPSHOT_PREFIX}${Date.now()}.heapsnapshot`);
|
|
16935
|
+
writeHeapSnapshot(path);
|
|
16936
|
+
prune(directory, SNAPSHOT_PREFIX, ".heapsnapshot", KEEP_SNAPSHOT_FILES);
|
|
16937
|
+
return path;
|
|
16938
|
+
} catch {
|
|
16939
|
+
return void 0;
|
|
16940
|
+
}
|
|
16941
|
+
}
|
|
16942
|
+
function prune(directory, prefix, suffix, keep) {
|
|
16943
|
+
const stale = readdirSync(directory).filter((name) => name.startsWith(prefix) && name.endsWith(suffix)).sort().slice(0, -keep);
|
|
16944
|
+
for (const name of stale) {
|
|
16945
|
+
try {
|
|
16946
|
+
unlinkSync2(join24(directory, name));
|
|
16947
|
+
} catch {
|
|
16948
|
+
}
|
|
16949
|
+
}
|
|
16950
|
+
}
|
|
16951
|
+
|
|
16567
16952
|
// src/sleep/organizer.ts
|
|
16568
16953
|
import { constants as constants8 } from "fs";
|
|
16569
16954
|
import { randomUUID as randomUUID14 } from "crypto";
|
|
16570
16955
|
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
|
|
16956
|
+
import { isAbsolute as isAbsolute10, join as join25, relative as relative15, resolve as resolve21, sep as sep15 } from "path";
|
|
16572
16957
|
import { z as z23 } from "zod";
|
|
16573
16958
|
var DateKeySchema = z23.string().regex(/^\d{4}-\d{2}-\d{2}$/);
|
|
16574
16959
|
var ReviewSchema = z23.object({
|
|
@@ -16623,7 +17008,7 @@ var ProjectSleepOrganizer = class {
|
|
|
16623
17008
|
#generate;
|
|
16624
17009
|
constructor(options) {
|
|
16625
17010
|
this.#workspace = resolve21(options.workspace);
|
|
16626
|
-
this.#sleepDirectory =
|
|
17011
|
+
this.#sleepDirectory = join25(this.#workspace, ".flavor", "sleep");
|
|
16627
17012
|
this.#sessions = options.sessions ?? new SessionStore({ workspace: this.#workspace });
|
|
16628
17013
|
this.#generate = options.generate;
|
|
16629
17014
|
}
|
|
@@ -16632,7 +17017,7 @@ var ProjectSleepOrganizer = class {
|
|
|
16632
17017
|
signal.throwIfAborted();
|
|
16633
17018
|
await this.#prepareDirectory();
|
|
16634
17019
|
if (await this.#hasReport(date)) return { status: "exists", date };
|
|
16635
|
-
const lockPath =
|
|
17020
|
+
const lockPath = join25(this.#sleepDirectory, `.${date}.lock`);
|
|
16636
17021
|
let lock;
|
|
16637
17022
|
try {
|
|
16638
17023
|
lock = await open9(lockPath, constants8.O_CREAT | constants8.O_EXCL | constants8.O_WRONLY, 384);
|
|
@@ -16667,7 +17052,7 @@ Previous output was invalid. Repair it and return strict JSON only.`;
|
|
|
16667
17052
|
}
|
|
16668
17053
|
if (review === void 0) throw lastError;
|
|
16669
17054
|
const filename2 = `${date}-${filenameSummary(review.title)}.md`;
|
|
16670
|
-
const path =
|
|
17055
|
+
const path = join25(this.#sleepDirectory, filename2);
|
|
16671
17056
|
const markdown = renderSleepReport(date, review, documents);
|
|
16672
17057
|
await this.#atomicWrite(path, markdown);
|
|
16673
17058
|
return { status: "written", date, path, sessionCount: documents.length };
|
|
@@ -16709,7 +17094,7 @@ Previous output was invalid. Repair it and return strict JSON only.`;
|
|
|
16709
17094
|
});
|
|
16710
17095
|
}
|
|
16711
17096
|
async #prepareDirectory() {
|
|
16712
|
-
const flavorDirectory =
|
|
17097
|
+
const flavorDirectory = join25(this.#workspace, ".flavor");
|
|
16713
17098
|
await assertNotSymlink(flavorDirectory);
|
|
16714
17099
|
await mkdir15(flavorDirectory, { recursive: true, mode: 448 });
|
|
16715
17100
|
await assertNotSymlink(this.#sleepDirectory);
|
|
@@ -16723,7 +17108,7 @@ Previous output was invalid. Repair it and return strict JSON only.`;
|
|
|
16723
17108
|
return (await readdir8(this.#sleepDirectory, { withFileTypes: true })).some((entry2) => entry2.isFile() && entry2.name.startsWith(prefix) && entry2.name.endsWith(".md"));
|
|
16724
17109
|
}
|
|
16725
17110
|
async #atomicWrite(path, content) {
|
|
16726
|
-
const temporary =
|
|
17111
|
+
const temporary = join25(this.#sleepDirectory, `.${randomUUID14()}.tmp`);
|
|
16727
17112
|
let handle;
|
|
16728
17113
|
try {
|
|
16729
17114
|
handle = await open9(temporary, constants8.O_CREAT | constants8.O_EXCL | constants8.O_WRONLY, 384);
|
|
@@ -17097,7 +17482,7 @@ function computeSessionStats(documents) {
|
|
|
17097
17482
|
|
|
17098
17483
|
// src/tools/files.ts
|
|
17099
17484
|
import { constants as constants9 } from "fs";
|
|
17100
|
-
import { mkdir as mkdir16, open as open10, readFile as
|
|
17485
|
+
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
17486
|
import { dirname as dirname14, isAbsolute as isAbsolute11, relative as relative16, resolve as resolve22, sep as sep16 } from "path";
|
|
17102
17487
|
import { randomUUID as randomUUID15 } from "crypto";
|
|
17103
17488
|
import { z as z24 } from "zod";
|
|
@@ -17250,7 +17635,7 @@ function createReadTool(workspace, options = {}) {
|
|
|
17250
17635
|
execute: async (input, signal) => {
|
|
17251
17636
|
abortIfNeeded(signal);
|
|
17252
17637
|
const path = await guard.existing(input.path);
|
|
17253
|
-
const info = await
|
|
17638
|
+
const info = await stat9(path);
|
|
17254
17639
|
const maxBytes = input.maxBytes ?? MAX_READ_BYTES;
|
|
17255
17640
|
if (!Number.isSafeInteger(maxBytes) || maxBytes <= 0 || maxBytes > MAX_READ_BYTES) {
|
|
17256
17641
|
throw new Error(`maxBytes must be a positive integer no greater than ${MAX_READ_BYTES}`);
|
|
@@ -17459,7 +17844,7 @@ async function atomicWrite(path, content, signal, expectedVersion) {
|
|
|
17459
17844
|
const temporary = `${path}.${randomUUID15()}.tmp`;
|
|
17460
17845
|
let mode;
|
|
17461
17846
|
try {
|
|
17462
|
-
mode = (await
|
|
17847
|
+
mode = (await stat9(path)).mode & 511;
|
|
17463
17848
|
} catch (error) {
|
|
17464
17849
|
if (!isMissing3(error)) throw error;
|
|
17465
17850
|
}
|
|
@@ -17482,7 +17867,7 @@ async function atomicWrite(path, content, signal, expectedVersion) {
|
|
|
17482
17867
|
}
|
|
17483
17868
|
}
|
|
17484
17869
|
async function readText(path) {
|
|
17485
|
-
const contents = await
|
|
17870
|
+
const contents = await readFile19(path);
|
|
17486
17871
|
if (isBinary(contents)) throw new Error("Cannot edit binary file as text");
|
|
17487
17872
|
return decodeFileText(contents);
|
|
17488
17873
|
}
|
|
@@ -17508,7 +17893,7 @@ async function assertMutationVersion(path, expected, observations) {
|
|
|
17508
17893
|
}
|
|
17509
17894
|
async function fileVersion(path) {
|
|
17510
17895
|
try {
|
|
17511
|
-
return versionOfStat(await
|
|
17896
|
+
return versionOfStat(await stat9(path));
|
|
17512
17897
|
} catch (error) {
|
|
17513
17898
|
if (isMissing3(error)) return ABSENT_VERSION;
|
|
17514
17899
|
throw error;
|
|
@@ -17519,7 +17904,7 @@ function versionOfStat(info) {
|
|
|
17519
17904
|
}
|
|
17520
17905
|
async function readOptionalPresentationText(path) {
|
|
17521
17906
|
try {
|
|
17522
|
-
const contents = await
|
|
17907
|
+
const contents = await readFile19(path);
|
|
17523
17908
|
return isBinary(contents) ? { exists: true } : { exists: true, text: contents.toString("utf8") };
|
|
17524
17909
|
} catch (error) {
|
|
17525
17910
|
return isMissing3(error) ? { exists: false } : { exists: true };
|
|
@@ -18171,7 +18556,7 @@ function charset(contentType) {
|
|
|
18171
18556
|
// src/tools/lsp.ts
|
|
18172
18557
|
import { spawn as spawn8 } from "child_process";
|
|
18173
18558
|
import { existsSync as existsSync5 } from "fs";
|
|
18174
|
-
import { access as access2, constants as constants10, readFile as
|
|
18559
|
+
import { access as access2, constants as constants10, readFile as readFile20 } from "fs/promises";
|
|
18175
18560
|
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
18561
|
import { fileURLToPath as fileURLToPath2, pathToFileURL as pathToFileURL2 } from "url";
|
|
18177
18562
|
import { z as z26 } from "zod";
|
|
@@ -18365,7 +18750,7 @@ var LspConnection = class {
|
|
|
18365
18750
|
const url = new URL(uri);
|
|
18366
18751
|
if (url.protocol !== "file:") return;
|
|
18367
18752
|
const filePath = fileURLToPath2(url);
|
|
18368
|
-
const text = await
|
|
18753
|
+
const text = await readFile20(filePath, "utf8");
|
|
18369
18754
|
const current = this.#documents.get(uri);
|
|
18370
18755
|
if (current === void 0) {
|
|
18371
18756
|
this.#documents.set(uri, { text, version: 1 });
|
|
@@ -18731,19 +19116,19 @@ async function changeSummary(workspace) {
|
|
|
18731
19116
|
};
|
|
18732
19117
|
}
|
|
18733
19118
|
async function stagedDiff(workspace, maxChars = 96e3) {
|
|
18734
|
-
const
|
|
19119
|
+
const stat12 = await git2(workspace, ["diff", "--staged", "--stat"]);
|
|
18735
19120
|
const diff = await git2(workspace, ["diff", "--staged", "--no-ext-diff", "--no-color"]);
|
|
18736
19121
|
if (!diff.ok) throw gitFailure(diff, "diff --staged");
|
|
18737
19122
|
const text = diff.stdout;
|
|
18738
19123
|
return {
|
|
18739
19124
|
diff: text.length > maxChars ? `${text.slice(0, maxChars)}
|
|
18740
19125
|
\u2026[diff truncated]` : text,
|
|
18741
|
-
stat:
|
|
19126
|
+
stat: stat12.stdout.trim(),
|
|
18742
19127
|
truncated: text.length > maxChars
|
|
18743
19128
|
};
|
|
18744
19129
|
}
|
|
18745
19130
|
async function uncommittedDiff(workspace, maxChars = 96e3) {
|
|
18746
|
-
const
|
|
19131
|
+
const stat12 = await git2(workspace, ["diff", "HEAD", "--stat"]);
|
|
18747
19132
|
const diff = await git2(workspace, ["diff", "HEAD", "--no-ext-diff", "--no-color"]);
|
|
18748
19133
|
if (!diff.ok && !diff.stderr.includes("unknown revision")) throw gitFailure(diff, "diff HEAD");
|
|
18749
19134
|
const summary2 = await changeSummary(workspace);
|
|
@@ -18751,7 +19136,7 @@ async function uncommittedDiff(workspace, maxChars = 96e3) {
|
|
|
18751
19136
|
return {
|
|
18752
19137
|
diff: text.length > maxChars ? `${text.slice(0, maxChars)}
|
|
18753
19138
|
\u2026[diff truncated]` : text,
|
|
18754
|
-
stat:
|
|
19139
|
+
stat: stat12.stdout.trim(),
|
|
18755
19140
|
untracked: summary2.untrackedFiles
|
|
18756
19141
|
};
|
|
18757
19142
|
}
|
|
@@ -19048,17 +19433,17 @@ ${explanation}` : "";
|
|
|
19048
19433
|
}
|
|
19049
19434
|
|
|
19050
19435
|
// src/desktop/astgraph-service.ts
|
|
19051
|
-
import { stat as
|
|
19436
|
+
import { stat as stat10 } from "fs/promises";
|
|
19052
19437
|
import { createRequire as nodeCreateRequire } from "module";
|
|
19053
|
-
import { join as
|
|
19438
|
+
import { join as join26, resolve as resolve24 } from "path";
|
|
19054
19439
|
var FIELDS = "id, kind, name, qualified_name, file_path, language, start_line, end_line, signature";
|
|
19055
19440
|
var DesktopAstGraphService = class {
|
|
19056
19441
|
#path;
|
|
19057
19442
|
constructor(workspace) {
|
|
19058
|
-
this.#path =
|
|
19443
|
+
this.#path = join26(resolve24(workspace), ".flavor", "astgraph", "index.db");
|
|
19059
19444
|
}
|
|
19060
19445
|
async status() {
|
|
19061
|
-
if ((await
|
|
19446
|
+
if ((await stat10(this.#path).catch(() => void 0))?.isFile() !== true) {
|
|
19062
19447
|
return { available: false, path: this.#path, files: 0, nodes: 0, edges: 0 };
|
|
19063
19448
|
}
|
|
19064
19449
|
return this.#read((db) => {
|
|
@@ -19338,8 +19723,8 @@ function createTodoWriteTool() {
|
|
|
19338
19723
|
|
|
19339
19724
|
// src/tools/managed.ts
|
|
19340
19725
|
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
|
|
19726
|
+
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";
|
|
19727
|
+
import { dirname as dirname16, join as join27, resolve as resolve26 } from "path";
|
|
19343
19728
|
import { z as z31 } from "zod";
|
|
19344
19729
|
var MANAGED_TOOL_VERSION = 1;
|
|
19345
19730
|
var MAX_RECORD_BYTES = 256 * 1024;
|
|
@@ -19382,8 +19767,8 @@ var ManagedToolStore = class {
|
|
|
19382
19767
|
#diagnostics = [];
|
|
19383
19768
|
constructor(options) {
|
|
19384
19769
|
this.#workspace = resolve26(options.workspace);
|
|
19385
|
-
this.#projectRoot =
|
|
19386
|
-
this.#globalRoot =
|
|
19770
|
+
this.#projectRoot = join27(this.#workspace, ".flavor", "tools");
|
|
19771
|
+
this.#globalRoot = join27(resolve26(options.home), ".flavor-code", "tools");
|
|
19387
19772
|
this.#now = options.now ?? (() => (/* @__PURE__ */ new Date()).toISOString());
|
|
19388
19773
|
}
|
|
19389
19774
|
get diagnostics() {
|
|
@@ -19430,7 +19815,7 @@ var ManagedToolStore = class {
|
|
|
19430
19815
|
version: MANAGED_TOOL_VERSION,
|
|
19431
19816
|
createdAt: this.#now()
|
|
19432
19817
|
});
|
|
19433
|
-
const path =
|
|
19818
|
+
const path = join27(this.directory(input.scope), filename(input.name));
|
|
19434
19819
|
const definition = compileDefinition(record, input.scope, this.#workspace);
|
|
19435
19820
|
await writeExclusive(path, `${JSON.stringify(record, void 0, 2)}
|
|
19436
19821
|
`);
|
|
@@ -19455,7 +19840,7 @@ var ManagedToolStore = class {
|
|
|
19455
19840
|
if (!parsed.success) return [];
|
|
19456
19841
|
const matches = this.#matching(parsed.data.name).filter((entry2) => parsed.data.scope === void 0 || entry2.scope === parsed.data.scope);
|
|
19457
19842
|
if (matches.length > 0) return matches.map((entry2) => entry2.path);
|
|
19458
|
-
return [
|
|
19843
|
+
return [join27(this.directory(parsed.data.scope ?? "project"), filename(parsed.data.name))];
|
|
19459
19844
|
}
|
|
19460
19845
|
async #loadDirectory(scope2) {
|
|
19461
19846
|
const root = this.directory(scope2);
|
|
@@ -19468,12 +19853,12 @@ var ManagedToolStore = class {
|
|
|
19468
19853
|
return;
|
|
19469
19854
|
}
|
|
19470
19855
|
for (const name of names) {
|
|
19471
|
-
const path =
|
|
19856
|
+
const path = join27(root, name);
|
|
19472
19857
|
try {
|
|
19473
19858
|
const info = await lstat12(path);
|
|
19474
19859
|
if (!info.isFile() || info.isSymbolicLink()) throw new Error("Managed tool record must be a regular file");
|
|
19475
19860
|
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
|
|
19861
|
+
const record = ManagedToolRecordSchema.parse(JSON.parse(await readFile21(path, "utf8")));
|
|
19477
19862
|
if (record.scope !== scope2) throw new Error(`Managed tool record scope must match its ${scope2} directory`);
|
|
19478
19863
|
const key = scopedKey(scope2, record.name);
|
|
19479
19864
|
if (this.#entries.has(key)) throw new Error(`Duplicate managed tool name in ${scope2} scope: ${record.name}`);
|
|
@@ -20386,8 +20771,8 @@ ${total}`;
|
|
|
20386
20771
|
|
|
20387
20772
|
// src/memory/store.ts
|
|
20388
20773
|
import { createHash as createHash16 } from "crypto";
|
|
20389
|
-
import { readFile as
|
|
20390
|
-
import { resolve as resolve27, join as
|
|
20774
|
+
import { readFile as readFile22, rm as rm9 } from "fs/promises";
|
|
20775
|
+
import { resolve as resolve27, join as join28, relative as relative19, sep as sep19 } from "path";
|
|
20391
20776
|
|
|
20392
20777
|
// src/memory/similarity.ts
|
|
20393
20778
|
function normalizeForSimilarity(value) {
|
|
@@ -20490,9 +20875,9 @@ var MemoryStore = class {
|
|
|
20490
20875
|
if (!Number.isSafeInteger(options.maxEntries) || options.maxEntries < 1) throw new Error("maxEntries must be an integer of at least 1");
|
|
20491
20876
|
if (!Number.isSafeInteger(options.maxEntryChars) || options.maxEntryChars < 1) throw new Error("maxEntryChars must be an integer of at least 1");
|
|
20492
20877
|
this.workspace = resolve27(options.workspace);
|
|
20493
|
-
this.#memoryRoot =
|
|
20494
|
-
this.path =
|
|
20495
|
-
this.#behaviorPath =
|
|
20878
|
+
this.#memoryRoot = join28(this.workspace, ".flavor", "memory");
|
|
20879
|
+
this.path = join28(this.#memoryRoot, "MEMORY.md");
|
|
20880
|
+
this.#behaviorPath = join28(this.#memoryRoot, "behavior.json");
|
|
20496
20881
|
this.#maxEntries = options.maxEntries;
|
|
20497
20882
|
this.#maxEntryChars = options.maxEntryChars;
|
|
20498
20883
|
}
|
|
@@ -20710,7 +21095,7 @@ var MemoryStore = class {
|
|
|
20710
21095
|
});
|
|
20711
21096
|
}
|
|
20712
21097
|
async #readTaskItem(reference) {
|
|
20713
|
-
const document = decodeTask(await
|
|
21098
|
+
const document = decodeTask(await readFile22(this.#resolveContentPath(reference.contentPath), "utf8"));
|
|
20714
21099
|
return document.items.find((item) => item.id === reference.id)?.content ?? reference.summary;
|
|
20715
21100
|
}
|
|
20716
21101
|
#resolveContentPath(contentPath) {
|
|
@@ -21186,8 +21571,8 @@ function createExecutionEnvironment(workspace, config) {
|
|
|
21186
21571
|
}
|
|
21187
21572
|
|
|
21188
21573
|
// src/ide/client.ts
|
|
21189
|
-
import { readFile as
|
|
21190
|
-
import { basename as basename6, join as
|
|
21574
|
+
import { readFile as readFile23, readdir as readdir10, stat as stat11 } from "fs/promises";
|
|
21575
|
+
import { basename as basename6, join as join29, relative as relative20, resolve as resolve28, sep as sep20 } from "path";
|
|
21191
21576
|
var FlavorIdeClient = class {
|
|
21192
21577
|
#workspace;
|
|
21193
21578
|
#home;
|
|
@@ -21283,7 +21668,7 @@ var FlavorIdeClient = class {
|
|
|
21283
21668
|
};
|
|
21284
21669
|
if (await this.#healthy(configured)) return configured;
|
|
21285
21670
|
}
|
|
21286
|
-
const directory =
|
|
21671
|
+
const directory = join29(this.#home, ".flavor-code", "ide");
|
|
21287
21672
|
let entries;
|
|
21288
21673
|
try {
|
|
21289
21674
|
entries = (await readdir10(directory)).filter((name) => name.endsWith(".lock"));
|
|
@@ -21291,9 +21676,9 @@ var FlavorIdeClient = class {
|
|
|
21291
21676
|
return void 0;
|
|
21292
21677
|
}
|
|
21293
21678
|
const candidates = (await Promise.all(entries.map(async (name) => {
|
|
21294
|
-
const path =
|
|
21679
|
+
const path = join29(directory, name);
|
|
21295
21680
|
try {
|
|
21296
|
-
const [contents, info] = await Promise.all([
|
|
21681
|
+
const [contents, info] = await Promise.all([readFile23(path, "utf8"), stat11(path)]);
|
|
21297
21682
|
const lock = parseLockfile(contents);
|
|
21298
21683
|
return lock === void 0 ? void 0 : { lock, modified: info.mtimeMs };
|
|
21299
21684
|
} catch {
|
|
@@ -21412,6 +21797,7 @@ function relativeDisplay(workspace, filePath) {
|
|
|
21412
21797
|
|
|
21413
21798
|
// src/jobs/registry.ts
|
|
21414
21799
|
import { randomUUID as randomUUID17 } from "crypto";
|
|
21800
|
+
var OUTPUT_SLICE_SLACK_CHARS = 64 * 1024;
|
|
21415
21801
|
var JobRegistry = class {
|
|
21416
21802
|
#jobs = /* @__PURE__ */ new Map();
|
|
21417
21803
|
#maxOutputChars;
|
|
@@ -21440,7 +21826,8 @@ var JobRegistry = class {
|
|
|
21440
21826
|
updatedAt: now,
|
|
21441
21827
|
outputChars: 0,
|
|
21442
21828
|
truncated: false,
|
|
21443
|
-
|
|
21829
|
+
chunks: [],
|
|
21830
|
+
windowChars: 0,
|
|
21444
21831
|
outputStart: 0,
|
|
21445
21832
|
settle,
|
|
21446
21833
|
done
|
|
@@ -21451,11 +21838,16 @@ var JobRegistry = class {
|
|
|
21451
21838
|
id,
|
|
21452
21839
|
append: (text) => {
|
|
21453
21840
|
if (record.state !== "running" || text === "") return;
|
|
21454
|
-
record.
|
|
21841
|
+
record.chunks.push(text);
|
|
21842
|
+
record.windowChars += text.length;
|
|
21455
21843
|
record.outputChars += text.length;
|
|
21456
|
-
if (record.
|
|
21457
|
-
|
|
21458
|
-
|
|
21844
|
+
if (record.outputChars > this.#maxOutputChars) record.truncated = true;
|
|
21845
|
+
if (record.windowChars > this.#maxOutputChars + OUTPUT_SLICE_SLACK_CHARS) {
|
|
21846
|
+
const joined = record.chunks.join("");
|
|
21847
|
+
const kept = joined.slice(joined.length - this.#maxOutputChars);
|
|
21848
|
+
const removed = joined.length - kept.length;
|
|
21849
|
+
record.chunks = [kept];
|
|
21850
|
+
record.windowChars = kept.length;
|
|
21459
21851
|
record.outputStart += removed;
|
|
21460
21852
|
record.truncated = true;
|
|
21461
21853
|
}
|
|
@@ -21476,8 +21868,20 @@ var JobRegistry = class {
|
|
|
21476
21868
|
}
|
|
21477
21869
|
read(id, owner2, cursor = 0) {
|
|
21478
21870
|
const job = this.#owned(id, owner2);
|
|
21479
|
-
const
|
|
21480
|
-
|
|
21871
|
+
const window = job.chunks.join("");
|
|
21872
|
+
const dropped = Math.max(0, window.length - this.#maxOutputChars);
|
|
21873
|
+
const effective = Math.max(cursor, job.outputStart + dropped);
|
|
21874
|
+
return { ...snapshot2(job), output: window.slice(effective - job.outputStart), cursor: job.outputStart + window.length };
|
|
21875
|
+
}
|
|
21876
|
+
/** Retained sizes for the rotation census. */
|
|
21877
|
+
census() {
|
|
21878
|
+
let windowChars = 0;
|
|
21879
|
+
let running = 0;
|
|
21880
|
+
for (const job of this.#jobs.values()) {
|
|
21881
|
+
windowChars += job.windowChars;
|
|
21882
|
+
if (job.state === "running") running += 1;
|
|
21883
|
+
}
|
|
21884
|
+
return { jobs: this.#jobs.size, running, windowChars };
|
|
21481
21885
|
}
|
|
21482
21886
|
async wait(id, owner2, signal) {
|
|
21483
21887
|
signal?.throwIfAborted();
|
|
@@ -22705,8 +23109,8 @@ async function createProductionRuntime(options) {
|
|
|
22705
23109
|
const registeredProviders = registration.registered;
|
|
22706
23110
|
let effectiveLlm = registration.effectiveLlm;
|
|
22707
23111
|
const pluginHost = new PluginHost({
|
|
22708
|
-
globalPluginDirs: [
|
|
22709
|
-
projectPluginDirs: [
|
|
23112
|
+
globalPluginDirs: [join30(home, ".flavor-code", "plugins")],
|
|
23113
|
+
projectPluginDirs: [join30(workspace, ".flavor", "plugins")],
|
|
22710
23114
|
config,
|
|
22711
23115
|
// Existing project plugins (including astgraph and superharness) use Node.js
|
|
22712
23116
|
// built-ins. The Worker/vm sandbox deliberately blocks those imports, so it
|
|
@@ -22815,7 +23219,7 @@ async function createProductionRuntime(options) {
|
|
|
22815
23219
|
const address = palSocketAddress({
|
|
22816
23220
|
platform: process.platform,
|
|
22817
23221
|
userScope: home,
|
|
22818
|
-
runtimeDir:
|
|
23222
|
+
runtimeDir: join30(tmpdir4(), `flavor-code-pals-${createHash18("sha256").update(home).digest("hex").slice(0, 16)}`)
|
|
22819
23223
|
});
|
|
22820
23224
|
const baseCollaborationClient = collaboration.client ?? collaboration.createClient?.({
|
|
22821
23225
|
instanceId: collaboration.instanceId,
|
|
@@ -22926,8 +23330,8 @@ async function createProductionRuntime(options) {
|
|
|
22926
23330
|
diagnostics.push(`MCP servers could not start: ${message(error)}`);
|
|
22927
23331
|
});
|
|
22928
23332
|
const skills = new SkillRegistry({
|
|
22929
|
-
globalRoots: [
|
|
22930
|
-
projectRoots: [
|
|
23333
|
+
globalRoots: [join30(home, ".flavor-code", "skills")],
|
|
23334
|
+
projectRoots: [join30(workspace, ".flavor", "skills"), ...pluginSkillRoots],
|
|
22931
23335
|
authorizeResource: async () => true,
|
|
22932
23336
|
disabledNames: config.skills.disabled
|
|
22933
23337
|
});
|
|
@@ -22935,7 +23339,7 @@ async function createProductionRuntime(options) {
|
|
|
22935
23339
|
skillsReady.catch(() => void 0);
|
|
22936
23340
|
tools.push(createSkillTool(skills), createSkillResourceTool(skills));
|
|
22937
23341
|
if (options.extraTools !== void 0) tools.push(...options.extraTools);
|
|
22938
|
-
const flavor = await optionalText2(
|
|
23342
|
+
const flavor = await optionalText2(join30(workspace, "FLAVOR.md"));
|
|
22939
23343
|
const instructionBaseline = await workspaceInstructions.baseline();
|
|
22940
23344
|
let memoryContext;
|
|
22941
23345
|
if (memoryStore !== void 0) {
|
|
@@ -23520,7 +23924,7 @@ async function createProductionRuntime(options) {
|
|
|
23520
23924
|
evolveService.toolDefinition(),
|
|
23521
23925
|
...mcpTools
|
|
23522
23926
|
];
|
|
23523
|
-
const loopFlavor = await optionalText2(
|
|
23927
|
+
const loopFlavor = await optionalText2(join30(input.workspace, "FLAVOR.md"));
|
|
23524
23928
|
const loopInstructionBaseline = await loopInstructions.baseline();
|
|
23525
23929
|
const loopEnvironment = createPromptEnvironment({
|
|
23526
23930
|
now: /* @__PURE__ */ new Date(),
|
|
@@ -23628,12 +24032,83 @@ async function createProductionRuntime(options) {
|
|
|
23628
24032
|
await loopExecutionEnvironment?.dispose();
|
|
23629
24033
|
}
|
|
23630
24034
|
};
|
|
24035
|
+
let activeLongTask;
|
|
24036
|
+
const readMemoryRestartMarker = () => {
|
|
24037
|
+
try {
|
|
24038
|
+
return parseMemoryRestartMarker(readFileSync3(memoryRestartMarkerPath(workspace), "utf8"));
|
|
24039
|
+
} catch {
|
|
24040
|
+
return void 0;
|
|
24041
|
+
}
|
|
24042
|
+
};
|
|
24043
|
+
const currentPressureReading = () => verifiedHeapPressure(MEMORY_PRESSURE_HEAP_RATIO) ?? { ...readHeap(), gcVerified: false };
|
|
24044
|
+
const collectRotationHolders = () => {
|
|
24045
|
+
const holders = {};
|
|
24046
|
+
try {
|
|
24047
|
+
const context = harness.main.context.census();
|
|
24048
|
+
holders.context = { entries: context.messages, chars: context.messageChars };
|
|
24049
|
+
holders.contextVisibility = { entries: context.visibilityEntries, chars: context.visibilityChars };
|
|
24050
|
+
holders.contextTransient = { entries: context.transientEntries, chars: context.transientChars };
|
|
24051
|
+
} catch {
|
|
24052
|
+
}
|
|
24053
|
+
try {
|
|
24054
|
+
holders.journal = { entries: harnessJournal.recordCount };
|
|
24055
|
+
} catch {
|
|
24056
|
+
}
|
|
24057
|
+
try {
|
|
24058
|
+
const jobCensus = jobs.census();
|
|
24059
|
+
holders.jobs = { entries: jobCensus.jobs, chars: jobCensus.windowChars };
|
|
24060
|
+
} catch {
|
|
24061
|
+
}
|
|
24062
|
+
try {
|
|
24063
|
+
holders.sessionTree = { entries: sessionHistory.nodeCount };
|
|
24064
|
+
} catch {
|
|
24065
|
+
}
|
|
24066
|
+
return holders;
|
|
24067
|
+
};
|
|
24068
|
+
const rotateForHeap = (continuation, reading) => {
|
|
24069
|
+
requestMemoryRestart(workspace, sessionId, emitOutput, {
|
|
24070
|
+
...continuation === void 0 ? {} : { continuation },
|
|
24071
|
+
...reading === void 0 ? {} : { reading },
|
|
24072
|
+
holders: collectRotationHolders()
|
|
24073
|
+
}, options.onMemoryRestartRequested);
|
|
24074
|
+
};
|
|
24075
|
+
const maybeRotateAtBoundary = (continuation) => {
|
|
24076
|
+
activeLongTask = continuation;
|
|
24077
|
+
const pressure = verifiedHeapPressure(MEMORY_PRESSURE_HEAP_RATIO);
|
|
24078
|
+
if (pressure !== void 0) {
|
|
24079
|
+
rotateForHeap(continuation, pressure);
|
|
24080
|
+
return;
|
|
24081
|
+
}
|
|
24082
|
+
const reading = heapRotationNeeded();
|
|
24083
|
+
if (reading === void 0) return;
|
|
24084
|
+
if (rotationCooldownActive(readMemoryRestartMarker(), sessionId, /* @__PURE__ */ new Date())) return;
|
|
24085
|
+
rotateForHeap(continuation, reading);
|
|
24086
|
+
};
|
|
24087
|
+
const maybeRotateAtTurnBoundary = () => {
|
|
24088
|
+
const reading = heapRotationNeeded();
|
|
24089
|
+
if (reading === void 0) return;
|
|
24090
|
+
if (rotationCooldownActive(readMemoryRestartMarker(), sessionId, /* @__PURE__ */ new Date())) return;
|
|
24091
|
+
rotateForHeap(void 0, reading);
|
|
24092
|
+
};
|
|
24093
|
+
const consumePendingContinuation = () => {
|
|
24094
|
+
const marker = readMemoryRestartMarker();
|
|
24095
|
+
const continuation = pendingContinuation(marker, sessionId, /* @__PURE__ */ new Date());
|
|
24096
|
+
if (continuation === void 0 || marker === void 0) return void 0;
|
|
24097
|
+
try {
|
|
24098
|
+
const markerPath = memoryRestartMarkerPath(workspace);
|
|
24099
|
+
writeMarkerAtomically(markerPath, consumedMemoryRestartMarker(marker));
|
|
24100
|
+
} catch {
|
|
24101
|
+
}
|
|
24102
|
+
return continuation;
|
|
24103
|
+
};
|
|
23631
24104
|
const loopStore = new LoopStore({ workspace });
|
|
23632
24105
|
const loopOrchestrator = new LoopOrchestrator({
|
|
23633
24106
|
workspace,
|
|
23634
24107
|
config: config.loop,
|
|
23635
24108
|
persistence: loopStore,
|
|
23636
24109
|
hallucinationGuard,
|
|
24110
|
+
onCycleBoundary: (loopId) => maybeRotateAtBoundary({ kind: "loop", id: loopId }),
|
|
24111
|
+
onMemoryCheckpoint: (loopId) => maybeRotateAtBoundary({ kind: "loop", id: loopId }),
|
|
23637
24112
|
prepareWorkspace: (input) => prepareLoopWorkspace(input),
|
|
23638
24113
|
inferVerification: inferVerificationPlan,
|
|
23639
24114
|
runWorker: ({ workspace: executionWorkspace, prompt, signal }) => runLoopWorker({ workspace: executionWorkspace, prompt, signal }),
|
|
@@ -23670,9 +24145,11 @@ async function createProductionRuntime(options) {
|
|
|
23670
24145
|
plannerModelId: mainModel,
|
|
23671
24146
|
classifierModelId: mainModel,
|
|
23672
24147
|
skepticCount: 3,
|
|
23673
|
-
maxRounds:
|
|
23674
|
-
maxStallStreak:
|
|
24148
|
+
maxRounds: config.goal.maxRounds,
|
|
24149
|
+
maxStallStreak: config.goal.maxStallStreak,
|
|
23675
24150
|
persistence: goalStore,
|
|
24151
|
+
onRoundBoundary: (goalId) => maybeRotateAtBoundary({ kind: "goal", id: goalId }),
|
|
24152
|
+
onMemoryCheckpoint: (goalId) => maybeRotateAtBoundary({ kind: "goal", id: goalId }),
|
|
23676
24153
|
verifyHost: async (signal) => runVerificationPlan(
|
|
23677
24154
|
await inferVerificationPlan(workspace),
|
|
23678
24155
|
workspace,
|
|
@@ -23767,30 +24244,49 @@ async function createProductionRuntime(options) {
|
|
|
23767
24244
|
contextEpoch: harness.main.context.snapshot().epoch?.id ?? "legacy"
|
|
23768
24245
|
});
|
|
23769
24246
|
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
|
-
|
|
24247
|
+
return monitorTurnMemory(
|
|
24248
|
+
durableTurn(persistAfter(runMain(
|
|
24249
|
+
harness,
|
|
24250
|
+
skills,
|
|
24251
|
+
prompt,
|
|
24252
|
+
signal,
|
|
24253
|
+
selectedModels.mainError,
|
|
24254
|
+
memoryStore === void 0 || !memoryHasRoutableEntries && userMemoryContext === void 0 ? void 0 : {
|
|
24255
|
+
store: memoryStore,
|
|
24256
|
+
taskId: memoryLifecycle.taskId ?? sessionId,
|
|
24257
|
+
topK: config.memory.retrievalTopK,
|
|
24258
|
+
maxChars: config.memory.maxPromptChars
|
|
24259
|
+
},
|
|
24260
|
+
runOptions?.getSteeringMessages,
|
|
24261
|
+
runOptions?.initialUserMessage,
|
|
24262
|
+
runOptions?.additionalContext,
|
|
24263
|
+
ide
|
|
24264
|
+
), persist), harnessJournal, turnId, turnConfig),
|
|
24265
|
+
() => rotateForHeap(void 0, currentPressureReading()),
|
|
24266
|
+
() => maybeRotateAtTurnBoundary()
|
|
24267
|
+
);
|
|
23787
24268
|
},
|
|
23788
24269
|
runSkill: (skill, prompt, signal) => persistAfter(
|
|
23789
24270
|
runExplicitSkill(harness, skills, skill, prompt, signal, selectedModels.mainError),
|
|
23790
24271
|
persist
|
|
23791
24272
|
),
|
|
23792
|
-
runLoop: (goal, signal) =>
|
|
23793
|
-
|
|
24273
|
+
runLoop: (goal, signal) => monitorLongTaskRestart(
|
|
24274
|
+
runLoopSession(loopOrchestrator, hooks, goal, signal),
|
|
24275
|
+
() => rotateForHeap(activeLongTask, currentPressureReading())
|
|
24276
|
+
),
|
|
24277
|
+
runGoal: (goal, signal) => monitorLongTaskRestart(
|
|
24278
|
+
persistEach(runGoalSession(goalOrchestrator, goal, signal), persist),
|
|
24279
|
+
() => rotateForHeap(activeLongTask, currentPressureReading())
|
|
24280
|
+
),
|
|
24281
|
+
resumeLoop: (loopId, signal) => monitorLongTaskRestart(
|
|
24282
|
+
runResumeLoopSession(loopOrchestrator, hooks, loopId, signal),
|
|
24283
|
+
() => rotateForHeap(activeLongTask ?? { kind: "loop", id: loopId }, currentPressureReading())
|
|
24284
|
+
),
|
|
24285
|
+
resumeGoal: (goalId, signal) => monitorLongTaskRestart(
|
|
24286
|
+
persistEach(runResumeGoalSession(goalOrchestrator, goalId, signal), persist),
|
|
24287
|
+
() => rotateForHeap(activeLongTask ?? { kind: "goal", id: goalId }, currentPressureReading())
|
|
24288
|
+
),
|
|
24289
|
+
continuation: () => consumePendingContinuation(),
|
|
23794
24290
|
mcp: async (command, signal) => {
|
|
23795
24291
|
signal.throwIfAborted();
|
|
23796
24292
|
const manager = await mcpReady;
|
|
@@ -23884,7 +24380,7 @@ async function createProductionRuntime(options) {
|
|
|
23884
24380
|
const root = resolve31(workspace);
|
|
23885
24381
|
const abs = resolve31(root, path);
|
|
23886
24382
|
if (abs !== root && !abs.startsWith(root + sep22)) throw new Error(`path outside workspace: ${path}`);
|
|
23887
|
-
return
|
|
24383
|
+
return readFile24(abs, "utf8");
|
|
23888
24384
|
},
|
|
23889
24385
|
history: async (path, limit) => fileHistory(workspace, path, limit),
|
|
23890
24386
|
questions,
|
|
@@ -23896,7 +24392,7 @@ async function createProductionRuntime(options) {
|
|
|
23896
24392
|
}, query, focus, signal),
|
|
23897
24393
|
audit: async (toolFilter) => {
|
|
23898
24394
|
try {
|
|
23899
|
-
const raw = await
|
|
24395
|
+
const raw = await readFile24(auditLogger.path, "utf8");
|
|
23900
24396
|
const lines = raw.trim().split("\n").filter((line) => line.length > 0);
|
|
23901
24397
|
const entries = lines.map((line) => {
|
|
23902
24398
|
try {
|
|
@@ -23936,7 +24432,7 @@ ${summary2}`;
|
|
|
23936
24432
|
doctor: async () => formatDoctorReport(await runDoctor({ workspace, home, environment })),
|
|
23937
24433
|
usage: async () => {
|
|
23938
24434
|
try {
|
|
23939
|
-
const raw = await
|
|
24435
|
+
const raw = await readFile24(usageLogPath(), "utf8");
|
|
23940
24436
|
return formatUsageSummary(summarizeUsage(parseUsageEntries(raw)));
|
|
23941
24437
|
} catch (error) {
|
|
23942
24438
|
if (typeof error === "object" && error !== null && "code" in error && error.code === "ENOENT") {
|
|
@@ -24089,7 +24585,7 @@ ${renderMemoryDocument(entries)}`;
|
|
|
24089
24585
|
return `Provider "${providerName}" is missing authorizationUrl, tokenUrl, or clientId.`;
|
|
24090
24586
|
}
|
|
24091
24587
|
try {
|
|
24092
|
-
const tokenStore = createFileTokenStore(
|
|
24588
|
+
const tokenStore = createFileTokenStore(join30(home, ".flavor-code", "auth.json"));
|
|
24093
24589
|
const credentialId = oauthCredentialId(oauthConfig.tokenUrl, oauthConfig.clientId);
|
|
24094
24590
|
await migrateLegacyOAuthToken(tokenStore, providerName, credentialId);
|
|
24095
24591
|
const oauth = new OAuthCallbackAuthProvider({
|
|
@@ -24136,7 +24632,7 @@ ${renderMemoryDocument(entries)}`;
|
|
|
24136
24632
|
}
|
|
24137
24633
|
},
|
|
24138
24634
|
async logout() {
|
|
24139
|
-
const tokenStore = createFileTokenStore(
|
|
24635
|
+
const tokenStore = createFileTokenStore(join30(home, ".flavor-code", "auth.json"));
|
|
24140
24636
|
const tokens = await tokenStore.load();
|
|
24141
24637
|
const hadCredentials = Object.keys(tokens).length > 0 || effectiveLlm !== void 0;
|
|
24142
24638
|
if (!hadCredentials) {
|
|
@@ -24329,10 +24825,16 @@ function formatMcpReconnect(server) {
|
|
|
24329
24825
|
return `Failed to reconnect MCP server "${server.name}": ${server.error ?? "unknown error"}`;
|
|
24330
24826
|
}
|
|
24331
24827
|
async function* runLoopSession(orchestrator, hooks, goal, signal) {
|
|
24828
|
+
yield* loopSessionEvents(orchestrator.run({ goal, signal }), hooks);
|
|
24829
|
+
}
|
|
24830
|
+
async function* runResumeLoopSession(orchestrator, hooks, loopId, signal) {
|
|
24831
|
+
yield* loopSessionEvents(orchestrator.resume({ loopId, signal }), hooks);
|
|
24832
|
+
}
|
|
24833
|
+
async function* loopSessionEvents(events, hooks) {
|
|
24332
24834
|
let totalInputTokens = 0;
|
|
24333
24835
|
let totalOutputTokens = 0;
|
|
24334
24836
|
let latestVerification;
|
|
24335
|
-
for await (const event of
|
|
24837
|
+
for await (const event of events) {
|
|
24336
24838
|
if (event.type === "worker-event") {
|
|
24337
24839
|
if (event.event.type === "usage") {
|
|
24338
24840
|
totalInputTokens += event.event.inputTokens;
|
|
@@ -24436,7 +24938,7 @@ async function workspaceFingerprint(workspace) {
|
|
|
24436
24938
|
hash.update(name);
|
|
24437
24939
|
if (remaining <= 0) continue;
|
|
24438
24940
|
try {
|
|
24439
|
-
const content = await
|
|
24941
|
+
const content = await readFile24(path);
|
|
24440
24942
|
const slice = content.subarray(0, remaining);
|
|
24441
24943
|
hash.update(slice);
|
|
24442
24944
|
remaining -= slice.length;
|
|
@@ -24548,7 +25050,16 @@ async function* durableTurn(source, journal, turnId, config) {
|
|
|
24548
25050
|
}
|
|
24549
25051
|
}
|
|
24550
25052
|
}
|
|
24551
|
-
async function*
|
|
25053
|
+
async function* monitorTurnMemory(source, onPressure, onBoundary) {
|
|
25054
|
+
let pressure = false;
|
|
25055
|
+
for await (const event of source) {
|
|
25056
|
+
if (event.type === "error" && event.error.code === "memory_pressure") pressure = true;
|
|
25057
|
+
yield event;
|
|
25058
|
+
}
|
|
25059
|
+
if (pressure) onPressure();
|
|
25060
|
+
else onBoundary();
|
|
25061
|
+
}
|
|
25062
|
+
async function* monitorLongTaskRestart(source, onRestartNeeded) {
|
|
24552
25063
|
let pressure = false;
|
|
24553
25064
|
for await (const event of source) {
|
|
24554
25065
|
if (event.type === "error" && event.error.code === "memory_pressure") pressure = true;
|
|
@@ -24556,7 +25067,10 @@ async function* monitorMemoryRestart(source, onRestartNeeded) {
|
|
|
24556
25067
|
}
|
|
24557
25068
|
if (pressure) onRestartNeeded();
|
|
24558
25069
|
}
|
|
24559
|
-
function requestMemoryRestart(workspace, sessionId, emitOutput) {
|
|
25070
|
+
function requestMemoryRestart(workspace, sessionId, emitOutput, rotation, onRestartRequested) {
|
|
25071
|
+
if (memoryRotationActive()) return;
|
|
25072
|
+
markMemoryRotation();
|
|
25073
|
+
const continuation = rotation?.continuation;
|
|
24560
25074
|
try {
|
|
24561
25075
|
const markerPath = memoryRestartMarkerPath(workspace);
|
|
24562
25076
|
let previous;
|
|
@@ -24564,14 +25078,43 @@ function requestMemoryRestart(workspace, sessionId, emitOutput) {
|
|
|
24564
25078
|
previous = parseMemoryRestartMarker(readFileSync3(markerPath, "utf8"));
|
|
24565
25079
|
} catch {
|
|
24566
25080
|
}
|
|
24567
|
-
|
|
24568
|
-
writeFileSync2(markerPath, `${JSON.stringify(nextMemoryRestartMarker(previous, sessionId, /* @__PURE__ */ new Date()))}
|
|
24569
|
-
`, "utf8");
|
|
25081
|
+
writeMarkerAtomically(markerPath, nextMemoryRestartMarker(previous, sessionId, /* @__PURE__ */ new Date(), continuation));
|
|
24570
25082
|
} catch {
|
|
24571
25083
|
}
|
|
24572
|
-
|
|
24573
|
-
|
|
24574
|
-
|
|
25084
|
+
if (rotation?.reading !== void 0) {
|
|
25085
|
+
const census = {
|
|
25086
|
+
at: (/* @__PURE__ */ new Date()).toISOString(),
|
|
25087
|
+
reason: continuation === void 0 ? "memory-pressure" : `heap-rotation:${continuation.kind}`,
|
|
25088
|
+
sessionId,
|
|
25089
|
+
heap: censusHeapBlock(rotation.reading),
|
|
25090
|
+
holders: rotation.holders ?? {}
|
|
25091
|
+
};
|
|
25092
|
+
writeRotationCensus(workspace, census);
|
|
25093
|
+
if (heapSnapshotWarranted(rotation.reading)) writeRotationHeapSnapshot(workspace);
|
|
25094
|
+
}
|
|
25095
|
+
emitOutput({
|
|
25096
|
+
type: "notice",
|
|
25097
|
+
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.`
|
|
25098
|
+
});
|
|
25099
|
+
if (onRestartRequested !== void 0) onRestartRequested(sessionId);
|
|
25100
|
+
else {
|
|
25101
|
+
process.exitCode = MEMORY_RESTART_EXIT_CODE;
|
|
25102
|
+
emitOutput({ type: "exit" });
|
|
25103
|
+
}
|
|
25104
|
+
}
|
|
25105
|
+
function writeMarkerAtomically(path, marker) {
|
|
25106
|
+
mkdirSync4(dirname17(path), { recursive: true });
|
|
25107
|
+
const temporary = `${path}.${process.pid}.${randomUUID20()}.tmp`;
|
|
25108
|
+
try {
|
|
25109
|
+
writeFileSync3(temporary, `${JSON.stringify(marker)}
|
|
25110
|
+
`, { encoding: "utf8", mode: 384 });
|
|
25111
|
+
renameSync2(temporary, path);
|
|
25112
|
+
} finally {
|
|
25113
|
+
try {
|
|
25114
|
+
unlinkSync3(temporary);
|
|
25115
|
+
} catch {
|
|
25116
|
+
}
|
|
25117
|
+
}
|
|
24575
25118
|
}
|
|
24576
25119
|
async function runGitCommit(deps, hint, signal) {
|
|
24577
25120
|
if (!await isGitRepository(deps.workspace)) return "Not a git repository \u2014 /commit needs git.";
|
|
@@ -24666,7 +25209,7 @@ async function registerConfiguredAdapters(providers, registry, environment, diag
|
|
|
24666
25209
|
const configured = { ...providers };
|
|
24667
25210
|
if (configured.openai === void 0 && environment.OPENAI_API_KEY) configured.openai = { type: "openai", apiKey: environment.OPENAI_API_KEY };
|
|
24668
25211
|
if (configured.anthropic === void 0 && environment.ANTHROPIC_API_KEY) configured.anthropic = { type: "anthropic", apiKey: environment.ANTHROPIC_API_KEY };
|
|
24669
|
-
const oauthTokenStore = createFileTokenStore(
|
|
25212
|
+
const oauthTokenStore = createFileTokenStore(join30(home, ".flavor-code", "auth.json"));
|
|
24670
25213
|
const registered = [];
|
|
24671
25214
|
let effectiveLlm;
|
|
24672
25215
|
for (const [name, provider] of Object.entries(configured)) {
|
|
@@ -24935,7 +25478,7 @@ async function generateSleepReview(registry, modelId, prompt, signal) {
|
|
|
24935
25478
|
}
|
|
24936
25479
|
async function optionalText2(path) {
|
|
24937
25480
|
try {
|
|
24938
|
-
return await
|
|
25481
|
+
return await readFile24(path, "utf8");
|
|
24939
25482
|
} catch (error) {
|
|
24940
25483
|
if (typeof error === "object" && error !== null && "code" in error && error.code === "ENOENT") return void 0;
|
|
24941
25484
|
throw error;
|
|
@@ -25020,7 +25563,17 @@ function attachCleanupError(primary, cleanup) {
|
|
|
25020
25563
|
}
|
|
25021
25564
|
}
|
|
25022
25565
|
async function* runGoalSession(orchestrator, goal, signal) {
|
|
25023
|
-
|
|
25566
|
+
yield* goalSessionEvents(orchestrator.run({ goal, signal }));
|
|
25567
|
+
}
|
|
25568
|
+
async function* runResumeGoalSession(orchestrator, goalId, signal) {
|
|
25569
|
+
yield* goalSessionEvents(orchestrator.resume({ goalId, signal }));
|
|
25570
|
+
}
|
|
25571
|
+
async function* goalSessionEvents(events) {
|
|
25572
|
+
for await (const event of events) {
|
|
25573
|
+
if (event.type === "goal-resumed") {
|
|
25574
|
+
yield { type: "notice", message: `Resuming goal ${event.goalId} at round ${event.round} after a heap rotation.` };
|
|
25575
|
+
continue;
|
|
25576
|
+
}
|
|
25024
25577
|
if (event.type === "goal-plan-created") {
|
|
25025
25578
|
yield { type: "notice", message: `Goal plan created (${event.plan.kind}) with ${event.plan.criteria.length} acceptance criteria.` };
|
|
25026
25579
|
yield { type: "notice", message: `Plan file: ${event.planPath}` };
|