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
package/dist/desktop/main.js
CHANGED
|
@@ -1,8 +1,8 @@
|
|
|
1
1
|
// src/desktop/main.ts
|
|
2
2
|
import { appendFileSync } from "fs";
|
|
3
3
|
import { createHash as createHash28, randomUUID as randomUUID29 } from "crypto";
|
|
4
|
-
import { readFile as
|
|
5
|
-
import { basename as basename12, dirname as dirname23, join as
|
|
4
|
+
import { readFile as readFile41, stat as stat20, writeFile as writeFile19 } from "fs/promises";
|
|
5
|
+
import { basename as basename12, dirname as dirname23, join as join51, resolve as resolve43 } from "path";
|
|
6
6
|
import { fileURLToPath as fileURLToPath3 } from "url";
|
|
7
7
|
import { app, BrowserWindow as BrowserWindow2, clipboard, dialog, ipcMain, Menu, nativeImage, Notification, shell } from "electron";
|
|
8
8
|
|
|
@@ -303,6 +303,10 @@ var FlavorConfigSchema = z2.object({
|
|
|
303
303
|
maxTokens: z2.number().int().positive().max(Number.MAX_SAFE_INTEGER).default(5e5),
|
|
304
304
|
isolation: z2.literal("auto").default("auto")
|
|
305
305
|
}).prefault({}),
|
|
306
|
+
goal: z2.object({
|
|
307
|
+
maxRounds: z2.number().int().positive().max(1e4).default(100),
|
|
308
|
+
maxStallStreak: z2.number().int().positive().max(100).default(3)
|
|
309
|
+
}).prefault({}),
|
|
306
310
|
context: z2.object({
|
|
307
311
|
windowTokens: z2.number().int().positive().default(2e5),
|
|
308
312
|
reservedOutputTokens: z2.number().int().nonnegative().default(2e4),
|
|
@@ -4225,11 +4229,11 @@ ${summarizeReport(report2)}`).join("\n\n")
|
|
|
4225
4229
|
}
|
|
4226
4230
|
|
|
4227
4231
|
// src/production.ts
|
|
4228
|
-
import { readFile as
|
|
4229
|
-
import { mkdirSync as
|
|
4232
|
+
import { readFile as readFile29 } from "fs/promises";
|
|
4233
|
+
import { mkdirSync as mkdirSync4, readFileSync as readFileSync3, renameSync as renameSync2, unlinkSync as unlinkSync3, writeFileSync as writeFileSync3 } from "fs";
|
|
4230
4234
|
import { createHash as createHash21, randomUUID as randomUUID25 } from "crypto";
|
|
4231
4235
|
import { homedir as homedir3, release as osRelease, tmpdir as tmpdir4, version as osVersion } from "os";
|
|
4232
|
-
import { basename as basename9, dirname as dirname22, join as
|
|
4236
|
+
import { basename as basename9, dirname as dirname22, join as join38, relative as relative25, resolve as resolve36, sep as sep25 } from "path";
|
|
4233
4237
|
|
|
4234
4238
|
// src/agent/subagents.ts
|
|
4235
4239
|
import { z as z10 } from "zod";
|
|
@@ -5572,6 +5576,18 @@ var ContextManager = class _ContextManager {
|
|
|
5572
5576
|
estimatedTokens() {
|
|
5573
5577
|
return estimateMessageTokens(this.messagesForModel());
|
|
5574
5578
|
}
|
|
5579
|
+
/** Retained sizes for the rotation census: what this context holds right now. */
|
|
5580
|
+
census() {
|
|
5581
|
+
const chars = (messages) => messages.reduce((sum, entry2) => sum + modelContentText(entry2.content).length, 0);
|
|
5582
|
+
return {
|
|
5583
|
+
messages: this.#messages.length,
|
|
5584
|
+
messageChars: chars(this.#messages),
|
|
5585
|
+
visibilityEntries: this.#visibilityLog.length,
|
|
5586
|
+
visibilityChars: this.#visibilityLog.reduce((sum, record3) => sum + record3.content.length, 0),
|
|
5587
|
+
transientEntries: this.#activeTransientSystem.size,
|
|
5588
|
+
transientChars: chars([...this.#activeTransientSystem.values()])
|
|
5589
|
+
};
|
|
5590
|
+
}
|
|
5575
5591
|
needsCompaction() {
|
|
5576
5592
|
if (modelVisibleText(this.messagesForModel()).length >= this.#compactAtChars) return true;
|
|
5577
5593
|
return calculateContextPressure(this.#currentTokenUsage(), this.#compaction).shouldAutoCompact;
|
|
@@ -6140,8 +6156,75 @@ function reportProgress(callback, percentage) {
|
|
|
6140
6156
|
}
|
|
6141
6157
|
}
|
|
6142
6158
|
|
|
6143
|
-
// src/
|
|
6159
|
+
// src/utils/heap.ts
|
|
6144
6160
|
import { getHeapStatistics } from "v8";
|
|
6161
|
+
import { totalmem } from "os";
|
|
6162
|
+
var MEMORY_PRESSURE_HEAP_RATIO = 0.8;
|
|
6163
|
+
var MEMORY_ROTATION_HEAP_RATIO = 0.6;
|
|
6164
|
+
var MEMORY_PRESSURE_RSS_RATIO = 0.75;
|
|
6165
|
+
var MEMORY_ROTATION_RSS_RATIO = 0.6;
|
|
6166
|
+
var ROTATION_PRECHECK_RATIO = 0.45;
|
|
6167
|
+
function readHeap() {
|
|
6168
|
+
const heapLimit = getHeapStatistics().heap_size_limit;
|
|
6169
|
+
const heapUsed = process.memoryUsage().heapUsed;
|
|
6170
|
+
return { heapUsed, heapLimit, ratio: heapLimit > 0 ? heapUsed / heapLimit : 0 };
|
|
6171
|
+
}
|
|
6172
|
+
function forceFullGc() {
|
|
6173
|
+
const gc = globalThis.gc;
|
|
6174
|
+
if (typeof gc !== "function") return false;
|
|
6175
|
+
gc();
|
|
6176
|
+
gc();
|
|
6177
|
+
return true;
|
|
6178
|
+
}
|
|
6179
|
+
function ratioOf(heapLimit, heapUsed) {
|
|
6180
|
+
return heapLimit > 0 ? heapUsed / heapLimit : 0;
|
|
6181
|
+
}
|
|
6182
|
+
function effectiveMemoryLimit() {
|
|
6183
|
+
const physical = totalmem();
|
|
6184
|
+
const constrained = process.constrainedMemory?.() ?? 0;
|
|
6185
|
+
return constrained > 0 ? Math.min(physical, constrained) : physical;
|
|
6186
|
+
}
|
|
6187
|
+
function withRss(reading, gcVerified) {
|
|
6188
|
+
const usage = process.memoryUsage();
|
|
6189
|
+
const memoryLimit = effectiveMemoryLimit();
|
|
6190
|
+
return {
|
|
6191
|
+
...reading,
|
|
6192
|
+
gcVerified,
|
|
6193
|
+
rss: usage.rss,
|
|
6194
|
+
memoryLimit,
|
|
6195
|
+
rssRatio: ratioOf(memoryLimit, usage.rss),
|
|
6196
|
+
external: usage.external,
|
|
6197
|
+
arrayBuffers: usage.arrayBuffers
|
|
6198
|
+
};
|
|
6199
|
+
}
|
|
6200
|
+
function verifiedHeapPressure(threshold) {
|
|
6201
|
+
const raw = readHeap();
|
|
6202
|
+
const before = withRss(raw, false);
|
|
6203
|
+
const rssThreshold = threshold >= MEMORY_PRESSURE_HEAP_RATIO ? MEMORY_PRESSURE_RSS_RATIO : threshold;
|
|
6204
|
+
if ((raw.heapLimit <= 0 || raw.ratio < threshold) && (before.rssRatio ?? 0) < rssThreshold) return void 0;
|
|
6205
|
+
if (!forceFullGc()) return before;
|
|
6206
|
+
const heapUsed = process.memoryUsage().heapUsed;
|
|
6207
|
+
const ratio = ratioOf(raw.heapLimit, heapUsed);
|
|
6208
|
+
const verified = withRss({ heapUsed, heapLimit: raw.heapLimit, ratio, verifiedRatio: ratio }, true);
|
|
6209
|
+
if (ratio < threshold && (verified.rssRatio ?? 0) < rssThreshold) return void 0;
|
|
6210
|
+
return verified;
|
|
6211
|
+
}
|
|
6212
|
+
function heapRotationNeeded() {
|
|
6213
|
+
const raw = readHeap();
|
|
6214
|
+
const before = withRss(raw, false);
|
|
6215
|
+
if ((raw.heapLimit <= 0 || raw.ratio < ROTATION_PRECHECK_RATIO) && (before.rssRatio ?? 0) < ROTATION_PRECHECK_RATIO) return void 0;
|
|
6216
|
+
if (!forceFullGc()) {
|
|
6217
|
+
return raw.ratio >= MEMORY_PRESSURE_HEAP_RATIO || (before.rssRatio ?? 0) >= MEMORY_PRESSURE_RSS_RATIO ? before : void 0;
|
|
6218
|
+
}
|
|
6219
|
+
const heapUsed = process.memoryUsage().heapUsed;
|
|
6220
|
+
const ratio = ratioOf(raw.heapLimit, heapUsed);
|
|
6221
|
+
const verified = withRss({ heapUsed, heapLimit: raw.heapLimit, ratio, verifiedRatio: ratio }, true);
|
|
6222
|
+
if (ratio < MEMORY_ROTATION_HEAP_RATIO && (verified.rssRatio ?? 0) < MEMORY_ROTATION_RSS_RATIO) return void 0;
|
|
6223
|
+
return verified;
|
|
6224
|
+
}
|
|
6225
|
+
function heapSnapshotWarranted(_reading) {
|
|
6226
|
+
return process.env.FLAVOR_HEAP_SNAPSHOT_ON_ROTATE === "1";
|
|
6227
|
+
}
|
|
6145
6228
|
|
|
6146
6229
|
// src/models/structured.ts
|
|
6147
6230
|
import { z as z12 } from "zod";
|
|
@@ -6560,7 +6643,6 @@ function envMaxIterations() {
|
|
|
6560
6643
|
if (!Number.isInteger(parsed) || parsed <= 0) return void 0;
|
|
6561
6644
|
return parsed;
|
|
6562
6645
|
}
|
|
6563
|
-
var MEMORY_PRESSURE_HEAP_RATIO = 0.8;
|
|
6564
6646
|
function memoryPressureError(heapUsed, heapLimit) {
|
|
6565
6647
|
if (heapLimit <= 0 || heapUsed < heapLimit * MEMORY_PRESSURE_HEAP_RATIO) return void 0;
|
|
6566
6648
|
const mb = (bytes) => Math.round(bytes / 1048576);
|
|
@@ -6570,7 +6652,12 @@ function memoryPressureError(heapUsed, heapLimit) {
|
|
|
6570
6652
|
};
|
|
6571
6653
|
}
|
|
6572
6654
|
function currentMemoryPressure() {
|
|
6573
|
-
|
|
6655
|
+
const reading = verifiedHeapPressure(MEMORY_PRESSURE_HEAP_RATIO);
|
|
6656
|
+
if (reading === void 0) return void 0;
|
|
6657
|
+
return memoryPressureError(reading.heapUsed, reading.heapLimit) ?? {
|
|
6658
|
+
code: "memory_pressure",
|
|
6659
|
+
message: `Process RSS reached ${Math.round((reading.rssRatio ?? 0) * 100)}% of the available memory limit; stopping before system OOM`
|
|
6660
|
+
};
|
|
6574
6661
|
}
|
|
6575
6662
|
var AgentLoop = class {
|
|
6576
6663
|
#options;
|
|
@@ -8393,6 +8480,10 @@ var HarnessJournal = class {
|
|
|
8393
8480
|
get records() {
|
|
8394
8481
|
return this.#records.map((record3) => structuredClone(record3));
|
|
8395
8482
|
}
|
|
8483
|
+
/** Retained record count for the rotation census. */
|
|
8484
|
+
get recordCount() {
|
|
8485
|
+
return this.#records.length;
|
|
8486
|
+
}
|
|
8396
8487
|
admitQueue(kind, payload) {
|
|
8397
8488
|
QueueKindSchema.parse(kind);
|
|
8398
8489
|
const id = randomUUID8();
|
|
@@ -8669,19 +8760,38 @@ function jsonSafeRecord(value) {
|
|
|
8669
8760
|
return JSON.parse(json);
|
|
8670
8761
|
}
|
|
8671
8762
|
function hashJson(value) {
|
|
8672
|
-
|
|
8673
|
-
|
|
8674
|
-
|
|
8675
|
-
const { hash: _hash, ...unsigned } = record3;
|
|
8676
|
-
return hashJson(unsigned);
|
|
8763
|
+
const hash = createHash6("sha256");
|
|
8764
|
+
stableHashInto(hash, value);
|
|
8765
|
+
return hash.digest("hex");
|
|
8677
8766
|
}
|
|
8678
|
-
function
|
|
8679
|
-
if (Array.isArray(value))
|
|
8767
|
+
function stableHashInto(hash, value) {
|
|
8768
|
+
if (Array.isArray(value)) {
|
|
8769
|
+
hash.update("[");
|
|
8770
|
+
for (let index = 0; index < value.length; index += 1) {
|
|
8771
|
+
if (index > 0) hash.update(",");
|
|
8772
|
+
stableHashInto(hash, value[index]);
|
|
8773
|
+
}
|
|
8774
|
+
hash.update("]");
|
|
8775
|
+
return;
|
|
8776
|
+
}
|
|
8680
8777
|
if (typeof value === "object" && value !== null) {
|
|
8681
8778
|
const object = value;
|
|
8682
|
-
|
|
8779
|
+
hash.update("{");
|
|
8780
|
+
const keys = Object.keys(object).sort();
|
|
8781
|
+
for (let index = 0; index < keys.length; index += 1) {
|
|
8782
|
+
const key = keys[index];
|
|
8783
|
+
if (index > 0) hash.update(",");
|
|
8784
|
+
hash.update(`${JSON.stringify(key)}:`);
|
|
8785
|
+
stableHashInto(hash, object[key]);
|
|
8786
|
+
}
|
|
8787
|
+
hash.update("}");
|
|
8788
|
+
return;
|
|
8683
8789
|
}
|
|
8684
|
-
|
|
8790
|
+
hash.update(JSON.stringify(value) ?? String(value));
|
|
8791
|
+
}
|
|
8792
|
+
function recordHash(record3) {
|
|
8793
|
+
const { hash: _hash, ...unsigned } = record3;
|
|
8794
|
+
return hashJson(unsigned);
|
|
8685
8795
|
}
|
|
8686
8796
|
function assertSessionId(value) {
|
|
8687
8797
|
if (!/^[A-Za-z0-9][A-Za-z0-9._-]{0,127}$/.test(value)) throw new Error("Invalid session id");
|
|
@@ -11387,6 +11497,81 @@ async function initializeFlavor(cwd) {
|
|
|
11387
11497
|
return { path, created: existing === void 0, content, facts };
|
|
11388
11498
|
}
|
|
11389
11499
|
|
|
11500
|
+
// src/utils/memory-restart.ts
|
|
11501
|
+
import { join as join14 } from "path";
|
|
11502
|
+
var MEMORY_RESTART_EXIT_CODE = 75;
|
|
11503
|
+
var MEMORY_RESTART_MARKER_FILE = "memory-restart.json";
|
|
11504
|
+
var MEMORY_RESTART_WINDOW_MS = 60 * 60 * 1e3;
|
|
11505
|
+
var MEMORY_RESTART_MIN_GAP_MS = 5 * 60 * 1e3;
|
|
11506
|
+
var MEMORY_RESTART_CONTINUATION_TTL_MS = 10 * 60 * 1e3;
|
|
11507
|
+
var rotationActive = false;
|
|
11508
|
+
function markMemoryRotation() {
|
|
11509
|
+
rotationActive = true;
|
|
11510
|
+
}
|
|
11511
|
+
function memoryRotationActive() {
|
|
11512
|
+
return rotationActive;
|
|
11513
|
+
}
|
|
11514
|
+
function clearMemoryRotation() {
|
|
11515
|
+
rotationActive = false;
|
|
11516
|
+
}
|
|
11517
|
+
function memoryRestartMarkerPath(workspace) {
|
|
11518
|
+
return join14(workspace, ".flavor", "tmp", MEMORY_RESTART_MARKER_FILE);
|
|
11519
|
+
}
|
|
11520
|
+
function nextMemoryRestartMarker(previous, sessionId, now, continuation) {
|
|
11521
|
+
const previousWindowStart = previous?.windowStartedAt ?? previous?.requestedAt;
|
|
11522
|
+
const reusable = previous !== void 0 && previous.sessionId === sessionId && previousWindowStart !== void 0 && now.getTime() - Date.parse(previousWindowStart) < MEMORY_RESTART_WINDOW_MS && Number.isInteger(previous.attempts) && previous.attempts > 0;
|
|
11523
|
+
return {
|
|
11524
|
+
sessionId,
|
|
11525
|
+
windowStartedAt: reusable ? previousWindowStart : now.toISOString(),
|
|
11526
|
+
requestedAt: now.toISOString(),
|
|
11527
|
+
attempts: reusable ? previous.attempts + 1 : 1,
|
|
11528
|
+
...continuation === void 0 ? {} : { continuation }
|
|
11529
|
+
};
|
|
11530
|
+
}
|
|
11531
|
+
function consumedMemoryRestartMarker(marker) {
|
|
11532
|
+
return {
|
|
11533
|
+
sessionId: marker.sessionId,
|
|
11534
|
+
...marker.windowStartedAt === void 0 ? {} : { windowStartedAt: marker.windowStartedAt },
|
|
11535
|
+
requestedAt: marker.requestedAt,
|
|
11536
|
+
attempts: marker.attempts
|
|
11537
|
+
};
|
|
11538
|
+
}
|
|
11539
|
+
function rotationCooldownActive(marker, sessionId, now) {
|
|
11540
|
+
return marker !== void 0 && marker.sessionId === sessionId && now.getTime() - Date.parse(marker.requestedAt) < MEMORY_RESTART_MIN_GAP_MS;
|
|
11541
|
+
}
|
|
11542
|
+
function pendingContinuation(marker, sessionId, now) {
|
|
11543
|
+
if (marker === void 0 || marker.sessionId !== sessionId) return void 0;
|
|
11544
|
+
if (now.getTime() - Date.parse(marker.requestedAt) > MEMORY_RESTART_CONTINUATION_TTL_MS) return void 0;
|
|
11545
|
+
const continuation = marker.continuation;
|
|
11546
|
+
if (continuation === void 0) return void 0;
|
|
11547
|
+
if (continuation.kind !== "loop" && continuation.kind !== "goal") return void 0;
|
|
11548
|
+
if (typeof continuation.id !== "string" || continuation.id.length === 0) return void 0;
|
|
11549
|
+
return continuation;
|
|
11550
|
+
}
|
|
11551
|
+
function parseMemoryRestartMarker(json) {
|
|
11552
|
+
try {
|
|
11553
|
+
const value = JSON.parse(json);
|
|
11554
|
+
if (typeof value.sessionId !== "string" || typeof value.requestedAt !== "string" || typeof value.attempts !== "number") {
|
|
11555
|
+
return void 0;
|
|
11556
|
+
}
|
|
11557
|
+
const continuation = value.continuation;
|
|
11558
|
+
if (continuation !== void 0 && (typeof continuation !== "object" || continuation === null || continuation.kind !== "loop" && continuation.kind !== "goal" || typeof continuation.id !== "string")) {
|
|
11559
|
+
return void 0;
|
|
11560
|
+
}
|
|
11561
|
+
const windowStartedAt = value.windowStartedAt;
|
|
11562
|
+
if (windowStartedAt !== void 0 && typeof windowStartedAt !== "string") return void 0;
|
|
11563
|
+
return {
|
|
11564
|
+
sessionId: value.sessionId,
|
|
11565
|
+
...windowStartedAt === void 0 ? {} : { windowStartedAt },
|
|
11566
|
+
requestedAt: value.requestedAt,
|
|
11567
|
+
attempts: value.attempts,
|
|
11568
|
+
...continuation === void 0 ? {} : { continuation }
|
|
11569
|
+
};
|
|
11570
|
+
} catch {
|
|
11571
|
+
return void 0;
|
|
11572
|
+
}
|
|
11573
|
+
}
|
|
11574
|
+
|
|
11390
11575
|
// src/skills/builtin-loop.ts
|
|
11391
11576
|
function buildLoopCyclePrompt(input) {
|
|
11392
11577
|
return [
|
|
@@ -11467,6 +11652,14 @@ var LoopStateSchema = z20.object({
|
|
|
11467
11652
|
approvals: z20.array(LoopApprovalSchema)
|
|
11468
11653
|
}).strict(),
|
|
11469
11654
|
cycles: z20.array(LoopCycleEvidenceSchema),
|
|
11655
|
+
/** Completed worker awaiting host verification; avoids replaying tool work after rotation. */
|
|
11656
|
+
pendingCycle: z20.object({
|
|
11657
|
+
cycle: z20.number().int().positive(),
|
|
11658
|
+
startedAt: z20.string().datetime({ offset: true }),
|
|
11659
|
+
inputTokens: z20.number().int().nonnegative(),
|
|
11660
|
+
outputTokens: z20.number().int().nonnegative(),
|
|
11661
|
+
workerText: z20.string().max(16e3)
|
|
11662
|
+
}).strict().nullable().optional(),
|
|
11470
11663
|
terminalReason: z20.string().optional()
|
|
11471
11664
|
}).strict();
|
|
11472
11665
|
var LoopEventSchema = z20.object({
|
|
@@ -11555,15 +11748,45 @@ var LoopOrchestrator = class {
|
|
|
11555
11748
|
tokenCheckpoint: this.#options.config.maxTokens,
|
|
11556
11749
|
approvals: []
|
|
11557
11750
|
},
|
|
11558
|
-
cycles: []
|
|
11751
|
+
cycles: [],
|
|
11752
|
+
pendingCycle: null
|
|
11559
11753
|
});
|
|
11560
11754
|
await this.#options.persistence.create(state);
|
|
11755
|
+
yield* this.#execute(state, request.goal, request.signal);
|
|
11756
|
+
}
|
|
11757
|
+
/**
|
|
11758
|
+
* Continue a loop whose process rotated onto a fresh heap. The persisted
|
|
11759
|
+
* state decides where to pick up; an interrupted cycle simply runs again.
|
|
11760
|
+
*/
|
|
11761
|
+
async *resume(request) {
|
|
11762
|
+
const persistence = this.#options.persistence;
|
|
11763
|
+
if (persistence.load === void 0) throw new Error("Loop persistence does not support resume");
|
|
11764
|
+
let state;
|
|
11765
|
+
try {
|
|
11766
|
+
state = await persistence.load(request.loopId);
|
|
11767
|
+
} catch (error) {
|
|
11768
|
+
yield { type: "loop-terminal", loopId: request.loopId, status: "failed", reason: message(error) };
|
|
11769
|
+
return;
|
|
11770
|
+
}
|
|
11771
|
+
if (state.status !== "running") {
|
|
11772
|
+
const status = state.status;
|
|
11773
|
+
yield { type: "loop-terminal", loopId: state.loopId, status, reason: state.terminalReason ?? "The loop is not running." };
|
|
11774
|
+
return;
|
|
11775
|
+
}
|
|
11776
|
+
yield* this.#execute(state, state.goal, request.signal);
|
|
11777
|
+
}
|
|
11778
|
+
async *#execute(initialState, goal, signal) {
|
|
11779
|
+
const now = this.#options.now ?? (() => (/* @__PURE__ */ new Date()).toISOString());
|
|
11780
|
+
const loopId = initialState.loopId;
|
|
11781
|
+
let state = initialState;
|
|
11561
11782
|
try {
|
|
11783
|
+
await this.#options.onMemoryCheckpoint?.(loopId);
|
|
11784
|
+
if (memoryRotationActive()) return;
|
|
11562
11785
|
const workspaceResolution = await this.#options.prepareWorkspace({
|
|
11563
11786
|
root: this.#options.workspace,
|
|
11564
11787
|
loopId,
|
|
11565
|
-
goal
|
|
11566
|
-
signal
|
|
11788
|
+
goal,
|
|
11789
|
+
signal
|
|
11567
11790
|
});
|
|
11568
11791
|
if (workspaceResolution.kind === "needs_human") {
|
|
11569
11792
|
state = await this.#terminal(state, "needs_human", workspaceResolution.reason, now());
|
|
@@ -11581,67 +11804,87 @@ var LoopOrchestrator = class {
|
|
|
11581
11804
|
cycleCheckpoint: state.budget.cycleCheckpoint,
|
|
11582
11805
|
tokenCheckpoint: state.budget.tokenCheckpoint
|
|
11583
11806
|
};
|
|
11584
|
-
|
|
11807
|
+
const lastCompletedCycle = state.cycles.at(-1);
|
|
11808
|
+
let previousVerification = lastCompletedCycle?.verification ?? (plan.commands.length === 0 ? {
|
|
11585
11809
|
passed: false,
|
|
11586
11810
|
commands: [],
|
|
11587
11811
|
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.`
|
|
11588
|
-
} : void 0;
|
|
11589
|
-
let previousFailureSignature
|
|
11590
|
-
|
|
11812
|
+
} : void 0);
|
|
11813
|
+
let previousFailureSignature = lastCompletedCycle === void 0 ? void 0 : `${lastCompletedCycle.workspaceFingerprint}
|
|
11814
|
+
${lastCompletedCycle.verification.summary}`;
|
|
11815
|
+
let repeatedFailures = previousFailureSignature === void 0 ? 0 : countTrailingFailureSignature(state.cycles, previousFailureSignature);
|
|
11591
11816
|
while (state.status === "running") {
|
|
11592
|
-
|
|
11593
|
-
|
|
11594
|
-
|
|
11595
|
-
|
|
11596
|
-
|
|
11597
|
-
const
|
|
11598
|
-
|
|
11599
|
-
|
|
11600
|
-
|
|
11601
|
-
|
|
11602
|
-
|
|
11603
|
-
}
|
|
11604
|
-
|
|
11605
|
-
|
|
11606
|
-
|
|
11607
|
-
|
|
11608
|
-
|
|
11609
|
-
|
|
11610
|
-
|
|
11611
|
-
|
|
11612
|
-
|
|
11613
|
-
|
|
11614
|
-
|
|
11615
|
-
|
|
11616
|
-
|
|
11617
|
-
|
|
11618
|
-
|
|
11619
|
-
|
|
11620
|
-
|
|
11621
|
-
|
|
11622
|
-
|
|
11623
|
-
|
|
11624
|
-
|
|
11625
|
-
|
|
11626
|
-
|
|
11817
|
+
signal.throwIfAborted();
|
|
11818
|
+
await this.#options.onCycleBoundary?.(loopId);
|
|
11819
|
+
if (memoryRotationActive()) return;
|
|
11820
|
+
const pendingCycle = state.pendingCycle;
|
|
11821
|
+
const cycle = pendingCycle?.cycle ?? state.budget.cyclesUsed + 1;
|
|
11822
|
+
const startedAt = pendingCycle?.startedAt ?? now();
|
|
11823
|
+
let cycleInputTokens = pendingCycle?.inputTokens ?? 0;
|
|
11824
|
+
let cycleOutputTokens = pendingCycle?.outputTokens ?? 0;
|
|
11825
|
+
let workerText = pendingCycle?.workerText ?? "";
|
|
11826
|
+
if (pendingCycle === void 0 || pendingCycle === null) {
|
|
11827
|
+
await this.#options.persistence.append({ version: 1, type: "cycle_started", timestamp: startedAt, loopId, payload: { cycle } });
|
|
11828
|
+
yield { type: "loop-cycle-start", loopId, cycle };
|
|
11829
|
+
const prompt = buildLoopCyclePrompt({
|
|
11830
|
+
goal,
|
|
11831
|
+
cycle,
|
|
11832
|
+
...previousVerification === void 0 ? {} : {
|
|
11833
|
+
memory: `Previous host verification: ${previousVerification.summary}`,
|
|
11834
|
+
verification: previousVerification
|
|
11835
|
+
}
|
|
11836
|
+
});
|
|
11837
|
+
let workerError;
|
|
11838
|
+
let workerMemoryPressure = false;
|
|
11839
|
+
for await (const event of this.#options.runWorker({
|
|
11840
|
+
goal,
|
|
11841
|
+
cycle,
|
|
11842
|
+
workspace: executionWorkspace.root,
|
|
11843
|
+
prompt,
|
|
11844
|
+
signal
|
|
11845
|
+
})) {
|
|
11846
|
+
if (event.type === "usage") {
|
|
11847
|
+
cycleInputTokens += event.inputTokens;
|
|
11848
|
+
cycleOutputTokens += event.outputTokens;
|
|
11849
|
+
}
|
|
11850
|
+
if (event.type === "error") {
|
|
11851
|
+
workerError = event.error.message;
|
|
11852
|
+
if (event.error.code === "memory_pressure") workerMemoryPressure = true;
|
|
11627
11853
|
}
|
|
11854
|
+
if (event.type === "text") workerText = `${workerText}${event.text}`.slice(-16e3);
|
|
11855
|
+
if (this.#options.hallucinationGuard !== void 0) {
|
|
11856
|
+
if (event.type === "tool-start") {
|
|
11857
|
+
this.#options.hallucinationGuard.recordToolCall(event.name, event.input, event.id);
|
|
11858
|
+
} else if (event.type === "tool-end") {
|
|
11859
|
+
this.#options.hallucinationGuard.recordToolResult(event.name, event.result, event.id);
|
|
11860
|
+
}
|
|
11861
|
+
}
|
|
11862
|
+
yield { type: "worker-event", event };
|
|
11863
|
+
}
|
|
11864
|
+
if (workerMemoryPressure) return;
|
|
11865
|
+
if (workerError !== void 0) {
|
|
11866
|
+
state = LoopStateSchema.parse({
|
|
11867
|
+
...state,
|
|
11868
|
+
budget: {
|
|
11869
|
+
...state.budget,
|
|
11870
|
+
cyclesUsed: cycle,
|
|
11871
|
+
inputTokens: state.budget.inputTokens + cycleInputTokens,
|
|
11872
|
+
outputTokens: state.budget.outputTokens + cycleOutputTokens
|
|
11873
|
+
}
|
|
11874
|
+
});
|
|
11875
|
+
state = await this.#terminal(state, "failed", workerError, now());
|
|
11876
|
+
yield { type: "loop-terminal", loopId, status: "failed", reason: workerError };
|
|
11877
|
+
return;
|
|
11628
11878
|
}
|
|
11629
|
-
yield { type: "worker-event", event };
|
|
11630
|
-
}
|
|
11631
|
-
if (workerError !== void 0) {
|
|
11632
11879
|
state = LoopStateSchema.parse({
|
|
11633
11880
|
...state,
|
|
11634
|
-
|
|
11635
|
-
|
|
11636
|
-
cyclesUsed: cycle,
|
|
11637
|
-
inputTokens: state.budget.inputTokens + cycleInputTokens,
|
|
11638
|
-
outputTokens: state.budget.outputTokens + cycleOutputTokens
|
|
11639
|
-
}
|
|
11881
|
+
pendingCycle: { cycle, startedAt, inputTokens: cycleInputTokens, outputTokens: cycleOutputTokens, workerText },
|
|
11882
|
+
updatedAt: now()
|
|
11640
11883
|
});
|
|
11641
|
-
|
|
11642
|
-
yield { type: "loop-terminal", loopId, status: "failed", reason: workerError };
|
|
11643
|
-
return;
|
|
11884
|
+
await this.#options.persistence.save(state);
|
|
11644
11885
|
}
|
|
11886
|
+
await this.#options.onMemoryCheckpoint?.(loopId);
|
|
11887
|
+
if (memoryRotationActive()) return;
|
|
11645
11888
|
if (plan.commands.length === 0) {
|
|
11646
11889
|
plan = await this.#options.inferVerification(executionWorkspace.root);
|
|
11647
11890
|
if (plan.commands.length > 0) {
|
|
@@ -11661,8 +11904,28 @@ var LoopOrchestrator = class {
|
|
|
11661
11904
|
passed: false,
|
|
11662
11905
|
commands: [],
|
|
11663
11906
|
summary: plan.needsHumanReason ?? "No deterministic verification command was found after the discovery cycle."
|
|
11664
|
-
} : await this.#options.runVerifier(plan, executionWorkspace.root,
|
|
11907
|
+
} : await this.#options.runVerifier(plan, executionWorkspace.root, signal);
|
|
11908
|
+
await this.#options.onMemoryCheckpoint?.(loopId);
|
|
11909
|
+
if (memoryRotationActive()) return;
|
|
11665
11910
|
const fingerprint2 = await this.#options.fingerprint(executionWorkspace.root);
|
|
11911
|
+
let guardWarnings = [];
|
|
11912
|
+
if (evidence.passed && this.#options.hallucinationGuard !== void 0) {
|
|
11913
|
+
try {
|
|
11914
|
+
await this.#options.onMemoryCheckpoint?.(loopId);
|
|
11915
|
+
if (memoryRotationActive()) return;
|
|
11916
|
+
const report = await this.#options.hallucinationGuard.evaluate(goal, workerText);
|
|
11917
|
+
await this.#options.onMemoryCheckpoint?.(loopId);
|
|
11918
|
+
if (memoryRotationActive()) return;
|
|
11919
|
+
if (!report.passed) {
|
|
11920
|
+
const guardReason = report.blockingReasons.join("; ") || "Hallucination guard blocked completion.";
|
|
11921
|
+
state = await this.#terminal(state, "failed", guardReason, now());
|
|
11922
|
+
yield { type: "loop-terminal", loopId, status: "failed", reason: guardReason };
|
|
11923
|
+
return;
|
|
11924
|
+
}
|
|
11925
|
+
guardWarnings = report.warnings;
|
|
11926
|
+
} catch {
|
|
11927
|
+
}
|
|
11928
|
+
}
|
|
11666
11929
|
const completedAt = now();
|
|
11667
11930
|
state = LoopStateSchema.parse({
|
|
11668
11931
|
...state,
|
|
@@ -11681,7 +11944,8 @@ var LoopOrchestrator = class {
|
|
|
11681
11944
|
outputTokens: cycleOutputTokens,
|
|
11682
11945
|
workspaceFingerprint: fingerprint2,
|
|
11683
11946
|
verification: evidence
|
|
11684
|
-
}]
|
|
11947
|
+
}],
|
|
11948
|
+
pendingCycle: null
|
|
11685
11949
|
});
|
|
11686
11950
|
await this.#options.persistence.save(state);
|
|
11687
11951
|
await this.#options.persistence.append({
|
|
@@ -11699,20 +11963,6 @@ var LoopOrchestrator = class {
|
|
|
11699
11963
|
return;
|
|
11700
11964
|
}
|
|
11701
11965
|
if (evidence.passed) {
|
|
11702
|
-
let guardWarnings = [];
|
|
11703
|
-
if (this.#options.hallucinationGuard !== void 0) {
|
|
11704
|
-
try {
|
|
11705
|
-
const report = await this.#options.hallucinationGuard.evaluate(request.goal, workerText);
|
|
11706
|
-
if (!report.passed) {
|
|
11707
|
-
const guardReason = report.blockingReasons.join("; ") || "Hallucination guard blocked completion.";
|
|
11708
|
-
state = await this.#terminal(state, "failed", guardReason, now());
|
|
11709
|
-
yield { type: "loop-terminal", loopId, status: "failed", reason: guardReason };
|
|
11710
|
-
return;
|
|
11711
|
-
}
|
|
11712
|
-
guardWarnings = report.warnings;
|
|
11713
|
-
} catch {
|
|
11714
|
-
}
|
|
11715
|
-
}
|
|
11716
11966
|
const reason = [evidence.summary, ...guardWarnings].filter(Boolean).join("; ");
|
|
11717
11967
|
state = await this.#terminal(state, "succeeded", reason, now());
|
|
11718
11968
|
yield { type: "loop-terminal", loopId, status: "succeeded", reason };
|
|
@@ -11738,7 +11988,7 @@ ${evidence.summary}`;
|
|
|
11738
11988
|
cycleCheckpoint: state.budget.cycleCheckpoint,
|
|
11739
11989
|
tokenCheckpoint: state.budget.tokenCheckpoint
|
|
11740
11990
|
};
|
|
11741
|
-
const confirmation = await this.#options.confirmBudget(state, decision.dimensions,
|
|
11991
|
+
const confirmation = await this.#options.confirmBudget(state, decision.dimensions, signal);
|
|
11742
11992
|
if (confirmation === "unavailable") {
|
|
11743
11993
|
const reason = "Loop budget extension requires user confirmation.";
|
|
11744
11994
|
state = await this.#terminal(state, "needs_human", reason, now());
|
|
@@ -11765,8 +12015,9 @@ ${evidence.summary}`;
|
|
|
11765
12015
|
}
|
|
11766
12016
|
}
|
|
11767
12017
|
} catch (error) {
|
|
11768
|
-
|
|
11769
|
-
const
|
|
12018
|
+
if (memoryRotationActive()) return;
|
|
12019
|
+
const status = signal.aborted ? "cancelled" : "failed";
|
|
12020
|
+
const reason = signal.aborted ? "Loop cancelled by user." : message(error);
|
|
11770
12021
|
state = await this.#terminal(state, status, reason, now());
|
|
11771
12022
|
yield { type: "loop-terminal", loopId, status, reason };
|
|
11772
12023
|
}
|
|
@@ -11784,11 +12035,21 @@ ${evidence.summary}`;
|
|
|
11784
12035
|
return state;
|
|
11785
12036
|
}
|
|
11786
12037
|
};
|
|
12038
|
+
function countTrailingFailureSignature(cycles, signature) {
|
|
12039
|
+
let count = 0;
|
|
12040
|
+
for (let index = cycles.length - 1; index >= 0; index -= 1) {
|
|
12041
|
+
const cycle = cycles[index];
|
|
12042
|
+
if (`${cycle.workspaceFingerprint}
|
|
12043
|
+
${cycle.verification.summary}` !== signature) break;
|
|
12044
|
+
count += 1;
|
|
12045
|
+
}
|
|
12046
|
+
return count;
|
|
12047
|
+
}
|
|
11787
12048
|
|
|
11788
12049
|
// src/goal/orchestrator.ts
|
|
11789
12050
|
import { createHash as createHash11 } from "crypto";
|
|
11790
12051
|
import { writeFile as writeFile9, mkdir as mkdir10 } from "fs/promises";
|
|
11791
|
-
import { join as
|
|
12052
|
+
import { join as join16 } from "path";
|
|
11792
12053
|
|
|
11793
12054
|
// src/goal/types.ts
|
|
11794
12055
|
import { z as z21 } from "zod";
|
|
@@ -11851,6 +12112,11 @@ var GoalStateSchema = z21.object({
|
|
|
11851
12112
|
planPath: z21.string().nullable(),
|
|
11852
12113
|
verifyRounds: z21.number().int().min(0),
|
|
11853
12114
|
workerRounds: z21.number().int().min(0),
|
|
12115
|
+
/** Worker output checkpoint used to resume verification without rerunning tools. */
|
|
12116
|
+
pendingVerification: z21.object({
|
|
12117
|
+
round: z21.number().int().positive(),
|
|
12118
|
+
finalResponse: z21.string().max(16e3)
|
|
12119
|
+
}).strict().nullable().optional(),
|
|
11854
12120
|
lastGaps: z21.array(GapSchema),
|
|
11855
12121
|
gapFingerprint: z21.string(),
|
|
11856
12122
|
stallStreak: z21.number().int().min(0),
|
|
@@ -11896,6 +12162,7 @@ ${objective}`
|
|
|
11896
12162
|
].join("\n");
|
|
11897
12163
|
}
|
|
11898
12164
|
async function runPlanner(options) {
|
|
12165
|
+
await options.onProgress?.();
|
|
11899
12166
|
const { adapter, model } = options.registry.get(options.modelId);
|
|
11900
12167
|
const prompt = buildPlannerPrompt(options.objective);
|
|
11901
12168
|
const messages = [{ role: "user", content: prompt }];
|
|
@@ -11907,6 +12174,7 @@ async function runPlanner(options) {
|
|
|
11907
12174
|
tools: [],
|
|
11908
12175
|
...options.signal === void 0 ? {} : { signal: options.signal }
|
|
11909
12176
|
})) {
|
|
12177
|
+
await options.onProgress?.();
|
|
11910
12178
|
if (event.type === "text") text += event.text;
|
|
11911
12179
|
else if (event.type === "error") throw event.error;
|
|
11912
12180
|
else if (event.type === "done") {
|
|
@@ -11953,7 +12221,7 @@ function extractJson(text) {
|
|
|
11953
12221
|
// src/goal/classifier.ts
|
|
11954
12222
|
import { createHash as createHash10 } from "crypto";
|
|
11955
12223
|
import { readFile as readFile10 } from "fs/promises";
|
|
11956
|
-
import { join as
|
|
12224
|
+
import { join as join15 } from "path";
|
|
11957
12225
|
function buildSkepticPrompt(evidence, plan, index) {
|
|
11958
12226
|
const criteriaList = plan.criteria.map((c) => ` ${c.id}. [${c.type}] ${c.description}`).join("\n");
|
|
11959
12227
|
return [
|
|
@@ -12013,7 +12281,7 @@ async function runClassifier(evidence, plan, options) {
|
|
|
12013
12281
|
const skepticResults = await Promise.all(
|
|
12014
12282
|
Array.from(
|
|
12015
12283
|
{ length: n },
|
|
12016
|
-
(_, i) => runOneSkeptic(evidence, plan, i + 1, adapter, model, options.signal)
|
|
12284
|
+
(_, i) => runOneSkeptic(evidence, plan, i + 1, adapter, model, options.signal, options.onProgress)
|
|
12017
12285
|
)
|
|
12018
12286
|
);
|
|
12019
12287
|
const verdicts = [];
|
|
@@ -12030,7 +12298,8 @@ async function runClassifier(evidence, plan, options) {
|
|
|
12030
12298
|
}
|
|
12031
12299
|
return aggregate(verdicts);
|
|
12032
12300
|
}
|
|
12033
|
-
async function runOneSkeptic(evidence, plan, index, adapter, model, signal) {
|
|
12301
|
+
async function runOneSkeptic(evidence, plan, index, adapter, model, signal, onProgress) {
|
|
12302
|
+
await onProgress?.();
|
|
12034
12303
|
const prompt = buildSkepticPrompt(evidence, plan, index);
|
|
12035
12304
|
const messages = [{ role: "user", content: prompt }];
|
|
12036
12305
|
let text = "";
|
|
@@ -12040,6 +12309,7 @@ async function runOneSkeptic(evidence, plan, index, adapter, model, signal) {
|
|
|
12040
12309
|
tools: [],
|
|
12041
12310
|
...signal === void 0 ? {} : { signal }
|
|
12042
12311
|
})) {
|
|
12312
|
+
await onProgress?.();
|
|
12043
12313
|
if (event.type === "text") text += event.text;
|
|
12044
12314
|
else if (event.type === "error") throw event.error;
|
|
12045
12315
|
else if (event.type === "done") break;
|
|
@@ -12130,7 +12400,7 @@ function computeFingerprint(gaps) {
|
|
|
12130
12400
|
}
|
|
12131
12401
|
async function collectEvidence(workspace, objective, finalResponse, priorGaps, contractHash2 = createHash10("sha256").update(objective).digest("hex"), hostVerification) {
|
|
12132
12402
|
const workspaceState = await collectWorkspaceState(workspace);
|
|
12133
|
-
const planPath2 =
|
|
12403
|
+
const planPath2 = join15(workspace, ".flavor", "goal-plan.md");
|
|
12134
12404
|
let planFile = null;
|
|
12135
12405
|
try {
|
|
12136
12406
|
planFile = await readFile10(planPath2, "utf8");
|
|
@@ -12194,7 +12464,7 @@ var GoalOrchestrator = class {
|
|
|
12194
12464
|
async *run(request) {
|
|
12195
12465
|
const timestamp2 = () => this.#options.now?.() ?? (/* @__PURE__ */ new Date()).toISOString();
|
|
12196
12466
|
const createdAt = timestamp2();
|
|
12197
|
-
|
|
12467
|
+
const state = {
|
|
12198
12468
|
id: this.#options.idFactory?.() ?? `goal-${Date.now().toString(36)}`,
|
|
12199
12469
|
objective: request.goal,
|
|
12200
12470
|
phase: "planning",
|
|
@@ -12203,6 +12473,7 @@ var GoalOrchestrator = class {
|
|
|
12203
12473
|
planPath: null,
|
|
12204
12474
|
verifyRounds: 0,
|
|
12205
12475
|
workerRounds: 0,
|
|
12476
|
+
pendingVerification: null,
|
|
12206
12477
|
lastGaps: [],
|
|
12207
12478
|
gapFingerprint: "",
|
|
12208
12479
|
stallStreak: 0,
|
|
@@ -12211,87 +12482,147 @@ var GoalOrchestrator = class {
|
|
|
12211
12482
|
createdAt,
|
|
12212
12483
|
updatedAt: createdAt
|
|
12213
12484
|
};
|
|
12485
|
+
yield* this.#execute(state, request.signal);
|
|
12486
|
+
}
|
|
12487
|
+
/**
|
|
12488
|
+
* Continue a goal whose process rotated onto a fresh heap. The persisted
|
|
12489
|
+
* state decides where to pick up; an interrupted round simply runs again.
|
|
12490
|
+
*/
|
|
12491
|
+
async *resume(request) {
|
|
12492
|
+
const persistence = this.#options.persistence;
|
|
12493
|
+
if (persistence === void 0 || persistence.load === void 0) {
|
|
12494
|
+
throw new Error("Goal persistence does not support resume");
|
|
12495
|
+
}
|
|
12496
|
+
let state;
|
|
12497
|
+
try {
|
|
12498
|
+
state = await persistence.load(request.goalId);
|
|
12499
|
+
} catch (error) {
|
|
12500
|
+
yield { type: "goal-failed", reason: message(error) };
|
|
12501
|
+
return;
|
|
12502
|
+
}
|
|
12503
|
+
if (state.status !== "active" && state.status !== "not_achieved") {
|
|
12504
|
+
yield { type: "goal-failed", reason: `Goal "${state.id}" is ${state.status}; it cannot be resumed.` };
|
|
12505
|
+
return;
|
|
12506
|
+
}
|
|
12507
|
+
yield { type: "goal-resumed", goalId: state.id, round: resumeStartRound(state) };
|
|
12508
|
+
yield* this.#execute(state, request.signal);
|
|
12509
|
+
}
|
|
12510
|
+
async *#execute(initialState, signal) {
|
|
12511
|
+
const timestamp2 = () => this.#options.now?.() ?? (/* @__PURE__ */ new Date()).toISOString();
|
|
12512
|
+
let state = initialState;
|
|
12214
12513
|
const persistState = async (patch) => {
|
|
12215
12514
|
state = { ...state, ...patch, updatedAt: timestamp2() };
|
|
12216
12515
|
await this.#options.persistence?.save(state);
|
|
12217
12516
|
};
|
|
12517
|
+
const memoryCheckpoint = async () => {
|
|
12518
|
+
await this.#options.onMemoryCheckpoint?.(state.id);
|
|
12519
|
+
if (memoryRotationActive()) throw new Error("Heap rotation requested");
|
|
12520
|
+
};
|
|
12218
12521
|
await persistState();
|
|
12219
12522
|
const workspace = this.#options.workspace;
|
|
12220
12523
|
let plan;
|
|
12221
12524
|
let planPath2;
|
|
12222
|
-
|
|
12223
|
-
request.signal.throwIfAborted();
|
|
12224
|
-
plan = await runPlanner({
|
|
12225
|
-
registry: this.#options.registry,
|
|
12226
|
-
modelId: this.#options.plannerModelId,
|
|
12227
|
-
objective: request.goal,
|
|
12228
|
-
signal: request.signal
|
|
12229
|
-
});
|
|
12230
|
-
planPath2 = await writePlanFile(workspace, plan);
|
|
12231
|
-
await persistState({ plan, planPath: planPath2, contractHash: contractHash(request.goal, plan) });
|
|
12232
|
-
yield { type: "goal-plan-created", plan, planPath: planPath2 };
|
|
12233
|
-
} catch (error) {
|
|
12234
|
-
const reason = `Goal planning failed: ${message(error)}`;
|
|
12235
|
-
await persistState({ phase: "complete", status: "failed" });
|
|
12236
|
-
yield { type: "goal-plan-failed", reason };
|
|
12237
|
-
yield { type: "goal-failed", reason };
|
|
12238
|
-
return;
|
|
12239
|
-
}
|
|
12240
|
-
let priorGaps = [];
|
|
12241
|
-
let priorFingerprint = "";
|
|
12242
|
-
let stallStreak = 0;
|
|
12243
|
-
for (let round = 1; round <= this.#options.maxRounds; round++) {
|
|
12244
|
-
request.signal.throwIfAborted();
|
|
12245
|
-
const workerPrompt = buildWorkerPrompt(request.goal, plan, priorGaps, round);
|
|
12246
|
-
await persistState({
|
|
12247
|
-
phase: "executing",
|
|
12248
|
-
status: "active",
|
|
12249
|
-
workerRounds: round,
|
|
12250
|
-
lastGaps: priorGaps,
|
|
12251
|
-
gapFingerprint: priorFingerprint,
|
|
12252
|
-
stallStreak
|
|
12253
|
-
});
|
|
12254
|
-
yield { type: "goal-worker-start", round };
|
|
12255
|
-
let finalResponse = "";
|
|
12256
|
-
let workerError;
|
|
12525
|
+
if (state.plan === null) {
|
|
12257
12526
|
try {
|
|
12258
|
-
|
|
12259
|
-
|
|
12260
|
-
|
|
12261
|
-
|
|
12262
|
-
|
|
12263
|
-
|
|
12264
|
-
signal
|
|
12265
|
-
|
|
12266
|
-
|
|
12267
|
-
|
|
12268
|
-
|
|
12269
|
-
|
|
12270
|
-
break;
|
|
12271
|
-
}
|
|
12272
|
-
}
|
|
12527
|
+
signal.throwIfAborted();
|
|
12528
|
+
await memoryCheckpoint();
|
|
12529
|
+
plan = await runPlanner({
|
|
12530
|
+
registry: this.#options.registry,
|
|
12531
|
+
modelId: this.#options.plannerModelId,
|
|
12532
|
+
objective: state.objective,
|
|
12533
|
+
signal,
|
|
12534
|
+
onProgress: memoryCheckpoint
|
|
12535
|
+
});
|
|
12536
|
+
planPath2 = await writePlanFile(workspace, plan);
|
|
12537
|
+
await persistState({ phase: "executing", plan, planPath: planPath2, contractHash: contractHash(state.objective, plan) });
|
|
12538
|
+
yield { type: "goal-plan-created", plan, planPath: planPath2 };
|
|
12273
12539
|
} catch (error) {
|
|
12274
|
-
|
|
12275
|
-
|
|
12276
|
-
if (workerError !== void 0) {
|
|
12277
|
-
const reason = `Worker error in round ${round}: ${workerError}`;
|
|
12540
|
+
if (memoryRotationActive()) return;
|
|
12541
|
+
const reason = `Goal planning failed: ${message(error)}`;
|
|
12278
12542
|
await persistState({ phase: "complete", status: "failed" });
|
|
12543
|
+
yield { type: "goal-plan-failed", reason };
|
|
12279
12544
|
yield { type: "goal-failed", reason };
|
|
12280
12545
|
return;
|
|
12281
12546
|
}
|
|
12282
|
-
|
|
12547
|
+
} else {
|
|
12548
|
+
plan = state.plan;
|
|
12549
|
+
planPath2 = state.planPath ?? await writePlanFile(workspace, plan);
|
|
12550
|
+
await persistState({ phase: "executing", status: "active", ...state.planPath === null ? { planPath: planPath2 } : {} });
|
|
12551
|
+
}
|
|
12552
|
+
let priorGaps = state.lastGaps;
|
|
12553
|
+
let priorFingerprint = state.gapFingerprint;
|
|
12554
|
+
let stallStreak = state.stallStreak;
|
|
12555
|
+
const startRound = resumeStartRound(state);
|
|
12556
|
+
for (let round = startRound; round <= this.#options.maxRounds; round++) {
|
|
12557
|
+
signal.throwIfAborted();
|
|
12558
|
+
await this.#options.onRoundBoundary?.(state.id);
|
|
12559
|
+
if (memoryRotationActive()) return;
|
|
12560
|
+
const pendingVerification = state.pendingVerification?.round === round ? state.pendingVerification : void 0;
|
|
12561
|
+
let finalResponse = pendingVerification?.finalResponse ?? "";
|
|
12562
|
+
if (pendingVerification === void 0) {
|
|
12563
|
+
const workerPrompt = buildWorkerPrompt(state.objective, plan, priorGaps, round);
|
|
12564
|
+
await persistState({
|
|
12565
|
+
phase: "executing",
|
|
12566
|
+
status: "active",
|
|
12567
|
+
workerRounds: round,
|
|
12568
|
+
pendingVerification: null,
|
|
12569
|
+
lastGaps: priorGaps,
|
|
12570
|
+
gapFingerprint: priorFingerprint,
|
|
12571
|
+
stallStreak
|
|
12572
|
+
});
|
|
12573
|
+
yield { type: "goal-worker-start", round };
|
|
12574
|
+
let workerError;
|
|
12575
|
+
let workerMemoryPressure = false;
|
|
12576
|
+
try {
|
|
12577
|
+
for await (const event of this.#options.runWorker({
|
|
12578
|
+
goal: state.objective,
|
|
12579
|
+
round,
|
|
12580
|
+
workspace,
|
|
12581
|
+
prompt: workerPrompt,
|
|
12582
|
+
priorGaps: formatPriorGaps(priorGaps),
|
|
12583
|
+
signal
|
|
12584
|
+
})) {
|
|
12585
|
+
yield { type: "goal-worker-event", round, event };
|
|
12586
|
+
if (event.type === "text") finalResponse = `${finalResponse}${event.text}`.slice(-16e3);
|
|
12587
|
+
if (event.type === "error") {
|
|
12588
|
+
workerError = event.error.message;
|
|
12589
|
+
if (event.error.code === "memory_pressure") workerMemoryPressure = true;
|
|
12590
|
+
break;
|
|
12591
|
+
}
|
|
12592
|
+
}
|
|
12593
|
+
} catch (error) {
|
|
12594
|
+
if (memoryRotationActive()) return;
|
|
12595
|
+
workerError = message(error);
|
|
12596
|
+
}
|
|
12597
|
+
if (workerMemoryPressure) return;
|
|
12598
|
+
if (workerError !== void 0) {
|
|
12599
|
+
const reason = `Worker error in round ${round}: ${workerError}`;
|
|
12600
|
+
await persistState({ phase: "complete", status: "failed" });
|
|
12601
|
+
yield { type: "goal-failed", reason };
|
|
12602
|
+
return;
|
|
12603
|
+
}
|
|
12604
|
+
await persistState({
|
|
12605
|
+
phase: "verifying",
|
|
12606
|
+
status: "active",
|
|
12607
|
+
pendingVerification: { round, finalResponse }
|
|
12608
|
+
});
|
|
12609
|
+
}
|
|
12283
12610
|
yield { type: "goal-verification-start", round };
|
|
12284
|
-
|
|
12611
|
+
await this.#options.onMemoryCheckpoint?.(state.id);
|
|
12612
|
+
if (memoryRotationActive()) return;
|
|
12613
|
+
const hostVerification = await this.#options.verifyHost?.(signal);
|
|
12614
|
+
await this.#options.onMemoryCheckpoint?.(state.id);
|
|
12615
|
+
if (memoryRotationActive()) return;
|
|
12285
12616
|
const evidence = await collectEvidence(
|
|
12286
12617
|
workspace,
|
|
12287
|
-
|
|
12618
|
+
state.objective,
|
|
12288
12619
|
finalResponse.slice(-4e3),
|
|
12289
12620
|
// Last 4000 chars of response
|
|
12290
12621
|
formatPriorGaps(priorGaps),
|
|
12291
12622
|
state.contractHash,
|
|
12292
12623
|
hostVerification
|
|
12293
12624
|
);
|
|
12294
|
-
await persistState({ evidenceRounds: [...state.evidenceRounds, {
|
|
12625
|
+
await persistState({ evidenceRounds: [...state.evidenceRounds.filter((item) => item.round !== round), {
|
|
12295
12626
|
round,
|
|
12296
12627
|
workspaceDiffHash: evidence.workspaceDiffHash,
|
|
12297
12628
|
...hostVerification === void 0 ? {} : { hostVerification }
|
|
@@ -12315,14 +12646,17 @@ var GoalOrchestrator = class {
|
|
|
12315
12646
|
modelId: this.#options.classifierModelId,
|
|
12316
12647
|
skepticCount: this.#options.skepticCount,
|
|
12317
12648
|
workspace,
|
|
12318
|
-
signal
|
|
12649
|
+
signal,
|
|
12650
|
+
onProgress: memoryCheckpoint
|
|
12319
12651
|
});
|
|
12320
12652
|
} catch (error) {
|
|
12653
|
+
if (memoryRotationActive()) return;
|
|
12321
12654
|
outcome = {
|
|
12322
12655
|
type: "blocked",
|
|
12323
12656
|
reason: `Classifier infrastructure error: ${message(error)}`
|
|
12324
12657
|
};
|
|
12325
12658
|
}
|
|
12659
|
+
await persistState({ verifyRounds: round, pendingVerification: null });
|
|
12326
12660
|
yield {
|
|
12327
12661
|
type: "goal-verdict",
|
|
12328
12662
|
round,
|
|
@@ -12382,9 +12716,15 @@ var GoalOrchestrator = class {
|
|
|
12382
12716
|
function contractHash(objective, plan) {
|
|
12383
12717
|
return createHash11("sha256").update(JSON.stringify({ objective, plan })).digest("hex");
|
|
12384
12718
|
}
|
|
12719
|
+
function resumeStartRound(state) {
|
|
12720
|
+
if (state.pendingVerification !== void 0 && state.pendingVerification !== null) {
|
|
12721
|
+
return state.pendingVerification.round;
|
|
12722
|
+
}
|
|
12723
|
+
return state.verifyRounds >= state.workerRounds ? state.workerRounds + 1 : Math.max(state.workerRounds, 1);
|
|
12724
|
+
}
|
|
12385
12725
|
async function writePlanFile(workspace, plan) {
|
|
12386
|
-
const dir =
|
|
12387
|
-
const planPath2 =
|
|
12726
|
+
const dir = join16(workspace, ".flavor");
|
|
12727
|
+
const planPath2 = join16(dir, "goal-plan.md");
|
|
12388
12728
|
await mkdir10(dir, { recursive: true });
|
|
12389
12729
|
const criteriaText = plan.criteria.map((c) => `- [${c.type}] AC-${c.id}: ${c.description}`).join("\n");
|
|
12390
12730
|
const content = [
|
|
@@ -12461,8 +12801,8 @@ function formatPriorGaps(gaps) {
|
|
|
12461
12801
|
// src/goal/store.ts
|
|
12462
12802
|
import { constants as constants4 } from "fs";
|
|
12463
12803
|
import { randomUUID as randomUUID11 } from "crypto";
|
|
12464
|
-
import { lstat as lstat5, mkdir as mkdir11, open as open5, rename as rename4, rm as rm6 } from "fs/promises";
|
|
12465
|
-
import { dirname as dirname9, join as
|
|
12804
|
+
import { lstat as lstat5, mkdir as mkdir11, open as open5, readFile as readFile11, rename as rename4, rm as rm6, stat as stat6 } from "fs/promises";
|
|
12805
|
+
import { dirname as dirname9, join as join17, relative as relative9, resolve as resolve12, sep as sep9 } from "path";
|
|
12466
12806
|
var DEFAULT_MAX_BYTES = 1024 * 1024;
|
|
12467
12807
|
var GoalStore = class {
|
|
12468
12808
|
#workspace;
|
|
@@ -12470,7 +12810,7 @@ var GoalStore = class {
|
|
|
12470
12810
|
#maxBytes;
|
|
12471
12811
|
constructor(options) {
|
|
12472
12812
|
this.#workspace = resolve12(options.workspace);
|
|
12473
|
-
this.#root =
|
|
12813
|
+
this.#root = join17(this.#workspace, ".flavor", "goals");
|
|
12474
12814
|
this.#maxBytes = options.maxBytes ?? DEFAULT_MAX_BYTES;
|
|
12475
12815
|
}
|
|
12476
12816
|
async save(input) {
|
|
@@ -12478,13 +12818,13 @@ var GoalStore = class {
|
|
|
12478
12818
|
await assertNoSymlink(this.#workspace, dirname9(this.#root));
|
|
12479
12819
|
await mkdir11(this.#root, { recursive: true, mode: 448 });
|
|
12480
12820
|
await assertNoSymlink(this.#workspace, this.#root);
|
|
12481
|
-
const target =
|
|
12821
|
+
const target = join17(this.#root, `${state.id}.json`);
|
|
12482
12822
|
const body = `${JSON.stringify(state, null, 2)}
|
|
12483
12823
|
`;
|
|
12484
12824
|
if (Buffer.byteLength(body) > this.#maxBytes) {
|
|
12485
12825
|
throw new Error(`Goal state exceeds maximum size of ${this.#maxBytes} bytes`);
|
|
12486
12826
|
}
|
|
12487
|
-
const temporary =
|
|
12827
|
+
const temporary = join17(this.#root, `.${state.id}.${process.pid}.${randomUUID11()}.tmp`);
|
|
12488
12828
|
let handle;
|
|
12489
12829
|
try {
|
|
12490
12830
|
handle = await open5(temporary, constants4.O_CREAT | constants4.O_EXCL | constants4.O_WRONLY, 384);
|
|
@@ -12498,6 +12838,28 @@ var GoalStore = class {
|
|
|
12498
12838
|
await rm6(temporary, { force: true }).catch(() => void 0);
|
|
12499
12839
|
}
|
|
12500
12840
|
}
|
|
12841
|
+
/** Reload a persisted goal so a rotated process can resume it. */
|
|
12842
|
+
async load(goalId) {
|
|
12843
|
+
if (!/^[A-Za-z0-9][A-Za-z0-9._-]{0,127}$/.test(goalId)) {
|
|
12844
|
+
throw new Error(`Goal "${goalId}" was not found`);
|
|
12845
|
+
}
|
|
12846
|
+
await assertNoSymlink(this.#workspace, dirname9(this.#root));
|
|
12847
|
+
await assertNoSymlink(this.#workspace, this.#root);
|
|
12848
|
+
const target = join17(this.#root, `${goalId}.json`);
|
|
12849
|
+
try {
|
|
12850
|
+
const metadata = await stat6(target);
|
|
12851
|
+
if (!metadata.isFile() || metadata.size > this.#maxBytes) {
|
|
12852
|
+
throw new Error("Goal state is not a bounded regular file");
|
|
12853
|
+
}
|
|
12854
|
+
const raw = await readFile11(target, "utf8");
|
|
12855
|
+
return GoalStateSchema.parse(JSON.parse(raw));
|
|
12856
|
+
} catch (error) {
|
|
12857
|
+
if (isCode4(error, "ENOENT")) throw new Error(`Goal "${goalId}" was not found`);
|
|
12858
|
+
const quarantine = `${target}.corrupt-${Date.now()}-${randomUUID11().slice(0, 8)}`;
|
|
12859
|
+
await rename4(target, quarantine).catch(() => void 0);
|
|
12860
|
+
throw new Error(`Goal "${goalId}" is corrupt and was quarantined`);
|
|
12861
|
+
}
|
|
12862
|
+
}
|
|
12501
12863
|
};
|
|
12502
12864
|
async function assertNoSymlink(root, target) {
|
|
12503
12865
|
let cursor = resolve12(target);
|
|
@@ -12524,8 +12886,8 @@ function isCode4(error, code) {
|
|
|
12524
12886
|
}
|
|
12525
12887
|
|
|
12526
12888
|
// src/loop/isolation.ts
|
|
12527
|
-
import { appendFile as appendFile2, mkdir as mkdir12, readFile as
|
|
12528
|
-
import { join as
|
|
12889
|
+
import { appendFile as appendFile2, mkdir as mkdir12, readFile as readFile12 } from "fs/promises";
|
|
12890
|
+
import { join as join18, resolve as resolve13 } from "path";
|
|
12529
12891
|
var CODE_CHANGE = /\b(?:fix|implement|add|update|change|modify|refactor|build|create|remove|delete|write|migrate)\b|修复|实现|添加|更新|修改|重构|创建|删除|迁移/i;
|
|
12530
12892
|
var READ_ONLY = /\b(?:review|analy[sz]e|inspect|explain|research|investigate|audit|summari[sz]e|report)\b|调研|分析|审查|检查|解释|研究|总结|报告/i;
|
|
12531
12893
|
function inferGoalIntent(goal) {
|
|
@@ -12548,8 +12910,8 @@ async function prepareLoopWorkspace(options) {
|
|
|
12548
12910
|
return { kind: "needs_human", reason: "The workspace has uncommitted changes that a new worktree would omit." };
|
|
12549
12911
|
}
|
|
12550
12912
|
options.signal?.throwIfAborted();
|
|
12551
|
-
const worktrees =
|
|
12552
|
-
const path =
|
|
12913
|
+
const worktrees = join18(root, ".worktrees");
|
|
12914
|
+
const path = join18(worktrees, `loop-${options.loopId}`);
|
|
12553
12915
|
const branch = `loop/${options.loopId}`;
|
|
12554
12916
|
try {
|
|
12555
12917
|
await ensureWorktreeDirectoryIgnored(root);
|
|
@@ -12579,12 +12941,12 @@ async function ensureWorktreeDirectoryIgnored(root) {
|
|
|
12579
12941
|
const commonDirectory = await git(root, ["rev-parse", "--git-common-dir"]);
|
|
12580
12942
|
if (commonDirectory.code !== 0) throw new Error(commonDirectory.stderr.trim() || "Git common directory is unavailable");
|
|
12581
12943
|
const gitDirectory = resolve13(root, commonDirectory.stdout.trim());
|
|
12582
|
-
const infoDirectory =
|
|
12583
|
-
const excludePath =
|
|
12944
|
+
const infoDirectory = join18(gitDirectory, "info");
|
|
12945
|
+
const excludePath = join18(infoDirectory, "exclude");
|
|
12584
12946
|
await mkdir12(infoDirectory, { recursive: true });
|
|
12585
12947
|
let existing = "";
|
|
12586
12948
|
try {
|
|
12587
|
-
existing = await
|
|
12949
|
+
existing = await readFile12(excludePath, "utf8");
|
|
12588
12950
|
} catch (error) {
|
|
12589
12951
|
if (!(typeof error === "object" && error !== null && "code" in error && error.code === "ENOENT")) throw error;
|
|
12590
12952
|
}
|
|
@@ -12595,8 +12957,8 @@ async function ensureWorktreeDirectoryIgnored(root) {
|
|
|
12595
12957
|
|
|
12596
12958
|
// src/loop/store.ts
|
|
12597
12959
|
import { constants as constants5 } from "fs";
|
|
12598
|
-
import { lstat as lstat6, mkdir as mkdir13, open as open6, readFile as
|
|
12599
|
-
import { dirname as dirname10, join as
|
|
12960
|
+
import { lstat as lstat6, mkdir as mkdir13, open as open6, readFile as readFile13, realpath as realpath8, rename as rename5, rm as rm7, stat as stat7 } from "fs/promises";
|
|
12961
|
+
import { dirname as dirname10, join as join19, relative as relative10, resolve as resolve14, sep as sep10 } from "path";
|
|
12600
12962
|
import { randomUUID as randomUUID12 } from "crypto";
|
|
12601
12963
|
var LOOP_ID = /^[A-Za-z0-9][A-Za-z0-9._-]{0,127}$/;
|
|
12602
12964
|
var DEFAULT_MAX_BYTES2 = 5 * 1024 * 1024;
|
|
@@ -12607,7 +12969,7 @@ var LoopStore = class {
|
|
|
12607
12969
|
#appendTails = /* @__PURE__ */ new Map();
|
|
12608
12970
|
constructor(options) {
|
|
12609
12971
|
this.#workspace = resolve14(options.workspace);
|
|
12610
|
-
this.#root =
|
|
12972
|
+
this.#root = join19(this.#workspace, ".flavor", "loops");
|
|
12611
12973
|
this.#maxBytes = options.maxBytes ?? DEFAULT_MAX_BYTES2;
|
|
12612
12974
|
}
|
|
12613
12975
|
async create(state) {
|
|
@@ -12624,11 +12986,11 @@ var LoopStore = class {
|
|
|
12624
12986
|
const state = LoopStateSchema.parse(input);
|
|
12625
12987
|
await this.#assertWorkspace(state.workspace);
|
|
12626
12988
|
const directory = await this.#prepare(state.loopId);
|
|
12627
|
-
const target =
|
|
12989
|
+
const target = join19(directory, "state.json");
|
|
12628
12990
|
const body = `${JSON.stringify(state, null, 2)}
|
|
12629
12991
|
`;
|
|
12630
12992
|
if (Buffer.byteLength(body) > this.#maxBytes) throw new Error(`Loop state exceeds maximum size of ${this.#maxBytes} bytes`);
|
|
12631
|
-
const temporary =
|
|
12993
|
+
const temporary = join19(directory, `.state.${process.pid}.${randomUUID12()}.tmp`);
|
|
12632
12994
|
let handle;
|
|
12633
12995
|
try {
|
|
12634
12996
|
handle = await open6(temporary, constants5.O_CREAT | constants5.O_EXCL | constants5.O_WRONLY, 384);
|
|
@@ -12644,11 +13006,11 @@ var LoopStore = class {
|
|
|
12644
13006
|
}
|
|
12645
13007
|
async load(loopId) {
|
|
12646
13008
|
const directory = await this.#safeExisting(loopId);
|
|
12647
|
-
const target =
|
|
13009
|
+
const target = join19(directory, "state.json");
|
|
12648
13010
|
try {
|
|
12649
|
-
const metadata = await
|
|
13011
|
+
const metadata = await stat7(target);
|
|
12650
13012
|
if (!metadata.isFile() || metadata.size > this.#maxBytes) throw new Error("Loop state is not a bounded regular file");
|
|
12651
|
-
const raw = await
|
|
13013
|
+
const raw = await readFile13(target, "utf8");
|
|
12652
13014
|
const state = LoopStateSchema.parse(JSON.parse(raw));
|
|
12653
13015
|
await this.#assertWorkspace(state.workspace);
|
|
12654
13016
|
return state;
|
|
@@ -12664,7 +13026,7 @@ var LoopStore = class {
|
|
|
12664
13026
|
const previous = this.#appendTails.get(event.loopId) ?? Promise.resolve();
|
|
12665
13027
|
const next = previous.catch(() => void 0).then(async () => {
|
|
12666
13028
|
const directory = await this.#prepare(event.loopId);
|
|
12667
|
-
const path =
|
|
13029
|
+
const path = join19(directory, "events.jsonl");
|
|
12668
13030
|
const handle = await open6(path, constants5.O_CREAT | constants5.O_APPEND | constants5.O_WRONLY, 384);
|
|
12669
13031
|
try {
|
|
12670
13032
|
await handle.writeFile(`${JSON.stringify(event)}
|
|
@@ -12681,7 +13043,7 @@ var LoopStore = class {
|
|
|
12681
13043
|
await assertNoSymlink2(this.#workspace, dirname10(this.#root));
|
|
12682
13044
|
await mkdir13(this.#root, { recursive: true, mode: 448 });
|
|
12683
13045
|
await assertNoSymlink2(this.#workspace, this.#root);
|
|
12684
|
-
const directory =
|
|
13046
|
+
const directory = join19(this.#root, loopId);
|
|
12685
13047
|
await mkdir13(directory, { recursive: true, mode: 448 });
|
|
12686
13048
|
await assertNoSymlink2(this.#workspace, directory);
|
|
12687
13049
|
return directory;
|
|
@@ -12689,7 +13051,7 @@ var LoopStore = class {
|
|
|
12689
13051
|
async #safeExisting(loopId) {
|
|
12690
13052
|
validateId(loopId);
|
|
12691
13053
|
await assertNoSymlink2(this.#workspace, this.#root);
|
|
12692
|
-
const directory =
|
|
13054
|
+
const directory = join19(this.#root, loopId);
|
|
12693
13055
|
await assertNoSymlink2(this.#workspace, directory);
|
|
12694
13056
|
return directory;
|
|
12695
13057
|
}
|
|
@@ -12737,13 +13099,13 @@ function isCode5(error, code) {
|
|
|
12737
13099
|
}
|
|
12738
13100
|
|
|
12739
13101
|
// src/loop/verifier.ts
|
|
12740
|
-
import { readFile as
|
|
12741
|
-
import { join as
|
|
13102
|
+
import { readFile as readFile14 } from "fs/promises";
|
|
13103
|
+
import { join as join21 } from "path";
|
|
12742
13104
|
|
|
12743
13105
|
// src/tools/shell.ts
|
|
12744
13106
|
import { spawn as spawn4 } from "child_process";
|
|
12745
13107
|
import { existsSync as existsSync4 } from "fs";
|
|
12746
|
-
import { basename as basename5, delimiter as delimiter2, isAbsolute as isAbsolute9, join as
|
|
13108
|
+
import { basename as basename5, delimiter as delimiter2, isAbsolute as isAbsolute9, join as join20, relative as relative11, resolve as resolve15, sep as sep11 } from "path";
|
|
12747
13109
|
import { z as z23 } from "zod";
|
|
12748
13110
|
|
|
12749
13111
|
// src/tools/jobs.ts
|
|
@@ -13406,7 +13768,7 @@ function decodeOutput(buffer) {
|
|
|
13406
13768
|
return new TextDecoder(encoding).decode(buffer);
|
|
13407
13769
|
}
|
|
13408
13770
|
function shellEnvironment(root, environment) {
|
|
13409
|
-
const localBin =
|
|
13771
|
+
const localBin = join20(root, "node_modules", ".bin");
|
|
13410
13772
|
if (!existsSync4(localBin)) return environment;
|
|
13411
13773
|
const pathKey = Object.keys(environment).find((key) => key.toLowerCase() === "path") ?? "PATH";
|
|
13412
13774
|
const current = environment[pathKey] ?? "";
|
|
@@ -13509,13 +13871,13 @@ var SCRIPT_PRIORITY = ["test", "typecheck", "lint", "build", "smoke:install"];
|
|
|
13509
13871
|
async function inferVerificationPlan(workspace) {
|
|
13510
13872
|
const commands = [];
|
|
13511
13873
|
const seen = /* @__PURE__ */ new Set();
|
|
13512
|
-
const packageJson = await optionalJson(
|
|
13874
|
+
const packageJson = await optionalJson(join21(workspace, "package.json"));
|
|
13513
13875
|
const scripts = isRecord3(packageJson?.scripts) ? packageJson.scripts : {};
|
|
13514
13876
|
for (const name of SCRIPT_PRIORITY) {
|
|
13515
13877
|
if (typeof scripts[name] !== "string" || isUnconditionalPassThrough(scripts[name])) continue;
|
|
13516
13878
|
add(commands, seen, npmScript(name));
|
|
13517
13879
|
}
|
|
13518
|
-
const flavor = await optionalText(
|
|
13880
|
+
const flavor = await optionalText(join21(workspace, "FLAVOR.md"));
|
|
13519
13881
|
if (flavor !== void 0) {
|
|
13520
13882
|
for (const match of flavor.matchAll(/^- `([^`]+)`\s*$/gm)) {
|
|
13521
13883
|
const parsed = parseTrustedCommand(match[1] ?? "");
|
|
@@ -13582,7 +13944,7 @@ function isUnconditionalPassThrough(script) {
|
|
|
13582
13944
|
}
|
|
13583
13945
|
async function optionalText(path) {
|
|
13584
13946
|
try {
|
|
13585
|
-
return await
|
|
13947
|
+
return await readFile14(path, "utf8");
|
|
13586
13948
|
} catch (error) {
|
|
13587
13949
|
if (isCode6(error, "ENOENT")) return void 0;
|
|
13588
13950
|
throw error;
|
|
@@ -13603,7 +13965,7 @@ function isCode6(error, code) {
|
|
|
13603
13965
|
|
|
13604
13966
|
// src/models/anthropic.ts
|
|
13605
13967
|
import Anthropic from "@anthropic-ai/sdk";
|
|
13606
|
-
import { readFile as
|
|
13968
|
+
import { readFile as readFile15 } from "fs/promises";
|
|
13607
13969
|
|
|
13608
13970
|
// src/utils/json.ts
|
|
13609
13971
|
function safeParseJson(input) {
|
|
@@ -13652,11 +14014,11 @@ function isEnvTruthy(value) {
|
|
|
13652
14014
|
|
|
13653
14015
|
// src/utils/log.ts
|
|
13654
14016
|
import { appendFile as appendFile3, mkdir as mkdir14 } from "fs/promises";
|
|
13655
|
-
import { dirname as dirname11, join as
|
|
14017
|
+
import { dirname as dirname11, join as join22, resolve as resolve16 } from "path";
|
|
13656
14018
|
var pending = Promise.resolve();
|
|
13657
14019
|
var usagePending = Promise.resolve();
|
|
13658
14020
|
function usageLogPath() {
|
|
13659
|
-
return process.env.FLAVOR_USAGE_FILE ??
|
|
14021
|
+
return process.env.FLAVOR_USAGE_FILE ?? join22(process.cwd(), ".flavor", "usage.jsonl");
|
|
13660
14022
|
}
|
|
13661
14023
|
var usageSessionId = "unknown";
|
|
13662
14024
|
var truncateOnNextAppend = false;
|
|
@@ -13685,7 +14047,7 @@ var AuditLogger = class {
|
|
|
13685
14047
|
#path;
|
|
13686
14048
|
#closed = false;
|
|
13687
14049
|
constructor(workspace) {
|
|
13688
|
-
this.#path =
|
|
14050
|
+
this.#path = join22(resolve16(workspace), ".flavor", "audit.jsonl");
|
|
13689
14051
|
}
|
|
13690
14052
|
get path() {
|
|
13691
14053
|
return this.#path;
|
|
@@ -13905,7 +14267,7 @@ var AnthropicModelAdapter = class {
|
|
|
13905
14267
|
source: {
|
|
13906
14268
|
type: "base64",
|
|
13907
14269
|
media_type: block.mediaType,
|
|
13908
|
-
data: (await
|
|
14270
|
+
data: (await readFile15(block.source.path)).toString("base64")
|
|
13909
14271
|
}
|
|
13910
14272
|
}));
|
|
13911
14273
|
if (message2.cacheBreakpoint && Array.isArray(content) && content.length > 0) {
|
|
@@ -14125,7 +14487,7 @@ function resolveCacheProfile(input) {
|
|
|
14125
14487
|
|
|
14126
14488
|
// src/models/openai.ts
|
|
14127
14489
|
import OpenAI from "openai";
|
|
14128
|
-
import { readFile as
|
|
14490
|
+
import { readFile as readFile16 } from "fs/promises";
|
|
14129
14491
|
async function toInput(message2) {
|
|
14130
14492
|
if (message2.role === "tool") {
|
|
14131
14493
|
if (!message2.toolCallId) throw new Error("Tool messages require toolCallId");
|
|
@@ -14137,7 +14499,7 @@ async function toInput(message2) {
|
|
|
14137
14499
|
}
|
|
14138
14500
|
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 } : {
|
|
14139
14501
|
type: "input_image",
|
|
14140
|
-
image_url: `data:${block.mediaType};base64,${(await
|
|
14502
|
+
image_url: `data:${block.mediaType};base64,${(await readFile16(block.source.path)).toString("base64")}`,
|
|
14141
14503
|
detail: "auto"
|
|
14142
14504
|
}));
|
|
14143
14505
|
return [
|
|
@@ -14559,7 +14921,7 @@ import {
|
|
|
14559
14921
|
|
|
14560
14922
|
// src/utils/version.ts
|
|
14561
14923
|
import { readFileSync as readFileSync2 } from "fs";
|
|
14562
|
-
import { dirname as dirname12, join as
|
|
14924
|
+
import { dirname as dirname12, join as join23 } from "path";
|
|
14563
14925
|
import { fileURLToPath } from "url";
|
|
14564
14926
|
var cached;
|
|
14565
14927
|
function packageVersion() {
|
|
@@ -14567,7 +14929,7 @@ function packageVersion() {
|
|
|
14567
14929
|
let directory = dirname12(fileURLToPath(import.meta.url));
|
|
14568
14930
|
for (; ; ) {
|
|
14569
14931
|
try {
|
|
14570
|
-
const manifest = JSON.parse(readFileSync2(
|
|
14932
|
+
const manifest = JSON.parse(readFileSync2(join23(directory, "package.json"), "utf8"));
|
|
14571
14933
|
if (typeof manifest.version === "string" && manifest.version.length > 0) {
|
|
14572
14934
|
cached = manifest.version;
|
|
14573
14935
|
return cached;
|
|
@@ -14994,8 +15356,8 @@ function isStoredToken(value) {
|
|
|
14994
15356
|
}
|
|
14995
15357
|
|
|
14996
15358
|
// src/permissions/policy.ts
|
|
14997
|
-
import { readFile as
|
|
14998
|
-
import { join as
|
|
15359
|
+
import { readFile as readFile17 } from "fs/promises";
|
|
15360
|
+
import { join as join24, resolve as resolve18 } from "path";
|
|
14999
15361
|
import { z as z25 } from "zod";
|
|
15000
15362
|
var DecisionSchema2 = z25.enum(["allow", "ask", "deny"]);
|
|
15001
15363
|
var PrefixSchema = z25.array(z25.string().min(1).max(1024)).min(1).max(64);
|
|
@@ -15032,15 +15394,15 @@ var CompiledPermissionPolicy = class {
|
|
|
15032
15394
|
async function loadPermissionPolicy(options) {
|
|
15033
15395
|
const sources = [
|
|
15034
15396
|
...options.managedPath === void 0 ? [] : [{ tier: "managed", path: resolve18(options.managedPath) }],
|
|
15035
|
-
{ tier: "user", path:
|
|
15036
|
-
{ tier: "project", path:
|
|
15037
|
-
{ tier: "local", path:
|
|
15397
|
+
{ tier: "user", path: join24(resolve18(options.home), ".flavor-code", "permissions.json") },
|
|
15398
|
+
{ tier: "project", path: join24(resolve18(options.workspace), ".flavor", "permissions.json") },
|
|
15399
|
+
{ tier: "local", path: join24(resolve18(options.workspace), ".flavor", "permissions.local.json") }
|
|
15038
15400
|
];
|
|
15039
15401
|
const rules = [];
|
|
15040
15402
|
for (const source of sources) {
|
|
15041
15403
|
let raw;
|
|
15042
15404
|
try {
|
|
15043
|
-
raw = await
|
|
15405
|
+
raw = await readFile17(source.path, "utf8");
|
|
15044
15406
|
} catch (error) {
|
|
15045
15407
|
if (isCode7(error, "ENOENT")) continue;
|
|
15046
15408
|
throw error;
|
|
@@ -15806,8 +16168,8 @@ function createSkillResourceTool(registry) {
|
|
|
15806
16168
|
|
|
15807
16169
|
// src/session/store.ts
|
|
15808
16170
|
import { constants as constants7 } from "fs";
|
|
15809
|
-
import { lstat as lstat8, mkdir as mkdir15, open as open7, readFile as
|
|
15810
|
-
import { basename as basename6, dirname as dirname14, join as
|
|
16171
|
+
import { lstat as lstat8, mkdir as mkdir15, open as open7, readFile as readFile18, readdir as readdir6, realpath as realpath10, rename as rename6, rm as rm8, stat as stat8 } from "fs/promises";
|
|
16172
|
+
import { basename as basename6, dirname as dirname14, join as join25, relative as relative13, resolve as resolve20, sep as sep13 } from "path";
|
|
15811
16173
|
import { randomUUID as randomUUID13 } from "crypto";
|
|
15812
16174
|
import { z as z27 } from "zod";
|
|
15813
16175
|
|
|
@@ -16503,7 +16865,7 @@ var SessionStore = class {
|
|
|
16503
16865
|
#maxSessions;
|
|
16504
16866
|
constructor(options) {
|
|
16505
16867
|
this.#workspace = resolve20(options.workspace);
|
|
16506
|
-
this.#sessions =
|
|
16868
|
+
this.#sessions = join25(this.#workspace, ".flavor", "sessions");
|
|
16507
16869
|
this.#maxBytes = options.maxBytes ?? DEFAULT_MAX_SESSION_BYTES;
|
|
16508
16870
|
if (!Number.isSafeInteger(this.#maxBytes) || this.#maxBytes < 256) throw new Error("maxBytes must be an integer of at least 256");
|
|
16509
16871
|
this.#maxSessions = options.maxSessions ?? 50;
|
|
@@ -16535,7 +16897,7 @@ var SessionStore = class {
|
|
|
16535
16897
|
const body = `${lines.join("\n")}
|
|
16536
16898
|
`;
|
|
16537
16899
|
if (Buffer.byteLength(body) > this.#maxBytes) throw new Error(`Session exceeds maximum size of ${this.#maxBytes} bytes`);
|
|
16538
|
-
const temporary =
|
|
16900
|
+
const temporary = join25(this.#sessions, `.${document.sessionId}.${process.pid}.${randomUUID13()}.tmp`);
|
|
16539
16901
|
let handle;
|
|
16540
16902
|
try {
|
|
16541
16903
|
handle = await open7(temporary, constants7.O_CREAT | constants7.O_EXCL | constants7.O_WRONLY, 384);
|
|
@@ -16554,7 +16916,7 @@ var SessionStore = class {
|
|
|
16554
16916
|
async acquireLease(sessionId) {
|
|
16555
16917
|
SessionIdSchema.parse(sessionId);
|
|
16556
16918
|
await this.#prepareDirectory();
|
|
16557
|
-
const path =
|
|
16919
|
+
const path = join25(this.#sessions, `.${sessionId}.lock`);
|
|
16558
16920
|
const token = JSON.stringify({ pid: process.pid, nonce: randomUUID13(), createdAt: (/* @__PURE__ */ new Date()).toISOString() });
|
|
16559
16921
|
for (let attempt = 0; attempt < 3; attempt += 1) {
|
|
16560
16922
|
let handle;
|
|
@@ -16572,7 +16934,7 @@ var SessionStore = class {
|
|
|
16572
16934
|
release: async () => {
|
|
16573
16935
|
if (released) return;
|
|
16574
16936
|
try {
|
|
16575
|
-
if (await
|
|
16937
|
+
if (await readFile18(path, "utf8") === token) await rm8(path, { force: true });
|
|
16576
16938
|
} catch (error) {
|
|
16577
16939
|
if (!isCode8(error, "ENOENT")) throw error;
|
|
16578
16940
|
}
|
|
@@ -16688,14 +17050,14 @@ var SessionStore = class {
|
|
|
16688
17050
|
if (isCode8(error, "ENOENT")) return;
|
|
16689
17051
|
throw error;
|
|
16690
17052
|
}
|
|
16691
|
-
const owner2 = await reclaimStaleSessionLock(
|
|
17053
|
+
const owner2 = await reclaimStaleSessionLock(join25(this.#sessions, `.${sessionId}.lock`));
|
|
16692
17054
|
if (owner2 !== void 0) throw new Error(`Session "${sessionId}" is active in process ${owner2} and cannot be deleted`);
|
|
16693
17055
|
const path = this.#path(sessionId);
|
|
16694
17056
|
try {
|
|
16695
17057
|
const metadata = await lstat8(path);
|
|
16696
17058
|
if (!metadata.isFile()) throw new Error("Session path is not a regular file");
|
|
16697
17059
|
await rm8(path);
|
|
16698
|
-
await rm8(
|
|
17060
|
+
await rm8(join25(this.#sessions, `${sessionId}.events.jsonl`), { force: true }).catch(() => void 0);
|
|
16699
17061
|
} catch (error) {
|
|
16700
17062
|
if (!isCode8(error, "ENOENT")) throw error;
|
|
16701
17063
|
}
|
|
@@ -16711,14 +17073,14 @@ var SessionStore = class {
|
|
|
16711
17073
|
if (entries.length <= this.#maxSessions) return;
|
|
16712
17074
|
let retained = 0;
|
|
16713
17075
|
for (const entry2 of entries) {
|
|
16714
|
-
const owner2 = await reclaimStaleSessionLock(
|
|
17076
|
+
const owner2 = await reclaimStaleSessionLock(join25(this.#sessions, `.${entry2.sessionId}.lock`));
|
|
16715
17077
|
if (owner2 !== void 0) continue;
|
|
16716
17078
|
if (retained < this.#maxSessions) {
|
|
16717
17079
|
retained += 1;
|
|
16718
17080
|
continue;
|
|
16719
17081
|
}
|
|
16720
17082
|
await rm8(this.#path(entry2.sessionId), { force: true }).catch(() => void 0);
|
|
16721
|
-
await rm8(
|
|
17083
|
+
await rm8(join25(this.#sessions, `${entry2.sessionId}.events.jsonl`), { force: true }).catch(() => void 0);
|
|
16722
17084
|
}
|
|
16723
17085
|
}
|
|
16724
17086
|
async #assertWorkspace(stored) {
|
|
@@ -16766,14 +17128,14 @@ function storedSessionVersion(raw) {
|
|
|
16766
17128
|
}
|
|
16767
17129
|
async function reclaimStaleSessionLock(path) {
|
|
16768
17130
|
try {
|
|
16769
|
-
const token = await
|
|
16770
|
-
const metadata = await
|
|
17131
|
+
const token = await readFile18(path, "utf8");
|
|
17132
|
+
const metadata = await stat8(path);
|
|
16771
17133
|
const owner2 = parseSessionLockOwner(token);
|
|
16772
17134
|
if (owner2 !== void 0 && isProcessAlive2(owner2)) return owner2;
|
|
16773
17135
|
if (owner2 === void 0 && Date.now() - metadata.mtimeMs <= INVALID_SESSION_LOCK_STALE_MS) {
|
|
16774
17136
|
throw new Error(`Session writer lock ${path} is incomplete or invalid; retry after the owning process exits`);
|
|
16775
17137
|
}
|
|
16776
|
-
if (await
|
|
17138
|
+
if (await readFile18(path, "utf8") !== token) return void 0;
|
|
16777
17139
|
const stale = `${path}.stale-${process.pid}-${randomUUID13()}`;
|
|
16778
17140
|
try {
|
|
16779
17141
|
await rename6(path, stale);
|
|
@@ -16806,10 +17168,10 @@ function isProcessAlive2(pid) {
|
|
|
16806
17168
|
}
|
|
16807
17169
|
}
|
|
16808
17170
|
async function boundedRead(path, maxBytes) {
|
|
16809
|
-
const metadata = await
|
|
17171
|
+
const metadata = await stat8(path);
|
|
16810
17172
|
if (!metadata.isFile()) throw new Error("Session path is not a regular file");
|
|
16811
17173
|
if (metadata.size > maxBytes) throw new Error(`Session exceeds maximum read size of ${maxBytes} bytes`);
|
|
16812
|
-
const value = await
|
|
17174
|
+
const value = await readFile18(path);
|
|
16813
17175
|
if (value.byteLength > maxBytes) throw new Error(`Session exceeds maximum read size of ${maxBytes} bytes`);
|
|
16814
17176
|
return value.toString("utf8");
|
|
16815
17177
|
}
|
|
@@ -17001,14 +17363,14 @@ function sanitize2(input, seen = /* @__PURE__ */ new WeakSet()) {
|
|
|
17001
17363
|
|
|
17002
17364
|
// src/session/tree.ts
|
|
17003
17365
|
import { randomUUID as randomUUID15 } from "crypto";
|
|
17004
|
-
import { mkdir as mkdir17, readFile as
|
|
17005
|
-
import { dirname as dirname16, join as
|
|
17366
|
+
import { mkdir as mkdir17, readFile as readFile20, rename as rename8, writeFile as writeFile11 } from "fs/promises";
|
|
17367
|
+
import { dirname as dirname16, join as join27, resolve as resolve22 } from "path";
|
|
17006
17368
|
|
|
17007
17369
|
// src/session/checkpoint.ts
|
|
17008
17370
|
import { createHash as createHash15, randomUUID as randomUUID14 } from "crypto";
|
|
17009
17371
|
import { execFile as execFile2 } from "child_process";
|
|
17010
|
-
import { lstat as lstat9, mkdir as mkdir16, open as open8, readFile as
|
|
17011
|
-
import { dirname as dirname15, join as
|
|
17372
|
+
import { lstat as lstat9, mkdir as mkdir16, open as open8, readFile as readFile19, readdir as readdir7, rename as rename7, rm as rm9, writeFile as writeFile10 } from "fs/promises";
|
|
17373
|
+
import { dirname as dirname15, join as join26, relative as relative14, resolve as resolve21, sep as sep14 } from "path";
|
|
17012
17374
|
var EXCLUDED = /* @__PURE__ */ new Set([".git", ".flavor", "node_modules", "dist", "build", "release", ".worktrees"]);
|
|
17013
17375
|
var MAX_FILE_BYTES = 16 * 1024 * 1024;
|
|
17014
17376
|
var MAX_TOTAL_BYTES = 128 * 1024 * 1024;
|
|
@@ -17021,9 +17383,9 @@ var WorkspaceCheckpointStore = class {
|
|
|
17021
17383
|
#maxTotalBytes;
|
|
17022
17384
|
constructor(options) {
|
|
17023
17385
|
this.#workspace = resolve21(options.workspace);
|
|
17024
|
-
this.#root =
|
|
17025
|
-
this.#objects =
|
|
17026
|
-
this.#manifests =
|
|
17386
|
+
this.#root = join26(this.#workspace, ".flavor", "checkpoints");
|
|
17387
|
+
this.#objects = join26(this.#root, "objects");
|
|
17388
|
+
this.#manifests = join26(this.#root, "manifests");
|
|
17027
17389
|
this.#maxFileBytes = options.maxFileBytes ?? MAX_FILE_BYTES;
|
|
17028
17390
|
this.#maxTotalBytes = options.maxTotalBytes ?? MAX_TOTAL_BYTES;
|
|
17029
17391
|
}
|
|
@@ -17041,9 +17403,9 @@ var WorkspaceCheckpointStore = class {
|
|
|
17041
17403
|
if (info.size > this.#maxFileBytes) throw new Error(`Checkpoint file exceeds size limit: ${path}`);
|
|
17042
17404
|
total += info.size;
|
|
17043
17405
|
if (total > this.#maxTotalBytes) throw new Error("Checkpoint exceeds total size limit");
|
|
17044
|
-
const content = await
|
|
17406
|
+
const content = await readFile19(absolute);
|
|
17045
17407
|
const digest = createHash15("sha256").update(content).digest("hex");
|
|
17046
|
-
const objectPath =
|
|
17408
|
+
const objectPath = join26(this.#objects, digest);
|
|
17047
17409
|
try {
|
|
17048
17410
|
const handle = await open8(objectPath, "wx");
|
|
17049
17411
|
try {
|
|
@@ -17063,12 +17425,12 @@ var WorkspaceCheckpointStore = class {
|
|
|
17063
17425
|
...label === void 0 || label.trim().length === 0 ? {} : { label: label.trim() },
|
|
17064
17426
|
files
|
|
17065
17427
|
};
|
|
17066
|
-
await atomicJson(
|
|
17428
|
+
await atomicJson(join26(this.#manifests, `${checkpoint.id}.json`), checkpoint);
|
|
17067
17429
|
return checkpoint;
|
|
17068
17430
|
}
|
|
17069
17431
|
async load(id) {
|
|
17070
17432
|
if (!/^checkpoint-[A-Za-z0-9-]+$/.test(id)) throw new Error("Invalid checkpoint id");
|
|
17071
|
-
const value = JSON.parse(await
|
|
17433
|
+
const value = JSON.parse(await readFile19(join26(this.#manifests, `${id}.json`), "utf8"));
|
|
17072
17434
|
if (value.version !== 1 || value.id !== id || !Array.isArray(value.files)) throw new Error("Invalid checkpoint manifest");
|
|
17073
17435
|
for (const file of value.files) {
|
|
17074
17436
|
contained(this.#workspace, file.path);
|
|
@@ -17080,7 +17442,7 @@ var WorkspaceCheckpointStore = class {
|
|
|
17080
17442
|
const checkpoint = await this.load(id);
|
|
17081
17443
|
const objects = /* @__PURE__ */ new Map();
|
|
17082
17444
|
for (const file of checkpoint.files) {
|
|
17083
|
-
const object = await
|
|
17445
|
+
const object = await readFile19(join26(this.#objects, file.digest));
|
|
17084
17446
|
if (object.length !== file.size || createHash15("sha256").update(object).digest("hex") !== file.digest) {
|
|
17085
17447
|
throw new Error(`Checkpoint object is corrupt: ${file.digest}`);
|
|
17086
17448
|
}
|
|
@@ -17103,7 +17465,7 @@ var WorkspaceCheckpointStore = class {
|
|
|
17103
17465
|
}
|
|
17104
17466
|
};
|
|
17105
17467
|
async function discoverFiles(workspace) {
|
|
17106
|
-
if (await exists2(
|
|
17468
|
+
if (await exists2(join26(workspace, ".git"))) {
|
|
17107
17469
|
const listed = await gitFiles(workspace);
|
|
17108
17470
|
return listed.filter((path) => !EXCLUDED.has(path.split("/")[0] ?? "")).sort();
|
|
17109
17471
|
}
|
|
@@ -17112,7 +17474,7 @@ async function discoverFiles(workspace) {
|
|
|
17112
17474
|
const entries = await readdir7(directory, { withFileTypes: true });
|
|
17113
17475
|
for (const entry2 of entries) {
|
|
17114
17476
|
if (EXCLUDED.has(entry2.name)) continue;
|
|
17115
|
-
const absolute =
|
|
17477
|
+
const absolute = join26(directory, entry2.name);
|
|
17116
17478
|
const path = normalizeRelative(relative14(workspace, absolute));
|
|
17117
17479
|
if (entry2.isSymbolicLink()) throw new Error(`Checkpoint refuses symbolic link: ${path}`);
|
|
17118
17480
|
if (entry2.isDirectory()) await walk(absolute);
|
|
@@ -17187,17 +17549,17 @@ var SessionHistory = class _SessionHistory {
|
|
|
17187
17549
|
#leafId;
|
|
17188
17550
|
#unrevert;
|
|
17189
17551
|
constructor(options, document) {
|
|
17190
|
-
this.#path =
|
|
17552
|
+
this.#path = join27(resolve22(options.workspace), ".flavor", "session-trees", options.sessionId, "tree.json");
|
|
17191
17553
|
this.#checkpoints = new WorkspaceCheckpointStore({ workspace: options.workspace });
|
|
17192
17554
|
this.#restoreContext = options.restoreContext;
|
|
17193
17555
|
this.#nodes = boundedNodes(document.nodes);
|
|
17194
17556
|
this.#leafId = document.leafId !== null && this.#nodes.some((node) => node.id === document.leafId) ? document.leafId : this.#nodes.at(-1)?.id ?? null;
|
|
17195
17557
|
}
|
|
17196
17558
|
static async open(options) {
|
|
17197
|
-
const path =
|
|
17559
|
+
const path = join27(resolve22(options.workspace), ".flavor", "session-trees", options.sessionId, "tree.json");
|
|
17198
17560
|
let document = { version: 1, sessionId: options.sessionId, leafId: null, nodes: [] };
|
|
17199
17561
|
try {
|
|
17200
|
-
const parsed = JSON.parse(await
|
|
17562
|
+
const parsed = JSON.parse(await readFile20(path, "utf8"));
|
|
17201
17563
|
validate(parsed, options.sessionId);
|
|
17202
17564
|
document = parsed;
|
|
17203
17565
|
} catch (error) {
|
|
@@ -17210,6 +17572,10 @@ var SessionHistory = class _SessionHistory {
|
|
|
17210
17572
|
get leafId() {
|
|
17211
17573
|
return this.#leafId;
|
|
17212
17574
|
}
|
|
17575
|
+
/** Cheap retained-node count for the rotation census (no cloning). */
|
|
17576
|
+
get nodeCount() {
|
|
17577
|
+
return this.#nodes.length;
|
|
17578
|
+
}
|
|
17213
17579
|
tree() {
|
|
17214
17580
|
return this.#nodes.map((node) => structuredClone(node));
|
|
17215
17581
|
}
|
|
@@ -17324,35 +17690,70 @@ function isCode10(error, code) {
|
|
|
17324
17690
|
return typeof error === "object" && error !== null && "code" in error && error.code === code;
|
|
17325
17691
|
}
|
|
17326
17692
|
|
|
17327
|
-
// src/utils/
|
|
17328
|
-
import {
|
|
17329
|
-
|
|
17330
|
-
|
|
17331
|
-
var
|
|
17332
|
-
|
|
17333
|
-
|
|
17693
|
+
// src/utils/rotation-census.ts
|
|
17694
|
+
import { mkdirSync as mkdirSync2, readdirSync, unlinkSync as unlinkSync2, writeFileSync } from "fs";
|
|
17695
|
+
import { join as join28 } from "path";
|
|
17696
|
+
import { writeHeapSnapshot } from "v8";
|
|
17697
|
+
var CENSUS_PREFIX = "rotation-census-";
|
|
17698
|
+
var SNAPSHOT_PREFIX = "heap-";
|
|
17699
|
+
var KEEP_CENSUS_FILES = 5;
|
|
17700
|
+
var KEEP_SNAPSHOT_FILES = 2;
|
|
17701
|
+
function censusHeapBlock(reading) {
|
|
17702
|
+
const usage = process.memoryUsage();
|
|
17703
|
+
const mb = (bytes) => Math.round(bytes / 1048576);
|
|
17704
|
+
return {
|
|
17705
|
+
heapUsedMb: mb(reading.heapUsed),
|
|
17706
|
+
heapLimitMb: mb(reading.heapLimit),
|
|
17707
|
+
rawRatio: Number(reading.ratio.toFixed(3)),
|
|
17708
|
+
...reading.verifiedRatio === void 0 ? {} : { verifiedRatio: Number(reading.verifiedRatio.toFixed(3)) },
|
|
17709
|
+
gcVerified: reading.gcVerified,
|
|
17710
|
+
rssMb: mb(usage.rss),
|
|
17711
|
+
externalMb: mb(usage.external),
|
|
17712
|
+
arrayBuffersMb: mb(usage.arrayBuffers),
|
|
17713
|
+
...reading.memoryLimit === void 0 ? {} : { memoryLimitMb: mb(reading.memoryLimit) },
|
|
17714
|
+
...reading.rssRatio === void 0 ? {} : { rssRatio: Number(reading.rssRatio.toFixed(3)) }
|
|
17715
|
+
};
|
|
17334
17716
|
}
|
|
17335
|
-
function
|
|
17336
|
-
|
|
17337
|
-
|
|
17717
|
+
function writeRotationCensus(workspace, census) {
|
|
17718
|
+
try {
|
|
17719
|
+
const directory = join28(workspace, ".flavor", "tmp");
|
|
17720
|
+
mkdirSync2(directory, { recursive: true });
|
|
17721
|
+
const path = join28(directory, `${CENSUS_PREFIX}${Date.now()}.json`);
|
|
17722
|
+
writeFileSync(path, `${JSON.stringify(census, null, 2)}
|
|
17723
|
+
`, { encoding: "utf8", mode: 384 });
|
|
17724
|
+
prune(directory, CENSUS_PREFIX, ".json", KEEP_CENSUS_FILES);
|
|
17725
|
+
return path;
|
|
17726
|
+
} catch {
|
|
17727
|
+
return void 0;
|
|
17728
|
+
}
|
|
17338
17729
|
}
|
|
17339
|
-
function
|
|
17730
|
+
function writeRotationHeapSnapshot(workspace) {
|
|
17340
17731
|
try {
|
|
17341
|
-
const
|
|
17342
|
-
|
|
17343
|
-
|
|
17344
|
-
|
|
17345
|
-
|
|
17732
|
+
const directory = join28(workspace, ".flavor", "tmp");
|
|
17733
|
+
mkdirSync2(directory, { recursive: true });
|
|
17734
|
+
const path = join28(directory, `${SNAPSHOT_PREFIX}${Date.now()}.heapsnapshot`);
|
|
17735
|
+
writeHeapSnapshot(path);
|
|
17736
|
+
prune(directory, SNAPSHOT_PREFIX, ".heapsnapshot", KEEP_SNAPSHOT_FILES);
|
|
17737
|
+
return path;
|
|
17346
17738
|
} catch {
|
|
17347
17739
|
return void 0;
|
|
17348
17740
|
}
|
|
17349
17741
|
}
|
|
17742
|
+
function prune(directory, prefix, suffix, keep) {
|
|
17743
|
+
const stale = readdirSync(directory).filter((name) => name.startsWith(prefix) && name.endsWith(suffix)).sort().slice(0, -keep);
|
|
17744
|
+
for (const name of stale) {
|
|
17745
|
+
try {
|
|
17746
|
+
unlinkSync2(join28(directory, name));
|
|
17747
|
+
} catch {
|
|
17748
|
+
}
|
|
17749
|
+
}
|
|
17750
|
+
}
|
|
17350
17751
|
|
|
17351
17752
|
// src/sleep/organizer.ts
|
|
17352
17753
|
import { constants as constants8 } from "fs";
|
|
17353
17754
|
import { randomUUID as randomUUID16 } from "crypto";
|
|
17354
17755
|
import { lstat as lstat10, mkdir as mkdir18, open as open9, readdir as readdir8, realpath as realpath11, rename as rename9, rm as rm10 } from "fs/promises";
|
|
17355
|
-
import { isAbsolute as isAbsolute11, join as
|
|
17756
|
+
import { isAbsolute as isAbsolute11, join as join29, relative as relative15, resolve as resolve23, sep as sep15 } from "path";
|
|
17356
17757
|
import { z as z28 } from "zod";
|
|
17357
17758
|
var DateKeySchema = z28.string().regex(/^\d{4}-\d{2}-\d{2}$/);
|
|
17358
17759
|
var ReviewSchema = z28.object({
|
|
@@ -17407,7 +17808,7 @@ var ProjectSleepOrganizer = class {
|
|
|
17407
17808
|
#generate;
|
|
17408
17809
|
constructor(options) {
|
|
17409
17810
|
this.#workspace = resolve23(options.workspace);
|
|
17410
|
-
this.#sleepDirectory =
|
|
17811
|
+
this.#sleepDirectory = join29(this.#workspace, ".flavor", "sleep");
|
|
17411
17812
|
this.#sessions = options.sessions ?? new SessionStore({ workspace: this.#workspace });
|
|
17412
17813
|
this.#generate = options.generate;
|
|
17413
17814
|
}
|
|
@@ -17416,7 +17817,7 @@ var ProjectSleepOrganizer = class {
|
|
|
17416
17817
|
signal.throwIfAborted();
|
|
17417
17818
|
await this.#prepareDirectory();
|
|
17418
17819
|
if (await this.#hasReport(date)) return { status: "exists", date };
|
|
17419
|
-
const lockPath =
|
|
17820
|
+
const lockPath = join29(this.#sleepDirectory, `.${date}.lock`);
|
|
17420
17821
|
let lock;
|
|
17421
17822
|
try {
|
|
17422
17823
|
lock = await open9(lockPath, constants8.O_CREAT | constants8.O_EXCL | constants8.O_WRONLY, 384);
|
|
@@ -17451,7 +17852,7 @@ Previous output was invalid. Repair it and return strict JSON only.`;
|
|
|
17451
17852
|
}
|
|
17452
17853
|
if (review === void 0) throw lastError;
|
|
17453
17854
|
const filename2 = `${date}-${filenameSummary(review.title)}.md`;
|
|
17454
|
-
const path =
|
|
17855
|
+
const path = join29(this.#sleepDirectory, filename2);
|
|
17455
17856
|
const markdown = renderSleepReport(date, review, documents);
|
|
17456
17857
|
await this.#atomicWrite(path, markdown);
|
|
17457
17858
|
return { status: "written", date, path, sessionCount: documents.length };
|
|
@@ -17493,7 +17894,7 @@ Previous output was invalid. Repair it and return strict JSON only.`;
|
|
|
17493
17894
|
});
|
|
17494
17895
|
}
|
|
17495
17896
|
async #prepareDirectory() {
|
|
17496
|
-
const flavorDirectory =
|
|
17897
|
+
const flavorDirectory = join29(this.#workspace, ".flavor");
|
|
17497
17898
|
await assertNotSymlink(flavorDirectory);
|
|
17498
17899
|
await mkdir18(flavorDirectory, { recursive: true, mode: 448 });
|
|
17499
17900
|
await assertNotSymlink(this.#sleepDirectory);
|
|
@@ -17507,7 +17908,7 @@ Previous output was invalid. Repair it and return strict JSON only.`;
|
|
|
17507
17908
|
return (await readdir8(this.#sleepDirectory, { withFileTypes: true })).some((entry2) => entry2.isFile() && entry2.name.startsWith(prefix) && entry2.name.endsWith(".md"));
|
|
17508
17909
|
}
|
|
17509
17910
|
async #atomicWrite(path, content) {
|
|
17510
|
-
const temporary =
|
|
17911
|
+
const temporary = join29(this.#sleepDirectory, `.${randomUUID16()}.tmp`);
|
|
17511
17912
|
let handle;
|
|
17512
17913
|
try {
|
|
17513
17914
|
handle = await open9(temporary, constants8.O_CREAT | constants8.O_EXCL | constants8.O_WRONLY, 384);
|
|
@@ -17881,7 +18282,7 @@ function computeSessionStats(documents) {
|
|
|
17881
18282
|
|
|
17882
18283
|
// src/tools/files.ts
|
|
17883
18284
|
import { constants as constants9 } from "fs";
|
|
17884
|
-
import { mkdir as mkdir19, open as open10, readFile as
|
|
18285
|
+
import { mkdir as mkdir19, open as open10, readFile as readFile21, realpath as realpath12, rename as rename10, rm as rm11, stat as stat9 } from "fs/promises";
|
|
17885
18286
|
import { dirname as dirname17, isAbsolute as isAbsolute12, relative as relative16, resolve as resolve24, sep as sep16 } from "path";
|
|
17886
18287
|
import { randomUUID as randomUUID17 } from "crypto";
|
|
17887
18288
|
import { z as z29 } from "zod";
|
|
@@ -18034,7 +18435,7 @@ function createReadTool(workspace, options = {}) {
|
|
|
18034
18435
|
execute: async (input, signal) => {
|
|
18035
18436
|
abortIfNeeded(signal);
|
|
18036
18437
|
const path = await guard.existing(input.path);
|
|
18037
|
-
const info = await
|
|
18438
|
+
const info = await stat9(path);
|
|
18038
18439
|
const maxBytes = input.maxBytes ?? MAX_READ_BYTES;
|
|
18039
18440
|
if (!Number.isSafeInteger(maxBytes) || maxBytes <= 0 || maxBytes > MAX_READ_BYTES) {
|
|
18040
18441
|
throw new Error(`maxBytes must be a positive integer no greater than ${MAX_READ_BYTES}`);
|
|
@@ -18243,7 +18644,7 @@ async function atomicWrite(path, content, signal, expectedVersion) {
|
|
|
18243
18644
|
const temporary = `${path}.${randomUUID17()}.tmp`;
|
|
18244
18645
|
let mode;
|
|
18245
18646
|
try {
|
|
18246
|
-
mode = (await
|
|
18647
|
+
mode = (await stat9(path)).mode & 511;
|
|
18247
18648
|
} catch (error) {
|
|
18248
18649
|
if (!isMissing2(error)) throw error;
|
|
18249
18650
|
}
|
|
@@ -18266,7 +18667,7 @@ async function atomicWrite(path, content, signal, expectedVersion) {
|
|
|
18266
18667
|
}
|
|
18267
18668
|
}
|
|
18268
18669
|
async function readText(path) {
|
|
18269
|
-
const contents = await
|
|
18670
|
+
const contents = await readFile21(path);
|
|
18270
18671
|
if (isBinary(contents)) throw new Error("Cannot edit binary file as text");
|
|
18271
18672
|
return decodeFileText(contents);
|
|
18272
18673
|
}
|
|
@@ -18292,7 +18693,7 @@ async function assertMutationVersion(path, expected, observations) {
|
|
|
18292
18693
|
}
|
|
18293
18694
|
async function fileVersion(path) {
|
|
18294
18695
|
try {
|
|
18295
|
-
return versionOfStat(await
|
|
18696
|
+
return versionOfStat(await stat9(path));
|
|
18296
18697
|
} catch (error) {
|
|
18297
18698
|
if (isMissing2(error)) return ABSENT_VERSION;
|
|
18298
18699
|
throw error;
|
|
@@ -18303,7 +18704,7 @@ function versionOfStat(info) {
|
|
|
18303
18704
|
}
|
|
18304
18705
|
async function readOptionalPresentationText(path) {
|
|
18305
18706
|
try {
|
|
18306
|
-
const contents = await
|
|
18707
|
+
const contents = await readFile21(path);
|
|
18307
18708
|
return isBinary(contents) ? { exists: true } : { exists: true, text: contents.toString("utf8") };
|
|
18308
18709
|
} catch (error) {
|
|
18309
18710
|
return isMissing2(error) ? { exists: false } : { exists: true };
|
|
@@ -18614,7 +19015,7 @@ function matchesLines(source, start, expected) {
|
|
|
18614
19015
|
|
|
18615
19016
|
// src/tools/search.ts
|
|
18616
19017
|
import { spawn as spawn5 } from "child_process";
|
|
18617
|
-
import { open as open11, opendir, stat as
|
|
19018
|
+
import { open as open11, opendir, stat as stat10 } from "fs/promises";
|
|
18618
19019
|
import { isAbsolute as isAbsolute13, relative as relative17, resolve as resolve25, sep as sep17 } from "path";
|
|
18619
19020
|
import { rgPath as bundledRgPath } from "@vscode/ripgrep";
|
|
18620
19021
|
import createIgnore from "ignore";
|
|
@@ -18962,7 +19363,7 @@ async function nodeFiles(root, start, signal, accept, limit, resources, ignoreBu
|
|
|
18962
19363
|
}
|
|
18963
19364
|
ignores.length = ruleCount;
|
|
18964
19365
|
}
|
|
18965
|
-
if ((await
|
|
19366
|
+
if ((await stat10(start)).isFile()) {
|
|
18966
19367
|
const path = normalizedRelative(root, start);
|
|
18967
19368
|
if (!isGitMetadataPath(root, start) && (includeIgnored || !ignored(path, false, ignores)) && accept(path)) output.push(path);
|
|
18968
19369
|
} else {
|
|
@@ -18997,7 +19398,7 @@ async function collectIgnoreLayers(root, start, signal, resources, ignoreBudget)
|
|
|
18997
19398
|
if (!ignored(path, true, layers)) await walk(absolute);
|
|
18998
19399
|
}
|
|
18999
19400
|
}
|
|
19000
|
-
if (!(await
|
|
19401
|
+
if (!(await stat10(start)).isFile()) await walk(start);
|
|
19001
19402
|
return layers;
|
|
19002
19403
|
}
|
|
19003
19404
|
async function loadIgnoreFiles(directory, base, rules, budget, signal) {
|
|
@@ -19116,7 +19517,7 @@ function scope(root, path = ".") {
|
|
|
19116
19517
|
async function resolveGrepPath(root, path = ".") {
|
|
19117
19518
|
const candidate = scope(root, path);
|
|
19118
19519
|
try {
|
|
19119
|
-
const info = await
|
|
19520
|
+
const info = await stat10(candidate);
|
|
19120
19521
|
if (!info.isFile() && !info.isDirectory()) throw new Error("Search path must be a file or directory");
|
|
19121
19522
|
} catch (error) {
|
|
19122
19523
|
if (error.code === "ENOENT") {
|
|
@@ -19657,7 +20058,7 @@ function charset(contentType) {
|
|
|
19657
20058
|
// src/tools/lsp.ts
|
|
19658
20059
|
import { spawn as spawn6 } from "child_process";
|
|
19659
20060
|
import { existsSync as existsSync5 } from "fs";
|
|
19660
|
-
import { access as access2, constants as constants10, readFile as
|
|
20061
|
+
import { access as access2, constants as constants10, readFile as readFile22 } from "fs/promises";
|
|
19661
20062
|
import { basename as basename7, dirname as dirname18, extname as extname3, isAbsolute as isAbsolute14, relative as relative18, resolve as resolve26, sep as sep18 } from "path";
|
|
19662
20063
|
import { fileURLToPath as fileURLToPath2, pathToFileURL as pathToFileURL3 } from "url";
|
|
19663
20064
|
import { z as z32 } from "zod";
|
|
@@ -19851,7 +20252,7 @@ var LspConnection = class {
|
|
|
19851
20252
|
const url = new URL(uri);
|
|
19852
20253
|
if (url.protocol !== "file:") return;
|
|
19853
20254
|
const filePath = fileURLToPath2(url);
|
|
19854
|
-
const text = await
|
|
20255
|
+
const text = await readFile22(filePath, "utf8");
|
|
19855
20256
|
const current = this.#documents.get(uri);
|
|
19856
20257
|
if (current === void 0) {
|
|
19857
20258
|
this.#documents.set(uri, { text, version: 1 });
|
|
@@ -20217,19 +20618,19 @@ async function changeSummary(workspace) {
|
|
|
20217
20618
|
};
|
|
20218
20619
|
}
|
|
20219
20620
|
async function stagedDiff(workspace, maxChars = 96e3) {
|
|
20220
|
-
const
|
|
20621
|
+
const stat21 = await git2(workspace, ["diff", "--staged", "--stat"]);
|
|
20221
20622
|
const diff = await git2(workspace, ["diff", "--staged", "--no-ext-diff", "--no-color"]);
|
|
20222
20623
|
if (!diff.ok) throw gitFailure(diff, "diff --staged");
|
|
20223
20624
|
const text = diff.stdout;
|
|
20224
20625
|
return {
|
|
20225
20626
|
diff: text.length > maxChars ? `${text.slice(0, maxChars)}
|
|
20226
20627
|
\u2026[diff truncated]` : text,
|
|
20227
|
-
stat:
|
|
20628
|
+
stat: stat21.stdout.trim(),
|
|
20228
20629
|
truncated: text.length > maxChars
|
|
20229
20630
|
};
|
|
20230
20631
|
}
|
|
20231
20632
|
async function uncommittedDiff(workspace, maxChars = 96e3) {
|
|
20232
|
-
const
|
|
20633
|
+
const stat21 = await git2(workspace, ["diff", "HEAD", "--stat"]);
|
|
20233
20634
|
const diff = await git2(workspace, ["diff", "HEAD", "--no-ext-diff", "--no-color"]);
|
|
20234
20635
|
if (!diff.ok && !diff.stderr.includes("unknown revision")) throw gitFailure(diff, "diff HEAD");
|
|
20235
20636
|
const summary2 = await changeSummary(workspace);
|
|
@@ -20237,7 +20638,7 @@ async function uncommittedDiff(workspace, maxChars = 96e3) {
|
|
|
20237
20638
|
return {
|
|
20238
20639
|
diff: text.length > maxChars ? `${text.slice(0, maxChars)}
|
|
20239
20640
|
\u2026[diff truncated]` : text,
|
|
20240
|
-
stat:
|
|
20641
|
+
stat: stat21.stdout.trim(),
|
|
20241
20642
|
untracked: summary2.untrackedFiles
|
|
20242
20643
|
};
|
|
20243
20644
|
}
|
|
@@ -20534,17 +20935,17 @@ ${explanation}` : "";
|
|
|
20534
20935
|
}
|
|
20535
20936
|
|
|
20536
20937
|
// src/desktop/astgraph-service.ts
|
|
20537
|
-
import { stat as
|
|
20938
|
+
import { stat as stat11 } from "fs/promises";
|
|
20538
20939
|
import { createRequire as nodeCreateRequire } from "module";
|
|
20539
|
-
import { join as
|
|
20940
|
+
import { join as join30, resolve as resolve27 } from "path";
|
|
20540
20941
|
var FIELDS = "id, kind, name, qualified_name, file_path, language, start_line, end_line, signature";
|
|
20541
20942
|
var DesktopAstGraphService = class {
|
|
20542
20943
|
#path;
|
|
20543
20944
|
constructor(workspace) {
|
|
20544
|
-
this.#path =
|
|
20945
|
+
this.#path = join30(resolve27(workspace), ".flavor", "astgraph", "index.db");
|
|
20545
20946
|
}
|
|
20546
20947
|
async status() {
|
|
20547
|
-
if ((await
|
|
20948
|
+
if ((await stat11(this.#path).catch(() => void 0))?.isFile() !== true) {
|
|
20548
20949
|
return { available: false, path: this.#path, files: 0, nodes: 0, edges: 0 };
|
|
20549
20950
|
}
|
|
20550
20951
|
return this.#read((db) => {
|
|
@@ -20824,8 +21225,8 @@ function createTodoWriteTool() {
|
|
|
20824
21225
|
|
|
20825
21226
|
// src/tools/managed.ts
|
|
20826
21227
|
import { randomUUID as randomUUID18 } from "crypto";
|
|
20827
|
-
import { link, lstat as lstat11, mkdir as mkdir20, readFile as
|
|
20828
|
-
import { dirname as dirname19, join as
|
|
21228
|
+
import { link, lstat as lstat11, mkdir as mkdir20, readFile as readFile23, readdir as readdir9, unlink as unlink3, writeFile as writeFile12 } from "fs/promises";
|
|
21229
|
+
import { dirname as dirname19, join as join31, resolve as resolve29 } from "path";
|
|
20829
21230
|
import { z as z37 } from "zod";
|
|
20830
21231
|
var MANAGED_TOOL_VERSION = 1;
|
|
20831
21232
|
var MAX_RECORD_BYTES = 256 * 1024;
|
|
@@ -20868,8 +21269,8 @@ var ManagedToolStore = class {
|
|
|
20868
21269
|
#diagnostics = [];
|
|
20869
21270
|
constructor(options) {
|
|
20870
21271
|
this.#workspace = resolve29(options.workspace);
|
|
20871
|
-
this.#projectRoot =
|
|
20872
|
-
this.#globalRoot =
|
|
21272
|
+
this.#projectRoot = join31(this.#workspace, ".flavor", "tools");
|
|
21273
|
+
this.#globalRoot = join31(resolve29(options.home), ".flavor-code", "tools");
|
|
20873
21274
|
this.#now = options.now ?? (() => (/* @__PURE__ */ new Date()).toISOString());
|
|
20874
21275
|
}
|
|
20875
21276
|
get diagnostics() {
|
|
@@ -20916,7 +21317,7 @@ var ManagedToolStore = class {
|
|
|
20916
21317
|
version: MANAGED_TOOL_VERSION,
|
|
20917
21318
|
createdAt: this.#now()
|
|
20918
21319
|
});
|
|
20919
|
-
const path =
|
|
21320
|
+
const path = join31(this.directory(input.scope), filename(input.name));
|
|
20920
21321
|
const definition = compileDefinition(record3, input.scope, this.#workspace);
|
|
20921
21322
|
await writeExclusive(path, `${JSON.stringify(record3, void 0, 2)}
|
|
20922
21323
|
`);
|
|
@@ -20941,7 +21342,7 @@ var ManagedToolStore = class {
|
|
|
20941
21342
|
if (!parsed.success) return [];
|
|
20942
21343
|
const matches = this.#matching(parsed.data.name).filter((entry2) => parsed.data.scope === void 0 || entry2.scope === parsed.data.scope);
|
|
20943
21344
|
if (matches.length > 0) return matches.map((entry2) => entry2.path);
|
|
20944
|
-
return [
|
|
21345
|
+
return [join31(this.directory(parsed.data.scope ?? "project"), filename(parsed.data.name))];
|
|
20945
21346
|
}
|
|
20946
21347
|
async #loadDirectory(scope2) {
|
|
20947
21348
|
const root = this.directory(scope2);
|
|
@@ -20954,12 +21355,12 @@ var ManagedToolStore = class {
|
|
|
20954
21355
|
return;
|
|
20955
21356
|
}
|
|
20956
21357
|
for (const name of names) {
|
|
20957
|
-
const path =
|
|
21358
|
+
const path = join31(root, name);
|
|
20958
21359
|
try {
|
|
20959
21360
|
const info = await lstat11(path);
|
|
20960
21361
|
if (!info.isFile() || info.isSymbolicLink()) throw new Error("Managed tool record must be a regular file");
|
|
20961
21362
|
if (info.size > MAX_RECORD_BYTES) throw new Error(`Managed tool record exceeds ${MAX_RECORD_BYTES} bytes`);
|
|
20962
|
-
const record3 = ManagedToolRecordSchema.parse(JSON.parse(await
|
|
21363
|
+
const record3 = ManagedToolRecordSchema.parse(JSON.parse(await readFile23(path, "utf8")));
|
|
20963
21364
|
if (record3.scope !== scope2) throw new Error(`Managed tool record scope must match its ${scope2} directory`);
|
|
20964
21365
|
const key = scopedKey(scope2, record3.name);
|
|
20965
21366
|
if (this.#entries.has(key)) throw new Error(`Duplicate managed tool name in ${scope2} scope: ${record3.name}`);
|
|
@@ -21868,6 +22269,7 @@ var FlavorSession = class {
|
|
|
21868
22269
|
#pendingCoWorkStarts = [];
|
|
21869
22270
|
#activeSteeringDurableIds = /* @__PURE__ */ new Set();
|
|
21870
22271
|
#durableResumeStarted = false;
|
|
22272
|
+
#continuationResumeStarted = false;
|
|
21871
22273
|
#permissionBaseline;
|
|
21872
22274
|
#activeSubmission;
|
|
21873
22275
|
constructor(services) {
|
|
@@ -21888,6 +22290,10 @@ var FlavorSession = class {
|
|
|
21888
22290
|
queueSnapshot() {
|
|
21889
22291
|
return this.#queue.snapshot();
|
|
21890
22292
|
}
|
|
22293
|
+
/** True when start() admitted a persisted /loop or /goal continuation. */
|
|
22294
|
+
get rotationContinuationResumed() {
|
|
22295
|
+
return this.#continuationResumeStarted;
|
|
22296
|
+
}
|
|
21891
22297
|
clearQueue() {
|
|
21892
22298
|
const snapshot4 = this.#queue.clear();
|
|
21893
22299
|
for (const kind of ["steer", "followUp"]) {
|
|
@@ -22037,6 +22443,7 @@ var FlavorSession = class {
|
|
|
22037
22443
|
if (decision.additionalContext !== void 0) await this.#services.addContext?.(decision.additionalContext);
|
|
22038
22444
|
this.#started = true;
|
|
22039
22445
|
this.#resumeDurableQueue();
|
|
22446
|
+
this.#resumeRotationContinuation();
|
|
22040
22447
|
});
|
|
22041
22448
|
return this.#startPromise;
|
|
22042
22449
|
}
|
|
@@ -22119,7 +22526,8 @@ var FlavorSession = class {
|
|
|
22119
22526
|
skillNames,
|
|
22120
22527
|
this.#services.managedToolCommands().map(({ name }) => name)
|
|
22121
22528
|
);
|
|
22122
|
-
if (
|
|
22529
|
+
if (submission.continuation !== void 0) await this.#runContinuation(submission.continuation, controller2.signal);
|
|
22530
|
+
else if (command !== null) await this.#dispatch(command, controller2.signal);
|
|
22123
22531
|
else for await (const event of this.#services.run(prompt, controller2.signal, {
|
|
22124
22532
|
getSteeringMessages: () => this.#drainMessages("steer", true).map((item) => item.text),
|
|
22125
22533
|
...decision.additionalContext === void 0 ? {} : { additionalContext: decision.additionalContext },
|
|
@@ -22377,6 +22785,33 @@ Address this now (for example run the required verification or fix), then finish
|
|
|
22377
22785
|
});
|
|
22378
22786
|
}
|
|
22379
22787
|
}
|
|
22788
|
+
/**
|
|
22789
|
+
* After a heap rotation the relaunched process inherits the same session and
|
|
22790
|
+
* a marker continuation naming the /loop or /goal that was in flight. Resume
|
|
22791
|
+
* it once here so a multi-day autonomous run survives the rotation instead of
|
|
22792
|
+
* silently stopping at the boundary.
|
|
22793
|
+
*/
|
|
22794
|
+
#resumeRotationContinuation() {
|
|
22795
|
+
if (this.#continuationResumeStarted || this.#closed) return;
|
|
22796
|
+
const continuation = this.#services.continuation?.();
|
|
22797
|
+
if (continuation === void 0) return;
|
|
22798
|
+
const supported = continuation.kind === "loop" ? this.#services.resumeLoop !== void 0 : this.#services.resumeGoal !== void 0;
|
|
22799
|
+
if (!supported) return;
|
|
22800
|
+
this.#continuationResumeStarted = true;
|
|
22801
|
+
const displayText = `Resuming the interrupted ${continuation.kind} ${continuation.id} after a heap rotation.`;
|
|
22802
|
+
this.#notice(displayText);
|
|
22803
|
+
this.#enqueueNormalizedSubmission({ text: displayText, displayText, continuation }).catch((error) => {
|
|
22804
|
+
this.#services.output({ type: "error", error: { code: "unknown", message: message(error) } });
|
|
22805
|
+
});
|
|
22806
|
+
}
|
|
22807
|
+
async #runContinuation(continuation, signal) {
|
|
22808
|
+
const stream = continuation.kind === "loop" ? this.#services.resumeLoop?.(continuation.id, signal) : this.#services.resumeGoal?.(continuation.id, signal);
|
|
22809
|
+
if (stream === void 0) {
|
|
22810
|
+
this.#notice(`The runtime cannot resume the interrupted ${continuation.kind} ${continuation.id}; start it again with /${continuation.kind}.`);
|
|
22811
|
+
return;
|
|
22812
|
+
}
|
|
22813
|
+
for await (const event of stream) this.#services.output(event);
|
|
22814
|
+
}
|
|
22380
22815
|
#ackDurable(submission) {
|
|
22381
22816
|
if (submission.durableId === void 0) return;
|
|
22382
22817
|
this.#services.durableQueue?.ack(submission.durableId);
|
|
@@ -22534,9 +22969,9 @@ function requiredPals(service) {
|
|
|
22534
22969
|
|
|
22535
22970
|
// src/doctor.ts
|
|
22536
22971
|
import { constants as constants11 } from "fs";
|
|
22537
|
-
import { access as access3, lstat as lstat12, readFile as
|
|
22972
|
+
import { access as access3, lstat as lstat12, readFile as readFile24, readdir as readdir10, stat as stat12 } from "fs/promises";
|
|
22538
22973
|
import { homedir } from "os";
|
|
22539
|
-
import { isAbsolute as isAbsolute16, join as
|
|
22974
|
+
import { isAbsolute as isAbsolute16, join as join32, relative as relative20, resolve as resolve30, sep as sep20 } from "path";
|
|
22540
22975
|
import { rgPath as bundledRgPath2 } from "@vscode/ripgrep";
|
|
22541
22976
|
|
|
22542
22977
|
// src/utils/semver.ts
|
|
@@ -22583,7 +23018,7 @@ async function runDoctor(options = {}, dependencies2 = {}) {
|
|
|
22583
23018
|
const checks = [];
|
|
22584
23019
|
checks.push(runtimeCheck(nodeVersion, platform, arch));
|
|
22585
23020
|
checks.push(await directoryCheck("workspace", workspace, false));
|
|
22586
|
-
checks.push(await directoryCheck("state directory",
|
|
23021
|
+
checks.push(await directoryCheck("state directory", join32(home, ".flavor-code"), true, home));
|
|
22587
23022
|
let loaded;
|
|
22588
23023
|
try {
|
|
22589
23024
|
loaded = await (dependencies2.loadConfig ?? loadConfig)({
|
|
@@ -22609,8 +23044,8 @@ async function runDoctor(options = {}, dependencies2 = {}) {
|
|
|
22609
23044
|
checks.push({ name: "shell", status: "fail", message: errorMessage3(error) });
|
|
22610
23045
|
}
|
|
22611
23046
|
checks.push(await pluginCheck([
|
|
22612
|
-
|
|
22613
|
-
|
|
23047
|
+
join32(home, ".flavor-code", "plugins"),
|
|
23048
|
+
join32(workspace, ".flavor", "plugins")
|
|
22614
23049
|
]));
|
|
22615
23050
|
try {
|
|
22616
23051
|
const latest = await (dependencies2.fetchLatestVersion ?? fetchLatestVersion)({
|
|
@@ -22658,7 +23093,7 @@ function runtimeCheck(version, platform, arch) {
|
|
|
22658
23093
|
}
|
|
22659
23094
|
async function directoryCheck(name, path, mayBeCreated, parent = path) {
|
|
22660
23095
|
try {
|
|
22661
|
-
const info = await
|
|
23096
|
+
const info = await stat12(path);
|
|
22662
23097
|
if (!info.isDirectory()) return { name, status: "fail", message: `${path} is not a directory` };
|
|
22663
23098
|
await access3(path, constants11.R_OK | constants11.W_OK);
|
|
22664
23099
|
return { name, status: "pass", message: `${path} is readable and writable` };
|
|
@@ -22727,7 +23162,7 @@ async function pluginCheck(directories) {
|
|
|
22727
23162
|
try {
|
|
22728
23163
|
const rootInfo = await lstat12(root);
|
|
22729
23164
|
if (rootInfo.isSymbolicLink()) throw new Error("plugin directory must not be a symbolic link");
|
|
22730
|
-
const manifest = PluginManifestSchema.parse(JSON.parse(await
|
|
23165
|
+
const manifest = PluginManifestSchema.parse(JSON.parse(await readFile24(join32(root, "flavor-plugin.json"), "utf8")));
|
|
22731
23166
|
const main = resolve30(root, manifest.main);
|
|
22732
23167
|
const delta = relative20(root, main);
|
|
22733
23168
|
if (delta === ".." || delta.startsWith(`..${sep20}`) || isAbsolute16(delta)) throw new Error("plugin entry escapes its root");
|
|
@@ -22794,8 +23229,8 @@ var LANG_LABELS = /* @__PURE__ */ new Map([
|
|
|
22794
23229
|
]);
|
|
22795
23230
|
|
|
22796
23231
|
// src/utils/crash-guard.ts
|
|
22797
|
-
import { mkdirSync as
|
|
22798
|
-
import { join as
|
|
23232
|
+
import { mkdirSync as mkdirSync3, writeFileSync as writeFileSync2 } from "fs";
|
|
23233
|
+
import { join as join33 } from "path";
|
|
22799
23234
|
var installed = false;
|
|
22800
23235
|
var crashing = false;
|
|
22801
23236
|
var crashCleanups = /* @__PURE__ */ new Set();
|
|
@@ -22826,7 +23261,7 @@ function installCrashGuard(options = {}) {
|
|
|
22826
23261
|
installed = true;
|
|
22827
23262
|
const crashLogDir = () => {
|
|
22828
23263
|
try {
|
|
22829
|
-
return
|
|
23264
|
+
return join33((options.workspace ?? process.cwd)(), ".flavor");
|
|
22830
23265
|
} catch {
|
|
22831
23266
|
return process.cwd();
|
|
22832
23267
|
}
|
|
@@ -22834,9 +23269,9 @@ function installCrashGuard(options = {}) {
|
|
|
22834
23269
|
const writeCrashLog = (kind, detail) => {
|
|
22835
23270
|
try {
|
|
22836
23271
|
const dir = crashLogDir();
|
|
22837
|
-
|
|
22838
|
-
const path =
|
|
22839
|
-
|
|
23272
|
+
mkdirSync3(dir, { recursive: true });
|
|
23273
|
+
const path = join33(dir, `crash-${(/* @__PURE__ */ new Date()).toISOString().replace(/[^0-9]/g, "")}-${process.pid}.log`);
|
|
23274
|
+
writeFileSync2(path, [
|
|
22840
23275
|
`kind: ${kind}`,
|
|
22841
23276
|
`version: ${process.version}`,
|
|
22842
23277
|
`platform: ${process.platform}`,
|
|
@@ -23606,8 +24041,8 @@ ${total}`;
|
|
|
23606
24041
|
|
|
23607
24042
|
// src/memory/store.ts
|
|
23608
24043
|
import { createHash as createHash17 } from "crypto";
|
|
23609
|
-
import { readFile as
|
|
23610
|
-
import { resolve as resolve31, join as
|
|
24044
|
+
import { readFile as readFile25, rm as rm12 } from "fs/promises";
|
|
24045
|
+
import { resolve as resolve31, join as join34, relative as relative21, sep as sep21 } from "path";
|
|
23611
24046
|
|
|
23612
24047
|
// src/memory/similarity.ts
|
|
23613
24048
|
function normalizeForSimilarity(value) {
|
|
@@ -23710,9 +24145,9 @@ var MemoryStore = class {
|
|
|
23710
24145
|
if (!Number.isSafeInteger(options.maxEntries) || options.maxEntries < 1) throw new Error("maxEntries must be an integer of at least 1");
|
|
23711
24146
|
if (!Number.isSafeInteger(options.maxEntryChars) || options.maxEntryChars < 1) throw new Error("maxEntryChars must be an integer of at least 1");
|
|
23712
24147
|
this.workspace = resolve31(options.workspace);
|
|
23713
|
-
this.#memoryRoot =
|
|
23714
|
-
this.path =
|
|
23715
|
-
this.#behaviorPath =
|
|
24148
|
+
this.#memoryRoot = join34(this.workspace, ".flavor", "memory");
|
|
24149
|
+
this.path = join34(this.#memoryRoot, "MEMORY.md");
|
|
24150
|
+
this.#behaviorPath = join34(this.#memoryRoot, "behavior.json");
|
|
23716
24151
|
this.#maxEntries = options.maxEntries;
|
|
23717
24152
|
this.#maxEntryChars = options.maxEntryChars;
|
|
23718
24153
|
}
|
|
@@ -23930,7 +24365,7 @@ var MemoryStore = class {
|
|
|
23930
24365
|
});
|
|
23931
24366
|
}
|
|
23932
24367
|
async #readTaskItem(reference) {
|
|
23933
|
-
const document = decodeTask(await
|
|
24368
|
+
const document = decodeTask(await readFile25(this.#resolveContentPath(reference.contentPath), "utf8"));
|
|
23934
24369
|
return document.items.find((item) => item.id === reference.id)?.content ?? reference.summary;
|
|
23935
24370
|
}
|
|
23936
24371
|
#resolveContentPath(contentPath) {
|
|
@@ -24570,8 +25005,8 @@ function createExecutionEnvironment(workspace, config) {
|
|
|
24570
25005
|
}
|
|
24571
25006
|
|
|
24572
25007
|
// src/ide/client.ts
|
|
24573
|
-
import { readFile as
|
|
24574
|
-
import { basename as basename8, join as
|
|
25008
|
+
import { readFile as readFile26, readdir as readdir11, stat as stat13 } from "fs/promises";
|
|
25009
|
+
import { basename as basename8, join as join35, relative as relative23, resolve as resolve33, sep as sep23 } from "path";
|
|
24575
25010
|
var FlavorIdeClient = class {
|
|
24576
25011
|
#workspace;
|
|
24577
25012
|
#home;
|
|
@@ -24667,7 +25102,7 @@ var FlavorIdeClient = class {
|
|
|
24667
25102
|
};
|
|
24668
25103
|
if (await this.#healthy(configured)) return configured;
|
|
24669
25104
|
}
|
|
24670
|
-
const directory =
|
|
25105
|
+
const directory = join35(this.#home, ".flavor-code", "ide");
|
|
24671
25106
|
let entries;
|
|
24672
25107
|
try {
|
|
24673
25108
|
entries = (await readdir11(directory)).filter((name) => name.endsWith(".lock"));
|
|
@@ -24675,9 +25110,9 @@ var FlavorIdeClient = class {
|
|
|
24675
25110
|
return void 0;
|
|
24676
25111
|
}
|
|
24677
25112
|
const candidates = (await Promise.all(entries.map(async (name) => {
|
|
24678
|
-
const path =
|
|
25113
|
+
const path = join35(directory, name);
|
|
24679
25114
|
try {
|
|
24680
|
-
const [contents, info] = await Promise.all([
|
|
25115
|
+
const [contents, info] = await Promise.all([readFile26(path, "utf8"), stat13(path)]);
|
|
24681
25116
|
const lock = parseLockfile(contents);
|
|
24682
25117
|
return lock === void 0 ? void 0 : { lock, modified: info.mtimeMs };
|
|
24683
25118
|
} catch {
|
|
@@ -24796,6 +25231,7 @@ function relativeDisplay(workspace, filePath) {
|
|
|
24796
25231
|
|
|
24797
25232
|
// src/jobs/registry.ts
|
|
24798
25233
|
import { randomUUID as randomUUID19 } from "crypto";
|
|
25234
|
+
var OUTPUT_SLICE_SLACK_CHARS = 64 * 1024;
|
|
24799
25235
|
var JobRegistry = class {
|
|
24800
25236
|
#jobs = /* @__PURE__ */ new Map();
|
|
24801
25237
|
#maxOutputChars;
|
|
@@ -24824,7 +25260,8 @@ var JobRegistry = class {
|
|
|
24824
25260
|
updatedAt: now,
|
|
24825
25261
|
outputChars: 0,
|
|
24826
25262
|
truncated: false,
|
|
24827
|
-
|
|
25263
|
+
chunks: [],
|
|
25264
|
+
windowChars: 0,
|
|
24828
25265
|
outputStart: 0,
|
|
24829
25266
|
settle,
|
|
24830
25267
|
done
|
|
@@ -24835,11 +25272,16 @@ var JobRegistry = class {
|
|
|
24835
25272
|
id,
|
|
24836
25273
|
append: (text) => {
|
|
24837
25274
|
if (record3.state !== "running" || text === "") return;
|
|
24838
|
-
record3.
|
|
25275
|
+
record3.chunks.push(text);
|
|
25276
|
+
record3.windowChars += text.length;
|
|
24839
25277
|
record3.outputChars += text.length;
|
|
24840
|
-
if (record3.
|
|
24841
|
-
|
|
24842
|
-
|
|
25278
|
+
if (record3.outputChars > this.#maxOutputChars) record3.truncated = true;
|
|
25279
|
+
if (record3.windowChars > this.#maxOutputChars + OUTPUT_SLICE_SLACK_CHARS) {
|
|
25280
|
+
const joined = record3.chunks.join("");
|
|
25281
|
+
const kept = joined.slice(joined.length - this.#maxOutputChars);
|
|
25282
|
+
const removed = joined.length - kept.length;
|
|
25283
|
+
record3.chunks = [kept];
|
|
25284
|
+
record3.windowChars = kept.length;
|
|
24843
25285
|
record3.outputStart += removed;
|
|
24844
25286
|
record3.truncated = true;
|
|
24845
25287
|
}
|
|
@@ -24860,8 +25302,20 @@ var JobRegistry = class {
|
|
|
24860
25302
|
}
|
|
24861
25303
|
read(id, owner2, cursor = 0) {
|
|
24862
25304
|
const job = this.#owned(id, owner2);
|
|
24863
|
-
const
|
|
24864
|
-
|
|
25305
|
+
const window = job.chunks.join("");
|
|
25306
|
+
const dropped = Math.max(0, window.length - this.#maxOutputChars);
|
|
25307
|
+
const effective = Math.max(cursor, job.outputStart + dropped);
|
|
25308
|
+
return { ...snapshot2(job), output: window.slice(effective - job.outputStart), cursor: job.outputStart + window.length };
|
|
25309
|
+
}
|
|
25310
|
+
/** Retained sizes for the rotation census. */
|
|
25311
|
+
census() {
|
|
25312
|
+
let windowChars = 0;
|
|
25313
|
+
let running = 0;
|
|
25314
|
+
for (const job of this.#jobs.values()) {
|
|
25315
|
+
windowChars += job.windowChars;
|
|
25316
|
+
if (job.state === "running") running += 1;
|
|
25317
|
+
}
|
|
25318
|
+
return { jobs: this.#jobs.size, running, windowChars };
|
|
24865
25319
|
}
|
|
24866
25320
|
async wait(id, owner2, signal) {
|
|
24867
25321
|
signal?.throwIfAborted();
|
|
@@ -25198,7 +25652,7 @@ function palSocketAddress(options) {
|
|
|
25198
25652
|
import { createHash as createHash20 } from "crypto";
|
|
25199
25653
|
import { Socket as Socket2 } from "net";
|
|
25200
25654
|
import { tmpdir as tmpdir3 } from "os";
|
|
25201
|
-
import { join as
|
|
25655
|
+
import { join as join37 } from "path";
|
|
25202
25656
|
import { spawn as spawn8 } from "child_process";
|
|
25203
25657
|
|
|
25204
25658
|
// src/pals/broker.ts
|
|
@@ -25209,18 +25663,18 @@ import { dirname as dirname21 } from "path";
|
|
|
25209
25663
|
|
|
25210
25664
|
// src/pals/auth.ts
|
|
25211
25665
|
import { randomBytes as randomBytes3, randomUUID as randomUUID21 } from "crypto";
|
|
25212
|
-
import { chmod as chmod2, link as link2, mkdir as mkdir21, open as open12, readFile as
|
|
25666
|
+
import { chmod as chmod2, link as link2, mkdir as mkdir21, open as open12, readFile as readFile27, unlink as unlink4 } from "fs/promises";
|
|
25213
25667
|
import { homedir as homedir2 } from "os";
|
|
25214
|
-
import { join as
|
|
25668
|
+
import { join as join36 } from "path";
|
|
25215
25669
|
var PAL_AUTH_TOKEN_BYTES = 32;
|
|
25216
25670
|
var TOKEN_PATTERN = /^[a-f0-9]{64}$/;
|
|
25217
25671
|
async function loadOrCreatePalAuthToken(options = {}) {
|
|
25218
|
-
const directory =
|
|
25219
|
-
const tokenPath =
|
|
25672
|
+
const directory = join36(options.home ?? homedir2(), ".flavor-code", "pals");
|
|
25673
|
+
const tokenPath = join36(directory, "auth-token");
|
|
25220
25674
|
await mkdir21(directory, { recursive: true, mode: 448 });
|
|
25221
25675
|
await chmod2(directory, 448);
|
|
25222
25676
|
const token = (options.random ?? randomBytes3)(PAL_AUTH_TOKEN_BYTES).toString("hex");
|
|
25223
|
-
const temporaryPath =
|
|
25677
|
+
const temporaryPath = join36(directory, `.auth-token-${process.pid}-${randomUUID21()}.tmp`);
|
|
25224
25678
|
let handle;
|
|
25225
25679
|
try {
|
|
25226
25680
|
handle = await open12(temporaryPath, "wx", 384);
|
|
@@ -25243,7 +25697,7 @@ async function loadOrCreatePalAuthToken(options = {}) {
|
|
|
25243
25697
|
if (error.code !== "ENOENT") throw error;
|
|
25244
25698
|
});
|
|
25245
25699
|
}
|
|
25246
|
-
const stored = (await
|
|
25700
|
+
const stored = (await readFile27(tokenPath, "utf8")).trim();
|
|
25247
25701
|
if (!TOKEN_PATTERN.test(stored)) throw new Error("Pals authentication token store is invalid");
|
|
25248
25702
|
await chmod2(tokenPath, 384);
|
|
25249
25703
|
return stored;
|
|
@@ -25255,7 +25709,7 @@ function validatePalAuthToken(token) {
|
|
|
25255
25709
|
|
|
25256
25710
|
// src/pals/lifecycle.ts
|
|
25257
25711
|
import { randomUUID as randomUUID22 } from "crypto";
|
|
25258
|
-
import { mkdir as mkdir22, open as open13, readFile as
|
|
25712
|
+
import { mkdir as mkdir22, open as open13, readFile as readFile28, rename as rename11, stat as stat14, unlink as unlink5 } from "fs/promises";
|
|
25259
25713
|
import { dirname as dirname20 } from "path";
|
|
25260
25714
|
function defaultProcessAlive(pid) {
|
|
25261
25715
|
if (!Number.isSafeInteger(pid) || pid <= 0) return true;
|
|
@@ -25287,7 +25741,7 @@ async function acquirePalFileLock(options) {
|
|
|
25287
25741
|
handle = await open13(options.path, "wx", 384);
|
|
25288
25742
|
} catch (error) {
|
|
25289
25743
|
if (error.code !== "EEXIST") throw error;
|
|
25290
|
-
const [contents, info] = await Promise.all([
|
|
25744
|
+
const [contents, info] = await Promise.all([readFile28(options.path, "utf8").catch(() => ""), stat14(options.path).catch(() => void 0)]);
|
|
25291
25745
|
if (await options.endpointLive()) return void 0;
|
|
25292
25746
|
const existing = parseMetadata(contents);
|
|
25293
25747
|
if (existing !== void 0 && (options.processAlive ?? defaultProcessAlive)(existing.pid)) return void 0;
|
|
@@ -25317,7 +25771,7 @@ async function acquirePalFileLock(options) {
|
|
|
25317
25771
|
await handle.close();
|
|
25318
25772
|
}
|
|
25319
25773
|
const release = async () => {
|
|
25320
|
-
const current = parseMetadata(await
|
|
25774
|
+
const current = parseMetadata(await readFile28(options.path, "utf8").catch(() => ""));
|
|
25321
25775
|
if (current?.nonce !== nonce) return;
|
|
25322
25776
|
await unlink5(options.path).catch((error) => {
|
|
25323
25777
|
if (error.code !== "ENOENT") throw error;
|
|
@@ -25350,7 +25804,7 @@ function canConnect(address) {
|
|
|
25350
25804
|
}
|
|
25351
25805
|
function startupLockPath(address) {
|
|
25352
25806
|
const digest = createHash20("sha256").update(address, "utf8").digest("hex").slice(0, 24);
|
|
25353
|
-
return
|
|
25807
|
+
return join37(tmpdir3(), `flavor-code-pals-${digest}.startup.lock`);
|
|
25354
25808
|
}
|
|
25355
25809
|
async function spawnProductionBroker(address) {
|
|
25356
25810
|
const entry2 = process.argv[1];
|
|
@@ -26293,8 +26747,8 @@ async function createProductionRuntime(options) {
|
|
|
26293
26747
|
const registeredProviders = registration.registered;
|
|
26294
26748
|
let effectiveLlm = registration.effectiveLlm;
|
|
26295
26749
|
const pluginHost = new PluginHost({
|
|
26296
|
-
globalPluginDirs: [
|
|
26297
|
-
projectPluginDirs: [
|
|
26750
|
+
globalPluginDirs: [join38(home, ".flavor-code", "plugins")],
|
|
26751
|
+
projectPluginDirs: [join38(workspace, ".flavor", "plugins")],
|
|
26298
26752
|
config,
|
|
26299
26753
|
// Existing project plugins (including astgraph and superharness) use Node.js
|
|
26300
26754
|
// built-ins. The Worker/vm sandbox deliberately blocks those imports, so it
|
|
@@ -26403,7 +26857,7 @@ async function createProductionRuntime(options) {
|
|
|
26403
26857
|
const address = palSocketAddress({
|
|
26404
26858
|
platform: process.platform,
|
|
26405
26859
|
userScope: home,
|
|
26406
|
-
runtimeDir:
|
|
26860
|
+
runtimeDir: join38(tmpdir4(), `flavor-code-pals-${createHash21("sha256").update(home).digest("hex").slice(0, 16)}`)
|
|
26407
26861
|
});
|
|
26408
26862
|
const baseCollaborationClient = collaboration.client ?? collaboration.createClient?.({
|
|
26409
26863
|
instanceId: collaboration.instanceId,
|
|
@@ -26514,8 +26968,8 @@ async function createProductionRuntime(options) {
|
|
|
26514
26968
|
diagnostics.push(`MCP servers could not start: ${message(error)}`);
|
|
26515
26969
|
});
|
|
26516
26970
|
const skills = new SkillRegistry({
|
|
26517
|
-
globalRoots: [
|
|
26518
|
-
projectRoots: [
|
|
26971
|
+
globalRoots: [join38(home, ".flavor-code", "skills")],
|
|
26972
|
+
projectRoots: [join38(workspace, ".flavor", "skills"), ...pluginSkillRoots],
|
|
26519
26973
|
authorizeResource: async () => true,
|
|
26520
26974
|
disabledNames: config.skills.disabled
|
|
26521
26975
|
});
|
|
@@ -26523,7 +26977,7 @@ async function createProductionRuntime(options) {
|
|
|
26523
26977
|
skillsReady.catch(() => void 0);
|
|
26524
26978
|
tools.push(createSkillTool(skills), createSkillResourceTool(skills));
|
|
26525
26979
|
if (options.extraTools !== void 0) tools.push(...options.extraTools);
|
|
26526
|
-
const flavor = await optionalText2(
|
|
26980
|
+
const flavor = await optionalText2(join38(workspace, "FLAVOR.md"));
|
|
26527
26981
|
const instructionBaseline = await workspaceInstructions.baseline();
|
|
26528
26982
|
let memoryContext;
|
|
26529
26983
|
if (memoryStore !== void 0) {
|
|
@@ -27108,7 +27562,7 @@ async function createProductionRuntime(options) {
|
|
|
27108
27562
|
evolveService.toolDefinition(),
|
|
27109
27563
|
...mcpTools
|
|
27110
27564
|
];
|
|
27111
|
-
const loopFlavor = await optionalText2(
|
|
27565
|
+
const loopFlavor = await optionalText2(join38(input.workspace, "FLAVOR.md"));
|
|
27112
27566
|
const loopInstructionBaseline = await loopInstructions.baseline();
|
|
27113
27567
|
const loopEnvironment = createPromptEnvironment({
|
|
27114
27568
|
now: /* @__PURE__ */ new Date(),
|
|
@@ -27216,12 +27670,83 @@ async function createProductionRuntime(options) {
|
|
|
27216
27670
|
await loopExecutionEnvironment?.dispose();
|
|
27217
27671
|
}
|
|
27218
27672
|
};
|
|
27673
|
+
let activeLongTask;
|
|
27674
|
+
const readMemoryRestartMarker = () => {
|
|
27675
|
+
try {
|
|
27676
|
+
return parseMemoryRestartMarker(readFileSync3(memoryRestartMarkerPath(workspace), "utf8"));
|
|
27677
|
+
} catch {
|
|
27678
|
+
return void 0;
|
|
27679
|
+
}
|
|
27680
|
+
};
|
|
27681
|
+
const currentPressureReading = () => verifiedHeapPressure(MEMORY_PRESSURE_HEAP_RATIO) ?? { ...readHeap(), gcVerified: false };
|
|
27682
|
+
const collectRotationHolders = () => {
|
|
27683
|
+
const holders = {};
|
|
27684
|
+
try {
|
|
27685
|
+
const context = harness.main.context.census();
|
|
27686
|
+
holders.context = { entries: context.messages, chars: context.messageChars };
|
|
27687
|
+
holders.contextVisibility = { entries: context.visibilityEntries, chars: context.visibilityChars };
|
|
27688
|
+
holders.contextTransient = { entries: context.transientEntries, chars: context.transientChars };
|
|
27689
|
+
} catch {
|
|
27690
|
+
}
|
|
27691
|
+
try {
|
|
27692
|
+
holders.journal = { entries: harnessJournal.recordCount };
|
|
27693
|
+
} catch {
|
|
27694
|
+
}
|
|
27695
|
+
try {
|
|
27696
|
+
const jobCensus = jobs.census();
|
|
27697
|
+
holders.jobs = { entries: jobCensus.jobs, chars: jobCensus.windowChars };
|
|
27698
|
+
} catch {
|
|
27699
|
+
}
|
|
27700
|
+
try {
|
|
27701
|
+
holders.sessionTree = { entries: sessionHistory.nodeCount };
|
|
27702
|
+
} catch {
|
|
27703
|
+
}
|
|
27704
|
+
return holders;
|
|
27705
|
+
};
|
|
27706
|
+
const rotateForHeap = (continuation, reading) => {
|
|
27707
|
+
requestMemoryRestart(workspace, sessionId, emitOutput, {
|
|
27708
|
+
...continuation === void 0 ? {} : { continuation },
|
|
27709
|
+
...reading === void 0 ? {} : { reading },
|
|
27710
|
+
holders: collectRotationHolders()
|
|
27711
|
+
}, options.onMemoryRestartRequested);
|
|
27712
|
+
};
|
|
27713
|
+
const maybeRotateAtBoundary = (continuation) => {
|
|
27714
|
+
activeLongTask = continuation;
|
|
27715
|
+
const pressure = verifiedHeapPressure(MEMORY_PRESSURE_HEAP_RATIO);
|
|
27716
|
+
if (pressure !== void 0) {
|
|
27717
|
+
rotateForHeap(continuation, pressure);
|
|
27718
|
+
return;
|
|
27719
|
+
}
|
|
27720
|
+
const reading = heapRotationNeeded();
|
|
27721
|
+
if (reading === void 0) return;
|
|
27722
|
+
if (rotationCooldownActive(readMemoryRestartMarker(), sessionId, /* @__PURE__ */ new Date())) return;
|
|
27723
|
+
rotateForHeap(continuation, reading);
|
|
27724
|
+
};
|
|
27725
|
+
const maybeRotateAtTurnBoundary = () => {
|
|
27726
|
+
const reading = heapRotationNeeded();
|
|
27727
|
+
if (reading === void 0) return;
|
|
27728
|
+
if (rotationCooldownActive(readMemoryRestartMarker(), sessionId, /* @__PURE__ */ new Date())) return;
|
|
27729
|
+
rotateForHeap(void 0, reading);
|
|
27730
|
+
};
|
|
27731
|
+
const consumePendingContinuation = () => {
|
|
27732
|
+
const marker = readMemoryRestartMarker();
|
|
27733
|
+
const continuation = pendingContinuation(marker, sessionId, /* @__PURE__ */ new Date());
|
|
27734
|
+
if (continuation === void 0 || marker === void 0) return void 0;
|
|
27735
|
+
try {
|
|
27736
|
+
const markerPath = memoryRestartMarkerPath(workspace);
|
|
27737
|
+
writeMarkerAtomically(markerPath, consumedMemoryRestartMarker(marker));
|
|
27738
|
+
} catch {
|
|
27739
|
+
}
|
|
27740
|
+
return continuation;
|
|
27741
|
+
};
|
|
27219
27742
|
const loopStore = new LoopStore({ workspace });
|
|
27220
27743
|
const loopOrchestrator = new LoopOrchestrator({
|
|
27221
27744
|
workspace,
|
|
27222
27745
|
config: config.loop,
|
|
27223
27746
|
persistence: loopStore,
|
|
27224
27747
|
hallucinationGuard,
|
|
27748
|
+
onCycleBoundary: (loopId) => maybeRotateAtBoundary({ kind: "loop", id: loopId }),
|
|
27749
|
+
onMemoryCheckpoint: (loopId) => maybeRotateAtBoundary({ kind: "loop", id: loopId }),
|
|
27225
27750
|
prepareWorkspace: (input) => prepareLoopWorkspace(input),
|
|
27226
27751
|
inferVerification: inferVerificationPlan,
|
|
27227
27752
|
runWorker: ({ workspace: executionWorkspace, prompt, signal }) => runLoopWorker({ workspace: executionWorkspace, prompt, signal }),
|
|
@@ -27258,9 +27783,11 @@ async function createProductionRuntime(options) {
|
|
|
27258
27783
|
plannerModelId: mainModel,
|
|
27259
27784
|
classifierModelId: mainModel,
|
|
27260
27785
|
skepticCount: 3,
|
|
27261
|
-
maxRounds:
|
|
27262
|
-
maxStallStreak:
|
|
27786
|
+
maxRounds: config.goal.maxRounds,
|
|
27787
|
+
maxStallStreak: config.goal.maxStallStreak,
|
|
27263
27788
|
persistence: goalStore,
|
|
27789
|
+
onRoundBoundary: (goalId) => maybeRotateAtBoundary({ kind: "goal", id: goalId }),
|
|
27790
|
+
onMemoryCheckpoint: (goalId) => maybeRotateAtBoundary({ kind: "goal", id: goalId }),
|
|
27264
27791
|
verifyHost: async (signal) => runVerificationPlan(
|
|
27265
27792
|
await inferVerificationPlan(workspace),
|
|
27266
27793
|
workspace,
|
|
@@ -27355,30 +27882,49 @@ async function createProductionRuntime(options) {
|
|
|
27355
27882
|
contextEpoch: harness.main.context.snapshot().epoch?.id ?? "legacy"
|
|
27356
27883
|
});
|
|
27357
27884
|
const turnId = harnessJournal.startTurn(turnConfig, { prompt, initialUserMessage: runOptions?.initialUserMessage });
|
|
27358
|
-
return
|
|
27359
|
-
|
|
27360
|
-
|
|
27361
|
-
|
|
27362
|
-
|
|
27363
|
-
|
|
27364
|
-
|
|
27365
|
-
|
|
27366
|
-
|
|
27367
|
-
|
|
27368
|
-
|
|
27369
|
-
|
|
27370
|
-
|
|
27371
|
-
|
|
27372
|
-
|
|
27373
|
-
|
|
27374
|
-
|
|
27885
|
+
return monitorTurnMemory(
|
|
27886
|
+
durableTurn(persistAfter(runMain(
|
|
27887
|
+
harness,
|
|
27888
|
+
skills,
|
|
27889
|
+
prompt,
|
|
27890
|
+
signal,
|
|
27891
|
+
selectedModels.mainError,
|
|
27892
|
+
memoryStore === void 0 || !memoryHasRoutableEntries && userMemoryContext === void 0 ? void 0 : {
|
|
27893
|
+
store: memoryStore,
|
|
27894
|
+
taskId: memoryLifecycle.taskId ?? sessionId,
|
|
27895
|
+
topK: config.memory.retrievalTopK,
|
|
27896
|
+
maxChars: config.memory.maxPromptChars
|
|
27897
|
+
},
|
|
27898
|
+
runOptions?.getSteeringMessages,
|
|
27899
|
+
runOptions?.initialUserMessage,
|
|
27900
|
+
runOptions?.additionalContext,
|
|
27901
|
+
ide
|
|
27902
|
+
), persist), harnessJournal, turnId, turnConfig),
|
|
27903
|
+
() => rotateForHeap(void 0, currentPressureReading()),
|
|
27904
|
+
() => maybeRotateAtTurnBoundary()
|
|
27905
|
+
);
|
|
27375
27906
|
},
|
|
27376
27907
|
runSkill: (skill, prompt, signal) => persistAfter(
|
|
27377
27908
|
runExplicitSkill(harness, skills, skill, prompt, signal, selectedModels.mainError),
|
|
27378
27909
|
persist
|
|
27379
27910
|
),
|
|
27380
|
-
runLoop: (goal, signal) =>
|
|
27381
|
-
|
|
27911
|
+
runLoop: (goal, signal) => monitorLongTaskRestart(
|
|
27912
|
+
runLoopSession(loopOrchestrator, hooks, goal, signal),
|
|
27913
|
+
() => rotateForHeap(activeLongTask, currentPressureReading())
|
|
27914
|
+
),
|
|
27915
|
+
runGoal: (goal, signal) => monitorLongTaskRestart(
|
|
27916
|
+
persistEach(runGoalSession(goalOrchestrator, goal, signal), persist),
|
|
27917
|
+
() => rotateForHeap(activeLongTask, currentPressureReading())
|
|
27918
|
+
),
|
|
27919
|
+
resumeLoop: (loopId, signal) => monitorLongTaskRestart(
|
|
27920
|
+
runResumeLoopSession(loopOrchestrator, hooks, loopId, signal),
|
|
27921
|
+
() => rotateForHeap(activeLongTask ?? { kind: "loop", id: loopId }, currentPressureReading())
|
|
27922
|
+
),
|
|
27923
|
+
resumeGoal: (goalId, signal) => monitorLongTaskRestart(
|
|
27924
|
+
persistEach(runResumeGoalSession(goalOrchestrator, goalId, signal), persist),
|
|
27925
|
+
() => rotateForHeap(activeLongTask ?? { kind: "goal", id: goalId }, currentPressureReading())
|
|
27926
|
+
),
|
|
27927
|
+
continuation: () => consumePendingContinuation(),
|
|
27382
27928
|
mcp: async (command, signal) => {
|
|
27383
27929
|
signal.throwIfAborted();
|
|
27384
27930
|
const manager = await mcpReady;
|
|
@@ -27472,7 +28018,7 @@ async function createProductionRuntime(options) {
|
|
|
27472
28018
|
const root = resolve36(workspace);
|
|
27473
28019
|
const abs = resolve36(root, path);
|
|
27474
28020
|
if (abs !== root && !abs.startsWith(root + sep25)) throw new Error(`path outside workspace: ${path}`);
|
|
27475
|
-
return
|
|
28021
|
+
return readFile29(abs, "utf8");
|
|
27476
28022
|
},
|
|
27477
28023
|
history: async (path, limit) => fileHistory(workspace, path, limit),
|
|
27478
28024
|
questions,
|
|
@@ -27484,7 +28030,7 @@ async function createProductionRuntime(options) {
|
|
|
27484
28030
|
}, query, focus, signal),
|
|
27485
28031
|
audit: async (toolFilter) => {
|
|
27486
28032
|
try {
|
|
27487
|
-
const raw = await
|
|
28033
|
+
const raw = await readFile29(auditLogger.path, "utf8");
|
|
27488
28034
|
const lines = raw.trim().split("\n").filter((line) => line.length > 0);
|
|
27489
28035
|
const entries = lines.map((line) => {
|
|
27490
28036
|
try {
|
|
@@ -27524,7 +28070,7 @@ ${summary2}`;
|
|
|
27524
28070
|
doctor: async () => formatDoctorReport(await runDoctor({ workspace, home, environment })),
|
|
27525
28071
|
usage: async () => {
|
|
27526
28072
|
try {
|
|
27527
|
-
const raw = await
|
|
28073
|
+
const raw = await readFile29(usageLogPath(), "utf8");
|
|
27528
28074
|
return formatUsageSummary(summarizeUsage(parseUsageEntries(raw)));
|
|
27529
28075
|
} catch (error) {
|
|
27530
28076
|
if (typeof error === "object" && error !== null && "code" in error && error.code === "ENOENT") {
|
|
@@ -27677,7 +28223,7 @@ ${renderMemoryDocument(entries)}`;
|
|
|
27677
28223
|
return `Provider "${providerName}" is missing authorizationUrl, tokenUrl, or clientId.`;
|
|
27678
28224
|
}
|
|
27679
28225
|
try {
|
|
27680
|
-
const tokenStore = createFileTokenStore(
|
|
28226
|
+
const tokenStore = createFileTokenStore(join38(home, ".flavor-code", "auth.json"));
|
|
27681
28227
|
const credentialId = oauthCredentialId(oauthConfig.tokenUrl, oauthConfig.clientId);
|
|
27682
28228
|
await migrateLegacyOAuthToken(tokenStore, providerName, credentialId);
|
|
27683
28229
|
const oauth = new OAuthCallbackAuthProvider({
|
|
@@ -27724,7 +28270,7 @@ ${renderMemoryDocument(entries)}`;
|
|
|
27724
28270
|
}
|
|
27725
28271
|
},
|
|
27726
28272
|
async logout() {
|
|
27727
|
-
const tokenStore = createFileTokenStore(
|
|
28273
|
+
const tokenStore = createFileTokenStore(join38(home, ".flavor-code", "auth.json"));
|
|
27728
28274
|
const tokens2 = await tokenStore.load();
|
|
27729
28275
|
const hadCredentials = Object.keys(tokens2).length > 0 || effectiveLlm !== void 0;
|
|
27730
28276
|
if (!hadCredentials) {
|
|
@@ -27917,10 +28463,16 @@ function formatMcpReconnect(server) {
|
|
|
27917
28463
|
return `Failed to reconnect MCP server "${server.name}": ${server.error ?? "unknown error"}`;
|
|
27918
28464
|
}
|
|
27919
28465
|
async function* runLoopSession(orchestrator, hooks, goal, signal) {
|
|
28466
|
+
yield* loopSessionEvents(orchestrator.run({ goal, signal }), hooks);
|
|
28467
|
+
}
|
|
28468
|
+
async function* runResumeLoopSession(orchestrator, hooks, loopId, signal) {
|
|
28469
|
+
yield* loopSessionEvents(orchestrator.resume({ loopId, signal }), hooks);
|
|
28470
|
+
}
|
|
28471
|
+
async function* loopSessionEvents(events, hooks) {
|
|
27920
28472
|
let totalInputTokens = 0;
|
|
27921
28473
|
let totalOutputTokens = 0;
|
|
27922
28474
|
let latestVerification;
|
|
27923
|
-
for await (const event of
|
|
28475
|
+
for await (const event of events) {
|
|
27924
28476
|
if (event.type === "worker-event") {
|
|
27925
28477
|
if (event.event.type === "usage") {
|
|
27926
28478
|
totalInputTokens += event.event.inputTokens;
|
|
@@ -28024,7 +28576,7 @@ async function workspaceFingerprint(workspace) {
|
|
|
28024
28576
|
hash.update(name);
|
|
28025
28577
|
if (remaining <= 0) continue;
|
|
28026
28578
|
try {
|
|
28027
|
-
const content = await
|
|
28579
|
+
const content = await readFile29(path);
|
|
28028
28580
|
const slice = content.subarray(0, remaining);
|
|
28029
28581
|
hash.update(slice);
|
|
28030
28582
|
remaining -= slice.length;
|
|
@@ -28136,7 +28688,16 @@ async function* durableTurn(source, journal, turnId, config) {
|
|
|
28136
28688
|
}
|
|
28137
28689
|
}
|
|
28138
28690
|
}
|
|
28139
|
-
async function*
|
|
28691
|
+
async function* monitorTurnMemory(source, onPressure, onBoundary) {
|
|
28692
|
+
let pressure = false;
|
|
28693
|
+
for await (const event of source) {
|
|
28694
|
+
if (event.type === "error" && event.error.code === "memory_pressure") pressure = true;
|
|
28695
|
+
yield event;
|
|
28696
|
+
}
|
|
28697
|
+
if (pressure) onPressure();
|
|
28698
|
+
else onBoundary();
|
|
28699
|
+
}
|
|
28700
|
+
async function* monitorLongTaskRestart(source, onRestartNeeded) {
|
|
28140
28701
|
let pressure = false;
|
|
28141
28702
|
for await (const event of source) {
|
|
28142
28703
|
if (event.type === "error" && event.error.code === "memory_pressure") pressure = true;
|
|
@@ -28144,7 +28705,10 @@ async function* monitorMemoryRestart(source, onRestartNeeded) {
|
|
|
28144
28705
|
}
|
|
28145
28706
|
if (pressure) onRestartNeeded();
|
|
28146
28707
|
}
|
|
28147
|
-
function requestMemoryRestart(workspace, sessionId, emitOutput) {
|
|
28708
|
+
function requestMemoryRestart(workspace, sessionId, emitOutput, rotation, onRestartRequested) {
|
|
28709
|
+
if (memoryRotationActive()) return;
|
|
28710
|
+
markMemoryRotation();
|
|
28711
|
+
const continuation = rotation?.continuation;
|
|
28148
28712
|
try {
|
|
28149
28713
|
const markerPath = memoryRestartMarkerPath(workspace);
|
|
28150
28714
|
let previous;
|
|
@@ -28152,14 +28716,43 @@ function requestMemoryRestart(workspace, sessionId, emitOutput) {
|
|
|
28152
28716
|
previous = parseMemoryRestartMarker(readFileSync3(markerPath, "utf8"));
|
|
28153
28717
|
} catch {
|
|
28154
28718
|
}
|
|
28155
|
-
|
|
28156
|
-
writeFileSync2(markerPath, `${JSON.stringify(nextMemoryRestartMarker(previous, sessionId, /* @__PURE__ */ new Date()))}
|
|
28157
|
-
`, "utf8");
|
|
28719
|
+
writeMarkerAtomically(markerPath, nextMemoryRestartMarker(previous, sessionId, /* @__PURE__ */ new Date(), continuation));
|
|
28158
28720
|
} catch {
|
|
28159
28721
|
}
|
|
28160
|
-
|
|
28161
|
-
|
|
28162
|
-
|
|
28722
|
+
if (rotation?.reading !== void 0) {
|
|
28723
|
+
const census = {
|
|
28724
|
+
at: (/* @__PURE__ */ new Date()).toISOString(),
|
|
28725
|
+
reason: continuation === void 0 ? "memory-pressure" : `heap-rotation:${continuation.kind}`,
|
|
28726
|
+
sessionId,
|
|
28727
|
+
heap: censusHeapBlock(rotation.reading),
|
|
28728
|
+
holders: rotation.holders ?? {}
|
|
28729
|
+
};
|
|
28730
|
+
writeRotationCensus(workspace, census);
|
|
28731
|
+
if (heapSnapshotWarranted(rotation.reading)) writeRotationHeapSnapshot(workspace);
|
|
28732
|
+
}
|
|
28733
|
+
emitOutput({
|
|
28734
|
+
type: "notice",
|
|
28735
|
+
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.`
|
|
28736
|
+
});
|
|
28737
|
+
if (onRestartRequested !== void 0) onRestartRequested(sessionId);
|
|
28738
|
+
else {
|
|
28739
|
+
process.exitCode = MEMORY_RESTART_EXIT_CODE;
|
|
28740
|
+
emitOutput({ type: "exit" });
|
|
28741
|
+
}
|
|
28742
|
+
}
|
|
28743
|
+
function writeMarkerAtomically(path, marker) {
|
|
28744
|
+
mkdirSync4(dirname22(path), { recursive: true });
|
|
28745
|
+
const temporary = `${path}.${process.pid}.${randomUUID25()}.tmp`;
|
|
28746
|
+
try {
|
|
28747
|
+
writeFileSync3(temporary, `${JSON.stringify(marker)}
|
|
28748
|
+
`, { encoding: "utf8", mode: 384 });
|
|
28749
|
+
renameSync2(temporary, path);
|
|
28750
|
+
} finally {
|
|
28751
|
+
try {
|
|
28752
|
+
unlinkSync3(temporary);
|
|
28753
|
+
} catch {
|
|
28754
|
+
}
|
|
28755
|
+
}
|
|
28163
28756
|
}
|
|
28164
28757
|
async function runGitCommit(deps, hint, signal) {
|
|
28165
28758
|
if (!await isGitRepository(deps.workspace)) return "Not a git repository \u2014 /commit needs git.";
|
|
@@ -28254,7 +28847,7 @@ async function registerConfiguredAdapters(providers, registry, environment, diag
|
|
|
28254
28847
|
const configured = { ...providers };
|
|
28255
28848
|
if (configured.openai === void 0 && environment.OPENAI_API_KEY) configured.openai = { type: "openai", apiKey: environment.OPENAI_API_KEY };
|
|
28256
28849
|
if (configured.anthropic === void 0 && environment.ANTHROPIC_API_KEY) configured.anthropic = { type: "anthropic", apiKey: environment.ANTHROPIC_API_KEY };
|
|
28257
|
-
const oauthTokenStore = createFileTokenStore(
|
|
28850
|
+
const oauthTokenStore = createFileTokenStore(join38(home, ".flavor-code", "auth.json"));
|
|
28258
28851
|
const registered = [];
|
|
28259
28852
|
let effectiveLlm;
|
|
28260
28853
|
for (const [name, provider] of Object.entries(configured)) {
|
|
@@ -28523,7 +29116,7 @@ async function generateSleepReview(registry, modelId, prompt, signal) {
|
|
|
28523
29116
|
}
|
|
28524
29117
|
async function optionalText2(path) {
|
|
28525
29118
|
try {
|
|
28526
|
-
return await
|
|
29119
|
+
return await readFile29(path, "utf8");
|
|
28527
29120
|
} catch (error) {
|
|
28528
29121
|
if (typeof error === "object" && error !== null && "code" in error && error.code === "ENOENT") return void 0;
|
|
28529
29122
|
throw error;
|
|
@@ -28608,7 +29201,17 @@ function attachCleanupError(primary, cleanup) {
|
|
|
28608
29201
|
}
|
|
28609
29202
|
}
|
|
28610
29203
|
async function* runGoalSession(orchestrator, goal, signal) {
|
|
28611
|
-
|
|
29204
|
+
yield* goalSessionEvents(orchestrator.run({ goal, signal }));
|
|
29205
|
+
}
|
|
29206
|
+
async function* runResumeGoalSession(orchestrator, goalId, signal) {
|
|
29207
|
+
yield* goalSessionEvents(orchestrator.resume({ goalId, signal }));
|
|
29208
|
+
}
|
|
29209
|
+
async function* goalSessionEvents(events) {
|
|
29210
|
+
for await (const event of events) {
|
|
29211
|
+
if (event.type === "goal-resumed") {
|
|
29212
|
+
yield { type: "notice", message: `Resuming goal ${event.goalId} at round ${event.round} after a heap rotation.` };
|
|
29213
|
+
continue;
|
|
29214
|
+
}
|
|
28612
29215
|
if (event.type === "goal-plan-created") {
|
|
28613
29216
|
yield { type: "notice", message: `Goal plan created (${event.plan.kind}) with ${event.plan.criteria.length} acceptance criteria.` };
|
|
28614
29217
|
yield { type: "notice", message: `Plan file: ${event.planPath}` };
|
|
@@ -28685,13 +29288,13 @@ function createMemoryTaskId() {
|
|
|
28685
29288
|
|
|
28686
29289
|
// src/desktop/runtime-controller.ts
|
|
28687
29290
|
import { createHash as createHash27 } from "crypto";
|
|
28688
|
-
import { mkdir as mkdir27, readFile as
|
|
29291
|
+
import { mkdir as mkdir27, readFile as readFile37, readdir as readdir13, rm as rm15, stat as stat17, writeFile as writeFile17 } from "fs/promises";
|
|
28689
29292
|
import { homedir as homedir5 } from "os";
|
|
28690
|
-
import { basename as basename10, join as
|
|
29293
|
+
import { basename as basename10, join as join48, resolve as resolve39 } from "path";
|
|
28691
29294
|
|
|
28692
29295
|
// src/d2c/workflow.ts
|
|
28693
29296
|
import { createHash as createHash22 } from "crypto";
|
|
28694
|
-
import { join as
|
|
29297
|
+
import { join as join39 } from "path";
|
|
28695
29298
|
import { z as z42 } from "zod";
|
|
28696
29299
|
|
|
28697
29300
|
// src/d2c/workflow-shared.ts
|
|
@@ -28837,7 +29440,7 @@ function reviewsFor(report, previous) {
|
|
|
28837
29440
|
function implementationDirectory(report) {
|
|
28838
29441
|
const source = report.implementation.source;
|
|
28839
29442
|
if (!/^https?:\/\//i.test(source)) return source;
|
|
28840
|
-
return
|
|
29443
|
+
return join39("src", "d2c-output", report.task);
|
|
28841
29444
|
}
|
|
28842
29445
|
function createWorkflow(report, framework) {
|
|
28843
29446
|
const now = (/* @__PURE__ */ new Date()).toISOString();
|
|
@@ -28961,7 +29564,7 @@ function applyQualityIssueDecision(workflow, issueId2, decision) {
|
|
|
28961
29564
|
};
|
|
28962
29565
|
}
|
|
28963
29566
|
function workflowPath(workspace, task) {
|
|
28964
|
-
return
|
|
29567
|
+
return join39(taskDir(workspace, task), "workflow.json");
|
|
28965
29568
|
}
|
|
28966
29569
|
async function readWorkflow(workspace, task) {
|
|
28967
29570
|
return (await readRecoverableFile(workflowPath(workspace, task), decodeWorkflow))?.value;
|
|
@@ -29214,8 +29817,8 @@ function confirmApiMapping(mapping, operationKey, operations) {
|
|
|
29214
29817
|
}
|
|
29215
29818
|
|
|
29216
29819
|
// src/d2c/modules.ts
|
|
29217
|
-
import { readFile as
|
|
29218
|
-
import { isAbsolute as isAbsolute18, join as
|
|
29820
|
+
import { readFile as readFile30 } from "fs/promises";
|
|
29821
|
+
import { isAbsolute as isAbsolute18, join as join40, normalize as normalize2, posix, sep as sep26, win32 as win322 } from "path";
|
|
29219
29822
|
import { z as z43 } from "zod";
|
|
29220
29823
|
var ModuleSchema = z43.object({
|
|
29221
29824
|
id: z43.string().regex(/^[A-Za-z0-9][A-Za-z0-9._-]{0,127}$/),
|
|
@@ -29234,13 +29837,13 @@ function safeSource(path) {
|
|
|
29234
29837
|
}
|
|
29235
29838
|
function d2cOutputDirectory(workspace, task) {
|
|
29236
29839
|
if (!D2C_TASK_PATTERN.test(task)) throw new Error(`Invalid D2C task: ${task}`);
|
|
29237
|
-
return
|
|
29840
|
+
return join40(workspace, "src", "d2c-output", task);
|
|
29238
29841
|
}
|
|
29239
29842
|
async function readD2cModules(workspace, task) {
|
|
29240
29843
|
const project = d2cOutputDirectory(workspace, task);
|
|
29241
29844
|
let raw;
|
|
29242
29845
|
try {
|
|
29243
|
-
raw = await
|
|
29846
|
+
raw = await readFile30(join40(project, "d2c.modules.json"), "utf8");
|
|
29244
29847
|
} catch {
|
|
29245
29848
|
return [{ id: "page", label: task, sourceFiles: ["src/App.vue", "src/App.jsx"], keywords: [task] }];
|
|
29246
29849
|
}
|
|
@@ -29260,8 +29863,8 @@ async function readD2cModules(workspace, task) {
|
|
|
29260
29863
|
}
|
|
29261
29864
|
|
|
29262
29865
|
// src/d2c/integration.ts
|
|
29263
|
-
import { mkdir as mkdir24, readFile as
|
|
29264
|
-
import { join as
|
|
29866
|
+
import { mkdir as mkdir24, readFile as readFile31, writeFile as writeFile13 } from "fs/promises";
|
|
29867
|
+
import { join as join41 } from "path";
|
|
29265
29868
|
function record2(value) {
|
|
29266
29869
|
return typeof value === "object" && value !== null && !Array.isArray(value) ? value : void 0;
|
|
29267
29870
|
}
|
|
@@ -29397,19 +30000,19 @@ async def reset_acceptance(request: Request):
|
|
|
29397
30000
|
}
|
|
29398
30001
|
async function generateIntegrationArtifacts(projectDir, document, mappings, options = {}) {
|
|
29399
30002
|
if (mappings.some((mapping) => mapping.status === "needs-confirmation")) throw new Error("Confirm every uncertain OpenAPI mapping before generating integration code");
|
|
29400
|
-
const packagePath =
|
|
30003
|
+
const packagePath = join41(projectDir, "package.json");
|
|
29401
30004
|
let pkg;
|
|
29402
30005
|
try {
|
|
29403
|
-
pkg = JSON.parse(await
|
|
30006
|
+
pkg = JSON.parse(await readFile31(packagePath, "utf8"));
|
|
29404
30007
|
} catch {
|
|
29405
30008
|
throw new Error(`D2C integration requires a valid package.json at ${packagePath}`);
|
|
29406
30009
|
}
|
|
29407
30010
|
const scripts = options.pythonServer ? { ...record2(pkg.scripts) ?? {} } : { ...record2(pkg.scripts) ?? {}, mock: "node mock/server.mjs" };
|
|
29408
30011
|
const dependencies2 = options.pythonServer ? { ...record2(pkg.dependencies) ?? {}, axios: "^1.7.9" } : { ...record2(pkg.dependencies) ?? {}, axios: "^1.7.9", express: "^5.2.1" };
|
|
29409
30012
|
const selected = [...new Set(mappings.map((mapping) => mapping.operationKey))].map((key) => document.operations.find((operation) => operation.id === key)).filter((operation) => operation !== void 0);
|
|
29410
|
-
const apiDir =
|
|
29411
|
-
const mockDir =
|
|
29412
|
-
const serverDir =
|
|
30013
|
+
const apiDir = join41(projectDir, "src", "api");
|
|
30014
|
+
const mockDir = join41(projectDir, "mock");
|
|
30015
|
+
const serverDir = join41(projectDir, "server");
|
|
29413
30016
|
await Promise.all([
|
|
29414
30017
|
mkdir24(apiDir, { recursive: true }),
|
|
29415
30018
|
...options.pythonServer ? [] : [mkdir24(mockDir, { recursive: true })],
|
|
@@ -29419,23 +30022,23 @@ async function generateIntegrationArtifacts(projectDir, document, mappings, opti
|
|
|
29419
30022
|
if (options.pythonServer) files.push("server/main.py", "server/requirements.txt", "server/README.md", "server/flavor-runtime.json");
|
|
29420
30023
|
else files.push("mock/server.mjs");
|
|
29421
30024
|
await Promise.all([
|
|
29422
|
-
writeFile13(
|
|
30025
|
+
writeFile13(join41(apiDir, "http.js"), `import axios from "axios";
|
|
29423
30026
|
|
|
29424
30027
|
export const http = axios.create({ baseURL: import.meta.env.VITE_API_BASE_URL || ${JSON.stringify(document.baseUrl ?? "")}, timeout: 15000 });
|
|
29425
30028
|
|
|
29426
30029
|
http.interceptors.response.use((response) => response, (error) => Promise.reject({ name: "ApiError", status: error.response?.status, message: error.response?.data?.message || error.message, cause: error }));
|
|
29427
30030
|
`),
|
|
29428
|
-
writeFile13(
|
|
29429
|
-
writeFile13(
|
|
30031
|
+
writeFile13(join41(apiDir, "d2c-api.js"), apiModule(selected)),
|
|
30032
|
+
writeFile13(join41(apiDir, "d2c-bindings.json"), `${JSON.stringify({ schema: 1, generatedAt: (/* @__PURE__ */ new Date()).toISOString(), mappings }, null, 2)}
|
|
29430
30033
|
`),
|
|
29431
|
-
...options.pythonServer ? [] : [writeFile13(
|
|
30034
|
+
...options.pythonServer ? [] : [writeFile13(join41(mockDir, "server.mjs"), mockModule({ ...document, operations: selected }))],
|
|
29432
30035
|
writeFile13(packagePath, `${JSON.stringify({ ...pkg, scripts, dependencies: dependencies2 }, null, 2)}
|
|
29433
30036
|
`),
|
|
29434
30037
|
...options.pythonServer ? [
|
|
29435
|
-
writeFile13(
|
|
29436
|
-
writeFile13(
|
|
29437
|
-
writeFile13(
|
|
29438
|
-
writeFile13(
|
|
30038
|
+
writeFile13(join41(serverDir, "main.py"), pythonServerModule({ ...document, operations: selected })),
|
|
30039
|
+
writeFile13(join41(serverDir, "requirements.txt"), "fastapi>=0.116,<1\nuvicorn[standard]>=0.35,<1\nsqlalchemy>=2.0,<3\n"),
|
|
30040
|
+
writeFile13(join41(serverDir, "README.md"), "# Python API service\n\nThis is the real integration service, not a mock. Development defaults to SQLite through `DATABASE_URL=sqlite:///./data/app.db`. Set the same environment variable to a SQLAlchemy MySQL or PostgreSQL URL for deployment; keep schema changes in migrations instead of database-specific SQL. Flavor Code starts the service automatically for acceptance.\n"),
|
|
30041
|
+
writeFile13(join41(serverDir, "flavor-runtime.json"), `${JSON.stringify({
|
|
29439
30042
|
schema: 1,
|
|
29440
30043
|
kind: "python-fastapi",
|
|
29441
30044
|
cwd: "server",
|
|
@@ -29454,9 +30057,9 @@ http.interceptors.response.use((response) => response, (error) => Promise.reject
|
|
|
29454
30057
|
|
|
29455
30058
|
// src/d2c/mock-runner.ts
|
|
29456
30059
|
import { spawn as spawn9 } from "child_process";
|
|
29457
|
-
import { access as access4, readFile as
|
|
30060
|
+
import { access as access4, readFile as readFile32 } from "fs/promises";
|
|
29458
30061
|
import { createServer as createServer5 } from "net";
|
|
29459
|
-
import { join as
|
|
30062
|
+
import { join as join42 } from "path";
|
|
29460
30063
|
async function exists3(path) {
|
|
29461
30064
|
try {
|
|
29462
30065
|
await access4(path);
|
|
@@ -29564,7 +30167,7 @@ ${output.trim()}` : ""}`);
|
|
|
29564
30167
|
}
|
|
29565
30168
|
}
|
|
29566
30169
|
async function runPythonBackend(projectDir, options) {
|
|
29567
|
-
const serverDir =
|
|
30170
|
+
const serverDir = join42(projectDir, "server");
|
|
29568
30171
|
if (options.installDependencies !== false) await installPython(serverDir, options.signal);
|
|
29569
30172
|
const selectedPort = await port();
|
|
29570
30173
|
const url = `http://127.0.0.1:${selectedPort}`;
|
|
@@ -29610,20 +30213,20 @@ ${managed.output().slice(-4e3)}`);
|
|
|
29610
30213
|
};
|
|
29611
30214
|
}
|
|
29612
30215
|
async function runD2cMockServer(projectDir, options = {}) {
|
|
29613
|
-
const runtimePath =
|
|
30216
|
+
const runtimePath = join42(projectDir, "server", "flavor-runtime.json");
|
|
29614
30217
|
if (await exists3(runtimePath)) {
|
|
29615
30218
|
let runtime;
|
|
29616
30219
|
try {
|
|
29617
|
-
runtime = JSON.parse(await
|
|
30220
|
+
runtime = JSON.parse(await readFile32(runtimePath, "utf8"));
|
|
29618
30221
|
} catch {
|
|
29619
30222
|
throw new Error(`E2E backend runtime manifest is invalid at ${runtimePath}`);
|
|
29620
30223
|
}
|
|
29621
30224
|
if (runtime.schema !== 1 || runtime.kind !== "python-fastapi") throw new Error("Unsupported E2E backend runtime manifest");
|
|
29622
30225
|
return runPythonBackend(projectDir, options);
|
|
29623
30226
|
}
|
|
29624
|
-
const serverPath =
|
|
30227
|
+
const serverPath = join42(projectDir, "mock", "server.mjs");
|
|
29625
30228
|
if (!await exists3(serverPath)) throw new Error(`D2C mock server is not generated at ${serverPath}`);
|
|
29626
|
-
if (options.installDependencies === false && !await exists3(
|
|
30229
|
+
if (options.installDependencies === false && !await exists3(join42(projectDir, "node_modules", "express", "package.json"))) {
|
|
29627
30230
|
throw new Error("D2C mock server dependencies are not installed");
|
|
29628
30231
|
}
|
|
29629
30232
|
if (options.installDependencies !== false) await install(projectDir, options.signal);
|
|
@@ -29665,13 +30268,13 @@ ${managed.output().slice(-4e3)}`);
|
|
|
29665
30268
|
|
|
29666
30269
|
// src/d2c/product.ts
|
|
29667
30270
|
import { createHash as createHash24, randomUUID as randomUUID27 } from "crypto";
|
|
29668
|
-
import { copyFile as copyFile2, mkdir as mkdir25, readFile as
|
|
29669
|
-
import { join as
|
|
30271
|
+
import { copyFile as copyFile2, mkdir as mkdir25, readFile as readFile34, rename as rename13, stat as stat15, unlink as unlink7, writeFile as writeFile15 } from "fs/promises";
|
|
30272
|
+
import { join as join43 } from "path";
|
|
29670
30273
|
import { z as z44 } from "zod";
|
|
29671
30274
|
|
|
29672
30275
|
// src/e2e/prd-governance.ts
|
|
29673
30276
|
import { createHash as createHash23, randomUUID as randomUUID26 } from "crypto";
|
|
29674
|
-
import { readFile as
|
|
30277
|
+
import { readFile as readFile33, rename as rename12, rm as rm13, writeFile as writeFile14 } from "fs/promises";
|
|
29675
30278
|
function hashPrd(markdown) {
|
|
29676
30279
|
return createHash23("sha256").update(markdown, "utf8").digest("hex");
|
|
29677
30280
|
}
|
|
@@ -29748,14 +30351,14 @@ function approvePrd(markdown, now = /* @__PURE__ */ new Date()) {
|
|
|
29748
30351
|
return { hash: hashPrd(markdown), approvedAt: now.toISOString(), criteria };
|
|
29749
30352
|
}
|
|
29750
30353
|
async function assertApprovedPrd(path, approved) {
|
|
29751
|
-
const current = await
|
|
30354
|
+
const current = await readFile33(path, "utf8");
|
|
29752
30355
|
if (hashPrd(current) !== approved.hash) {
|
|
29753
30356
|
throw new Error("PRD_LOCK_VIOLATION: confirmed PRD content changed after approval");
|
|
29754
30357
|
}
|
|
29755
30358
|
return approved;
|
|
29756
30359
|
}
|
|
29757
30360
|
async function updatePrdSectionFile(path, input) {
|
|
29758
|
-
const current = await
|
|
30361
|
+
const current = await readFile33(path, "utf8");
|
|
29759
30362
|
if (hashPrd(current) !== input.expectedHash) {
|
|
29760
30363
|
throw new Error("PRD_EDIT_CONFLICT: the PRD changed; refresh before saving this section");
|
|
29761
30364
|
}
|
|
@@ -29821,13 +30424,13 @@ var PlanSchema2 = z44.object({
|
|
|
29821
30424
|
}).strict();
|
|
29822
30425
|
function d2cProductDirectory(workspace, task) {
|
|
29823
30426
|
if (!D2C_TASK_PATTERN.test(task)) throw new Error("Invalid D2C task name");
|
|
29824
|
-
return
|
|
30427
|
+
return join43(taskDir(workspace, task), "product");
|
|
29825
30428
|
}
|
|
29826
30429
|
function d2cProductPrototypeDirectory(workspace, task) {
|
|
29827
|
-
return
|
|
30430
|
+
return join43(d2cProductDirectory(workspace, task), "prototype");
|
|
29828
30431
|
}
|
|
29829
30432
|
function planPath(workspace, task) {
|
|
29830
|
-
return
|
|
30433
|
+
return join43(taskDir(workspace, task), "product-plan.json");
|
|
29831
30434
|
}
|
|
29832
30435
|
function resolveD2cProductTechnology(requirement, fallbackFramework = "vue") {
|
|
29833
30436
|
const frontendCandidates = [
|
|
@@ -29915,18 +30518,18 @@ function buildD2cProductOpenApi(task, modules) {
|
|
|
29915
30518
|
`;
|
|
29916
30519
|
}
|
|
29917
30520
|
async function fileTimestamp(path) {
|
|
29918
|
-
const info = await
|
|
30521
|
+
const info = await stat15(path).catch(() => void 0);
|
|
29919
30522
|
return info?.isFile() ? info.mtime.toISOString() : void 0;
|
|
29920
30523
|
}
|
|
29921
30524
|
async function fileHash(path) {
|
|
29922
|
-
const buffer = await
|
|
30525
|
+
const buffer = await readFile34(path).catch((error) => {
|
|
29923
30526
|
if (error.code === "ENOENT") return void 0;
|
|
29924
30527
|
throw error;
|
|
29925
30528
|
});
|
|
29926
30529
|
return buffer === void 0 ? void 0 : createHash24("sha256").update(buffer).digest("hex");
|
|
29927
30530
|
}
|
|
29928
30531
|
async function validateAndNormalizePrototypeManifest(path) {
|
|
29929
|
-
const raw = await
|
|
30532
|
+
const raw = await readFile34(path, "utf8").catch((error) => {
|
|
29930
30533
|
if (error.code === "ENOENT") return void 0;
|
|
29931
30534
|
throw error;
|
|
29932
30535
|
});
|
|
@@ -30002,7 +30605,7 @@ async function createD2cProductPlan(workspace, input, now = () => /* @__PURE__ *
|
|
|
30002
30605
|
});
|
|
30003
30606
|
}
|
|
30004
30607
|
async function readD2cProductPlan(workspace, task) {
|
|
30005
|
-
const raw = await
|
|
30608
|
+
const raw = await readFile34(planPath(workspace, task), "utf8").catch((error) => {
|
|
30006
30609
|
if (error.code === "ENOENT") return void 0;
|
|
30007
30610
|
throw error;
|
|
30008
30611
|
});
|
|
@@ -30010,16 +30613,16 @@ async function readD2cProductPlan(workspace, task) {
|
|
|
30010
30613
|
}
|
|
30011
30614
|
async function discoverD2cProductArtifacts(workspace, plan) {
|
|
30012
30615
|
const product = d2cProductDirectory(workspace, plan.task);
|
|
30013
|
-
const prdUpdatedAt = await fileTimestamp(
|
|
30014
|
-
const prdHash = await fileHash(
|
|
30015
|
-
const prototypeUpdatedAt = await fileTimestamp(
|
|
30016
|
-
const prototypeHash = await fileHash(
|
|
30017
|
-
const expectedManifest =
|
|
30616
|
+
const prdUpdatedAt = await fileTimestamp(join43(product, "prd.md"));
|
|
30617
|
+
const prdHash = await fileHash(join43(product, "prd.md"));
|
|
30618
|
+
const prototypeUpdatedAt = await fileTimestamp(join43(product, "prototype", "index.html"));
|
|
30619
|
+
const prototypeHash = await fileHash(join43(product, "prototype", "index.html"));
|
|
30620
|
+
const expectedManifest = join43(product, "prototype", "interaction-manifest.json");
|
|
30018
30621
|
let manifestUpdatedAt = await fileTimestamp(expectedManifest);
|
|
30019
30622
|
if (manifestUpdatedAt === void 0) {
|
|
30020
|
-
const misplacedManifest =
|
|
30623
|
+
const misplacedManifest = join43(product, "interaction-manifest.json");
|
|
30021
30624
|
if (await fileTimestamp(misplacedManifest) !== void 0) {
|
|
30022
|
-
await mkdir25(
|
|
30625
|
+
await mkdir25(join43(product, "prototype"), { recursive: true });
|
|
30023
30626
|
await copyFile2(misplacedManifest, expectedManifest);
|
|
30024
30627
|
manifestUpdatedAt = await fileTimestamp(expectedManifest);
|
|
30025
30628
|
}
|
|
@@ -30052,8 +30655,8 @@ async function readD2cProductPlanView(workspace, task) {
|
|
|
30052
30655
|
if (stored === void 0) return void 0;
|
|
30053
30656
|
const plan = await discoverD2cProductArtifacts(workspace, stored);
|
|
30054
30657
|
if (plan.revision !== stored.revision) await writeD2cProductPlan(workspace, plan);
|
|
30055
|
-
const prdMarkdown = plan.prd === void 0 ? void 0 : await
|
|
30056
|
-
const manifestPath =
|
|
30658
|
+
const prdMarkdown = plan.prd === void 0 ? void 0 : await readFile34(join43(taskDir(workspace, task), plan.prd.path), "utf8");
|
|
30659
|
+
const manifestPath = join43(d2cProductDirectory(workspace, task), "prototype", "interaction-manifest.json");
|
|
30057
30660
|
const validationError = plan.phase === "design-generating" && await fileTimestamp(manifestPath) !== void 0 ? await validateAndNormalizePrototypeManifest(manifestPath) : void 0;
|
|
30058
30661
|
return {
|
|
30059
30662
|
plan,
|
|
@@ -30143,9 +30746,9 @@ ${prdMarkdown.slice(0, 2e4)}`
|
|
|
30143
30746
|
|
|
30144
30747
|
// src/e2e/acceptance-baseline.ts
|
|
30145
30748
|
import { createHash as createHash25 } from "crypto";
|
|
30146
|
-
import { readFile as
|
|
30749
|
+
import { readFile as readFile35 } from "fs/promises";
|
|
30147
30750
|
async function fileArtifact(path) {
|
|
30148
|
-
const content = await
|
|
30751
|
+
const content = await readFile35(path);
|
|
30149
30752
|
return { path, hash: createHash25("sha256").update(content).digest("hex"), bytes: content.byteLength };
|
|
30150
30753
|
}
|
|
30151
30754
|
async function verifyAcceptanceArtifact(path, expected, label) {
|
|
@@ -30212,7 +30815,7 @@ function createAcceptanceEvidence(coverage, run, now = /* @__PURE__ */ new Date(
|
|
|
30212
30815
|
|
|
30213
30816
|
// src/e2e/delivery-run.ts
|
|
30214
30817
|
import { createHash as createHash26 } from "crypto";
|
|
30215
|
-
import { join as
|
|
30818
|
+
import { join as join44 } from "path";
|
|
30216
30819
|
import { z as z45 } from "zod";
|
|
30217
30820
|
var DELIVERY_NODE_IDS = ["requirement", "prd", "design", "d2c", "api", "acceptance", "delivery"];
|
|
30218
30821
|
var ArtifactSchema = z45.object({
|
|
@@ -30329,7 +30932,7 @@ function completeDeliveryNode(run, node, outputs, now = /* @__PURE__ */ new Date
|
|
|
30329
30932
|
return { ...run, nodes, updatedAt: timestamp2 };
|
|
30330
30933
|
}
|
|
30331
30934
|
function deliveryRunPath(workspace, task) {
|
|
30332
|
-
return
|
|
30935
|
+
return join44(taskDir(workspace, task), "delivery-run.json");
|
|
30333
30936
|
}
|
|
30334
30937
|
function decodeDeliveryRun(raw) {
|
|
30335
30938
|
return DeliveryRunSchema.parse(JSON.parse(raw));
|
|
@@ -30370,9 +30973,9 @@ async function updateDeliveryRun(workspace, task, expectedRevision, mutation) {
|
|
|
30370
30973
|
}
|
|
30371
30974
|
|
|
30372
30975
|
// src/skills/manager.ts
|
|
30373
|
-
import { lstat as lstat14, mkdir as mkdir26, readFile as
|
|
30976
|
+
import { lstat as lstat14, mkdir as mkdir26, readFile as readFile36, readdir as readdir12, realpath as realpath13, rm as rm14, writeFile as writeFile16 } from "fs/promises";
|
|
30374
30977
|
import { homedir as homedir4 } from "os";
|
|
30375
|
-
import { isAbsolute as isAbsolute19, join as
|
|
30978
|
+
import { isAbsolute as isAbsolute19, join as join45, relative as relative26, resolve as resolve37, sep as sep27 } from "path";
|
|
30376
30979
|
import { stringify } from "yaml";
|
|
30377
30980
|
var SkillManager = class {
|
|
30378
30981
|
#workspace;
|
|
@@ -30395,7 +30998,7 @@ var SkillManager = class {
|
|
|
30395
30998
|
async create(draft) {
|
|
30396
30999
|
const value = parseDraft(draft);
|
|
30397
31000
|
const projectRoot = this.#projectRoot();
|
|
30398
|
-
const directory =
|
|
31001
|
+
const directory = join45(projectRoot, value.name);
|
|
30399
31002
|
await mkdir26(projectRoot, { recursive: true, mode: 448 });
|
|
30400
31003
|
try {
|
|
30401
31004
|
await mkdir26(directory, { mode: 448 });
|
|
@@ -30404,7 +31007,7 @@ var SkillManager = class {
|
|
|
30404
31007
|
throw error;
|
|
30405
31008
|
}
|
|
30406
31009
|
try {
|
|
30407
|
-
await writeFile16(
|
|
31010
|
+
await writeFile16(join45(directory, "SKILL.md"), renderSkill(value), { encoding: "utf8", flag: "wx", mode: 384 });
|
|
30408
31011
|
await this.#persistEnabled(value.name, true);
|
|
30409
31012
|
return this.get(value.name);
|
|
30410
31013
|
} catch (error) {
|
|
@@ -30422,7 +31025,7 @@ var SkillManager = class {
|
|
|
30422
31025
|
throw error;
|
|
30423
31026
|
});
|
|
30424
31027
|
if (!info.isDirectory() || info.isSymbolicLink()) throw new Error(`Project skill path is not a real directory: ${validName}`);
|
|
30425
|
-
const skillFile =
|
|
31028
|
+
const skillFile = join45(directory, "SKILL.md");
|
|
30426
31029
|
const fileInfo = await lstat14(skillFile);
|
|
30427
31030
|
if (!fileInfo.isFile() || fileInfo.isSymbolicLink()) throw new Error(`Project skill file is not a real file: ${validName}`);
|
|
30428
31031
|
await writeFile16(skillFile, renderSkill(value), { encoding: "utf8", mode: 384 });
|
|
@@ -30454,12 +31057,12 @@ var SkillManager = class {
|
|
|
30454
31057
|
async #registry() {
|
|
30455
31058
|
const loaded = await loadConfig({ cwd: this.#workspace, home: this.#home });
|
|
30456
31059
|
const [globalPluginRoots, projectPluginRoots] = await Promise.all([
|
|
30457
|
-
discoverPluginSkillRoots(
|
|
30458
|
-
discoverPluginSkillRoots(
|
|
31060
|
+
discoverPluginSkillRoots(join45(this.#home, ".flavor-code", "plugins")),
|
|
31061
|
+
discoverPluginSkillRoots(join45(this.#workspace, ".flavor", "plugins"))
|
|
30459
31062
|
]);
|
|
30460
31063
|
return {
|
|
30461
31064
|
registry: new SkillRegistry({
|
|
30462
|
-
globalRoots: [
|
|
31065
|
+
globalRoots: [join45(this.#home, ".flavor-code", "skills"), ...globalPluginRoots],
|
|
30463
31066
|
projectRoots: [this.#projectRoot(), ...projectPluginRoots]
|
|
30464
31067
|
}),
|
|
30465
31068
|
disabled: new Set(loaded.config.skills.disabled)
|
|
@@ -30475,10 +31078,10 @@ var SkillManager = class {
|
|
|
30475
31078
|
};
|
|
30476
31079
|
}
|
|
30477
31080
|
#projectRoot() {
|
|
30478
|
-
return
|
|
31081
|
+
return join45(this.#workspace, ".flavor", "skills");
|
|
30479
31082
|
}
|
|
30480
31083
|
#projectSkillDirectory(name) {
|
|
30481
|
-
return
|
|
31084
|
+
return join45(this.#projectRoot(), name);
|
|
30482
31085
|
}
|
|
30483
31086
|
};
|
|
30484
31087
|
function parseName(value) {
|
|
@@ -30519,9 +31122,9 @@ async function discoverPluginSkillRoots(directory) {
|
|
|
30519
31122
|
const roots = [];
|
|
30520
31123
|
for (const entry2 of entries) {
|
|
30521
31124
|
if (!entry2.isDirectory() || entry2.isSymbolicLink()) continue;
|
|
30522
|
-
const pluginRoot = await realpath13(
|
|
31125
|
+
const pluginRoot = await realpath13(join45(directory, entry2.name));
|
|
30523
31126
|
try {
|
|
30524
|
-
const manifest = PluginManifestSchema.parse(JSON.parse(await
|
|
31127
|
+
const manifest = PluginManifestSchema.parse(JSON.parse(await readFile36(join45(pluginRoot, "flavor-plugin.json"), "utf8")));
|
|
30525
31128
|
for (const contribution of manifest.contributes.skillRoots) {
|
|
30526
31129
|
const candidate = resolve37(pluginRoot, contribution.path);
|
|
30527
31130
|
const info = await lstat14(candidate);
|
|
@@ -30596,11 +31199,11 @@ async function createProjectMemoryManager(options) {
|
|
|
30596
31199
|
}
|
|
30597
31200
|
|
|
30598
31201
|
// src/mcp/config-manager.ts
|
|
30599
|
-
import { join as
|
|
31202
|
+
import { join as join46 } from "path";
|
|
30600
31203
|
var ProjectMcpConfigManager = class {
|
|
30601
31204
|
path;
|
|
30602
31205
|
constructor(workspace) {
|
|
30603
|
-
this.path =
|
|
31206
|
+
this.path = join46(workspace, ".flavor", "flavor.json");
|
|
30604
31207
|
}
|
|
30605
31208
|
async list() {
|
|
30606
31209
|
const document = await readRecoverableFile(this.path, (raw) => parseDocument2(this.path, raw));
|
|
@@ -30697,7 +31300,7 @@ function isRecord7(value) {
|
|
|
30697
31300
|
}
|
|
30698
31301
|
|
|
30699
31302
|
// src/desktop/model-config.ts
|
|
30700
|
-
import { join as
|
|
31303
|
+
import { join as join47 } from "path";
|
|
30701
31304
|
var DEFAULT_DESKTOP_MODELS = [
|
|
30702
31305
|
{
|
|
30703
31306
|
id: "anthropic:deepseek-v4-pro",
|
|
@@ -30718,7 +31321,7 @@ var DEFAULT_DESKTOP_MODELS = [
|
|
|
30718
31321
|
];
|
|
30719
31322
|
async function loadDesktopModels(workspace, home) {
|
|
30720
31323
|
try {
|
|
30721
|
-
const tokens2 = await createFileTokenStore(
|
|
31324
|
+
const tokens2 = await createFileTokenStore(join47(home, ".flavor-code", "auth.json")).load();
|
|
30722
31325
|
const pkceModels = [];
|
|
30723
31326
|
const knownPkce = /* @__PURE__ */ new Set();
|
|
30724
31327
|
for (const token of Object.values(tokens2)) {
|
|
@@ -30786,7 +31389,7 @@ function protocolLabel(type) {
|
|
|
30786
31389
|
|
|
30787
31390
|
// src/desktop/d2c-product-preview.ts
|
|
30788
31391
|
import { createReadStream as createReadStream2 } from "fs";
|
|
30789
|
-
import { realpath as realpath14, stat as
|
|
31392
|
+
import { realpath as realpath14, stat as stat16 } from "fs/promises";
|
|
30790
31393
|
import { createServer as createServer6 } from "http";
|
|
30791
31394
|
import { extname as extname4, isAbsolute as isAbsolute20, relative as relative27, resolve as resolve38, sep as sep28 } from "path";
|
|
30792
31395
|
var MIME_TYPES = {
|
|
@@ -30811,7 +31414,7 @@ function isInside(root, candidate) {
|
|
|
30811
31414
|
}
|
|
30812
31415
|
async function startD2cProductPreview(rootDirectory) {
|
|
30813
31416
|
const root = await realpath14(resolve38(rootDirectory));
|
|
30814
|
-
const rootInfo = await
|
|
31417
|
+
const rootInfo = await stat16(root);
|
|
30815
31418
|
if (!rootInfo.isDirectory()) throw new Error("D2C product prototype directory is unavailable");
|
|
30816
31419
|
const server = createServer6(async (request, response2) => {
|
|
30817
31420
|
response2.setHeader("cache-control", "no-store");
|
|
@@ -30826,7 +31429,7 @@ async function startD2cProductPreview(rootDirectory) {
|
|
|
30826
31429
|
if (!isInside(root, unresolved)) return sendNotFound(response2);
|
|
30827
31430
|
const target = await realpath14(unresolved).catch(() => void 0);
|
|
30828
31431
|
if (target === void 0 || !isInside(root, target)) return sendNotFound(response2);
|
|
30829
|
-
const info = await
|
|
31432
|
+
const info = await stat16(target);
|
|
30830
31433
|
if (!info.isFile()) return sendNotFound(response2);
|
|
30831
31434
|
response2.writeHead(200, {
|
|
30832
31435
|
"content-length": info.size,
|
|
@@ -30863,33 +31466,33 @@ function pngDataUrl(png) {
|
|
|
30863
31466
|
}
|
|
30864
31467
|
async function detectD2cFramework(workspace, task) {
|
|
30865
31468
|
try {
|
|
30866
|
-
const pkg = JSON.parse(await
|
|
31469
|
+
const pkg = JSON.parse(await readFile37(join48(d2cOutputDirectory(workspace, task), "package.json"), "utf8"));
|
|
30867
31470
|
return pkg.dependencies?.react === void 0 ? "vue" : "react";
|
|
30868
31471
|
} catch {
|
|
30869
31472
|
return "vue";
|
|
30870
31473
|
}
|
|
30871
31474
|
}
|
|
30872
31475
|
function integrationDirectory(workspace, task) {
|
|
30873
|
-
return
|
|
31476
|
+
return join48(workspace, ".flavor", "d2c", task, "integration");
|
|
30874
31477
|
}
|
|
30875
31478
|
var D2C_BACKEND_SOURCE_EXTENSIONS = /* @__PURE__ */ new Set([".cfg", ".env", ".ini", ".js", ".json", ".lock", ".mjs", ".py", ".toml", ".txt", ".yaml", ".yml"]);
|
|
30876
31479
|
var D2C_BACKEND_IGNORED_DIRECTORIES = /* @__PURE__ */ new Set([".git", ".venv", "__pycache__", "data", "node_modules", "venv"]);
|
|
30877
31480
|
async function d2cBackendSourceFingerprint(project) {
|
|
30878
|
-
const runtimeManifest =
|
|
30879
|
-
const realBackend = (await
|
|
30880
|
-
const root = realBackend ?
|
|
30881
|
-
if ((await
|
|
31481
|
+
const runtimeManifest = join48(project, "server", "flavor-runtime.json");
|
|
31482
|
+
const realBackend = (await stat17(runtimeManifest).catch(() => void 0))?.isFile() === true;
|
|
31483
|
+
const root = realBackend ? join48(project, "server") : join48(project, "mock");
|
|
31484
|
+
if ((await stat17(root).catch(() => void 0))?.isDirectory() !== true) return void 0;
|
|
30882
31485
|
const files = [];
|
|
30883
31486
|
const visit = async (directory) => {
|
|
30884
31487
|
const entries = await readdir13(directory, { withFileTypes: true });
|
|
30885
31488
|
for (const entry2 of entries) {
|
|
30886
31489
|
if (entry2.isDirectory()) {
|
|
30887
|
-
if (!D2C_BACKEND_IGNORED_DIRECTORIES.has(entry2.name)) await visit(
|
|
31490
|
+
if (!D2C_BACKEND_IGNORED_DIRECTORIES.has(entry2.name)) await visit(join48(directory, entry2.name));
|
|
30888
31491
|
continue;
|
|
30889
31492
|
}
|
|
30890
31493
|
if (!entry2.isFile()) continue;
|
|
30891
31494
|
const extension2 = entry2.name.includes(".") ? `.${entry2.name.split(".").pop().toLowerCase()}` : "";
|
|
30892
|
-
if (D2C_BACKEND_SOURCE_EXTENSIONS.has(extension2)) files.push(
|
|
31495
|
+
if (D2C_BACKEND_SOURCE_EXTENSIONS.has(extension2)) files.push(join48(directory, entry2.name));
|
|
30893
31496
|
}
|
|
30894
31497
|
};
|
|
30895
31498
|
await visit(root);
|
|
@@ -30897,7 +31500,7 @@ async function d2cBackendSourceFingerprint(project) {
|
|
|
30897
31500
|
for (const path of files.sort()) {
|
|
30898
31501
|
hash.update(path.slice(root.length));
|
|
30899
31502
|
hash.update("\0");
|
|
30900
|
-
hash.update(await
|
|
31503
|
+
hash.update(await readFile37(path));
|
|
30901
31504
|
hash.update("\0");
|
|
30902
31505
|
}
|
|
30903
31506
|
return hash.digest("hex");
|
|
@@ -30933,6 +31536,7 @@ var DesktopRuntimeController = class {
|
|
|
30933
31536
|
#busy = false;
|
|
30934
31537
|
#tasks;
|
|
30935
31538
|
#disposeJobSubscription;
|
|
31539
|
+
#memoryRestartPromise;
|
|
30936
31540
|
#d2cMocks = /* @__PURE__ */ new Map();
|
|
30937
31541
|
#d2cPreviews = /* @__PURE__ */ new Map();
|
|
30938
31542
|
#d2cProductPreviews = /* @__PURE__ */ new Map();
|
|
@@ -31059,6 +31663,7 @@ var DesktopRuntimeController = class {
|
|
|
31059
31663
|
this.#captureSessionOutput(event);
|
|
31060
31664
|
this.#emit({ type: "session-output", sessionId: outputSessionId, event });
|
|
31061
31665
|
},
|
|
31666
|
+
onMemoryRestartRequested: (sessionId) => this.#scheduleMemoryRestart(sessionId),
|
|
31062
31667
|
onApprovalChange: () => {
|
|
31063
31668
|
if (this.#runtime !== void 0) this.#publishSnapshot();
|
|
31064
31669
|
},
|
|
@@ -31443,8 +32048,8 @@ var DesktopRuntimeController = class {
|
|
|
31443
32048
|
if (workflow === void 0 || !reviewProgress(workflow).complete || workflow.stage === "visual-review") {
|
|
31444
32049
|
throw new Error("Complete visual review before importing OpenAPI");
|
|
31445
32050
|
}
|
|
31446
|
-
const moduleManifest =
|
|
31447
|
-
const moduleManifestInfo = await
|
|
32051
|
+
const moduleManifest = join48(d2cOutputDirectory(workspace, task), "d2c.modules.json");
|
|
32052
|
+
const moduleManifestInfo = await stat17(moduleManifest).catch(() => void 0);
|
|
31448
32053
|
if (moduleManifestInfo === void 0 || !moduleManifestInfo.isFile()) {
|
|
31449
32054
|
throw new Error("D2C implementation must write d2c.modules.json before API mapping; ask the agent to split the project into modules first");
|
|
31450
32055
|
}
|
|
@@ -31456,8 +32061,8 @@ var DesktopRuntimeController = class {
|
|
|
31456
32061
|
await mkdir27(dir, { recursive: true });
|
|
31457
32062
|
const hash = createHash27("sha256").update(raw).digest("hex");
|
|
31458
32063
|
await Promise.all([
|
|
31459
|
-
writeFile17(
|
|
31460
|
-
writeFile17(
|
|
32064
|
+
writeFile17(join48(dir, "swagger.json"), raw),
|
|
32065
|
+
writeFile17(join48(dir, "openapi.normalized.json"), `${JSON.stringify(document, null, 2)}
|
|
31461
32066
|
`)
|
|
31462
32067
|
]);
|
|
31463
32068
|
const next = {
|
|
@@ -31481,9 +32086,9 @@ var DesktopRuntimeController = class {
|
|
|
31481
32086
|
const workspace = this.#workspace;
|
|
31482
32087
|
if (workspace === void 0) throw new Error("Open a project before importing OpenAPI");
|
|
31483
32088
|
await this.#assertProductPrdLocked(workspace, task);
|
|
31484
|
-
const info = await
|
|
32089
|
+
const info = await stat17(sourcePath);
|
|
31485
32090
|
if (!info.isFile() || info.size > 8 * 1024 * 1024) throw new Error("OpenAPI JSON must be a file no larger than 8 MiB");
|
|
31486
|
-
return this.#storeD2cOpenApi(workspace, task, basename10(sourcePath), await
|
|
32091
|
+
return this.#storeD2cOpenApi(workspace, task, basename10(sourcePath), await readFile37(sourcePath, "utf8"), false);
|
|
31487
32092
|
}
|
|
31488
32093
|
async getD2cIntegration(task) {
|
|
31489
32094
|
const workspace = this.#workspace;
|
|
@@ -31492,8 +32097,8 @@ var DesktopRuntimeController = class {
|
|
|
31492
32097
|
let workflow = await readWorkflow(workspace, task);
|
|
31493
32098
|
if (workflow?.openapi === void 0 && workflow !== void 0 && reviewProgress(workflow).complete && await readD2cProductPlanView(workspace, task) !== void 0) {
|
|
31494
32099
|
const modules = await readD2cModules(workspace, task);
|
|
31495
|
-
const productOpenApiPath =
|
|
31496
|
-
const authored = await
|
|
32100
|
+
const productOpenApiPath = join48(taskDir(workspace, task), "product", "openapi.json");
|
|
32101
|
+
const authored = await readFile37(productOpenApiPath, "utf8").catch(() => void 0);
|
|
31497
32102
|
const generated = authored === void 0;
|
|
31498
32103
|
const prepared = await this.#storeD2cOpenApi(
|
|
31499
32104
|
workspace,
|
|
@@ -31505,7 +32110,7 @@ var DesktopRuntimeController = class {
|
|
|
31505
32110
|
return prepared;
|
|
31506
32111
|
}
|
|
31507
32112
|
if (workflow?.openapi === void 0 || workflow.mappings === void 0) return void 0;
|
|
31508
|
-
const document = JSON.parse(await
|
|
32113
|
+
const document = JSON.parse(await readFile37(join48(integrationDirectory(workspace, task), "openapi.normalized.json"), "utf8"));
|
|
31509
32114
|
return { workflow, document, mappings: workflow.mappings };
|
|
31510
32115
|
}
|
|
31511
32116
|
async confirmD2cMapping(task, moduleId, operationKey) {
|
|
@@ -31572,7 +32177,7 @@ var DesktopRuntimeController = class {
|
|
|
31572
32177
|
].join("\n");
|
|
31573
32178
|
const delivery = await readDeliveryRun(workspace, task);
|
|
31574
32179
|
if (delivery?.nodes.api.status === "running") {
|
|
31575
|
-
const apiArtifacts = await Promise.all(generated.files.map(async (file) => artifactRef(file, await
|
|
32180
|
+
const apiArtifacts = await Promise.all(generated.files.map(async (file) => artifactRef(file, await readFile37(join48(project, file)))));
|
|
31576
32181
|
const completed = await updateDeliveryRun(
|
|
31577
32182
|
workspace,
|
|
31578
32183
|
task,
|
|
@@ -31599,8 +32204,8 @@ var DesktopRuntimeController = class {
|
|
|
31599
32204
|
try {
|
|
31600
32205
|
const fingerprint2 = await d2cBackendSourceFingerprint(project);
|
|
31601
32206
|
if (fingerprint2 !== void 0) this.#d2cMockSourceFingerprints.set(task, fingerprint2);
|
|
31602
|
-
const environmentPath =
|
|
31603
|
-
const existingEnvironment = await
|
|
32207
|
+
const environmentPath = join48(project, ".env.local");
|
|
32208
|
+
const existingEnvironment = await readFile37(environmentPath, "utf8").catch(() => "");
|
|
31604
32209
|
const environment = `${existingEnvironment.split(/\r?\n/).filter((line) => !/^\s*VITE_API_BASE_URL\s*=/.test(line)).join("\n").trim()}
|
|
31605
32210
|
VITE_API_BASE_URL=${running.url}
|
|
31606
32211
|
`.replace(/^\n/, "");
|
|
@@ -31680,8 +32285,8 @@ VITE_API_BASE_URL=${running.url}
|
|
|
31680
32285
|
const readyPreview = this.#d2cPreviews.get(task);
|
|
31681
32286
|
const readyMock = this.#d2cMocks.get(task);
|
|
31682
32287
|
if (readyPreview === void 0 || readyMock === void 0) throw new Error("D2C preview or backend service is unavailable after synchronization");
|
|
31683
|
-
const manifestPath =
|
|
31684
|
-
const manifest = parseInteractionManifest(await
|
|
32288
|
+
const manifestPath = join48(taskDir(workspace, task), "design", "interaction-manifest.json");
|
|
32289
|
+
const manifest = parseInteractionManifest(await readFile37(manifestPath, "utf8"));
|
|
31685
32290
|
const baseline = await this.#verifyAcceptanceBaseline(workspace, task);
|
|
31686
32291
|
const coverage = baseline === void 0 ? void 0 : verifyRequirementCoverage(baseline.approvedPrd.criteria, manifest);
|
|
31687
32292
|
let executableManifest = manifest;
|
|
@@ -31692,7 +32297,7 @@ VITE_API_BASE_URL=${running.url}
|
|
|
31692
32297
|
await mkdir27(planDir, { recursive: true });
|
|
31693
32298
|
try {
|
|
31694
32299
|
const observations = await this.#observeD2cPages(manifest, readyPreview.url);
|
|
31695
|
-
const prd = await
|
|
32300
|
+
const prd = await readFile37(join48(taskDir(workspace, task), "product", "prd.md"), "utf8").catch(() => void 0);
|
|
31696
32301
|
const apiContext = JSON.stringify({ openapi: workflow.openapi, mappings: workflow.mappings }, null, 2);
|
|
31697
32302
|
const plan = await this.#d2cJudge.planInteractions({ task, seed: manifest, observations, ...prd === void 0 ? {} : { prd }, apiContext });
|
|
31698
32303
|
executableManifest = mergeInteractionManifests(manifest, plan.manifest);
|
|
@@ -31703,9 +32308,9 @@ VITE_API_BASE_URL=${running.url}
|
|
|
31703
32308
|
plannedScenarios: executableManifest.pages.reduce((total, page) => total + page.scenarios.length, 0),
|
|
31704
32309
|
pageAnalyses: plan.pageAnalyses
|
|
31705
32310
|
};
|
|
31706
|
-
await writeFile17(
|
|
32311
|
+
await writeFile17(join48(planDir, "autonomous-interaction-plan.json"), `${JSON.stringify({ ...plan, manifest: executableManifest }, null, 2)}
|
|
31707
32312
|
`, { encoding: "utf8", mode: 384 });
|
|
31708
|
-
await rm15(
|
|
32313
|
+
await rm15(join48(planDir, "autonomous-interaction-diagnostic.json"), { force: true });
|
|
31709
32314
|
} catch (cause) {
|
|
31710
32315
|
const detail = message(cause);
|
|
31711
32316
|
review = {
|
|
@@ -31713,8 +32318,8 @@ VITE_API_BASE_URL=${running.url}
|
|
|
31713
32318
|
plannedScenarios: manifest.pages.reduce((total, page) => total + page.scenarios.length, 0),
|
|
31714
32319
|
warning: `\u591A\u6A21\u6001\u81EA\u4E3B\u89C4\u5212\u5931\u8D25\uFF0C\u5DF2\u7EE7\u7EED\u6267\u884C\u5DF2\u786E\u8BA4\u7684\u8BBE\u8BA1\u5951\u7EA6\uFF1A${detail}`
|
|
31715
32320
|
};
|
|
31716
|
-
await rm15(
|
|
31717
|
-
await writeFile17(
|
|
32321
|
+
await rm15(join48(planDir, "autonomous-interaction-plan.json"), { force: true });
|
|
32322
|
+
await writeFile17(join48(planDir, "autonomous-interaction-diagnostic.json"), `${JSON.stringify({
|
|
31718
32323
|
schema: 1,
|
|
31719
32324
|
stage: "planning",
|
|
31720
32325
|
occurredAt: (/* @__PURE__ */ new Date()).toISOString(),
|
|
@@ -31728,13 +32333,13 @@ VITE_API_BASE_URL=${running.url}
|
|
|
31728
32333
|
let result = { ...rawResult, evidenceMode: review.mode };
|
|
31729
32334
|
if (coverage !== void 0) {
|
|
31730
32335
|
const evidence = createAcceptanceEvidence(coverage, result);
|
|
31731
|
-
await writeFile17(
|
|
32336
|
+
await writeFile17(join48(taskDir(workspace, task), "acceptance-evidence.json"), `${JSON.stringify({ ...evidence, baseline }, null, 2)}
|
|
31732
32337
|
`, { encoding: "utf8", mode: 384 });
|
|
31733
32338
|
if (!evidence.passed) result = { ...result, passed: false, failures: Math.max(1, result.failures) };
|
|
31734
32339
|
}
|
|
31735
32340
|
const dir = integrationDirectory(workspace, task);
|
|
31736
32341
|
await mkdir27(dir, { recursive: true });
|
|
31737
|
-
await writeFile17(
|
|
32342
|
+
await writeFile17(join48(dir, "interaction-results.json"), `${JSON.stringify(result, null, 2)}
|
|
31738
32343
|
`);
|
|
31739
32344
|
const next = await updateWorkflow(workspace, task, workflow.revision, (current) => applyInteractionRun(current, result));
|
|
31740
32345
|
return { workflow: next, result, review };
|
|
@@ -31747,7 +32352,7 @@ VITE_API_BASE_URL=${running.url}
|
|
|
31747
32352
|
if (mock === void 0 || !mock.exited() || result.failures === 0) return result;
|
|
31748
32353
|
const output = mock.output().trim();
|
|
31749
32354
|
try {
|
|
31750
|
-
const dir =
|
|
32355
|
+
const dir = join48(d2cOutputDirectory(workspace, task), "mock-server.log");
|
|
31751
32356
|
await writeFile17(dir, `${output}
|
|
31752
32357
|
`);
|
|
31753
32358
|
} catch {
|
|
@@ -31842,7 +32447,7 @@ VITE_API_BASE_URL=${running.url}
|
|
|
31842
32447
|
if (current === void 0) throw new Error("D2C product plan is unavailable");
|
|
31843
32448
|
if (current.plan.approvedPrd !== void 0) throw new Error("The approved PRD is immutable and cannot be edited");
|
|
31844
32449
|
if (current.plan.phase !== "prd-review") throw new Error("PRD sections can only be edited during PRD review");
|
|
31845
|
-
const path =
|
|
32450
|
+
const path = join48(taskDir(workspace, task), "product", "prd.md");
|
|
31846
32451
|
const updated = await updatePrdSectionFile(path, { sectionId, body, expectedHash });
|
|
31847
32452
|
const now = (/* @__PURE__ */ new Date()).toISOString();
|
|
31848
32453
|
await writeD2cProductPlan(workspace, {
|
|
@@ -31862,33 +32467,33 @@ VITE_API_BASE_URL=${running.url}
|
|
|
31862
32467
|
async #assertProductPrdLocked(workspace, task) {
|
|
31863
32468
|
const current = await readD2cProductPlanView(workspace, task);
|
|
31864
32469
|
if (current?.plan.approvedPrd === void 0) return;
|
|
31865
|
-
await assertApprovedPrd(
|
|
32470
|
+
await assertApprovedPrd(join48(taskDir(workspace, task), "product", "prd.md"), current.plan.approvedPrd);
|
|
31866
32471
|
}
|
|
31867
32472
|
async #acceptancePaths(workspace, task) {
|
|
31868
|
-
const product =
|
|
31869
|
-
const openapi =
|
|
32473
|
+
const product = join48(taskDir(workspace, task), "product");
|
|
32474
|
+
const openapi = join48(product, "openapi.json");
|
|
31870
32475
|
return {
|
|
31871
|
-
prd:
|
|
31872
|
-
prototype:
|
|
31873
|
-
interaction:
|
|
31874
|
-
...await
|
|
32476
|
+
prd: join48(product, "prd.md"),
|
|
32477
|
+
prototype: join48(product, "prototype", "index.html"),
|
|
32478
|
+
interaction: join48(product, "prototype", "interaction-manifest.json"),
|
|
32479
|
+
...await stat17(openapi).catch(() => void 0) === void 0 ? {} : { openapi }
|
|
31875
32480
|
};
|
|
31876
32481
|
}
|
|
31877
32482
|
async #verifyAcceptanceBaseline(workspace, task) {
|
|
31878
32483
|
const product = await readD2cProductPlanView(workspace, task);
|
|
31879
32484
|
if (product?.plan.approvedPrd === void 0) return void 0;
|
|
31880
32485
|
await this.#assertProductPrdLocked(workspace, task);
|
|
31881
|
-
const baseline = JSON.parse(await
|
|
32486
|
+
const baseline = JSON.parse(await readFile37(join48(taskDir(workspace, task), "acceptance-baseline.json"), "utf8"));
|
|
31882
32487
|
await verifyAcceptanceBaseline(await this.#acceptancePaths(workspace, task), baseline);
|
|
31883
|
-
const design =
|
|
32488
|
+
const design = join48(taskDir(workspace, task), "design");
|
|
31884
32489
|
await Promise.all([
|
|
31885
|
-
verifyAcceptanceArtifact(
|
|
31886
|
-
verifyAcceptanceArtifact(
|
|
32490
|
+
verifyAcceptanceArtifact(join48(design, "index.html"), baseline.artifacts.prototype, "runtime prototype"),
|
|
32491
|
+
verifyAcceptanceArtifact(join48(design, "interaction-manifest.json"), baseline.artifacts.interaction, "runtime interaction contract")
|
|
31887
32492
|
]);
|
|
31888
32493
|
const workflow = await readWorkflow(workspace, task);
|
|
31889
32494
|
if (workflow?.openapi !== void 0) {
|
|
31890
32495
|
await verifyAcceptanceArtifact(
|
|
31891
|
-
|
|
32496
|
+
join48(taskDir(workspace, task), "integration", "swagger.json"),
|
|
31892
32497
|
{ hash: workflow.openapi.hash },
|
|
31893
32498
|
"runtime OpenAPI contract"
|
|
31894
32499
|
);
|
|
@@ -31909,14 +32514,14 @@ VITE_API_BASE_URL=${running.url}
|
|
|
31909
32514
|
throw new Error("D2C PRD is empty and cannot be accepted");
|
|
31910
32515
|
}
|
|
31911
32516
|
if (stage === "design" && accepted) {
|
|
31912
|
-
const manifest =
|
|
31913
|
-
const normalizedManifest = parseInteractionManifest(await
|
|
32517
|
+
const manifest = join48(d2cProductPrototypeDirectory(workspace, task), "interaction-manifest.json");
|
|
32518
|
+
const normalizedManifest = parseInteractionManifest(await readFile37(manifest, "utf8"));
|
|
31914
32519
|
if (current.plan.approvedPrd === void 0) throw new Error("Approve the PRD before confirming the design");
|
|
31915
32520
|
verifyRequirementCoverage(current.plan.approvedPrd.criteria, normalizedManifest);
|
|
31916
32521
|
await writeFile17(manifest, `${JSON.stringify(normalizedManifest, null, 2)}
|
|
31917
32522
|
`, { encoding: "utf8", mode: 384 });
|
|
31918
32523
|
const baseline = await captureAcceptanceBaseline(await this.#acceptancePaths(workspace, task), current.plan.approvedPrd);
|
|
31919
|
-
await writeFile17(
|
|
32524
|
+
await writeFile17(join48(taskDir(workspace, task), "acceptance-baseline.json"), `${JSON.stringify(baseline, null, 2)}
|
|
31920
32525
|
`, { encoding: "utf8", mode: 384 });
|
|
31921
32526
|
}
|
|
31922
32527
|
const plan = await writeD2cProductPlan(
|
|
@@ -31950,8 +32555,8 @@ VITE_API_BASE_URL=${running.url}
|
|
|
31950
32555
|
const imported = await this.importD2cDesign(task, d2cProductPrototypeDirectory(workspace, task));
|
|
31951
32556
|
const delivery = await readDeliveryRun(workspace, task);
|
|
31952
32557
|
if (delivery !== void 0) {
|
|
31953
|
-
const prototype = await
|
|
31954
|
-
const interaction = await
|
|
32558
|
+
const prototype = await readFile37(join48(d2cProductPrototypeDirectory(workspace, task), "index.html"), "utf8");
|
|
32559
|
+
const interaction = await readFile37(join48(d2cProductPrototypeDirectory(workspace, task), "interaction-manifest.json"), "utf8");
|
|
31955
32560
|
const completed = await updateDeliveryRun(workspace, task, delivery.revision, (run) => completeDeliveryNode(run, "design", [
|
|
31956
32561
|
artifactRef("product/prototype/index.html", prototype),
|
|
31957
32562
|
artifactRef("product/prototype/interaction-manifest.json", interaction)
|
|
@@ -32035,10 +32640,10 @@ VITE_API_BASE_URL=${running.url}
|
|
|
32035
32640
|
const next = await updateWorkflow(workspace, task, workflow.revision, (current) => applyQualityJudgment(current, judgment));
|
|
32036
32641
|
const qualityContent = `${JSON.stringify(judgment, null, 2)}
|
|
32037
32642
|
`;
|
|
32038
|
-
await writeFile17(
|
|
32643
|
+
await writeFile17(join48(taskDir(workspace, task), "quality-judge.json"), qualityContent, { encoding: "utf8", mode: 384 });
|
|
32039
32644
|
const delivery = await readDeliveryRun(workspace, task);
|
|
32040
32645
|
if (next.stage === "completed" && delivery?.nodes.acceptance.status === "running") {
|
|
32041
|
-
const interactionContent = await
|
|
32646
|
+
const interactionContent = await readFile37(join48(integrationDirectory(workspace, task), "interaction-results.json"), "utf8");
|
|
32042
32647
|
const accepted = await updateDeliveryRun(workspace, task, delivery.revision, (run) => completeDeliveryNode(run, "acceptance", [
|
|
32043
32648
|
artifactRef("integration/interaction-results.json", interactionContent),
|
|
32044
32649
|
artifactRef("quality-judge.json", qualityContent)
|
|
@@ -32120,6 +32725,39 @@ VITE_API_BASE_URL=${running.url}
|
|
|
32120
32725
|
await runtime.session.close();
|
|
32121
32726
|
await runtime.dispose();
|
|
32122
32727
|
}
|
|
32728
|
+
/**
|
|
32729
|
+
* Electron owns the process, so a heap rotation rebuilds only the production
|
|
32730
|
+
* runtime. Disposing every holder and forcing GC gives the resumed /loop or
|
|
32731
|
+
* /goal a clean live set without terminating the desktop application.
|
|
32732
|
+
*/
|
|
32733
|
+
#scheduleMemoryRestart(sessionId) {
|
|
32734
|
+
if (this.#memoryRestartPromise !== void 0) return;
|
|
32735
|
+
const previous = this.#runtime;
|
|
32736
|
+
this.#memoryRestartPromise = (async () => {
|
|
32737
|
+
await previous?.session.whenIdle?.().catch(() => void 0);
|
|
32738
|
+
if (previous === void 0 || this.#runtime !== previous) return;
|
|
32739
|
+
await this.#disposeRuntime();
|
|
32740
|
+
clearMemoryRotation();
|
|
32741
|
+
const gc = globalThis.gc;
|
|
32742
|
+
if (typeof gc === "function") {
|
|
32743
|
+
gc();
|
|
32744
|
+
gc();
|
|
32745
|
+
}
|
|
32746
|
+
await this.startSession(sessionId);
|
|
32747
|
+
const resumed = this.#runtime;
|
|
32748
|
+
if (resumed !== void 0) {
|
|
32749
|
+
this.#busy = true;
|
|
32750
|
+
this.#publishSnapshot();
|
|
32751
|
+
await resumed.session.whenIdle?.();
|
|
32752
|
+
}
|
|
32753
|
+
})().catch((error) => {
|
|
32754
|
+
this.#emit({ type: "runtime-error", sessionId, message: `Memory restart failed: ${message(error)}` });
|
|
32755
|
+
}).finally(() => {
|
|
32756
|
+
this.#memoryRestartPromise = void 0;
|
|
32757
|
+
this.#busy = false;
|
|
32758
|
+
this.#publishSnapshot();
|
|
32759
|
+
});
|
|
32760
|
+
}
|
|
32123
32761
|
async #stopAllD2cMocks() {
|
|
32124
32762
|
const running = [...this.#d2cMocks.values()];
|
|
32125
32763
|
this.#d2cMocks.clear();
|
|
@@ -32237,7 +32875,7 @@ async function loginShellEnvironment(environment, platform = process.platform, d
|
|
|
32237
32875
|
}
|
|
32238
32876
|
|
|
32239
32877
|
// src/desktop/git-manager.ts
|
|
32240
|
-
import { readFile as
|
|
32878
|
+
import { readFile as readFile38, rm as rm16 } from "fs/promises";
|
|
32241
32879
|
import { isAbsolute as isAbsolute21, relative as relative28, resolve as resolve40 } from "path";
|
|
32242
32880
|
function failure(action, stderr, stdout) {
|
|
32243
32881
|
return new Error(`${action}\u5931\u8D25\uFF1A${(stderr.trim() || stdout.trim() || "\u672A\u77E5 Git \u9519\u8BEF").split("\n")[0]}`);
|
|
@@ -32269,7 +32907,7 @@ async function desktopGitDiff(workspace, path, staged = false) {
|
|
|
32269
32907
|
if (file === void 0) throw new Error("\u8BE5\u6587\u4EF6\u5DF2\u4E0D\u5728\u53D8\u66F4\u5217\u8868\u4E2D");
|
|
32270
32908
|
if (file.untracked) {
|
|
32271
32909
|
const target = safeWorkspacePath(workspace, path);
|
|
32272
|
-
const value = await
|
|
32910
|
+
const value = await readFile38(target, "utf8");
|
|
32273
32911
|
return value.length > 12e4 ? `${value.slice(0, 12e4)}
|
|
32274
32912
|
\u2026[\u5185\u5BB9\u5DF2\u622A\u65AD]` : value;
|
|
32275
32913
|
}
|
|
@@ -32331,13 +32969,13 @@ async function desktopLastTurnDiff(workspace, sessionId, paths = []) {
|
|
|
32331
32969
|
return path.replaceAll("\\", "/");
|
|
32332
32970
|
}));
|
|
32333
32971
|
try {
|
|
32334
|
-
const tree = JSON.parse(await
|
|
32972
|
+
const tree = JSON.parse(await readFile38(resolve40(workspace, ".flavor", "session-trees", sessionId, "tree.json"), "utf8"));
|
|
32335
32973
|
const leaf = tree.nodes?.find((node) => node.id === tree.leafId);
|
|
32336
32974
|
const parent = leaf?.parentId == null ? void 0 : tree.nodes?.find((node) => node.id === leaf.parentId);
|
|
32337
32975
|
if (leaf === void 0 || parent === void 0) return desktopGitReviewDiff(workspace, { scope: "last-turn", paths });
|
|
32338
32976
|
const load = async (id) => {
|
|
32339
32977
|
if (!/^checkpoint-[A-Za-z0-9-]+$/.test(id)) throw new Error("Invalid checkpoint id");
|
|
32340
|
-
return JSON.parse(await
|
|
32978
|
+
return JSON.parse(await readFile38(resolve40(workspace, ".flavor", "checkpoints", "manifests", `${id}.json`), "utf8"));
|
|
32341
32979
|
};
|
|
32342
32980
|
const [before, after] = await Promise.all([load(parent.checkpointId), load(leaf.checkpointId)]);
|
|
32343
32981
|
const oldFiles = new Map(before.files.map((file) => [file.path, file.digest]));
|
|
@@ -32346,7 +32984,7 @@ async function desktopLastTurnDiff(workspace, sessionId, paths = []) {
|
|
|
32346
32984
|
const object = async (digest) => {
|
|
32347
32985
|
if (digest === void 0) return "";
|
|
32348
32986
|
if (!/^[a-f0-9]{64}$/.test(digest)) throw new Error("Invalid checkpoint object");
|
|
32349
|
-
const value = await
|
|
32987
|
+
const value = await readFile38(resolve40(workspace, ".flavor", "checkpoints", "objects", digest));
|
|
32350
32988
|
return value.includes(0) ? "[binary content]" : value.toString("utf8");
|
|
32351
32989
|
};
|
|
32352
32990
|
const chunks = await Promise.all(changed.map(async (path) => wholeFileDiff(path, await object(oldFiles.get(path)), await object(newFiles.get(path)))));
|
|
@@ -32385,8 +33023,8 @@ function safeGitTarget(value) {
|
|
|
32385
33023
|
|
|
32386
33024
|
// src/desktop/worktree-manager.ts
|
|
32387
33025
|
import { randomUUID as randomUUID28 } from "crypto";
|
|
32388
|
-
import { mkdir as mkdir28, readFile as
|
|
32389
|
-
import { basename as basename11, isAbsolute as isAbsolute22, join as
|
|
33026
|
+
import { mkdir as mkdir28, readFile as readFile39, readdir as readdir14, rm as rm17, stat as stat18, writeFile as writeFile18 } from "fs/promises";
|
|
33027
|
+
import { basename as basename11, isAbsolute as isAbsolute22, join as join49, relative as relative29, resolve as resolve41, sep as sep29 } from "path";
|
|
32390
33028
|
var DesktopWorktreeManager = class {
|
|
32391
33029
|
#repository;
|
|
32392
33030
|
#storage;
|
|
@@ -32432,8 +33070,8 @@ var DesktopWorktreeManager = class {
|
|
|
32432
33070
|
}
|
|
32433
33071
|
const values = await Promise.all(names.filter((name) => name.endsWith(".json")).map(async (name) => {
|
|
32434
33072
|
try {
|
|
32435
|
-
const stored = validateStored(JSON.parse(await
|
|
32436
|
-
if ((await
|
|
33073
|
+
const stored = validateStored(JSON.parse(await readFile39(join49(this.#storage, name), "utf8")), this.#repository, this.#storage);
|
|
33074
|
+
if ((await stat18(stored.path).catch(() => void 0))?.isDirectory() !== true) return void 0;
|
|
32437
33075
|
const status = await git2(stored.path, ["status", "--porcelain"]);
|
|
32438
33076
|
const merged = await git2(this.#repository, ["merge-base", "--is-ancestor", stored.branch, "HEAD"]);
|
|
32439
33077
|
const { version: _version, ...item } = stored;
|
|
@@ -32466,7 +33104,7 @@ var DesktopWorktreeManager = class {
|
|
|
32466
33104
|
}
|
|
32467
33105
|
async #load(id) {
|
|
32468
33106
|
const path = this.#metadataPath(id);
|
|
32469
|
-
return validateStored(JSON.parse(await
|
|
33107
|
+
return validateStored(JSON.parse(await readFile39(path, "utf8")), this.#repository, this.#storage);
|
|
32470
33108
|
}
|
|
32471
33109
|
#metadataPath(id) {
|
|
32472
33110
|
if (!/^worktree-[a-z0-9._-]{1,80}$/.test(id)) throw new Error("Invalid worktree id");
|
|
@@ -32494,9 +33132,9 @@ function validateStored(value, repository, storage) {
|
|
|
32494
33132
|
}
|
|
32495
33133
|
|
|
32496
33134
|
// src/desktop/workbench-service.ts
|
|
32497
|
-
import { readFile as
|
|
33135
|
+
import { readFile as readFile40, readdir as readdir15, stat as stat19 } from "fs/promises";
|
|
32498
33136
|
import { homedir as homedir6 } from "os";
|
|
32499
|
-
import { join as
|
|
33137
|
+
import { join as join50, resolve as resolve42 } from "path";
|
|
32500
33138
|
var MAX_TEXT = 128e3;
|
|
32501
33139
|
var MAX_ITEMS = 200;
|
|
32502
33140
|
var DesktopWorkbenchService = class {
|
|
@@ -32529,11 +33167,11 @@ var DesktopWorkbenchService = class {
|
|
|
32529
33167
|
return url.toString().replace(/\/$/, input.trim().endsWith("/") ? "/" : "");
|
|
32530
33168
|
}
|
|
32531
33169
|
async #goals() {
|
|
32532
|
-
const root =
|
|
33170
|
+
const root = join50(this.#workspace, ".flavor", "goals");
|
|
32533
33171
|
const names = await readdir15(root).catch((error) => error.code === "ENOENT" ? [] : Promise.reject(error));
|
|
32534
33172
|
const values = await Promise.all(names.filter((name) => /^goal-[A-Za-z0-9._-]+\.json$/.test(name)).slice(0, MAX_ITEMS).map(async (name) => {
|
|
32535
33173
|
try {
|
|
32536
|
-
return GoalStateSchema.parse(JSON.parse(await boundedRead2(
|
|
33174
|
+
return GoalStateSchema.parse(JSON.parse(await boundedRead2(join50(root, name))));
|
|
32537
33175
|
} catch {
|
|
32538
33176
|
return void 0;
|
|
32539
33177
|
}
|
|
@@ -32543,7 +33181,7 @@ var DesktopWorkbenchService = class {
|
|
|
32543
33181
|
async #instructions() {
|
|
32544
33182
|
const names = ["FLAVOR.md", "AGENTS.md", "CLAUDE.md", "AGENTS.local.md", "CLAUDE.local.md"];
|
|
32545
33183
|
const items = await Promise.all(names.map(async (name) => {
|
|
32546
|
-
const path =
|
|
33184
|
+
const path = join50(this.#workspace, name);
|
|
32547
33185
|
try {
|
|
32548
33186
|
return { name, path, content: redactErrorText(await boundedRead2(path)) };
|
|
32549
33187
|
} catch (error) {
|
|
@@ -32556,9 +33194,9 @@ var DesktopWorkbenchService = class {
|
|
|
32556
33194
|
async #permissionFiles() {
|
|
32557
33195
|
const candidates = [
|
|
32558
33196
|
...process.env.FLAVOR_MANAGED_PERMISSIONS ? [{ tier: "managed", path: resolve42(process.env.FLAVOR_MANAGED_PERMISSIONS), content: "" }] : [],
|
|
32559
|
-
{ tier: "user", path:
|
|
32560
|
-
{ tier: "project", path:
|
|
32561
|
-
{ tier: "local", path:
|
|
33197
|
+
{ tier: "user", path: join50(this.#home, ".flavor-code", "permissions.json"), content: "" },
|
|
33198
|
+
{ tier: "project", path: join50(this.#workspace, ".flavor", "permissions.json"), content: "" },
|
|
33199
|
+
{ tier: "local", path: join50(this.#workspace, ".flavor", "permissions.local.json"), content: "" }
|
|
32562
33200
|
];
|
|
32563
33201
|
const values = await Promise.all(candidates.map(async (item) => {
|
|
32564
33202
|
try {
|
|
@@ -32573,7 +33211,7 @@ var DesktopWorkbenchService = class {
|
|
|
32573
33211
|
async #audit() {
|
|
32574
33212
|
let raw;
|
|
32575
33213
|
try {
|
|
32576
|
-
raw = await boundedRead2(
|
|
33214
|
+
raw = await boundedRead2(join50(this.#workspace, ".flavor", "audit.jsonl"));
|
|
32577
33215
|
} catch (error) {
|
|
32578
33216
|
if (error.code === "ENOENT") return [];
|
|
32579
33217
|
throw error;
|
|
@@ -32588,7 +33226,7 @@ var DesktopWorkbenchService = class {
|
|
|
32588
33226
|
}).reverse();
|
|
32589
33227
|
}
|
|
32590
33228
|
async #context(sessionId) {
|
|
32591
|
-
const usage = await readJsonLines2(
|
|
33229
|
+
const usage = await readJsonLines2(join50(this.#workspace, ".flavor", "usage.jsonl"));
|
|
32592
33230
|
if (sessionId === void 0) return { visibility: [], usage };
|
|
32593
33231
|
try {
|
|
32594
33232
|
const document = await new SessionStore({ workspace: this.#workspace }).load(sessionId);
|
|
@@ -32599,10 +33237,10 @@ var DesktopWorkbenchService = class {
|
|
|
32599
33237
|
}
|
|
32600
33238
|
};
|
|
32601
33239
|
async function boundedRead2(path) {
|
|
32602
|
-
const info = await
|
|
33240
|
+
const info = await stat19(path);
|
|
32603
33241
|
if (!info.isFile()) throw new Error(`Expected a file: ${path}`);
|
|
32604
33242
|
if (info.size > MAX_TEXT) throw new Error(`Workbench file exceeds ${MAX_TEXT} bytes: ${path}`);
|
|
32605
|
-
return
|
|
33243
|
+
return readFile40(path, "utf8");
|
|
32606
33244
|
}
|
|
32607
33245
|
async function readJsonLines2(path) {
|
|
32608
33246
|
try {
|
|
@@ -32635,7 +33273,7 @@ var appMenu;
|
|
|
32635
33273
|
var quitting = false;
|
|
32636
33274
|
function logStartup(step, detail) {
|
|
32637
33275
|
try {
|
|
32638
|
-
const logPath =
|
|
33276
|
+
const logPath = join51(dirname23(process.execPath), "flavor-code-startup.log");
|
|
32639
33277
|
const ts = (/* @__PURE__ */ new Date()).toISOString();
|
|
32640
33278
|
appendFileSync(logPath, `[${ts}] ${step}${detail ? ` | ${detail}` : ""}
|
|
32641
33279
|
`);
|
|
@@ -32660,7 +33298,7 @@ var d2cEmbedded = createEmbeddedD2cAutomation(() => {
|
|
|
32660
33298
|
};
|
|
32661
33299
|
});
|
|
32662
33300
|
var d2cJudgeClient = createD2cJudgeClient();
|
|
32663
|
-
var judgeStore = () => createD2cJudgeConfigStore(
|
|
33301
|
+
var judgeStore = () => createD2cJudgeConfigStore(join51(app.getPath("userData"), "d2c-judge.json"));
|
|
32664
33302
|
function judgeImage(png) {
|
|
32665
33303
|
const image = nativeImage.createFromBuffer(png);
|
|
32666
33304
|
const size = image.getSize();
|
|
@@ -32686,7 +33324,7 @@ function sessionMetaKey(workspace, sessionId) {
|
|
|
32686
33324
|
}
|
|
32687
33325
|
function worktreeManager(workspace) {
|
|
32688
33326
|
const key = createHash28("sha256").update(resolve43(workspace)).digest("hex").slice(0, 20);
|
|
32689
|
-
return new DesktopWorktreeManager({ repository: workspace, storage:
|
|
33327
|
+
return new DesktopWorktreeManager({ repository: workspace, storage: join51(app.getPath("userData"), "task-worktrees", key) });
|
|
32690
33328
|
}
|
|
32691
33329
|
function activeWorkingDirectory() {
|
|
32692
33330
|
if (activeWorkspace === void 0) throw new Error("\u8BF7\u5148\u6253\u5F00\u9879\u76EE");
|
|
@@ -32831,7 +33469,7 @@ function createDesktopController(workspace, owner2) {
|
|
|
32831
33469
|
alias: `desktop-${basename12(workspace ?? runtimeOptions.workspace ?? "flavor")}`.slice(0, 64),
|
|
32832
33470
|
optional: true,
|
|
32833
33471
|
startBroker: (address) => {
|
|
32834
|
-
const entry2 = app.isPackaged ?
|
|
33472
|
+
const entry2 = app.isPackaged ? join51(process.resourcesPath, "app.asar.unpacked", "dist", "desktop", "pals-broker-entry.js") : join51(moduleDirectory, "pals-broker-entry.js");
|
|
32835
33473
|
return spawnPalBrokerProcess({
|
|
32836
33474
|
address,
|
|
32837
33475
|
executable: process.execPath,
|
|
@@ -32853,11 +33491,11 @@ function createDesktopController(workspace, owner2) {
|
|
|
32853
33491
|
var detachedController = createDesktopController();
|
|
32854
33492
|
var controller = detachedController;
|
|
32855
33493
|
function statePath() {
|
|
32856
|
-
return
|
|
33494
|
+
return join51(app.getPath("userData"), "desktop-state.json");
|
|
32857
33495
|
}
|
|
32858
33496
|
async function loadPersistedProjects() {
|
|
32859
33497
|
try {
|
|
32860
|
-
const raw = await
|
|
33498
|
+
const raw = await readFile41(statePath(), "utf8");
|
|
32861
33499
|
if (raw.length > 1e5) return { projects: [] };
|
|
32862
33500
|
const parsed = JSON.parse(raw);
|
|
32863
33501
|
Object.assign(workbench, {
|
|
@@ -32898,7 +33536,7 @@ function validRecord(value) {
|
|
|
32898
33536
|
return typeof value === "object" && value !== null && !Array.isArray(value) ? value : {};
|
|
32899
33537
|
}
|
|
32900
33538
|
async function assertDirectory(path) {
|
|
32901
|
-
if (!(await
|
|
33539
|
+
if (!(await stat20(path)).isDirectory()) throw new Error("The selected project is not a directory");
|
|
32902
33540
|
}
|
|
32903
33541
|
async function openWorkspace(path) {
|
|
32904
33542
|
const workspace = resolve43(path);
|
|
@@ -33029,7 +33667,7 @@ async function chooseAndOpenWorkspace() {
|
|
|
33029
33667
|
var cachedAppIconDataUrl;
|
|
33030
33668
|
function appIconDataUrl() {
|
|
33031
33669
|
if (cachedAppIconDataUrl !== void 0) return cachedAppIconDataUrl;
|
|
33032
|
-
const icon = nativeImage.createFromPath(
|
|
33670
|
+
const icon = nativeImage.createFromPath(join51(app.getAppPath(), "assets", "icon.png"));
|
|
33033
33671
|
if (icon.isEmpty()) return void 0;
|
|
33034
33672
|
cachedAppIconDataUrl = icon.toDataURL();
|
|
33035
33673
|
return cachedAppIconDataUrl;
|
|
@@ -33447,7 +34085,7 @@ function applicationMenu() {
|
|
|
33447
34085
|
]);
|
|
33448
34086
|
}
|
|
33449
34087
|
async function createWindow() {
|
|
33450
|
-
const rendererPath =
|
|
34088
|
+
const rendererPath = join51(app.getAppPath(), "dist", "desktop-renderer", "index.html");
|
|
33451
34089
|
mainWindow = new BrowserWindow2({
|
|
33452
34090
|
title: "Flavor Code",
|
|
33453
34091
|
width: 1280,
|
|
@@ -33458,14 +34096,14 @@ async function createWindow() {
|
|
|
33458
34096
|
show: false,
|
|
33459
34097
|
...desktopWindowChrome(),
|
|
33460
34098
|
webPreferences: {
|
|
33461
|
-
preload:
|
|
34099
|
+
preload: join51(moduleDirectory, "preload.cjs"),
|
|
33462
34100
|
contextIsolation: true,
|
|
33463
34101
|
nodeIntegration: false,
|
|
33464
34102
|
sandbox: false,
|
|
33465
34103
|
webSecurity: true
|
|
33466
34104
|
}
|
|
33467
34105
|
});
|
|
33468
|
-
const iconPath =
|
|
34106
|
+
const iconPath = join51(app.getAppPath(), "assets", "icon.png");
|
|
33469
34107
|
const icon = nativeImage.createFromPath(iconPath);
|
|
33470
34108
|
if (!icon.isEmpty()) {
|
|
33471
34109
|
mainWindow.setIcon(icon);
|