flavor-code 1.4.0-beta.3 → 1.4.0-beta.5
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/agent/loop.d.ts +4 -2
- package/dist/{app-H7RYDMD3.js → app-SXNJDSI2.js} +3 -3
- package/dist/{chunk-NIKCPCJG.js → chunk-J7TLT6LR.js} +1098 -366
- package/dist/{chunk-3BHKW5A7.js → chunk-JZVC4477.js} +8 -2
- package/dist/{chunk-KBHWS7M6.js → chunk-KQMKY7C5.js} +4 -0
- package/dist/chunk-N5MXHJKD.js +107 -0
- package/dist/cli-main.js +20 -13
- package/dist/cli.js +51 -10
- package/dist/config/schema.d.ts +4 -0
- package/dist/context/manager.d.ts +9 -0
- package/dist/desktop/main.js +1442 -625
- package/dist/desktop-renderer/assets/index-nR67Z0XT.js +154 -0
- package/dist/desktop-renderer/assets/{interactive-terminal-j52Ty3QO.js → interactive-terminal-D2J4feTJ.js} +1 -1
- package/dist/desktop-renderer/index.html +1 -1
- package/dist/goal/classifier.d.ts +2 -0
- package/dist/goal/orchestrator.d.ts +23 -2
- package/dist/goal/planner.d.ts +2 -0
- package/dist/goal/store.d.ts +2 -0
- package/dist/goal/types.d.ts +14 -0
- package/dist/harness/journal.d.ts +2 -0
- package/dist/jobs/registry.d.ts +6 -0
- package/dist/{load-SLSL3IWR.js → load-F4AYLCF6.js} +1 -1
- package/dist/loop/orchestrator.d.ts +20 -0
- package/dist/loop/types.d.ts +7 -0
- package/dist/loop/verifier.d.ts +2 -0
- package/dist/production.d.ts +17 -0
- package/dist/sdk/index.js +4 -4
- package/dist/session/tree.d.ts +2 -0
- package/dist/ui/session.d.ts +11 -0
- package/dist/ui/transcript.d.ts +9 -0
- package/dist/utils/abort.d.ts +16 -0
- package/dist/utils/heap.d.ts +58 -0
- package/dist/utils/memory-restart.d.ts +42 -3
- package/dist/utils/rotation-census.d.ts +36 -0
- package/package.json +1 -1
- package/dist/chunk-CIVPKHF4.js +0 -53
- package/dist/desktop-renderer/assets/index-CYd5JkTR.js +0 -154
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;
|
|
@@ -5612,10 +5628,6 @@ var ContextManager = class _ContextManager {
|
|
|
5612
5628
|
}
|
|
5613
5629
|
return microcompact.changed;
|
|
5614
5630
|
}
|
|
5615
|
-
if (this.#consecutiveAutoCompactFailures >= 3) {
|
|
5616
|
-
rollbackMicrocompact();
|
|
5617
|
-
return false;
|
|
5618
|
-
}
|
|
5619
5631
|
try {
|
|
5620
5632
|
const compacted = await this.#compactConversation(signal);
|
|
5621
5633
|
if (compacted) {
|
|
@@ -6079,10 +6091,10 @@ function within(root, candidate) {
|
|
|
6079
6091
|
|
|
6080
6092
|
// src/context/summarizer.ts
|
|
6081
6093
|
async function summarizeWithModel(options) {
|
|
6082
|
-
const maxAttempts = options.maxPromptTooLongAttempts ?? 3;
|
|
6083
|
-
if (!Number.isInteger(maxAttempts) || maxAttempts <= 0) throw new Error("maxPromptTooLongAttempts must be positive");
|
|
6084
6094
|
options.signal?.throwIfAborted();
|
|
6085
6095
|
let groups = groupMessagesByApiRound(options.messages);
|
|
6096
|
+
const maxAttempts = options.maxPromptTooLongAttempts ?? Math.max(1, Math.ceil(Math.log2(Math.max(1, groups.length))) + 1);
|
|
6097
|
+
if (!Number.isInteger(maxAttempts) || maxAttempts <= 0) throw new Error("maxPromptTooLongAttempts must be positive");
|
|
6086
6098
|
let lastOverflow;
|
|
6087
6099
|
let progress = 20;
|
|
6088
6100
|
reportProgress(options.onProgress, progress);
|
|
@@ -6123,7 +6135,7 @@ async function summarizeWithModel(options) {
|
|
|
6123
6135
|
if (terminalError.code !== "context_overflow") throw terminalError;
|
|
6124
6136
|
lastOverflow = terminalError;
|
|
6125
6137
|
if (attempt >= maxAttempts || groups.length <= 1) throw terminalError;
|
|
6126
|
-
groups = groups.slice(1);
|
|
6138
|
+
groups = groups.slice(Math.max(1, Math.floor(groups.length / 2)));
|
|
6127
6139
|
}
|
|
6128
6140
|
throw lastOverflow ?? new Error("Compact summary failed");
|
|
6129
6141
|
}
|
|
@@ -6140,8 +6152,75 @@ function reportProgress(callback, percentage) {
|
|
|
6140
6152
|
}
|
|
6141
6153
|
}
|
|
6142
6154
|
|
|
6143
|
-
// src/
|
|
6155
|
+
// src/utils/heap.ts
|
|
6144
6156
|
import { getHeapStatistics } from "v8";
|
|
6157
|
+
import { totalmem } from "os";
|
|
6158
|
+
var MEMORY_PRESSURE_HEAP_RATIO = 0.8;
|
|
6159
|
+
var MEMORY_ROTATION_HEAP_RATIO = 0.6;
|
|
6160
|
+
var MEMORY_PRESSURE_RSS_RATIO = 0.75;
|
|
6161
|
+
var MEMORY_ROTATION_RSS_RATIO = 0.6;
|
|
6162
|
+
var ROTATION_PRECHECK_RATIO = 0.45;
|
|
6163
|
+
function readHeap() {
|
|
6164
|
+
const heapLimit = getHeapStatistics().heap_size_limit;
|
|
6165
|
+
const heapUsed = process.memoryUsage().heapUsed;
|
|
6166
|
+
return { heapUsed, heapLimit, ratio: heapLimit > 0 ? heapUsed / heapLimit : 0 };
|
|
6167
|
+
}
|
|
6168
|
+
function forceFullGc() {
|
|
6169
|
+
const gc = globalThis.gc;
|
|
6170
|
+
if (typeof gc !== "function") return false;
|
|
6171
|
+
gc();
|
|
6172
|
+
gc();
|
|
6173
|
+
return true;
|
|
6174
|
+
}
|
|
6175
|
+
function ratioOf(heapLimit, heapUsed) {
|
|
6176
|
+
return heapLimit > 0 ? heapUsed / heapLimit : 0;
|
|
6177
|
+
}
|
|
6178
|
+
function effectiveMemoryLimit() {
|
|
6179
|
+
const physical = totalmem();
|
|
6180
|
+
const constrained = process.constrainedMemory?.() ?? 0;
|
|
6181
|
+
return constrained > 0 ? Math.min(physical, constrained) : physical;
|
|
6182
|
+
}
|
|
6183
|
+
function withRss(reading, gcVerified) {
|
|
6184
|
+
const usage = process.memoryUsage();
|
|
6185
|
+
const memoryLimit = effectiveMemoryLimit();
|
|
6186
|
+
return {
|
|
6187
|
+
...reading,
|
|
6188
|
+
gcVerified,
|
|
6189
|
+
rss: usage.rss,
|
|
6190
|
+
memoryLimit,
|
|
6191
|
+
rssRatio: ratioOf(memoryLimit, usage.rss),
|
|
6192
|
+
external: usage.external,
|
|
6193
|
+
arrayBuffers: usage.arrayBuffers
|
|
6194
|
+
};
|
|
6195
|
+
}
|
|
6196
|
+
function verifiedHeapPressure(threshold) {
|
|
6197
|
+
const raw = readHeap();
|
|
6198
|
+
const before = withRss(raw, false);
|
|
6199
|
+
const rssThreshold = threshold >= MEMORY_PRESSURE_HEAP_RATIO ? MEMORY_PRESSURE_RSS_RATIO : threshold;
|
|
6200
|
+
if ((raw.heapLimit <= 0 || raw.ratio < threshold) && (before.rssRatio ?? 0) < rssThreshold) return void 0;
|
|
6201
|
+
if (!forceFullGc()) return before;
|
|
6202
|
+
const heapUsed = process.memoryUsage().heapUsed;
|
|
6203
|
+
const ratio = ratioOf(raw.heapLimit, heapUsed);
|
|
6204
|
+
const verified = withRss({ heapUsed, heapLimit: raw.heapLimit, ratio, verifiedRatio: ratio }, true);
|
|
6205
|
+
if (ratio < threshold && (verified.rssRatio ?? 0) < rssThreshold) return void 0;
|
|
6206
|
+
return verified;
|
|
6207
|
+
}
|
|
6208
|
+
function heapRotationNeeded() {
|
|
6209
|
+
const raw = readHeap();
|
|
6210
|
+
const before = withRss(raw, false);
|
|
6211
|
+
if ((raw.heapLimit <= 0 || raw.ratio < ROTATION_PRECHECK_RATIO) && (before.rssRatio ?? 0) < ROTATION_PRECHECK_RATIO) return void 0;
|
|
6212
|
+
if (!forceFullGc()) {
|
|
6213
|
+
return raw.ratio >= MEMORY_PRESSURE_HEAP_RATIO || (before.rssRatio ?? 0) >= MEMORY_PRESSURE_RSS_RATIO ? before : void 0;
|
|
6214
|
+
}
|
|
6215
|
+
const heapUsed = process.memoryUsage().heapUsed;
|
|
6216
|
+
const ratio = ratioOf(raw.heapLimit, heapUsed);
|
|
6217
|
+
const verified = withRss({ heapUsed, heapLimit: raw.heapLimit, ratio, verifiedRatio: ratio }, true);
|
|
6218
|
+
if (ratio < MEMORY_ROTATION_HEAP_RATIO && (verified.rssRatio ?? 0) < MEMORY_ROTATION_RSS_RATIO) return void 0;
|
|
6219
|
+
return verified;
|
|
6220
|
+
}
|
|
6221
|
+
function heapSnapshotWarranted(_reading) {
|
|
6222
|
+
return process.env.FLAVOR_HEAP_SNAPSHOT_ON_ROTATE === "1";
|
|
6223
|
+
}
|
|
6145
6224
|
|
|
6146
6225
|
// src/models/structured.ts
|
|
6147
6226
|
import { z as z12 } from "zod";
|
|
@@ -6560,7 +6639,6 @@ function envMaxIterations() {
|
|
|
6560
6639
|
if (!Number.isInteger(parsed) || parsed <= 0) return void 0;
|
|
6561
6640
|
return parsed;
|
|
6562
6641
|
}
|
|
6563
|
-
var MEMORY_PRESSURE_HEAP_RATIO = 0.8;
|
|
6564
6642
|
function memoryPressureError(heapUsed, heapLimit) {
|
|
6565
6643
|
if (heapLimit <= 0 || heapUsed < heapLimit * MEMORY_PRESSURE_HEAP_RATIO) return void 0;
|
|
6566
6644
|
const mb = (bytes) => Math.round(bytes / 1048576);
|
|
@@ -6570,7 +6648,12 @@ function memoryPressureError(heapUsed, heapLimit) {
|
|
|
6570
6648
|
};
|
|
6571
6649
|
}
|
|
6572
6650
|
function currentMemoryPressure() {
|
|
6573
|
-
|
|
6651
|
+
const reading = verifiedHeapPressure(MEMORY_PRESSURE_HEAP_RATIO);
|
|
6652
|
+
if (reading === void 0) return void 0;
|
|
6653
|
+
return memoryPressureError(reading.heapUsed, reading.heapLimit) ?? {
|
|
6654
|
+
code: "memory_pressure",
|
|
6655
|
+
message: `Process RSS reached ${Math.round((reading.rssRatio ?? 0) * 100)}% of the available memory limit; stopping before system OOM`
|
|
6656
|
+
};
|
|
6574
6657
|
}
|
|
6575
6658
|
var AgentLoop = class {
|
|
6576
6659
|
#options;
|
|
@@ -8393,6 +8476,10 @@ var HarnessJournal = class {
|
|
|
8393
8476
|
get records() {
|
|
8394
8477
|
return this.#records.map((record3) => structuredClone(record3));
|
|
8395
8478
|
}
|
|
8479
|
+
/** Retained record count for the rotation census. */
|
|
8480
|
+
get recordCount() {
|
|
8481
|
+
return this.#records.length;
|
|
8482
|
+
}
|
|
8396
8483
|
admitQueue(kind, payload) {
|
|
8397
8484
|
QueueKindSchema.parse(kind);
|
|
8398
8485
|
const id = randomUUID8();
|
|
@@ -8669,19 +8756,38 @@ function jsonSafeRecord(value) {
|
|
|
8669
8756
|
return JSON.parse(json);
|
|
8670
8757
|
}
|
|
8671
8758
|
function hashJson(value) {
|
|
8672
|
-
|
|
8673
|
-
|
|
8674
|
-
|
|
8675
|
-
const { hash: _hash, ...unsigned } = record3;
|
|
8676
|
-
return hashJson(unsigned);
|
|
8759
|
+
const hash = createHash6("sha256");
|
|
8760
|
+
stableHashInto(hash, value);
|
|
8761
|
+
return hash.digest("hex");
|
|
8677
8762
|
}
|
|
8678
|
-
function
|
|
8679
|
-
if (Array.isArray(value))
|
|
8763
|
+
function stableHashInto(hash, value) {
|
|
8764
|
+
if (Array.isArray(value)) {
|
|
8765
|
+
hash.update("[");
|
|
8766
|
+
for (let index = 0; index < value.length; index += 1) {
|
|
8767
|
+
if (index > 0) hash.update(",");
|
|
8768
|
+
stableHashInto(hash, value[index]);
|
|
8769
|
+
}
|
|
8770
|
+
hash.update("]");
|
|
8771
|
+
return;
|
|
8772
|
+
}
|
|
8680
8773
|
if (typeof value === "object" && value !== null) {
|
|
8681
8774
|
const object = value;
|
|
8682
|
-
|
|
8775
|
+
hash.update("{");
|
|
8776
|
+
const keys = Object.keys(object).sort();
|
|
8777
|
+
for (let index = 0; index < keys.length; index += 1) {
|
|
8778
|
+
const key = keys[index];
|
|
8779
|
+
if (index > 0) hash.update(",");
|
|
8780
|
+
hash.update(`${JSON.stringify(key)}:`);
|
|
8781
|
+
stableHashInto(hash, object[key]);
|
|
8782
|
+
}
|
|
8783
|
+
hash.update("}");
|
|
8784
|
+
return;
|
|
8683
8785
|
}
|
|
8684
|
-
|
|
8786
|
+
hash.update(JSON.stringify(value) ?? String(value));
|
|
8787
|
+
}
|
|
8788
|
+
function recordHash(record3) {
|
|
8789
|
+
const { hash: _hash, ...unsigned } = record3;
|
|
8790
|
+
return hashJson(unsigned);
|
|
8685
8791
|
}
|
|
8686
8792
|
function assertSessionId(value) {
|
|
8687
8793
|
if (!/^[A-Za-z0-9][A-Za-z0-9._-]{0,127}$/.test(value)) throw new Error("Invalid session id");
|
|
@@ -11387,6 +11493,81 @@ async function initializeFlavor(cwd) {
|
|
|
11387
11493
|
return { path, created: existing === void 0, content, facts };
|
|
11388
11494
|
}
|
|
11389
11495
|
|
|
11496
|
+
// src/utils/memory-restart.ts
|
|
11497
|
+
import { join as join14 } from "path";
|
|
11498
|
+
var MEMORY_RESTART_EXIT_CODE = 75;
|
|
11499
|
+
var MEMORY_RESTART_MARKER_FILE = "memory-restart.json";
|
|
11500
|
+
var MEMORY_RESTART_WINDOW_MS = 60 * 60 * 1e3;
|
|
11501
|
+
var MEMORY_RESTART_MIN_GAP_MS = 5 * 60 * 1e3;
|
|
11502
|
+
var MEMORY_RESTART_CONTINUATION_TTL_MS = 24 * 60 * 60 * 1e3;
|
|
11503
|
+
var rotationActive = false;
|
|
11504
|
+
function markMemoryRotation() {
|
|
11505
|
+
rotationActive = true;
|
|
11506
|
+
}
|
|
11507
|
+
function memoryRotationActive() {
|
|
11508
|
+
return rotationActive;
|
|
11509
|
+
}
|
|
11510
|
+
function clearMemoryRotation() {
|
|
11511
|
+
rotationActive = false;
|
|
11512
|
+
}
|
|
11513
|
+
function memoryRestartMarkerPath(workspace) {
|
|
11514
|
+
return join14(workspace, ".flavor", "tmp", MEMORY_RESTART_MARKER_FILE);
|
|
11515
|
+
}
|
|
11516
|
+
function nextMemoryRestartMarker(previous, sessionId, now, continuation) {
|
|
11517
|
+
const previousWindowStart = previous?.windowStartedAt ?? previous?.requestedAt;
|
|
11518
|
+
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;
|
|
11519
|
+
return {
|
|
11520
|
+
sessionId,
|
|
11521
|
+
windowStartedAt: reusable ? previousWindowStart : now.toISOString(),
|
|
11522
|
+
requestedAt: now.toISOString(),
|
|
11523
|
+
attempts: reusable ? previous.attempts + 1 : 1,
|
|
11524
|
+
...continuation === void 0 ? {} : { continuation }
|
|
11525
|
+
};
|
|
11526
|
+
}
|
|
11527
|
+
function consumedMemoryRestartMarker(marker) {
|
|
11528
|
+
return {
|
|
11529
|
+
sessionId: marker.sessionId,
|
|
11530
|
+
...marker.windowStartedAt === void 0 ? {} : { windowStartedAt: marker.windowStartedAt },
|
|
11531
|
+
requestedAt: marker.requestedAt,
|
|
11532
|
+
attempts: marker.attempts
|
|
11533
|
+
};
|
|
11534
|
+
}
|
|
11535
|
+
function rotationCooldownActive(marker, sessionId, now) {
|
|
11536
|
+
return marker !== void 0 && marker.sessionId === sessionId && now.getTime() - Date.parse(marker.requestedAt) < MEMORY_RESTART_MIN_GAP_MS;
|
|
11537
|
+
}
|
|
11538
|
+
function pendingContinuation(marker, sessionId, now) {
|
|
11539
|
+
if (marker === void 0 || marker.sessionId !== sessionId) return void 0;
|
|
11540
|
+
if (now.getTime() - Date.parse(marker.requestedAt) > MEMORY_RESTART_CONTINUATION_TTL_MS) return void 0;
|
|
11541
|
+
const continuation = marker.continuation;
|
|
11542
|
+
if (continuation === void 0) return void 0;
|
|
11543
|
+
if (continuation.kind !== "loop" && continuation.kind !== "goal") return void 0;
|
|
11544
|
+
if (typeof continuation.id !== "string" || continuation.id.length === 0) return void 0;
|
|
11545
|
+
return continuation;
|
|
11546
|
+
}
|
|
11547
|
+
function parseMemoryRestartMarker(json) {
|
|
11548
|
+
try {
|
|
11549
|
+
const value = JSON.parse(json);
|
|
11550
|
+
if (typeof value.sessionId !== "string" || typeof value.requestedAt !== "string" || typeof value.attempts !== "number") {
|
|
11551
|
+
return void 0;
|
|
11552
|
+
}
|
|
11553
|
+
const continuation = value.continuation;
|
|
11554
|
+
if (continuation !== void 0 && (typeof continuation !== "object" || continuation === null || continuation.kind !== "loop" && continuation.kind !== "goal" || typeof continuation.id !== "string")) {
|
|
11555
|
+
return void 0;
|
|
11556
|
+
}
|
|
11557
|
+
const windowStartedAt = value.windowStartedAt;
|
|
11558
|
+
if (windowStartedAt !== void 0 && typeof windowStartedAt !== "string") return void 0;
|
|
11559
|
+
return {
|
|
11560
|
+
sessionId: value.sessionId,
|
|
11561
|
+
...windowStartedAt === void 0 ? {} : { windowStartedAt },
|
|
11562
|
+
requestedAt: value.requestedAt,
|
|
11563
|
+
attempts: value.attempts,
|
|
11564
|
+
...continuation === void 0 ? {} : { continuation }
|
|
11565
|
+
};
|
|
11566
|
+
} catch {
|
|
11567
|
+
return void 0;
|
|
11568
|
+
}
|
|
11569
|
+
}
|
|
11570
|
+
|
|
11390
11571
|
// src/skills/builtin-loop.ts
|
|
11391
11572
|
function buildLoopCyclePrompt(input) {
|
|
11392
11573
|
return [
|
|
@@ -11467,6 +11648,14 @@ var LoopStateSchema = z20.object({
|
|
|
11467
11648
|
approvals: z20.array(LoopApprovalSchema)
|
|
11468
11649
|
}).strict(),
|
|
11469
11650
|
cycles: z20.array(LoopCycleEvidenceSchema),
|
|
11651
|
+
/** Completed worker awaiting host verification; avoids replaying tool work after rotation. */
|
|
11652
|
+
pendingCycle: z20.object({
|
|
11653
|
+
cycle: z20.number().int().positive(),
|
|
11654
|
+
startedAt: z20.string().datetime({ offset: true }),
|
|
11655
|
+
inputTokens: z20.number().int().nonnegative(),
|
|
11656
|
+
outputTokens: z20.number().int().nonnegative(),
|
|
11657
|
+
workerText: z20.string().max(16e3)
|
|
11658
|
+
}).strict().nullable().optional(),
|
|
11470
11659
|
terminalReason: z20.string().optional()
|
|
11471
11660
|
}).strict();
|
|
11472
11661
|
var LoopEventSchema = z20.object({
|
|
@@ -11516,7 +11705,7 @@ function extendBudget(input, dimensions, approvedAt) {
|
|
|
11516
11705
|
return LoopStateSchema.parse({
|
|
11517
11706
|
...state,
|
|
11518
11707
|
updatedAt: approvedAt,
|
|
11519
|
-
budget: { ...state.budget, cycleCheckpoint, tokenCheckpoint, approvals }
|
|
11708
|
+
budget: { ...state.budget, cycleCheckpoint, tokenCheckpoint, approvals: approvals.slice(-64) }
|
|
11520
11709
|
});
|
|
11521
11710
|
}
|
|
11522
11711
|
function rejectBudget(input, terminalReason) {
|
|
@@ -11555,15 +11744,45 @@ var LoopOrchestrator = class {
|
|
|
11555
11744
|
tokenCheckpoint: this.#options.config.maxTokens,
|
|
11556
11745
|
approvals: []
|
|
11557
11746
|
},
|
|
11558
|
-
cycles: []
|
|
11747
|
+
cycles: [],
|
|
11748
|
+
pendingCycle: null
|
|
11559
11749
|
});
|
|
11560
11750
|
await this.#options.persistence.create(state);
|
|
11751
|
+
yield* this.#execute(state, request.goal, request.signal);
|
|
11752
|
+
}
|
|
11753
|
+
/**
|
|
11754
|
+
* Continue a loop whose process rotated onto a fresh heap. The persisted
|
|
11755
|
+
* state decides where to pick up; an interrupted cycle simply runs again.
|
|
11756
|
+
*/
|
|
11757
|
+
async *resume(request) {
|
|
11758
|
+
const persistence = this.#options.persistence;
|
|
11759
|
+
if (persistence.load === void 0) throw new Error("Loop persistence does not support resume");
|
|
11760
|
+
let state;
|
|
11761
|
+
try {
|
|
11762
|
+
state = await persistence.load(request.loopId);
|
|
11763
|
+
} catch (error) {
|
|
11764
|
+
yield { type: "loop-terminal", loopId: request.loopId, status: "failed", reason: message(error) };
|
|
11765
|
+
return;
|
|
11766
|
+
}
|
|
11767
|
+
if (state.status !== "running") {
|
|
11768
|
+
const status = state.status;
|
|
11769
|
+
yield { type: "loop-terminal", loopId: state.loopId, status, reason: state.terminalReason ?? "The loop is not running." };
|
|
11770
|
+
return;
|
|
11771
|
+
}
|
|
11772
|
+
yield* this.#execute(state, state.goal, request.signal);
|
|
11773
|
+
}
|
|
11774
|
+
async *#execute(initialState, goal, signal) {
|
|
11775
|
+
const now = this.#options.now ?? (() => (/* @__PURE__ */ new Date()).toISOString());
|
|
11776
|
+
const loopId = initialState.loopId;
|
|
11777
|
+
let state = initialState;
|
|
11561
11778
|
try {
|
|
11779
|
+
await this.#options.onMemoryCheckpoint?.(loopId);
|
|
11780
|
+
if (memoryRotationActive()) return;
|
|
11562
11781
|
const workspaceResolution = await this.#options.prepareWorkspace({
|
|
11563
11782
|
root: this.#options.workspace,
|
|
11564
11783
|
loopId,
|
|
11565
|
-
goal
|
|
11566
|
-
signal
|
|
11784
|
+
goal,
|
|
11785
|
+
signal
|
|
11567
11786
|
});
|
|
11568
11787
|
if (workspaceResolution.kind === "needs_human") {
|
|
11569
11788
|
state = await this.#terminal(state, "needs_human", workspaceResolution.reason, now());
|
|
@@ -11581,67 +11800,90 @@ var LoopOrchestrator = class {
|
|
|
11581
11800
|
cycleCheckpoint: state.budget.cycleCheckpoint,
|
|
11582
11801
|
tokenCheckpoint: state.budget.tokenCheckpoint
|
|
11583
11802
|
};
|
|
11584
|
-
|
|
11803
|
+
const lastCompletedCycle = state.cycles.at(-1);
|
|
11804
|
+
let previousVerification = lastCompletedCycle?.verification ?? (plan.commands.length === 0 ? {
|
|
11585
11805
|
passed: false,
|
|
11586
11806
|
commands: [],
|
|
11587
11807
|
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
|
-
|
|
11808
|
+
} : void 0);
|
|
11809
|
+
let previousFailureSignature = lastCompletedCycle === void 0 ? void 0 : `${lastCompletedCycle.workspaceFingerprint}
|
|
11810
|
+
${lastCompletedCycle.verification.summary}`;
|
|
11811
|
+
let repeatedFailures = previousFailureSignature === void 0 ? 0 : countTrailingFailureSignature(state.cycles, previousFailureSignature);
|
|
11591
11812
|
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
|
-
|
|
11813
|
+
signal.throwIfAborted();
|
|
11814
|
+
await this.#options.onCycleBoundary?.(loopId);
|
|
11815
|
+
if (memoryRotationActive()) return;
|
|
11816
|
+
const pendingCycle = state.pendingCycle;
|
|
11817
|
+
const cycle = pendingCycle?.cycle ?? state.budget.cyclesUsed + 1;
|
|
11818
|
+
const startedAt = pendingCycle?.startedAt ?? now();
|
|
11819
|
+
let cycleInputTokens = pendingCycle?.inputTokens ?? 0;
|
|
11820
|
+
let cycleOutputTokens = pendingCycle?.outputTokens ?? 0;
|
|
11821
|
+
let workerText = pendingCycle?.workerText ?? "";
|
|
11822
|
+
if (pendingCycle === void 0 || pendingCycle === null) {
|
|
11823
|
+
await this.#options.persistence.append({ version: 1, type: "cycle_started", timestamp: startedAt, loopId, payload: { cycle } });
|
|
11824
|
+
yield { type: "loop-cycle-start", loopId, cycle };
|
|
11825
|
+
const prompt = buildLoopCyclePrompt({
|
|
11826
|
+
goal,
|
|
11827
|
+
cycle,
|
|
11828
|
+
...previousVerification === void 0 ? {} : {
|
|
11829
|
+
memory: `Previous host verification: ${previousVerification.summary}`,
|
|
11830
|
+
verification: previousVerification
|
|
11831
|
+
}
|
|
11832
|
+
});
|
|
11833
|
+
let workerError;
|
|
11834
|
+
let workerMemoryPressure = false;
|
|
11835
|
+
let workerIterationLimit = false;
|
|
11836
|
+
for await (const event of this.#options.runWorker({
|
|
11837
|
+
goal,
|
|
11838
|
+
cycle,
|
|
11839
|
+
workspace: executionWorkspace.root,
|
|
11840
|
+
prompt,
|
|
11841
|
+
signal
|
|
11842
|
+
})) {
|
|
11843
|
+
if (event.type === "usage") {
|
|
11844
|
+
cycleInputTokens += event.inputTokens;
|
|
11845
|
+
cycleOutputTokens += event.outputTokens;
|
|
11846
|
+
}
|
|
11847
|
+
if (event.type === "error") {
|
|
11848
|
+
workerError = event.error;
|
|
11849
|
+
if (event.error.code === "memory_pressure") workerMemoryPressure = true;
|
|
11850
|
+
if (event.error.code === "iteration_limit") workerIterationLimit = true;
|
|
11627
11851
|
}
|
|
11852
|
+
if (event.type === "text") workerText = `${workerText}${event.text}`.slice(-16e3);
|
|
11853
|
+
if (this.#options.hallucinationGuard !== void 0) {
|
|
11854
|
+
if (event.type === "tool-start") {
|
|
11855
|
+
this.#options.hallucinationGuard.recordToolCall(event.name, event.input, event.id);
|
|
11856
|
+
} else if (event.type === "tool-end") {
|
|
11857
|
+
this.#options.hallucinationGuard.recordToolResult(event.name, event.result, event.id);
|
|
11858
|
+
}
|
|
11859
|
+
}
|
|
11860
|
+
yield { type: "worker-event", event };
|
|
11861
|
+
}
|
|
11862
|
+
if (workerMemoryPressure) return;
|
|
11863
|
+
if (workerError !== void 0 && !workerIterationLimit && !isResumableWorkerError(workerError)) {
|
|
11864
|
+
const status = workerError.code === "cancelled" ? "cancelled" : "failed";
|
|
11865
|
+
state = LoopStateSchema.parse({
|
|
11866
|
+
...state,
|
|
11867
|
+
budget: {
|
|
11868
|
+
...state.budget,
|
|
11869
|
+
cyclesUsed: cycle,
|
|
11870
|
+
inputTokens: state.budget.inputTokens + cycleInputTokens,
|
|
11871
|
+
outputTokens: state.budget.outputTokens + cycleOutputTokens
|
|
11872
|
+
}
|
|
11873
|
+
});
|
|
11874
|
+
state = await this.#terminal(state, status, workerError.message, now());
|
|
11875
|
+
yield { type: "loop-terminal", loopId, status, reason: workerError.message };
|
|
11876
|
+
return;
|
|
11628
11877
|
}
|
|
11629
|
-
yield { type: "worker-event", event };
|
|
11630
|
-
}
|
|
11631
|
-
if (workerError !== void 0) {
|
|
11632
11878
|
state = LoopStateSchema.parse({
|
|
11633
11879
|
...state,
|
|
11634
|
-
|
|
11635
|
-
|
|
11636
|
-
cyclesUsed: cycle,
|
|
11637
|
-
inputTokens: state.budget.inputTokens + cycleInputTokens,
|
|
11638
|
-
outputTokens: state.budget.outputTokens + cycleOutputTokens
|
|
11639
|
-
}
|
|
11880
|
+
pendingCycle: { cycle, startedAt, inputTokens: cycleInputTokens, outputTokens: cycleOutputTokens, workerText },
|
|
11881
|
+
updatedAt: now()
|
|
11640
11882
|
});
|
|
11641
|
-
|
|
11642
|
-
yield { type: "loop-terminal", loopId, status: "failed", reason: workerError };
|
|
11643
|
-
return;
|
|
11883
|
+
await this.#options.persistence.save(state);
|
|
11644
11884
|
}
|
|
11885
|
+
await this.#options.onMemoryCheckpoint?.(loopId);
|
|
11886
|
+
if (memoryRotationActive()) return;
|
|
11645
11887
|
if (plan.commands.length === 0) {
|
|
11646
11888
|
plan = await this.#options.inferVerification(executionWorkspace.root);
|
|
11647
11889
|
if (plan.commands.length > 0) {
|
|
@@ -11661,8 +11903,22 @@ var LoopOrchestrator = class {
|
|
|
11661
11903
|
passed: false,
|
|
11662
11904
|
commands: [],
|
|
11663
11905
|
summary: plan.needsHumanReason ?? "No deterministic verification command was found after the discovery cycle."
|
|
11664
|
-
} : await this.#options.runVerifier(plan, executionWorkspace.root,
|
|
11906
|
+
} : await this.#options.runVerifier(plan, executionWorkspace.root, signal);
|
|
11907
|
+
await this.#options.onMemoryCheckpoint?.(loopId);
|
|
11908
|
+
if (memoryRotationActive()) return;
|
|
11665
11909
|
const fingerprint2 = await this.#options.fingerprint(executionWorkspace.root);
|
|
11910
|
+
let guardWarnings = [];
|
|
11911
|
+
if (evidence.passed && this.#options.hallucinationGuard !== void 0) {
|
|
11912
|
+
try {
|
|
11913
|
+
await this.#options.onMemoryCheckpoint?.(loopId);
|
|
11914
|
+
if (memoryRotationActive()) return;
|
|
11915
|
+
const report = await this.#options.hallucinationGuard.evaluate(goal, workerText);
|
|
11916
|
+
await this.#options.onMemoryCheckpoint?.(loopId);
|
|
11917
|
+
if (memoryRotationActive()) return;
|
|
11918
|
+
guardWarnings = [...report.blockingReasons, ...report.warnings];
|
|
11919
|
+
} catch {
|
|
11920
|
+
}
|
|
11921
|
+
}
|
|
11666
11922
|
const completedAt = now();
|
|
11667
11923
|
state = LoopStateSchema.parse({
|
|
11668
11924
|
...state,
|
|
@@ -11681,7 +11937,8 @@ var LoopOrchestrator = class {
|
|
|
11681
11937
|
outputTokens: cycleOutputTokens,
|
|
11682
11938
|
workspaceFingerprint: fingerprint2,
|
|
11683
11939
|
verification: evidence
|
|
11684
|
-
}]
|
|
11940
|
+
}].slice(-64),
|
|
11941
|
+
pendingCycle: null
|
|
11685
11942
|
});
|
|
11686
11943
|
await this.#options.persistence.save(state);
|
|
11687
11944
|
await this.#options.persistence.append({
|
|
@@ -11699,20 +11956,6 @@ var LoopOrchestrator = class {
|
|
|
11699
11956
|
return;
|
|
11700
11957
|
}
|
|
11701
11958
|
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
11959
|
const reason = [evidence.summary, ...guardWarnings].filter(Boolean).join("; ");
|
|
11717
11960
|
state = await this.#terminal(state, "succeeded", reason, now());
|
|
11718
11961
|
yield { type: "loop-terminal", loopId, status: "succeeded", reason };
|
|
@@ -11723,8 +11966,8 @@ ${evidence.summary}`;
|
|
|
11723
11966
|
repeatedFailures = failureSignature === previousFailureSignature ? repeatedFailures + 1 : 1;
|
|
11724
11967
|
previousFailureSignature = failureSignature;
|
|
11725
11968
|
previousVerification = evidence;
|
|
11726
|
-
if (repeatedFailures >=
|
|
11727
|
-
const reason =
|
|
11969
|
+
if (this.#options.maxNoProgressCycles !== void 0 && repeatedFailures >= this.#options.maxNoProgressCycles) {
|
|
11970
|
+
const reason = `The same verification failure repeated ${this.#options.maxNoProgressCycles} times without material workspace progress.`;
|
|
11728
11971
|
state = await this.#terminal(state, "no_progress", reason, now());
|
|
11729
11972
|
yield { type: "loop-terminal", loopId, status: "no_progress", reason };
|
|
11730
11973
|
return;
|
|
@@ -11738,7 +11981,7 @@ ${evidence.summary}`;
|
|
|
11738
11981
|
cycleCheckpoint: state.budget.cycleCheckpoint,
|
|
11739
11982
|
tokenCheckpoint: state.budget.tokenCheckpoint
|
|
11740
11983
|
};
|
|
11741
|
-
const confirmation = await this.#options.confirmBudget(state, decision.dimensions,
|
|
11984
|
+
const confirmation = await this.#options.confirmBudget(state, decision.dimensions, signal);
|
|
11742
11985
|
if (confirmation === "unavailable") {
|
|
11743
11986
|
const reason = "Loop budget extension requires user confirmation.";
|
|
11744
11987
|
state = await this.#terminal(state, "needs_human", reason, now());
|
|
@@ -11765,8 +12008,9 @@ ${evidence.summary}`;
|
|
|
11765
12008
|
}
|
|
11766
12009
|
}
|
|
11767
12010
|
} catch (error) {
|
|
11768
|
-
|
|
11769
|
-
const
|
|
12011
|
+
if (memoryRotationActive()) return;
|
|
12012
|
+
const status = signal.aborted ? "cancelled" : "failed";
|
|
12013
|
+
const reason = signal.aborted ? "Loop cancelled by user." : message(error);
|
|
11770
12014
|
state = await this.#terminal(state, status, reason, now());
|
|
11771
12015
|
yield { type: "loop-terminal", loopId, status, reason };
|
|
11772
12016
|
}
|
|
@@ -11784,11 +12028,24 @@ ${evidence.summary}`;
|
|
|
11784
12028
|
return state;
|
|
11785
12029
|
}
|
|
11786
12030
|
};
|
|
12031
|
+
function isResumableWorkerError(error) {
|
|
12032
|
+
return error.code !== "authentication" && error.code !== "model_not_found" && error.code !== "cancelled" && error.code !== "memory_pressure";
|
|
12033
|
+
}
|
|
12034
|
+
function countTrailingFailureSignature(cycles, signature) {
|
|
12035
|
+
let count = 0;
|
|
12036
|
+
for (let index = cycles.length - 1; index >= 0; index -= 1) {
|
|
12037
|
+
const cycle = cycles[index];
|
|
12038
|
+
if (`${cycle.workspaceFingerprint}
|
|
12039
|
+
${cycle.verification.summary}` !== signature) break;
|
|
12040
|
+
count += 1;
|
|
12041
|
+
}
|
|
12042
|
+
return count;
|
|
12043
|
+
}
|
|
11787
12044
|
|
|
11788
12045
|
// src/goal/orchestrator.ts
|
|
11789
12046
|
import { createHash as createHash11 } from "crypto";
|
|
11790
12047
|
import { writeFile as writeFile9, mkdir as mkdir10 } from "fs/promises";
|
|
11791
|
-
import { join as
|
|
12048
|
+
import { join as join16 } from "path";
|
|
11792
12049
|
|
|
11793
12050
|
// src/goal/types.ts
|
|
11794
12051
|
import { z as z21 } from "zod";
|
|
@@ -11851,6 +12108,11 @@ var GoalStateSchema = z21.object({
|
|
|
11851
12108
|
planPath: z21.string().nullable(),
|
|
11852
12109
|
verifyRounds: z21.number().int().min(0),
|
|
11853
12110
|
workerRounds: z21.number().int().min(0),
|
|
12111
|
+
/** Worker output checkpoint used to resume verification without rerunning tools. */
|
|
12112
|
+
pendingVerification: z21.object({
|
|
12113
|
+
round: z21.number().int().positive(),
|
|
12114
|
+
finalResponse: z21.string().max(16e3)
|
|
12115
|
+
}).strict().nullable().optional(),
|
|
11854
12116
|
lastGaps: z21.array(GapSchema),
|
|
11855
12117
|
gapFingerprint: z21.string(),
|
|
11856
12118
|
stallStreak: z21.number().int().min(0),
|
|
@@ -11896,6 +12158,7 @@ ${objective}`
|
|
|
11896
12158
|
].join("\n");
|
|
11897
12159
|
}
|
|
11898
12160
|
async function runPlanner(options) {
|
|
12161
|
+
await options.onProgress?.();
|
|
11899
12162
|
const { adapter, model } = options.registry.get(options.modelId);
|
|
11900
12163
|
const prompt = buildPlannerPrompt(options.objective);
|
|
11901
12164
|
const messages = [{ role: "user", content: prompt }];
|
|
@@ -11907,6 +12170,7 @@ async function runPlanner(options) {
|
|
|
11907
12170
|
tools: [],
|
|
11908
12171
|
...options.signal === void 0 ? {} : { signal: options.signal }
|
|
11909
12172
|
})) {
|
|
12173
|
+
await options.onProgress?.();
|
|
11910
12174
|
if (event.type === "text") text += event.text;
|
|
11911
12175
|
else if (event.type === "error") throw event.error;
|
|
11912
12176
|
else if (event.type === "done") {
|
|
@@ -11953,7 +12217,7 @@ function extractJson(text) {
|
|
|
11953
12217
|
// src/goal/classifier.ts
|
|
11954
12218
|
import { createHash as createHash10 } from "crypto";
|
|
11955
12219
|
import { readFile as readFile10 } from "fs/promises";
|
|
11956
|
-
import { join as
|
|
12220
|
+
import { join as join15 } from "path";
|
|
11957
12221
|
function buildSkepticPrompt(evidence, plan, index) {
|
|
11958
12222
|
const criteriaList = plan.criteria.map((c) => ` ${c.id}. [${c.type}] ${c.description}`).join("\n");
|
|
11959
12223
|
return [
|
|
@@ -12013,7 +12277,7 @@ async function runClassifier(evidence, plan, options) {
|
|
|
12013
12277
|
const skepticResults = await Promise.all(
|
|
12014
12278
|
Array.from(
|
|
12015
12279
|
{ length: n },
|
|
12016
|
-
(_, i) => runOneSkeptic(evidence, plan, i + 1, adapter, model, options.signal)
|
|
12280
|
+
(_, i) => runOneSkeptic(evidence, plan, i + 1, adapter, model, options.signal, options.onProgress)
|
|
12017
12281
|
)
|
|
12018
12282
|
);
|
|
12019
12283
|
const verdicts = [];
|
|
@@ -12030,7 +12294,8 @@ async function runClassifier(evidence, plan, options) {
|
|
|
12030
12294
|
}
|
|
12031
12295
|
return aggregate(verdicts);
|
|
12032
12296
|
}
|
|
12033
|
-
async function runOneSkeptic(evidence, plan, index, adapter, model, signal) {
|
|
12297
|
+
async function runOneSkeptic(evidence, plan, index, adapter, model, signal, onProgress) {
|
|
12298
|
+
await onProgress?.();
|
|
12034
12299
|
const prompt = buildSkepticPrompt(evidence, plan, index);
|
|
12035
12300
|
const messages = [{ role: "user", content: prompt }];
|
|
12036
12301
|
let text = "";
|
|
@@ -12040,6 +12305,7 @@ async function runOneSkeptic(evidence, plan, index, adapter, model, signal) {
|
|
|
12040
12305
|
tools: [],
|
|
12041
12306
|
...signal === void 0 ? {} : { signal }
|
|
12042
12307
|
})) {
|
|
12308
|
+
await onProgress?.();
|
|
12043
12309
|
if (event.type === "text") text += event.text;
|
|
12044
12310
|
else if (event.type === "error") throw event.error;
|
|
12045
12311
|
else if (event.type === "done") break;
|
|
@@ -12130,7 +12396,7 @@ function computeFingerprint(gaps) {
|
|
|
12130
12396
|
}
|
|
12131
12397
|
async function collectEvidence(workspace, objective, finalResponse, priorGaps, contractHash2 = createHash10("sha256").update(objective).digest("hex"), hostVerification) {
|
|
12132
12398
|
const workspaceState = await collectWorkspaceState(workspace);
|
|
12133
|
-
const planPath2 =
|
|
12399
|
+
const planPath2 = join15(workspace, ".flavor", "goal-plan.md");
|
|
12134
12400
|
let planFile = null;
|
|
12135
12401
|
try {
|
|
12136
12402
|
planFile = await readFile10(planPath2, "utf8");
|
|
@@ -12194,7 +12460,7 @@ var GoalOrchestrator = class {
|
|
|
12194
12460
|
async *run(request) {
|
|
12195
12461
|
const timestamp2 = () => this.#options.now?.() ?? (/* @__PURE__ */ new Date()).toISOString();
|
|
12196
12462
|
const createdAt = timestamp2();
|
|
12197
|
-
|
|
12463
|
+
const state = {
|
|
12198
12464
|
id: this.#options.idFactory?.() ?? `goal-${Date.now().toString(36)}`,
|
|
12199
12465
|
objective: request.goal,
|
|
12200
12466
|
phase: "planning",
|
|
@@ -12203,6 +12469,7 @@ var GoalOrchestrator = class {
|
|
|
12203
12469
|
planPath: null,
|
|
12204
12470
|
verifyRounds: 0,
|
|
12205
12471
|
workerRounds: 0,
|
|
12472
|
+
pendingVerification: null,
|
|
12206
12473
|
lastGaps: [],
|
|
12207
12474
|
gapFingerprint: "",
|
|
12208
12475
|
stallStreak: 0,
|
|
@@ -12211,91 +12478,164 @@ var GoalOrchestrator = class {
|
|
|
12211
12478
|
createdAt,
|
|
12212
12479
|
updatedAt: createdAt
|
|
12213
12480
|
};
|
|
12481
|
+
yield* this.#execute(state, request.signal);
|
|
12482
|
+
}
|
|
12483
|
+
/**
|
|
12484
|
+
* Continue a goal whose process rotated onto a fresh heap. The persisted
|
|
12485
|
+
* state decides where to pick up; an interrupted round simply runs again.
|
|
12486
|
+
*/
|
|
12487
|
+
async *resume(request) {
|
|
12488
|
+
const persistence = this.#options.persistence;
|
|
12489
|
+
if (persistence === void 0 || persistence.load === void 0) {
|
|
12490
|
+
throw new Error("Goal persistence does not support resume");
|
|
12491
|
+
}
|
|
12492
|
+
let state;
|
|
12493
|
+
try {
|
|
12494
|
+
state = await persistence.load(request.goalId);
|
|
12495
|
+
} catch (error) {
|
|
12496
|
+
yield { type: "goal-failed", reason: message(error) };
|
|
12497
|
+
return;
|
|
12498
|
+
}
|
|
12499
|
+
if (state.status !== "active" && state.status !== "not_achieved") {
|
|
12500
|
+
yield { type: "goal-failed", reason: `Goal "${state.id}" is ${state.status}; it cannot be resumed.` };
|
|
12501
|
+
return;
|
|
12502
|
+
}
|
|
12503
|
+
yield { type: "goal-resumed", goalId: state.id, round: resumeStartRound(state) };
|
|
12504
|
+
yield* this.#execute(state, request.signal);
|
|
12505
|
+
}
|
|
12506
|
+
async *#execute(initialState, signal) {
|
|
12507
|
+
const timestamp2 = () => this.#options.now?.() ?? (/* @__PURE__ */ new Date()).toISOString();
|
|
12508
|
+
let state = initialState;
|
|
12214
12509
|
const persistState = async (patch) => {
|
|
12215
12510
|
state = { ...state, ...patch, updatedAt: timestamp2() };
|
|
12216
12511
|
await this.#options.persistence?.save(state);
|
|
12217
12512
|
};
|
|
12513
|
+
const memoryCheckpoint = async () => {
|
|
12514
|
+
await this.#options.onMemoryCheckpoint?.(state.id);
|
|
12515
|
+
if (memoryRotationActive()) throw new Error("Heap rotation requested");
|
|
12516
|
+
};
|
|
12218
12517
|
await persistState();
|
|
12219
12518
|
const workspace = this.#options.workspace;
|
|
12220
12519
|
let plan;
|
|
12221
12520
|
let planPath2;
|
|
12222
|
-
|
|
12223
|
-
|
|
12224
|
-
|
|
12225
|
-
|
|
12226
|
-
|
|
12227
|
-
|
|
12228
|
-
|
|
12229
|
-
|
|
12230
|
-
|
|
12231
|
-
|
|
12232
|
-
|
|
12233
|
-
|
|
12234
|
-
|
|
12235
|
-
|
|
12236
|
-
|
|
12237
|
-
|
|
12238
|
-
|
|
12239
|
-
|
|
12240
|
-
|
|
12241
|
-
|
|
12242
|
-
|
|
12243
|
-
|
|
12244
|
-
|
|
12245
|
-
|
|
12246
|
-
|
|
12247
|
-
|
|
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;
|
|
12257
|
-
try {
|
|
12258
|
-
for await (const event of this.#options.runWorker({
|
|
12259
|
-
goal: request.goal,
|
|
12260
|
-
round,
|
|
12261
|
-
workspace,
|
|
12262
|
-
prompt: workerPrompt,
|
|
12263
|
-
priorGaps: formatPriorGaps(priorGaps),
|
|
12264
|
-
signal: request.signal
|
|
12265
|
-
})) {
|
|
12266
|
-
yield { type: "goal-worker-event", round, event };
|
|
12267
|
-
if (event.type === "text") finalResponse += event.text;
|
|
12268
|
-
if (event.type === "error") {
|
|
12269
|
-
workerError = event.error.message;
|
|
12270
|
-
break;
|
|
12521
|
+
if (state.plan === null) {
|
|
12522
|
+
let planningAttempt = 0;
|
|
12523
|
+
while (true) {
|
|
12524
|
+
try {
|
|
12525
|
+
signal.throwIfAborted();
|
|
12526
|
+
await memoryCheckpoint();
|
|
12527
|
+
plan = await runPlanner({
|
|
12528
|
+
registry: this.#options.registry,
|
|
12529
|
+
modelId: this.#options.plannerModelId,
|
|
12530
|
+
objective: state.objective,
|
|
12531
|
+
signal,
|
|
12532
|
+
onProgress: memoryCheckpoint
|
|
12533
|
+
});
|
|
12534
|
+
planPath2 = await writePlanFile(workspace, plan);
|
|
12535
|
+
await persistState({ phase: "executing", plan, planPath: planPath2, contractHash: contractHash(state.objective, plan) });
|
|
12536
|
+
yield { type: "goal-plan-created", plan, planPath: planPath2 };
|
|
12537
|
+
break;
|
|
12538
|
+
} catch (error) {
|
|
12539
|
+
if (memoryRotationActive()) return;
|
|
12540
|
+
const normalized = normalizeProviderError(error);
|
|
12541
|
+
if (!isResumableStageError(normalized)) {
|
|
12542
|
+
const reason = `Goal planning failed: ${normalized.message}`;
|
|
12543
|
+
await persistState({ phase: "complete", status: "failed" });
|
|
12544
|
+
yield { type: "goal-plan-failed", reason };
|
|
12545
|
+
yield { type: "goal-failed", reason };
|
|
12546
|
+
return;
|
|
12271
12547
|
}
|
|
12548
|
+
planningAttempt += 1;
|
|
12549
|
+
const delayMs = stageRetryDelay(this.#options, planningAttempt);
|
|
12550
|
+
yield { type: "goal-stage-retry", stage: "planning", attempt: planningAttempt, reason: normalized.message, delayMs };
|
|
12551
|
+
await waitForStageRetry(delayMs, signal);
|
|
12272
12552
|
}
|
|
12273
|
-
} catch (error) {
|
|
12274
|
-
workerError = message(error);
|
|
12275
12553
|
}
|
|
12276
|
-
|
|
12277
|
-
|
|
12278
|
-
|
|
12279
|
-
|
|
12280
|
-
|
|
12554
|
+
} else {
|
|
12555
|
+
plan = state.plan;
|
|
12556
|
+
planPath2 = state.planPath ?? await writePlanFile(workspace, plan);
|
|
12557
|
+
await persistState({ phase: "executing", status: "active", ...state.planPath === null ? { planPath: planPath2 } : {} });
|
|
12558
|
+
}
|
|
12559
|
+
let priorGaps = state.lastGaps;
|
|
12560
|
+
let priorFingerprint = state.gapFingerprint;
|
|
12561
|
+
let stallStreak = state.stallStreak;
|
|
12562
|
+
const startRound = resumeStartRound(state);
|
|
12563
|
+
for (let round = startRound; this.#options.maxRounds === void 0 || round <= this.#options.maxRounds; round++) {
|
|
12564
|
+
signal.throwIfAborted();
|
|
12565
|
+
await this.#options.onRoundBoundary?.(state.id);
|
|
12566
|
+
if (memoryRotationActive()) return;
|
|
12567
|
+
const pendingVerification = state.pendingVerification?.round === round ? state.pendingVerification : void 0;
|
|
12568
|
+
let finalResponse = pendingVerification?.finalResponse ?? "";
|
|
12569
|
+
if (pendingVerification === void 0) {
|
|
12570
|
+
const workerPrompt = buildWorkerPrompt(state.objective, plan, priorGaps, round);
|
|
12571
|
+
await persistState({
|
|
12572
|
+
phase: "executing",
|
|
12573
|
+
status: "active",
|
|
12574
|
+
workerRounds: round,
|
|
12575
|
+
pendingVerification: null,
|
|
12576
|
+
lastGaps: priorGaps,
|
|
12577
|
+
gapFingerprint: priorFingerprint,
|
|
12578
|
+
stallStreak
|
|
12579
|
+
});
|
|
12580
|
+
yield { type: "goal-worker-start", round };
|
|
12581
|
+
let workerError;
|
|
12582
|
+
let workerMemoryPressure = false;
|
|
12583
|
+
let workerIterationLimit = false;
|
|
12584
|
+
try {
|
|
12585
|
+
for await (const event of this.#options.runWorker({
|
|
12586
|
+
goal: state.objective,
|
|
12587
|
+
round,
|
|
12588
|
+
workspace,
|
|
12589
|
+
prompt: workerPrompt,
|
|
12590
|
+
priorGaps: formatPriorGaps(priorGaps),
|
|
12591
|
+
signal
|
|
12592
|
+
})) {
|
|
12593
|
+
yield { type: "goal-worker-event", round, event };
|
|
12594
|
+
if (event.type === "text") finalResponse = `${finalResponse}${event.text}`.slice(-16e3);
|
|
12595
|
+
if (event.type === "error") {
|
|
12596
|
+
workerError = event.error;
|
|
12597
|
+
if (event.error.code === "memory_pressure") workerMemoryPressure = true;
|
|
12598
|
+
if (event.error.code === "iteration_limit") workerIterationLimit = true;
|
|
12599
|
+
break;
|
|
12600
|
+
}
|
|
12601
|
+
}
|
|
12602
|
+
} catch (error) {
|
|
12603
|
+
if (memoryRotationActive()) return;
|
|
12604
|
+
workerError = { code: "unknown", message: message(error) };
|
|
12605
|
+
}
|
|
12606
|
+
if (workerMemoryPressure) return;
|
|
12607
|
+
if (workerError !== void 0 && !workerIterationLimit && !isResumableWorkerError2(workerError)) {
|
|
12608
|
+
const reason = `Worker error in round ${round}: ${workerError.message}`;
|
|
12609
|
+
await persistState({ phase: "complete", status: "failed" });
|
|
12610
|
+
yield { type: "goal-failed", reason };
|
|
12611
|
+
return;
|
|
12612
|
+
}
|
|
12613
|
+
await persistState({
|
|
12614
|
+
phase: "verifying",
|
|
12615
|
+
status: "active",
|
|
12616
|
+
pendingVerification: { round, finalResponse }
|
|
12617
|
+
});
|
|
12281
12618
|
}
|
|
12282
|
-
await persistState({ phase: "verifying", status: "active", verifyRounds: round });
|
|
12283
12619
|
yield { type: "goal-verification-start", round };
|
|
12284
|
-
|
|
12620
|
+
await this.#options.onMemoryCheckpoint?.(state.id);
|
|
12621
|
+
if (memoryRotationActive()) return;
|
|
12622
|
+
const hostVerification = await this.#options.verifyHost?.(signal);
|
|
12623
|
+
await this.#options.onMemoryCheckpoint?.(state.id);
|
|
12624
|
+
if (memoryRotationActive()) return;
|
|
12285
12625
|
const evidence = await collectEvidence(
|
|
12286
12626
|
workspace,
|
|
12287
|
-
|
|
12627
|
+
state.objective,
|
|
12288
12628
|
finalResponse.slice(-4e3),
|
|
12289
12629
|
// Last 4000 chars of response
|
|
12290
12630
|
formatPriorGaps(priorGaps),
|
|
12291
12631
|
state.contractHash,
|
|
12292
12632
|
hostVerification
|
|
12293
12633
|
);
|
|
12294
|
-
await persistState({ evidenceRounds: [...state.evidenceRounds, {
|
|
12634
|
+
await persistState({ evidenceRounds: [...state.evidenceRounds.filter((item) => item.round !== round), {
|
|
12295
12635
|
round,
|
|
12296
12636
|
workspaceDiffHash: evidence.workspaceDiffHash,
|
|
12297
12637
|
...hostVerification === void 0 ? {} : { hostVerification }
|
|
12298
|
-
}] });
|
|
12638
|
+
}].slice(-64) });
|
|
12299
12639
|
let outcome;
|
|
12300
12640
|
if (plan.kind === "code-change" && hostVerification !== void 0 && !hostVerification.passed) {
|
|
12301
12641
|
if (hostVerification.commands.length === 0) {
|
|
@@ -12309,20 +12649,34 @@ var GoalOrchestrator = class {
|
|
|
12309
12649
|
fingerprint: createHash11("sha256").update(hostVerification.summary).digest("hex").slice(0, 16)
|
|
12310
12650
|
};
|
|
12311
12651
|
}
|
|
12312
|
-
} else
|
|
12313
|
-
|
|
12314
|
-
|
|
12315
|
-
|
|
12316
|
-
|
|
12317
|
-
|
|
12318
|
-
|
|
12319
|
-
|
|
12320
|
-
|
|
12321
|
-
|
|
12322
|
-
|
|
12323
|
-
|
|
12324
|
-
|
|
12652
|
+
} else {
|
|
12653
|
+
let classifierAttempt = 0;
|
|
12654
|
+
while (true) {
|
|
12655
|
+
try {
|
|
12656
|
+
outcome = await runClassifier(evidence, plan, {
|
|
12657
|
+
registry: this.#options.registry,
|
|
12658
|
+
modelId: this.#options.classifierModelId,
|
|
12659
|
+
skepticCount: this.#options.skepticCount,
|
|
12660
|
+
workspace,
|
|
12661
|
+
signal,
|
|
12662
|
+
onProgress: memoryCheckpoint
|
|
12663
|
+
});
|
|
12664
|
+
break;
|
|
12665
|
+
} catch (error) {
|
|
12666
|
+
if (memoryRotationActive()) return;
|
|
12667
|
+
const normalized = normalizeProviderError(error);
|
|
12668
|
+
if (!isResumableStageError(normalized)) {
|
|
12669
|
+
outcome = { type: "blocked", reason: `Classifier infrastructure error: ${normalized.message}` };
|
|
12670
|
+
break;
|
|
12671
|
+
}
|
|
12672
|
+
classifierAttempt += 1;
|
|
12673
|
+
const delayMs = stageRetryDelay(this.#options, classifierAttempt);
|
|
12674
|
+
yield { type: "goal-stage-retry", stage: "classification", attempt: classifierAttempt, reason: normalized.message, delayMs };
|
|
12675
|
+
await waitForStageRetry(delayMs, signal);
|
|
12676
|
+
}
|
|
12677
|
+
}
|
|
12325
12678
|
}
|
|
12679
|
+
await persistState({ verifyRounds: round, pendingVerification: null });
|
|
12326
12680
|
yield {
|
|
12327
12681
|
type: "goal-verdict",
|
|
12328
12682
|
round,
|
|
@@ -12342,7 +12696,7 @@ var GoalOrchestrator = class {
|
|
|
12342
12696
|
}
|
|
12343
12697
|
if (outcome.fingerprint === priorFingerprint) {
|
|
12344
12698
|
stallStreak++;
|
|
12345
|
-
if (stallStreak >= this.#options.maxStallStreak) {
|
|
12699
|
+
if (this.#options.maxStallStreak !== void 0 && stallStreak >= this.#options.maxStallStreak) {
|
|
12346
12700
|
await persistState({
|
|
12347
12701
|
phase: "complete",
|
|
12348
12702
|
status: "failed",
|
|
@@ -12382,9 +12736,40 @@ var GoalOrchestrator = class {
|
|
|
12382
12736
|
function contractHash(objective, plan) {
|
|
12383
12737
|
return createHash11("sha256").update(JSON.stringify({ objective, plan })).digest("hex");
|
|
12384
12738
|
}
|
|
12739
|
+
function resumeStartRound(state) {
|
|
12740
|
+
if (state.pendingVerification !== void 0 && state.pendingVerification !== null) {
|
|
12741
|
+
return state.pendingVerification.round;
|
|
12742
|
+
}
|
|
12743
|
+
return state.verifyRounds >= state.workerRounds ? state.workerRounds + 1 : Math.max(state.workerRounds, 1);
|
|
12744
|
+
}
|
|
12745
|
+
function isResumableWorkerError2(error) {
|
|
12746
|
+
return error.code !== "authentication" && error.code !== "model_not_found" && error.code !== "cancelled" && error.code !== "memory_pressure";
|
|
12747
|
+
}
|
|
12748
|
+
function isResumableStageError(error) {
|
|
12749
|
+
return error.code !== "authentication" && error.code !== "model_not_found" && error.code !== "cancelled" && error.code !== "memory_pressure";
|
|
12750
|
+
}
|
|
12751
|
+
function stageRetryDelay(options, attempt) {
|
|
12752
|
+
const delayMs = options.stageRetryDelayMs?.(attempt) ?? Math.min(6e4, 1e3 * 2 ** Math.min(attempt - 1, 6));
|
|
12753
|
+
return Number.isSafeInteger(delayMs) && delayMs >= 0 ? delayMs : 1e3;
|
|
12754
|
+
}
|
|
12755
|
+
function waitForStageRetry(delayMs, signal) {
|
|
12756
|
+
signal.throwIfAborted();
|
|
12757
|
+
if (delayMs === 0) return Promise.resolve();
|
|
12758
|
+
return new Promise((resolvePromise, reject) => {
|
|
12759
|
+
const onAbort = () => {
|
|
12760
|
+
clearTimeout(timer);
|
|
12761
|
+
reject(signal.reason instanceof Error ? signal.reason : new Error("Goal cancelled"));
|
|
12762
|
+
};
|
|
12763
|
+
const timer = setTimeout(() => {
|
|
12764
|
+
signal.removeEventListener("abort", onAbort);
|
|
12765
|
+
resolvePromise();
|
|
12766
|
+
}, delayMs);
|
|
12767
|
+
signal.addEventListener("abort", onAbort, { once: true });
|
|
12768
|
+
});
|
|
12769
|
+
}
|
|
12385
12770
|
async function writePlanFile(workspace, plan) {
|
|
12386
|
-
const dir =
|
|
12387
|
-
const planPath2 =
|
|
12771
|
+
const dir = join16(workspace, ".flavor");
|
|
12772
|
+
const planPath2 = join16(dir, "goal-plan.md");
|
|
12388
12773
|
await mkdir10(dir, { recursive: true });
|
|
12389
12774
|
const criteriaText = plan.criteria.map((c) => `- [${c.type}] AC-${c.id}: ${c.description}`).join("\n");
|
|
12390
12775
|
const content = [
|
|
@@ -12461,8 +12846,8 @@ function formatPriorGaps(gaps) {
|
|
|
12461
12846
|
// src/goal/store.ts
|
|
12462
12847
|
import { constants as constants4 } from "fs";
|
|
12463
12848
|
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
|
|
12849
|
+
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";
|
|
12850
|
+
import { dirname as dirname9, join as join17, relative as relative9, resolve as resolve12, sep as sep9 } from "path";
|
|
12466
12851
|
var DEFAULT_MAX_BYTES = 1024 * 1024;
|
|
12467
12852
|
var GoalStore = class {
|
|
12468
12853
|
#workspace;
|
|
@@ -12470,7 +12855,7 @@ var GoalStore = class {
|
|
|
12470
12855
|
#maxBytes;
|
|
12471
12856
|
constructor(options) {
|
|
12472
12857
|
this.#workspace = resolve12(options.workspace);
|
|
12473
|
-
this.#root =
|
|
12858
|
+
this.#root = join17(this.#workspace, ".flavor", "goals");
|
|
12474
12859
|
this.#maxBytes = options.maxBytes ?? DEFAULT_MAX_BYTES;
|
|
12475
12860
|
}
|
|
12476
12861
|
async save(input) {
|
|
@@ -12478,13 +12863,13 @@ var GoalStore = class {
|
|
|
12478
12863
|
await assertNoSymlink(this.#workspace, dirname9(this.#root));
|
|
12479
12864
|
await mkdir11(this.#root, { recursive: true, mode: 448 });
|
|
12480
12865
|
await assertNoSymlink(this.#workspace, this.#root);
|
|
12481
|
-
const target =
|
|
12866
|
+
const target = join17(this.#root, `${state.id}.json`);
|
|
12482
12867
|
const body = `${JSON.stringify(state, null, 2)}
|
|
12483
12868
|
`;
|
|
12484
12869
|
if (Buffer.byteLength(body) > this.#maxBytes) {
|
|
12485
12870
|
throw new Error(`Goal state exceeds maximum size of ${this.#maxBytes} bytes`);
|
|
12486
12871
|
}
|
|
12487
|
-
const temporary =
|
|
12872
|
+
const temporary = join17(this.#root, `.${state.id}.${process.pid}.${randomUUID11()}.tmp`);
|
|
12488
12873
|
let handle;
|
|
12489
12874
|
try {
|
|
12490
12875
|
handle = await open5(temporary, constants4.O_CREAT | constants4.O_EXCL | constants4.O_WRONLY, 384);
|
|
@@ -12498,6 +12883,28 @@ var GoalStore = class {
|
|
|
12498
12883
|
await rm6(temporary, { force: true }).catch(() => void 0);
|
|
12499
12884
|
}
|
|
12500
12885
|
}
|
|
12886
|
+
/** Reload a persisted goal so a rotated process can resume it. */
|
|
12887
|
+
async load(goalId) {
|
|
12888
|
+
if (!/^[A-Za-z0-9][A-Za-z0-9._-]{0,127}$/.test(goalId)) {
|
|
12889
|
+
throw new Error(`Goal "${goalId}" was not found`);
|
|
12890
|
+
}
|
|
12891
|
+
await assertNoSymlink(this.#workspace, dirname9(this.#root));
|
|
12892
|
+
await assertNoSymlink(this.#workspace, this.#root);
|
|
12893
|
+
const target = join17(this.#root, `${goalId}.json`);
|
|
12894
|
+
try {
|
|
12895
|
+
const metadata = await stat6(target);
|
|
12896
|
+
if (!metadata.isFile() || metadata.size > this.#maxBytes) {
|
|
12897
|
+
throw new Error("Goal state is not a bounded regular file");
|
|
12898
|
+
}
|
|
12899
|
+
const raw = await readFile11(target, "utf8");
|
|
12900
|
+
return GoalStateSchema.parse(JSON.parse(raw));
|
|
12901
|
+
} catch (error) {
|
|
12902
|
+
if (isCode4(error, "ENOENT")) throw new Error(`Goal "${goalId}" was not found`);
|
|
12903
|
+
const quarantine = `${target}.corrupt-${Date.now()}-${randomUUID11().slice(0, 8)}`;
|
|
12904
|
+
await rename4(target, quarantine).catch(() => void 0);
|
|
12905
|
+
throw new Error(`Goal "${goalId}" is corrupt and was quarantined`);
|
|
12906
|
+
}
|
|
12907
|
+
}
|
|
12501
12908
|
};
|
|
12502
12909
|
async function assertNoSymlink(root, target) {
|
|
12503
12910
|
let cursor = resolve12(target);
|
|
@@ -12524,8 +12931,8 @@ function isCode4(error, code) {
|
|
|
12524
12931
|
}
|
|
12525
12932
|
|
|
12526
12933
|
// src/loop/isolation.ts
|
|
12527
|
-
import { appendFile as appendFile2, mkdir as mkdir12, readFile as
|
|
12528
|
-
import { join as
|
|
12934
|
+
import { appendFile as appendFile2, mkdir as mkdir12, readFile as readFile12 } from "fs/promises";
|
|
12935
|
+
import { join as join18, resolve as resolve13 } from "path";
|
|
12529
12936
|
var CODE_CHANGE = /\b(?:fix|implement|add|update|change|modify|refactor|build|create|remove|delete|write|migrate)\b|修复|实现|添加|更新|修改|重构|创建|删除|迁移/i;
|
|
12530
12937
|
var READ_ONLY = /\b(?:review|analy[sz]e|inspect|explain|research|investigate|audit|summari[sz]e|report)\b|调研|分析|审查|检查|解释|研究|总结|报告/i;
|
|
12531
12938
|
function inferGoalIntent(goal) {
|
|
@@ -12548,8 +12955,8 @@ async function prepareLoopWorkspace(options) {
|
|
|
12548
12955
|
return { kind: "needs_human", reason: "The workspace has uncommitted changes that a new worktree would omit." };
|
|
12549
12956
|
}
|
|
12550
12957
|
options.signal?.throwIfAborted();
|
|
12551
|
-
const worktrees =
|
|
12552
|
-
const path =
|
|
12958
|
+
const worktrees = join18(root, ".worktrees");
|
|
12959
|
+
const path = join18(worktrees, `loop-${options.loopId}`);
|
|
12553
12960
|
const branch = `loop/${options.loopId}`;
|
|
12554
12961
|
try {
|
|
12555
12962
|
await ensureWorktreeDirectoryIgnored(root);
|
|
@@ -12579,12 +12986,12 @@ async function ensureWorktreeDirectoryIgnored(root) {
|
|
|
12579
12986
|
const commonDirectory = await git(root, ["rev-parse", "--git-common-dir"]);
|
|
12580
12987
|
if (commonDirectory.code !== 0) throw new Error(commonDirectory.stderr.trim() || "Git common directory is unavailable");
|
|
12581
12988
|
const gitDirectory = resolve13(root, commonDirectory.stdout.trim());
|
|
12582
|
-
const infoDirectory =
|
|
12583
|
-
const excludePath =
|
|
12989
|
+
const infoDirectory = join18(gitDirectory, "info");
|
|
12990
|
+
const excludePath = join18(infoDirectory, "exclude");
|
|
12584
12991
|
await mkdir12(infoDirectory, { recursive: true });
|
|
12585
12992
|
let existing = "";
|
|
12586
12993
|
try {
|
|
12587
|
-
existing = await
|
|
12994
|
+
existing = await readFile12(excludePath, "utf8");
|
|
12588
12995
|
} catch (error) {
|
|
12589
12996
|
if (!(typeof error === "object" && error !== null && "code" in error && error.code === "ENOENT")) throw error;
|
|
12590
12997
|
}
|
|
@@ -12595,8 +13002,8 @@ async function ensureWorktreeDirectoryIgnored(root) {
|
|
|
12595
13002
|
|
|
12596
13003
|
// src/loop/store.ts
|
|
12597
13004
|
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
|
|
13005
|
+
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";
|
|
13006
|
+
import { dirname as dirname10, join as join19, relative as relative10, resolve as resolve14, sep as sep10 } from "path";
|
|
12600
13007
|
import { randomUUID as randomUUID12 } from "crypto";
|
|
12601
13008
|
var LOOP_ID = /^[A-Za-z0-9][A-Za-z0-9._-]{0,127}$/;
|
|
12602
13009
|
var DEFAULT_MAX_BYTES2 = 5 * 1024 * 1024;
|
|
@@ -12607,7 +13014,7 @@ var LoopStore = class {
|
|
|
12607
13014
|
#appendTails = /* @__PURE__ */ new Map();
|
|
12608
13015
|
constructor(options) {
|
|
12609
13016
|
this.#workspace = resolve14(options.workspace);
|
|
12610
|
-
this.#root =
|
|
13017
|
+
this.#root = join19(this.#workspace, ".flavor", "loops");
|
|
12611
13018
|
this.#maxBytes = options.maxBytes ?? DEFAULT_MAX_BYTES2;
|
|
12612
13019
|
}
|
|
12613
13020
|
async create(state) {
|
|
@@ -12624,11 +13031,11 @@ var LoopStore = class {
|
|
|
12624
13031
|
const state = LoopStateSchema.parse(input);
|
|
12625
13032
|
await this.#assertWorkspace(state.workspace);
|
|
12626
13033
|
const directory = await this.#prepare(state.loopId);
|
|
12627
|
-
const target =
|
|
13034
|
+
const target = join19(directory, "state.json");
|
|
12628
13035
|
const body = `${JSON.stringify(state, null, 2)}
|
|
12629
13036
|
`;
|
|
12630
13037
|
if (Buffer.byteLength(body) > this.#maxBytes) throw new Error(`Loop state exceeds maximum size of ${this.#maxBytes} bytes`);
|
|
12631
|
-
const temporary =
|
|
13038
|
+
const temporary = join19(directory, `.state.${process.pid}.${randomUUID12()}.tmp`);
|
|
12632
13039
|
let handle;
|
|
12633
13040
|
try {
|
|
12634
13041
|
handle = await open6(temporary, constants5.O_CREAT | constants5.O_EXCL | constants5.O_WRONLY, 384);
|
|
@@ -12644,11 +13051,11 @@ var LoopStore = class {
|
|
|
12644
13051
|
}
|
|
12645
13052
|
async load(loopId) {
|
|
12646
13053
|
const directory = await this.#safeExisting(loopId);
|
|
12647
|
-
const target =
|
|
13054
|
+
const target = join19(directory, "state.json");
|
|
12648
13055
|
try {
|
|
12649
|
-
const metadata = await
|
|
13056
|
+
const metadata = await stat7(target);
|
|
12650
13057
|
if (!metadata.isFile() || metadata.size > this.#maxBytes) throw new Error("Loop state is not a bounded regular file");
|
|
12651
|
-
const raw = await
|
|
13058
|
+
const raw = await readFile13(target, "utf8");
|
|
12652
13059
|
const state = LoopStateSchema.parse(JSON.parse(raw));
|
|
12653
13060
|
await this.#assertWorkspace(state.workspace);
|
|
12654
13061
|
return state;
|
|
@@ -12664,7 +13071,7 @@ var LoopStore = class {
|
|
|
12664
13071
|
const previous = this.#appendTails.get(event.loopId) ?? Promise.resolve();
|
|
12665
13072
|
const next = previous.catch(() => void 0).then(async () => {
|
|
12666
13073
|
const directory = await this.#prepare(event.loopId);
|
|
12667
|
-
const path =
|
|
13074
|
+
const path = join19(directory, "events.jsonl");
|
|
12668
13075
|
const handle = await open6(path, constants5.O_CREAT | constants5.O_APPEND | constants5.O_WRONLY, 384);
|
|
12669
13076
|
try {
|
|
12670
13077
|
await handle.writeFile(`${JSON.stringify(event)}
|
|
@@ -12681,7 +13088,7 @@ var LoopStore = class {
|
|
|
12681
13088
|
await assertNoSymlink2(this.#workspace, dirname10(this.#root));
|
|
12682
13089
|
await mkdir13(this.#root, { recursive: true, mode: 448 });
|
|
12683
13090
|
await assertNoSymlink2(this.#workspace, this.#root);
|
|
12684
|
-
const directory =
|
|
13091
|
+
const directory = join19(this.#root, loopId);
|
|
12685
13092
|
await mkdir13(directory, { recursive: true, mode: 448 });
|
|
12686
13093
|
await assertNoSymlink2(this.#workspace, directory);
|
|
12687
13094
|
return directory;
|
|
@@ -12689,7 +13096,7 @@ var LoopStore = class {
|
|
|
12689
13096
|
async #safeExisting(loopId) {
|
|
12690
13097
|
validateId(loopId);
|
|
12691
13098
|
await assertNoSymlink2(this.#workspace, this.#root);
|
|
12692
|
-
const directory =
|
|
13099
|
+
const directory = join19(this.#root, loopId);
|
|
12693
13100
|
await assertNoSymlink2(this.#workspace, directory);
|
|
12694
13101
|
return directory;
|
|
12695
13102
|
}
|
|
@@ -12737,13 +13144,13 @@ function isCode5(error, code) {
|
|
|
12737
13144
|
}
|
|
12738
13145
|
|
|
12739
13146
|
// src/loop/verifier.ts
|
|
12740
|
-
import { readFile as
|
|
12741
|
-
import { join as
|
|
13147
|
+
import { readFile as readFile14 } from "fs/promises";
|
|
13148
|
+
import { join as join21 } from "path";
|
|
12742
13149
|
|
|
12743
13150
|
// src/tools/shell.ts
|
|
12744
13151
|
import { spawn as spawn4 } from "child_process";
|
|
12745
13152
|
import { existsSync as existsSync4 } from "fs";
|
|
12746
|
-
import { basename as basename5, delimiter as delimiter2, isAbsolute as isAbsolute9, join as
|
|
13153
|
+
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
13154
|
import { z as z23 } from "zod";
|
|
12748
13155
|
|
|
12749
13156
|
// src/tools/jobs.ts
|
|
@@ -13406,7 +13813,7 @@ function decodeOutput(buffer) {
|
|
|
13406
13813
|
return new TextDecoder(encoding).decode(buffer);
|
|
13407
13814
|
}
|
|
13408
13815
|
function shellEnvironment(root, environment) {
|
|
13409
|
-
const localBin =
|
|
13816
|
+
const localBin = join20(root, "node_modules", ".bin");
|
|
13410
13817
|
if (!existsSync4(localBin)) return environment;
|
|
13411
13818
|
const pathKey = Object.keys(environment).find((key) => key.toLowerCase() === "path") ?? "PATH";
|
|
13412
13819
|
const current = environment[pathKey] ?? "";
|
|
@@ -13506,16 +13913,17 @@ function incompleteUtf8SuffixLength(buffer) {
|
|
|
13506
13913
|
|
|
13507
13914
|
// src/loop/verifier.ts
|
|
13508
13915
|
var SCRIPT_PRIORITY = ["test", "typecheck", "lint", "build", "smoke:install"];
|
|
13916
|
+
var MAX_PERSISTED_VERIFICATION_STREAM_BYTES = 1024;
|
|
13509
13917
|
async function inferVerificationPlan(workspace) {
|
|
13510
13918
|
const commands = [];
|
|
13511
13919
|
const seen = /* @__PURE__ */ new Set();
|
|
13512
|
-
const packageJson = await optionalJson(
|
|
13920
|
+
const packageJson = await optionalJson(join21(workspace, "package.json"));
|
|
13513
13921
|
const scripts = isRecord3(packageJson?.scripts) ? packageJson.scripts : {};
|
|
13514
13922
|
for (const name of SCRIPT_PRIORITY) {
|
|
13515
13923
|
if (typeof scripts[name] !== "string" || isUnconditionalPassThrough(scripts[name])) continue;
|
|
13516
13924
|
add(commands, seen, npmScript(name));
|
|
13517
13925
|
}
|
|
13518
|
-
const flavor = await optionalText(
|
|
13926
|
+
const flavor = await optionalText(join21(workspace, "FLAVOR.md"));
|
|
13519
13927
|
if (flavor !== void 0) {
|
|
13520
13928
|
for (const match of flavor.matchAll(/^- `([^`]+)`\s*$/gm)) {
|
|
13521
13929
|
const parsed = parseTrustedCommand(match[1] ?? "");
|
|
@@ -13541,9 +13949,9 @@ async function runVerificationPlan(plan, workspace, signal, executionEnvironment
|
|
|
13541
13949
|
command: item.command,
|
|
13542
13950
|
args: item.args,
|
|
13543
13951
|
exitCode: result.exitCode,
|
|
13544
|
-
stdout: result.stdout,
|
|
13545
|
-
stderr: result.stderr,
|
|
13546
|
-
truncated: result.truncated
|
|
13952
|
+
stdout: boundedEvidenceTail(result.stdout),
|
|
13953
|
+
stderr: boundedEvidenceTail(result.stderr),
|
|
13954
|
+
truncated: result.truncated || Buffer.byteLength(result.stdout, "utf8") > MAX_PERSISTED_VERIFICATION_STREAM_BYTES || Buffer.byteLength(result.stderr, "utf8") > MAX_PERSISTED_VERIFICATION_STREAM_BYTES
|
|
13547
13955
|
});
|
|
13548
13956
|
if (result.exitCode !== 0) {
|
|
13549
13957
|
return { passed: false, commands, summary: `${item.label} failed with exit code ${String(result.exitCode)}.` };
|
|
@@ -13551,6 +13959,14 @@ async function runVerificationPlan(plan, workspace, signal, executionEnvironment
|
|
|
13551
13959
|
}
|
|
13552
13960
|
return { passed: true, commands, summary: `Passed ${commands.length} deterministic verification command(s).` };
|
|
13553
13961
|
}
|
|
13962
|
+
function boundedEvidenceTail(value) {
|
|
13963
|
+
const encoded = Buffer.from(value, "utf8");
|
|
13964
|
+
if (encoded.byteLength <= MAX_PERSISTED_VERIFICATION_STREAM_BYTES) return value;
|
|
13965
|
+
const prefix = Buffer.from("\u2026\n", "utf8");
|
|
13966
|
+
let start = encoded.byteLength - (MAX_PERSISTED_VERIFICATION_STREAM_BYTES - prefix.byteLength);
|
|
13967
|
+
while (start < encoded.byteLength && (encoded[start] & 192) === 128) start += 1;
|
|
13968
|
+
return `${prefix.toString("utf8")}${encoded.subarray(start).toString("utf8")}`;
|
|
13969
|
+
}
|
|
13554
13970
|
function npmScript(name) {
|
|
13555
13971
|
return name === "test" ? { label: name, command: "npm", args: ["test"] } : { label: name, command: "npm", args: ["run", name] };
|
|
13556
13972
|
}
|
|
@@ -13582,7 +13998,7 @@ function isUnconditionalPassThrough(script) {
|
|
|
13582
13998
|
}
|
|
13583
13999
|
async function optionalText(path) {
|
|
13584
14000
|
try {
|
|
13585
|
-
return await
|
|
14001
|
+
return await readFile14(path, "utf8");
|
|
13586
14002
|
} catch (error) {
|
|
13587
14003
|
if (isCode6(error, "ENOENT")) return void 0;
|
|
13588
14004
|
throw error;
|
|
@@ -13603,7 +14019,7 @@ function isCode6(error, code) {
|
|
|
13603
14019
|
|
|
13604
14020
|
// src/models/anthropic.ts
|
|
13605
14021
|
import Anthropic from "@anthropic-ai/sdk";
|
|
13606
|
-
import { readFile as
|
|
14022
|
+
import { readFile as readFile15 } from "fs/promises";
|
|
13607
14023
|
|
|
13608
14024
|
// src/utils/json.ts
|
|
13609
14025
|
function safeParseJson(input) {
|
|
@@ -13652,11 +14068,11 @@ function isEnvTruthy(value) {
|
|
|
13652
14068
|
|
|
13653
14069
|
// src/utils/log.ts
|
|
13654
14070
|
import { appendFile as appendFile3, mkdir as mkdir14 } from "fs/promises";
|
|
13655
|
-
import { dirname as dirname11, join as
|
|
14071
|
+
import { dirname as dirname11, join as join22, resolve as resolve16 } from "path";
|
|
13656
14072
|
var pending = Promise.resolve();
|
|
13657
14073
|
var usagePending = Promise.resolve();
|
|
13658
14074
|
function usageLogPath() {
|
|
13659
|
-
return process.env.FLAVOR_USAGE_FILE ??
|
|
14075
|
+
return process.env.FLAVOR_USAGE_FILE ?? join22(process.cwd(), ".flavor", "usage.jsonl");
|
|
13660
14076
|
}
|
|
13661
14077
|
var usageSessionId = "unknown";
|
|
13662
14078
|
var truncateOnNextAppend = false;
|
|
@@ -13685,7 +14101,7 @@ var AuditLogger = class {
|
|
|
13685
14101
|
#path;
|
|
13686
14102
|
#closed = false;
|
|
13687
14103
|
constructor(workspace) {
|
|
13688
|
-
this.#path =
|
|
14104
|
+
this.#path = join22(resolve16(workspace), ".flavor", "audit.jsonl");
|
|
13689
14105
|
}
|
|
13690
14106
|
get path() {
|
|
13691
14107
|
return this.#path;
|
|
@@ -13709,6 +14125,35 @@ var AuditLogger = class {
|
|
|
13709
14125
|
}
|
|
13710
14126
|
};
|
|
13711
14127
|
|
|
14128
|
+
// src/utils/abort.ts
|
|
14129
|
+
function waitWithSignal(operation, signal) {
|
|
14130
|
+
if (signal === void 0) return operation;
|
|
14131
|
+
return new Promise((resolve44, reject) => {
|
|
14132
|
+
const abort2 = () => reject(signal.reason);
|
|
14133
|
+
if (signal.aborted) abort2();
|
|
14134
|
+
else signal.addEventListener("abort", abort2, { once: true });
|
|
14135
|
+
operation.then(resolve44, reject).finally(() => signal.removeEventListener("abort", abort2));
|
|
14136
|
+
});
|
|
14137
|
+
}
|
|
14138
|
+
function createScopedAbortSignal(parent) {
|
|
14139
|
+
const controller2 = new AbortController();
|
|
14140
|
+
let disposed = false;
|
|
14141
|
+
const abort2 = () => {
|
|
14142
|
+
if (!controller2.signal.aborted) controller2.abort(parent?.reason);
|
|
14143
|
+
};
|
|
14144
|
+
if (parent?.aborted) abort2();
|
|
14145
|
+
else parent?.addEventListener("abort", abort2, { once: true });
|
|
14146
|
+
return {
|
|
14147
|
+
signal: controller2.signal,
|
|
14148
|
+
dispose: () => {
|
|
14149
|
+
if (disposed) return;
|
|
14150
|
+
disposed = true;
|
|
14151
|
+
parent?.removeEventListener("abort", abort2);
|
|
14152
|
+
abort2();
|
|
14153
|
+
}
|
|
14154
|
+
};
|
|
14155
|
+
}
|
|
14156
|
+
|
|
13712
14157
|
// src/models/anthropic.ts
|
|
13713
14158
|
var CLAUDE_CLIENT_HEADERS = {
|
|
13714
14159
|
"User-Agent": "claude-cli/2.1.0 (external, cli)",
|
|
@@ -13831,6 +14276,7 @@ var AnthropicModelAdapter = class {
|
|
|
13831
14276
|
void appendUsageLog(line);
|
|
13832
14277
|
}
|
|
13833
14278
|
async *stream(request) {
|
|
14279
|
+
const requestAbort = createScopedAbortSignal(request.signal);
|
|
13834
14280
|
let inputTokens = 0;
|
|
13835
14281
|
let outputTokens = 0;
|
|
13836
14282
|
let hasUsage = false;
|
|
@@ -13905,7 +14351,7 @@ var AnthropicModelAdapter = class {
|
|
|
13905
14351
|
source: {
|
|
13906
14352
|
type: "base64",
|
|
13907
14353
|
media_type: block.mediaType,
|
|
13908
|
-
data: (await
|
|
14354
|
+
data: (await readFile15(block.source.path)).toString("base64")
|
|
13909
14355
|
}
|
|
13910
14356
|
}));
|
|
13911
14357
|
if (message2.cacheBreakpoint && Array.isArray(content) && content.length > 0) {
|
|
@@ -13954,13 +14400,13 @@ var AnthropicModelAdapter = class {
|
|
|
13954
14400
|
};
|
|
13955
14401
|
let stream;
|
|
13956
14402
|
try {
|
|
13957
|
-
stream = await this.client.messages.create(body, { signal:
|
|
14403
|
+
stream = await this.client.messages.create(body, { signal: requestAbort.signal });
|
|
13958
14404
|
} catch (error) {
|
|
13959
14405
|
if (this.thinkingBudget > 0 && !this.#thinkingRejected && isThinkingParamRejected(error)) {
|
|
13960
14406
|
this.#thinkingRejected = true;
|
|
13961
14407
|
delete body.thinking;
|
|
13962
14408
|
stripThinkingBlocks(body.messages);
|
|
13963
|
-
stream = await this.client.messages.create(body, { signal:
|
|
14409
|
+
stream = await this.client.messages.create(body, { signal: requestAbort.signal });
|
|
13964
14410
|
} else {
|
|
13965
14411
|
throw error;
|
|
13966
14412
|
}
|
|
@@ -14072,6 +14518,8 @@ var AnthropicModelAdapter = class {
|
|
|
14072
14518
|
};
|
|
14073
14519
|
}
|
|
14074
14520
|
yield { type: "error", error: normalizeProviderError(error) };
|
|
14521
|
+
} finally {
|
|
14522
|
+
requestAbort.dispose();
|
|
14075
14523
|
}
|
|
14076
14524
|
}
|
|
14077
14525
|
};
|
|
@@ -14125,7 +14573,7 @@ function resolveCacheProfile(input) {
|
|
|
14125
14573
|
|
|
14126
14574
|
// src/models/openai.ts
|
|
14127
14575
|
import OpenAI from "openai";
|
|
14128
|
-
import { readFile as
|
|
14576
|
+
import { readFile as readFile16 } from "fs/promises";
|
|
14129
14577
|
async function toInput(message2) {
|
|
14130
14578
|
if (message2.role === "tool") {
|
|
14131
14579
|
if (!message2.toolCallId) throw new Error("Tool messages require toolCallId");
|
|
@@ -14137,7 +14585,7 @@ async function toInput(message2) {
|
|
|
14137
14585
|
}
|
|
14138
14586
|
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
14587
|
type: "input_image",
|
|
14140
|
-
image_url: `data:${block.mediaType};base64,${(await
|
|
14588
|
+
image_url: `data:${block.mediaType};base64,${(await readFile16(block.source.path)).toString("base64")}`,
|
|
14141
14589
|
detail: "auto"
|
|
14142
14590
|
}));
|
|
14143
14591
|
return [
|
|
@@ -14216,6 +14664,7 @@ var OpenAIModelAdapter = class {
|
|
|
14216
14664
|
void appendUsageLog(line);
|
|
14217
14665
|
}
|
|
14218
14666
|
async *stream(request) {
|
|
14667
|
+
const requestAbort = createScopedAbortSignal(request.signal);
|
|
14219
14668
|
const callIds = /* @__PURE__ */ new Map();
|
|
14220
14669
|
const pendingCalls = /* @__PURE__ */ new Map();
|
|
14221
14670
|
const emittedCalls = /* @__PURE__ */ new Set();
|
|
@@ -14232,7 +14681,7 @@ var OpenAIModelAdapter = class {
|
|
|
14232
14681
|
strict: tool.strict ?? true
|
|
14233
14682
|
}))
|
|
14234
14683
|
};
|
|
14235
|
-
const stream = this.client.responses.stream(body, { signal:
|
|
14684
|
+
const stream = this.client.responses.stream(body, { signal: requestAbort.signal });
|
|
14236
14685
|
for await (const event of stream) {
|
|
14237
14686
|
if (event.type === "response.output_item.added" && event.item?.type === "function_call" && event.output_index !== void 0 && event.item.call_id) {
|
|
14238
14687
|
callIds.set(event.output_index, event.item.call_id);
|
|
@@ -14326,6 +14775,8 @@ var OpenAIModelAdapter = class {
|
|
|
14326
14775
|
}
|
|
14327
14776
|
} catch (error) {
|
|
14328
14777
|
yield { type: "error", error: normalizeProviderError(error) };
|
|
14778
|
+
} finally {
|
|
14779
|
+
requestAbort.dispose();
|
|
14329
14780
|
}
|
|
14330
14781
|
}
|
|
14331
14782
|
};
|
|
@@ -14559,7 +15010,7 @@ import {
|
|
|
14559
15010
|
|
|
14560
15011
|
// src/utils/version.ts
|
|
14561
15012
|
import { readFileSync as readFileSync2 } from "fs";
|
|
14562
|
-
import { dirname as dirname12, join as
|
|
15013
|
+
import { dirname as dirname12, join as join23 } from "path";
|
|
14563
15014
|
import { fileURLToPath } from "url";
|
|
14564
15015
|
var cached;
|
|
14565
15016
|
function packageVersion() {
|
|
@@ -14567,7 +15018,7 @@ function packageVersion() {
|
|
|
14567
15018
|
let directory = dirname12(fileURLToPath(import.meta.url));
|
|
14568
15019
|
for (; ; ) {
|
|
14569
15020
|
try {
|
|
14570
|
-
const manifest = JSON.parse(readFileSync2(
|
|
15021
|
+
const manifest = JSON.parse(readFileSync2(join23(directory, "package.json"), "utf8"));
|
|
14571
15022
|
if (typeof manifest.version === "string" && manifest.version.length > 0) {
|
|
14572
15023
|
cached = manifest.version;
|
|
14573
15024
|
return cached;
|
|
@@ -14994,8 +15445,8 @@ function isStoredToken(value) {
|
|
|
14994
15445
|
}
|
|
14995
15446
|
|
|
14996
15447
|
// src/permissions/policy.ts
|
|
14997
|
-
import { readFile as
|
|
14998
|
-
import { join as
|
|
15448
|
+
import { readFile as readFile17 } from "fs/promises";
|
|
15449
|
+
import { join as join24, resolve as resolve18 } from "path";
|
|
14999
15450
|
import { z as z25 } from "zod";
|
|
15000
15451
|
var DecisionSchema2 = z25.enum(["allow", "ask", "deny"]);
|
|
15001
15452
|
var PrefixSchema = z25.array(z25.string().min(1).max(1024)).min(1).max(64);
|
|
@@ -15032,15 +15483,15 @@ var CompiledPermissionPolicy = class {
|
|
|
15032
15483
|
async function loadPermissionPolicy(options) {
|
|
15033
15484
|
const sources = [
|
|
15034
15485
|
...options.managedPath === void 0 ? [] : [{ tier: "managed", path: resolve18(options.managedPath) }],
|
|
15035
|
-
{ tier: "user", path:
|
|
15036
|
-
{ tier: "project", path:
|
|
15037
|
-
{ tier: "local", path:
|
|
15486
|
+
{ tier: "user", path: join24(resolve18(options.home), ".flavor-code", "permissions.json") },
|
|
15487
|
+
{ tier: "project", path: join24(resolve18(options.workspace), ".flavor", "permissions.json") },
|
|
15488
|
+
{ tier: "local", path: join24(resolve18(options.workspace), ".flavor", "permissions.local.json") }
|
|
15038
15489
|
];
|
|
15039
15490
|
const rules = [];
|
|
15040
15491
|
for (const source of sources) {
|
|
15041
15492
|
let raw;
|
|
15042
15493
|
try {
|
|
15043
|
-
raw = await
|
|
15494
|
+
raw = await readFile17(source.path, "utf8");
|
|
15044
15495
|
} catch (error) {
|
|
15045
15496
|
if (isCode7(error, "ENOENT")) continue;
|
|
15046
15497
|
throw error;
|
|
@@ -15806,8 +16257,8 @@ function createSkillResourceTool(registry) {
|
|
|
15806
16257
|
|
|
15807
16258
|
// src/session/store.ts
|
|
15808
16259
|
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
|
|
16260
|
+
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";
|
|
16261
|
+
import { basename as basename6, dirname as dirname14, join as join25, relative as relative13, resolve as resolve20, sep as sep13 } from "path";
|
|
15811
16262
|
import { randomUUID as randomUUID13 } from "crypto";
|
|
15812
16263
|
import { z as z27 } from "zod";
|
|
15813
16264
|
|
|
@@ -15821,6 +16272,11 @@ function appendThinkingText(previous, delta) {
|
|
|
15821
16272
|
// src/ui/transcript.ts
|
|
15822
16273
|
var MAX_TRANSCRIPT_TURNS = 200;
|
|
15823
16274
|
var MAX_TRANSCRIPT_CHARS = 2e6;
|
|
16275
|
+
var MAX_ACTIVE_TRANSCRIPT_BLOCKS = 160;
|
|
16276
|
+
var MAX_ACTIVE_ASSISTANT_CHARS = 64e3;
|
|
16277
|
+
var MAX_TRANSCRIPT_BLOCK_TEXT_CHARS = 16e3;
|
|
16278
|
+
var MAX_TRANSCRIPT_TOOL_VALUE_CHARS = 16e3;
|
|
16279
|
+
var MAX_TRANSCRIPT_PRESENTATION_CHARS = 32e3;
|
|
15824
16280
|
function createTranscriptState() {
|
|
15825
16281
|
return { completed: [], nextId: 1 };
|
|
15826
16282
|
}
|
|
@@ -16181,11 +16637,9 @@ function applyTaskSnapshot(turn, snapshot4, includeTerminal = true) {
|
|
|
16181
16637
|
else blocks[index] = block;
|
|
16182
16638
|
}
|
|
16183
16639
|
return {
|
|
16184
|
-
...turn,
|
|
16640
|
+
...withBlocks(turn, blocks),
|
|
16185
16641
|
taskSnapshot: snapshot4,
|
|
16186
|
-
suppressedTaskIds: [...suppressed]
|
|
16187
|
-
blocks,
|
|
16188
|
-
statusLines: blocks.filter((block) => block.kind === "status").map((block) => block.text)
|
|
16642
|
+
suppressedTaskIds: [...suppressed]
|
|
16189
16643
|
};
|
|
16190
16644
|
}
|
|
16191
16645
|
function terminalTaskIds(snapshot4) {
|
|
@@ -16199,13 +16653,10 @@ function upsertStatus(state, block) {
|
|
|
16199
16653
|
if (state.active === void 0) return state;
|
|
16200
16654
|
const blocks = [...state.active.blocks];
|
|
16201
16655
|
const index = blocks.findIndex((item) => item.kind === "status" && item.id === block.id);
|
|
16202
|
-
|
|
16203
|
-
|
|
16204
|
-
|
|
16205
|
-
|
|
16206
|
-
statusLines: blocks.filter((item) => item.kind === "status").map((item) => item.text),
|
|
16207
|
-
blocks
|
|
16208
|
-
} };
|
|
16656
|
+
const bounded2 = boundedTranscriptBlock(block);
|
|
16657
|
+
if (index < 0) blocks.push(bounded2);
|
|
16658
|
+
else blocks[index] = bounded2;
|
|
16659
|
+
return { ...state, active: withBlocks(state.active, blocks) };
|
|
16209
16660
|
}
|
|
16210
16661
|
function finishActive(state) {
|
|
16211
16662
|
if (state.active === void 0) return state;
|
|
@@ -16229,16 +16680,12 @@ function attachThinking(turn, text) {
|
|
|
16229
16680
|
...block,
|
|
16230
16681
|
thinkingText: appendThinkingText(block.thinkingText, text)
|
|
16231
16682
|
};
|
|
16232
|
-
return
|
|
16683
|
+
return withBlocks(turn, blocks);
|
|
16233
16684
|
}
|
|
16234
16685
|
function withoutModelActivity(turn, id) {
|
|
16235
16686
|
const blocks = turn.blocks.filter((block) => block.kind !== "status" || block.activity !== "model" || id !== void 0 && block.id !== id);
|
|
16236
16687
|
if (blocks.length === turn.blocks.length) return turn;
|
|
16237
|
-
return
|
|
16238
|
-
...turn,
|
|
16239
|
-
blocks,
|
|
16240
|
-
statusLines: blocks.filter((block) => block.kind === "status").map((block) => block.text)
|
|
16241
|
-
};
|
|
16688
|
+
return withBlocks(turn, blocks);
|
|
16242
16689
|
}
|
|
16243
16690
|
function restoreTranscriptState(state) {
|
|
16244
16691
|
const completed = state.completed.map(cloneTurn);
|
|
@@ -16340,15 +16787,18 @@ function cloneTurn(turn) {
|
|
|
16340
16787
|
function upsertTurnStatus(turn, block) {
|
|
16341
16788
|
const blocks = [...turn.blocks];
|
|
16342
16789
|
const index = blocks.findIndex((item) => item.kind === "status" && item.id === block.id);
|
|
16343
|
-
|
|
16344
|
-
|
|
16790
|
+
const bounded2 = boundedTranscriptBlock(block);
|
|
16791
|
+
if (index < 0) blocks.push(bounded2);
|
|
16792
|
+
else blocks[index] = bounded2;
|
|
16345
16793
|
return withBlocks(turn, blocks);
|
|
16346
16794
|
}
|
|
16347
16795
|
function withBlocks(turn, blocks) {
|
|
16796
|
+
const bounded2 = blocks.length <= MAX_ACTIVE_TRANSCRIPT_BLOCKS ? blocks : blocks.slice(-MAX_ACTIVE_TRANSCRIPT_BLOCKS);
|
|
16348
16797
|
return {
|
|
16349
16798
|
...turn,
|
|
16350
|
-
|
|
16351
|
-
|
|
16799
|
+
assistantText: tailText(turn.assistantText, MAX_ACTIVE_ASSISTANT_CHARS),
|
|
16800
|
+
blocks: bounded2,
|
|
16801
|
+
statusLines: bounded2.filter((block) => block.kind === "status").map((block) => block.text)
|
|
16352
16802
|
};
|
|
16353
16803
|
}
|
|
16354
16804
|
function appendLine(text, line) {
|
|
@@ -16362,19 +16812,95 @@ function stripRetryBlocks(turn) {
|
|
|
16362
16812
|
if (block.id.startsWith("structured-retry:")) return false;
|
|
16363
16813
|
return true;
|
|
16364
16814
|
});
|
|
16365
|
-
return
|
|
16366
|
-
...turn,
|
|
16367
|
-
blocks,
|
|
16368
|
-
statusLines: blocks.filter((item) => item.kind === "status").map((item) => item.text)
|
|
16369
|
-
};
|
|
16815
|
+
return withBlocks(turn, blocks);
|
|
16370
16816
|
}
|
|
16371
16817
|
function addText(turn, text, onNewLine = false) {
|
|
16372
|
-
const assistantText =
|
|
16818
|
+
const assistantText = tailText(
|
|
16819
|
+
onNewLine ? appendLine(turn.assistantText, text) : turn.assistantText + text,
|
|
16820
|
+
MAX_ACTIVE_ASSISTANT_CHARS
|
|
16821
|
+
);
|
|
16373
16822
|
const blocks = [...turn.blocks];
|
|
16374
16823
|
const last = blocks[blocks.length - 1];
|
|
16375
|
-
if (!onNewLine && last?.kind === "text")
|
|
16376
|
-
|
|
16377
|
-
|
|
16824
|
+
if (!onNewLine && last?.kind === "text") {
|
|
16825
|
+
blocks[blocks.length - 1] = { kind: "text", text: tailText(last.text + text, MAX_TRANSCRIPT_BLOCK_TEXT_CHARS) };
|
|
16826
|
+
} else {
|
|
16827
|
+
blocks.push({ kind: "text", text: tailText(text, MAX_TRANSCRIPT_BLOCK_TEXT_CHARS) });
|
|
16828
|
+
}
|
|
16829
|
+
return withBlocks({ ...turn, assistantText }, blocks);
|
|
16830
|
+
}
|
|
16831
|
+
function boundedTranscriptBlock(block) {
|
|
16832
|
+
const presentation = block.presentation === void 0 ? void 0 : boundedTranscriptPresentation(block.presentation);
|
|
16833
|
+
const tool = block.tool === void 0 ? void 0 : {
|
|
16834
|
+
name: block.tool.name,
|
|
16835
|
+
input: boundedTranscriptValue(block.tool.input),
|
|
16836
|
+
...block.tool.result === void 0 ? {} : {
|
|
16837
|
+
result: {
|
|
16838
|
+
ok: block.tool.result.ok,
|
|
16839
|
+
...block.tool.result.output === void 0 ? {} : { output: boundedTranscriptValue(block.tool.result.output) },
|
|
16840
|
+
...block.tool.result.content === void 0 ? {} : {
|
|
16841
|
+
content: tailText(block.tool.result.content, MAX_TRANSCRIPT_TOOL_VALUE_CHARS)
|
|
16842
|
+
},
|
|
16843
|
+
...block.tool.result.presentation === void 0 ? {} : {
|
|
16844
|
+
presentation: boundedTranscriptPresentation(block.tool.result.presentation)
|
|
16845
|
+
},
|
|
16846
|
+
...block.tool.result.additionalContext === void 0 ? {} : {
|
|
16847
|
+
additionalContext: block.tool.result.additionalContext.slice(-20).map((item) => tailText(item, MAX_TRANSCRIPT_TOOL_VALUE_CHARS))
|
|
16848
|
+
},
|
|
16849
|
+
...block.tool.result.error === void 0 ? {} : { error: {
|
|
16850
|
+
code: block.tool.result.error.code,
|
|
16851
|
+
message: tailText(block.tool.result.error.message, MAX_TRANSCRIPT_BLOCK_TEXT_CHARS)
|
|
16852
|
+
} }
|
|
16853
|
+
}
|
|
16854
|
+
}
|
|
16855
|
+
};
|
|
16856
|
+
return {
|
|
16857
|
+
...block,
|
|
16858
|
+
text: tailText(block.text, MAX_TRANSCRIPT_BLOCK_TEXT_CHARS),
|
|
16859
|
+
...block.hint === void 0 ? {} : { hint: tailText(block.hint, MAX_TRANSCRIPT_BLOCK_TEXT_CHARS) },
|
|
16860
|
+
...block.details === void 0 ? {} : { details: tailText(block.details, MAX_TRANSCRIPT_BLOCK_TEXT_CHARS) },
|
|
16861
|
+
...presentation === void 0 ? {} : { presentation },
|
|
16862
|
+
...tool === void 0 ? {} : { tool }
|
|
16863
|
+
};
|
|
16864
|
+
}
|
|
16865
|
+
function boundedTranscriptPresentation(presentation) {
|
|
16866
|
+
return jsonSize(presentation) <= MAX_TRANSCRIPT_PRESENTATION_CHARS ? presentation : {
|
|
16867
|
+
kind: "generic",
|
|
16868
|
+
title: "Tool output",
|
|
16869
|
+
summary: "Large presentation omitted from the live transcript; the model-facing result remains available in context."
|
|
16870
|
+
};
|
|
16871
|
+
}
|
|
16872
|
+
function boundedTranscriptValue(value) {
|
|
16873
|
+
const chars = jsonSize(value);
|
|
16874
|
+
if (chars <= MAX_TRANSCRIPT_TOOL_VALUE_CHARS) return value;
|
|
16875
|
+
const record3 = typeof value === "object" && value !== null && !Array.isArray(value) ? value : void 0;
|
|
16876
|
+
const summary2 = { truncated: true, originalChars: chars };
|
|
16877
|
+
for (const key of ["path", "command", "id", "name", "exitCode", "bytes", "replacements"]) {
|
|
16878
|
+
const item = record3?.[key];
|
|
16879
|
+
if (typeof item === "string") summary2[key] = tailText(item, 1e3);
|
|
16880
|
+
else if (typeof item === "number" || typeof item === "boolean" || item === null) summary2[key] = item;
|
|
16881
|
+
}
|
|
16882
|
+
return summary2;
|
|
16883
|
+
}
|
|
16884
|
+
function jsonSize(value) {
|
|
16885
|
+
try {
|
|
16886
|
+
return JSON.stringify(value)?.length ?? 0;
|
|
16887
|
+
} catch {
|
|
16888
|
+
return Number.POSITIVE_INFINITY;
|
|
16889
|
+
}
|
|
16890
|
+
}
|
|
16891
|
+
function tailText(text, limit) {
|
|
16892
|
+
if (text.length <= limit) return text;
|
|
16893
|
+
const marker = `\u2026 ${text.length - limit} earlier characters omitted \u2026
|
|
16894
|
+
`;
|
|
16895
|
+
return marker.length >= limit ? text.slice(-limit) : marker + text.slice(-(limit - marker.length));
|
|
16896
|
+
}
|
|
16897
|
+
function transcriptCensus(state) {
|
|
16898
|
+
const turns = [...state.completed, ...state.active === void 0 ? [] : [state.active]];
|
|
16899
|
+
return {
|
|
16900
|
+
turns: turns.length,
|
|
16901
|
+
blocks: turns.reduce((total, turn) => total + turn.blocks.length, 0),
|
|
16902
|
+
chars: jsonSize(state)
|
|
16903
|
+
};
|
|
16378
16904
|
}
|
|
16379
16905
|
|
|
16380
16906
|
// src/session/store.ts
|
|
@@ -16503,7 +17029,7 @@ var SessionStore = class {
|
|
|
16503
17029
|
#maxSessions;
|
|
16504
17030
|
constructor(options) {
|
|
16505
17031
|
this.#workspace = resolve20(options.workspace);
|
|
16506
|
-
this.#sessions =
|
|
17032
|
+
this.#sessions = join25(this.#workspace, ".flavor", "sessions");
|
|
16507
17033
|
this.#maxBytes = options.maxBytes ?? DEFAULT_MAX_SESSION_BYTES;
|
|
16508
17034
|
if (!Number.isSafeInteger(this.#maxBytes) || this.#maxBytes < 256) throw new Error("maxBytes must be an integer of at least 256");
|
|
16509
17035
|
this.#maxSessions = options.maxSessions ?? 50;
|
|
@@ -16535,7 +17061,7 @@ var SessionStore = class {
|
|
|
16535
17061
|
const body = `${lines.join("\n")}
|
|
16536
17062
|
`;
|
|
16537
17063
|
if (Buffer.byteLength(body) > this.#maxBytes) throw new Error(`Session exceeds maximum size of ${this.#maxBytes} bytes`);
|
|
16538
|
-
const temporary =
|
|
17064
|
+
const temporary = join25(this.#sessions, `.${document.sessionId}.${process.pid}.${randomUUID13()}.tmp`);
|
|
16539
17065
|
let handle;
|
|
16540
17066
|
try {
|
|
16541
17067
|
handle = await open7(temporary, constants7.O_CREAT | constants7.O_EXCL | constants7.O_WRONLY, 384);
|
|
@@ -16554,7 +17080,7 @@ var SessionStore = class {
|
|
|
16554
17080
|
async acquireLease(sessionId) {
|
|
16555
17081
|
SessionIdSchema.parse(sessionId);
|
|
16556
17082
|
await this.#prepareDirectory();
|
|
16557
|
-
const path =
|
|
17083
|
+
const path = join25(this.#sessions, `.${sessionId}.lock`);
|
|
16558
17084
|
const token = JSON.stringify({ pid: process.pid, nonce: randomUUID13(), createdAt: (/* @__PURE__ */ new Date()).toISOString() });
|
|
16559
17085
|
for (let attempt = 0; attempt < 3; attempt += 1) {
|
|
16560
17086
|
let handle;
|
|
@@ -16572,7 +17098,7 @@ var SessionStore = class {
|
|
|
16572
17098
|
release: async () => {
|
|
16573
17099
|
if (released) return;
|
|
16574
17100
|
try {
|
|
16575
|
-
if (await
|
|
17101
|
+
if (await readFile18(path, "utf8") === token) await rm8(path, { force: true });
|
|
16576
17102
|
} catch (error) {
|
|
16577
17103
|
if (!isCode8(error, "ENOENT")) throw error;
|
|
16578
17104
|
}
|
|
@@ -16688,14 +17214,14 @@ var SessionStore = class {
|
|
|
16688
17214
|
if (isCode8(error, "ENOENT")) return;
|
|
16689
17215
|
throw error;
|
|
16690
17216
|
}
|
|
16691
|
-
const owner2 = await reclaimStaleSessionLock(
|
|
17217
|
+
const owner2 = await reclaimStaleSessionLock(join25(this.#sessions, `.${sessionId}.lock`));
|
|
16692
17218
|
if (owner2 !== void 0) throw new Error(`Session "${sessionId}" is active in process ${owner2} and cannot be deleted`);
|
|
16693
17219
|
const path = this.#path(sessionId);
|
|
16694
17220
|
try {
|
|
16695
17221
|
const metadata = await lstat8(path);
|
|
16696
17222
|
if (!metadata.isFile()) throw new Error("Session path is not a regular file");
|
|
16697
17223
|
await rm8(path);
|
|
16698
|
-
await rm8(
|
|
17224
|
+
await rm8(join25(this.#sessions, `${sessionId}.events.jsonl`), { force: true }).catch(() => void 0);
|
|
16699
17225
|
} catch (error) {
|
|
16700
17226
|
if (!isCode8(error, "ENOENT")) throw error;
|
|
16701
17227
|
}
|
|
@@ -16711,14 +17237,14 @@ var SessionStore = class {
|
|
|
16711
17237
|
if (entries.length <= this.#maxSessions) return;
|
|
16712
17238
|
let retained = 0;
|
|
16713
17239
|
for (const entry2 of entries) {
|
|
16714
|
-
const owner2 = await reclaimStaleSessionLock(
|
|
17240
|
+
const owner2 = await reclaimStaleSessionLock(join25(this.#sessions, `.${entry2.sessionId}.lock`));
|
|
16715
17241
|
if (owner2 !== void 0) continue;
|
|
16716
17242
|
if (retained < this.#maxSessions) {
|
|
16717
17243
|
retained += 1;
|
|
16718
17244
|
continue;
|
|
16719
17245
|
}
|
|
16720
17246
|
await rm8(this.#path(entry2.sessionId), { force: true }).catch(() => void 0);
|
|
16721
|
-
await rm8(
|
|
17247
|
+
await rm8(join25(this.#sessions, `${entry2.sessionId}.events.jsonl`), { force: true }).catch(() => void 0);
|
|
16722
17248
|
}
|
|
16723
17249
|
}
|
|
16724
17250
|
async #assertWorkspace(stored) {
|
|
@@ -16766,14 +17292,14 @@ function storedSessionVersion(raw) {
|
|
|
16766
17292
|
}
|
|
16767
17293
|
async function reclaimStaleSessionLock(path) {
|
|
16768
17294
|
try {
|
|
16769
|
-
const token = await
|
|
16770
|
-
const metadata = await
|
|
17295
|
+
const token = await readFile18(path, "utf8");
|
|
17296
|
+
const metadata = await stat8(path);
|
|
16771
17297
|
const owner2 = parseSessionLockOwner(token);
|
|
16772
17298
|
if (owner2 !== void 0 && isProcessAlive2(owner2)) return owner2;
|
|
16773
17299
|
if (owner2 === void 0 && Date.now() - metadata.mtimeMs <= INVALID_SESSION_LOCK_STALE_MS) {
|
|
16774
17300
|
throw new Error(`Session writer lock ${path} is incomplete or invalid; retry after the owning process exits`);
|
|
16775
17301
|
}
|
|
16776
|
-
if (await
|
|
17302
|
+
if (await readFile18(path, "utf8") !== token) return void 0;
|
|
16777
17303
|
const stale = `${path}.stale-${process.pid}-${randomUUID13()}`;
|
|
16778
17304
|
try {
|
|
16779
17305
|
await rename6(path, stale);
|
|
@@ -16806,10 +17332,10 @@ function isProcessAlive2(pid) {
|
|
|
16806
17332
|
}
|
|
16807
17333
|
}
|
|
16808
17334
|
async function boundedRead(path, maxBytes) {
|
|
16809
|
-
const metadata = await
|
|
17335
|
+
const metadata = await stat8(path);
|
|
16810
17336
|
if (!metadata.isFile()) throw new Error("Session path is not a regular file");
|
|
16811
17337
|
if (metadata.size > maxBytes) throw new Error(`Session exceeds maximum read size of ${maxBytes} bytes`);
|
|
16812
|
-
const value = await
|
|
17338
|
+
const value = await readFile18(path);
|
|
16813
17339
|
if (value.byteLength > maxBytes) throw new Error(`Session exceeds maximum read size of ${maxBytes} bytes`);
|
|
16814
17340
|
return value.toString("utf8");
|
|
16815
17341
|
}
|
|
@@ -17001,14 +17527,14 @@ function sanitize2(input, seen = /* @__PURE__ */ new WeakSet()) {
|
|
|
17001
17527
|
|
|
17002
17528
|
// src/session/tree.ts
|
|
17003
17529
|
import { randomUUID as randomUUID15 } from "crypto";
|
|
17004
|
-
import { mkdir as mkdir17, readFile as
|
|
17005
|
-
import { dirname as dirname16, join as
|
|
17530
|
+
import { mkdir as mkdir17, readFile as readFile20, rename as rename8, writeFile as writeFile11 } from "fs/promises";
|
|
17531
|
+
import { dirname as dirname16, join as join27, resolve as resolve22 } from "path";
|
|
17006
17532
|
|
|
17007
17533
|
// src/session/checkpoint.ts
|
|
17008
17534
|
import { createHash as createHash15, randomUUID as randomUUID14 } from "crypto";
|
|
17009
17535
|
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
|
|
17536
|
+
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";
|
|
17537
|
+
import { dirname as dirname15, join as join26, relative as relative14, resolve as resolve21, sep as sep14 } from "path";
|
|
17012
17538
|
var EXCLUDED = /* @__PURE__ */ new Set([".git", ".flavor", "node_modules", "dist", "build", "release", ".worktrees"]);
|
|
17013
17539
|
var MAX_FILE_BYTES = 16 * 1024 * 1024;
|
|
17014
17540
|
var MAX_TOTAL_BYTES = 128 * 1024 * 1024;
|
|
@@ -17021,9 +17547,9 @@ var WorkspaceCheckpointStore = class {
|
|
|
17021
17547
|
#maxTotalBytes;
|
|
17022
17548
|
constructor(options) {
|
|
17023
17549
|
this.#workspace = resolve21(options.workspace);
|
|
17024
|
-
this.#root =
|
|
17025
|
-
this.#objects =
|
|
17026
|
-
this.#manifests =
|
|
17550
|
+
this.#root = join26(this.#workspace, ".flavor", "checkpoints");
|
|
17551
|
+
this.#objects = join26(this.#root, "objects");
|
|
17552
|
+
this.#manifests = join26(this.#root, "manifests");
|
|
17027
17553
|
this.#maxFileBytes = options.maxFileBytes ?? MAX_FILE_BYTES;
|
|
17028
17554
|
this.#maxTotalBytes = options.maxTotalBytes ?? MAX_TOTAL_BYTES;
|
|
17029
17555
|
}
|
|
@@ -17041,9 +17567,9 @@ var WorkspaceCheckpointStore = class {
|
|
|
17041
17567
|
if (info.size > this.#maxFileBytes) throw new Error(`Checkpoint file exceeds size limit: ${path}`);
|
|
17042
17568
|
total += info.size;
|
|
17043
17569
|
if (total > this.#maxTotalBytes) throw new Error("Checkpoint exceeds total size limit");
|
|
17044
|
-
const content = await
|
|
17570
|
+
const content = await readFile19(absolute);
|
|
17045
17571
|
const digest = createHash15("sha256").update(content).digest("hex");
|
|
17046
|
-
const objectPath =
|
|
17572
|
+
const objectPath = join26(this.#objects, digest);
|
|
17047
17573
|
try {
|
|
17048
17574
|
const handle = await open8(objectPath, "wx");
|
|
17049
17575
|
try {
|
|
@@ -17063,12 +17589,12 @@ var WorkspaceCheckpointStore = class {
|
|
|
17063
17589
|
...label === void 0 || label.trim().length === 0 ? {} : { label: label.trim() },
|
|
17064
17590
|
files
|
|
17065
17591
|
};
|
|
17066
|
-
await atomicJson(
|
|
17592
|
+
await atomicJson(join26(this.#manifests, `${checkpoint.id}.json`), checkpoint);
|
|
17067
17593
|
return checkpoint;
|
|
17068
17594
|
}
|
|
17069
17595
|
async load(id) {
|
|
17070
17596
|
if (!/^checkpoint-[A-Za-z0-9-]+$/.test(id)) throw new Error("Invalid checkpoint id");
|
|
17071
|
-
const value = JSON.parse(await
|
|
17597
|
+
const value = JSON.parse(await readFile19(join26(this.#manifests, `${id}.json`), "utf8"));
|
|
17072
17598
|
if (value.version !== 1 || value.id !== id || !Array.isArray(value.files)) throw new Error("Invalid checkpoint manifest");
|
|
17073
17599
|
for (const file of value.files) {
|
|
17074
17600
|
contained(this.#workspace, file.path);
|
|
@@ -17080,7 +17606,7 @@ var WorkspaceCheckpointStore = class {
|
|
|
17080
17606
|
const checkpoint = await this.load(id);
|
|
17081
17607
|
const objects = /* @__PURE__ */ new Map();
|
|
17082
17608
|
for (const file of checkpoint.files) {
|
|
17083
|
-
const object = await
|
|
17609
|
+
const object = await readFile19(join26(this.#objects, file.digest));
|
|
17084
17610
|
if (object.length !== file.size || createHash15("sha256").update(object).digest("hex") !== file.digest) {
|
|
17085
17611
|
throw new Error(`Checkpoint object is corrupt: ${file.digest}`);
|
|
17086
17612
|
}
|
|
@@ -17103,7 +17629,7 @@ var WorkspaceCheckpointStore = class {
|
|
|
17103
17629
|
}
|
|
17104
17630
|
};
|
|
17105
17631
|
async function discoverFiles(workspace) {
|
|
17106
|
-
if (await exists2(
|
|
17632
|
+
if (await exists2(join26(workspace, ".git"))) {
|
|
17107
17633
|
const listed = await gitFiles(workspace);
|
|
17108
17634
|
return listed.filter((path) => !EXCLUDED.has(path.split("/")[0] ?? "")).sort();
|
|
17109
17635
|
}
|
|
@@ -17112,7 +17638,7 @@ async function discoverFiles(workspace) {
|
|
|
17112
17638
|
const entries = await readdir7(directory, { withFileTypes: true });
|
|
17113
17639
|
for (const entry2 of entries) {
|
|
17114
17640
|
if (EXCLUDED.has(entry2.name)) continue;
|
|
17115
|
-
const absolute =
|
|
17641
|
+
const absolute = join26(directory, entry2.name);
|
|
17116
17642
|
const path = normalizeRelative(relative14(workspace, absolute));
|
|
17117
17643
|
if (entry2.isSymbolicLink()) throw new Error(`Checkpoint refuses symbolic link: ${path}`);
|
|
17118
17644
|
if (entry2.isDirectory()) await walk(absolute);
|
|
@@ -17187,17 +17713,17 @@ var SessionHistory = class _SessionHistory {
|
|
|
17187
17713
|
#leafId;
|
|
17188
17714
|
#unrevert;
|
|
17189
17715
|
constructor(options, document) {
|
|
17190
|
-
this.#path =
|
|
17716
|
+
this.#path = join27(resolve22(options.workspace), ".flavor", "session-trees", options.sessionId, "tree.json");
|
|
17191
17717
|
this.#checkpoints = new WorkspaceCheckpointStore({ workspace: options.workspace });
|
|
17192
17718
|
this.#restoreContext = options.restoreContext;
|
|
17193
17719
|
this.#nodes = boundedNodes(document.nodes);
|
|
17194
17720
|
this.#leafId = document.leafId !== null && this.#nodes.some((node) => node.id === document.leafId) ? document.leafId : this.#nodes.at(-1)?.id ?? null;
|
|
17195
17721
|
}
|
|
17196
17722
|
static async open(options) {
|
|
17197
|
-
const path =
|
|
17723
|
+
const path = join27(resolve22(options.workspace), ".flavor", "session-trees", options.sessionId, "tree.json");
|
|
17198
17724
|
let document = { version: 1, sessionId: options.sessionId, leafId: null, nodes: [] };
|
|
17199
17725
|
try {
|
|
17200
|
-
const parsed = JSON.parse(await
|
|
17726
|
+
const parsed = JSON.parse(await readFile20(path, "utf8"));
|
|
17201
17727
|
validate(parsed, options.sessionId);
|
|
17202
17728
|
document = parsed;
|
|
17203
17729
|
} catch (error) {
|
|
@@ -17210,6 +17736,10 @@ var SessionHistory = class _SessionHistory {
|
|
|
17210
17736
|
get leafId() {
|
|
17211
17737
|
return this.#leafId;
|
|
17212
17738
|
}
|
|
17739
|
+
/** Cheap retained-node count for the rotation census (no cloning). */
|
|
17740
|
+
get nodeCount() {
|
|
17741
|
+
return this.#nodes.length;
|
|
17742
|
+
}
|
|
17213
17743
|
tree() {
|
|
17214
17744
|
return this.#nodes.map((node) => structuredClone(node));
|
|
17215
17745
|
}
|
|
@@ -17324,35 +17854,70 @@ function isCode10(error, code) {
|
|
|
17324
17854
|
return typeof error === "object" && error !== null && "code" in error && error.code === code;
|
|
17325
17855
|
}
|
|
17326
17856
|
|
|
17327
|
-
// src/utils/
|
|
17328
|
-
import {
|
|
17329
|
-
|
|
17330
|
-
|
|
17331
|
-
var
|
|
17332
|
-
|
|
17333
|
-
|
|
17857
|
+
// src/utils/rotation-census.ts
|
|
17858
|
+
import { mkdirSync as mkdirSync2, readdirSync, unlinkSync as unlinkSync2, writeFileSync } from "fs";
|
|
17859
|
+
import { join as join28 } from "path";
|
|
17860
|
+
import { writeHeapSnapshot } from "v8";
|
|
17861
|
+
var CENSUS_PREFIX = "rotation-census-";
|
|
17862
|
+
var SNAPSHOT_PREFIX = "heap-";
|
|
17863
|
+
var KEEP_CENSUS_FILES = 5;
|
|
17864
|
+
var KEEP_SNAPSHOT_FILES = 2;
|
|
17865
|
+
function censusHeapBlock(reading) {
|
|
17866
|
+
const usage = process.memoryUsage();
|
|
17867
|
+
const mb = (bytes) => Math.round(bytes / 1048576);
|
|
17868
|
+
return {
|
|
17869
|
+
heapUsedMb: mb(reading.heapUsed),
|
|
17870
|
+
heapLimitMb: mb(reading.heapLimit),
|
|
17871
|
+
rawRatio: Number(reading.ratio.toFixed(3)),
|
|
17872
|
+
...reading.verifiedRatio === void 0 ? {} : { verifiedRatio: Number(reading.verifiedRatio.toFixed(3)) },
|
|
17873
|
+
gcVerified: reading.gcVerified,
|
|
17874
|
+
rssMb: mb(usage.rss),
|
|
17875
|
+
externalMb: mb(usage.external),
|
|
17876
|
+
arrayBuffersMb: mb(usage.arrayBuffers),
|
|
17877
|
+
...reading.memoryLimit === void 0 ? {} : { memoryLimitMb: mb(reading.memoryLimit) },
|
|
17878
|
+
...reading.rssRatio === void 0 ? {} : { rssRatio: Number(reading.rssRatio.toFixed(3)) }
|
|
17879
|
+
};
|
|
17334
17880
|
}
|
|
17335
|
-
function
|
|
17336
|
-
|
|
17337
|
-
|
|
17881
|
+
function writeRotationCensus(workspace, census) {
|
|
17882
|
+
try {
|
|
17883
|
+
const directory = join28(workspace, ".flavor", "tmp");
|
|
17884
|
+
mkdirSync2(directory, { recursive: true });
|
|
17885
|
+
const path = join28(directory, `${CENSUS_PREFIX}${Date.now()}.json`);
|
|
17886
|
+
writeFileSync(path, `${JSON.stringify(census, null, 2)}
|
|
17887
|
+
`, { encoding: "utf8", mode: 384 });
|
|
17888
|
+
prune(directory, CENSUS_PREFIX, ".json", KEEP_CENSUS_FILES);
|
|
17889
|
+
return path;
|
|
17890
|
+
} catch {
|
|
17891
|
+
return void 0;
|
|
17892
|
+
}
|
|
17338
17893
|
}
|
|
17339
|
-
function
|
|
17894
|
+
function writeRotationHeapSnapshot(workspace) {
|
|
17340
17895
|
try {
|
|
17341
|
-
const
|
|
17342
|
-
|
|
17343
|
-
|
|
17344
|
-
|
|
17345
|
-
|
|
17896
|
+
const directory = join28(workspace, ".flavor", "tmp");
|
|
17897
|
+
mkdirSync2(directory, { recursive: true });
|
|
17898
|
+
const path = join28(directory, `${SNAPSHOT_PREFIX}${Date.now()}.heapsnapshot`);
|
|
17899
|
+
writeHeapSnapshot(path);
|
|
17900
|
+
prune(directory, SNAPSHOT_PREFIX, ".heapsnapshot", KEEP_SNAPSHOT_FILES);
|
|
17901
|
+
return path;
|
|
17346
17902
|
} catch {
|
|
17347
17903
|
return void 0;
|
|
17348
17904
|
}
|
|
17349
17905
|
}
|
|
17906
|
+
function prune(directory, prefix, suffix, keep) {
|
|
17907
|
+
const stale = readdirSync(directory).filter((name) => name.startsWith(prefix) && name.endsWith(suffix)).sort().slice(0, -keep);
|
|
17908
|
+
for (const name of stale) {
|
|
17909
|
+
try {
|
|
17910
|
+
unlinkSync2(join28(directory, name));
|
|
17911
|
+
} catch {
|
|
17912
|
+
}
|
|
17913
|
+
}
|
|
17914
|
+
}
|
|
17350
17915
|
|
|
17351
17916
|
// src/sleep/organizer.ts
|
|
17352
17917
|
import { constants as constants8 } from "fs";
|
|
17353
17918
|
import { randomUUID as randomUUID16 } from "crypto";
|
|
17354
17919
|
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
|
|
17920
|
+
import { isAbsolute as isAbsolute11, join as join29, relative as relative15, resolve as resolve23, sep as sep15 } from "path";
|
|
17356
17921
|
import { z as z28 } from "zod";
|
|
17357
17922
|
var DateKeySchema = z28.string().regex(/^\d{4}-\d{2}-\d{2}$/);
|
|
17358
17923
|
var ReviewSchema = z28.object({
|
|
@@ -17407,7 +17972,7 @@ var ProjectSleepOrganizer = class {
|
|
|
17407
17972
|
#generate;
|
|
17408
17973
|
constructor(options) {
|
|
17409
17974
|
this.#workspace = resolve23(options.workspace);
|
|
17410
|
-
this.#sleepDirectory =
|
|
17975
|
+
this.#sleepDirectory = join29(this.#workspace, ".flavor", "sleep");
|
|
17411
17976
|
this.#sessions = options.sessions ?? new SessionStore({ workspace: this.#workspace });
|
|
17412
17977
|
this.#generate = options.generate;
|
|
17413
17978
|
}
|
|
@@ -17416,7 +17981,7 @@ var ProjectSleepOrganizer = class {
|
|
|
17416
17981
|
signal.throwIfAborted();
|
|
17417
17982
|
await this.#prepareDirectory();
|
|
17418
17983
|
if (await this.#hasReport(date)) return { status: "exists", date };
|
|
17419
|
-
const lockPath =
|
|
17984
|
+
const lockPath = join29(this.#sleepDirectory, `.${date}.lock`);
|
|
17420
17985
|
let lock;
|
|
17421
17986
|
try {
|
|
17422
17987
|
lock = await open9(lockPath, constants8.O_CREAT | constants8.O_EXCL | constants8.O_WRONLY, 384);
|
|
@@ -17451,7 +18016,7 @@ Previous output was invalid. Repair it and return strict JSON only.`;
|
|
|
17451
18016
|
}
|
|
17452
18017
|
if (review === void 0) throw lastError;
|
|
17453
18018
|
const filename2 = `${date}-${filenameSummary(review.title)}.md`;
|
|
17454
|
-
const path =
|
|
18019
|
+
const path = join29(this.#sleepDirectory, filename2);
|
|
17455
18020
|
const markdown = renderSleepReport(date, review, documents);
|
|
17456
18021
|
await this.#atomicWrite(path, markdown);
|
|
17457
18022
|
return { status: "written", date, path, sessionCount: documents.length };
|
|
@@ -17493,7 +18058,7 @@ Previous output was invalid. Repair it and return strict JSON only.`;
|
|
|
17493
18058
|
});
|
|
17494
18059
|
}
|
|
17495
18060
|
async #prepareDirectory() {
|
|
17496
|
-
const flavorDirectory =
|
|
18061
|
+
const flavorDirectory = join29(this.#workspace, ".flavor");
|
|
17497
18062
|
await assertNotSymlink(flavorDirectory);
|
|
17498
18063
|
await mkdir18(flavorDirectory, { recursive: true, mode: 448 });
|
|
17499
18064
|
await assertNotSymlink(this.#sleepDirectory);
|
|
@@ -17507,7 +18072,7 @@ Previous output was invalid. Repair it and return strict JSON only.`;
|
|
|
17507
18072
|
return (await readdir8(this.#sleepDirectory, { withFileTypes: true })).some((entry2) => entry2.isFile() && entry2.name.startsWith(prefix) && entry2.name.endsWith(".md"));
|
|
17508
18073
|
}
|
|
17509
18074
|
async #atomicWrite(path, content) {
|
|
17510
|
-
const temporary =
|
|
18075
|
+
const temporary = join29(this.#sleepDirectory, `.${randomUUID16()}.tmp`);
|
|
17511
18076
|
let handle;
|
|
17512
18077
|
try {
|
|
17513
18078
|
handle = await open9(temporary, constants8.O_CREAT | constants8.O_EXCL | constants8.O_WRONLY, 384);
|
|
@@ -17881,7 +18446,7 @@ function computeSessionStats(documents) {
|
|
|
17881
18446
|
|
|
17882
18447
|
// src/tools/files.ts
|
|
17883
18448
|
import { constants as constants9 } from "fs";
|
|
17884
|
-
import { mkdir as mkdir19, open as open10, readFile as
|
|
18449
|
+
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
18450
|
import { dirname as dirname17, isAbsolute as isAbsolute12, relative as relative16, resolve as resolve24, sep as sep16 } from "path";
|
|
17886
18451
|
import { randomUUID as randomUUID17 } from "crypto";
|
|
17887
18452
|
import { z as z29 } from "zod";
|
|
@@ -18034,7 +18599,7 @@ function createReadTool(workspace, options = {}) {
|
|
|
18034
18599
|
execute: async (input, signal) => {
|
|
18035
18600
|
abortIfNeeded(signal);
|
|
18036
18601
|
const path = await guard.existing(input.path);
|
|
18037
|
-
const info = await
|
|
18602
|
+
const info = await stat9(path);
|
|
18038
18603
|
const maxBytes = input.maxBytes ?? MAX_READ_BYTES;
|
|
18039
18604
|
if (!Number.isSafeInteger(maxBytes) || maxBytes <= 0 || maxBytes > MAX_READ_BYTES) {
|
|
18040
18605
|
throw new Error(`maxBytes must be a positive integer no greater than ${MAX_READ_BYTES}`);
|
|
@@ -18243,7 +18808,7 @@ async function atomicWrite(path, content, signal, expectedVersion) {
|
|
|
18243
18808
|
const temporary = `${path}.${randomUUID17()}.tmp`;
|
|
18244
18809
|
let mode;
|
|
18245
18810
|
try {
|
|
18246
|
-
mode = (await
|
|
18811
|
+
mode = (await stat9(path)).mode & 511;
|
|
18247
18812
|
} catch (error) {
|
|
18248
18813
|
if (!isMissing2(error)) throw error;
|
|
18249
18814
|
}
|
|
@@ -18266,7 +18831,7 @@ async function atomicWrite(path, content, signal, expectedVersion) {
|
|
|
18266
18831
|
}
|
|
18267
18832
|
}
|
|
18268
18833
|
async function readText(path) {
|
|
18269
|
-
const contents = await
|
|
18834
|
+
const contents = await readFile21(path);
|
|
18270
18835
|
if (isBinary(contents)) throw new Error("Cannot edit binary file as text");
|
|
18271
18836
|
return decodeFileText(contents);
|
|
18272
18837
|
}
|
|
@@ -18292,7 +18857,7 @@ async function assertMutationVersion(path, expected, observations) {
|
|
|
18292
18857
|
}
|
|
18293
18858
|
async function fileVersion(path) {
|
|
18294
18859
|
try {
|
|
18295
|
-
return versionOfStat(await
|
|
18860
|
+
return versionOfStat(await stat9(path));
|
|
18296
18861
|
} catch (error) {
|
|
18297
18862
|
if (isMissing2(error)) return ABSENT_VERSION;
|
|
18298
18863
|
throw error;
|
|
@@ -18303,7 +18868,7 @@ function versionOfStat(info) {
|
|
|
18303
18868
|
}
|
|
18304
18869
|
async function readOptionalPresentationText(path) {
|
|
18305
18870
|
try {
|
|
18306
|
-
const contents = await
|
|
18871
|
+
const contents = await readFile21(path);
|
|
18307
18872
|
return isBinary(contents) ? { exists: true } : { exists: true, text: contents.toString("utf8") };
|
|
18308
18873
|
} catch (error) {
|
|
18309
18874
|
return isMissing2(error) ? { exists: false } : { exists: true };
|
|
@@ -18614,7 +19179,7 @@ function matchesLines(source, start, expected) {
|
|
|
18614
19179
|
|
|
18615
19180
|
// src/tools/search.ts
|
|
18616
19181
|
import { spawn as spawn5 } from "child_process";
|
|
18617
|
-
import { open as open11, opendir, stat as
|
|
19182
|
+
import { open as open11, opendir, stat as stat10 } from "fs/promises";
|
|
18618
19183
|
import { isAbsolute as isAbsolute13, relative as relative17, resolve as resolve25, sep as sep17 } from "path";
|
|
18619
19184
|
import { rgPath as bundledRgPath } from "@vscode/ripgrep";
|
|
18620
19185
|
import createIgnore from "ignore";
|
|
@@ -18962,7 +19527,7 @@ async function nodeFiles(root, start, signal, accept, limit, resources, ignoreBu
|
|
|
18962
19527
|
}
|
|
18963
19528
|
ignores.length = ruleCount;
|
|
18964
19529
|
}
|
|
18965
|
-
if ((await
|
|
19530
|
+
if ((await stat10(start)).isFile()) {
|
|
18966
19531
|
const path = normalizedRelative(root, start);
|
|
18967
19532
|
if (!isGitMetadataPath(root, start) && (includeIgnored || !ignored(path, false, ignores)) && accept(path)) output.push(path);
|
|
18968
19533
|
} else {
|
|
@@ -18997,7 +19562,7 @@ async function collectIgnoreLayers(root, start, signal, resources, ignoreBudget)
|
|
|
18997
19562
|
if (!ignored(path, true, layers)) await walk(absolute);
|
|
18998
19563
|
}
|
|
18999
19564
|
}
|
|
19000
|
-
if (!(await
|
|
19565
|
+
if (!(await stat10(start)).isFile()) await walk(start);
|
|
19001
19566
|
return layers;
|
|
19002
19567
|
}
|
|
19003
19568
|
async function loadIgnoreFiles(directory, base, rules, budget, signal) {
|
|
@@ -19116,7 +19681,7 @@ function scope(root, path = ".") {
|
|
|
19116
19681
|
async function resolveGrepPath(root, path = ".") {
|
|
19117
19682
|
const candidate = scope(root, path);
|
|
19118
19683
|
try {
|
|
19119
|
-
const info = await
|
|
19684
|
+
const info = await stat10(candidate);
|
|
19120
19685
|
if (!info.isFile() && !info.isDirectory()) throw new Error("Search path must be a file or directory");
|
|
19121
19686
|
} catch (error) {
|
|
19122
19687
|
if (error.code === "ENOENT") {
|
|
@@ -19320,19 +19885,6 @@ import { isIP } from "net";
|
|
|
19320
19885
|
import { request as httpRequest } from "http";
|
|
19321
19886
|
import { request as httpsRequest } from "https";
|
|
19322
19887
|
import { z as z31 } from "zod";
|
|
19323
|
-
|
|
19324
|
-
// src/utils/abort.ts
|
|
19325
|
-
function waitWithSignal(operation, signal) {
|
|
19326
|
-
if (signal === void 0) return operation;
|
|
19327
|
-
return new Promise((resolve44, reject) => {
|
|
19328
|
-
const abort2 = () => reject(signal.reason);
|
|
19329
|
-
if (signal.aborted) abort2();
|
|
19330
|
-
else signal.addEventListener("abort", abort2, { once: true });
|
|
19331
|
-
operation.then(resolve44, reject).finally(() => signal.removeEventListener("abort", abort2));
|
|
19332
|
-
});
|
|
19333
|
-
}
|
|
19334
|
-
|
|
19335
|
-
// src/tools/web.ts
|
|
19336
19888
|
var WebFetchInput = z31.object({
|
|
19337
19889
|
url: z31.string().url().max(8192),
|
|
19338
19890
|
timeoutMs: z31.coerce.number().int().positive().max(12e4).optional(),
|
|
@@ -19657,7 +20209,7 @@ function charset(contentType) {
|
|
|
19657
20209
|
// src/tools/lsp.ts
|
|
19658
20210
|
import { spawn as spawn6 } from "child_process";
|
|
19659
20211
|
import { existsSync as existsSync5 } from "fs";
|
|
19660
|
-
import { access as access2, constants as constants10, readFile as
|
|
20212
|
+
import { access as access2, constants as constants10, readFile as readFile22 } from "fs/promises";
|
|
19661
20213
|
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
20214
|
import { fileURLToPath as fileURLToPath2, pathToFileURL as pathToFileURL3 } from "url";
|
|
19663
20215
|
import { z as z32 } from "zod";
|
|
@@ -19851,7 +20403,7 @@ var LspConnection = class {
|
|
|
19851
20403
|
const url = new URL(uri);
|
|
19852
20404
|
if (url.protocol !== "file:") return;
|
|
19853
20405
|
const filePath = fileURLToPath2(url);
|
|
19854
|
-
const text = await
|
|
20406
|
+
const text = await readFile22(filePath, "utf8");
|
|
19855
20407
|
const current = this.#documents.get(uri);
|
|
19856
20408
|
if (current === void 0) {
|
|
19857
20409
|
this.#documents.set(uri, { text, version: 1 });
|
|
@@ -20217,19 +20769,19 @@ async function changeSummary(workspace) {
|
|
|
20217
20769
|
};
|
|
20218
20770
|
}
|
|
20219
20771
|
async function stagedDiff(workspace, maxChars = 96e3) {
|
|
20220
|
-
const
|
|
20772
|
+
const stat21 = await git2(workspace, ["diff", "--staged", "--stat"]);
|
|
20221
20773
|
const diff = await git2(workspace, ["diff", "--staged", "--no-ext-diff", "--no-color"]);
|
|
20222
20774
|
if (!diff.ok) throw gitFailure(diff, "diff --staged");
|
|
20223
20775
|
const text = diff.stdout;
|
|
20224
20776
|
return {
|
|
20225
20777
|
diff: text.length > maxChars ? `${text.slice(0, maxChars)}
|
|
20226
20778
|
\u2026[diff truncated]` : text,
|
|
20227
|
-
stat:
|
|
20779
|
+
stat: stat21.stdout.trim(),
|
|
20228
20780
|
truncated: text.length > maxChars
|
|
20229
20781
|
};
|
|
20230
20782
|
}
|
|
20231
20783
|
async function uncommittedDiff(workspace, maxChars = 96e3) {
|
|
20232
|
-
const
|
|
20784
|
+
const stat21 = await git2(workspace, ["diff", "HEAD", "--stat"]);
|
|
20233
20785
|
const diff = await git2(workspace, ["diff", "HEAD", "--no-ext-diff", "--no-color"]);
|
|
20234
20786
|
if (!diff.ok && !diff.stderr.includes("unknown revision")) throw gitFailure(diff, "diff HEAD");
|
|
20235
20787
|
const summary2 = await changeSummary(workspace);
|
|
@@ -20237,7 +20789,7 @@ async function uncommittedDiff(workspace, maxChars = 96e3) {
|
|
|
20237
20789
|
return {
|
|
20238
20790
|
diff: text.length > maxChars ? `${text.slice(0, maxChars)}
|
|
20239
20791
|
\u2026[diff truncated]` : text,
|
|
20240
|
-
stat:
|
|
20792
|
+
stat: stat21.stdout.trim(),
|
|
20241
20793
|
untracked: summary2.untrackedFiles
|
|
20242
20794
|
};
|
|
20243
20795
|
}
|
|
@@ -20534,17 +21086,17 @@ ${explanation}` : "";
|
|
|
20534
21086
|
}
|
|
20535
21087
|
|
|
20536
21088
|
// src/desktop/astgraph-service.ts
|
|
20537
|
-
import { stat as
|
|
21089
|
+
import { stat as stat11 } from "fs/promises";
|
|
20538
21090
|
import { createRequire as nodeCreateRequire } from "module";
|
|
20539
|
-
import { join as
|
|
21091
|
+
import { join as join30, resolve as resolve27 } from "path";
|
|
20540
21092
|
var FIELDS = "id, kind, name, qualified_name, file_path, language, start_line, end_line, signature";
|
|
20541
21093
|
var DesktopAstGraphService = class {
|
|
20542
21094
|
#path;
|
|
20543
21095
|
constructor(workspace) {
|
|
20544
|
-
this.#path =
|
|
21096
|
+
this.#path = join30(resolve27(workspace), ".flavor", "astgraph", "index.db");
|
|
20545
21097
|
}
|
|
20546
21098
|
async status() {
|
|
20547
|
-
if ((await
|
|
21099
|
+
if ((await stat11(this.#path).catch(() => void 0))?.isFile() !== true) {
|
|
20548
21100
|
return { available: false, path: this.#path, files: 0, nodes: 0, edges: 0 };
|
|
20549
21101
|
}
|
|
20550
21102
|
return this.#read((db) => {
|
|
@@ -20824,8 +21376,8 @@ function createTodoWriteTool() {
|
|
|
20824
21376
|
|
|
20825
21377
|
// src/tools/managed.ts
|
|
20826
21378
|
import { randomUUID as randomUUID18 } from "crypto";
|
|
20827
|
-
import { link, lstat as lstat11, mkdir as mkdir20, readFile as
|
|
20828
|
-
import { dirname as dirname19, join as
|
|
21379
|
+
import { link, lstat as lstat11, mkdir as mkdir20, readFile as readFile23, readdir as readdir9, unlink as unlink3, writeFile as writeFile12 } from "fs/promises";
|
|
21380
|
+
import { dirname as dirname19, join as join31, resolve as resolve29 } from "path";
|
|
20829
21381
|
import { z as z37 } from "zod";
|
|
20830
21382
|
var MANAGED_TOOL_VERSION = 1;
|
|
20831
21383
|
var MAX_RECORD_BYTES = 256 * 1024;
|
|
@@ -20868,8 +21420,8 @@ var ManagedToolStore = class {
|
|
|
20868
21420
|
#diagnostics = [];
|
|
20869
21421
|
constructor(options) {
|
|
20870
21422
|
this.#workspace = resolve29(options.workspace);
|
|
20871
|
-
this.#projectRoot =
|
|
20872
|
-
this.#globalRoot =
|
|
21423
|
+
this.#projectRoot = join31(this.#workspace, ".flavor", "tools");
|
|
21424
|
+
this.#globalRoot = join31(resolve29(options.home), ".flavor-code", "tools");
|
|
20873
21425
|
this.#now = options.now ?? (() => (/* @__PURE__ */ new Date()).toISOString());
|
|
20874
21426
|
}
|
|
20875
21427
|
get diagnostics() {
|
|
@@ -20916,7 +21468,7 @@ var ManagedToolStore = class {
|
|
|
20916
21468
|
version: MANAGED_TOOL_VERSION,
|
|
20917
21469
|
createdAt: this.#now()
|
|
20918
21470
|
});
|
|
20919
|
-
const path =
|
|
21471
|
+
const path = join31(this.directory(input.scope), filename(input.name));
|
|
20920
21472
|
const definition = compileDefinition(record3, input.scope, this.#workspace);
|
|
20921
21473
|
await writeExclusive(path, `${JSON.stringify(record3, void 0, 2)}
|
|
20922
21474
|
`);
|
|
@@ -20941,7 +21493,7 @@ var ManagedToolStore = class {
|
|
|
20941
21493
|
if (!parsed.success) return [];
|
|
20942
21494
|
const matches = this.#matching(parsed.data.name).filter((entry2) => parsed.data.scope === void 0 || entry2.scope === parsed.data.scope);
|
|
20943
21495
|
if (matches.length > 0) return matches.map((entry2) => entry2.path);
|
|
20944
|
-
return [
|
|
21496
|
+
return [join31(this.directory(parsed.data.scope ?? "project"), filename(parsed.data.name))];
|
|
20945
21497
|
}
|
|
20946
21498
|
async #loadDirectory(scope2) {
|
|
20947
21499
|
const root = this.directory(scope2);
|
|
@@ -20954,12 +21506,12 @@ var ManagedToolStore = class {
|
|
|
20954
21506
|
return;
|
|
20955
21507
|
}
|
|
20956
21508
|
for (const name of names) {
|
|
20957
|
-
const path =
|
|
21509
|
+
const path = join31(root, name);
|
|
20958
21510
|
try {
|
|
20959
21511
|
const info = await lstat11(path);
|
|
20960
21512
|
if (!info.isFile() || info.isSymbolicLink()) throw new Error("Managed tool record must be a regular file");
|
|
20961
21513
|
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
|
|
21514
|
+
const record3 = ManagedToolRecordSchema.parse(JSON.parse(await readFile23(path, "utf8")));
|
|
20963
21515
|
if (record3.scope !== scope2) throw new Error(`Managed tool record scope must match its ${scope2} directory`);
|
|
20964
21516
|
const key = scopedKey(scope2, record3.name);
|
|
20965
21517
|
if (this.#entries.has(key)) throw new Error(`Duplicate managed tool name in ${scope2} scope: ${record3.name}`);
|
|
@@ -21845,6 +22397,11 @@ var HELP = [
|
|
|
21845
22397
|
"/co-work <pal> <goal> | status [id] | cancel <id> [reason]"
|
|
21846
22398
|
].join("\n");
|
|
21847
22399
|
var MAX_STOP_DENIALS = 2;
|
|
22400
|
+
var RESUMABLE_TURN_BOUNDARIES = /* @__PURE__ */ new Set([
|
|
22401
|
+
"iteration_limit",
|
|
22402
|
+
"output_limit",
|
|
22403
|
+
"context_overflow"
|
|
22404
|
+
]);
|
|
21848
22405
|
var FlavorSession = class {
|
|
21849
22406
|
#services;
|
|
21850
22407
|
#active;
|
|
@@ -21868,6 +22425,7 @@ var FlavorSession = class {
|
|
|
21868
22425
|
#pendingCoWorkStarts = [];
|
|
21869
22426
|
#activeSteeringDurableIds = /* @__PURE__ */ new Set();
|
|
21870
22427
|
#durableResumeStarted = false;
|
|
22428
|
+
#continuationResumeStarted = false;
|
|
21871
22429
|
#permissionBaseline;
|
|
21872
22430
|
#activeSubmission;
|
|
21873
22431
|
constructor(services) {
|
|
@@ -21888,6 +22446,10 @@ var FlavorSession = class {
|
|
|
21888
22446
|
queueSnapshot() {
|
|
21889
22447
|
return this.#queue.snapshot();
|
|
21890
22448
|
}
|
|
22449
|
+
/** True when start() admitted a persisted /loop or /goal continuation. */
|
|
22450
|
+
get rotationContinuationResumed() {
|
|
22451
|
+
return this.#continuationResumeStarted;
|
|
22452
|
+
}
|
|
21891
22453
|
clearQueue() {
|
|
21892
22454
|
const snapshot4 = this.#queue.clear();
|
|
21893
22455
|
for (const kind of ["steer", "followUp"]) {
|
|
@@ -22037,6 +22599,7 @@ var FlavorSession = class {
|
|
|
22037
22599
|
if (decision.additionalContext !== void 0) await this.#services.addContext?.(decision.additionalContext);
|
|
22038
22600
|
this.#started = true;
|
|
22039
22601
|
this.#resumeDurableQueue();
|
|
22602
|
+
this.#resumeRotationContinuation();
|
|
22040
22603
|
});
|
|
22041
22604
|
return this.#startPromise;
|
|
22042
22605
|
}
|
|
@@ -22070,6 +22633,7 @@ var FlavorSession = class {
|
|
|
22070
22633
|
if (!initial) this.#outputQueuedSubmission(submission);
|
|
22071
22634
|
initial = false;
|
|
22072
22635
|
await this.#runSubmission(submission);
|
|
22636
|
+
if (this.#services.rotationPending?.() === true) return;
|
|
22073
22637
|
pending2.push(...this.#drainMessages("steer"), ...this.#drainMessages("followUp"));
|
|
22074
22638
|
}
|
|
22075
22639
|
}
|
|
@@ -22093,6 +22657,7 @@ var FlavorSession = class {
|
|
|
22093
22657
|
this.#activeSubmission = submission;
|
|
22094
22658
|
this.#interrupted = false;
|
|
22095
22659
|
let outcome = "completed";
|
|
22660
|
+
let resumableBoundary;
|
|
22096
22661
|
let assistantSummary = "";
|
|
22097
22662
|
let deliverables = [];
|
|
22098
22663
|
try {
|
|
@@ -22119,7 +22684,8 @@ var FlavorSession = class {
|
|
|
22119
22684
|
skillNames,
|
|
22120
22685
|
this.#services.managedToolCommands().map(({ name }) => name)
|
|
22121
22686
|
);
|
|
22122
|
-
if (
|
|
22687
|
+
if (submission.continuation !== void 0) await this.#runContinuation(submission.continuation, controller2.signal);
|
|
22688
|
+
else if (command !== null) await this.#dispatch(command, controller2.signal);
|
|
22123
22689
|
else for await (const event of this.#services.run(prompt, controller2.signal, {
|
|
22124
22690
|
getSteeringMessages: () => this.#drainMessages("steer", true).map((item) => item.text),
|
|
22125
22691
|
...decision.additionalContext === void 0 ? {} : { additionalContext: decision.additionalContext },
|
|
@@ -22128,7 +22694,10 @@ var FlavorSession = class {
|
|
|
22128
22694
|
this.#services.output(event);
|
|
22129
22695
|
if (event.type === "text") assistantSummary = `${assistantSummary}${event.text}`.slice(-2e3);
|
|
22130
22696
|
if (event.type === "deliverables") deliverables = event.files.slice(0, 100);
|
|
22131
|
-
if (event.type === "error")
|
|
22697
|
+
if (event.type === "error") {
|
|
22698
|
+
outcome = "failed";
|
|
22699
|
+
if (RESUMABLE_TURN_BOUNDARIES.has(event.error.code)) resumableBoundary = event.error.code;
|
|
22700
|
+
}
|
|
22132
22701
|
}
|
|
22133
22702
|
if (controller2.signal.aborted) outcome = "cancelled";
|
|
22134
22703
|
} catch (error) {
|
|
@@ -22169,6 +22738,13 @@ var FlavorSession = class {
|
|
|
22169
22738
|
}
|
|
22170
22739
|
}
|
|
22171
22740
|
try {
|
|
22741
|
+
if (resumableBoundary !== void 0 && !this.#closed && !controller2.signal.aborted) {
|
|
22742
|
+
const attempt = (submission.iterationContinuation ?? 0) + 1;
|
|
22743
|
+
const text = `[automatic continuation after ${resumableBoundary} boundary; segment ${attempt}]
|
|
22744
|
+
Continue the unfinished task from the current conversation and workspace state. Do not repeat completed operations. Inspect existing results, finish the remaining work, and verify it before stopping.`;
|
|
22745
|
+
this.#enqueueMessage("followUp", { text, displayText: text, iterationContinuation: attempt });
|
|
22746
|
+
this.#notice(`Agent ${resumableBoundary} boundary reached; durable continuation ${attempt} queued.`);
|
|
22747
|
+
}
|
|
22172
22748
|
if (stopDecision?.decision === "deny" && typeof stopDecision.reason === "string" && stopDecision.reason.length > 0 && !this.#closed && outcome !== "cancelled" && outcome !== "denied") {
|
|
22173
22749
|
if (this.#stopDenials < MAX_STOP_DENIALS) {
|
|
22174
22750
|
this.#stopDenials += 1;
|
|
@@ -22365,7 +22941,7 @@ Address this now (for example run the required verification or fix), then finish
|
|
|
22365
22941
|
});
|
|
22366
22942
|
}
|
|
22367
22943
|
#resumeDurableQueue() {
|
|
22368
|
-
if (this.#durableResumeStarted || this.#closed || this.active || this.#pendingSubmissions > 0) return;
|
|
22944
|
+
if (this.#durableResumeStarted || this.#closed || this.active || this.#pendingSubmissions > 0 || this.#services.rotationPending?.() === true) return;
|
|
22369
22945
|
if (!this.#queue.hasPending) return;
|
|
22370
22946
|
this.#durableResumeStarted = true;
|
|
22371
22947
|
const submissions = [...this.#drainMessages("steer"), ...this.#drainMessages("followUp")];
|
|
@@ -22377,6 +22953,33 @@ Address this now (for example run the required verification or fix), then finish
|
|
|
22377
22953
|
});
|
|
22378
22954
|
}
|
|
22379
22955
|
}
|
|
22956
|
+
/**
|
|
22957
|
+
* After a heap rotation the relaunched process inherits the same session and
|
|
22958
|
+
* a marker continuation naming the /loop or /goal that was in flight. Resume
|
|
22959
|
+
* it once here so a multi-day autonomous run survives the rotation instead of
|
|
22960
|
+
* silently stopping at the boundary.
|
|
22961
|
+
*/
|
|
22962
|
+
#resumeRotationContinuation() {
|
|
22963
|
+
if (this.#continuationResumeStarted || this.#closed) return;
|
|
22964
|
+
const continuation = this.#services.continuation?.();
|
|
22965
|
+
if (continuation === void 0) return;
|
|
22966
|
+
const supported = continuation.kind === "loop" ? this.#services.resumeLoop !== void 0 : this.#services.resumeGoal !== void 0;
|
|
22967
|
+
if (!supported) return;
|
|
22968
|
+
this.#continuationResumeStarted = true;
|
|
22969
|
+
const displayText = `Resuming the interrupted ${continuation.kind} ${continuation.id} after a heap rotation.`;
|
|
22970
|
+
this.#notice(displayText);
|
|
22971
|
+
this.#enqueueNormalizedSubmission({ text: displayText, displayText, continuation }).catch((error) => {
|
|
22972
|
+
this.#services.output({ type: "error", error: { code: "unknown", message: message(error) } });
|
|
22973
|
+
});
|
|
22974
|
+
}
|
|
22975
|
+
async #runContinuation(continuation, signal) {
|
|
22976
|
+
const stream = continuation.kind === "loop" ? this.#services.resumeLoop?.(continuation.id, signal) : this.#services.resumeGoal?.(continuation.id, signal);
|
|
22977
|
+
if (stream === void 0) {
|
|
22978
|
+
this.#notice(`The runtime cannot resume the interrupted ${continuation.kind} ${continuation.id}; start it again with /${continuation.kind}.`);
|
|
22979
|
+
return;
|
|
22980
|
+
}
|
|
22981
|
+
for await (const event of stream) this.#services.output(event);
|
|
22982
|
+
}
|
|
22380
22983
|
#ackDurable(submission) {
|
|
22381
22984
|
if (submission.durableId === void 0) return;
|
|
22382
22985
|
this.#services.durableQueue?.ack(submission.durableId);
|
|
@@ -22489,6 +23092,7 @@ function recoveredSubmission(value) {
|
|
|
22489
23092
|
if (typeof input.text !== "string" || input.text.trim().length === 0) return void 0;
|
|
22490
23093
|
if (typeof input.displayText !== "string") return void 0;
|
|
22491
23094
|
if (input.controlOnly !== void 0 && typeof input.controlOnly !== "boolean") return void 0;
|
|
23095
|
+
if (input.iterationContinuation !== void 0 && (!Number.isSafeInteger(input.iterationContinuation) || input.iterationContinuation < 1)) return void 0;
|
|
22492
23096
|
if (input.coWorkPlanningKey !== void 0 && typeof input.coWorkPlanningKey !== "string") return void 0;
|
|
22493
23097
|
if (input.remoteOrigin !== void 0) {
|
|
22494
23098
|
const origin = input.remoteOrigin;
|
|
@@ -22534,9 +23138,9 @@ function requiredPals(service) {
|
|
|
22534
23138
|
|
|
22535
23139
|
// src/doctor.ts
|
|
22536
23140
|
import { constants as constants11 } from "fs";
|
|
22537
|
-
import { access as access3, lstat as lstat12, readFile as
|
|
23141
|
+
import { access as access3, lstat as lstat12, readFile as readFile24, readdir as readdir10, stat as stat12 } from "fs/promises";
|
|
22538
23142
|
import { homedir } from "os";
|
|
22539
|
-
import { isAbsolute as isAbsolute16, join as
|
|
23143
|
+
import { isAbsolute as isAbsolute16, join as join32, relative as relative20, resolve as resolve30, sep as sep20 } from "path";
|
|
22540
23144
|
import { rgPath as bundledRgPath2 } from "@vscode/ripgrep";
|
|
22541
23145
|
|
|
22542
23146
|
// src/utils/semver.ts
|
|
@@ -22583,7 +23187,7 @@ async function runDoctor(options = {}, dependencies2 = {}) {
|
|
|
22583
23187
|
const checks = [];
|
|
22584
23188
|
checks.push(runtimeCheck(nodeVersion, platform, arch));
|
|
22585
23189
|
checks.push(await directoryCheck("workspace", workspace, false));
|
|
22586
|
-
checks.push(await directoryCheck("state directory",
|
|
23190
|
+
checks.push(await directoryCheck("state directory", join32(home, ".flavor-code"), true, home));
|
|
22587
23191
|
let loaded;
|
|
22588
23192
|
try {
|
|
22589
23193
|
loaded = await (dependencies2.loadConfig ?? loadConfig)({
|
|
@@ -22609,8 +23213,8 @@ async function runDoctor(options = {}, dependencies2 = {}) {
|
|
|
22609
23213
|
checks.push({ name: "shell", status: "fail", message: errorMessage3(error) });
|
|
22610
23214
|
}
|
|
22611
23215
|
checks.push(await pluginCheck([
|
|
22612
|
-
|
|
22613
|
-
|
|
23216
|
+
join32(home, ".flavor-code", "plugins"),
|
|
23217
|
+
join32(workspace, ".flavor", "plugins")
|
|
22614
23218
|
]));
|
|
22615
23219
|
try {
|
|
22616
23220
|
const latest = await (dependencies2.fetchLatestVersion ?? fetchLatestVersion)({
|
|
@@ -22658,7 +23262,7 @@ function runtimeCheck(version, platform, arch) {
|
|
|
22658
23262
|
}
|
|
22659
23263
|
async function directoryCheck(name, path, mayBeCreated, parent = path) {
|
|
22660
23264
|
try {
|
|
22661
|
-
const info = await
|
|
23265
|
+
const info = await stat12(path);
|
|
22662
23266
|
if (!info.isDirectory()) return { name, status: "fail", message: `${path} is not a directory` };
|
|
22663
23267
|
await access3(path, constants11.R_OK | constants11.W_OK);
|
|
22664
23268
|
return { name, status: "pass", message: `${path} is readable and writable` };
|
|
@@ -22727,7 +23331,7 @@ async function pluginCheck(directories) {
|
|
|
22727
23331
|
try {
|
|
22728
23332
|
const rootInfo = await lstat12(root);
|
|
22729
23333
|
if (rootInfo.isSymbolicLink()) throw new Error("plugin directory must not be a symbolic link");
|
|
22730
|
-
const manifest = PluginManifestSchema.parse(JSON.parse(await
|
|
23334
|
+
const manifest = PluginManifestSchema.parse(JSON.parse(await readFile24(join32(root, "flavor-plugin.json"), "utf8")));
|
|
22731
23335
|
const main = resolve30(root, manifest.main);
|
|
22732
23336
|
const delta = relative20(root, main);
|
|
22733
23337
|
if (delta === ".." || delta.startsWith(`..${sep20}`) || isAbsolute16(delta)) throw new Error("plugin entry escapes its root");
|
|
@@ -22794,8 +23398,8 @@ var LANG_LABELS = /* @__PURE__ */ new Map([
|
|
|
22794
23398
|
]);
|
|
22795
23399
|
|
|
22796
23400
|
// src/utils/crash-guard.ts
|
|
22797
|
-
import { mkdirSync as
|
|
22798
|
-
import { join as
|
|
23401
|
+
import { mkdirSync as mkdirSync3, writeFileSync as writeFileSync2 } from "fs";
|
|
23402
|
+
import { join as join33 } from "path";
|
|
22799
23403
|
var installed = false;
|
|
22800
23404
|
var crashing = false;
|
|
22801
23405
|
var crashCleanups = /* @__PURE__ */ new Set();
|
|
@@ -22826,7 +23430,7 @@ function installCrashGuard(options = {}) {
|
|
|
22826
23430
|
installed = true;
|
|
22827
23431
|
const crashLogDir = () => {
|
|
22828
23432
|
try {
|
|
22829
|
-
return
|
|
23433
|
+
return join33((options.workspace ?? process.cwd)(), ".flavor");
|
|
22830
23434
|
} catch {
|
|
22831
23435
|
return process.cwd();
|
|
22832
23436
|
}
|
|
@@ -22834,9 +23438,9 @@ function installCrashGuard(options = {}) {
|
|
|
22834
23438
|
const writeCrashLog = (kind, detail) => {
|
|
22835
23439
|
try {
|
|
22836
23440
|
const dir = crashLogDir();
|
|
22837
|
-
|
|
22838
|
-
const path =
|
|
22839
|
-
|
|
23441
|
+
mkdirSync3(dir, { recursive: true });
|
|
23442
|
+
const path = join33(dir, `crash-${(/* @__PURE__ */ new Date()).toISOString().replace(/[^0-9]/g, "")}-${process.pid}.log`);
|
|
23443
|
+
writeFileSync2(path, [
|
|
22840
23444
|
`kind: ${kind}`,
|
|
22841
23445
|
`version: ${process.version}`,
|
|
22842
23446
|
`platform: ${process.platform}`,
|
|
@@ -23606,8 +24210,8 @@ ${total}`;
|
|
|
23606
24210
|
|
|
23607
24211
|
// src/memory/store.ts
|
|
23608
24212
|
import { createHash as createHash17 } from "crypto";
|
|
23609
|
-
import { readFile as
|
|
23610
|
-
import { resolve as resolve31, join as
|
|
24213
|
+
import { readFile as readFile25, rm as rm12 } from "fs/promises";
|
|
24214
|
+
import { resolve as resolve31, join as join34, relative as relative21, sep as sep21 } from "path";
|
|
23611
24215
|
|
|
23612
24216
|
// src/memory/similarity.ts
|
|
23613
24217
|
function normalizeForSimilarity(value) {
|
|
@@ -23710,9 +24314,9 @@ var MemoryStore = class {
|
|
|
23710
24314
|
if (!Number.isSafeInteger(options.maxEntries) || options.maxEntries < 1) throw new Error("maxEntries must be an integer of at least 1");
|
|
23711
24315
|
if (!Number.isSafeInteger(options.maxEntryChars) || options.maxEntryChars < 1) throw new Error("maxEntryChars must be an integer of at least 1");
|
|
23712
24316
|
this.workspace = resolve31(options.workspace);
|
|
23713
|
-
this.#memoryRoot =
|
|
23714
|
-
this.path =
|
|
23715
|
-
this.#behaviorPath =
|
|
24317
|
+
this.#memoryRoot = join34(this.workspace, ".flavor", "memory");
|
|
24318
|
+
this.path = join34(this.#memoryRoot, "MEMORY.md");
|
|
24319
|
+
this.#behaviorPath = join34(this.#memoryRoot, "behavior.json");
|
|
23716
24320
|
this.#maxEntries = options.maxEntries;
|
|
23717
24321
|
this.#maxEntryChars = options.maxEntryChars;
|
|
23718
24322
|
}
|
|
@@ -23930,7 +24534,7 @@ var MemoryStore = class {
|
|
|
23930
24534
|
});
|
|
23931
24535
|
}
|
|
23932
24536
|
async #readTaskItem(reference) {
|
|
23933
|
-
const document = decodeTask(await
|
|
24537
|
+
const document = decodeTask(await readFile25(this.#resolveContentPath(reference.contentPath), "utf8"));
|
|
23934
24538
|
return document.items.find((item) => item.id === reference.id)?.content ?? reference.summary;
|
|
23935
24539
|
}
|
|
23936
24540
|
#resolveContentPath(contentPath) {
|
|
@@ -24570,8 +25174,8 @@ function createExecutionEnvironment(workspace, config) {
|
|
|
24570
25174
|
}
|
|
24571
25175
|
|
|
24572
25176
|
// src/ide/client.ts
|
|
24573
|
-
import { readFile as
|
|
24574
|
-
import { basename as basename8, join as
|
|
25177
|
+
import { readFile as readFile26, readdir as readdir11, stat as stat13 } from "fs/promises";
|
|
25178
|
+
import { basename as basename8, join as join35, relative as relative23, resolve as resolve33, sep as sep23 } from "path";
|
|
24575
25179
|
var FlavorIdeClient = class {
|
|
24576
25180
|
#workspace;
|
|
24577
25181
|
#home;
|
|
@@ -24667,7 +25271,7 @@ var FlavorIdeClient = class {
|
|
|
24667
25271
|
};
|
|
24668
25272
|
if (await this.#healthy(configured)) return configured;
|
|
24669
25273
|
}
|
|
24670
|
-
const directory =
|
|
25274
|
+
const directory = join35(this.#home, ".flavor-code", "ide");
|
|
24671
25275
|
let entries;
|
|
24672
25276
|
try {
|
|
24673
25277
|
entries = (await readdir11(directory)).filter((name) => name.endsWith(".lock"));
|
|
@@ -24675,9 +25279,9 @@ var FlavorIdeClient = class {
|
|
|
24675
25279
|
return void 0;
|
|
24676
25280
|
}
|
|
24677
25281
|
const candidates = (await Promise.all(entries.map(async (name) => {
|
|
24678
|
-
const path =
|
|
25282
|
+
const path = join35(directory, name);
|
|
24679
25283
|
try {
|
|
24680
|
-
const [contents, info] = await Promise.all([
|
|
25284
|
+
const [contents, info] = await Promise.all([readFile26(path, "utf8"), stat13(path)]);
|
|
24681
25285
|
const lock = parseLockfile(contents);
|
|
24682
25286
|
return lock === void 0 ? void 0 : { lock, modified: info.mtimeMs };
|
|
24683
25287
|
} catch {
|
|
@@ -24796,6 +25400,7 @@ function relativeDisplay(workspace, filePath) {
|
|
|
24796
25400
|
|
|
24797
25401
|
// src/jobs/registry.ts
|
|
24798
25402
|
import { randomUUID as randomUUID19 } from "crypto";
|
|
25403
|
+
var OUTPUT_SLICE_SLACK_CHARS = 64 * 1024;
|
|
24799
25404
|
var JobRegistry = class {
|
|
24800
25405
|
#jobs = /* @__PURE__ */ new Map();
|
|
24801
25406
|
#maxOutputChars;
|
|
@@ -24824,7 +25429,8 @@ var JobRegistry = class {
|
|
|
24824
25429
|
updatedAt: now,
|
|
24825
25430
|
outputChars: 0,
|
|
24826
25431
|
truncated: false,
|
|
24827
|
-
|
|
25432
|
+
chunks: [],
|
|
25433
|
+
windowChars: 0,
|
|
24828
25434
|
outputStart: 0,
|
|
24829
25435
|
settle,
|
|
24830
25436
|
done
|
|
@@ -24835,11 +25441,16 @@ var JobRegistry = class {
|
|
|
24835
25441
|
id,
|
|
24836
25442
|
append: (text) => {
|
|
24837
25443
|
if (record3.state !== "running" || text === "") return;
|
|
24838
|
-
record3.
|
|
25444
|
+
record3.chunks.push(text);
|
|
25445
|
+
record3.windowChars += text.length;
|
|
24839
25446
|
record3.outputChars += text.length;
|
|
24840
|
-
if (record3.
|
|
24841
|
-
|
|
24842
|
-
|
|
25447
|
+
if (record3.outputChars > this.#maxOutputChars) record3.truncated = true;
|
|
25448
|
+
if (record3.windowChars > this.#maxOutputChars + OUTPUT_SLICE_SLACK_CHARS) {
|
|
25449
|
+
const joined = record3.chunks.join("");
|
|
25450
|
+
const kept = joined.slice(joined.length - this.#maxOutputChars);
|
|
25451
|
+
const removed = joined.length - kept.length;
|
|
25452
|
+
record3.chunks = [kept];
|
|
25453
|
+
record3.windowChars = kept.length;
|
|
24843
25454
|
record3.outputStart += removed;
|
|
24844
25455
|
record3.truncated = true;
|
|
24845
25456
|
}
|
|
@@ -24860,8 +25471,20 @@ var JobRegistry = class {
|
|
|
24860
25471
|
}
|
|
24861
25472
|
read(id, owner2, cursor = 0) {
|
|
24862
25473
|
const job = this.#owned(id, owner2);
|
|
24863
|
-
const
|
|
24864
|
-
|
|
25474
|
+
const window = job.chunks.join("");
|
|
25475
|
+
const dropped = Math.max(0, window.length - this.#maxOutputChars);
|
|
25476
|
+
const effective = Math.max(cursor, job.outputStart + dropped);
|
|
25477
|
+
return { ...snapshot2(job), output: window.slice(effective - job.outputStart), cursor: job.outputStart + window.length };
|
|
25478
|
+
}
|
|
25479
|
+
/** Retained sizes for the rotation census. */
|
|
25480
|
+
census() {
|
|
25481
|
+
let windowChars = 0;
|
|
25482
|
+
let running = 0;
|
|
25483
|
+
for (const job of this.#jobs.values()) {
|
|
25484
|
+
windowChars += job.windowChars;
|
|
25485
|
+
if (job.state === "running") running += 1;
|
|
25486
|
+
}
|
|
25487
|
+
return { jobs: this.#jobs.size, running, windowChars };
|
|
24865
25488
|
}
|
|
24866
25489
|
async wait(id, owner2, signal) {
|
|
24867
25490
|
signal?.throwIfAborted();
|
|
@@ -25198,7 +25821,7 @@ function palSocketAddress(options) {
|
|
|
25198
25821
|
import { createHash as createHash20 } from "crypto";
|
|
25199
25822
|
import { Socket as Socket2 } from "net";
|
|
25200
25823
|
import { tmpdir as tmpdir3 } from "os";
|
|
25201
|
-
import { join as
|
|
25824
|
+
import { join as join37 } from "path";
|
|
25202
25825
|
import { spawn as spawn8 } from "child_process";
|
|
25203
25826
|
|
|
25204
25827
|
// src/pals/broker.ts
|
|
@@ -25209,18 +25832,18 @@ import { dirname as dirname21 } from "path";
|
|
|
25209
25832
|
|
|
25210
25833
|
// src/pals/auth.ts
|
|
25211
25834
|
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
|
|
25835
|
+
import { chmod as chmod2, link as link2, mkdir as mkdir21, open as open12, readFile as readFile27, unlink as unlink4 } from "fs/promises";
|
|
25213
25836
|
import { homedir as homedir2 } from "os";
|
|
25214
|
-
import { join as
|
|
25837
|
+
import { join as join36 } from "path";
|
|
25215
25838
|
var PAL_AUTH_TOKEN_BYTES = 32;
|
|
25216
25839
|
var TOKEN_PATTERN = /^[a-f0-9]{64}$/;
|
|
25217
25840
|
async function loadOrCreatePalAuthToken(options = {}) {
|
|
25218
|
-
const directory =
|
|
25219
|
-
const tokenPath =
|
|
25841
|
+
const directory = join36(options.home ?? homedir2(), ".flavor-code", "pals");
|
|
25842
|
+
const tokenPath = join36(directory, "auth-token");
|
|
25220
25843
|
await mkdir21(directory, { recursive: true, mode: 448 });
|
|
25221
25844
|
await chmod2(directory, 448);
|
|
25222
25845
|
const token = (options.random ?? randomBytes3)(PAL_AUTH_TOKEN_BYTES).toString("hex");
|
|
25223
|
-
const temporaryPath =
|
|
25846
|
+
const temporaryPath = join36(directory, `.auth-token-${process.pid}-${randomUUID21()}.tmp`);
|
|
25224
25847
|
let handle;
|
|
25225
25848
|
try {
|
|
25226
25849
|
handle = await open12(temporaryPath, "wx", 384);
|
|
@@ -25243,7 +25866,7 @@ async function loadOrCreatePalAuthToken(options = {}) {
|
|
|
25243
25866
|
if (error.code !== "ENOENT") throw error;
|
|
25244
25867
|
});
|
|
25245
25868
|
}
|
|
25246
|
-
const stored = (await
|
|
25869
|
+
const stored = (await readFile27(tokenPath, "utf8")).trim();
|
|
25247
25870
|
if (!TOKEN_PATTERN.test(stored)) throw new Error("Pals authentication token store is invalid");
|
|
25248
25871
|
await chmod2(tokenPath, 384);
|
|
25249
25872
|
return stored;
|
|
@@ -25255,7 +25878,7 @@ function validatePalAuthToken(token) {
|
|
|
25255
25878
|
|
|
25256
25879
|
// src/pals/lifecycle.ts
|
|
25257
25880
|
import { randomUUID as randomUUID22 } from "crypto";
|
|
25258
|
-
import { mkdir as mkdir22, open as open13, readFile as
|
|
25881
|
+
import { mkdir as mkdir22, open as open13, readFile as readFile28, rename as rename11, stat as stat14, unlink as unlink5 } from "fs/promises";
|
|
25259
25882
|
import { dirname as dirname20 } from "path";
|
|
25260
25883
|
function defaultProcessAlive(pid) {
|
|
25261
25884
|
if (!Number.isSafeInteger(pid) || pid <= 0) return true;
|
|
@@ -25287,7 +25910,7 @@ async function acquirePalFileLock(options) {
|
|
|
25287
25910
|
handle = await open13(options.path, "wx", 384);
|
|
25288
25911
|
} catch (error) {
|
|
25289
25912
|
if (error.code !== "EEXIST") throw error;
|
|
25290
|
-
const [contents, info] = await Promise.all([
|
|
25913
|
+
const [contents, info] = await Promise.all([readFile28(options.path, "utf8").catch(() => ""), stat14(options.path).catch(() => void 0)]);
|
|
25291
25914
|
if (await options.endpointLive()) return void 0;
|
|
25292
25915
|
const existing = parseMetadata(contents);
|
|
25293
25916
|
if (existing !== void 0 && (options.processAlive ?? defaultProcessAlive)(existing.pid)) return void 0;
|
|
@@ -25317,7 +25940,7 @@ async function acquirePalFileLock(options) {
|
|
|
25317
25940
|
await handle.close();
|
|
25318
25941
|
}
|
|
25319
25942
|
const release = async () => {
|
|
25320
|
-
const current = parseMetadata(await
|
|
25943
|
+
const current = parseMetadata(await readFile28(options.path, "utf8").catch(() => ""));
|
|
25321
25944
|
if (current?.nonce !== nonce) return;
|
|
25322
25945
|
await unlink5(options.path).catch((error) => {
|
|
25323
25946
|
if (error.code !== "ENOENT") throw error;
|
|
@@ -25350,7 +25973,7 @@ function canConnect(address) {
|
|
|
25350
25973
|
}
|
|
25351
25974
|
function startupLockPath(address) {
|
|
25352
25975
|
const digest = createHash20("sha256").update(address, "utf8").digest("hex").slice(0, 24);
|
|
25353
|
-
return
|
|
25976
|
+
return join37(tmpdir3(), `flavor-code-pals-${digest}.startup.lock`);
|
|
25354
25977
|
}
|
|
25355
25978
|
async function spawnProductionBroker(address) {
|
|
25356
25979
|
const entry2 = process.argv[1];
|
|
@@ -26293,8 +26916,8 @@ async function createProductionRuntime(options) {
|
|
|
26293
26916
|
const registeredProviders = registration.registered;
|
|
26294
26917
|
let effectiveLlm = registration.effectiveLlm;
|
|
26295
26918
|
const pluginHost = new PluginHost({
|
|
26296
|
-
globalPluginDirs: [
|
|
26297
|
-
projectPluginDirs: [
|
|
26919
|
+
globalPluginDirs: [join38(home, ".flavor-code", "plugins")],
|
|
26920
|
+
projectPluginDirs: [join38(workspace, ".flavor", "plugins")],
|
|
26298
26921
|
config,
|
|
26299
26922
|
// Existing project plugins (including astgraph and superharness) use Node.js
|
|
26300
26923
|
// built-ins. The Worker/vm sandbox deliberately blocks those imports, so it
|
|
@@ -26403,7 +27026,7 @@ async function createProductionRuntime(options) {
|
|
|
26403
27026
|
const address = palSocketAddress({
|
|
26404
27027
|
platform: process.platform,
|
|
26405
27028
|
userScope: home,
|
|
26406
|
-
runtimeDir:
|
|
27029
|
+
runtimeDir: join38(tmpdir4(), `flavor-code-pals-${createHash21("sha256").update(home).digest("hex").slice(0, 16)}`)
|
|
26407
27030
|
});
|
|
26408
27031
|
const baseCollaborationClient = collaboration.client ?? collaboration.createClient?.({
|
|
26409
27032
|
instanceId: collaboration.instanceId,
|
|
@@ -26514,8 +27137,8 @@ async function createProductionRuntime(options) {
|
|
|
26514
27137
|
diagnostics.push(`MCP servers could not start: ${message(error)}`);
|
|
26515
27138
|
});
|
|
26516
27139
|
const skills = new SkillRegistry({
|
|
26517
|
-
globalRoots: [
|
|
26518
|
-
projectRoots: [
|
|
27140
|
+
globalRoots: [join38(home, ".flavor-code", "skills")],
|
|
27141
|
+
projectRoots: [join38(workspace, ".flavor", "skills"), ...pluginSkillRoots],
|
|
26519
27142
|
authorizeResource: async () => true,
|
|
26520
27143
|
disabledNames: config.skills.disabled
|
|
26521
27144
|
});
|
|
@@ -26523,7 +27146,7 @@ async function createProductionRuntime(options) {
|
|
|
26523
27146
|
skillsReady.catch(() => void 0);
|
|
26524
27147
|
tools.push(createSkillTool(skills), createSkillResourceTool(skills));
|
|
26525
27148
|
if (options.extraTools !== void 0) tools.push(...options.extraTools);
|
|
26526
|
-
const flavor = await optionalText2(
|
|
27149
|
+
const flavor = await optionalText2(join38(workspace, "FLAVOR.md"));
|
|
26527
27150
|
const instructionBaseline = await workspaceInstructions.baseline();
|
|
26528
27151
|
let memoryContext;
|
|
26529
27152
|
if (memoryStore !== void 0) {
|
|
@@ -27108,7 +27731,7 @@ async function createProductionRuntime(options) {
|
|
|
27108
27731
|
evolveService.toolDefinition(),
|
|
27109
27732
|
...mcpTools
|
|
27110
27733
|
];
|
|
27111
|
-
const loopFlavor = await optionalText2(
|
|
27734
|
+
const loopFlavor = await optionalText2(join38(input.workspace, "FLAVOR.md"));
|
|
27112
27735
|
const loopInstructionBaseline = await loopInstructions.baseline();
|
|
27113
27736
|
const loopEnvironment = createPromptEnvironment({
|
|
27114
27737
|
now: /* @__PURE__ */ new Date(),
|
|
@@ -27216,12 +27839,89 @@ async function createProductionRuntime(options) {
|
|
|
27216
27839
|
await loopExecutionEnvironment?.dispose();
|
|
27217
27840
|
}
|
|
27218
27841
|
};
|
|
27842
|
+
let activeLongTask;
|
|
27843
|
+
const readMemoryRestartMarker = () => {
|
|
27844
|
+
try {
|
|
27845
|
+
return parseMemoryRestartMarker(readFileSync3(memoryRestartMarkerPath(workspace), "utf8"));
|
|
27846
|
+
} catch {
|
|
27847
|
+
return void 0;
|
|
27848
|
+
}
|
|
27849
|
+
};
|
|
27850
|
+
const currentPressureReading = () => verifiedHeapPressure(MEMORY_PRESSURE_HEAP_RATIO) ?? { ...readHeap(), gcVerified: false };
|
|
27851
|
+
const collectRotationHolders = () => {
|
|
27852
|
+
const holders = {};
|
|
27853
|
+
try {
|
|
27854
|
+
const context = harness.main.context.census();
|
|
27855
|
+
holders.context = { entries: context.messages, chars: context.messageChars };
|
|
27856
|
+
holders.contextVisibility = { entries: context.visibilityEntries, chars: context.visibilityChars };
|
|
27857
|
+
holders.contextTransient = { entries: context.transientEntries, chars: context.transientChars };
|
|
27858
|
+
} catch {
|
|
27859
|
+
}
|
|
27860
|
+
try {
|
|
27861
|
+
holders.journal = { entries: harnessJournal.recordCount };
|
|
27862
|
+
} catch {
|
|
27863
|
+
}
|
|
27864
|
+
try {
|
|
27865
|
+
const jobCensus = jobs.census();
|
|
27866
|
+
holders.jobs = { entries: jobCensus.jobs, chars: jobCensus.windowChars };
|
|
27867
|
+
} catch {
|
|
27868
|
+
}
|
|
27869
|
+
try {
|
|
27870
|
+
holders.sessionTree = { entries: sessionHistory.nodeCount };
|
|
27871
|
+
} catch {
|
|
27872
|
+
}
|
|
27873
|
+
try {
|
|
27874
|
+
const transcript = transcriptCensus(timelineState);
|
|
27875
|
+
holders.transcript = { entries: transcript.blocks, chars: transcript.chars };
|
|
27876
|
+
holders.transcriptTurns = { entries: transcript.turns };
|
|
27877
|
+
} catch {
|
|
27878
|
+
}
|
|
27879
|
+
return holders;
|
|
27880
|
+
};
|
|
27881
|
+
const rotateForHeap = (continuation, reading) => {
|
|
27882
|
+
requestMemoryRestart(workspace, sessionId, emitOutput, {
|
|
27883
|
+
...continuation === void 0 ? {} : { continuation },
|
|
27884
|
+
...reading === void 0 ? {} : { reading },
|
|
27885
|
+
holders: collectRotationHolders()
|
|
27886
|
+
}, options.onMemoryRestartRequested);
|
|
27887
|
+
};
|
|
27888
|
+
const maybeRotateAtBoundary = (continuation) => {
|
|
27889
|
+
activeLongTask = continuation;
|
|
27890
|
+
const pressure = verifiedHeapPressure(MEMORY_PRESSURE_HEAP_RATIO);
|
|
27891
|
+
if (pressure !== void 0) {
|
|
27892
|
+
rotateForHeap(continuation, pressure);
|
|
27893
|
+
return;
|
|
27894
|
+
}
|
|
27895
|
+
const reading = heapRotationNeeded();
|
|
27896
|
+
if (reading === void 0) return;
|
|
27897
|
+
if (rotationCooldownActive(readMemoryRestartMarker(), sessionId, /* @__PURE__ */ new Date())) return;
|
|
27898
|
+
rotateForHeap(continuation, reading);
|
|
27899
|
+
};
|
|
27900
|
+
const maybeRotateAtTurnBoundary = () => {
|
|
27901
|
+
const reading = heapRotationNeeded();
|
|
27902
|
+
if (reading === void 0) return;
|
|
27903
|
+
if (rotationCooldownActive(readMemoryRestartMarker(), sessionId, /* @__PURE__ */ new Date())) return;
|
|
27904
|
+
rotateForHeap(void 0, reading);
|
|
27905
|
+
};
|
|
27906
|
+
const consumePendingContinuation = () => {
|
|
27907
|
+
const marker = readMemoryRestartMarker();
|
|
27908
|
+
const continuation = pendingContinuation(marker, sessionId, /* @__PURE__ */ new Date());
|
|
27909
|
+
if (continuation === void 0 || marker === void 0) return void 0;
|
|
27910
|
+
try {
|
|
27911
|
+
const markerPath = memoryRestartMarkerPath(workspace);
|
|
27912
|
+
writeMarkerAtomically(markerPath, consumedMemoryRestartMarker(marker));
|
|
27913
|
+
} catch {
|
|
27914
|
+
}
|
|
27915
|
+
return continuation;
|
|
27916
|
+
};
|
|
27219
27917
|
const loopStore = new LoopStore({ workspace });
|
|
27220
27918
|
const loopOrchestrator = new LoopOrchestrator({
|
|
27221
27919
|
workspace,
|
|
27222
27920
|
config: config.loop,
|
|
27223
27921
|
persistence: loopStore,
|
|
27224
27922
|
hallucinationGuard,
|
|
27923
|
+
onCycleBoundary: (loopId) => maybeRotateAtBoundary({ kind: "loop", id: loopId }),
|
|
27924
|
+
onMemoryCheckpoint: (loopId) => maybeRotateAtBoundary({ kind: "loop", id: loopId }),
|
|
27225
27925
|
prepareWorkspace: (input) => prepareLoopWorkspace(input),
|
|
27226
27926
|
inferVerification: inferVerificationPlan,
|
|
27227
27927
|
runWorker: ({ workspace: executionWorkspace, prompt, signal }) => runLoopWorker({ workspace: executionWorkspace, prompt, signal }),
|
|
@@ -27233,21 +27933,9 @@ async function createProductionRuntime(options) {
|
|
|
27233
27933
|
await verifierEnvironment?.dispose();
|
|
27234
27934
|
}
|
|
27235
27935
|
},
|
|
27236
|
-
|
|
27237
|
-
|
|
27238
|
-
|
|
27239
|
-
const next = dimensions.map((dimension) => dimension === "cycles" ? `${state.budget.cycleCheckpoint + state.config.cycleStep} cycles` : `${state.budget.tokenCheckpoint + state.config.tokenStep} tokens`).join(" and ");
|
|
27240
|
-
const latestVerification = state.cycles.at(-1)?.verification.summary ?? "No host verification evidence yet.";
|
|
27241
|
-
const answers = await questions.ask([{
|
|
27242
|
-
header: "Loop budget",
|
|
27243
|
-
question: `Loop ${state.loopId} reached ${reached}. Latest verification: ${latestVerification} Continue until the next checkpoint (${next})?`,
|
|
27244
|
-
options: [
|
|
27245
|
-
{ label: "Continue", description: "Extend only the reached budget tranche and keep looping." },
|
|
27246
|
-
{ label: "Stop", description: "End this loop as budget exhausted." }
|
|
27247
|
-
]
|
|
27248
|
-
}], signal);
|
|
27249
|
-
return answers[0] === "Continue" ? "approved" : "rejected";
|
|
27250
|
-
},
|
|
27936
|
+
// Cycle/token values are telemetry checkpoint cadence, not an unattended
|
|
27937
|
+
// execution cap. The user can still cancel explicitly at any time.
|
|
27938
|
+
confirmBudget: async () => "approved",
|
|
27251
27939
|
fingerprint: workspaceFingerprint,
|
|
27252
27940
|
idFactory: () => `loop-${(/* @__PURE__ */ new Date()).toISOString().replace(/[^0-9]/g, "").slice(0, 17)}-${randomUUID25().slice(0, 8)}`
|
|
27253
27941
|
});
|
|
@@ -27258,9 +27946,12 @@ async function createProductionRuntime(options) {
|
|
|
27258
27946
|
plannerModelId: mainModel,
|
|
27259
27947
|
classifierModelId: mainModel,
|
|
27260
27948
|
skepticCount: 3,
|
|
27261
|
-
|
|
27262
|
-
|
|
27949
|
+
// Production goals run until verified, explicitly cancelled, or blocked
|
|
27950
|
+
// by a non-recoverable external condition. Numeric config values remain
|
|
27951
|
+
// accepted for compatibility but no longer terminate autonomous work.
|
|
27263
27952
|
persistence: goalStore,
|
|
27953
|
+
onRoundBoundary: (goalId) => maybeRotateAtBoundary({ kind: "goal", id: goalId }),
|
|
27954
|
+
onMemoryCheckpoint: (goalId) => maybeRotateAtBoundary({ kind: "goal", id: goalId }),
|
|
27264
27955
|
verifyHost: async (signal) => runVerificationPlan(
|
|
27265
27956
|
await inferVerificationPlan(workspace),
|
|
27266
27957
|
workspace,
|
|
@@ -27355,30 +28046,50 @@ async function createProductionRuntime(options) {
|
|
|
27355
28046
|
contextEpoch: harness.main.context.snapshot().epoch?.id ?? "legacy"
|
|
27356
28047
|
});
|
|
27357
28048
|
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
|
-
|
|
28049
|
+
return monitorTurnMemory(
|
|
28050
|
+
durableTurn(persistAfter(runMain(
|
|
28051
|
+
harness,
|
|
28052
|
+
skills,
|
|
28053
|
+
prompt,
|
|
28054
|
+
signal,
|
|
28055
|
+
selectedModels.mainError,
|
|
28056
|
+
memoryStore === void 0 || !memoryHasRoutableEntries && userMemoryContext === void 0 ? void 0 : {
|
|
28057
|
+
store: memoryStore,
|
|
28058
|
+
taskId: memoryLifecycle.taskId ?? sessionId,
|
|
28059
|
+
topK: config.memory.retrievalTopK,
|
|
28060
|
+
maxChars: config.memory.maxPromptChars
|
|
28061
|
+
},
|
|
28062
|
+
runOptions?.getSteeringMessages,
|
|
28063
|
+
runOptions?.initialUserMessage,
|
|
28064
|
+
runOptions?.additionalContext,
|
|
28065
|
+
ide
|
|
28066
|
+
), persist), harnessJournal, turnId, turnConfig),
|
|
28067
|
+
() => rotateForHeap(void 0, currentPressureReading()),
|
|
28068
|
+
() => maybeRotateAtTurnBoundary()
|
|
28069
|
+
);
|
|
27375
28070
|
},
|
|
27376
28071
|
runSkill: (skill, prompt, signal) => persistAfter(
|
|
27377
28072
|
runExplicitSkill(harness, skills, skill, prompt, signal, selectedModels.mainError),
|
|
27378
28073
|
persist
|
|
27379
28074
|
),
|
|
27380
|
-
runLoop: (goal, signal) =>
|
|
27381
|
-
|
|
28075
|
+
runLoop: (goal, signal) => monitorLongTaskRestart(
|
|
28076
|
+
runLoopSession(loopOrchestrator, hooks, goal, signal),
|
|
28077
|
+
() => rotateForHeap(activeLongTask, currentPressureReading())
|
|
28078
|
+
),
|
|
28079
|
+
runGoal: (goal, signal) => monitorLongTaskRestart(
|
|
28080
|
+
persistEach(runGoalSession(goalOrchestrator, goal, signal), persist),
|
|
28081
|
+
() => rotateForHeap(activeLongTask, currentPressureReading())
|
|
28082
|
+
),
|
|
28083
|
+
resumeLoop: (loopId, signal) => monitorLongTaskRestart(
|
|
28084
|
+
runResumeLoopSession(loopOrchestrator, hooks, loopId, signal),
|
|
28085
|
+
() => rotateForHeap(activeLongTask ?? { kind: "loop", id: loopId }, currentPressureReading())
|
|
28086
|
+
),
|
|
28087
|
+
resumeGoal: (goalId, signal) => monitorLongTaskRestart(
|
|
28088
|
+
persistEach(runResumeGoalSession(goalOrchestrator, goalId, signal), persist),
|
|
28089
|
+
() => rotateForHeap(activeLongTask ?? { kind: "goal", id: goalId }, currentPressureReading())
|
|
28090
|
+
),
|
|
28091
|
+
continuation: () => consumePendingContinuation(),
|
|
28092
|
+
rotationPending: () => memoryRotationActive(),
|
|
27382
28093
|
mcp: async (command, signal) => {
|
|
27383
28094
|
signal.throwIfAborted();
|
|
27384
28095
|
const manager = await mcpReady;
|
|
@@ -27472,7 +28183,7 @@ async function createProductionRuntime(options) {
|
|
|
27472
28183
|
const root = resolve36(workspace);
|
|
27473
28184
|
const abs = resolve36(root, path);
|
|
27474
28185
|
if (abs !== root && !abs.startsWith(root + sep25)) throw new Error(`path outside workspace: ${path}`);
|
|
27475
|
-
return
|
|
28186
|
+
return readFile29(abs, "utf8");
|
|
27476
28187
|
},
|
|
27477
28188
|
history: async (path, limit) => fileHistory(workspace, path, limit),
|
|
27478
28189
|
questions,
|
|
@@ -27484,7 +28195,7 @@ async function createProductionRuntime(options) {
|
|
|
27484
28195
|
}, query, focus, signal),
|
|
27485
28196
|
audit: async (toolFilter) => {
|
|
27486
28197
|
try {
|
|
27487
|
-
const raw = await
|
|
28198
|
+
const raw = await readFile29(auditLogger.path, "utf8");
|
|
27488
28199
|
const lines = raw.trim().split("\n").filter((line) => line.length > 0);
|
|
27489
28200
|
const entries = lines.map((line) => {
|
|
27490
28201
|
try {
|
|
@@ -27524,7 +28235,7 @@ ${summary2}`;
|
|
|
27524
28235
|
doctor: async () => formatDoctorReport(await runDoctor({ workspace, home, environment })),
|
|
27525
28236
|
usage: async () => {
|
|
27526
28237
|
try {
|
|
27527
|
-
const raw = await
|
|
28238
|
+
const raw = await readFile29(usageLogPath(), "utf8");
|
|
27528
28239
|
return formatUsageSummary(summarizeUsage(parseUsageEntries(raw)));
|
|
27529
28240
|
} catch (error) {
|
|
27530
28241
|
if (typeof error === "object" && error !== null && "code" in error && error.code === "ENOENT") {
|
|
@@ -27677,7 +28388,7 @@ ${renderMemoryDocument(entries)}`;
|
|
|
27677
28388
|
return `Provider "${providerName}" is missing authorizationUrl, tokenUrl, or clientId.`;
|
|
27678
28389
|
}
|
|
27679
28390
|
try {
|
|
27680
|
-
const tokenStore = createFileTokenStore(
|
|
28391
|
+
const tokenStore = createFileTokenStore(join38(home, ".flavor-code", "auth.json"));
|
|
27681
28392
|
const credentialId = oauthCredentialId(oauthConfig.tokenUrl, oauthConfig.clientId);
|
|
27682
28393
|
await migrateLegacyOAuthToken(tokenStore, providerName, credentialId);
|
|
27683
28394
|
const oauth = new OAuthCallbackAuthProvider({
|
|
@@ -27724,7 +28435,7 @@ ${renderMemoryDocument(entries)}`;
|
|
|
27724
28435
|
}
|
|
27725
28436
|
},
|
|
27726
28437
|
async logout() {
|
|
27727
|
-
const tokenStore = createFileTokenStore(
|
|
28438
|
+
const tokenStore = createFileTokenStore(join38(home, ".flavor-code", "auth.json"));
|
|
27728
28439
|
const tokens2 = await tokenStore.load();
|
|
27729
28440
|
const hadCredentials = Object.keys(tokens2).length > 0 || effectiveLlm !== void 0;
|
|
27730
28441
|
if (!hadCredentials) {
|
|
@@ -27917,10 +28628,16 @@ function formatMcpReconnect(server) {
|
|
|
27917
28628
|
return `Failed to reconnect MCP server "${server.name}": ${server.error ?? "unknown error"}`;
|
|
27918
28629
|
}
|
|
27919
28630
|
async function* runLoopSession(orchestrator, hooks, goal, signal) {
|
|
28631
|
+
yield* loopSessionEvents(orchestrator.run({ goal, signal }), hooks);
|
|
28632
|
+
}
|
|
28633
|
+
async function* runResumeLoopSession(orchestrator, hooks, loopId, signal) {
|
|
28634
|
+
yield* loopSessionEvents(orchestrator.resume({ loopId, signal }), hooks);
|
|
28635
|
+
}
|
|
28636
|
+
async function* loopSessionEvents(events, hooks) {
|
|
27920
28637
|
let totalInputTokens = 0;
|
|
27921
28638
|
let totalOutputTokens = 0;
|
|
27922
28639
|
let latestVerification;
|
|
27923
|
-
for await (const event of
|
|
28640
|
+
for await (const event of events) {
|
|
27924
28641
|
if (event.type === "worker-event") {
|
|
27925
28642
|
if (event.event.type === "usage") {
|
|
27926
28643
|
totalInputTokens += event.event.inputTokens;
|
|
@@ -27930,6 +28647,8 @@ async function* runLoopSession(orchestrator, hooks, goal, signal) {
|
|
|
27930
28647
|
totalInputTokens,
|
|
27931
28648
|
totalOutputTokens
|
|
27932
28649
|
};
|
|
28650
|
+
} else if (event.event.type === "error" && isResumableLongTaskError(event.event.error.code)) {
|
|
28651
|
+
yield { type: "warning", message: `Loop worker segment ended at ${event.event.error.code}; checkpointing and verifying before continuing.` };
|
|
27933
28652
|
} else if (event.event.type !== "done") yield event.event;
|
|
27934
28653
|
continue;
|
|
27935
28654
|
}
|
|
@@ -27984,7 +28703,7 @@ function loopProgressEvent(event) {
|
|
|
27984
28703
|
loopId: event.loopId,
|
|
27985
28704
|
phase: "budget",
|
|
27986
28705
|
state: "info",
|
|
27987
|
-
message:
|
|
28706
|
+
message: `${event.dimensions.join(" and ")} checkpoint reached; continuing automatically.`
|
|
27988
28707
|
};
|
|
27989
28708
|
}
|
|
27990
28709
|
return {
|
|
@@ -28001,6 +28720,9 @@ function terminalProgressState(status) {
|
|
|
28001
28720
|
if (status === "needs_human") return "info";
|
|
28002
28721
|
return "failed";
|
|
28003
28722
|
}
|
|
28723
|
+
function isResumableLongTaskError(code) {
|
|
28724
|
+
return code !== "authentication" && code !== "model_not_found" && code !== "cancelled" && code !== "memory_pressure";
|
|
28725
|
+
}
|
|
28004
28726
|
async function workspaceFingerprint(workspace) {
|
|
28005
28727
|
const hash = createHash21("sha256");
|
|
28006
28728
|
const diff = await execFileNoThrow(
|
|
@@ -28024,7 +28746,7 @@ async function workspaceFingerprint(workspace) {
|
|
|
28024
28746
|
hash.update(name);
|
|
28025
28747
|
if (remaining <= 0) continue;
|
|
28026
28748
|
try {
|
|
28027
|
-
const content = await
|
|
28749
|
+
const content = await readFile29(path);
|
|
28028
28750
|
const slice = content.subarray(0, remaining);
|
|
28029
28751
|
hash.update(slice);
|
|
28030
28752
|
remaining -= slice.length;
|
|
@@ -28136,7 +28858,16 @@ async function* durableTurn(source, journal, turnId, config) {
|
|
|
28136
28858
|
}
|
|
28137
28859
|
}
|
|
28138
28860
|
}
|
|
28139
|
-
async function*
|
|
28861
|
+
async function* monitorTurnMemory(source, onPressure, onBoundary) {
|
|
28862
|
+
let pressure = false;
|
|
28863
|
+
for await (const event of source) {
|
|
28864
|
+
if (event.type === "error" && event.error.code === "memory_pressure") pressure = true;
|
|
28865
|
+
yield event;
|
|
28866
|
+
}
|
|
28867
|
+
if (pressure) onPressure();
|
|
28868
|
+
else onBoundary();
|
|
28869
|
+
}
|
|
28870
|
+
async function* monitorLongTaskRestart(source, onRestartNeeded) {
|
|
28140
28871
|
let pressure = false;
|
|
28141
28872
|
for await (const event of source) {
|
|
28142
28873
|
if (event.type === "error" && event.error.code === "memory_pressure") pressure = true;
|
|
@@ -28144,7 +28875,10 @@ async function* monitorMemoryRestart(source, onRestartNeeded) {
|
|
|
28144
28875
|
}
|
|
28145
28876
|
if (pressure) onRestartNeeded();
|
|
28146
28877
|
}
|
|
28147
|
-
function requestMemoryRestart(workspace, sessionId, emitOutput) {
|
|
28878
|
+
function requestMemoryRestart(workspace, sessionId, emitOutput, rotation, onRestartRequested) {
|
|
28879
|
+
if (memoryRotationActive()) return;
|
|
28880
|
+
markMemoryRotation();
|
|
28881
|
+
const continuation = rotation?.continuation;
|
|
28148
28882
|
try {
|
|
28149
28883
|
const markerPath = memoryRestartMarkerPath(workspace);
|
|
28150
28884
|
let previous;
|
|
@@ -28152,14 +28886,43 @@ function requestMemoryRestart(workspace, sessionId, emitOutput) {
|
|
|
28152
28886
|
previous = parseMemoryRestartMarker(readFileSync3(markerPath, "utf8"));
|
|
28153
28887
|
} catch {
|
|
28154
28888
|
}
|
|
28155
|
-
|
|
28156
|
-
writeFileSync2(markerPath, `${JSON.stringify(nextMemoryRestartMarker(previous, sessionId, /* @__PURE__ */ new Date()))}
|
|
28157
|
-
`, "utf8");
|
|
28889
|
+
writeMarkerAtomically(markerPath, nextMemoryRestartMarker(previous, sessionId, /* @__PURE__ */ new Date(), continuation));
|
|
28158
28890
|
} catch {
|
|
28159
28891
|
}
|
|
28160
|
-
|
|
28161
|
-
|
|
28162
|
-
|
|
28892
|
+
if (rotation?.reading !== void 0) {
|
|
28893
|
+
const census = {
|
|
28894
|
+
at: (/* @__PURE__ */ new Date()).toISOString(),
|
|
28895
|
+
reason: continuation === void 0 ? "memory-pressure" : `heap-rotation:${continuation.kind}`,
|
|
28896
|
+
sessionId,
|
|
28897
|
+
heap: censusHeapBlock(rotation.reading),
|
|
28898
|
+
holders: rotation.holders ?? {}
|
|
28899
|
+
};
|
|
28900
|
+
writeRotationCensus(workspace, census);
|
|
28901
|
+
if (heapSnapshotWarranted(rotation.reading)) writeRotationHeapSnapshot(workspace);
|
|
28902
|
+
}
|
|
28903
|
+
emitOutput({
|
|
28904
|
+
type: "notice",
|
|
28905
|
+
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.`
|
|
28906
|
+
});
|
|
28907
|
+
if (onRestartRequested !== void 0) onRestartRequested(sessionId);
|
|
28908
|
+
else {
|
|
28909
|
+
process.exitCode = MEMORY_RESTART_EXIT_CODE;
|
|
28910
|
+
emitOutput({ type: "exit" });
|
|
28911
|
+
}
|
|
28912
|
+
}
|
|
28913
|
+
function writeMarkerAtomically(path, marker) {
|
|
28914
|
+
mkdirSync4(dirname22(path), { recursive: true });
|
|
28915
|
+
const temporary = `${path}.${process.pid}.${randomUUID25()}.tmp`;
|
|
28916
|
+
try {
|
|
28917
|
+
writeFileSync3(temporary, `${JSON.stringify(marker)}
|
|
28918
|
+
`, { encoding: "utf8", mode: 384 });
|
|
28919
|
+
renameSync2(temporary, path);
|
|
28920
|
+
} finally {
|
|
28921
|
+
try {
|
|
28922
|
+
unlinkSync3(temporary);
|
|
28923
|
+
} catch {
|
|
28924
|
+
}
|
|
28925
|
+
}
|
|
28163
28926
|
}
|
|
28164
28927
|
async function runGitCommit(deps, hint, signal) {
|
|
28165
28928
|
if (!await isGitRepository(deps.workspace)) return "Not a git repository \u2014 /commit needs git.";
|
|
@@ -28254,7 +29017,7 @@ async function registerConfiguredAdapters(providers, registry, environment, diag
|
|
|
28254
29017
|
const configured = { ...providers };
|
|
28255
29018
|
if (configured.openai === void 0 && environment.OPENAI_API_KEY) configured.openai = { type: "openai", apiKey: environment.OPENAI_API_KEY };
|
|
28256
29019
|
if (configured.anthropic === void 0 && environment.ANTHROPIC_API_KEY) configured.anthropic = { type: "anthropic", apiKey: environment.ANTHROPIC_API_KEY };
|
|
28257
|
-
const oauthTokenStore = createFileTokenStore(
|
|
29020
|
+
const oauthTokenStore = createFileTokenStore(join38(home, ".flavor-code", "auth.json"));
|
|
28258
29021
|
const registered = [];
|
|
28259
29022
|
let effectiveLlm;
|
|
28260
29023
|
for (const [name, provider] of Object.entries(configured)) {
|
|
@@ -28523,7 +29286,7 @@ async function generateSleepReview(registry, modelId, prompt, signal) {
|
|
|
28523
29286
|
}
|
|
28524
29287
|
async function optionalText2(path) {
|
|
28525
29288
|
try {
|
|
28526
|
-
return await
|
|
29289
|
+
return await readFile29(path, "utf8");
|
|
28527
29290
|
} catch (error) {
|
|
28528
29291
|
if (typeof error === "object" && error !== null && "code" in error && error.code === "ENOENT") return void 0;
|
|
28529
29292
|
throw error;
|
|
@@ -28608,7 +29371,24 @@ function attachCleanupError(primary, cleanup) {
|
|
|
28608
29371
|
}
|
|
28609
29372
|
}
|
|
28610
29373
|
async function* runGoalSession(orchestrator, goal, signal) {
|
|
28611
|
-
|
|
29374
|
+
yield* goalSessionEvents(orchestrator.run({ goal, signal }));
|
|
29375
|
+
}
|
|
29376
|
+
async function* runResumeGoalSession(orchestrator, goalId, signal) {
|
|
29377
|
+
yield* goalSessionEvents(orchestrator.resume({ goalId, signal }));
|
|
29378
|
+
}
|
|
29379
|
+
async function* goalSessionEvents(events) {
|
|
29380
|
+
for await (const event of events) {
|
|
29381
|
+
if (event.type === "goal-resumed") {
|
|
29382
|
+
yield { type: "notice", message: `Resuming goal ${event.goalId} at round ${event.round} after a heap rotation.` };
|
|
29383
|
+
continue;
|
|
29384
|
+
}
|
|
29385
|
+
if (event.type === "goal-stage-retry") {
|
|
29386
|
+
yield {
|
|
29387
|
+
type: "warning",
|
|
29388
|
+
message: `Goal ${event.stage} attempt ${event.attempt} hit a recoverable boundary (${event.reason}); retrying in ${event.delayMs}ms.`
|
|
29389
|
+
};
|
|
29390
|
+
continue;
|
|
29391
|
+
}
|
|
28612
29392
|
if (event.type === "goal-plan-created") {
|
|
28613
29393
|
yield { type: "notice", message: `Goal plan created (${event.plan.kind}) with ${event.plan.criteria.length} acceptance criteria.` };
|
|
28614
29394
|
yield { type: "notice", message: `Plan file: ${event.planPath}` };
|
|
@@ -28635,6 +29415,8 @@ async function* runGoalSession(orchestrator, goal, signal) {
|
|
|
28635
29415
|
totalInputTokens: event.event.usage.inputTokens,
|
|
28636
29416
|
totalOutputTokens: event.event.usage.outputTokens
|
|
28637
29417
|
};
|
|
29418
|
+
} else if (event.event.type === "error" && isResumableLongTaskError(event.event.error.code)) {
|
|
29419
|
+
yield { type: "warning", message: `Goal worker segment ended at ${event.event.error.code}; checkpointing and verifying before continuing.` };
|
|
28638
29420
|
} else {
|
|
28639
29421
|
yield event.event;
|
|
28640
29422
|
}
|
|
@@ -28685,13 +29467,13 @@ function createMemoryTaskId() {
|
|
|
28685
29467
|
|
|
28686
29468
|
// src/desktop/runtime-controller.ts
|
|
28687
29469
|
import { createHash as createHash27 } from "crypto";
|
|
28688
|
-
import { mkdir as mkdir27, readFile as
|
|
29470
|
+
import { mkdir as mkdir27, readFile as readFile37, readdir as readdir13, rm as rm15, stat as stat17, writeFile as writeFile17 } from "fs/promises";
|
|
28689
29471
|
import { homedir as homedir5 } from "os";
|
|
28690
|
-
import { basename as basename10, join as
|
|
29472
|
+
import { basename as basename10, join as join48, resolve as resolve39 } from "path";
|
|
28691
29473
|
|
|
28692
29474
|
// src/d2c/workflow.ts
|
|
28693
29475
|
import { createHash as createHash22 } from "crypto";
|
|
28694
|
-
import { join as
|
|
29476
|
+
import { join as join39 } from "path";
|
|
28695
29477
|
import { z as z42 } from "zod";
|
|
28696
29478
|
|
|
28697
29479
|
// src/d2c/workflow-shared.ts
|
|
@@ -28837,7 +29619,7 @@ function reviewsFor(report, previous) {
|
|
|
28837
29619
|
function implementationDirectory(report) {
|
|
28838
29620
|
const source = report.implementation.source;
|
|
28839
29621
|
if (!/^https?:\/\//i.test(source)) return source;
|
|
28840
|
-
return
|
|
29622
|
+
return join39("src", "d2c-output", report.task);
|
|
28841
29623
|
}
|
|
28842
29624
|
function createWorkflow(report, framework) {
|
|
28843
29625
|
const now = (/* @__PURE__ */ new Date()).toISOString();
|
|
@@ -28961,7 +29743,7 @@ function applyQualityIssueDecision(workflow, issueId2, decision) {
|
|
|
28961
29743
|
};
|
|
28962
29744
|
}
|
|
28963
29745
|
function workflowPath(workspace, task) {
|
|
28964
|
-
return
|
|
29746
|
+
return join39(taskDir(workspace, task), "workflow.json");
|
|
28965
29747
|
}
|
|
28966
29748
|
async function readWorkflow(workspace, task) {
|
|
28967
29749
|
return (await readRecoverableFile(workflowPath(workspace, task), decodeWorkflow))?.value;
|
|
@@ -29214,8 +29996,8 @@ function confirmApiMapping(mapping, operationKey, operations) {
|
|
|
29214
29996
|
}
|
|
29215
29997
|
|
|
29216
29998
|
// src/d2c/modules.ts
|
|
29217
|
-
import { readFile as
|
|
29218
|
-
import { isAbsolute as isAbsolute18, join as
|
|
29999
|
+
import { readFile as readFile30 } from "fs/promises";
|
|
30000
|
+
import { isAbsolute as isAbsolute18, join as join40, normalize as normalize2, posix, sep as sep26, win32 as win322 } from "path";
|
|
29219
30001
|
import { z as z43 } from "zod";
|
|
29220
30002
|
var ModuleSchema = z43.object({
|
|
29221
30003
|
id: z43.string().regex(/^[A-Za-z0-9][A-Za-z0-9._-]{0,127}$/),
|
|
@@ -29234,13 +30016,13 @@ function safeSource(path) {
|
|
|
29234
30016
|
}
|
|
29235
30017
|
function d2cOutputDirectory(workspace, task) {
|
|
29236
30018
|
if (!D2C_TASK_PATTERN.test(task)) throw new Error(`Invalid D2C task: ${task}`);
|
|
29237
|
-
return
|
|
30019
|
+
return join40(workspace, "src", "d2c-output", task);
|
|
29238
30020
|
}
|
|
29239
30021
|
async function readD2cModules(workspace, task) {
|
|
29240
30022
|
const project = d2cOutputDirectory(workspace, task);
|
|
29241
30023
|
let raw;
|
|
29242
30024
|
try {
|
|
29243
|
-
raw = await
|
|
30025
|
+
raw = await readFile30(join40(project, "d2c.modules.json"), "utf8");
|
|
29244
30026
|
} catch {
|
|
29245
30027
|
return [{ id: "page", label: task, sourceFiles: ["src/App.vue", "src/App.jsx"], keywords: [task] }];
|
|
29246
30028
|
}
|
|
@@ -29260,8 +30042,8 @@ async function readD2cModules(workspace, task) {
|
|
|
29260
30042
|
}
|
|
29261
30043
|
|
|
29262
30044
|
// src/d2c/integration.ts
|
|
29263
|
-
import { mkdir as mkdir24, readFile as
|
|
29264
|
-
import { join as
|
|
30045
|
+
import { mkdir as mkdir24, readFile as readFile31, writeFile as writeFile13 } from "fs/promises";
|
|
30046
|
+
import { join as join41 } from "path";
|
|
29265
30047
|
function record2(value) {
|
|
29266
30048
|
return typeof value === "object" && value !== null && !Array.isArray(value) ? value : void 0;
|
|
29267
30049
|
}
|
|
@@ -29397,19 +30179,19 @@ async def reset_acceptance(request: Request):
|
|
|
29397
30179
|
}
|
|
29398
30180
|
async function generateIntegrationArtifacts(projectDir, document, mappings, options = {}) {
|
|
29399
30181
|
if (mappings.some((mapping) => mapping.status === "needs-confirmation")) throw new Error("Confirm every uncertain OpenAPI mapping before generating integration code");
|
|
29400
|
-
const packagePath =
|
|
30182
|
+
const packagePath = join41(projectDir, "package.json");
|
|
29401
30183
|
let pkg;
|
|
29402
30184
|
try {
|
|
29403
|
-
pkg = JSON.parse(await
|
|
30185
|
+
pkg = JSON.parse(await readFile31(packagePath, "utf8"));
|
|
29404
30186
|
} catch {
|
|
29405
30187
|
throw new Error(`D2C integration requires a valid package.json at ${packagePath}`);
|
|
29406
30188
|
}
|
|
29407
30189
|
const scripts = options.pythonServer ? { ...record2(pkg.scripts) ?? {} } : { ...record2(pkg.scripts) ?? {}, mock: "node mock/server.mjs" };
|
|
29408
30190
|
const dependencies2 = options.pythonServer ? { ...record2(pkg.dependencies) ?? {}, axios: "^1.7.9" } : { ...record2(pkg.dependencies) ?? {}, axios: "^1.7.9", express: "^5.2.1" };
|
|
29409
30191
|
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 =
|
|
30192
|
+
const apiDir = join41(projectDir, "src", "api");
|
|
30193
|
+
const mockDir = join41(projectDir, "mock");
|
|
30194
|
+
const serverDir = join41(projectDir, "server");
|
|
29413
30195
|
await Promise.all([
|
|
29414
30196
|
mkdir24(apiDir, { recursive: true }),
|
|
29415
30197
|
...options.pythonServer ? [] : [mkdir24(mockDir, { recursive: true })],
|
|
@@ -29419,23 +30201,23 @@ async function generateIntegrationArtifacts(projectDir, document, mappings, opti
|
|
|
29419
30201
|
if (options.pythonServer) files.push("server/main.py", "server/requirements.txt", "server/README.md", "server/flavor-runtime.json");
|
|
29420
30202
|
else files.push("mock/server.mjs");
|
|
29421
30203
|
await Promise.all([
|
|
29422
|
-
writeFile13(
|
|
30204
|
+
writeFile13(join41(apiDir, "http.js"), `import axios from "axios";
|
|
29423
30205
|
|
|
29424
30206
|
export const http = axios.create({ baseURL: import.meta.env.VITE_API_BASE_URL || ${JSON.stringify(document.baseUrl ?? "")}, timeout: 15000 });
|
|
29425
30207
|
|
|
29426
30208
|
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
30209
|
`),
|
|
29428
|
-
writeFile13(
|
|
29429
|
-
writeFile13(
|
|
30210
|
+
writeFile13(join41(apiDir, "d2c-api.js"), apiModule(selected)),
|
|
30211
|
+
writeFile13(join41(apiDir, "d2c-bindings.json"), `${JSON.stringify({ schema: 1, generatedAt: (/* @__PURE__ */ new Date()).toISOString(), mappings }, null, 2)}
|
|
29430
30212
|
`),
|
|
29431
|
-
...options.pythonServer ? [] : [writeFile13(
|
|
30213
|
+
...options.pythonServer ? [] : [writeFile13(join41(mockDir, "server.mjs"), mockModule({ ...document, operations: selected }))],
|
|
29432
30214
|
writeFile13(packagePath, `${JSON.stringify({ ...pkg, scripts, dependencies: dependencies2 }, null, 2)}
|
|
29433
30215
|
`),
|
|
29434
30216
|
...options.pythonServer ? [
|
|
29435
|
-
writeFile13(
|
|
29436
|
-
writeFile13(
|
|
29437
|
-
writeFile13(
|
|
29438
|
-
writeFile13(
|
|
30217
|
+
writeFile13(join41(serverDir, "main.py"), pythonServerModule({ ...document, operations: selected })),
|
|
30218
|
+
writeFile13(join41(serverDir, "requirements.txt"), "fastapi>=0.116,<1\nuvicorn[standard]>=0.35,<1\nsqlalchemy>=2.0,<3\n"),
|
|
30219
|
+
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"),
|
|
30220
|
+
writeFile13(join41(serverDir, "flavor-runtime.json"), `${JSON.stringify({
|
|
29439
30221
|
schema: 1,
|
|
29440
30222
|
kind: "python-fastapi",
|
|
29441
30223
|
cwd: "server",
|
|
@@ -29454,9 +30236,9 @@ http.interceptors.response.use((response) => response, (error) => Promise.reject
|
|
|
29454
30236
|
|
|
29455
30237
|
// src/d2c/mock-runner.ts
|
|
29456
30238
|
import { spawn as spawn9 } from "child_process";
|
|
29457
|
-
import { access as access4, readFile as
|
|
30239
|
+
import { access as access4, readFile as readFile32 } from "fs/promises";
|
|
29458
30240
|
import { createServer as createServer5 } from "net";
|
|
29459
|
-
import { join as
|
|
30241
|
+
import { join as join42 } from "path";
|
|
29460
30242
|
async function exists3(path) {
|
|
29461
30243
|
try {
|
|
29462
30244
|
await access4(path);
|
|
@@ -29564,7 +30346,7 @@ ${output.trim()}` : ""}`);
|
|
|
29564
30346
|
}
|
|
29565
30347
|
}
|
|
29566
30348
|
async function runPythonBackend(projectDir, options) {
|
|
29567
|
-
const serverDir =
|
|
30349
|
+
const serverDir = join42(projectDir, "server");
|
|
29568
30350
|
if (options.installDependencies !== false) await installPython(serverDir, options.signal);
|
|
29569
30351
|
const selectedPort = await port();
|
|
29570
30352
|
const url = `http://127.0.0.1:${selectedPort}`;
|
|
@@ -29610,20 +30392,20 @@ ${managed.output().slice(-4e3)}`);
|
|
|
29610
30392
|
};
|
|
29611
30393
|
}
|
|
29612
30394
|
async function runD2cMockServer(projectDir, options = {}) {
|
|
29613
|
-
const runtimePath =
|
|
30395
|
+
const runtimePath = join42(projectDir, "server", "flavor-runtime.json");
|
|
29614
30396
|
if (await exists3(runtimePath)) {
|
|
29615
30397
|
let runtime;
|
|
29616
30398
|
try {
|
|
29617
|
-
runtime = JSON.parse(await
|
|
30399
|
+
runtime = JSON.parse(await readFile32(runtimePath, "utf8"));
|
|
29618
30400
|
} catch {
|
|
29619
30401
|
throw new Error(`E2E backend runtime manifest is invalid at ${runtimePath}`);
|
|
29620
30402
|
}
|
|
29621
30403
|
if (runtime.schema !== 1 || runtime.kind !== "python-fastapi") throw new Error("Unsupported E2E backend runtime manifest");
|
|
29622
30404
|
return runPythonBackend(projectDir, options);
|
|
29623
30405
|
}
|
|
29624
|
-
const serverPath =
|
|
30406
|
+
const serverPath = join42(projectDir, "mock", "server.mjs");
|
|
29625
30407
|
if (!await exists3(serverPath)) throw new Error(`D2C mock server is not generated at ${serverPath}`);
|
|
29626
|
-
if (options.installDependencies === false && !await exists3(
|
|
30408
|
+
if (options.installDependencies === false && !await exists3(join42(projectDir, "node_modules", "express", "package.json"))) {
|
|
29627
30409
|
throw new Error("D2C mock server dependencies are not installed");
|
|
29628
30410
|
}
|
|
29629
30411
|
if (options.installDependencies !== false) await install(projectDir, options.signal);
|
|
@@ -29665,13 +30447,13 @@ ${managed.output().slice(-4e3)}`);
|
|
|
29665
30447
|
|
|
29666
30448
|
// src/d2c/product.ts
|
|
29667
30449
|
import { createHash as createHash24, randomUUID as randomUUID27 } from "crypto";
|
|
29668
|
-
import { copyFile as copyFile2, mkdir as mkdir25, readFile as
|
|
29669
|
-
import { join as
|
|
30450
|
+
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";
|
|
30451
|
+
import { join as join43 } from "path";
|
|
29670
30452
|
import { z as z44 } from "zod";
|
|
29671
30453
|
|
|
29672
30454
|
// src/e2e/prd-governance.ts
|
|
29673
30455
|
import { createHash as createHash23, randomUUID as randomUUID26 } from "crypto";
|
|
29674
|
-
import { readFile as
|
|
30456
|
+
import { readFile as readFile33, rename as rename12, rm as rm13, writeFile as writeFile14 } from "fs/promises";
|
|
29675
30457
|
function hashPrd(markdown) {
|
|
29676
30458
|
return createHash23("sha256").update(markdown, "utf8").digest("hex");
|
|
29677
30459
|
}
|
|
@@ -29748,14 +30530,14 @@ function approvePrd(markdown, now = /* @__PURE__ */ new Date()) {
|
|
|
29748
30530
|
return { hash: hashPrd(markdown), approvedAt: now.toISOString(), criteria };
|
|
29749
30531
|
}
|
|
29750
30532
|
async function assertApprovedPrd(path, approved) {
|
|
29751
|
-
const current = await
|
|
30533
|
+
const current = await readFile33(path, "utf8");
|
|
29752
30534
|
if (hashPrd(current) !== approved.hash) {
|
|
29753
30535
|
throw new Error("PRD_LOCK_VIOLATION: confirmed PRD content changed after approval");
|
|
29754
30536
|
}
|
|
29755
30537
|
return approved;
|
|
29756
30538
|
}
|
|
29757
30539
|
async function updatePrdSectionFile(path, input) {
|
|
29758
|
-
const current = await
|
|
30540
|
+
const current = await readFile33(path, "utf8");
|
|
29759
30541
|
if (hashPrd(current) !== input.expectedHash) {
|
|
29760
30542
|
throw new Error("PRD_EDIT_CONFLICT: the PRD changed; refresh before saving this section");
|
|
29761
30543
|
}
|
|
@@ -29821,13 +30603,13 @@ var PlanSchema2 = z44.object({
|
|
|
29821
30603
|
}).strict();
|
|
29822
30604
|
function d2cProductDirectory(workspace, task) {
|
|
29823
30605
|
if (!D2C_TASK_PATTERN.test(task)) throw new Error("Invalid D2C task name");
|
|
29824
|
-
return
|
|
30606
|
+
return join43(taskDir(workspace, task), "product");
|
|
29825
30607
|
}
|
|
29826
30608
|
function d2cProductPrototypeDirectory(workspace, task) {
|
|
29827
|
-
return
|
|
30609
|
+
return join43(d2cProductDirectory(workspace, task), "prototype");
|
|
29828
30610
|
}
|
|
29829
30611
|
function planPath(workspace, task) {
|
|
29830
|
-
return
|
|
30612
|
+
return join43(taskDir(workspace, task), "product-plan.json");
|
|
29831
30613
|
}
|
|
29832
30614
|
function resolveD2cProductTechnology(requirement, fallbackFramework = "vue") {
|
|
29833
30615
|
const frontendCandidates = [
|
|
@@ -29915,18 +30697,18 @@ function buildD2cProductOpenApi(task, modules) {
|
|
|
29915
30697
|
`;
|
|
29916
30698
|
}
|
|
29917
30699
|
async function fileTimestamp(path) {
|
|
29918
|
-
const info = await
|
|
30700
|
+
const info = await stat15(path).catch(() => void 0);
|
|
29919
30701
|
return info?.isFile() ? info.mtime.toISOString() : void 0;
|
|
29920
30702
|
}
|
|
29921
30703
|
async function fileHash(path) {
|
|
29922
|
-
const buffer = await
|
|
30704
|
+
const buffer = await readFile34(path).catch((error) => {
|
|
29923
30705
|
if (error.code === "ENOENT") return void 0;
|
|
29924
30706
|
throw error;
|
|
29925
30707
|
});
|
|
29926
30708
|
return buffer === void 0 ? void 0 : createHash24("sha256").update(buffer).digest("hex");
|
|
29927
30709
|
}
|
|
29928
30710
|
async function validateAndNormalizePrototypeManifest(path) {
|
|
29929
|
-
const raw = await
|
|
30711
|
+
const raw = await readFile34(path, "utf8").catch((error) => {
|
|
29930
30712
|
if (error.code === "ENOENT") return void 0;
|
|
29931
30713
|
throw error;
|
|
29932
30714
|
});
|
|
@@ -30002,7 +30784,7 @@ async function createD2cProductPlan(workspace, input, now = () => /* @__PURE__ *
|
|
|
30002
30784
|
});
|
|
30003
30785
|
}
|
|
30004
30786
|
async function readD2cProductPlan(workspace, task) {
|
|
30005
|
-
const raw = await
|
|
30787
|
+
const raw = await readFile34(planPath(workspace, task), "utf8").catch((error) => {
|
|
30006
30788
|
if (error.code === "ENOENT") return void 0;
|
|
30007
30789
|
throw error;
|
|
30008
30790
|
});
|
|
@@ -30010,16 +30792,16 @@ async function readD2cProductPlan(workspace, task) {
|
|
|
30010
30792
|
}
|
|
30011
30793
|
async function discoverD2cProductArtifacts(workspace, plan) {
|
|
30012
30794
|
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 =
|
|
30795
|
+
const prdUpdatedAt = await fileTimestamp(join43(product, "prd.md"));
|
|
30796
|
+
const prdHash = await fileHash(join43(product, "prd.md"));
|
|
30797
|
+
const prototypeUpdatedAt = await fileTimestamp(join43(product, "prototype", "index.html"));
|
|
30798
|
+
const prototypeHash = await fileHash(join43(product, "prototype", "index.html"));
|
|
30799
|
+
const expectedManifest = join43(product, "prototype", "interaction-manifest.json");
|
|
30018
30800
|
let manifestUpdatedAt = await fileTimestamp(expectedManifest);
|
|
30019
30801
|
if (manifestUpdatedAt === void 0) {
|
|
30020
|
-
const misplacedManifest =
|
|
30802
|
+
const misplacedManifest = join43(product, "interaction-manifest.json");
|
|
30021
30803
|
if (await fileTimestamp(misplacedManifest) !== void 0) {
|
|
30022
|
-
await mkdir25(
|
|
30804
|
+
await mkdir25(join43(product, "prototype"), { recursive: true });
|
|
30023
30805
|
await copyFile2(misplacedManifest, expectedManifest);
|
|
30024
30806
|
manifestUpdatedAt = await fileTimestamp(expectedManifest);
|
|
30025
30807
|
}
|
|
@@ -30052,8 +30834,8 @@ async function readD2cProductPlanView(workspace, task) {
|
|
|
30052
30834
|
if (stored === void 0) return void 0;
|
|
30053
30835
|
const plan = await discoverD2cProductArtifacts(workspace, stored);
|
|
30054
30836
|
if (plan.revision !== stored.revision) await writeD2cProductPlan(workspace, plan);
|
|
30055
|
-
const prdMarkdown = plan.prd === void 0 ? void 0 : await
|
|
30056
|
-
const manifestPath =
|
|
30837
|
+
const prdMarkdown = plan.prd === void 0 ? void 0 : await readFile34(join43(taskDir(workspace, task), plan.prd.path), "utf8");
|
|
30838
|
+
const manifestPath = join43(d2cProductDirectory(workspace, task), "prototype", "interaction-manifest.json");
|
|
30057
30839
|
const validationError = plan.phase === "design-generating" && await fileTimestamp(manifestPath) !== void 0 ? await validateAndNormalizePrototypeManifest(manifestPath) : void 0;
|
|
30058
30840
|
return {
|
|
30059
30841
|
plan,
|
|
@@ -30143,9 +30925,9 @@ ${prdMarkdown.slice(0, 2e4)}`
|
|
|
30143
30925
|
|
|
30144
30926
|
// src/e2e/acceptance-baseline.ts
|
|
30145
30927
|
import { createHash as createHash25 } from "crypto";
|
|
30146
|
-
import { readFile as
|
|
30928
|
+
import { readFile as readFile35 } from "fs/promises";
|
|
30147
30929
|
async function fileArtifact(path) {
|
|
30148
|
-
const content = await
|
|
30930
|
+
const content = await readFile35(path);
|
|
30149
30931
|
return { path, hash: createHash25("sha256").update(content).digest("hex"), bytes: content.byteLength };
|
|
30150
30932
|
}
|
|
30151
30933
|
async function verifyAcceptanceArtifact(path, expected, label) {
|
|
@@ -30212,7 +30994,7 @@ function createAcceptanceEvidence(coverage, run, now = /* @__PURE__ */ new Date(
|
|
|
30212
30994
|
|
|
30213
30995
|
// src/e2e/delivery-run.ts
|
|
30214
30996
|
import { createHash as createHash26 } from "crypto";
|
|
30215
|
-
import { join as
|
|
30997
|
+
import { join as join44 } from "path";
|
|
30216
30998
|
import { z as z45 } from "zod";
|
|
30217
30999
|
var DELIVERY_NODE_IDS = ["requirement", "prd", "design", "d2c", "api", "acceptance", "delivery"];
|
|
30218
31000
|
var ArtifactSchema = z45.object({
|
|
@@ -30329,7 +31111,7 @@ function completeDeliveryNode(run, node, outputs, now = /* @__PURE__ */ new Date
|
|
|
30329
31111
|
return { ...run, nodes, updatedAt: timestamp2 };
|
|
30330
31112
|
}
|
|
30331
31113
|
function deliveryRunPath(workspace, task) {
|
|
30332
|
-
return
|
|
31114
|
+
return join44(taskDir(workspace, task), "delivery-run.json");
|
|
30333
31115
|
}
|
|
30334
31116
|
function decodeDeliveryRun(raw) {
|
|
30335
31117
|
return DeliveryRunSchema.parse(JSON.parse(raw));
|
|
@@ -30370,9 +31152,9 @@ async function updateDeliveryRun(workspace, task, expectedRevision, mutation) {
|
|
|
30370
31152
|
}
|
|
30371
31153
|
|
|
30372
31154
|
// src/skills/manager.ts
|
|
30373
|
-
import { lstat as lstat14, mkdir as mkdir26, readFile as
|
|
31155
|
+
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
31156
|
import { homedir as homedir4 } from "os";
|
|
30375
|
-
import { isAbsolute as isAbsolute19, join as
|
|
31157
|
+
import { isAbsolute as isAbsolute19, join as join45, relative as relative26, resolve as resolve37, sep as sep27 } from "path";
|
|
30376
31158
|
import { stringify } from "yaml";
|
|
30377
31159
|
var SkillManager = class {
|
|
30378
31160
|
#workspace;
|
|
@@ -30395,7 +31177,7 @@ var SkillManager = class {
|
|
|
30395
31177
|
async create(draft) {
|
|
30396
31178
|
const value = parseDraft(draft);
|
|
30397
31179
|
const projectRoot = this.#projectRoot();
|
|
30398
|
-
const directory =
|
|
31180
|
+
const directory = join45(projectRoot, value.name);
|
|
30399
31181
|
await mkdir26(projectRoot, { recursive: true, mode: 448 });
|
|
30400
31182
|
try {
|
|
30401
31183
|
await mkdir26(directory, { mode: 448 });
|
|
@@ -30404,7 +31186,7 @@ var SkillManager = class {
|
|
|
30404
31186
|
throw error;
|
|
30405
31187
|
}
|
|
30406
31188
|
try {
|
|
30407
|
-
await writeFile16(
|
|
31189
|
+
await writeFile16(join45(directory, "SKILL.md"), renderSkill(value), { encoding: "utf8", flag: "wx", mode: 384 });
|
|
30408
31190
|
await this.#persistEnabled(value.name, true);
|
|
30409
31191
|
return this.get(value.name);
|
|
30410
31192
|
} catch (error) {
|
|
@@ -30422,7 +31204,7 @@ var SkillManager = class {
|
|
|
30422
31204
|
throw error;
|
|
30423
31205
|
});
|
|
30424
31206
|
if (!info.isDirectory() || info.isSymbolicLink()) throw new Error(`Project skill path is not a real directory: ${validName}`);
|
|
30425
|
-
const skillFile =
|
|
31207
|
+
const skillFile = join45(directory, "SKILL.md");
|
|
30426
31208
|
const fileInfo = await lstat14(skillFile);
|
|
30427
31209
|
if (!fileInfo.isFile() || fileInfo.isSymbolicLink()) throw new Error(`Project skill file is not a real file: ${validName}`);
|
|
30428
31210
|
await writeFile16(skillFile, renderSkill(value), { encoding: "utf8", mode: 384 });
|
|
@@ -30454,12 +31236,12 @@ var SkillManager = class {
|
|
|
30454
31236
|
async #registry() {
|
|
30455
31237
|
const loaded = await loadConfig({ cwd: this.#workspace, home: this.#home });
|
|
30456
31238
|
const [globalPluginRoots, projectPluginRoots] = await Promise.all([
|
|
30457
|
-
discoverPluginSkillRoots(
|
|
30458
|
-
discoverPluginSkillRoots(
|
|
31239
|
+
discoverPluginSkillRoots(join45(this.#home, ".flavor-code", "plugins")),
|
|
31240
|
+
discoverPluginSkillRoots(join45(this.#workspace, ".flavor", "plugins"))
|
|
30459
31241
|
]);
|
|
30460
31242
|
return {
|
|
30461
31243
|
registry: new SkillRegistry({
|
|
30462
|
-
globalRoots: [
|
|
31244
|
+
globalRoots: [join45(this.#home, ".flavor-code", "skills"), ...globalPluginRoots],
|
|
30463
31245
|
projectRoots: [this.#projectRoot(), ...projectPluginRoots]
|
|
30464
31246
|
}),
|
|
30465
31247
|
disabled: new Set(loaded.config.skills.disabled)
|
|
@@ -30475,10 +31257,10 @@ var SkillManager = class {
|
|
|
30475
31257
|
};
|
|
30476
31258
|
}
|
|
30477
31259
|
#projectRoot() {
|
|
30478
|
-
return
|
|
31260
|
+
return join45(this.#workspace, ".flavor", "skills");
|
|
30479
31261
|
}
|
|
30480
31262
|
#projectSkillDirectory(name) {
|
|
30481
|
-
return
|
|
31263
|
+
return join45(this.#projectRoot(), name);
|
|
30482
31264
|
}
|
|
30483
31265
|
};
|
|
30484
31266
|
function parseName(value) {
|
|
@@ -30519,9 +31301,9 @@ async function discoverPluginSkillRoots(directory) {
|
|
|
30519
31301
|
const roots = [];
|
|
30520
31302
|
for (const entry2 of entries) {
|
|
30521
31303
|
if (!entry2.isDirectory() || entry2.isSymbolicLink()) continue;
|
|
30522
|
-
const pluginRoot = await realpath13(
|
|
31304
|
+
const pluginRoot = await realpath13(join45(directory, entry2.name));
|
|
30523
31305
|
try {
|
|
30524
|
-
const manifest = PluginManifestSchema.parse(JSON.parse(await
|
|
31306
|
+
const manifest = PluginManifestSchema.parse(JSON.parse(await readFile36(join45(pluginRoot, "flavor-plugin.json"), "utf8")));
|
|
30525
31307
|
for (const contribution of manifest.contributes.skillRoots) {
|
|
30526
31308
|
const candidate = resolve37(pluginRoot, contribution.path);
|
|
30527
31309
|
const info = await lstat14(candidate);
|
|
@@ -30596,11 +31378,11 @@ async function createProjectMemoryManager(options) {
|
|
|
30596
31378
|
}
|
|
30597
31379
|
|
|
30598
31380
|
// src/mcp/config-manager.ts
|
|
30599
|
-
import { join as
|
|
31381
|
+
import { join as join46 } from "path";
|
|
30600
31382
|
var ProjectMcpConfigManager = class {
|
|
30601
31383
|
path;
|
|
30602
31384
|
constructor(workspace) {
|
|
30603
|
-
this.path =
|
|
31385
|
+
this.path = join46(workspace, ".flavor", "flavor.json");
|
|
30604
31386
|
}
|
|
30605
31387
|
async list() {
|
|
30606
31388
|
const document = await readRecoverableFile(this.path, (raw) => parseDocument2(this.path, raw));
|
|
@@ -30697,7 +31479,7 @@ function isRecord7(value) {
|
|
|
30697
31479
|
}
|
|
30698
31480
|
|
|
30699
31481
|
// src/desktop/model-config.ts
|
|
30700
|
-
import { join as
|
|
31482
|
+
import { join as join47 } from "path";
|
|
30701
31483
|
var DEFAULT_DESKTOP_MODELS = [
|
|
30702
31484
|
{
|
|
30703
31485
|
id: "anthropic:deepseek-v4-pro",
|
|
@@ -30718,7 +31500,7 @@ var DEFAULT_DESKTOP_MODELS = [
|
|
|
30718
31500
|
];
|
|
30719
31501
|
async function loadDesktopModels(workspace, home) {
|
|
30720
31502
|
try {
|
|
30721
|
-
const tokens2 = await createFileTokenStore(
|
|
31503
|
+
const tokens2 = await createFileTokenStore(join47(home, ".flavor-code", "auth.json")).load();
|
|
30722
31504
|
const pkceModels = [];
|
|
30723
31505
|
const knownPkce = /* @__PURE__ */ new Set();
|
|
30724
31506
|
for (const token of Object.values(tokens2)) {
|
|
@@ -30786,7 +31568,7 @@ function protocolLabel(type) {
|
|
|
30786
31568
|
|
|
30787
31569
|
// src/desktop/d2c-product-preview.ts
|
|
30788
31570
|
import { createReadStream as createReadStream2 } from "fs";
|
|
30789
|
-
import { realpath as realpath14, stat as
|
|
31571
|
+
import { realpath as realpath14, stat as stat16 } from "fs/promises";
|
|
30790
31572
|
import { createServer as createServer6 } from "http";
|
|
30791
31573
|
import { extname as extname4, isAbsolute as isAbsolute20, relative as relative27, resolve as resolve38, sep as sep28 } from "path";
|
|
30792
31574
|
var MIME_TYPES = {
|
|
@@ -30811,7 +31593,7 @@ function isInside(root, candidate) {
|
|
|
30811
31593
|
}
|
|
30812
31594
|
async function startD2cProductPreview(rootDirectory) {
|
|
30813
31595
|
const root = await realpath14(resolve38(rootDirectory));
|
|
30814
|
-
const rootInfo = await
|
|
31596
|
+
const rootInfo = await stat16(root);
|
|
30815
31597
|
if (!rootInfo.isDirectory()) throw new Error("D2C product prototype directory is unavailable");
|
|
30816
31598
|
const server = createServer6(async (request, response2) => {
|
|
30817
31599
|
response2.setHeader("cache-control", "no-store");
|
|
@@ -30826,7 +31608,7 @@ async function startD2cProductPreview(rootDirectory) {
|
|
|
30826
31608
|
if (!isInside(root, unresolved)) return sendNotFound(response2);
|
|
30827
31609
|
const target = await realpath14(unresolved).catch(() => void 0);
|
|
30828
31610
|
if (target === void 0 || !isInside(root, target)) return sendNotFound(response2);
|
|
30829
|
-
const info = await
|
|
31611
|
+
const info = await stat16(target);
|
|
30830
31612
|
if (!info.isFile()) return sendNotFound(response2);
|
|
30831
31613
|
response2.writeHead(200, {
|
|
30832
31614
|
"content-length": info.size,
|
|
@@ -30863,33 +31645,33 @@ function pngDataUrl(png) {
|
|
|
30863
31645
|
}
|
|
30864
31646
|
async function detectD2cFramework(workspace, task) {
|
|
30865
31647
|
try {
|
|
30866
|
-
const pkg = JSON.parse(await
|
|
31648
|
+
const pkg = JSON.parse(await readFile37(join48(d2cOutputDirectory(workspace, task), "package.json"), "utf8"));
|
|
30867
31649
|
return pkg.dependencies?.react === void 0 ? "vue" : "react";
|
|
30868
31650
|
} catch {
|
|
30869
31651
|
return "vue";
|
|
30870
31652
|
}
|
|
30871
31653
|
}
|
|
30872
31654
|
function integrationDirectory(workspace, task) {
|
|
30873
|
-
return
|
|
31655
|
+
return join48(workspace, ".flavor", "d2c", task, "integration");
|
|
30874
31656
|
}
|
|
30875
31657
|
var D2C_BACKEND_SOURCE_EXTENSIONS = /* @__PURE__ */ new Set([".cfg", ".env", ".ini", ".js", ".json", ".lock", ".mjs", ".py", ".toml", ".txt", ".yaml", ".yml"]);
|
|
30876
31658
|
var D2C_BACKEND_IGNORED_DIRECTORIES = /* @__PURE__ */ new Set([".git", ".venv", "__pycache__", "data", "node_modules", "venv"]);
|
|
30877
31659
|
async function d2cBackendSourceFingerprint(project) {
|
|
30878
|
-
const runtimeManifest =
|
|
30879
|
-
const realBackend = (await
|
|
30880
|
-
const root = realBackend ?
|
|
30881
|
-
if ((await
|
|
31660
|
+
const runtimeManifest = join48(project, "server", "flavor-runtime.json");
|
|
31661
|
+
const realBackend = (await stat17(runtimeManifest).catch(() => void 0))?.isFile() === true;
|
|
31662
|
+
const root = realBackend ? join48(project, "server") : join48(project, "mock");
|
|
31663
|
+
if ((await stat17(root).catch(() => void 0))?.isDirectory() !== true) return void 0;
|
|
30882
31664
|
const files = [];
|
|
30883
31665
|
const visit = async (directory) => {
|
|
30884
31666
|
const entries = await readdir13(directory, { withFileTypes: true });
|
|
30885
31667
|
for (const entry2 of entries) {
|
|
30886
31668
|
if (entry2.isDirectory()) {
|
|
30887
|
-
if (!D2C_BACKEND_IGNORED_DIRECTORIES.has(entry2.name)) await visit(
|
|
31669
|
+
if (!D2C_BACKEND_IGNORED_DIRECTORIES.has(entry2.name)) await visit(join48(directory, entry2.name));
|
|
30888
31670
|
continue;
|
|
30889
31671
|
}
|
|
30890
31672
|
if (!entry2.isFile()) continue;
|
|
30891
31673
|
const extension2 = entry2.name.includes(".") ? `.${entry2.name.split(".").pop().toLowerCase()}` : "";
|
|
30892
|
-
if (D2C_BACKEND_SOURCE_EXTENSIONS.has(extension2)) files.push(
|
|
31674
|
+
if (D2C_BACKEND_SOURCE_EXTENSIONS.has(extension2)) files.push(join48(directory, entry2.name));
|
|
30893
31675
|
}
|
|
30894
31676
|
};
|
|
30895
31677
|
await visit(root);
|
|
@@ -30897,7 +31679,7 @@ async function d2cBackendSourceFingerprint(project) {
|
|
|
30897
31679
|
for (const path of files.sort()) {
|
|
30898
31680
|
hash.update(path.slice(root.length));
|
|
30899
31681
|
hash.update("\0");
|
|
30900
|
-
hash.update(await
|
|
31682
|
+
hash.update(await readFile37(path));
|
|
30901
31683
|
hash.update("\0");
|
|
30902
31684
|
}
|
|
30903
31685
|
return hash.digest("hex");
|
|
@@ -30933,6 +31715,7 @@ var DesktopRuntimeController = class {
|
|
|
30933
31715
|
#busy = false;
|
|
30934
31716
|
#tasks;
|
|
30935
31717
|
#disposeJobSubscription;
|
|
31718
|
+
#memoryRestartPromise;
|
|
30936
31719
|
#d2cMocks = /* @__PURE__ */ new Map();
|
|
30937
31720
|
#d2cPreviews = /* @__PURE__ */ new Map();
|
|
30938
31721
|
#d2cProductPreviews = /* @__PURE__ */ new Map();
|
|
@@ -31059,6 +31842,7 @@ var DesktopRuntimeController = class {
|
|
|
31059
31842
|
this.#captureSessionOutput(event);
|
|
31060
31843
|
this.#emit({ type: "session-output", sessionId: outputSessionId, event });
|
|
31061
31844
|
},
|
|
31845
|
+
onMemoryRestartRequested: (sessionId) => this.#scheduleMemoryRestart(sessionId),
|
|
31062
31846
|
onApprovalChange: () => {
|
|
31063
31847
|
if (this.#runtime !== void 0) this.#publishSnapshot();
|
|
31064
31848
|
},
|
|
@@ -31443,8 +32227,8 @@ var DesktopRuntimeController = class {
|
|
|
31443
32227
|
if (workflow === void 0 || !reviewProgress(workflow).complete || workflow.stage === "visual-review") {
|
|
31444
32228
|
throw new Error("Complete visual review before importing OpenAPI");
|
|
31445
32229
|
}
|
|
31446
|
-
const moduleManifest =
|
|
31447
|
-
const moduleManifestInfo = await
|
|
32230
|
+
const moduleManifest = join48(d2cOutputDirectory(workspace, task), "d2c.modules.json");
|
|
32231
|
+
const moduleManifestInfo = await stat17(moduleManifest).catch(() => void 0);
|
|
31448
32232
|
if (moduleManifestInfo === void 0 || !moduleManifestInfo.isFile()) {
|
|
31449
32233
|
throw new Error("D2C implementation must write d2c.modules.json before API mapping; ask the agent to split the project into modules first");
|
|
31450
32234
|
}
|
|
@@ -31456,8 +32240,8 @@ var DesktopRuntimeController = class {
|
|
|
31456
32240
|
await mkdir27(dir, { recursive: true });
|
|
31457
32241
|
const hash = createHash27("sha256").update(raw).digest("hex");
|
|
31458
32242
|
await Promise.all([
|
|
31459
|
-
writeFile17(
|
|
31460
|
-
writeFile17(
|
|
32243
|
+
writeFile17(join48(dir, "swagger.json"), raw),
|
|
32244
|
+
writeFile17(join48(dir, "openapi.normalized.json"), `${JSON.stringify(document, null, 2)}
|
|
31461
32245
|
`)
|
|
31462
32246
|
]);
|
|
31463
32247
|
const next = {
|
|
@@ -31481,9 +32265,9 @@ var DesktopRuntimeController = class {
|
|
|
31481
32265
|
const workspace = this.#workspace;
|
|
31482
32266
|
if (workspace === void 0) throw new Error("Open a project before importing OpenAPI");
|
|
31483
32267
|
await this.#assertProductPrdLocked(workspace, task);
|
|
31484
|
-
const info = await
|
|
32268
|
+
const info = await stat17(sourcePath);
|
|
31485
32269
|
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
|
|
32270
|
+
return this.#storeD2cOpenApi(workspace, task, basename10(sourcePath), await readFile37(sourcePath, "utf8"), false);
|
|
31487
32271
|
}
|
|
31488
32272
|
async getD2cIntegration(task) {
|
|
31489
32273
|
const workspace = this.#workspace;
|
|
@@ -31492,8 +32276,8 @@ var DesktopRuntimeController = class {
|
|
|
31492
32276
|
let workflow = await readWorkflow(workspace, task);
|
|
31493
32277
|
if (workflow?.openapi === void 0 && workflow !== void 0 && reviewProgress(workflow).complete && await readD2cProductPlanView(workspace, task) !== void 0) {
|
|
31494
32278
|
const modules = await readD2cModules(workspace, task);
|
|
31495
|
-
const productOpenApiPath =
|
|
31496
|
-
const authored = await
|
|
32279
|
+
const productOpenApiPath = join48(taskDir(workspace, task), "product", "openapi.json");
|
|
32280
|
+
const authored = await readFile37(productOpenApiPath, "utf8").catch(() => void 0);
|
|
31497
32281
|
const generated = authored === void 0;
|
|
31498
32282
|
const prepared = await this.#storeD2cOpenApi(
|
|
31499
32283
|
workspace,
|
|
@@ -31505,7 +32289,7 @@ var DesktopRuntimeController = class {
|
|
|
31505
32289
|
return prepared;
|
|
31506
32290
|
}
|
|
31507
32291
|
if (workflow?.openapi === void 0 || workflow.mappings === void 0) return void 0;
|
|
31508
|
-
const document = JSON.parse(await
|
|
32292
|
+
const document = JSON.parse(await readFile37(join48(integrationDirectory(workspace, task), "openapi.normalized.json"), "utf8"));
|
|
31509
32293
|
return { workflow, document, mappings: workflow.mappings };
|
|
31510
32294
|
}
|
|
31511
32295
|
async confirmD2cMapping(task, moduleId, operationKey) {
|
|
@@ -31572,7 +32356,7 @@ var DesktopRuntimeController = class {
|
|
|
31572
32356
|
].join("\n");
|
|
31573
32357
|
const delivery = await readDeliveryRun(workspace, task);
|
|
31574
32358
|
if (delivery?.nodes.api.status === "running") {
|
|
31575
|
-
const apiArtifacts = await Promise.all(generated.files.map(async (file) => artifactRef(file, await
|
|
32359
|
+
const apiArtifacts = await Promise.all(generated.files.map(async (file) => artifactRef(file, await readFile37(join48(project, file)))));
|
|
31576
32360
|
const completed = await updateDeliveryRun(
|
|
31577
32361
|
workspace,
|
|
31578
32362
|
task,
|
|
@@ -31599,8 +32383,8 @@ var DesktopRuntimeController = class {
|
|
|
31599
32383
|
try {
|
|
31600
32384
|
const fingerprint2 = await d2cBackendSourceFingerprint(project);
|
|
31601
32385
|
if (fingerprint2 !== void 0) this.#d2cMockSourceFingerprints.set(task, fingerprint2);
|
|
31602
|
-
const environmentPath =
|
|
31603
|
-
const existingEnvironment = await
|
|
32386
|
+
const environmentPath = join48(project, ".env.local");
|
|
32387
|
+
const existingEnvironment = await readFile37(environmentPath, "utf8").catch(() => "");
|
|
31604
32388
|
const environment = `${existingEnvironment.split(/\r?\n/).filter((line) => !/^\s*VITE_API_BASE_URL\s*=/.test(line)).join("\n").trim()}
|
|
31605
32389
|
VITE_API_BASE_URL=${running.url}
|
|
31606
32390
|
`.replace(/^\n/, "");
|
|
@@ -31680,8 +32464,8 @@ VITE_API_BASE_URL=${running.url}
|
|
|
31680
32464
|
const readyPreview = this.#d2cPreviews.get(task);
|
|
31681
32465
|
const readyMock = this.#d2cMocks.get(task);
|
|
31682
32466
|
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
|
|
32467
|
+
const manifestPath = join48(taskDir(workspace, task), "design", "interaction-manifest.json");
|
|
32468
|
+
const manifest = parseInteractionManifest(await readFile37(manifestPath, "utf8"));
|
|
31685
32469
|
const baseline = await this.#verifyAcceptanceBaseline(workspace, task);
|
|
31686
32470
|
const coverage = baseline === void 0 ? void 0 : verifyRequirementCoverage(baseline.approvedPrd.criteria, manifest);
|
|
31687
32471
|
let executableManifest = manifest;
|
|
@@ -31692,7 +32476,7 @@ VITE_API_BASE_URL=${running.url}
|
|
|
31692
32476
|
await mkdir27(planDir, { recursive: true });
|
|
31693
32477
|
try {
|
|
31694
32478
|
const observations = await this.#observeD2cPages(manifest, readyPreview.url);
|
|
31695
|
-
const prd = await
|
|
32479
|
+
const prd = await readFile37(join48(taskDir(workspace, task), "product", "prd.md"), "utf8").catch(() => void 0);
|
|
31696
32480
|
const apiContext = JSON.stringify({ openapi: workflow.openapi, mappings: workflow.mappings }, null, 2);
|
|
31697
32481
|
const plan = await this.#d2cJudge.planInteractions({ task, seed: manifest, observations, ...prd === void 0 ? {} : { prd }, apiContext });
|
|
31698
32482
|
executableManifest = mergeInteractionManifests(manifest, plan.manifest);
|
|
@@ -31703,9 +32487,9 @@ VITE_API_BASE_URL=${running.url}
|
|
|
31703
32487
|
plannedScenarios: executableManifest.pages.reduce((total, page) => total + page.scenarios.length, 0),
|
|
31704
32488
|
pageAnalyses: plan.pageAnalyses
|
|
31705
32489
|
};
|
|
31706
|
-
await writeFile17(
|
|
32490
|
+
await writeFile17(join48(planDir, "autonomous-interaction-plan.json"), `${JSON.stringify({ ...plan, manifest: executableManifest }, null, 2)}
|
|
31707
32491
|
`, { encoding: "utf8", mode: 384 });
|
|
31708
|
-
await rm15(
|
|
32492
|
+
await rm15(join48(planDir, "autonomous-interaction-diagnostic.json"), { force: true });
|
|
31709
32493
|
} catch (cause) {
|
|
31710
32494
|
const detail = message(cause);
|
|
31711
32495
|
review = {
|
|
@@ -31713,8 +32497,8 @@ VITE_API_BASE_URL=${running.url}
|
|
|
31713
32497
|
plannedScenarios: manifest.pages.reduce((total, page) => total + page.scenarios.length, 0),
|
|
31714
32498
|
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
32499
|
};
|
|
31716
|
-
await rm15(
|
|
31717
|
-
await writeFile17(
|
|
32500
|
+
await rm15(join48(planDir, "autonomous-interaction-plan.json"), { force: true });
|
|
32501
|
+
await writeFile17(join48(planDir, "autonomous-interaction-diagnostic.json"), `${JSON.stringify({
|
|
31718
32502
|
schema: 1,
|
|
31719
32503
|
stage: "planning",
|
|
31720
32504
|
occurredAt: (/* @__PURE__ */ new Date()).toISOString(),
|
|
@@ -31728,13 +32512,13 @@ VITE_API_BASE_URL=${running.url}
|
|
|
31728
32512
|
let result = { ...rawResult, evidenceMode: review.mode };
|
|
31729
32513
|
if (coverage !== void 0) {
|
|
31730
32514
|
const evidence = createAcceptanceEvidence(coverage, result);
|
|
31731
|
-
await writeFile17(
|
|
32515
|
+
await writeFile17(join48(taskDir(workspace, task), "acceptance-evidence.json"), `${JSON.stringify({ ...evidence, baseline }, null, 2)}
|
|
31732
32516
|
`, { encoding: "utf8", mode: 384 });
|
|
31733
32517
|
if (!evidence.passed) result = { ...result, passed: false, failures: Math.max(1, result.failures) };
|
|
31734
32518
|
}
|
|
31735
32519
|
const dir = integrationDirectory(workspace, task);
|
|
31736
32520
|
await mkdir27(dir, { recursive: true });
|
|
31737
|
-
await writeFile17(
|
|
32521
|
+
await writeFile17(join48(dir, "interaction-results.json"), `${JSON.stringify(result, null, 2)}
|
|
31738
32522
|
`);
|
|
31739
32523
|
const next = await updateWorkflow(workspace, task, workflow.revision, (current) => applyInteractionRun(current, result));
|
|
31740
32524
|
return { workflow: next, result, review };
|
|
@@ -31747,7 +32531,7 @@ VITE_API_BASE_URL=${running.url}
|
|
|
31747
32531
|
if (mock === void 0 || !mock.exited() || result.failures === 0) return result;
|
|
31748
32532
|
const output = mock.output().trim();
|
|
31749
32533
|
try {
|
|
31750
|
-
const dir =
|
|
32534
|
+
const dir = join48(d2cOutputDirectory(workspace, task), "mock-server.log");
|
|
31751
32535
|
await writeFile17(dir, `${output}
|
|
31752
32536
|
`);
|
|
31753
32537
|
} catch {
|
|
@@ -31842,7 +32626,7 @@ VITE_API_BASE_URL=${running.url}
|
|
|
31842
32626
|
if (current === void 0) throw new Error("D2C product plan is unavailable");
|
|
31843
32627
|
if (current.plan.approvedPrd !== void 0) throw new Error("The approved PRD is immutable and cannot be edited");
|
|
31844
32628
|
if (current.plan.phase !== "prd-review") throw new Error("PRD sections can only be edited during PRD review");
|
|
31845
|
-
const path =
|
|
32629
|
+
const path = join48(taskDir(workspace, task), "product", "prd.md");
|
|
31846
32630
|
const updated = await updatePrdSectionFile(path, { sectionId, body, expectedHash });
|
|
31847
32631
|
const now = (/* @__PURE__ */ new Date()).toISOString();
|
|
31848
32632
|
await writeD2cProductPlan(workspace, {
|
|
@@ -31862,33 +32646,33 @@ VITE_API_BASE_URL=${running.url}
|
|
|
31862
32646
|
async #assertProductPrdLocked(workspace, task) {
|
|
31863
32647
|
const current = await readD2cProductPlanView(workspace, task);
|
|
31864
32648
|
if (current?.plan.approvedPrd === void 0) return;
|
|
31865
|
-
await assertApprovedPrd(
|
|
32649
|
+
await assertApprovedPrd(join48(taskDir(workspace, task), "product", "prd.md"), current.plan.approvedPrd);
|
|
31866
32650
|
}
|
|
31867
32651
|
async #acceptancePaths(workspace, task) {
|
|
31868
|
-
const product =
|
|
31869
|
-
const openapi =
|
|
32652
|
+
const product = join48(taskDir(workspace, task), "product");
|
|
32653
|
+
const openapi = join48(product, "openapi.json");
|
|
31870
32654
|
return {
|
|
31871
|
-
prd:
|
|
31872
|
-
prototype:
|
|
31873
|
-
interaction:
|
|
31874
|
-
...await
|
|
32655
|
+
prd: join48(product, "prd.md"),
|
|
32656
|
+
prototype: join48(product, "prototype", "index.html"),
|
|
32657
|
+
interaction: join48(product, "prototype", "interaction-manifest.json"),
|
|
32658
|
+
...await stat17(openapi).catch(() => void 0) === void 0 ? {} : { openapi }
|
|
31875
32659
|
};
|
|
31876
32660
|
}
|
|
31877
32661
|
async #verifyAcceptanceBaseline(workspace, task) {
|
|
31878
32662
|
const product = await readD2cProductPlanView(workspace, task);
|
|
31879
32663
|
if (product?.plan.approvedPrd === void 0) return void 0;
|
|
31880
32664
|
await this.#assertProductPrdLocked(workspace, task);
|
|
31881
|
-
const baseline = JSON.parse(await
|
|
32665
|
+
const baseline = JSON.parse(await readFile37(join48(taskDir(workspace, task), "acceptance-baseline.json"), "utf8"));
|
|
31882
32666
|
await verifyAcceptanceBaseline(await this.#acceptancePaths(workspace, task), baseline);
|
|
31883
|
-
const design =
|
|
32667
|
+
const design = join48(taskDir(workspace, task), "design");
|
|
31884
32668
|
await Promise.all([
|
|
31885
|
-
verifyAcceptanceArtifact(
|
|
31886
|
-
verifyAcceptanceArtifact(
|
|
32669
|
+
verifyAcceptanceArtifact(join48(design, "index.html"), baseline.artifacts.prototype, "runtime prototype"),
|
|
32670
|
+
verifyAcceptanceArtifact(join48(design, "interaction-manifest.json"), baseline.artifacts.interaction, "runtime interaction contract")
|
|
31887
32671
|
]);
|
|
31888
32672
|
const workflow = await readWorkflow(workspace, task);
|
|
31889
32673
|
if (workflow?.openapi !== void 0) {
|
|
31890
32674
|
await verifyAcceptanceArtifact(
|
|
31891
|
-
|
|
32675
|
+
join48(taskDir(workspace, task), "integration", "swagger.json"),
|
|
31892
32676
|
{ hash: workflow.openapi.hash },
|
|
31893
32677
|
"runtime OpenAPI contract"
|
|
31894
32678
|
);
|
|
@@ -31909,14 +32693,14 @@ VITE_API_BASE_URL=${running.url}
|
|
|
31909
32693
|
throw new Error("D2C PRD is empty and cannot be accepted");
|
|
31910
32694
|
}
|
|
31911
32695
|
if (stage === "design" && accepted) {
|
|
31912
|
-
const manifest =
|
|
31913
|
-
const normalizedManifest = parseInteractionManifest(await
|
|
32696
|
+
const manifest = join48(d2cProductPrototypeDirectory(workspace, task), "interaction-manifest.json");
|
|
32697
|
+
const normalizedManifest = parseInteractionManifest(await readFile37(manifest, "utf8"));
|
|
31914
32698
|
if (current.plan.approvedPrd === void 0) throw new Error("Approve the PRD before confirming the design");
|
|
31915
32699
|
verifyRequirementCoverage(current.plan.approvedPrd.criteria, normalizedManifest);
|
|
31916
32700
|
await writeFile17(manifest, `${JSON.stringify(normalizedManifest, null, 2)}
|
|
31917
32701
|
`, { encoding: "utf8", mode: 384 });
|
|
31918
32702
|
const baseline = await captureAcceptanceBaseline(await this.#acceptancePaths(workspace, task), current.plan.approvedPrd);
|
|
31919
|
-
await writeFile17(
|
|
32703
|
+
await writeFile17(join48(taskDir(workspace, task), "acceptance-baseline.json"), `${JSON.stringify(baseline, null, 2)}
|
|
31920
32704
|
`, { encoding: "utf8", mode: 384 });
|
|
31921
32705
|
}
|
|
31922
32706
|
const plan = await writeD2cProductPlan(
|
|
@@ -31950,8 +32734,8 @@ VITE_API_BASE_URL=${running.url}
|
|
|
31950
32734
|
const imported = await this.importD2cDesign(task, d2cProductPrototypeDirectory(workspace, task));
|
|
31951
32735
|
const delivery = await readDeliveryRun(workspace, task);
|
|
31952
32736
|
if (delivery !== void 0) {
|
|
31953
|
-
const prototype = await
|
|
31954
|
-
const interaction = await
|
|
32737
|
+
const prototype = await readFile37(join48(d2cProductPrototypeDirectory(workspace, task), "index.html"), "utf8");
|
|
32738
|
+
const interaction = await readFile37(join48(d2cProductPrototypeDirectory(workspace, task), "interaction-manifest.json"), "utf8");
|
|
31955
32739
|
const completed = await updateDeliveryRun(workspace, task, delivery.revision, (run) => completeDeliveryNode(run, "design", [
|
|
31956
32740
|
artifactRef("product/prototype/index.html", prototype),
|
|
31957
32741
|
artifactRef("product/prototype/interaction-manifest.json", interaction)
|
|
@@ -32035,10 +32819,10 @@ VITE_API_BASE_URL=${running.url}
|
|
|
32035
32819
|
const next = await updateWorkflow(workspace, task, workflow.revision, (current) => applyQualityJudgment(current, judgment));
|
|
32036
32820
|
const qualityContent = `${JSON.stringify(judgment, null, 2)}
|
|
32037
32821
|
`;
|
|
32038
|
-
await writeFile17(
|
|
32822
|
+
await writeFile17(join48(taskDir(workspace, task), "quality-judge.json"), qualityContent, { encoding: "utf8", mode: 384 });
|
|
32039
32823
|
const delivery = await readDeliveryRun(workspace, task);
|
|
32040
32824
|
if (next.stage === "completed" && delivery?.nodes.acceptance.status === "running") {
|
|
32041
|
-
const interactionContent = await
|
|
32825
|
+
const interactionContent = await readFile37(join48(integrationDirectory(workspace, task), "interaction-results.json"), "utf8");
|
|
32042
32826
|
const accepted = await updateDeliveryRun(workspace, task, delivery.revision, (run) => completeDeliveryNode(run, "acceptance", [
|
|
32043
32827
|
artifactRef("integration/interaction-results.json", interactionContent),
|
|
32044
32828
|
artifactRef("quality-judge.json", qualityContent)
|
|
@@ -32120,6 +32904,39 @@ VITE_API_BASE_URL=${running.url}
|
|
|
32120
32904
|
await runtime.session.close();
|
|
32121
32905
|
await runtime.dispose();
|
|
32122
32906
|
}
|
|
32907
|
+
/**
|
|
32908
|
+
* Electron owns the process, so a heap rotation rebuilds only the production
|
|
32909
|
+
* runtime. Disposing every holder and forcing GC gives the resumed /loop or
|
|
32910
|
+
* /goal a clean live set without terminating the desktop application.
|
|
32911
|
+
*/
|
|
32912
|
+
#scheduleMemoryRestart(sessionId) {
|
|
32913
|
+
if (this.#memoryRestartPromise !== void 0) return;
|
|
32914
|
+
const previous = this.#runtime;
|
|
32915
|
+
this.#memoryRestartPromise = (async () => {
|
|
32916
|
+
await previous?.session.whenIdle?.().catch(() => void 0);
|
|
32917
|
+
if (previous === void 0 || this.#runtime !== previous) return;
|
|
32918
|
+
await this.#disposeRuntime();
|
|
32919
|
+
clearMemoryRotation();
|
|
32920
|
+
const gc = globalThis.gc;
|
|
32921
|
+
if (typeof gc === "function") {
|
|
32922
|
+
gc();
|
|
32923
|
+
gc();
|
|
32924
|
+
}
|
|
32925
|
+
await this.startSession(sessionId);
|
|
32926
|
+
const resumed = this.#runtime;
|
|
32927
|
+
if (resumed !== void 0) {
|
|
32928
|
+
this.#busy = true;
|
|
32929
|
+
this.#publishSnapshot();
|
|
32930
|
+
await resumed.session.whenIdle?.();
|
|
32931
|
+
}
|
|
32932
|
+
})().catch((error) => {
|
|
32933
|
+
this.#emit({ type: "runtime-error", sessionId, message: `Memory restart failed: ${message(error)}` });
|
|
32934
|
+
}).finally(() => {
|
|
32935
|
+
this.#memoryRestartPromise = void 0;
|
|
32936
|
+
this.#busy = false;
|
|
32937
|
+
this.#publishSnapshot();
|
|
32938
|
+
});
|
|
32939
|
+
}
|
|
32123
32940
|
async #stopAllD2cMocks() {
|
|
32124
32941
|
const running = [...this.#d2cMocks.values()];
|
|
32125
32942
|
this.#d2cMocks.clear();
|
|
@@ -32237,7 +33054,7 @@ async function loginShellEnvironment(environment, platform = process.platform, d
|
|
|
32237
33054
|
}
|
|
32238
33055
|
|
|
32239
33056
|
// src/desktop/git-manager.ts
|
|
32240
|
-
import { readFile as
|
|
33057
|
+
import { readFile as readFile38, rm as rm16 } from "fs/promises";
|
|
32241
33058
|
import { isAbsolute as isAbsolute21, relative as relative28, resolve as resolve40 } from "path";
|
|
32242
33059
|
function failure(action, stderr, stdout) {
|
|
32243
33060
|
return new Error(`${action}\u5931\u8D25\uFF1A${(stderr.trim() || stdout.trim() || "\u672A\u77E5 Git \u9519\u8BEF").split("\n")[0]}`);
|
|
@@ -32269,7 +33086,7 @@ async function desktopGitDiff(workspace, path, staged = false) {
|
|
|
32269
33086
|
if (file === void 0) throw new Error("\u8BE5\u6587\u4EF6\u5DF2\u4E0D\u5728\u53D8\u66F4\u5217\u8868\u4E2D");
|
|
32270
33087
|
if (file.untracked) {
|
|
32271
33088
|
const target = safeWorkspacePath(workspace, path);
|
|
32272
|
-
const value = await
|
|
33089
|
+
const value = await readFile38(target, "utf8");
|
|
32273
33090
|
return value.length > 12e4 ? `${value.slice(0, 12e4)}
|
|
32274
33091
|
\u2026[\u5185\u5BB9\u5DF2\u622A\u65AD]` : value;
|
|
32275
33092
|
}
|
|
@@ -32331,13 +33148,13 @@ async function desktopLastTurnDiff(workspace, sessionId, paths = []) {
|
|
|
32331
33148
|
return path.replaceAll("\\", "/");
|
|
32332
33149
|
}));
|
|
32333
33150
|
try {
|
|
32334
|
-
const tree = JSON.parse(await
|
|
33151
|
+
const tree = JSON.parse(await readFile38(resolve40(workspace, ".flavor", "session-trees", sessionId, "tree.json"), "utf8"));
|
|
32335
33152
|
const leaf = tree.nodes?.find((node) => node.id === tree.leafId);
|
|
32336
33153
|
const parent = leaf?.parentId == null ? void 0 : tree.nodes?.find((node) => node.id === leaf.parentId);
|
|
32337
33154
|
if (leaf === void 0 || parent === void 0) return desktopGitReviewDiff(workspace, { scope: "last-turn", paths });
|
|
32338
33155
|
const load = async (id) => {
|
|
32339
33156
|
if (!/^checkpoint-[A-Za-z0-9-]+$/.test(id)) throw new Error("Invalid checkpoint id");
|
|
32340
|
-
return JSON.parse(await
|
|
33157
|
+
return JSON.parse(await readFile38(resolve40(workspace, ".flavor", "checkpoints", "manifests", `${id}.json`), "utf8"));
|
|
32341
33158
|
};
|
|
32342
33159
|
const [before, after] = await Promise.all([load(parent.checkpointId), load(leaf.checkpointId)]);
|
|
32343
33160
|
const oldFiles = new Map(before.files.map((file) => [file.path, file.digest]));
|
|
@@ -32346,7 +33163,7 @@ async function desktopLastTurnDiff(workspace, sessionId, paths = []) {
|
|
|
32346
33163
|
const object = async (digest) => {
|
|
32347
33164
|
if (digest === void 0) return "";
|
|
32348
33165
|
if (!/^[a-f0-9]{64}$/.test(digest)) throw new Error("Invalid checkpoint object");
|
|
32349
|
-
const value = await
|
|
33166
|
+
const value = await readFile38(resolve40(workspace, ".flavor", "checkpoints", "objects", digest));
|
|
32350
33167
|
return value.includes(0) ? "[binary content]" : value.toString("utf8");
|
|
32351
33168
|
};
|
|
32352
33169
|
const chunks = await Promise.all(changed.map(async (path) => wholeFileDiff(path, await object(oldFiles.get(path)), await object(newFiles.get(path)))));
|
|
@@ -32385,8 +33202,8 @@ function safeGitTarget(value) {
|
|
|
32385
33202
|
|
|
32386
33203
|
// src/desktop/worktree-manager.ts
|
|
32387
33204
|
import { randomUUID as randomUUID28 } from "crypto";
|
|
32388
|
-
import { mkdir as mkdir28, readFile as
|
|
32389
|
-
import { basename as basename11, isAbsolute as isAbsolute22, join as
|
|
33205
|
+
import { mkdir as mkdir28, readFile as readFile39, readdir as readdir14, rm as rm17, stat as stat18, writeFile as writeFile18 } from "fs/promises";
|
|
33206
|
+
import { basename as basename11, isAbsolute as isAbsolute22, join as join49, relative as relative29, resolve as resolve41, sep as sep29 } from "path";
|
|
32390
33207
|
var DesktopWorktreeManager = class {
|
|
32391
33208
|
#repository;
|
|
32392
33209
|
#storage;
|
|
@@ -32432,8 +33249,8 @@ var DesktopWorktreeManager = class {
|
|
|
32432
33249
|
}
|
|
32433
33250
|
const values = await Promise.all(names.filter((name) => name.endsWith(".json")).map(async (name) => {
|
|
32434
33251
|
try {
|
|
32435
|
-
const stored = validateStored(JSON.parse(await
|
|
32436
|
-
if ((await
|
|
33252
|
+
const stored = validateStored(JSON.parse(await readFile39(join49(this.#storage, name), "utf8")), this.#repository, this.#storage);
|
|
33253
|
+
if ((await stat18(stored.path).catch(() => void 0))?.isDirectory() !== true) return void 0;
|
|
32437
33254
|
const status = await git2(stored.path, ["status", "--porcelain"]);
|
|
32438
33255
|
const merged = await git2(this.#repository, ["merge-base", "--is-ancestor", stored.branch, "HEAD"]);
|
|
32439
33256
|
const { version: _version, ...item } = stored;
|
|
@@ -32466,7 +33283,7 @@ var DesktopWorktreeManager = class {
|
|
|
32466
33283
|
}
|
|
32467
33284
|
async #load(id) {
|
|
32468
33285
|
const path = this.#metadataPath(id);
|
|
32469
|
-
return validateStored(JSON.parse(await
|
|
33286
|
+
return validateStored(JSON.parse(await readFile39(path, "utf8")), this.#repository, this.#storage);
|
|
32470
33287
|
}
|
|
32471
33288
|
#metadataPath(id) {
|
|
32472
33289
|
if (!/^worktree-[a-z0-9._-]{1,80}$/.test(id)) throw new Error("Invalid worktree id");
|
|
@@ -32494,9 +33311,9 @@ function validateStored(value, repository, storage) {
|
|
|
32494
33311
|
}
|
|
32495
33312
|
|
|
32496
33313
|
// src/desktop/workbench-service.ts
|
|
32497
|
-
import { readFile as
|
|
33314
|
+
import { readFile as readFile40, readdir as readdir15, stat as stat19 } from "fs/promises";
|
|
32498
33315
|
import { homedir as homedir6 } from "os";
|
|
32499
|
-
import { join as
|
|
33316
|
+
import { join as join50, resolve as resolve42 } from "path";
|
|
32500
33317
|
var MAX_TEXT = 128e3;
|
|
32501
33318
|
var MAX_ITEMS = 200;
|
|
32502
33319
|
var DesktopWorkbenchService = class {
|
|
@@ -32529,11 +33346,11 @@ var DesktopWorkbenchService = class {
|
|
|
32529
33346
|
return url.toString().replace(/\/$/, input.trim().endsWith("/") ? "/" : "");
|
|
32530
33347
|
}
|
|
32531
33348
|
async #goals() {
|
|
32532
|
-
const root =
|
|
33349
|
+
const root = join50(this.#workspace, ".flavor", "goals");
|
|
32533
33350
|
const names = await readdir15(root).catch((error) => error.code === "ENOENT" ? [] : Promise.reject(error));
|
|
32534
33351
|
const values = await Promise.all(names.filter((name) => /^goal-[A-Za-z0-9._-]+\.json$/.test(name)).slice(0, MAX_ITEMS).map(async (name) => {
|
|
32535
33352
|
try {
|
|
32536
|
-
return GoalStateSchema.parse(JSON.parse(await boundedRead2(
|
|
33353
|
+
return GoalStateSchema.parse(JSON.parse(await boundedRead2(join50(root, name))));
|
|
32537
33354
|
} catch {
|
|
32538
33355
|
return void 0;
|
|
32539
33356
|
}
|
|
@@ -32543,7 +33360,7 @@ var DesktopWorkbenchService = class {
|
|
|
32543
33360
|
async #instructions() {
|
|
32544
33361
|
const names = ["FLAVOR.md", "AGENTS.md", "CLAUDE.md", "AGENTS.local.md", "CLAUDE.local.md"];
|
|
32545
33362
|
const items = await Promise.all(names.map(async (name) => {
|
|
32546
|
-
const path =
|
|
33363
|
+
const path = join50(this.#workspace, name);
|
|
32547
33364
|
try {
|
|
32548
33365
|
return { name, path, content: redactErrorText(await boundedRead2(path)) };
|
|
32549
33366
|
} catch (error) {
|
|
@@ -32556,9 +33373,9 @@ var DesktopWorkbenchService = class {
|
|
|
32556
33373
|
async #permissionFiles() {
|
|
32557
33374
|
const candidates = [
|
|
32558
33375
|
...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:
|
|
33376
|
+
{ tier: "user", path: join50(this.#home, ".flavor-code", "permissions.json"), content: "" },
|
|
33377
|
+
{ tier: "project", path: join50(this.#workspace, ".flavor", "permissions.json"), content: "" },
|
|
33378
|
+
{ tier: "local", path: join50(this.#workspace, ".flavor", "permissions.local.json"), content: "" }
|
|
32562
33379
|
];
|
|
32563
33380
|
const values = await Promise.all(candidates.map(async (item) => {
|
|
32564
33381
|
try {
|
|
@@ -32573,7 +33390,7 @@ var DesktopWorkbenchService = class {
|
|
|
32573
33390
|
async #audit() {
|
|
32574
33391
|
let raw;
|
|
32575
33392
|
try {
|
|
32576
|
-
raw = await boundedRead2(
|
|
33393
|
+
raw = await boundedRead2(join50(this.#workspace, ".flavor", "audit.jsonl"));
|
|
32577
33394
|
} catch (error) {
|
|
32578
33395
|
if (error.code === "ENOENT") return [];
|
|
32579
33396
|
throw error;
|
|
@@ -32588,7 +33405,7 @@ var DesktopWorkbenchService = class {
|
|
|
32588
33405
|
}).reverse();
|
|
32589
33406
|
}
|
|
32590
33407
|
async #context(sessionId) {
|
|
32591
|
-
const usage = await readJsonLines2(
|
|
33408
|
+
const usage = await readJsonLines2(join50(this.#workspace, ".flavor", "usage.jsonl"));
|
|
32592
33409
|
if (sessionId === void 0) return { visibility: [], usage };
|
|
32593
33410
|
try {
|
|
32594
33411
|
const document = await new SessionStore({ workspace: this.#workspace }).load(sessionId);
|
|
@@ -32599,10 +33416,10 @@ var DesktopWorkbenchService = class {
|
|
|
32599
33416
|
}
|
|
32600
33417
|
};
|
|
32601
33418
|
async function boundedRead2(path) {
|
|
32602
|
-
const info = await
|
|
33419
|
+
const info = await stat19(path);
|
|
32603
33420
|
if (!info.isFile()) throw new Error(`Expected a file: ${path}`);
|
|
32604
33421
|
if (info.size > MAX_TEXT) throw new Error(`Workbench file exceeds ${MAX_TEXT} bytes: ${path}`);
|
|
32605
|
-
return
|
|
33422
|
+
return readFile40(path, "utf8");
|
|
32606
33423
|
}
|
|
32607
33424
|
async function readJsonLines2(path) {
|
|
32608
33425
|
try {
|
|
@@ -32635,7 +33452,7 @@ var appMenu;
|
|
|
32635
33452
|
var quitting = false;
|
|
32636
33453
|
function logStartup(step, detail) {
|
|
32637
33454
|
try {
|
|
32638
|
-
const logPath =
|
|
33455
|
+
const logPath = join51(dirname23(process.execPath), "flavor-code-startup.log");
|
|
32639
33456
|
const ts = (/* @__PURE__ */ new Date()).toISOString();
|
|
32640
33457
|
appendFileSync(logPath, `[${ts}] ${step}${detail ? ` | ${detail}` : ""}
|
|
32641
33458
|
`);
|
|
@@ -32660,7 +33477,7 @@ var d2cEmbedded = createEmbeddedD2cAutomation(() => {
|
|
|
32660
33477
|
};
|
|
32661
33478
|
});
|
|
32662
33479
|
var d2cJudgeClient = createD2cJudgeClient();
|
|
32663
|
-
var judgeStore = () => createD2cJudgeConfigStore(
|
|
33480
|
+
var judgeStore = () => createD2cJudgeConfigStore(join51(app.getPath("userData"), "d2c-judge.json"));
|
|
32664
33481
|
function judgeImage(png) {
|
|
32665
33482
|
const image = nativeImage.createFromBuffer(png);
|
|
32666
33483
|
const size = image.getSize();
|
|
@@ -32686,7 +33503,7 @@ function sessionMetaKey(workspace, sessionId) {
|
|
|
32686
33503
|
}
|
|
32687
33504
|
function worktreeManager(workspace) {
|
|
32688
33505
|
const key = createHash28("sha256").update(resolve43(workspace)).digest("hex").slice(0, 20);
|
|
32689
|
-
return new DesktopWorktreeManager({ repository: workspace, storage:
|
|
33506
|
+
return new DesktopWorktreeManager({ repository: workspace, storage: join51(app.getPath("userData"), "task-worktrees", key) });
|
|
32690
33507
|
}
|
|
32691
33508
|
function activeWorkingDirectory() {
|
|
32692
33509
|
if (activeWorkspace === void 0) throw new Error("\u8BF7\u5148\u6253\u5F00\u9879\u76EE");
|
|
@@ -32831,7 +33648,7 @@ function createDesktopController(workspace, owner2) {
|
|
|
32831
33648
|
alias: `desktop-${basename12(workspace ?? runtimeOptions.workspace ?? "flavor")}`.slice(0, 64),
|
|
32832
33649
|
optional: true,
|
|
32833
33650
|
startBroker: (address) => {
|
|
32834
|
-
const entry2 = app.isPackaged ?
|
|
33651
|
+
const entry2 = app.isPackaged ? join51(process.resourcesPath, "app.asar.unpacked", "dist", "desktop", "pals-broker-entry.js") : join51(moduleDirectory, "pals-broker-entry.js");
|
|
32835
33652
|
return spawnPalBrokerProcess({
|
|
32836
33653
|
address,
|
|
32837
33654
|
executable: process.execPath,
|
|
@@ -32853,11 +33670,11 @@ function createDesktopController(workspace, owner2) {
|
|
|
32853
33670
|
var detachedController = createDesktopController();
|
|
32854
33671
|
var controller = detachedController;
|
|
32855
33672
|
function statePath() {
|
|
32856
|
-
return
|
|
33673
|
+
return join51(app.getPath("userData"), "desktop-state.json");
|
|
32857
33674
|
}
|
|
32858
33675
|
async function loadPersistedProjects() {
|
|
32859
33676
|
try {
|
|
32860
|
-
const raw = await
|
|
33677
|
+
const raw = await readFile41(statePath(), "utf8");
|
|
32861
33678
|
if (raw.length > 1e5) return { projects: [] };
|
|
32862
33679
|
const parsed = JSON.parse(raw);
|
|
32863
33680
|
Object.assign(workbench, {
|
|
@@ -32898,7 +33715,7 @@ function validRecord(value) {
|
|
|
32898
33715
|
return typeof value === "object" && value !== null && !Array.isArray(value) ? value : {};
|
|
32899
33716
|
}
|
|
32900
33717
|
async function assertDirectory(path) {
|
|
32901
|
-
if (!(await
|
|
33718
|
+
if (!(await stat20(path)).isDirectory()) throw new Error("The selected project is not a directory");
|
|
32902
33719
|
}
|
|
32903
33720
|
async function openWorkspace(path) {
|
|
32904
33721
|
const workspace = resolve43(path);
|
|
@@ -33029,7 +33846,7 @@ async function chooseAndOpenWorkspace() {
|
|
|
33029
33846
|
var cachedAppIconDataUrl;
|
|
33030
33847
|
function appIconDataUrl() {
|
|
33031
33848
|
if (cachedAppIconDataUrl !== void 0) return cachedAppIconDataUrl;
|
|
33032
|
-
const icon = nativeImage.createFromPath(
|
|
33849
|
+
const icon = nativeImage.createFromPath(join51(app.getAppPath(), "assets", "icon.png"));
|
|
33033
33850
|
if (icon.isEmpty()) return void 0;
|
|
33034
33851
|
cachedAppIconDataUrl = icon.toDataURL();
|
|
33035
33852
|
return cachedAppIconDataUrl;
|
|
@@ -33447,7 +34264,7 @@ function applicationMenu() {
|
|
|
33447
34264
|
]);
|
|
33448
34265
|
}
|
|
33449
34266
|
async function createWindow() {
|
|
33450
|
-
const rendererPath =
|
|
34267
|
+
const rendererPath = join51(app.getAppPath(), "dist", "desktop-renderer", "index.html");
|
|
33451
34268
|
mainWindow = new BrowserWindow2({
|
|
33452
34269
|
title: "Flavor Code",
|
|
33453
34270
|
width: 1280,
|
|
@@ -33458,14 +34275,14 @@ async function createWindow() {
|
|
|
33458
34275
|
show: false,
|
|
33459
34276
|
...desktopWindowChrome(),
|
|
33460
34277
|
webPreferences: {
|
|
33461
|
-
preload:
|
|
34278
|
+
preload: join51(moduleDirectory, "preload.cjs"),
|
|
33462
34279
|
contextIsolation: true,
|
|
33463
34280
|
nodeIntegration: false,
|
|
33464
34281
|
sandbox: false,
|
|
33465
34282
|
webSecurity: true
|
|
33466
34283
|
}
|
|
33467
34284
|
});
|
|
33468
|
-
const iconPath =
|
|
34285
|
+
const iconPath = join51(app.getAppPath(), "assets", "icon.png");
|
|
33469
34286
|
const icon = nativeImage.createFromPath(iconPath);
|
|
33470
34287
|
if (!icon.isEmpty()) {
|
|
33471
34288
|
mainWindow.setIcon(icon);
|