flavor-code 1.4.3-beta.2 → 1.4.4-beta.2
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/README.md +27 -2
- package/README.zh-CN.md +27 -2
- package/dist/agent/loop.d.ts +1 -0
- package/dist/{app-G35YBHPR.js → app-C5I2TH7I.js} +399 -109
- package/dist/{chunk-FAJ6YDZK.js → chunk-36IDJRLQ.js} +1 -1
- package/dist/{chunk-HGTCIHWA.js → chunk-5BDKRA6U.js} +30 -1
- package/dist/{chunk-T6RE76D2.js → chunk-IKGT4MDT.js} +35 -23
- package/dist/{chunk-K7UGGWJV.js → chunk-JQM2L2YM.js} +1 -1
- package/dist/{chunk-6X62CGMA.js → chunk-ONRY3C2L.js} +3 -0
- package/dist/{chunk-IDJAHEG6.js → chunk-U4XTQ5PU.js} +392 -188
- package/dist/{chunk-SJUIIHIA.js → chunk-X6U6BO5D.js} +1 -1
- package/dist/{claude-ink-BE56DB6N.js → claude-ink-F554I3GJ.js} +1 -1
- package/dist/{cli-3MF6EL2H.js → cli-V3BLZ5UW.js} +7 -7
- package/dist/cli-main.js +16 -16
- package/dist/config/protected-file.d.ts +2 -0
- package/dist/config/schema.d.ts +3 -0
- package/dist/context/global-instructions.d.ts +12 -0
- package/dist/context/manager.d.ts +2 -0
- package/dist/desktop/main.js +660 -419
- package/dist/desktop-renderer/assets/{index-DCqIrF1S.js → index-CjC0Ibiq.js} +2 -2
- package/dist/desktop-renderer/assets/{interactive-terminal-B6GbpzIn.js → interactive-terminal-IjIT3Xoa.js} +1 -1
- package/dist/desktop-renderer/index.html +1 -1
- package/dist/{doctor-OHCGK52S.js → doctor-SEL32R7M.js} +3 -3
- package/dist/harness/local.d.ts +1 -0
- package/dist/{load-QK6CMLDF.js → load-RHWXNUCS.js} +2 -2
- package/dist/{manager-5SI4MPES.js → manager-IYBNNKUP.js} +2 -2
- package/dist/{production-6DIZ6YLQ.js → production-TD7UWWXW.js} +6 -6
- package/dist/production.d.ts +2 -0
- package/dist/sdk/index.js +6 -6
- package/dist/{store-ZIHN6363.js → store-MZZERW7K.js} +2 -2
- package/dist/ui/commands.d.ts +11 -3
- package/dist/ui/session.d.ts +2 -1
- package/package.json +1 -1
- package//346/212/200/346/234/257/346/226/271/346/241/210/346/212/245/345/221/212.md +649 -1
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 createHash29, randomUUID as randomUUID29 } from "crypto";
|
|
4
|
-
import { readFile as
|
|
5
|
-
import { basename as basename12, dirname as dirname23, join as
|
|
4
|
+
import { readFile as readFile42, stat as stat20, writeFile as writeFile19 } from "fs/promises";
|
|
5
|
+
import { basename as basename12, dirname as dirname23, join as join52, 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
|
|
|
@@ -262,6 +262,9 @@ var FlavorConfigSchema = z2.object({
|
|
|
262
262
|
skills: z2.object({
|
|
263
263
|
disabled: z2.array(SkillNameSchema).max(1e3).default([])
|
|
264
264
|
}).prefault({}),
|
|
265
|
+
globalInstructions: z2.object({
|
|
266
|
+
enabled: z2.boolean().default(true)
|
|
267
|
+
}).prefault({}),
|
|
265
268
|
agents: z2.object({
|
|
266
269
|
main: z2.object({ model: z2.string() }).optional(),
|
|
267
270
|
subagent: z2.object({ model: z2.string() }).optional()
|
|
@@ -2326,6 +2329,34 @@ async function updateProtectedFile(options) {
|
|
|
2326
2329
|
return next;
|
|
2327
2330
|
}, options.lockTimeoutMs ?? LOCK_TIMEOUT_MS, options.staleLockMs ?? STALE_LOCK_MS);
|
|
2328
2331
|
}
|
|
2332
|
+
async function updatePlainTextFile(path, update, maxBytes) {
|
|
2333
|
+
await mkdir2(dirname(path), { recursive: true, mode: 448 });
|
|
2334
|
+
return withFileLock(path, async () => {
|
|
2335
|
+
const current = await readOptionalPlainText(path, maxBytes);
|
|
2336
|
+
const next = await update(current);
|
|
2337
|
+
if (next === void 0 || next === current) return current;
|
|
2338
|
+
if (await readOptionalPlainText(path, maxBytes) !== current) {
|
|
2339
|
+
throw new Error(`Text file changed while the update was prepared: ${path}`);
|
|
2340
|
+
}
|
|
2341
|
+
await writeAtomic(path, next, defaultFileOperations);
|
|
2342
|
+
return next;
|
|
2343
|
+
}, LOCK_TIMEOUT_MS, STALE_LOCK_MS);
|
|
2344
|
+
}
|
|
2345
|
+
async function readOptionalPlainText(path, maxBytes) {
|
|
2346
|
+
try {
|
|
2347
|
+
if (maxBytes !== void 0 && (await stat(path)).size > maxBytes) {
|
|
2348
|
+
throw new Error(`Text file exceeds ${maxBytes} bytes: ${path}`);
|
|
2349
|
+
}
|
|
2350
|
+
const bytes = await readFile(path);
|
|
2351
|
+
if (maxBytes !== void 0 && bytes.length > maxBytes) {
|
|
2352
|
+
throw new Error(`Text file exceeds ${maxBytes} bytes: ${path}`);
|
|
2353
|
+
}
|
|
2354
|
+
return new TextDecoder("utf-8", { fatal: true, ignoreBOM: true }).decode(bytes);
|
|
2355
|
+
} catch (error) {
|
|
2356
|
+
if (isCode2(error, "ENOENT")) return void 0;
|
|
2357
|
+
throw error;
|
|
2358
|
+
}
|
|
2359
|
+
}
|
|
2329
2360
|
async function withFileLock(path, operation, lockTimeoutMs, staleLockMs) {
|
|
2330
2361
|
const lockPath = `${path}.lock`;
|
|
2331
2362
|
const token = JSON.stringify({ pid: process.pid, nonce: randomUUID3() });
|
|
@@ -7132,11 +7163,11 @@ function createElectronBrowserHost(options) {
|
|
|
7132
7163
|
}
|
|
7133
7164
|
|
|
7134
7165
|
// src/production.ts
|
|
7135
|
-
import { readFile as
|
|
7166
|
+
import { readFile as readFile30 } from "fs/promises";
|
|
7136
7167
|
import { mkdirSync as mkdirSync4, readFileSync as readFileSync3, renameSync as renameSync2, unlinkSync as unlinkSync3, writeFileSync as writeFileSync3 } from "fs";
|
|
7137
7168
|
import { createHash as createHash22, randomUUID as randomUUID25 } from "crypto";
|
|
7138
7169
|
import { homedir as homedir3, release as osRelease, tmpdir as tmpdir4, version as osVersion } from "os";
|
|
7139
|
-
import { basename as basename9, dirname as dirname22, join as
|
|
7170
|
+
import { basename as basename9, dirname as dirname22, join as join39, relative as relative25, resolve as resolve36, sep as sep25 } from "path";
|
|
7140
7171
|
import { performance } from "perf_hooks";
|
|
7141
7172
|
|
|
7142
7173
|
// src/agent/subagents.ts
|
|
@@ -8267,6 +8298,7 @@ var ContextManager = class _ContextManager {
|
|
|
8267
8298
|
#workspaceInstructions;
|
|
8268
8299
|
#memory;
|
|
8269
8300
|
#userMemory;
|
|
8301
|
+
#globalInstructions;
|
|
8270
8302
|
#compactAtChars;
|
|
8271
8303
|
#toolOutputChars;
|
|
8272
8304
|
#recentTurns;
|
|
@@ -8311,6 +8343,7 @@ var ContextManager = class _ContextManager {
|
|
|
8311
8343
|
this.#workspaceInstructions = options.workspaceInstructions;
|
|
8312
8344
|
this.#memory = options.memory;
|
|
8313
8345
|
this.#userMemory = options.userMemory;
|
|
8346
|
+
this.#globalInstructions = options.globalInstructions;
|
|
8314
8347
|
this.#taskState = options.taskState;
|
|
8315
8348
|
this.#compactAtChars = options.compactAtChars ?? Number.POSITIVE_INFINITY;
|
|
8316
8349
|
this.#toolOutputChars = options.toolOutputChars;
|
|
@@ -8350,6 +8383,7 @@ var ContextManager = class _ContextManager {
|
|
|
8350
8383
|
...this.#memory === void 0 ? {} : { memory: this.#memory },
|
|
8351
8384
|
...this.#taskState === void 0 ? {} : { taskState: this.#taskState },
|
|
8352
8385
|
...userMemory === void 0 ? {} : { userMemory },
|
|
8386
|
+
...this.#globalInstructions === void 0 ? {} : { globalInstructions: this.#globalInstructions },
|
|
8353
8387
|
compactAtChars: this.#compactAtChars,
|
|
8354
8388
|
toolOutputChars: this.#toolOutputChars,
|
|
8355
8389
|
compaction: this.#compaction,
|
|
@@ -8431,12 +8465,14 @@ var ContextManager = class _ContextManager {
|
|
|
8431
8465
|
for (const key of DYNAMIC_SOURCE_ORDER) {
|
|
8432
8466
|
const value = next[key];
|
|
8433
8467
|
if (value === void 0 || value === this.#epoch.sources[key]) continue;
|
|
8434
|
-
|
|
8468
|
+
const display = key === "global-instructions" ? this.#epoch.sources[key] === void 0 ? value : `These replace all earlier global instructions.
|
|
8469
|
+
${value}` : deltaSourceValue(this.#epoch.sources[key], displaySourceValue(key, value));
|
|
8470
|
+
updates.push(contextUpdateMessage(key, display));
|
|
8435
8471
|
this.#epoch.sources[key] = value;
|
|
8436
8472
|
}
|
|
8437
8473
|
for (const key of DYNAMIC_SOURCE_ORDER) {
|
|
8438
8474
|
if (key in next || !(key in this.#epoch.sources)) continue;
|
|
8439
|
-
updates.push(contextUpdateMessage(key, "(removed)"));
|
|
8475
|
+
updates.push(contextUpdateMessage(key, key === "global-instructions" ? "Global instructions were removed; disregard all earlier global instructions." : "(removed)"));
|
|
8440
8476
|
delete this.#epoch.sources[key];
|
|
8441
8477
|
}
|
|
8442
8478
|
if (updates.length === 0) return false;
|
|
@@ -8456,9 +8492,15 @@ var ContextManager = class _ContextManager {
|
|
|
8456
8492
|
const legacySummary = snapshot4.summary?.role === "system" && snapshot4.summary.content.startsWith("Conversation summary\n") ? snapshot4.summary.content.slice("Conversation summary\n".length) : void 0;
|
|
8457
8493
|
this.#compact = snapshot4.compact === void 0 ? legacySummary === void 0 ? void 0 : { summary: legacySummary, compactedAt: (/* @__PURE__ */ new Date(0)).toISOString() } : { ...snapshot4.compact };
|
|
8458
8494
|
if (snapshot4.epoch !== void 0) this.#epoch = cloneEpoch(snapshot4.epoch);
|
|
8495
|
+
const globalInstructions = tryResolveOptionalSystemSections(this.#globalInstructions);
|
|
8496
|
+
const globalInstructionsAbsent = globalInstructions.available && (globalInstructions.sections?.length ?? 0) === 0;
|
|
8497
|
+
if (globalInstructionsAbsent) {
|
|
8498
|
+
delete this.#epoch.sources["global-instructions"];
|
|
8499
|
+
if (this.#epoch.pinnedSources !== void 0) delete this.#epoch.pinnedSources["global-instructions"];
|
|
8500
|
+
}
|
|
8459
8501
|
this.#visibilityLog = boundVisibilityLog(snapshot4.visibilityLog);
|
|
8460
8502
|
this.#activeTransientSystem.clear();
|
|
8461
|
-
this.#messages = messages.map((message2) => message2.role === "tool" ? { ...message2, content: truncateToolOutput(message2.content, this.#toolOutputChars) } : cloneMessage2(message2));
|
|
8503
|
+
this.#messages = (globalInstructionsAbsent ? messages.filter((message2) => message2.role !== "system" || !modelContentText(message2.content).startsWith("Context update [global-instructions]\n")) : messages).map((message2) => message2.role === "tool" ? { ...message2, content: truncateToolOutput(message2.content, this.#toolOutputChars) } : cloneMessage2(message2));
|
|
8462
8504
|
this.#lastRecordedInputTokens = void 0;
|
|
8463
8505
|
this.#estimatedTokensAtLastRecordedUsage = void 0;
|
|
8464
8506
|
this.#consecutiveAutoCompactFailures = 0;
|
|
@@ -8674,6 +8716,12 @@ ${userMemory}`,
|
|
|
8674
8716
|
}
|
|
8675
8717
|
#resolvedDynamicSources(previous = {}) {
|
|
8676
8718
|
const result = {};
|
|
8719
|
+
const globalInstructions = tryResolveOptionalSystemSections(this.#globalInstructions);
|
|
8720
|
+
if (!globalInstructions.available && previous["global-instructions"] !== void 0) {
|
|
8721
|
+
result["global-instructions"] = previous["global-instructions"];
|
|
8722
|
+
} else if (globalInstructions.sections !== void 0 && globalInstructions.sections.length > 0) {
|
|
8723
|
+
result["global-instructions"] = globalInstructions.sections.join("\n\n");
|
|
8724
|
+
}
|
|
8677
8725
|
if (this.#memory !== void 0) result["long-term-memory"] = this.#memory;
|
|
8678
8726
|
if (this.#taskState !== void 0) result["task-state"] = this.#taskState;
|
|
8679
8727
|
const volatile = tryResolveOptionalSystemSections(this.#volatileSystem);
|
|
@@ -8682,7 +8730,7 @@ ${userMemory}`,
|
|
|
8682
8730
|
return result;
|
|
8683
8731
|
}
|
|
8684
8732
|
};
|
|
8685
|
-
var DYNAMIC_SOURCE_ORDER = ["long-term-memory", "task-state", "runtime"];
|
|
8733
|
+
var DYNAMIC_SOURCE_ORDER = ["global-instructions", "long-term-memory", "task-state", "runtime"];
|
|
8686
8734
|
function sourceMessages(sources) {
|
|
8687
8735
|
return DYNAMIC_SOURCE_ORDER.flatMap((key) => {
|
|
8688
8736
|
const content = sources[key];
|
|
@@ -8696,7 +8744,7 @@ function sourceMessages(sources) {
|
|
|
8696
8744
|
} catch {
|
|
8697
8745
|
}
|
|
8698
8746
|
}
|
|
8699
|
-
const label = key === "long-term-memory" ? "Long-term memory" : key === "task-state" ? "Task state" : "Runtime";
|
|
8747
|
+
const label = key === "global-instructions" ? "Global instructions" : key === "long-term-memory" ? "Long-term memory" : key === "task-state" ? "Task state" : "Runtime";
|
|
8700
8748
|
return [{
|
|
8701
8749
|
role: "system",
|
|
8702
8750
|
content: `${label}
|
|
@@ -8852,9 +8900,122 @@ function providerValidMessages(input) {
|
|
|
8852
8900
|
return output;
|
|
8853
8901
|
}
|
|
8854
8902
|
|
|
8903
|
+
// src/context/global-instructions.ts
|
|
8904
|
+
import { lstat as lstat2, readFile as readFile6 } from "fs/promises";
|
|
8905
|
+
import { join as join7 } from "path";
|
|
8906
|
+
var GlobalInstructions = class _GlobalInstructions {
|
|
8907
|
+
static MAX_BYTES = 64 * 1024;
|
|
8908
|
+
static MAX_RULE_BYTES = 4 * 1024;
|
|
8909
|
+
path;
|
|
8910
|
+
#content;
|
|
8911
|
+
constructor(home) {
|
|
8912
|
+
this.path = join7(home, ".flavor-code", "GLOBAL.md");
|
|
8913
|
+
}
|
|
8914
|
+
get content() {
|
|
8915
|
+
return this.#content;
|
|
8916
|
+
}
|
|
8917
|
+
async refresh() {
|
|
8918
|
+
let info;
|
|
8919
|
+
try {
|
|
8920
|
+
info = await lstat2(this.path);
|
|
8921
|
+
} catch (error) {
|
|
8922
|
+
if (isMissing(error)) {
|
|
8923
|
+
this.#content = void 0;
|
|
8924
|
+
return;
|
|
8925
|
+
}
|
|
8926
|
+
throw error;
|
|
8927
|
+
}
|
|
8928
|
+
if (!info.isFile()) throw new Error(`Global instructions must be a regular file: ${this.path}`);
|
|
8929
|
+
if (info.size > _GlobalInstructions.MAX_BYTES) {
|
|
8930
|
+
throw new Error(`Global instructions exceed ${_GlobalInstructions.MAX_BYTES} bytes: ${this.path}`);
|
|
8931
|
+
}
|
|
8932
|
+
const bytes = await readFile6(this.path);
|
|
8933
|
+
if (bytes.length > _GlobalInstructions.MAX_BYTES) {
|
|
8934
|
+
throw new Error(`Global instructions exceed ${_GlobalInstructions.MAX_BYTES} bytes: ${this.path}`);
|
|
8935
|
+
}
|
|
8936
|
+
const content = new TextDecoder("utf-8", { fatal: true }).decode(bytes).trim();
|
|
8937
|
+
this.#content = content || void 0;
|
|
8938
|
+
}
|
|
8939
|
+
async remember(input) {
|
|
8940
|
+
const rule = input.trim();
|
|
8941
|
+
if (!rule || /[\r\n]/u.test(rule)) throw new Error("Global rules must be one non-empty line.");
|
|
8942
|
+
if (Buffer.byteLength(rule, "utf8") > _GlobalInstructions.MAX_RULE_BYTES) {
|
|
8943
|
+
throw new Error(`Global rule exceeds ${_GlobalInstructions.MAX_RULE_BYTES} bytes.`);
|
|
8944
|
+
}
|
|
8945
|
+
let added = false;
|
|
8946
|
+
const next = await updatePlainTextFile(this.path, async (current) => {
|
|
8947
|
+
const exists4 = await regularFileExists(this.path);
|
|
8948
|
+
const base = exists4 ? current ?? "" : "";
|
|
8949
|
+
validateDocument(base);
|
|
8950
|
+
if (ruleLines(base).some((item) => item.text.toLocaleLowerCase() === rule.toLocaleLowerCase())) return base;
|
|
8951
|
+
const eol = base.includes("\r\n") ? "\r\n" : "\n";
|
|
8952
|
+
const nextContent = base.length === 0 ? `# Global instructions${eol}${eol}- ${rule}${eol}` : `${base}${base.endsWith(eol) ? "" : eol}${eol}- ${rule}${eol}`;
|
|
8953
|
+
validateDocument(nextContent);
|
|
8954
|
+
added = true;
|
|
8955
|
+
return nextContent;
|
|
8956
|
+
}, _GlobalInstructions.MAX_BYTES);
|
|
8957
|
+
this.#content = next?.trim() || void 0;
|
|
8958
|
+
return added ? "added" : "already-present";
|
|
8959
|
+
}
|
|
8960
|
+
async forget(input) {
|
|
8961
|
+
const query = input.trim().toLocaleLowerCase();
|
|
8962
|
+
if (!query || /[\r\n]/u.test(query)) throw new Error("Specify one global rule to forget.");
|
|
8963
|
+
if (!await regularFileExists(this.path)) return "not-found";
|
|
8964
|
+
let result = "not-found";
|
|
8965
|
+
const next = await updatePlainTextFile(this.path, async (current) => {
|
|
8966
|
+
if (!await regularFileExists(this.path)) return void 0;
|
|
8967
|
+
const base = current ?? "";
|
|
8968
|
+
validateDocument(base);
|
|
8969
|
+
const entries = ruleLines(base);
|
|
8970
|
+
const exact = entries.filter((entry2) => entry2.text.toLocaleLowerCase() === query);
|
|
8971
|
+
const matches = exact.length > 0 ? exact : entries.filter((entry2) => entry2.text.toLocaleLowerCase().includes(query));
|
|
8972
|
+
if (matches.length === 0) return base;
|
|
8973
|
+
if (matches.length > 1) {
|
|
8974
|
+
result = "ambiguous";
|
|
8975
|
+
return base;
|
|
8976
|
+
}
|
|
8977
|
+
const match = matches[0];
|
|
8978
|
+
result = "removed";
|
|
8979
|
+
const remaining = `${base.slice(0, match.start)}${base.slice(match.end)}`;
|
|
8980
|
+
return remaining.trim() === "# Global instructions" ? "" : remaining;
|
|
8981
|
+
}, _GlobalInstructions.MAX_BYTES);
|
|
8982
|
+
this.#content = next?.trim() || void 0;
|
|
8983
|
+
return result;
|
|
8984
|
+
}
|
|
8985
|
+
};
|
|
8986
|
+
function ruleLines(content) {
|
|
8987
|
+
return [...content.matchAll(/^[ \t]*[-*+][ \t]+([^\r\n]+)(?:\r?\n|$)/gmu)].map((match) => ({
|
|
8988
|
+
text: match[1].trim(),
|
|
8989
|
+
start: match.index,
|
|
8990
|
+
end: match.index + match[0].length
|
|
8991
|
+
}));
|
|
8992
|
+
}
|
|
8993
|
+
function validateDocument(content) {
|
|
8994
|
+
if (Buffer.byteLength(content, "utf8") > GlobalInstructions.MAX_BYTES) {
|
|
8995
|
+
throw new Error(`Global instructions exceed ${GlobalInstructions.MAX_BYTES} bytes.`);
|
|
8996
|
+
}
|
|
8997
|
+
return content;
|
|
8998
|
+
}
|
|
8999
|
+
async function regularFileExists(path) {
|
|
9000
|
+
try {
|
|
9001
|
+
const info = await lstat2(path);
|
|
9002
|
+
if (!info.isFile()) throw new Error(`Global instructions must be a regular file: ${path}`);
|
|
9003
|
+
if (info.size > GlobalInstructions.MAX_BYTES) {
|
|
9004
|
+
throw new Error(`Global instructions exceed ${GlobalInstructions.MAX_BYTES} bytes: ${path}`);
|
|
9005
|
+
}
|
|
9006
|
+
return true;
|
|
9007
|
+
} catch (error) {
|
|
9008
|
+
if (isMissing(error)) return false;
|
|
9009
|
+
throw error;
|
|
9010
|
+
}
|
|
9011
|
+
}
|
|
9012
|
+
function isMissing(error) {
|
|
9013
|
+
return error instanceof Error && "code" in error && error.code === "ENOENT";
|
|
9014
|
+
}
|
|
9015
|
+
|
|
8855
9016
|
// src/context/workspace-instructions.ts
|
|
8856
9017
|
import { createHash as createHash5 } from "crypto";
|
|
8857
|
-
import { lstat as
|
|
9018
|
+
import { lstat as lstat3, readFile as readFile7, realpath as realpath4 } from "fs/promises";
|
|
8858
9019
|
import { dirname as dirname5, isAbsolute as isAbsolute4, relative as relative4, resolve as resolve5, sep as sep4 } from "path";
|
|
8859
9020
|
var NAMES = ["AGENTS.md", "CLAUDE.md", "AGENTS.local.md", "CLAUDE.local.md"];
|
|
8860
9021
|
var ROOT_NAMES = ["AGENTS.md", "CLAUDE.md", "AGENTS.local.md", "CLAUDE.local.md"];
|
|
@@ -8914,11 +9075,11 @@ var WorkspaceInstructions = class {
|
|
|
8914
9075
|
for (const name of names) {
|
|
8915
9076
|
const path = resolve5(directory.path, name);
|
|
8916
9077
|
try {
|
|
8917
|
-
const info = await
|
|
9078
|
+
const info = await lstat3(path);
|
|
8918
9079
|
if (!info.isFile() && !info.isSymbolicLink()) continue;
|
|
8919
9080
|
const physical = await realpath4(path);
|
|
8920
9081
|
if (!within(physicalRoot, physical)) continue;
|
|
8921
|
-
const bytes = await
|
|
9082
|
+
const bytes = await readFile7(path);
|
|
8922
9083
|
const clipped = bytes.subarray(0, this.#maxFileBytes);
|
|
8923
9084
|
const suffix = bytes.length > clipped.length ? `
|
|
8924
9085
|
|
|
@@ -8957,7 +9118,7 @@ ${document.content}
|
|
|
8957
9118
|
};
|
|
8958
9119
|
async function directoryForPath(path) {
|
|
8959
9120
|
try {
|
|
8960
|
-
return (await
|
|
9121
|
+
return (await lstat3(path)).isDirectory() ? path : dirname5(path);
|
|
8961
9122
|
} catch {
|
|
8962
9123
|
return dirname5(path);
|
|
8963
9124
|
}
|
|
@@ -9754,6 +9915,12 @@ var AgentLoop = class {
|
|
|
9754
9915
|
}
|
|
9755
9916
|
}
|
|
9756
9917
|
async *#runInternal(request) {
|
|
9918
|
+
try {
|
|
9919
|
+
await this.#options.beforeModelCall?.(request.signal);
|
|
9920
|
+
} catch (error) {
|
|
9921
|
+
yield { type: "error", error: normalizeProviderError(error) };
|
|
9922
|
+
return;
|
|
9923
|
+
}
|
|
9757
9924
|
this.#options.context.refreshContextSources();
|
|
9758
9925
|
this.#options.context.append(request.initialUserMessage ?? { role: "user", content: request.prompt });
|
|
9759
9926
|
let totalInputTokens = 0;
|
|
@@ -9814,6 +9981,7 @@ var AgentLoop = class {
|
|
|
9814
9981
|
return;
|
|
9815
9982
|
}
|
|
9816
9983
|
try {
|
|
9984
|
+
await this.#options.beforeModelCall?.(request.signal);
|
|
9817
9985
|
if (await this.#options.context.prepareForModelCall(request.signal)) yield { type: "compacted" };
|
|
9818
9986
|
} catch (error) {
|
|
9819
9987
|
yield { type: "error", error: normalizeProviderError(error) };
|
|
@@ -10826,7 +10994,7 @@ function redactClassifierText(value) {
|
|
|
10826
10994
|
// src/tools/runtime.ts
|
|
10827
10995
|
import { randomUUID as randomUUID7 } from "crypto";
|
|
10828
10996
|
import { mkdir as mkdir6, writeFile as writeFile3 } from "fs/promises";
|
|
10829
|
-
import { basename as basename3, join as
|
|
10997
|
+
import { basename as basename3, join as join8, resolve as resolve7 } from "path";
|
|
10830
10998
|
import { z as z16 } from "zod";
|
|
10831
10999
|
var DEFAULT_TOOL_OUTPUT_LIMITS = Object.freeze({
|
|
10832
11000
|
perToolChars: 5e4,
|
|
@@ -11148,9 +11316,9 @@ var ToolRuntime = class {
|
|
|
11148
11316
|
const reason = perToolExceeded && turnExceeded ? "per_tool_and_turn_limit" : perToolExceeded ? "per_tool_limit" : "turn_limit";
|
|
11149
11317
|
const preview = headTailPreview(serialized, previewChars);
|
|
11150
11318
|
this.#turnOutputChars += preview.length;
|
|
11151
|
-
const directory =
|
|
11319
|
+
const directory = join8(this.#workspace, ".flavor", "tool-results");
|
|
11152
11320
|
const filename2 = `${(/* @__PURE__ */ new Date()).toISOString().replaceAll(":", "-")}-${randomUUID7()}-${safeFilename(tool)}.txt`;
|
|
11153
|
-
const savedTo =
|
|
11321
|
+
const savedTo = join8(directory, filename2);
|
|
11154
11322
|
let storage;
|
|
11155
11323
|
try {
|
|
11156
11324
|
await mkdir6(directory, { recursive: true });
|
|
@@ -11369,6 +11537,7 @@ var LocalHarness = class {
|
|
|
11369
11537
|
...this.#options.softLimitFactor === void 0 ? {} : { softLimitFactor: this.#options.softLimitFactor },
|
|
11370
11538
|
...this.#options.extendIterations === void 0 ? {} : { extendIterations: this.#options.extendIterations },
|
|
11371
11539
|
...isMain && this.#options.hasActiveProgress !== void 0 ? { hasActiveProgress: this.#options.hasActiveProgress } : {},
|
|
11540
|
+
...this.#options.beforeModelCall === void 0 ? {} : { beforeModelCall: this.#options.beforeModelCall },
|
|
11372
11541
|
...isMain && this.#options.hallucinationGuard !== void 0 ? { hallucinationGuard: this.#options.hallucinationGuard } : {},
|
|
11373
11542
|
...this.#options.modelJournal === void 0 ? {} : { modelJournal: this.#options.modelJournal }
|
|
11374
11543
|
});
|
|
@@ -11398,7 +11567,7 @@ function toModelTool(tool) {
|
|
|
11398
11567
|
// src/harness/journal.ts
|
|
11399
11568
|
import { createHash as createHash6, randomUUID as randomUUID8 } from "crypto";
|
|
11400
11569
|
import { closeSync, constants as constants2, existsSync as existsSync2, fsyncSync, mkdirSync, openSync, readFileSync, realpathSync as realpathSync2, renameSync, statSync, truncateSync, unlinkSync, writeSync } from "fs";
|
|
11401
|
-
import { join as
|
|
11570
|
+
import { join as join9, relative as relative6, resolve as resolve8, sep as sep6 } from "path";
|
|
11402
11571
|
import { z as z17 } from "zod";
|
|
11403
11572
|
|
|
11404
11573
|
// src/harness/invariants.ts
|
|
@@ -11488,14 +11657,14 @@ var HarnessJournal = class {
|
|
|
11488
11657
|
#records = [];
|
|
11489
11658
|
constructor(options) {
|
|
11490
11659
|
const workspace = resolve8(options.workspace);
|
|
11491
|
-
const directory =
|
|
11660
|
+
const directory = join9(workspace, ".flavor", "sessions");
|
|
11492
11661
|
assertSessionId(options.sessionId);
|
|
11493
11662
|
mkdirSync(directory, { recursive: true, mode: 448 });
|
|
11494
11663
|
assertWithin(workspace, directory);
|
|
11495
11664
|
const canonicalWorkspace = realpathSync2.native(workspace);
|
|
11496
11665
|
const canonicalDirectory = realpathSync2.native(directory);
|
|
11497
11666
|
if (!isWithin2(canonicalWorkspace, canonicalDirectory)) throw new Error("Harness journal directory escapes the workspace");
|
|
11498
|
-
this.#path =
|
|
11667
|
+
this.#path = join9(canonicalDirectory, `${options.sessionId}.events.jsonl`);
|
|
11499
11668
|
this.#now = options.now ?? (() => (/* @__PURE__ */ new Date()).toISOString());
|
|
11500
11669
|
this.#maxBytes = options.maxBytes ?? MAX_JOURNAL_BYTES;
|
|
11501
11670
|
if (!Number.isSafeInteger(this.#maxBytes) || this.#maxBytes < 1024) throw new Error("maxBytes must be an integer of at least 1024");
|
|
@@ -12300,7 +12469,7 @@ function createIncidentReporter(options) {
|
|
|
12300
12469
|
import { createHash as createHash7, randomUUID as randomUUID10 } from "crypto";
|
|
12301
12470
|
import { chmod, unlink as unlink2 } from "fs/promises";
|
|
12302
12471
|
import { tmpdir } from "os";
|
|
12303
|
-
import { join as
|
|
12472
|
+
import { join as join10 } from "path";
|
|
12304
12473
|
import { createServer as createServer2 } from "net";
|
|
12305
12474
|
var MAX_REQUEST_BYTES = 64 * 1024;
|
|
12306
12475
|
var SOCKET_TIMEOUT_MS = 1e4;
|
|
@@ -12308,7 +12477,7 @@ function islandControlEndpoint(sessionId, platform = process.platform) {
|
|
|
12308
12477
|
const digest = createHash7("sha256").update(`${process.pid}\0${sessionId}`).digest("hex").slice(0, 20);
|
|
12309
12478
|
if (platform === "win32") return `\\\\.\\pipe\\flavor-island-control-${digest}`;
|
|
12310
12479
|
const uid = typeof process.getuid === "function" ? process.getuid() : 0;
|
|
12311
|
-
return
|
|
12480
|
+
return join10(tmpdir(), `flavor-island-control-${uid}-${digest}.sock`);
|
|
12312
12481
|
}
|
|
12313
12482
|
async function createIslandControlServer(options) {
|
|
12314
12483
|
const platform = options.platform ?? process.platform;
|
|
@@ -12435,18 +12604,18 @@ function closeServer(server) {
|
|
|
12435
12604
|
// src/evolve/service.ts
|
|
12436
12605
|
import { spawn as spawn2 } from "child_process";
|
|
12437
12606
|
import { writeFile as writeFile7 } from "fs/promises";
|
|
12438
|
-
import { join as
|
|
12607
|
+
import { join as join13 } from "path";
|
|
12439
12608
|
import { z as z21 } from "zod";
|
|
12440
12609
|
|
|
12441
12610
|
// src/evolve/loader.ts
|
|
12442
12611
|
import { cp as cp2, mkdir as mkdir7, mkdtemp, readdir as readdir3, rm as rm4, stat as stat5, writeFile as writeFile5 } from "fs/promises";
|
|
12443
12612
|
import { tmpdir as tmpdir2 } from "os";
|
|
12444
|
-
import { join as
|
|
12613
|
+
import { join as join11, resolve as resolve11 } from "path";
|
|
12445
12614
|
|
|
12446
12615
|
// src/plugins/host.ts
|
|
12447
12616
|
import { createHash as createHash8 } from "crypto";
|
|
12448
12617
|
import { lstatSync, realpathSync as realpathSync3 } from "fs";
|
|
12449
|
-
import { lstat as
|
|
12618
|
+
import { lstat as lstat4, open as open4, readFile as readFile8, readdir as readdir2, realpath as realpath6, stat as stat4, writeFile as writeFile4 } from "fs/promises";
|
|
12450
12619
|
import { isAbsolute as isAbsolute7, relative as relative7, resolve as resolve10, sep as sep7 } from "path";
|
|
12451
12620
|
import { pathToFileURL as pathToFileURL2 } from "url";
|
|
12452
12621
|
import { z as z20 } from "zod";
|
|
@@ -12795,7 +12964,7 @@ var PluginHost = class {
|
|
|
12795
12964
|
async #readCandidate(inputRoot, source, label = inputRoot) {
|
|
12796
12965
|
let diagnosticLabel = label;
|
|
12797
12966
|
try {
|
|
12798
|
-
const rootInfo = await
|
|
12967
|
+
const rootInfo = await lstat4(inputRoot);
|
|
12799
12968
|
if (!rootInfo.isDirectory() || rootInfo.isSymbolicLink()) throw new Error("Plugin root must be a static directory");
|
|
12800
12969
|
const root = await realpath6(inputRoot);
|
|
12801
12970
|
const rawManifest = await readManifest2(resolve10(root, MANIFEST));
|
|
@@ -12805,7 +12974,7 @@ var PluginHost = class {
|
|
|
12805
12974
|
const manifest = PluginManifestSchema.parse(rawManifest);
|
|
12806
12975
|
const entry2 = resolveContained(root, manifest.main, "Plugin entry");
|
|
12807
12976
|
const entrySnapshot = await snapshotEntry(root, entry2);
|
|
12808
|
-
const fingerprint2 = createHash8("sha256").update(JSON.stringify(manifest)).update(await
|
|
12977
|
+
const fingerprint2 = createHash8("sha256").update(JSON.stringify(manifest)).update(await readFile8(entrySnapshot.physicalPath)).digest("hex");
|
|
12809
12978
|
return { manifest, root, source, entry: entry2, entrySnapshot, fingerprint: fingerprint2 };
|
|
12810
12979
|
} catch (error) {
|
|
12811
12980
|
this.#diagnose(diagnosticLabel, error, inputRoot);
|
|
@@ -13052,7 +13221,7 @@ var PluginHost = class {
|
|
|
13052
13221
|
}
|
|
13053
13222
|
};
|
|
13054
13223
|
async function readManifest2(path) {
|
|
13055
|
-
const lexical = await
|
|
13224
|
+
const lexical = await lstat4(path, { bigint: true });
|
|
13056
13225
|
if (!lexical.isFile() || lexical.isSymbolicLink()) throw new Error("Plugin manifest must be a static regular file");
|
|
13057
13226
|
const handle = await open4(path, "r");
|
|
13058
13227
|
try {
|
|
@@ -13069,7 +13238,7 @@ async function readManifest2(path) {
|
|
|
13069
13238
|
if (bytesRead > MAX_MANIFEST_BYTES) throw new Error(`Plugin manifest exceeds ${MAX_MANIFEST_BYTES} bytes`);
|
|
13070
13239
|
const after = fromStats(await handle.stat({ bigint: true }));
|
|
13071
13240
|
assertSnapshot(before, after, "Plugin manifest");
|
|
13072
|
-
const current = fromStats(await
|
|
13241
|
+
const current = fromStats(await lstat4(path, { bigint: true }));
|
|
13073
13242
|
assertSnapshot(before, current, "Plugin manifest path");
|
|
13074
13243
|
return JSON.parse(new TextDecoder("utf-8", { fatal: true }).decode(buffer.subarray(0, bytesRead)));
|
|
13075
13244
|
} finally {
|
|
@@ -13077,7 +13246,7 @@ async function readManifest2(path) {
|
|
|
13077
13246
|
}
|
|
13078
13247
|
}
|
|
13079
13248
|
async function snapshotEntry(root, entry2) {
|
|
13080
|
-
const lexicalStats = await
|
|
13249
|
+
const lexicalStats = await lstat4(entry2, { bigint: true });
|
|
13081
13250
|
if (!lexicalStats.isFile() && !lexicalStats.isSymbolicLink()) throw new Error("Plugin entry must be a file");
|
|
13082
13251
|
const physicalPath = await realpath6(entry2);
|
|
13083
13252
|
assertContained2(root, physicalPath, "Plugin entry symlink");
|
|
@@ -13165,8 +13334,8 @@ function compare(left, right) {
|
|
|
13165
13334
|
}
|
|
13166
13335
|
|
|
13167
13336
|
// src/evolve/loader.ts
|
|
13168
|
-
var PLUGIN_DIR =
|
|
13169
|
-
var VERSIONS_DIR =
|
|
13337
|
+
var PLUGIN_DIR = join11(".flavor", "plugins");
|
|
13338
|
+
var VERSIONS_DIR = join11(".flavor", "plugins", ".versions");
|
|
13170
13339
|
function sanitizePluginName(tool) {
|
|
13171
13340
|
const base = String(tool ?? "fix").replace(/[^a-z0-9-]/gi, "-").toLowerCase();
|
|
13172
13341
|
return base.startsWith("fix-") ? base : `fix-${base}`;
|
|
@@ -13210,8 +13379,8 @@ export function activate(ctx) {
|
|
|
13210
13379
|
async function scaffoldFixPlugin(workspace, name) {
|
|
13211
13380
|
const dir = fixPluginDir(workspace, name);
|
|
13212
13381
|
await mkdir7(dir, { recursive: true });
|
|
13213
|
-
const manifestPath =
|
|
13214
|
-
const entryPath =
|
|
13382
|
+
const manifestPath = join11(dir, "flavor-plugin.json");
|
|
13383
|
+
const entryPath = join11(dir, "index.js");
|
|
13215
13384
|
try {
|
|
13216
13385
|
await writeFile5(manifestPath, SCAFFOLD_MANIFEST(name), { encoding: "utf8", flag: "wx" });
|
|
13217
13386
|
await writeFile5(entryPath, SCAFFOLD_ENTRY(name), { encoding: "utf8", flag: "wx" });
|
|
@@ -13231,9 +13400,9 @@ async function verifyFixPlugin(workspace, name) {
|
|
|
13231
13400
|
return { ok: false, provided: [], tools: [], commands: [], error: `Plugin "${name}" not found.` };
|
|
13232
13401
|
}
|
|
13233
13402
|
const collected = { tools: [], commands: [] };
|
|
13234
|
-
const sandbox = await mkdtemp(
|
|
13403
|
+
const sandbox = await mkdtemp(join11(tmpdir2(), "flavor-evolve-verify-"));
|
|
13235
13404
|
try {
|
|
13236
|
-
await cp2(pluginRoot,
|
|
13405
|
+
await cp2(pluginRoot, join11(sandbox, name), { recursive: true });
|
|
13237
13406
|
const host = new PluginHost({
|
|
13238
13407
|
projectPluginDirs: [sandbox],
|
|
13239
13408
|
sandbox: true,
|
|
@@ -13284,7 +13453,7 @@ async function verifyFixPlugin(workspace, name) {
|
|
|
13284
13453
|
}
|
|
13285
13454
|
async function snapshotFixPlugin(workspace, name) {
|
|
13286
13455
|
const pluginRoot = fixPluginDir(workspace, name);
|
|
13287
|
-
const snapshotRoot =
|
|
13456
|
+
const snapshotRoot = join11(versionsDirFor(workspace, name), timestamp());
|
|
13288
13457
|
await mkdir7(snapshotRoot, { recursive: true });
|
|
13289
13458
|
await cp2(pluginRoot, snapshotRoot, { recursive: true });
|
|
13290
13459
|
return snapshotRoot;
|
|
@@ -13305,7 +13474,7 @@ async function revertFixPlugin(workspace, name) {
|
|
|
13305
13474
|
}
|
|
13306
13475
|
const snapshots = entries.filter((entry2) => !entry2.startsWith(".")).sort();
|
|
13307
13476
|
if (snapshots.length === 0) throw new Error(`Plugin "${name}" has no snapshot to restore.`);
|
|
13308
|
-
const latest =
|
|
13477
|
+
const latest = join11(versionsRoot, snapshots[snapshots.length - 1]);
|
|
13309
13478
|
await rm4(pluginRoot, { recursive: true, force: true });
|
|
13310
13479
|
await mkdir7(pluginRoot, { recursive: true });
|
|
13311
13480
|
await cp2(latest, pluginRoot, { recursive: true });
|
|
@@ -13314,8 +13483,8 @@ async function revertFixPlugin(workspace, name) {
|
|
|
13314
13483
|
|
|
13315
13484
|
// src/evolve/store.ts
|
|
13316
13485
|
import { createHash as createHash9 } from "crypto";
|
|
13317
|
-
import { appendFile, mkdir as mkdir8, readFile as
|
|
13318
|
-
import { dirname as dirname8, join as
|
|
13486
|
+
import { appendFile, mkdir as mkdir8, readFile as readFile9, rm as rm5, writeFile as writeFile6 } from "fs/promises";
|
|
13487
|
+
import { dirname as dirname8, join as join12 } from "path";
|
|
13319
13488
|
function normalizeError(message2) {
|
|
13320
13489
|
return String(message2 ?? "").replace(/\s+/g, " ").replace(/"[^"]*"/g, '"\u2026"').replace(/`[^`]*`/g, '"\u2026"').trim().slice(0, 160);
|
|
13321
13490
|
}
|
|
@@ -13328,7 +13497,7 @@ function argKeys(args) {
|
|
|
13328
13497
|
}
|
|
13329
13498
|
async function readJsonLines(file) {
|
|
13330
13499
|
try {
|
|
13331
|
-
const text = await
|
|
13500
|
+
const text = await readFile9(file, "utf8");
|
|
13332
13501
|
const records = [];
|
|
13333
13502
|
for (const line of text.split("\n")) {
|
|
13334
13503
|
const trimmed = line.trim();
|
|
@@ -13351,7 +13520,7 @@ async function writeJsonLines(file, records) {
|
|
|
13351
13520
|
}
|
|
13352
13521
|
async function readJsonArray(file) {
|
|
13353
13522
|
try {
|
|
13354
|
-
const parsed = JSON.parse(await
|
|
13523
|
+
const parsed = JSON.parse(await readFile9(file, "utf8"));
|
|
13355
13524
|
return Array.isArray(parsed) ? parsed.filter((item) => typeof item === "string") : [];
|
|
13356
13525
|
} catch {
|
|
13357
13526
|
return [];
|
|
@@ -13359,7 +13528,7 @@ async function readJsonArray(file) {
|
|
|
13359
13528
|
}
|
|
13360
13529
|
async function readRules(file) {
|
|
13361
13530
|
try {
|
|
13362
|
-
const parsed = JSON.parse(await
|
|
13531
|
+
const parsed = JSON.parse(await readFile9(file, "utf8"));
|
|
13363
13532
|
if (!Array.isArray(parsed)) return [];
|
|
13364
13533
|
return parsed.filter((item) => item !== null && typeof item === "object").filter((item) => typeof item.id === "string" && typeof item.text === "string").map((item) => ({
|
|
13365
13534
|
id: item.id,
|
|
@@ -13388,12 +13557,12 @@ var EvolveStore = class {
|
|
|
13388
13557
|
#queue = Promise.resolve();
|
|
13389
13558
|
constructor(options) {
|
|
13390
13559
|
const { workspace, maxSignals = 400, maxRules = 20 } = options;
|
|
13391
|
-
this.dir =
|
|
13392
|
-
this.signalsFile =
|
|
13393
|
-
this.reflectionsFile =
|
|
13394
|
-
this.doneFile =
|
|
13395
|
-
this.verifiedFile =
|
|
13396
|
-
this.rulesFile =
|
|
13560
|
+
this.dir = join12(workspace, ".flavor", "evolve");
|
|
13561
|
+
this.signalsFile = join12(this.dir, "signals.jsonl");
|
|
13562
|
+
this.reflectionsFile = join12(this.dir, "reflections.jsonl");
|
|
13563
|
+
this.doneFile = join12(this.dir, "done.json");
|
|
13564
|
+
this.verifiedFile = join12(this.dir, "verified.json");
|
|
13565
|
+
this.rulesFile = join12(this.dir, "rules.json");
|
|
13397
13566
|
this.maxSignals = maxSignals;
|
|
13398
13567
|
this.maxRules = maxRules;
|
|
13399
13568
|
}
|
|
@@ -13936,7 +14105,7 @@ function createEvolveService(options) {
|
|
|
13936
14105
|
if (suggestion === void 0) return `No open suggestion with id "${suggestionId}". Use /evolve suggest to list them.`;
|
|
13937
14106
|
const name = sanitizePluginName(suggestion.tool);
|
|
13938
14107
|
const dir = await scaffoldFixPlugin(workspace, name);
|
|
13939
|
-
await writeFile7(
|
|
14108
|
+
await writeFile7(join13(dir, "PLAN.md"), buildPlan(suggestion, name, "See the repeated failure below; describe your fix in PLAN.md."), "utf8");
|
|
13940
14109
|
await snapshotFixPlugin(workspace, name);
|
|
13941
14110
|
return [
|
|
13942
14111
|
`suggestion ${suggestion.id}: ${suggestion.tool} x${suggestion.count} \u2014 ${suggestion.error}`,
|
|
@@ -14009,7 +14178,7 @@ ${result.stderr.slice(-4e3)}` : ""}`;
|
|
|
14009
14178
|
name: "evolve_improve",
|
|
14010
14179
|
description: "Implement a fix for one repeated tool failure. Default kind=plugin scaffolds the fix-<tool>/ plugin dir and writes PLAN.md with instructions for implementing, verifying, reloading, and testing it. kind=prompt_rule instead stores a concise guardrail rule (from `implementation`) that is injected into future system prompts and closes the suggestion. Use when the model proposes a concrete fix for a suggestion in the system prompt.",
|
|
14011
14180
|
inputSchema,
|
|
14012
|
-
paths: (input) => [
|
|
14181
|
+
paths: (input) => [join13(workspace, ".flavor", "evolve"), ...input.kind === "prompt_rule" ? [] : [join13(workspace, ".flavor", "plugins")]],
|
|
14013
14182
|
execute: async (input, signal) => {
|
|
14014
14183
|
signal.throwIfAborted();
|
|
14015
14184
|
const { suggestionId, implementation, kind } = input;
|
|
@@ -14029,7 +14198,7 @@ ${result.stderr.slice(-4e3)}` : ""}`;
|
|
|
14029
14198
|
}
|
|
14030
14199
|
const name = sanitizePluginName(suggestion.tool);
|
|
14031
14200
|
const dir = await scaffoldFixPlugin(workspace, name);
|
|
14032
|
-
await writeFile7(
|
|
14201
|
+
await writeFile7(join13(dir, "PLAN.md"), buildPlan(suggestion, name, implementation), "utf8");
|
|
14033
14202
|
await snapshotFixPlugin(workspace, name);
|
|
14034
14203
|
return [
|
|
14035
14204
|
`Scaffolded fix plugin at ${dir} for suggestion [${suggestion.id}] (${suggestion.tool} x${suggestion.count}).`,
|
|
@@ -14052,8 +14221,8 @@ ${result.stderr.slice(-4e3)}` : ""}`;
|
|
|
14052
14221
|
}
|
|
14053
14222
|
|
|
14054
14223
|
// src/init/project.ts
|
|
14055
|
-
import { lstat as
|
|
14056
|
-
import { basename as basename4, isAbsolute as isAbsolute8, join as
|
|
14224
|
+
import { lstat as lstat5, mkdir as mkdir9, readdir as readdir4, readFile as readFile10, realpath as realpath7, writeFile as writeFile8 } from "fs/promises";
|
|
14225
|
+
import { basename as basename4, isAbsolute as isAbsolute8, join as join14, relative as relative8, sep as sep8 } from "path";
|
|
14057
14226
|
var GENERATED_START = "<!-- flavor-code:start -->";
|
|
14058
14227
|
var GENERATED_END = "<!-- flavor-code:end -->";
|
|
14059
14228
|
var MAX_MANIFEST_BYTES2 = 1e6;
|
|
@@ -14106,7 +14275,7 @@ function isContainedPath(root, candidate) {
|
|
|
14106
14275
|
}
|
|
14107
14276
|
async function pathKind(path, root) {
|
|
14108
14277
|
try {
|
|
14109
|
-
const details = await
|
|
14278
|
+
const details = await lstat5(path);
|
|
14110
14279
|
if (details.isSymbolicLink() || !isContainedPath(root, await realpath7(path))) return void 0;
|
|
14111
14280
|
if (details.isFile()) return "file";
|
|
14112
14281
|
if (details.isDirectory()) return "directory";
|
|
@@ -14120,14 +14289,14 @@ function isMissingPathError(error) {
|
|
|
14120
14289
|
return error instanceof Error && "code" in error && error.code === "ENOENT";
|
|
14121
14290
|
}
|
|
14122
14291
|
async function readTextIfSmall(path, root) {
|
|
14123
|
-
const details = await
|
|
14292
|
+
const details = await lstat5(path).catch((error) => {
|
|
14124
14293
|
if (isMissingPathError(error)) return void 0;
|
|
14125
14294
|
throw error;
|
|
14126
14295
|
});
|
|
14127
14296
|
if (details === void 0 || !details.isFile() || details.isSymbolicLink() || details.size > MAX_MANIFEST_BYTES2 || !isContainedPath(root, await realpath7(path))) {
|
|
14128
14297
|
return void 0;
|
|
14129
14298
|
}
|
|
14130
|
-
return
|
|
14299
|
+
return readFile10(path, "utf8");
|
|
14131
14300
|
}
|
|
14132
14301
|
function isRecord2(value) {
|
|
14133
14302
|
return typeof value === "object" && value !== null && !Array.isArray(value);
|
|
@@ -14174,7 +14343,7 @@ async function sampleSourceExtensions(cwd, root, directories) {
|
|
|
14174
14343
|
const extensions = /* @__PURE__ */ new Set();
|
|
14175
14344
|
const sampleDirectories = ["", ...directories].slice(0, 12);
|
|
14176
14345
|
for (const relativeDirectory of sampleDirectories) {
|
|
14177
|
-
const absoluteDirectory =
|
|
14346
|
+
const absoluteDirectory = join14(cwd, relativeDirectory);
|
|
14178
14347
|
if (!isContainedPath(root, await realpath7(absoluteDirectory))) continue;
|
|
14179
14348
|
let entries;
|
|
14180
14349
|
try {
|
|
@@ -14195,15 +14364,15 @@ async function inspectProject(cwd) {
|
|
|
14195
14364
|
const root = await realpath7(cwd);
|
|
14196
14365
|
const sourceDirectories = [];
|
|
14197
14366
|
for (const directory of SOURCE_DIRECTORIES) {
|
|
14198
|
-
if (await pathKind(
|
|
14367
|
+
if (await pathKind(join14(cwd, directory), root) === "directory") sourceDirectories.push(directory);
|
|
14199
14368
|
}
|
|
14200
14369
|
const instructionFiles = [];
|
|
14201
14370
|
for (const file of INSTRUCTION_FILES) {
|
|
14202
|
-
if (await pathKind(
|
|
14371
|
+
if (await pathKind(join14(cwd, file), root) === "file") instructionFiles.push(file);
|
|
14203
14372
|
}
|
|
14204
14373
|
const configFiles = [];
|
|
14205
14374
|
for (const file of CONFIG_FILES) {
|
|
14206
|
-
if (await pathKind(
|
|
14375
|
+
if (await pathKind(join14(cwd, file), root) === "file") configFiles.push(file);
|
|
14207
14376
|
}
|
|
14208
14377
|
const manifestNames = [
|
|
14209
14378
|
"package.json",
|
|
@@ -14216,7 +14385,7 @@ async function inspectProject(cwd) {
|
|
|
14216
14385
|
];
|
|
14217
14386
|
const present = /* @__PURE__ */ new Set();
|
|
14218
14387
|
for (const manifest of manifestNames) {
|
|
14219
|
-
if (await pathKind(
|
|
14388
|
+
if (await pathKind(join14(cwd, manifest), root) === "file") present.add(manifest);
|
|
14220
14389
|
}
|
|
14221
14390
|
const detectedLockfiles = JAVASCRIPT_LOCKFILES.filter(({ name }) => present.has(name));
|
|
14222
14391
|
const detectedJsManagers = [
|
|
@@ -14242,7 +14411,7 @@ async function inspectProject(cwd) {
|
|
|
14242
14411
|
let projectName = sanitizeProjectName(basename4(cwd)) ?? "project";
|
|
14243
14412
|
let declaredJsManager;
|
|
14244
14413
|
if (present.has("package.json")) {
|
|
14245
|
-
const text = await readTextIfSmall(
|
|
14414
|
+
const text = await readTextIfSmall(join14(cwd, "package.json"), root);
|
|
14246
14415
|
if (text === void 0) {
|
|
14247
14416
|
cautions.push("package.json was too large to inspect; its scripts were ignored.");
|
|
14248
14417
|
} else {
|
|
@@ -14296,8 +14465,8 @@ async function inspectProject(cwd) {
|
|
|
14296
14465
|
addScriptCommand(testCommands, scripts, jsManager, ["test"]);
|
|
14297
14466
|
addScriptCommand(lintCommands, scripts, jsManager, ["lint"]);
|
|
14298
14467
|
addScriptCommand(formatCommands, scripts, jsManager, ["format", "format:check"]);
|
|
14299
|
-
const pyproject = present.has("pyproject.toml") ? await readTextIfSmall(
|
|
14300
|
-
const requirements = present.has("requirements.txt") ? await readTextIfSmall(
|
|
14468
|
+
const pyproject = present.has("pyproject.toml") ? await readTextIfSmall(join14(cwd, "pyproject.toml"), root) : void 0;
|
|
14469
|
+
const requirements = present.has("requirements.txt") ? await readTextIfSmall(join14(cwd, "requirements.txt"), root) : void 0;
|
|
14301
14470
|
const pythonTools = detectPythonTools(pyproject, requirements, configFiles);
|
|
14302
14471
|
const pythonManager = pythonManagers[0];
|
|
14303
14472
|
const pythonPrefix = pythonManager === "uv" ? "uv run " : pythonManager === "poetry" ? "poetry run " : pythonManager === "pipenv" ? "pipenv run " : "";
|
|
@@ -14315,8 +14484,8 @@ async function inspectProject(cwd) {
|
|
|
14315
14484
|
if (present.has("pyproject.toml") || present.has("requirements.txt") || extensions.has("py")) {
|
|
14316
14485
|
languages.add("Python");
|
|
14317
14486
|
}
|
|
14318
|
-
const gitignorePath = await pathKind(
|
|
14319
|
-
const hasCodeGraph = await pathKind(
|
|
14487
|
+
const gitignorePath = await pathKind(join14(cwd, ".gitignore"), root) === "file" ? ".gitignore" : void 0;
|
|
14488
|
+
const hasCodeGraph = await pathKind(join14(cwd, ".flavor", "astgraph", "index.db"), root) === "file";
|
|
14320
14489
|
return {
|
|
14321
14490
|
cwd,
|
|
14322
14491
|
projectName,
|
|
@@ -14425,12 +14594,12 @@ function isEntryIgnored(lines, entry2) {
|
|
|
14425
14594
|
});
|
|
14426
14595
|
}
|
|
14427
14596
|
async function addSessionsToGitignore(cwd) {
|
|
14428
|
-
const path =
|
|
14597
|
+
const path = join14(cwd, ".gitignore");
|
|
14429
14598
|
const root = await realpath7(cwd);
|
|
14430
14599
|
await assertSafeManagedPath(path, root);
|
|
14431
14600
|
let existing;
|
|
14432
14601
|
try {
|
|
14433
|
-
existing = await
|
|
14602
|
+
existing = await readFile10(path, "utf8");
|
|
14434
14603
|
} catch (error) {
|
|
14435
14604
|
if (!isMissingPathError(error)) throw error;
|
|
14436
14605
|
existing = "";
|
|
@@ -14445,7 +14614,7 @@ async function addSessionsToGitignore(cwd) {
|
|
|
14445
14614
|
}
|
|
14446
14615
|
async function assertSafeManagedPath(path, root) {
|
|
14447
14616
|
try {
|
|
14448
|
-
const details = await
|
|
14617
|
+
const details = await lstat5(path);
|
|
14449
14618
|
if (details.isSymbolicLink()) {
|
|
14450
14619
|
throw new Error(`Refusing to overwrite managed file symbolic link: ${path}`);
|
|
14451
14620
|
}
|
|
@@ -14459,9 +14628,9 @@ async function assertSafeManagedPath(path, root) {
|
|
|
14459
14628
|
}
|
|
14460
14629
|
async function ensureFlavorDirectories(cwd) {
|
|
14461
14630
|
const root = await realpath7(cwd);
|
|
14462
|
-
const sessionsDir =
|
|
14463
|
-
const skillsDir =
|
|
14464
|
-
const pluginsDir =
|
|
14631
|
+
const sessionsDir = join14(cwd, ".flavor", "sessions");
|
|
14632
|
+
const skillsDir = join14(cwd, ".flavor", "skills");
|
|
14633
|
+
const pluginsDir = join14(cwd, ".flavor", "plugins");
|
|
14465
14634
|
await mkdir9(sessionsDir, { recursive: true, mode: 448 });
|
|
14466
14635
|
await mkdir9(skillsDir, { recursive: true, mode: 448 });
|
|
14467
14636
|
await mkdir9(pluginsDir, { recursive: true, mode: 448 });
|
|
@@ -14474,11 +14643,11 @@ async function ensureFlavorDirectories(cwd) {
|
|
|
14474
14643
|
}
|
|
14475
14644
|
}
|
|
14476
14645
|
async function createExampleFlavorConfig(cwd) {
|
|
14477
|
-
const configPath =
|
|
14646
|
+
const configPath = join14(cwd, ".flavor", "flavor.json");
|
|
14478
14647
|
const root = await realpath7(cwd);
|
|
14479
14648
|
await assertSafeManagedPath(configPath, root);
|
|
14480
14649
|
try {
|
|
14481
|
-
await
|
|
14650
|
+
await lstat5(configPath);
|
|
14482
14651
|
return;
|
|
14483
14652
|
} catch (error) {
|
|
14484
14653
|
if (!isMissingPathError(error)) throw error;
|
|
@@ -14509,12 +14678,12 @@ async function initializeFlavor(cwd) {
|
|
|
14509
14678
|
await ensureFlavorDirectories(cwd);
|
|
14510
14679
|
await createExampleFlavorConfig(cwd);
|
|
14511
14680
|
const facts = await inspectProject(cwd);
|
|
14512
|
-
const path =
|
|
14681
|
+
const path = join14(cwd, "FLAVOR.md");
|
|
14513
14682
|
const root = await realpath7(cwd);
|
|
14514
14683
|
await assertSafeManagedPath(path, root);
|
|
14515
14684
|
let existing;
|
|
14516
14685
|
try {
|
|
14517
|
-
existing = await
|
|
14686
|
+
existing = await readFile10(path, "utf8");
|
|
14518
14687
|
} catch (error) {
|
|
14519
14688
|
if (!isMissingPathError(error)) throw error;
|
|
14520
14689
|
}
|
|
@@ -14527,7 +14696,7 @@ async function initializeFlavor(cwd) {
|
|
|
14527
14696
|
}
|
|
14528
14697
|
|
|
14529
14698
|
// src/utils/memory-restart.ts
|
|
14530
|
-
import { join as
|
|
14699
|
+
import { join as join15 } from "path";
|
|
14531
14700
|
var MEMORY_RESTART_EXIT_CODE = 75;
|
|
14532
14701
|
var MEMORY_RESTART_MARKER_FILE = "memory-restart.json";
|
|
14533
14702
|
var MEMORY_RESTART_WINDOW_MS = 60 * 60 * 1e3;
|
|
@@ -14544,7 +14713,7 @@ function clearMemoryRotation() {
|
|
|
14544
14713
|
rotationActive = false;
|
|
14545
14714
|
}
|
|
14546
14715
|
function memoryRestartMarkerPath(workspace) {
|
|
14547
|
-
return
|
|
14716
|
+
return join15(workspace, ".flavor", "tmp", MEMORY_RESTART_MARKER_FILE);
|
|
14548
14717
|
}
|
|
14549
14718
|
function nextMemoryRestartMarker(previous, sessionId, now, continuation) {
|
|
14550
14719
|
const previousWindowStart = previous?.windowStartedAt ?? previous?.requestedAt;
|
|
@@ -15078,7 +15247,7 @@ ${cycle.verification.summary}` !== signature) break;
|
|
|
15078
15247
|
// src/goal/orchestrator.ts
|
|
15079
15248
|
import { createHash as createHash11 } from "crypto";
|
|
15080
15249
|
import { writeFile as writeFile9, mkdir as mkdir10 } from "fs/promises";
|
|
15081
|
-
import { join as
|
|
15250
|
+
import { join as join17 } from "path";
|
|
15082
15251
|
|
|
15083
15252
|
// src/goal/types.ts
|
|
15084
15253
|
import { z as z23 } from "zod";
|
|
@@ -15249,8 +15418,8 @@ function extractJson(text) {
|
|
|
15249
15418
|
|
|
15250
15419
|
// src/goal/classifier.ts
|
|
15251
15420
|
import { createHash as createHash10 } from "crypto";
|
|
15252
|
-
import { readFile as
|
|
15253
|
-
import { join as
|
|
15421
|
+
import { readFile as readFile11 } from "fs/promises";
|
|
15422
|
+
import { join as join16 } from "path";
|
|
15254
15423
|
function buildSkepticPrompt(evidence, plan, index) {
|
|
15255
15424
|
const criteriaList = plan.criteria.map((c) => ` ${c.id}. [${c.type}] ${c.description}`).join("\n");
|
|
15256
15425
|
return [
|
|
@@ -15429,10 +15598,10 @@ function computeFingerprint(gaps) {
|
|
|
15429
15598
|
}
|
|
15430
15599
|
async function collectEvidence(workspace, objective, finalResponse, priorGaps, contractHash2 = createHash10("sha256").update(objective).digest("hex"), hostVerification) {
|
|
15431
15600
|
const workspaceState = await collectWorkspaceState(workspace);
|
|
15432
|
-
const planPath2 =
|
|
15601
|
+
const planPath2 = join16(workspace, ".flavor", "goal-plan.md");
|
|
15433
15602
|
let planFile = null;
|
|
15434
15603
|
try {
|
|
15435
|
-
planFile = await
|
|
15604
|
+
planFile = await readFile11(planPath2, "utf8");
|
|
15436
15605
|
} catch {
|
|
15437
15606
|
}
|
|
15438
15607
|
return {
|
|
@@ -15801,8 +15970,8 @@ function waitForStageRetry(delayMs, signal) {
|
|
|
15801
15970
|
});
|
|
15802
15971
|
}
|
|
15803
15972
|
async function writePlanFile(workspace, plan) {
|
|
15804
|
-
const dir =
|
|
15805
|
-
const planPath2 =
|
|
15973
|
+
const dir = join17(workspace, ".flavor");
|
|
15974
|
+
const planPath2 = join17(dir, "goal-plan.md");
|
|
15806
15975
|
await mkdir10(dir, { recursive: true });
|
|
15807
15976
|
const criteriaText = plan.criteria.map((c) => `- [${c.type}] AC-${c.id}: ${c.description}`).join("\n");
|
|
15808
15977
|
const content = [
|
|
@@ -15879,8 +16048,8 @@ function formatPriorGaps(gaps) {
|
|
|
15879
16048
|
// src/goal/store.ts
|
|
15880
16049
|
import { constants as constants4 } from "fs";
|
|
15881
16050
|
import { randomUUID as randomUUID11 } from "crypto";
|
|
15882
|
-
import { lstat as
|
|
15883
|
-
import { dirname as dirname9, join as
|
|
16051
|
+
import { lstat as lstat6, mkdir as mkdir11, open as open5, readFile as readFile12, rename as rename4, rm as rm6, stat as stat6 } from "fs/promises";
|
|
16052
|
+
import { dirname as dirname9, join as join18, relative as relative9, resolve as resolve12, sep as sep9 } from "path";
|
|
15884
16053
|
var DEFAULT_MAX_BYTES = 1024 * 1024;
|
|
15885
16054
|
var GoalStore = class {
|
|
15886
16055
|
#workspace;
|
|
@@ -15888,7 +16057,7 @@ var GoalStore = class {
|
|
|
15888
16057
|
#maxBytes;
|
|
15889
16058
|
constructor(options) {
|
|
15890
16059
|
this.#workspace = resolve12(options.workspace);
|
|
15891
|
-
this.#root =
|
|
16060
|
+
this.#root = join18(this.#workspace, ".flavor", "goals");
|
|
15892
16061
|
this.#maxBytes = options.maxBytes ?? DEFAULT_MAX_BYTES;
|
|
15893
16062
|
}
|
|
15894
16063
|
async save(input) {
|
|
@@ -15896,13 +16065,13 @@ var GoalStore = class {
|
|
|
15896
16065
|
await assertNoSymlink(this.#workspace, dirname9(this.#root));
|
|
15897
16066
|
await mkdir11(this.#root, { recursive: true, mode: 448 });
|
|
15898
16067
|
await assertNoSymlink(this.#workspace, this.#root);
|
|
15899
|
-
const target =
|
|
16068
|
+
const target = join18(this.#root, `${state.id}.json`);
|
|
15900
16069
|
const body = `${JSON.stringify(state, null, 2)}
|
|
15901
16070
|
`;
|
|
15902
16071
|
if (Buffer.byteLength(body) > this.#maxBytes) {
|
|
15903
16072
|
throw new Error(`Goal state exceeds maximum size of ${this.#maxBytes} bytes`);
|
|
15904
16073
|
}
|
|
15905
|
-
const temporary =
|
|
16074
|
+
const temporary = join18(this.#root, `.${state.id}.${process.pid}.${randomUUID11()}.tmp`);
|
|
15906
16075
|
let handle;
|
|
15907
16076
|
try {
|
|
15908
16077
|
handle = await open5(temporary, constants4.O_CREAT | constants4.O_EXCL | constants4.O_WRONLY, 384);
|
|
@@ -15923,13 +16092,13 @@ var GoalStore = class {
|
|
|
15923
16092
|
}
|
|
15924
16093
|
await assertNoSymlink(this.#workspace, dirname9(this.#root));
|
|
15925
16094
|
await assertNoSymlink(this.#workspace, this.#root);
|
|
15926
|
-
const target =
|
|
16095
|
+
const target = join18(this.#root, `${goalId}.json`);
|
|
15927
16096
|
try {
|
|
15928
16097
|
const metadata = await stat6(target);
|
|
15929
16098
|
if (!metadata.isFile() || metadata.size > this.#maxBytes) {
|
|
15930
16099
|
throw new Error("Goal state is not a bounded regular file");
|
|
15931
16100
|
}
|
|
15932
|
-
const raw = await
|
|
16101
|
+
const raw = await readFile12(target, "utf8");
|
|
15933
16102
|
return GoalStateSchema.parse(JSON.parse(raw));
|
|
15934
16103
|
} catch (error) {
|
|
15935
16104
|
if (isCode4(error, "ENOENT")) throw new Error(`Goal "${goalId}" was not found`);
|
|
@@ -15953,7 +16122,7 @@ async function assertNoSymlink(root, target) {
|
|
|
15953
16122
|
}
|
|
15954
16123
|
for (const path of chain.reverse()) {
|
|
15955
16124
|
try {
|
|
15956
|
-
if ((await
|
|
16125
|
+
if ((await lstat6(path)).isSymbolicLink()) throw new Error(`Goal path contains a symbolic link: ${path}`);
|
|
15957
16126
|
} catch (error) {
|
|
15958
16127
|
if (!isCode4(error, "ENOENT")) throw error;
|
|
15959
16128
|
}
|
|
@@ -15964,8 +16133,8 @@ function isCode4(error, code) {
|
|
|
15964
16133
|
}
|
|
15965
16134
|
|
|
15966
16135
|
// src/loop/isolation.ts
|
|
15967
|
-
import { appendFile as appendFile2, mkdir as mkdir12, readFile as
|
|
15968
|
-
import { join as
|
|
16136
|
+
import { appendFile as appendFile2, mkdir as mkdir12, readFile as readFile13 } from "fs/promises";
|
|
16137
|
+
import { join as join19, resolve as resolve13 } from "path";
|
|
15969
16138
|
var CODE_CHANGE = /\b(?:fix|implement|add|update|change|modify|refactor|build|create|remove|delete|write|migrate)\b|修复|实现|添加|更新|修改|重构|创建|删除|迁移/i;
|
|
15970
16139
|
var READ_ONLY = /\b(?:review|analy[sz]e|inspect|explain|research|investigate|audit|summari[sz]e|report)\b|调研|分析|审查|检查|解释|研究|总结|报告/i;
|
|
15971
16140
|
function inferGoalIntent(goal) {
|
|
@@ -15988,8 +16157,8 @@ async function prepareLoopWorkspace(options) {
|
|
|
15988
16157
|
return { kind: "needs_human", reason: "The workspace has uncommitted changes that a new worktree would omit." };
|
|
15989
16158
|
}
|
|
15990
16159
|
options.signal?.throwIfAborted();
|
|
15991
|
-
const worktrees =
|
|
15992
|
-
const path =
|
|
16160
|
+
const worktrees = join19(root, ".worktrees");
|
|
16161
|
+
const path = join19(worktrees, `loop-${options.loopId}`);
|
|
15993
16162
|
const branch = `loop/${options.loopId}`;
|
|
15994
16163
|
try {
|
|
15995
16164
|
await ensureWorktreeDirectoryIgnored(root);
|
|
@@ -16019,12 +16188,12 @@ async function ensureWorktreeDirectoryIgnored(root) {
|
|
|
16019
16188
|
const commonDirectory = await git(root, ["rev-parse", "--git-common-dir"]);
|
|
16020
16189
|
if (commonDirectory.code !== 0) throw new Error(commonDirectory.stderr.trim() || "Git common directory is unavailable");
|
|
16021
16190
|
const gitDirectory = resolve13(root, commonDirectory.stdout.trim());
|
|
16022
|
-
const infoDirectory =
|
|
16023
|
-
const excludePath =
|
|
16191
|
+
const infoDirectory = join19(gitDirectory, "info");
|
|
16192
|
+
const excludePath = join19(infoDirectory, "exclude");
|
|
16024
16193
|
await mkdir12(infoDirectory, { recursive: true });
|
|
16025
16194
|
let existing = "";
|
|
16026
16195
|
try {
|
|
16027
|
-
existing = await
|
|
16196
|
+
existing = await readFile13(excludePath, "utf8");
|
|
16028
16197
|
} catch (error) {
|
|
16029
16198
|
if (!(typeof error === "object" && error !== null && "code" in error && error.code === "ENOENT")) throw error;
|
|
16030
16199
|
}
|
|
@@ -16035,8 +16204,8 @@ async function ensureWorktreeDirectoryIgnored(root) {
|
|
|
16035
16204
|
|
|
16036
16205
|
// src/loop/store.ts
|
|
16037
16206
|
import { constants as constants5 } from "fs";
|
|
16038
|
-
import { lstat as
|
|
16039
|
-
import { dirname as dirname10, join as
|
|
16207
|
+
import { lstat as lstat7, mkdir as mkdir13, open as open6, readFile as readFile14, realpath as realpath8, rename as rename5, rm as rm7, stat as stat7 } from "fs/promises";
|
|
16208
|
+
import { dirname as dirname10, join as join20, relative as relative10, resolve as resolve14, sep as sep10 } from "path";
|
|
16040
16209
|
import { randomUUID as randomUUID12 } from "crypto";
|
|
16041
16210
|
var LOOP_ID = /^[A-Za-z0-9][A-Za-z0-9._-]{0,127}$/;
|
|
16042
16211
|
var DEFAULT_MAX_BYTES2 = 5 * 1024 * 1024;
|
|
@@ -16047,7 +16216,7 @@ var LoopStore = class {
|
|
|
16047
16216
|
#appendTails = /* @__PURE__ */ new Map();
|
|
16048
16217
|
constructor(options) {
|
|
16049
16218
|
this.#workspace = resolve14(options.workspace);
|
|
16050
|
-
this.#root =
|
|
16219
|
+
this.#root = join20(this.#workspace, ".flavor", "loops");
|
|
16051
16220
|
this.#maxBytes = options.maxBytes ?? DEFAULT_MAX_BYTES2;
|
|
16052
16221
|
}
|
|
16053
16222
|
async create(state) {
|
|
@@ -16064,11 +16233,11 @@ var LoopStore = class {
|
|
|
16064
16233
|
const state = LoopStateSchema.parse(input);
|
|
16065
16234
|
await this.#assertWorkspace(state.workspace);
|
|
16066
16235
|
const directory = await this.#prepare(state.loopId);
|
|
16067
|
-
const target =
|
|
16236
|
+
const target = join20(directory, "state.json");
|
|
16068
16237
|
const body = `${JSON.stringify(state, null, 2)}
|
|
16069
16238
|
`;
|
|
16070
16239
|
if (Buffer.byteLength(body) > this.#maxBytes) throw new Error(`Loop state exceeds maximum size of ${this.#maxBytes} bytes`);
|
|
16071
|
-
const temporary =
|
|
16240
|
+
const temporary = join20(directory, `.state.${process.pid}.${randomUUID12()}.tmp`);
|
|
16072
16241
|
let handle;
|
|
16073
16242
|
try {
|
|
16074
16243
|
handle = await open6(temporary, constants5.O_CREAT | constants5.O_EXCL | constants5.O_WRONLY, 384);
|
|
@@ -16084,11 +16253,11 @@ var LoopStore = class {
|
|
|
16084
16253
|
}
|
|
16085
16254
|
async load(loopId) {
|
|
16086
16255
|
const directory = await this.#safeExisting(loopId);
|
|
16087
|
-
const target =
|
|
16256
|
+
const target = join20(directory, "state.json");
|
|
16088
16257
|
try {
|
|
16089
16258
|
const metadata = await stat7(target);
|
|
16090
16259
|
if (!metadata.isFile() || metadata.size > this.#maxBytes) throw new Error("Loop state is not a bounded regular file");
|
|
16091
|
-
const raw = await
|
|
16260
|
+
const raw = await readFile14(target, "utf8");
|
|
16092
16261
|
const state = LoopStateSchema.parse(JSON.parse(raw));
|
|
16093
16262
|
await this.#assertWorkspace(state.workspace);
|
|
16094
16263
|
return state;
|
|
@@ -16104,7 +16273,7 @@ var LoopStore = class {
|
|
|
16104
16273
|
const previous = this.#appendTails.get(event.loopId) ?? Promise.resolve();
|
|
16105
16274
|
const next = previous.catch(() => void 0).then(async () => {
|
|
16106
16275
|
const directory = await this.#prepare(event.loopId);
|
|
16107
|
-
const path =
|
|
16276
|
+
const path = join20(directory, "events.jsonl");
|
|
16108
16277
|
const handle = await open6(path, constants5.O_CREAT | constants5.O_APPEND | constants5.O_WRONLY, 384);
|
|
16109
16278
|
try {
|
|
16110
16279
|
await handle.writeFile(`${JSON.stringify(event)}
|
|
@@ -16121,7 +16290,7 @@ var LoopStore = class {
|
|
|
16121
16290
|
await assertNoSymlink2(this.#workspace, dirname10(this.#root));
|
|
16122
16291
|
await mkdir13(this.#root, { recursive: true, mode: 448 });
|
|
16123
16292
|
await assertNoSymlink2(this.#workspace, this.#root);
|
|
16124
|
-
const directory =
|
|
16293
|
+
const directory = join20(this.#root, loopId);
|
|
16125
16294
|
await mkdir13(directory, { recursive: true, mode: 448 });
|
|
16126
16295
|
await assertNoSymlink2(this.#workspace, directory);
|
|
16127
16296
|
return directory;
|
|
@@ -16129,7 +16298,7 @@ var LoopStore = class {
|
|
|
16129
16298
|
async #safeExisting(loopId) {
|
|
16130
16299
|
validateId(loopId);
|
|
16131
16300
|
await assertNoSymlink2(this.#workspace, this.#root);
|
|
16132
|
-
const directory =
|
|
16301
|
+
const directory = join20(this.#root, loopId);
|
|
16133
16302
|
await assertNoSymlink2(this.#workspace, directory);
|
|
16134
16303
|
return directory;
|
|
16135
16304
|
}
|
|
@@ -16155,7 +16324,7 @@ async function assertNoSymlink2(root, target) {
|
|
|
16155
16324
|
}
|
|
16156
16325
|
for (const path of chain.reverse()) {
|
|
16157
16326
|
try {
|
|
16158
|
-
if ((await
|
|
16327
|
+
if ((await lstat7(path)).isSymbolicLink()) throw new Error(`Loop path contains a symbolic link: ${path}`);
|
|
16159
16328
|
} catch (error) {
|
|
16160
16329
|
if (!isCode5(error, "ENOENT")) throw error;
|
|
16161
16330
|
}
|
|
@@ -16177,13 +16346,13 @@ function isCode5(error, code) {
|
|
|
16177
16346
|
}
|
|
16178
16347
|
|
|
16179
16348
|
// src/loop/verifier.ts
|
|
16180
|
-
import { readFile as
|
|
16181
|
-
import { join as
|
|
16349
|
+
import { readFile as readFile15 } from "fs/promises";
|
|
16350
|
+
import { join as join22 } from "path";
|
|
16182
16351
|
|
|
16183
16352
|
// src/tools/shell.ts
|
|
16184
16353
|
import { spawn as spawn4 } from "child_process";
|
|
16185
16354
|
import { existsSync as existsSync4 } from "fs";
|
|
16186
|
-
import { basename as basename5, delimiter as delimiter2, isAbsolute as isAbsolute9, join as
|
|
16355
|
+
import { basename as basename5, delimiter as delimiter2, isAbsolute as isAbsolute9, join as join21, relative as relative11, resolve as resolve15, sep as sep11 } from "path";
|
|
16187
16356
|
import { z as z25 } from "zod";
|
|
16188
16357
|
|
|
16189
16358
|
// src/tools/jobs.ts
|
|
@@ -16846,7 +17015,7 @@ function decodeOutput(buffer) {
|
|
|
16846
17015
|
return new TextDecoder(encoding).decode(buffer);
|
|
16847
17016
|
}
|
|
16848
17017
|
function shellEnvironment(root, environment) {
|
|
16849
|
-
const localBin =
|
|
17018
|
+
const localBin = join21(root, "node_modules", ".bin");
|
|
16850
17019
|
if (!existsSync4(localBin)) return environment;
|
|
16851
17020
|
const pathKey = Object.keys(environment).find((key) => key.toLowerCase() === "path") ?? "PATH";
|
|
16852
17021
|
const current = environment[pathKey] ?? "";
|
|
@@ -16950,13 +17119,13 @@ var MAX_PERSISTED_VERIFICATION_STREAM_BYTES = 1024;
|
|
|
16950
17119
|
async function inferVerificationPlan(workspace) {
|
|
16951
17120
|
const commands = [];
|
|
16952
17121
|
const seen = /* @__PURE__ */ new Set();
|
|
16953
|
-
const packageJson = await optionalJson(
|
|
17122
|
+
const packageJson = await optionalJson(join22(workspace, "package.json"));
|
|
16954
17123
|
const scripts = isRecord3(packageJson?.scripts) ? packageJson.scripts : {};
|
|
16955
17124
|
for (const name of SCRIPT_PRIORITY) {
|
|
16956
17125
|
if (typeof scripts[name] !== "string" || isUnconditionalPassThrough(scripts[name])) continue;
|
|
16957
17126
|
add(commands, seen, npmScript(name));
|
|
16958
17127
|
}
|
|
16959
|
-
const flavor = await optionalText(
|
|
17128
|
+
const flavor = await optionalText(join22(workspace, "FLAVOR.md"));
|
|
16960
17129
|
if (flavor !== void 0) {
|
|
16961
17130
|
for (const match of flavor.matchAll(/^- `([^`]+)`\s*$/gm)) {
|
|
16962
17131
|
const parsed = parseTrustedCommand(match[1] ?? "");
|
|
@@ -17031,7 +17200,7 @@ function isUnconditionalPassThrough(script) {
|
|
|
17031
17200
|
}
|
|
17032
17201
|
async function optionalText(path) {
|
|
17033
17202
|
try {
|
|
17034
|
-
return await
|
|
17203
|
+
return await readFile15(path, "utf8");
|
|
17035
17204
|
} catch (error) {
|
|
17036
17205
|
if (isCode6(error, "ENOENT")) return void 0;
|
|
17037
17206
|
throw error;
|
|
@@ -17052,7 +17221,7 @@ function isCode6(error, code) {
|
|
|
17052
17221
|
|
|
17053
17222
|
// src/models/anthropic.ts
|
|
17054
17223
|
import Anthropic from "@anthropic-ai/sdk";
|
|
17055
|
-
import { readFile as
|
|
17224
|
+
import { readFile as readFile16 } from "fs/promises";
|
|
17056
17225
|
|
|
17057
17226
|
// src/utils/json.ts
|
|
17058
17227
|
function safeParseJson(input) {
|
|
@@ -17101,11 +17270,11 @@ function isEnvTruthy(value) {
|
|
|
17101
17270
|
|
|
17102
17271
|
// src/utils/log.ts
|
|
17103
17272
|
import { appendFile as appendFile3, mkdir as mkdir14 } from "fs/promises";
|
|
17104
|
-
import { dirname as dirname11, join as
|
|
17273
|
+
import { dirname as dirname11, join as join23, resolve as resolve16 } from "path";
|
|
17105
17274
|
var pending = Promise.resolve();
|
|
17106
17275
|
var usagePending = Promise.resolve();
|
|
17107
17276
|
function usageLogPath() {
|
|
17108
|
-
return process.env.FLAVOR_USAGE_FILE ??
|
|
17277
|
+
return process.env.FLAVOR_USAGE_FILE ?? join23(process.cwd(), ".flavor", "usage.jsonl");
|
|
17109
17278
|
}
|
|
17110
17279
|
var usageSessionId = "unknown";
|
|
17111
17280
|
var truncateOnNextAppend = false;
|
|
@@ -17134,7 +17303,7 @@ var AuditLogger = class {
|
|
|
17134
17303
|
#path;
|
|
17135
17304
|
#closed = false;
|
|
17136
17305
|
constructor(workspace) {
|
|
17137
|
-
this.#path =
|
|
17306
|
+
this.#path = join23(resolve16(workspace), ".flavor", "audit.jsonl");
|
|
17138
17307
|
}
|
|
17139
17308
|
get path() {
|
|
17140
17309
|
return this.#path;
|
|
@@ -17391,7 +17560,7 @@ ${modelContentText(message2.content)}
|
|
|
17391
17560
|
source: {
|
|
17392
17561
|
type: "base64",
|
|
17393
17562
|
media_type: block.mediaType,
|
|
17394
|
-
data: (await
|
|
17563
|
+
data: (await readFile16(block.source.path)).toString("base64")
|
|
17395
17564
|
}
|
|
17396
17565
|
}));
|
|
17397
17566
|
if (message2.cacheBreakpoint && Array.isArray(content) && content.length > 0) {
|
|
@@ -17613,7 +17782,7 @@ function resolveCacheProfile(input) {
|
|
|
17613
17782
|
// src/models/openai.ts
|
|
17614
17783
|
import OpenAI from "openai";
|
|
17615
17784
|
import { createHash as createHash12 } from "crypto";
|
|
17616
|
-
import { readFile as
|
|
17785
|
+
import { readFile as readFile17 } from "fs/promises";
|
|
17617
17786
|
var DEFAULT_THINKING_EFFORT = "high";
|
|
17618
17787
|
var EXPLICIT_CACHE_BREAKPOINT = { mode: "explicit" };
|
|
17619
17788
|
var MAX_EXPLICIT_CACHE_BREAKPOINTS = 3;
|
|
@@ -17636,7 +17805,7 @@ async function toInput(message2, cacheBreakpoint = false) {
|
|
|
17636
17805
|
prompt_cache_breakpoint: EXPLICIT_CACHE_BREAKPOINT
|
|
17637
17806
|
}] : message2.content : await Promise.all(message2.content.map(async (block) => block.type === "text" ? { type: "input_text", text: block.text } : {
|
|
17638
17807
|
type: "input_image",
|
|
17639
|
-
image_url: `data:${block.mediaType};base64,${(await
|
|
17808
|
+
image_url: `data:${block.mediaType};base64,${(await readFile17(block.source.path)).toString("base64")}`,
|
|
17640
17809
|
detail: "auto"
|
|
17641
17810
|
}));
|
|
17642
17811
|
if (cacheBreakpoint && Array.isArray(content) && content.length > 0) {
|
|
@@ -17880,16 +18049,28 @@ function selectExplicitBreakpointIndexes(messages) {
|
|
|
17880
18049
|
const indexes = messages.map((message2, index) => message2.cacheBreakpoint ? index : -1).filter((index) => index >= 0);
|
|
17881
18050
|
const tail = messages.length - 1;
|
|
17882
18051
|
if (tail >= 0 && indexes.at(-1) !== tail) indexes.push(tail);
|
|
17883
|
-
|
|
18052
|
+
const firstNonSystem = messages.findIndex((message2) => message2.role !== "system");
|
|
18053
|
+
const leadingSystemEnd = firstNonSystem < 0 ? tail : firstNonSystem - 1;
|
|
18054
|
+
const stable = indexes.filter((index) => index <= leadingSystemEnd).at(-1);
|
|
18055
|
+
if (stable === void 0) return new Set(indexes.slice(-MAX_EXPLICIT_CACHE_BREAKPOINTS));
|
|
18056
|
+
return /* @__PURE__ */ new Set([
|
|
18057
|
+
stable,
|
|
18058
|
+
...indexes.filter((index) => index !== stable).slice(-(MAX_EXPLICIT_CACHE_BREAKPOINTS - 1))
|
|
18059
|
+
]);
|
|
17884
18060
|
}
|
|
17885
18061
|
function promptCacheKey(request, sortedTools) {
|
|
17886
|
-
let stableEnd = -1;
|
|
17887
|
-
request.messages.forEach((message2, index) => {
|
|
17888
|
-
if (message2.cacheBreakpoint) stableEnd = index;
|
|
17889
|
-
});
|
|
17890
18062
|
const firstNonSystem = request.messages.findIndex((message2) => message2.role !== "system");
|
|
17891
18063
|
const leadingSystemEnd = firstNonSystem < 0 ? request.messages.length - 1 : firstNonSystem - 1;
|
|
17892
|
-
stableEnd =
|
|
18064
|
+
let stableEnd = -1;
|
|
18065
|
+
for (let index = 0; index <= leadingSystemEnd; index += 1) {
|
|
18066
|
+
if (request.messages[index]?.cacheBreakpoint) stableEnd = index;
|
|
18067
|
+
}
|
|
18068
|
+
if (stableEnd < 0) {
|
|
18069
|
+
request.messages.forEach((message2, index) => {
|
|
18070
|
+
if (message2.cacheBreakpoint) stableEnd = index;
|
|
18071
|
+
});
|
|
18072
|
+
stableEnd = Math.max(stableEnd, leadingSystemEnd);
|
|
18073
|
+
}
|
|
17893
18074
|
const stableMessages = stableEnd < 0 ? [] : request.messages.slice(0, stableEnd + 1);
|
|
17894
18075
|
const digest = createHash12("sha256").update(JSON.stringify({ model: request.model, messages: stableMessages, tools: sortedTools })).digest("hex").slice(0, 56);
|
|
17895
18076
|
return `flavor-${digest}`;
|
|
@@ -18130,7 +18311,7 @@ import {
|
|
|
18130
18311
|
|
|
18131
18312
|
// src/utils/version.ts
|
|
18132
18313
|
import { readFileSync as readFileSync2 } from "fs";
|
|
18133
|
-
import { dirname as dirname12, join as
|
|
18314
|
+
import { dirname as dirname12, join as join24 } from "path";
|
|
18134
18315
|
import { fileURLToPath } from "url";
|
|
18135
18316
|
var cached;
|
|
18136
18317
|
function packageVersion() {
|
|
@@ -18138,7 +18319,7 @@ function packageVersion() {
|
|
|
18138
18319
|
let directory = dirname12(fileURLToPath(import.meta.url));
|
|
18139
18320
|
for (; ; ) {
|
|
18140
18321
|
try {
|
|
18141
|
-
const manifest = JSON.parse(readFileSync2(
|
|
18322
|
+
const manifest = JSON.parse(readFileSync2(join24(directory, "package.json"), "utf8"));
|
|
18142
18323
|
if (typeof manifest.version === "string" && manifest.version.length > 0) {
|
|
18143
18324
|
cached = manifest.version;
|
|
18144
18325
|
return cached;
|
|
@@ -18565,8 +18746,8 @@ function isStoredToken(value) {
|
|
|
18565
18746
|
}
|
|
18566
18747
|
|
|
18567
18748
|
// src/permissions/policy.ts
|
|
18568
|
-
import { readFile as
|
|
18569
|
-
import { join as
|
|
18749
|
+
import { readFile as readFile18 } from "fs/promises";
|
|
18750
|
+
import { join as join25, resolve as resolve18 } from "path";
|
|
18570
18751
|
import { z as z27 } from "zod";
|
|
18571
18752
|
var DecisionSchema2 = z27.enum(["allow", "ask", "deny"]);
|
|
18572
18753
|
var PrefixSchema = z27.array(z27.string().min(1).max(1024)).min(1).max(64);
|
|
@@ -18603,15 +18784,15 @@ var CompiledPermissionPolicy = class {
|
|
|
18603
18784
|
async function loadPermissionPolicy(options) {
|
|
18604
18785
|
const sources = [
|
|
18605
18786
|
...options.managedPath === void 0 ? [] : [{ tier: "managed", path: resolve18(options.managedPath) }],
|
|
18606
|
-
{ tier: "user", path:
|
|
18607
|
-
{ tier: "project", path:
|
|
18608
|
-
{ tier: "local", path:
|
|
18787
|
+
{ tier: "user", path: join25(resolve18(options.home), ".flavor-code", "permissions.json") },
|
|
18788
|
+
{ tier: "project", path: join25(resolve18(options.workspace), ".flavor", "permissions.json") },
|
|
18789
|
+
{ tier: "local", path: join25(resolve18(options.workspace), ".flavor", "permissions.local.json") }
|
|
18609
18790
|
];
|
|
18610
18791
|
const rules = [];
|
|
18611
18792
|
for (const source of sources) {
|
|
18612
18793
|
let raw;
|
|
18613
18794
|
try {
|
|
18614
|
-
raw = await
|
|
18795
|
+
raw = await readFile18(source.path, "utf8");
|
|
18615
18796
|
} catch (error) {
|
|
18616
18797
|
if (isCode7(error, "ENOENT")) continue;
|
|
18617
18798
|
throw error;
|
|
@@ -18826,7 +19007,7 @@ function data(value) {
|
|
|
18826
19007
|
|
|
18827
19008
|
// src/skills/registry.ts
|
|
18828
19009
|
import { constants as constants6 } from "fs";
|
|
18829
|
-
import { lstat as
|
|
19010
|
+
import { lstat as lstat8, open as defaultOpen, readdir as readdir5, realpath as realpath9 } from "fs/promises";
|
|
18830
19011
|
import { isAbsolute as isAbsolute10, relative as relative12, resolve as resolve19, sep as sep12 } from "path";
|
|
18831
19012
|
import { marked } from "marked";
|
|
18832
19013
|
import { parseDocument } from "yaml";
|
|
@@ -18991,12 +19172,12 @@ var SkillRegistry = class {
|
|
|
18991
19172
|
let entries;
|
|
18992
19173
|
let physicalRoot = root;
|
|
18993
19174
|
try {
|
|
18994
|
-
const rootInfo = await
|
|
19175
|
+
const rootInfo = await lstat8(root);
|
|
18995
19176
|
if (rootInfo.isSymbolicLink() || !rootInfo.isDirectory()) throw new Error("Skill root must be a real directory");
|
|
18996
19177
|
physicalRoot = await realpath9(root);
|
|
18997
19178
|
entries = await readdir5(root, { withFileTypes: true });
|
|
18998
19179
|
} catch (error) {
|
|
18999
|
-
if (
|
|
19180
|
+
if (isMissing2(error)) continue;
|
|
19000
19181
|
this.#diagnose(root, error);
|
|
19001
19182
|
continue;
|
|
19002
19183
|
}
|
|
@@ -19145,7 +19326,7 @@ async function openFile(path, flags) {
|
|
|
19145
19326
|
return defaultOpen(path, flags);
|
|
19146
19327
|
}
|
|
19147
19328
|
async function openVerifiedFile(path, root, label, opener, expected) {
|
|
19148
|
-
const initial = await
|
|
19329
|
+
const initial = await lstat8(path, { bigint: true });
|
|
19149
19330
|
if (initial.isSymbolicLink()) throw new Error(`${label} symlinks are not allowed`);
|
|
19150
19331
|
assertRegular(initial, label);
|
|
19151
19332
|
const canonical3 = await realpath9(path);
|
|
@@ -19212,7 +19393,7 @@ function isWithin4(root, candidate) {
|
|
|
19212
19393
|
const delta = relative12(root, candidate);
|
|
19213
19394
|
return delta === "" || delta !== ".." && !delta.startsWith(`..${sep12}`) && !isAbsolute10(delta);
|
|
19214
19395
|
}
|
|
19215
|
-
function
|
|
19396
|
+
function isMissing2(error) {
|
|
19216
19397
|
return typeof error === "object" && error !== null && "code" in error && error.code === "ENOENT";
|
|
19217
19398
|
}
|
|
19218
19399
|
function normalizeReference(reference) {
|
|
@@ -19377,8 +19558,8 @@ function createSkillResourceTool(registry) {
|
|
|
19377
19558
|
|
|
19378
19559
|
// src/session/store.ts
|
|
19379
19560
|
import { constants as constants7 } from "fs";
|
|
19380
|
-
import { lstat as
|
|
19381
|
-
import { basename as basename6, dirname as dirname14, join as
|
|
19561
|
+
import { lstat as lstat9, mkdir as mkdir15, open as open7, readFile as readFile19, readdir as readdir6, realpath as realpath10, rename as rename6, rm as rm8, stat as stat8 } from "fs/promises";
|
|
19562
|
+
import { basename as basename6, dirname as dirname14, join as join26, relative as relative13, resolve as resolve20, sep as sep13 } from "path";
|
|
19382
19563
|
import { randomUUID as randomUUID13 } from "crypto";
|
|
19383
19564
|
import { z as z29 } from "zod";
|
|
19384
19565
|
|
|
@@ -20161,7 +20342,7 @@ var SessionStore = class {
|
|
|
20161
20342
|
#maxSessions;
|
|
20162
20343
|
constructor(options) {
|
|
20163
20344
|
this.#workspace = resolve20(options.workspace);
|
|
20164
|
-
this.#sessions =
|
|
20345
|
+
this.#sessions = join26(this.#workspace, ".flavor", "sessions");
|
|
20165
20346
|
this.#maxBytes = options.maxBytes ?? DEFAULT_MAX_SESSION_BYTES;
|
|
20166
20347
|
if (!Number.isSafeInteger(this.#maxBytes) || this.#maxBytes < 256) throw new Error("maxBytes must be an integer of at least 256");
|
|
20167
20348
|
this.#maxSessions = options.maxSessions ?? 50;
|
|
@@ -20193,7 +20374,7 @@ var SessionStore = class {
|
|
|
20193
20374
|
const body = `${lines.join("\n")}
|
|
20194
20375
|
`;
|
|
20195
20376
|
if (Buffer.byteLength(body) > this.#maxBytes) throw new Error(`Session exceeds maximum size of ${this.#maxBytes} bytes`);
|
|
20196
|
-
const temporary =
|
|
20377
|
+
const temporary = join26(this.#sessions, `.${document.sessionId}.${process.pid}.${randomUUID13()}.tmp`);
|
|
20197
20378
|
let handle;
|
|
20198
20379
|
try {
|
|
20199
20380
|
handle = await open7(temporary, constants7.O_CREAT | constants7.O_EXCL | constants7.O_WRONLY, 384);
|
|
@@ -20212,7 +20393,7 @@ var SessionStore = class {
|
|
|
20212
20393
|
async acquireLease(sessionId) {
|
|
20213
20394
|
SessionIdSchema.parse(sessionId);
|
|
20214
20395
|
await this.#prepareDirectory();
|
|
20215
|
-
const path =
|
|
20396
|
+
const path = join26(this.#sessions, `.${sessionId}.lock`);
|
|
20216
20397
|
const token = JSON.stringify({ pid: process.pid, nonce: randomUUID13(), createdAt: (/* @__PURE__ */ new Date()).toISOString() });
|
|
20217
20398
|
for (let attempt = 0; attempt < 3; attempt += 1) {
|
|
20218
20399
|
let handle;
|
|
@@ -20230,7 +20411,7 @@ var SessionStore = class {
|
|
|
20230
20411
|
release: async () => {
|
|
20231
20412
|
if (released) return;
|
|
20232
20413
|
try {
|
|
20233
|
-
if (await
|
|
20414
|
+
if (await readFile19(path, "utf8") === token) await rm8(path, { force: true });
|
|
20234
20415
|
} catch (error) {
|
|
20235
20416
|
if (!isCode8(error, "ENOENT")) throw error;
|
|
20236
20417
|
}
|
|
@@ -20346,14 +20527,14 @@ var SessionStore = class {
|
|
|
20346
20527
|
if (isCode8(error, "ENOENT")) return;
|
|
20347
20528
|
throw error;
|
|
20348
20529
|
}
|
|
20349
|
-
const owner2 = await reclaimStaleSessionLock(
|
|
20530
|
+
const owner2 = await reclaimStaleSessionLock(join26(this.#sessions, `.${sessionId}.lock`));
|
|
20350
20531
|
if (owner2 !== void 0) throw new Error(`Session "${sessionId}" is active in process ${owner2} and cannot be deleted`);
|
|
20351
20532
|
const path = this.#path(sessionId);
|
|
20352
20533
|
try {
|
|
20353
|
-
const metadata = await
|
|
20534
|
+
const metadata = await lstat9(path);
|
|
20354
20535
|
if (!metadata.isFile()) throw new Error("Session path is not a regular file");
|
|
20355
20536
|
await rm8(path);
|
|
20356
|
-
await rm8(
|
|
20537
|
+
await rm8(join26(this.#sessions, `${sessionId}.events.jsonl`), { force: true }).catch(() => void 0);
|
|
20357
20538
|
} catch (error) {
|
|
20358
20539
|
if (!isCode8(error, "ENOENT")) throw error;
|
|
20359
20540
|
}
|
|
@@ -20369,14 +20550,14 @@ var SessionStore = class {
|
|
|
20369
20550
|
if (entries.length <= this.#maxSessions) return;
|
|
20370
20551
|
let retained = 0;
|
|
20371
20552
|
for (const entry2 of entries) {
|
|
20372
|
-
const owner2 = await reclaimStaleSessionLock(
|
|
20553
|
+
const owner2 = await reclaimStaleSessionLock(join26(this.#sessions, `.${entry2.sessionId}.lock`));
|
|
20373
20554
|
if (owner2 !== void 0) continue;
|
|
20374
20555
|
if (retained < this.#maxSessions) {
|
|
20375
20556
|
retained += 1;
|
|
20376
20557
|
continue;
|
|
20377
20558
|
}
|
|
20378
20559
|
await rm8(this.#path(entry2.sessionId), { force: true }).catch(() => void 0);
|
|
20379
|
-
await rm8(
|
|
20560
|
+
await rm8(join26(this.#sessions, `${entry2.sessionId}.events.jsonl`), { force: true }).catch(() => void 0);
|
|
20380
20561
|
}
|
|
20381
20562
|
}
|
|
20382
20563
|
async #assertWorkspace(stored) {
|
|
@@ -20424,14 +20605,14 @@ function storedSessionVersion(raw) {
|
|
|
20424
20605
|
}
|
|
20425
20606
|
async function reclaimStaleSessionLock(path) {
|
|
20426
20607
|
try {
|
|
20427
|
-
const token = await
|
|
20608
|
+
const token = await readFile19(path, "utf8");
|
|
20428
20609
|
const metadata = await stat8(path);
|
|
20429
20610
|
const owner2 = parseSessionLockOwner(token);
|
|
20430
20611
|
if (owner2 !== void 0 && isProcessAlive2(owner2)) return owner2;
|
|
20431
20612
|
if (owner2 === void 0 && Date.now() - metadata.mtimeMs <= INVALID_SESSION_LOCK_STALE_MS) {
|
|
20432
20613
|
throw new Error(`Session writer lock ${path} is incomplete or invalid; retry after the owning process exits`);
|
|
20433
20614
|
}
|
|
20434
|
-
if (await
|
|
20615
|
+
if (await readFile19(path, "utf8") !== token) return void 0;
|
|
20435
20616
|
const stale = `${path}.stale-${process.pid}-${randomUUID13()}`;
|
|
20436
20617
|
try {
|
|
20437
20618
|
await rename6(path, stale);
|
|
@@ -20467,7 +20648,7 @@ async function boundedRead(path, maxBytes) {
|
|
|
20467
20648
|
const metadata = await stat8(path);
|
|
20468
20649
|
if (!metadata.isFile()) throw new Error("Session path is not a regular file");
|
|
20469
20650
|
if (metadata.size > maxBytes) throw new Error(`Session exceeds maximum read size of ${maxBytes} bytes`);
|
|
20470
|
-
const value = await
|
|
20651
|
+
const value = await readFile19(path);
|
|
20471
20652
|
if (value.byteLength > maxBytes) throw new Error(`Session exceeds maximum read size of ${maxBytes} bytes`);
|
|
20472
20653
|
return value.toString("utf8");
|
|
20473
20654
|
}
|
|
@@ -20619,7 +20800,7 @@ async function assertNoSymlink3(root, target) {
|
|
|
20619
20800
|
}
|
|
20620
20801
|
for (const path of chain.reverse()) {
|
|
20621
20802
|
try {
|
|
20622
|
-
if ((await
|
|
20803
|
+
if ((await lstat9(path)).isSymbolicLink()) throw new Error(`Session path contains a symbolic link: ${path}`);
|
|
20623
20804
|
} catch (error) {
|
|
20624
20805
|
if (!isCode8(error, "ENOENT")) throw error;
|
|
20625
20806
|
}
|
|
@@ -20659,14 +20840,14 @@ function sanitize2(input, seen = /* @__PURE__ */ new WeakSet()) {
|
|
|
20659
20840
|
|
|
20660
20841
|
// src/session/tree.ts
|
|
20661
20842
|
import { randomUUID as randomUUID15 } from "crypto";
|
|
20662
|
-
import { mkdir as mkdir17, readFile as
|
|
20663
|
-
import { dirname as dirname16, join as
|
|
20843
|
+
import { mkdir as mkdir17, readFile as readFile21, rename as rename8, writeFile as writeFile11 } from "fs/promises";
|
|
20844
|
+
import { dirname as dirname16, join as join28, resolve as resolve22 } from "path";
|
|
20664
20845
|
|
|
20665
20846
|
// src/session/checkpoint.ts
|
|
20666
20847
|
import { createHash as createHash16, randomUUID as randomUUID14 } from "crypto";
|
|
20667
20848
|
import { execFile as execFile2 } from "child_process";
|
|
20668
|
-
import { lstat as
|
|
20669
|
-
import { dirname as dirname15, join as
|
|
20849
|
+
import { lstat as lstat10, mkdir as mkdir16, open as open8, readFile as readFile20, readdir as readdir7, rename as rename7, rm as rm9, writeFile as writeFile10 } from "fs/promises";
|
|
20850
|
+
import { dirname as dirname15, join as join27, relative as relative14, resolve as resolve21, sep as sep14 } from "path";
|
|
20670
20851
|
var EXCLUDED = /* @__PURE__ */ new Set([".git", ".flavor", "node_modules", "dist", "build", "release", ".worktrees"]);
|
|
20671
20852
|
var MAX_FILE_BYTES = 16 * 1024 * 1024;
|
|
20672
20853
|
var MAX_TOTAL_BYTES = 128 * 1024 * 1024;
|
|
@@ -20679,9 +20860,9 @@ var WorkspaceCheckpointStore = class {
|
|
|
20679
20860
|
#maxTotalBytes;
|
|
20680
20861
|
constructor(options) {
|
|
20681
20862
|
this.#workspace = resolve21(options.workspace);
|
|
20682
|
-
this.#root =
|
|
20683
|
-
this.#objects =
|
|
20684
|
-
this.#manifests =
|
|
20863
|
+
this.#root = join27(this.#workspace, ".flavor", "checkpoints");
|
|
20864
|
+
this.#objects = join27(this.#root, "objects");
|
|
20865
|
+
this.#manifests = join27(this.#root, "manifests");
|
|
20685
20866
|
this.#maxFileBytes = options.maxFileBytes ?? MAX_FILE_BYTES;
|
|
20686
20867
|
this.#maxTotalBytes = options.maxTotalBytes ?? MAX_TOTAL_BYTES;
|
|
20687
20868
|
}
|
|
@@ -20693,15 +20874,15 @@ var WorkspaceCheckpointStore = class {
|
|
|
20693
20874
|
await mkdir16(this.#manifests, { recursive: true });
|
|
20694
20875
|
for (const path of paths) {
|
|
20695
20876
|
const absolute = contained(this.#workspace, path);
|
|
20696
|
-
const info = await
|
|
20877
|
+
const info = await lstat10(absolute);
|
|
20697
20878
|
if (info.isSymbolicLink()) throw new Error(`Checkpoint refuses symbolic link: ${path}`);
|
|
20698
20879
|
if (!info.isFile()) continue;
|
|
20699
20880
|
if (info.size > this.#maxFileBytes) throw new Error(`Checkpoint file exceeds size limit: ${path}`);
|
|
20700
20881
|
total += info.size;
|
|
20701
20882
|
if (total > this.#maxTotalBytes) throw new Error("Checkpoint exceeds total size limit");
|
|
20702
|
-
const content = await
|
|
20883
|
+
const content = await readFile20(absolute);
|
|
20703
20884
|
const digest = createHash16("sha256").update(content).digest("hex");
|
|
20704
|
-
const objectPath =
|
|
20885
|
+
const objectPath = join27(this.#objects, digest);
|
|
20705
20886
|
try {
|
|
20706
20887
|
const handle = await open8(objectPath, "wx");
|
|
20707
20888
|
try {
|
|
@@ -20721,12 +20902,12 @@ var WorkspaceCheckpointStore = class {
|
|
|
20721
20902
|
...label === void 0 || label.trim().length === 0 ? {} : { label: label.trim() },
|
|
20722
20903
|
files
|
|
20723
20904
|
};
|
|
20724
|
-
await atomicJson(
|
|
20905
|
+
await atomicJson(join27(this.#manifests, `${checkpoint.id}.json`), checkpoint);
|
|
20725
20906
|
return checkpoint;
|
|
20726
20907
|
}
|
|
20727
20908
|
async load(id) {
|
|
20728
20909
|
if (!/^checkpoint-[A-Za-z0-9-]+$/.test(id)) throw new Error("Invalid checkpoint id");
|
|
20729
|
-
const value = JSON.parse(await
|
|
20910
|
+
const value = JSON.parse(await readFile20(join27(this.#manifests, `${id}.json`), "utf8"));
|
|
20730
20911
|
if (value.version !== 1 || value.id !== id || !Array.isArray(value.files)) throw new Error("Invalid checkpoint manifest");
|
|
20731
20912
|
for (const file of value.files) {
|
|
20732
20913
|
contained(this.#workspace, file.path);
|
|
@@ -20738,7 +20919,7 @@ var WorkspaceCheckpointStore = class {
|
|
|
20738
20919
|
const checkpoint = await this.load(id);
|
|
20739
20920
|
const objects = /* @__PURE__ */ new Map();
|
|
20740
20921
|
for (const file of checkpoint.files) {
|
|
20741
|
-
const object = await
|
|
20922
|
+
const object = await readFile20(join27(this.#objects, file.digest));
|
|
20742
20923
|
if (object.length !== file.size || createHash16("sha256").update(object).digest("hex") !== file.digest) {
|
|
20743
20924
|
throw new Error(`Checkpoint object is corrupt: ${file.digest}`);
|
|
20744
20925
|
}
|
|
@@ -20761,7 +20942,7 @@ var WorkspaceCheckpointStore = class {
|
|
|
20761
20942
|
}
|
|
20762
20943
|
};
|
|
20763
20944
|
async function discoverFiles(workspace) {
|
|
20764
|
-
if (await exists2(
|
|
20945
|
+
if (await exists2(join27(workspace, ".git"))) {
|
|
20765
20946
|
const listed = await gitFiles(workspace);
|
|
20766
20947
|
return listed.filter((path) => !EXCLUDED.has(path.split("/")[0] ?? "")).sort();
|
|
20767
20948
|
}
|
|
@@ -20770,7 +20951,7 @@ async function discoverFiles(workspace) {
|
|
|
20770
20951
|
const entries = await readdir7(directory, { withFileTypes: true });
|
|
20771
20952
|
for (const entry2 of entries) {
|
|
20772
20953
|
if (EXCLUDED.has(entry2.name)) continue;
|
|
20773
|
-
const absolute =
|
|
20954
|
+
const absolute = join27(directory, entry2.name);
|
|
20774
20955
|
const path = normalizeRelative(relative14(workspace, absolute));
|
|
20775
20956
|
if (entry2.isSymbolicLink()) throw new Error(`Checkpoint refuses symbolic link: ${path}`);
|
|
20776
20957
|
if (entry2.isDirectory()) await walk(absolute);
|
|
@@ -20804,7 +20985,7 @@ async function gitFiles(workspace) {
|
|
|
20804
20985
|
}
|
|
20805
20986
|
async function exists2(path) {
|
|
20806
20987
|
try {
|
|
20807
|
-
await
|
|
20988
|
+
await lstat10(path);
|
|
20808
20989
|
return true;
|
|
20809
20990
|
} catch (error) {
|
|
20810
20991
|
if (isCode9(error, "ENOENT")) return false;
|
|
@@ -20845,17 +21026,17 @@ var SessionHistory = class _SessionHistory {
|
|
|
20845
21026
|
#leafId;
|
|
20846
21027
|
#unrevert;
|
|
20847
21028
|
constructor(options, document) {
|
|
20848
|
-
this.#path =
|
|
21029
|
+
this.#path = join28(resolve22(options.workspace), ".flavor", "session-trees", options.sessionId, "tree.json");
|
|
20849
21030
|
this.#checkpoints = new WorkspaceCheckpointStore({ workspace: options.workspace });
|
|
20850
21031
|
this.#restoreContext = options.restoreContext;
|
|
20851
21032
|
this.#nodes = boundedNodes(document.nodes);
|
|
20852
21033
|
this.#leafId = document.leafId !== null && this.#nodes.some((node) => node.id === document.leafId) ? document.leafId : this.#nodes.at(-1)?.id ?? null;
|
|
20853
21034
|
}
|
|
20854
21035
|
static async open(options) {
|
|
20855
|
-
const path =
|
|
21036
|
+
const path = join28(resolve22(options.workspace), ".flavor", "session-trees", options.sessionId, "tree.json");
|
|
20856
21037
|
let document = { version: 1, sessionId: options.sessionId, leafId: null, nodes: [] };
|
|
20857
21038
|
try {
|
|
20858
|
-
const parsed = JSON.parse(await
|
|
21039
|
+
const parsed = JSON.parse(await readFile21(path, "utf8"));
|
|
20859
21040
|
validate(parsed, options.sessionId);
|
|
20860
21041
|
document = parsed;
|
|
20861
21042
|
} catch (error) {
|
|
@@ -20988,7 +21169,7 @@ function isCode10(error, code) {
|
|
|
20988
21169
|
|
|
20989
21170
|
// src/utils/rotation-census.ts
|
|
20990
21171
|
import { mkdirSync as mkdirSync2, readdirSync, unlinkSync as unlinkSync2, writeFileSync } from "fs";
|
|
20991
|
-
import { join as
|
|
21172
|
+
import { join as join29 } from "path";
|
|
20992
21173
|
import { writeHeapSnapshot } from "v8";
|
|
20993
21174
|
var CENSUS_PREFIX = "rotation-census-";
|
|
20994
21175
|
var SNAPSHOT_PREFIX = "heap-";
|
|
@@ -21012,9 +21193,9 @@ function censusHeapBlock(reading) {
|
|
|
21012
21193
|
}
|
|
21013
21194
|
function writeRotationCensus(workspace, census) {
|
|
21014
21195
|
try {
|
|
21015
|
-
const directory =
|
|
21196
|
+
const directory = join29(workspace, ".flavor", "tmp");
|
|
21016
21197
|
mkdirSync2(directory, { recursive: true });
|
|
21017
|
-
const path =
|
|
21198
|
+
const path = join29(directory, `${CENSUS_PREFIX}${Date.now()}.json`);
|
|
21018
21199
|
writeFileSync(path, `${JSON.stringify(census, null, 2)}
|
|
21019
21200
|
`, { encoding: "utf8", mode: 384 });
|
|
21020
21201
|
prune(directory, CENSUS_PREFIX, ".json", KEEP_CENSUS_FILES);
|
|
@@ -21025,9 +21206,9 @@ function writeRotationCensus(workspace, census) {
|
|
|
21025
21206
|
}
|
|
21026
21207
|
function writeRotationHeapSnapshot(workspace) {
|
|
21027
21208
|
try {
|
|
21028
|
-
const directory =
|
|
21209
|
+
const directory = join29(workspace, ".flavor", "tmp");
|
|
21029
21210
|
mkdirSync2(directory, { recursive: true });
|
|
21030
|
-
const path =
|
|
21211
|
+
const path = join29(directory, `${SNAPSHOT_PREFIX}${Date.now()}.heapsnapshot`);
|
|
21031
21212
|
writeHeapSnapshot(path);
|
|
21032
21213
|
prune(directory, SNAPSHOT_PREFIX, ".heapsnapshot", KEEP_SNAPSHOT_FILES);
|
|
21033
21214
|
return path;
|
|
@@ -21039,7 +21220,7 @@ function prune(directory, prefix, suffix, keep) {
|
|
|
21039
21220
|
const stale = readdirSync(directory).filter((name) => name.startsWith(prefix) && name.endsWith(suffix)).sort().slice(0, -keep);
|
|
21040
21221
|
for (const name of stale) {
|
|
21041
21222
|
try {
|
|
21042
|
-
unlinkSync2(
|
|
21223
|
+
unlinkSync2(join29(directory, name));
|
|
21043
21224
|
} catch {
|
|
21044
21225
|
}
|
|
21045
21226
|
}
|
|
@@ -21048,8 +21229,8 @@ function prune(directory, prefix, suffix, keep) {
|
|
|
21048
21229
|
// src/sleep/organizer.ts
|
|
21049
21230
|
import { constants as constants8 } from "fs";
|
|
21050
21231
|
import { randomUUID as randomUUID16 } from "crypto";
|
|
21051
|
-
import { lstat as
|
|
21052
|
-
import { isAbsolute as isAbsolute11, join as
|
|
21232
|
+
import { lstat as lstat11, mkdir as mkdir18, open as open9, readdir as readdir8, realpath as realpath11, rename as rename9, rm as rm10 } from "fs/promises";
|
|
21233
|
+
import { isAbsolute as isAbsolute11, join as join30, relative as relative15, resolve as resolve23, sep as sep15 } from "path";
|
|
21053
21234
|
import { z as z30 } from "zod";
|
|
21054
21235
|
var DateKeySchema = z30.string().regex(/^\d{4}-\d{2}-\d{2}$/);
|
|
21055
21236
|
var ReviewSchema = z30.object({
|
|
@@ -21104,7 +21285,7 @@ var ProjectSleepOrganizer = class {
|
|
|
21104
21285
|
#generate;
|
|
21105
21286
|
constructor(options) {
|
|
21106
21287
|
this.#workspace = resolve23(options.workspace);
|
|
21107
|
-
this.#sleepDirectory =
|
|
21288
|
+
this.#sleepDirectory = join30(this.#workspace, ".flavor", "sleep");
|
|
21108
21289
|
this.#sessions = options.sessions ?? new SessionStore({ workspace: this.#workspace });
|
|
21109
21290
|
this.#generate = options.generate;
|
|
21110
21291
|
}
|
|
@@ -21113,7 +21294,7 @@ var ProjectSleepOrganizer = class {
|
|
|
21113
21294
|
signal.throwIfAborted();
|
|
21114
21295
|
await this.#prepareDirectory();
|
|
21115
21296
|
if (await this.#hasReport(date)) return { status: "exists", date };
|
|
21116
|
-
const lockPath =
|
|
21297
|
+
const lockPath = join30(this.#sleepDirectory, `.${date}.lock`);
|
|
21117
21298
|
let lock;
|
|
21118
21299
|
try {
|
|
21119
21300
|
lock = await open9(lockPath, constants8.O_CREAT | constants8.O_EXCL | constants8.O_WRONLY, 384);
|
|
@@ -21148,7 +21329,7 @@ Previous output was invalid. Repair it and return strict JSON only.`;
|
|
|
21148
21329
|
}
|
|
21149
21330
|
if (review === void 0) throw lastError;
|
|
21150
21331
|
const filename2 = `${date}-${filenameSummary(review.title)}.md`;
|
|
21151
|
-
const path =
|
|
21332
|
+
const path = join30(this.#sleepDirectory, filename2);
|
|
21152
21333
|
const markdown = renderSleepReport(date, review, documents);
|
|
21153
21334
|
await this.#atomicWrite(path, markdown);
|
|
21154
21335
|
return { status: "written", date, path, sessionCount: documents.length };
|
|
@@ -21190,7 +21371,7 @@ Previous output was invalid. Repair it and return strict JSON only.`;
|
|
|
21190
21371
|
});
|
|
21191
21372
|
}
|
|
21192
21373
|
async #prepareDirectory() {
|
|
21193
|
-
const flavorDirectory =
|
|
21374
|
+
const flavorDirectory = join30(this.#workspace, ".flavor");
|
|
21194
21375
|
await assertNotSymlink(flavorDirectory);
|
|
21195
21376
|
await mkdir18(flavorDirectory, { recursive: true, mode: 448 });
|
|
21196
21377
|
await assertNotSymlink(this.#sleepDirectory);
|
|
@@ -21204,7 +21385,7 @@ Previous output was invalid. Repair it and return strict JSON only.`;
|
|
|
21204
21385
|
return (await readdir8(this.#sleepDirectory, { withFileTypes: true })).some((entry2) => entry2.isFile() && entry2.name.startsWith(prefix) && entry2.name.endsWith(".md"));
|
|
21205
21386
|
}
|
|
21206
21387
|
async #atomicWrite(path, content) {
|
|
21207
|
-
const temporary =
|
|
21388
|
+
const temporary = join30(this.#sleepDirectory, `.${randomUUID16()}.tmp`);
|
|
21208
21389
|
let handle;
|
|
21209
21390
|
try {
|
|
21210
21391
|
handle = await open9(temporary, constants8.O_CREAT | constants8.O_EXCL | constants8.O_WRONLY, 384);
|
|
@@ -21520,7 +21701,7 @@ function assertDateKey(value) {
|
|
|
21520
21701
|
}
|
|
21521
21702
|
async function assertNotSymlink(path) {
|
|
21522
21703
|
try {
|
|
21523
|
-
if ((await
|
|
21704
|
+
if ((await lstat11(path)).isSymbolicLink()) throw new Error(`Refusing to use symbolic link: ${path}`);
|
|
21524
21705
|
} catch (error) {
|
|
21525
21706
|
if (!hasCode(error, "ENOENT")) throw error;
|
|
21526
21707
|
}
|
|
@@ -21578,7 +21759,7 @@ function computeSessionStats(documents) {
|
|
|
21578
21759
|
|
|
21579
21760
|
// src/tools/files.ts
|
|
21580
21761
|
import { constants as constants9 } from "fs";
|
|
21581
|
-
import { mkdir as mkdir19, open as open10, readFile as
|
|
21762
|
+
import { mkdir as mkdir19, open as open10, readFile as readFile22, realpath as realpath12, rename as rename10, rm as rm11, stat as stat9 } from "fs/promises";
|
|
21582
21763
|
import { dirname as dirname17, isAbsolute as isAbsolute12, relative as relative16, resolve as resolve24, sep as sep16 } from "path";
|
|
21583
21764
|
import { randomUUID as randomUUID17 } from "crypto";
|
|
21584
21765
|
import { z as z31 } from "zod";
|
|
@@ -21924,7 +22105,7 @@ function createPathGuard(workspace) {
|
|
|
21924
22105
|
if (!within2(physicalRoot, physical)) throw new Error("Path escapes the workspace through a symlink");
|
|
21925
22106
|
break;
|
|
21926
22107
|
} catch (error) {
|
|
21927
|
-
if (!
|
|
22108
|
+
if (!isMissing3(error)) throw error;
|
|
21928
22109
|
const parent = dirname17(ancestor);
|
|
21929
22110
|
if (parent === ancestor) throw error;
|
|
21930
22111
|
ancestor = parent;
|
|
@@ -21942,7 +22123,7 @@ async function atomicWrite(path, content, signal, expectedVersion) {
|
|
|
21942
22123
|
try {
|
|
21943
22124
|
mode = (await stat9(path)).mode & 511;
|
|
21944
22125
|
} catch (error) {
|
|
21945
|
-
if (!
|
|
22126
|
+
if (!isMissing3(error)) throw error;
|
|
21946
22127
|
}
|
|
21947
22128
|
try {
|
|
21948
22129
|
const handle = await open10(temporary, constants9.O_CREAT | constants9.O_EXCL | constants9.O_WRONLY, 384);
|
|
@@ -21963,7 +22144,7 @@ async function atomicWrite(path, content, signal, expectedVersion) {
|
|
|
21963
22144
|
}
|
|
21964
22145
|
}
|
|
21965
22146
|
async function readText(path) {
|
|
21966
|
-
const contents = await
|
|
22147
|
+
const contents = await readFile22(path);
|
|
21967
22148
|
if (isBinary(contents)) throw new Error("Cannot edit binary file as text");
|
|
21968
22149
|
return decodeFileText(contents);
|
|
21969
22150
|
}
|
|
@@ -21991,7 +22172,7 @@ async function fileVersion(path) {
|
|
|
21991
22172
|
try {
|
|
21992
22173
|
return versionOfStat(await stat9(path));
|
|
21993
22174
|
} catch (error) {
|
|
21994
|
-
if (
|
|
22175
|
+
if (isMissing3(error)) return ABSENT_VERSION;
|
|
21995
22176
|
throw error;
|
|
21996
22177
|
}
|
|
21997
22178
|
}
|
|
@@ -22000,10 +22181,10 @@ function versionOfStat(info) {
|
|
|
22000
22181
|
}
|
|
22001
22182
|
async function readOptionalPresentationText(path) {
|
|
22002
22183
|
try {
|
|
22003
|
-
const contents = await
|
|
22184
|
+
const contents = await readFile22(path);
|
|
22004
22185
|
return isBinary(contents) ? { exists: true } : { exists: true, text: contents.toString("utf8") };
|
|
22005
22186
|
} catch (error) {
|
|
22006
|
-
return
|
|
22187
|
+
return isMissing3(error) ? { exists: false } : { exists: true };
|
|
22007
22188
|
}
|
|
22008
22189
|
}
|
|
22009
22190
|
function isBinary(contents) {
|
|
@@ -22112,7 +22293,7 @@ function mergeRange(ranges, start, end) {
|
|
|
22112
22293
|
function abortIfNeeded(signal) {
|
|
22113
22294
|
if (signal.aborted) throw signal.reason;
|
|
22114
22295
|
}
|
|
22115
|
-
function
|
|
22296
|
+
function isMissing3(error) {
|
|
22116
22297
|
return typeof error === "object" && error !== null && "code" in error && error.code === "ENOENT";
|
|
22117
22298
|
}
|
|
22118
22299
|
var PATCH_SEARCH_RADIUS = 100;
|
|
@@ -22197,7 +22378,7 @@ async function requireAbsent(guard, path) {
|
|
|
22197
22378
|
try {
|
|
22198
22379
|
await guard.existing(path);
|
|
22199
22380
|
} catch (error) {
|
|
22200
|
-
if (
|
|
22381
|
+
if (isMissing3(error)) return "";
|
|
22201
22382
|
throw error;
|
|
22202
22383
|
}
|
|
22203
22384
|
throw new Error("Patch creation destination already exists");
|
|
@@ -23033,7 +23214,7 @@ async function readOptionalIgnoreFile(path, maxBytes, signal) {
|
|
|
23033
23214
|
try {
|
|
23034
23215
|
handle = await open11(path, "r");
|
|
23035
23216
|
} catch (error) {
|
|
23036
|
-
if (
|
|
23217
|
+
if (isMissing4(error)) return void 0;
|
|
23037
23218
|
throw error;
|
|
23038
23219
|
}
|
|
23039
23220
|
const buffer = Buffer.allocUnsafe(maxBytes + 1);
|
|
@@ -23051,7 +23232,7 @@ async function readOptionalIgnoreFile(path, maxBytes, signal) {
|
|
|
23051
23232
|
if (offset > maxBytes) throw new Error(`Ignore file byte limit of ${maxBytes} exceeded: ${path}`);
|
|
23052
23233
|
return buffer.subarray(0, offset);
|
|
23053
23234
|
}
|
|
23054
|
-
function
|
|
23235
|
+
function isMissing4(error) {
|
|
23055
23236
|
return typeof error === "object" && error !== null && "code" in error && error.code === "ENOENT";
|
|
23056
23237
|
}
|
|
23057
23238
|
|
|
@@ -23385,7 +23566,7 @@ function charset(contentType) {
|
|
|
23385
23566
|
// src/tools/lsp.ts
|
|
23386
23567
|
import { spawn as spawn6 } from "child_process";
|
|
23387
23568
|
import { existsSync as existsSync5 } from "fs";
|
|
23388
|
-
import { access as access2, constants as constants10, readFile as
|
|
23569
|
+
import { access as access2, constants as constants10, readFile as readFile23 } from "fs/promises";
|
|
23389
23570
|
import { basename as basename7, dirname as dirname18, extname as extname3, isAbsolute as isAbsolute14, relative as relative18, resolve as resolve26, sep as sep18 } from "path";
|
|
23390
23571
|
import { fileURLToPath as fileURLToPath2, pathToFileURL as pathToFileURL3 } from "url";
|
|
23391
23572
|
import { z as z34 } from "zod";
|
|
@@ -23581,7 +23762,7 @@ var LspConnection = class {
|
|
|
23581
23762
|
const url = new URL(uri);
|
|
23582
23763
|
if (url.protocol !== "file:") return;
|
|
23583
23764
|
const filePath = fileURLToPath2(url);
|
|
23584
|
-
const text = await
|
|
23765
|
+
const text = await readFile23(filePath, "utf8");
|
|
23585
23766
|
const current = this.#documents.get(uri);
|
|
23586
23767
|
if (current === void 0) {
|
|
23587
23768
|
this.#documents.set(uri, { text, version: 1 });
|
|
@@ -24315,12 +24496,12 @@ ${explanation}` : "";
|
|
|
24315
24496
|
// src/desktop/astgraph-service.ts
|
|
24316
24497
|
import { stat as stat11 } from "fs/promises";
|
|
24317
24498
|
import { createRequire as nodeCreateRequire } from "module";
|
|
24318
|
-
import { join as
|
|
24499
|
+
import { join as join31, resolve as resolve27 } from "path";
|
|
24319
24500
|
var FIELDS = "id, kind, name, qualified_name, file_path, language, start_line, end_line, signature";
|
|
24320
24501
|
var DesktopAstGraphService = class {
|
|
24321
24502
|
#path;
|
|
24322
24503
|
constructor(workspace) {
|
|
24323
|
-
this.#path =
|
|
24504
|
+
this.#path = join31(resolve27(workspace), ".flavor", "astgraph", "index.db");
|
|
24324
24505
|
}
|
|
24325
24506
|
async status() {
|
|
24326
24507
|
if ((await stat11(this.#path).catch(() => void 0))?.isFile() !== true) {
|
|
@@ -24603,8 +24784,8 @@ function createTodoWriteTool() {
|
|
|
24603
24784
|
|
|
24604
24785
|
// src/tools/managed.ts
|
|
24605
24786
|
import { randomUUID as randomUUID18 } from "crypto";
|
|
24606
|
-
import { link, lstat as
|
|
24607
|
-
import { dirname as dirname19, join as
|
|
24787
|
+
import { link, lstat as lstat12, mkdir as mkdir20, readFile as readFile24, readdir as readdir9, unlink as unlink3, writeFile as writeFile12 } from "fs/promises";
|
|
24788
|
+
import { dirname as dirname19, join as join32, resolve as resolve29 } from "path";
|
|
24608
24789
|
import { z as z39 } from "zod";
|
|
24609
24790
|
var MANAGED_TOOL_VERSION = 1;
|
|
24610
24791
|
var MAX_RECORD_BYTES = 256 * 1024;
|
|
@@ -24656,8 +24837,8 @@ var ManagedToolStore = class {
|
|
|
24656
24837
|
#diagnostics = [];
|
|
24657
24838
|
constructor(options) {
|
|
24658
24839
|
this.#workspace = resolve29(options.workspace);
|
|
24659
|
-
this.#projectRoot =
|
|
24660
|
-
this.#globalRoot =
|
|
24840
|
+
this.#projectRoot = join32(this.#workspace, ".flavor", "tools");
|
|
24841
|
+
this.#globalRoot = join32(resolve29(options.home), ".flavor-code", "tools");
|
|
24661
24842
|
this.#now = options.now ?? (() => (/* @__PURE__ */ new Date()).toISOString());
|
|
24662
24843
|
}
|
|
24663
24844
|
get diagnostics() {
|
|
@@ -24704,7 +24885,7 @@ var ManagedToolStore = class {
|
|
|
24704
24885
|
version: MANAGED_TOOL_VERSION,
|
|
24705
24886
|
createdAt: this.#now()
|
|
24706
24887
|
});
|
|
24707
|
-
const path =
|
|
24888
|
+
const path = join32(this.directory(input.scope), filename(input.name));
|
|
24708
24889
|
const definition = compileDefinition(record3, input.scope, this.#workspace);
|
|
24709
24890
|
await writeExclusive(path, `${JSON.stringify(record3, void 0, 2)}
|
|
24710
24891
|
`);
|
|
@@ -24718,7 +24899,7 @@ var ManagedToolStore = class {
|
|
|
24718
24899
|
if (matches.length === 0) throw new Error(`Managed tool "${input.name}" was not found`);
|
|
24719
24900
|
if (matches.length > 1) throw new Error(`Managed tool "${input.name}" exists in multiple scopes; specify scope`);
|
|
24720
24901
|
const entry2 = matches[0];
|
|
24721
|
-
const info = await
|
|
24902
|
+
const info = await lstat12(entry2.path);
|
|
24722
24903
|
if (!info.isFile() || info.isSymbolicLink()) throw new Error(`Managed tool path is no longer a regular file: ${entry2.path}`);
|
|
24723
24904
|
await unlink3(entry2.path);
|
|
24724
24905
|
this.#entries.delete(scopedKey(entry2.scope, entry2.record.name));
|
|
@@ -24729,7 +24910,7 @@ var ManagedToolStore = class {
|
|
|
24729
24910
|
if (!parsed.success) return [];
|
|
24730
24911
|
const matches = this.#matching(parsed.data.name).filter((entry2) => parsed.data.scope === void 0 || entry2.scope === parsed.data.scope);
|
|
24731
24912
|
if (matches.length > 0) return matches.map((entry2) => entry2.path);
|
|
24732
|
-
return [
|
|
24913
|
+
return [join32(this.directory(parsed.data.scope ?? "project"), filename(parsed.data.name))];
|
|
24733
24914
|
}
|
|
24734
24915
|
async #loadDirectory(scope2) {
|
|
24735
24916
|
const root = this.directory(scope2);
|
|
@@ -24737,17 +24918,17 @@ var ManagedToolStore = class {
|
|
|
24737
24918
|
try {
|
|
24738
24919
|
names = (await readdir9(root, { withFileTypes: true })).filter((entry2) => entry2.name.endsWith(".json")).sort((left, right) => compare2(left.name, right.name)).map((entry2) => entry2.name);
|
|
24739
24920
|
} catch (error) {
|
|
24740
|
-
if (
|
|
24921
|
+
if (isMissing5(error)) return;
|
|
24741
24922
|
this.#diagnostics.push(`${root}: ${message(error)}`);
|
|
24742
24923
|
return;
|
|
24743
24924
|
}
|
|
24744
24925
|
for (const name of names) {
|
|
24745
|
-
const path =
|
|
24926
|
+
const path = join32(root, name);
|
|
24746
24927
|
try {
|
|
24747
|
-
const info = await
|
|
24928
|
+
const info = await lstat12(path);
|
|
24748
24929
|
if (!info.isFile() || info.isSymbolicLink()) throw new Error("Managed tool record must be a regular file");
|
|
24749
24930
|
if (info.size > MAX_RECORD_BYTES) throw new Error(`Managed tool record exceeds ${MAX_RECORD_BYTES} bytes`);
|
|
24750
|
-
const record3 = ManagedToolRecordSchema.parse(JSON.parse(await
|
|
24931
|
+
const record3 = ManagedToolRecordSchema.parse(JSON.parse(await readFile24(path, "utf8")));
|
|
24751
24932
|
if (record3.scope !== scope2) throw new Error(`Managed tool record scope must match its ${scope2} directory`);
|
|
24752
24933
|
const key = scopedKey(scope2, record3.name);
|
|
24753
24934
|
if (this.#entries.has(key)) throw new Error(`Duplicate managed tool name in ${scope2} scope: ${record3.name}`);
|
|
@@ -24940,7 +25121,7 @@ function compare2(left, right) {
|
|
|
24940
25121
|
function compareScope(left, right) {
|
|
24941
25122
|
return left === right ? 0 : left === "global" ? -1 : 1;
|
|
24942
25123
|
}
|
|
24943
|
-
function
|
|
25124
|
+
function isMissing5(error) {
|
|
24944
25125
|
return typeof error === "object" && error !== null && "code" in error && error.code === "ENOENT";
|
|
24945
25126
|
}
|
|
24946
25127
|
|
|
@@ -25341,6 +25522,7 @@ var MVP_COMMANDS = [
|
|
|
25341
25522
|
"remember",
|
|
25342
25523
|
"forget",
|
|
25343
25524
|
"forget-cold",
|
|
25525
|
+
"global",
|
|
25344
25526
|
"checkpoint",
|
|
25345
25527
|
"tree",
|
|
25346
25528
|
"rewind",
|
|
@@ -25439,6 +25621,13 @@ function parseSlashCommand(input, dynamicCommands = [], skillCommands = [], mana
|
|
|
25439
25621
|
const query = args.join(" ").trim();
|
|
25440
25622
|
return query.length === 0 ? { name: "invalid", command: name, message: "Use /forget <text-or-id>." } : { name, query };
|
|
25441
25623
|
}
|
|
25624
|
+
if (name === "global") {
|
|
25625
|
+
const [action, ...parts] = args;
|
|
25626
|
+
if (action === void 0) return { name, action: "show" };
|
|
25627
|
+
const text = parts.join(" ").trim();
|
|
25628
|
+
if ((action === "remember" || action === "forget") && text.length > 0) return { name, action, text };
|
|
25629
|
+
return { name: "invalid", command: name, message: "Use /global [remember <rule>|forget <rule>]." };
|
|
25630
|
+
}
|
|
25442
25631
|
if (name === "checkpoint") {
|
|
25443
25632
|
const label = args.join(" ").trim();
|
|
25444
25633
|
return label.length === 0 ? { name } : { name, label };
|
|
@@ -25603,6 +25792,7 @@ var HELP = [
|
|
|
25603
25792
|
"/logout clear stored OAuth credentials",
|
|
25604
25793
|
"/init /config /skills /plugins /hooks /tasks /doctor",
|
|
25605
25794
|
"/memory /remember [type] <text> /forget <text-or-id> /forget-cold /finish",
|
|
25795
|
+
"/global /global remember <rule> /global forget <rule>",
|
|
25606
25796
|
"/checkpoint [label] /tree /rewind <node> /unrevert /fork <node>",
|
|
25607
25797
|
"/compact /clear /paste-image /help /exit",
|
|
25608
25798
|
"/loop <goal> run a verified autonomous loop",
|
|
@@ -26073,6 +26263,8 @@ Address this now (for example run the required verification or fix), then finish
|
|
|
26073
26263
|
this.#notice(await required(this.#services.ide, "ide")());
|
|
26074
26264
|
} else if (command2.name === "memory") {
|
|
26075
26265
|
this.#notice(await this.#services.memory());
|
|
26266
|
+
} else if (command2.name === "global") {
|
|
26267
|
+
this.#notice(await required(this.#services.global, "global")(command2));
|
|
26076
26268
|
} else if (command2.name === "remember") {
|
|
26077
26269
|
this.#notice(await this.#services.remember(command2.type, command2.text));
|
|
26078
26270
|
} else if (command2.name === "forget") {
|
|
@@ -26358,9 +26550,9 @@ function requiredPals(service) {
|
|
|
26358
26550
|
|
|
26359
26551
|
// src/doctor.ts
|
|
26360
26552
|
import { constants as constants11 } from "fs";
|
|
26361
|
-
import { access as access3, lstat as
|
|
26553
|
+
import { access as access3, lstat as lstat13, readFile as readFile25, readdir as readdir10, stat as stat12 } from "fs/promises";
|
|
26362
26554
|
import { homedir } from "os";
|
|
26363
|
-
import { isAbsolute as isAbsolute16, join as
|
|
26555
|
+
import { isAbsolute as isAbsolute16, join as join33, relative as relative20, resolve as resolve30, sep as sep20 } from "path";
|
|
26364
26556
|
import { rgPath as bundledRgPath2 } from "@vscode/ripgrep";
|
|
26365
26557
|
|
|
26366
26558
|
// src/utils/semver.ts
|
|
@@ -26407,7 +26599,7 @@ async function runDoctor(options = {}, dependencies2 = {}) {
|
|
|
26407
26599
|
const checks = [];
|
|
26408
26600
|
checks.push(runtimeCheck(nodeVersion, platform, arch));
|
|
26409
26601
|
checks.push(await directoryCheck("workspace", workspace, false));
|
|
26410
|
-
checks.push(await directoryCheck("state directory",
|
|
26602
|
+
checks.push(await directoryCheck("state directory", join33(home, ".flavor-code"), true, home));
|
|
26411
26603
|
let loaded;
|
|
26412
26604
|
try {
|
|
26413
26605
|
loaded = await (dependencies2.loadConfig ?? loadConfig)({
|
|
@@ -26433,8 +26625,8 @@ async function runDoctor(options = {}, dependencies2 = {}) {
|
|
|
26433
26625
|
checks.push({ name: "shell", status: "fail", message: errorMessage3(error) });
|
|
26434
26626
|
}
|
|
26435
26627
|
checks.push(await pluginCheck([
|
|
26436
|
-
|
|
26437
|
-
|
|
26628
|
+
join33(home, ".flavor-code", "plugins"),
|
|
26629
|
+
join33(workspace, ".flavor", "plugins")
|
|
26438
26630
|
]));
|
|
26439
26631
|
try {
|
|
26440
26632
|
const latest = await (dependencies2.fetchLatestVersion ?? fetchLatestVersion)({
|
|
@@ -26549,13 +26741,13 @@ async function pluginCheck(directories) {
|
|
|
26549
26741
|
if (!entry2.isDirectory() || entry2.name.startsWith(".")) continue;
|
|
26550
26742
|
const root = resolve30(directory, entry2.name);
|
|
26551
26743
|
try {
|
|
26552
|
-
const rootInfo = await
|
|
26744
|
+
const rootInfo = await lstat13(root);
|
|
26553
26745
|
if (rootInfo.isSymbolicLink()) throw new Error("plugin directory must not be a symbolic link");
|
|
26554
|
-
const manifest = PluginManifestSchema.parse(JSON.parse(await
|
|
26746
|
+
const manifest = PluginManifestSchema.parse(JSON.parse(await readFile25(join33(root, "flavor-plugin.json"), "utf8")));
|
|
26555
26747
|
const main = resolve30(root, manifest.main);
|
|
26556
26748
|
const delta = relative20(root, main);
|
|
26557
26749
|
if (delta === ".." || delta.startsWith(`..${sep20}`) || isAbsolute16(delta)) throw new Error("plugin entry escapes its root");
|
|
26558
|
-
const mainInfo = await
|
|
26750
|
+
const mainInfo = await lstat13(main);
|
|
26559
26751
|
if (!mainInfo.isFile() || mainInfo.isSymbolicLink()) throw new Error("plugin entry must be a regular file");
|
|
26560
26752
|
valid += 1;
|
|
26561
26753
|
} catch (error) {
|
|
@@ -26619,7 +26811,7 @@ var LANG_LABELS = /* @__PURE__ */ new Map([
|
|
|
26619
26811
|
|
|
26620
26812
|
// src/utils/crash-guard.ts
|
|
26621
26813
|
import { mkdirSync as mkdirSync3, writeFileSync as writeFileSync2 } from "fs";
|
|
26622
|
-
import { join as
|
|
26814
|
+
import { join as join34 } from "path";
|
|
26623
26815
|
var installed = false;
|
|
26624
26816
|
var crashing = false;
|
|
26625
26817
|
var crashCleanups = /* @__PURE__ */ new Set();
|
|
@@ -26650,7 +26842,7 @@ function installCrashGuard(options = {}) {
|
|
|
26650
26842
|
installed = true;
|
|
26651
26843
|
const crashLogDir = () => {
|
|
26652
26844
|
try {
|
|
26653
|
-
return
|
|
26845
|
+
return join34((options.workspace ?? process.cwd)(), ".flavor");
|
|
26654
26846
|
} catch {
|
|
26655
26847
|
return process.cwd();
|
|
26656
26848
|
}
|
|
@@ -26659,7 +26851,7 @@ function installCrashGuard(options = {}) {
|
|
|
26659
26851
|
try {
|
|
26660
26852
|
const dir = crashLogDir();
|
|
26661
26853
|
mkdirSync3(dir, { recursive: true });
|
|
26662
|
-
const path =
|
|
26854
|
+
const path = join34(dir, `crash-${(/* @__PURE__ */ new Date()).toISOString().replace(/[^0-9]/g, "")}-${process.pid}.log`);
|
|
26663
26855
|
writeFileSync2(path, [
|
|
26664
26856
|
`kind: ${kind}`,
|
|
26665
26857
|
`version: ${process.version}`,
|
|
@@ -27430,8 +27622,8 @@ ${total}`;
|
|
|
27430
27622
|
|
|
27431
27623
|
// src/memory/store.ts
|
|
27432
27624
|
import { createHash as createHash18 } from "crypto";
|
|
27433
|
-
import { readFile as
|
|
27434
|
-
import { resolve as resolve31, join as
|
|
27625
|
+
import { readFile as readFile26, rm as rm12 } from "fs/promises";
|
|
27626
|
+
import { resolve as resolve31, join as join35, relative as relative21, sep as sep21 } from "path";
|
|
27435
27627
|
|
|
27436
27628
|
// src/memory/similarity.ts
|
|
27437
27629
|
function normalizeForSimilarity(value) {
|
|
@@ -27534,9 +27726,9 @@ var MemoryStore = class {
|
|
|
27534
27726
|
if (!Number.isSafeInteger(options.maxEntries) || options.maxEntries < 1) throw new Error("maxEntries must be an integer of at least 1");
|
|
27535
27727
|
if (!Number.isSafeInteger(options.maxEntryChars) || options.maxEntryChars < 1) throw new Error("maxEntryChars must be an integer of at least 1");
|
|
27536
27728
|
this.workspace = resolve31(options.workspace);
|
|
27537
|
-
this.#memoryRoot =
|
|
27538
|
-
this.path =
|
|
27539
|
-
this.#behaviorPath =
|
|
27729
|
+
this.#memoryRoot = join35(this.workspace, ".flavor", "memory");
|
|
27730
|
+
this.path = join35(this.#memoryRoot, "MEMORY.md");
|
|
27731
|
+
this.#behaviorPath = join35(this.#memoryRoot, "behavior.json");
|
|
27540
27732
|
this.#maxEntries = options.maxEntries;
|
|
27541
27733
|
this.#maxEntryChars = options.maxEntryChars;
|
|
27542
27734
|
}
|
|
@@ -27754,7 +27946,7 @@ var MemoryStore = class {
|
|
|
27754
27946
|
});
|
|
27755
27947
|
}
|
|
27756
27948
|
async #readTaskItem(reference) {
|
|
27757
|
-
const document = decodeTask(await
|
|
27949
|
+
const document = decodeTask(await readFile26(this.#resolveContentPath(reference.contentPath), "utf8"));
|
|
27758
27950
|
return document.items.find((item) => item.id === reference.id)?.content ?? reference.summary;
|
|
27759
27951
|
}
|
|
27760
27952
|
#resolveContentPath(contentPath) {
|
|
@@ -28394,8 +28586,8 @@ function createExecutionEnvironment(workspace, config) {
|
|
|
28394
28586
|
}
|
|
28395
28587
|
|
|
28396
28588
|
// src/ide/client.ts
|
|
28397
|
-
import { readFile as
|
|
28398
|
-
import { basename as basename8, join as
|
|
28589
|
+
import { readFile as readFile27, readdir as readdir11, stat as stat13 } from "fs/promises";
|
|
28590
|
+
import { basename as basename8, join as join36, relative as relative23, resolve as resolve33, sep as sep23 } from "path";
|
|
28399
28591
|
var FlavorIdeClient = class {
|
|
28400
28592
|
#workspace;
|
|
28401
28593
|
#home;
|
|
@@ -28491,7 +28683,7 @@ var FlavorIdeClient = class {
|
|
|
28491
28683
|
};
|
|
28492
28684
|
if (await this.#healthy(configured)) return configured;
|
|
28493
28685
|
}
|
|
28494
|
-
const directory =
|
|
28686
|
+
const directory = join36(this.#home, ".flavor-code", "ide");
|
|
28495
28687
|
let entries;
|
|
28496
28688
|
try {
|
|
28497
28689
|
entries = (await readdir11(directory)).filter((name) => name.endsWith(".lock"));
|
|
@@ -28499,9 +28691,9 @@ var FlavorIdeClient = class {
|
|
|
28499
28691
|
return void 0;
|
|
28500
28692
|
}
|
|
28501
28693
|
const candidates = (await Promise.all(entries.map(async (name) => {
|
|
28502
|
-
const path =
|
|
28694
|
+
const path = join36(directory, name);
|
|
28503
28695
|
try {
|
|
28504
|
-
const [contents, info] = await Promise.all([
|
|
28696
|
+
const [contents, info] = await Promise.all([readFile27(path, "utf8"), stat13(path)]);
|
|
28505
28697
|
const lock = parseLockfile(contents);
|
|
28506
28698
|
return lock === void 0 ? void 0 : { lock, modified: info.mtimeMs };
|
|
28507
28699
|
} catch {
|
|
@@ -29041,29 +29233,29 @@ function palSocketAddress(options) {
|
|
|
29041
29233
|
import { createHash as createHash21 } from "crypto";
|
|
29042
29234
|
import { Socket as Socket2 } from "net";
|
|
29043
29235
|
import { tmpdir as tmpdir3 } from "os";
|
|
29044
|
-
import { join as
|
|
29236
|
+
import { join as join38 } from "path";
|
|
29045
29237
|
import { spawn as spawn8 } from "child_process";
|
|
29046
29238
|
|
|
29047
29239
|
// src/pals/broker.ts
|
|
29048
29240
|
import { createHash as createHash20, randomUUID as randomUUID23, timingSafeEqual } from "crypto";
|
|
29049
|
-
import { chmod as chmod3, lstat as
|
|
29241
|
+
import { chmod as chmod3, lstat as lstat14, mkdir as mkdir23, unlink as unlink6 } from "fs/promises";
|
|
29050
29242
|
import { createServer as createServer4, Socket } from "net";
|
|
29051
29243
|
import { dirname as dirname21 } from "path";
|
|
29052
29244
|
|
|
29053
29245
|
// src/pals/auth.ts
|
|
29054
29246
|
import { randomBytes as randomBytes3, randomUUID as randomUUID21 } from "crypto";
|
|
29055
|
-
import { chmod as chmod2, link as link2, mkdir as mkdir21, open as open12, readFile as
|
|
29247
|
+
import { chmod as chmod2, link as link2, mkdir as mkdir21, open as open12, readFile as readFile28, unlink as unlink4 } from "fs/promises";
|
|
29056
29248
|
import { homedir as homedir2 } from "os";
|
|
29057
|
-
import { join as
|
|
29249
|
+
import { join as join37 } from "path";
|
|
29058
29250
|
var PAL_AUTH_TOKEN_BYTES = 32;
|
|
29059
29251
|
var TOKEN_PATTERN = /^[a-f0-9]{64}$/;
|
|
29060
29252
|
async function loadOrCreatePalAuthToken(options = {}) {
|
|
29061
|
-
const directory =
|
|
29062
|
-
const tokenPath =
|
|
29253
|
+
const directory = join37(options.home ?? homedir2(), ".flavor-code", "pals");
|
|
29254
|
+
const tokenPath = join37(directory, "auth-token");
|
|
29063
29255
|
await mkdir21(directory, { recursive: true, mode: 448 });
|
|
29064
29256
|
await chmod2(directory, 448);
|
|
29065
29257
|
const token = (options.random ?? randomBytes3)(PAL_AUTH_TOKEN_BYTES).toString("hex");
|
|
29066
|
-
const temporaryPath =
|
|
29258
|
+
const temporaryPath = join37(directory, `.auth-token-${process.pid}-${randomUUID21()}.tmp`);
|
|
29067
29259
|
let handle;
|
|
29068
29260
|
try {
|
|
29069
29261
|
handle = await open12(temporaryPath, "wx", 384);
|
|
@@ -29086,7 +29278,7 @@ async function loadOrCreatePalAuthToken(options = {}) {
|
|
|
29086
29278
|
if (error.code !== "ENOENT") throw error;
|
|
29087
29279
|
});
|
|
29088
29280
|
}
|
|
29089
|
-
const stored = (await
|
|
29281
|
+
const stored = (await readFile28(tokenPath, "utf8")).trim();
|
|
29090
29282
|
if (!TOKEN_PATTERN.test(stored)) throw new Error("Pals authentication token store is invalid");
|
|
29091
29283
|
await chmod2(tokenPath, 384);
|
|
29092
29284
|
return stored;
|
|
@@ -29098,7 +29290,7 @@ function validatePalAuthToken(token) {
|
|
|
29098
29290
|
|
|
29099
29291
|
// src/pals/lifecycle.ts
|
|
29100
29292
|
import { randomUUID as randomUUID22 } from "crypto";
|
|
29101
|
-
import { mkdir as mkdir22, open as open13, readFile as
|
|
29293
|
+
import { mkdir as mkdir22, open as open13, readFile as readFile29, rename as rename11, stat as stat14, unlink as unlink5 } from "fs/promises";
|
|
29102
29294
|
import { dirname as dirname20 } from "path";
|
|
29103
29295
|
function defaultProcessAlive(pid) {
|
|
29104
29296
|
if (!Number.isSafeInteger(pid) || pid <= 0) return true;
|
|
@@ -29130,7 +29322,7 @@ async function acquirePalFileLock(options) {
|
|
|
29130
29322
|
handle = await open13(options.path, "wx", 384);
|
|
29131
29323
|
} catch (error) {
|
|
29132
29324
|
if (error.code !== "EEXIST") throw error;
|
|
29133
|
-
const [contents, info] = await Promise.all([
|
|
29325
|
+
const [contents, info] = await Promise.all([readFile29(options.path, "utf8").catch(() => ""), stat14(options.path).catch(() => void 0)]);
|
|
29134
29326
|
if (await options.endpointLive()) return void 0;
|
|
29135
29327
|
const existing = parseMetadata(contents);
|
|
29136
29328
|
if (existing !== void 0 && (options.processAlive ?? defaultProcessAlive)(existing.pid)) return void 0;
|
|
@@ -29160,7 +29352,7 @@ async function acquirePalFileLock(options) {
|
|
|
29160
29352
|
await handle.close();
|
|
29161
29353
|
}
|
|
29162
29354
|
const release = async () => {
|
|
29163
|
-
const current = parseMetadata(await
|
|
29355
|
+
const current = parseMetadata(await readFile29(options.path, "utf8").catch(() => ""));
|
|
29164
29356
|
if (current?.nonce !== nonce) return;
|
|
29165
29357
|
await unlink5(options.path).catch((error) => {
|
|
29166
29358
|
if (error.code !== "ENOENT") throw error;
|
|
@@ -29193,7 +29385,7 @@ function canConnect(address) {
|
|
|
29193
29385
|
}
|
|
29194
29386
|
function startupLockPath(address) {
|
|
29195
29387
|
const digest = createHash21("sha256").update(address, "utf8").digest("hex").slice(0, 24);
|
|
29196
|
-
return
|
|
29388
|
+
return join38(tmpdir3(), `flavor-code-pals-${digest}.startup.lock`);
|
|
29197
29389
|
}
|
|
29198
29390
|
async function spawnProductionBroker(address) {
|
|
29199
29391
|
const entry2 = process.argv[1];
|
|
@@ -29900,6 +30092,8 @@ async function createProductionRuntime(options) {
|
|
|
29900
30092
|
const gitRepository = detectGitRepository(workspace);
|
|
29901
30093
|
const loaded = await loadConfig({ cwd: workspace, home, environment });
|
|
29902
30094
|
const config = loaded.config;
|
|
30095
|
+
const globalInstructions = config.globalInstructions.enabled ? new GlobalInstructions(home) : void 0;
|
|
30096
|
+
await globalInstructions?.refresh();
|
|
29903
30097
|
const mcpReady = connectMcpServers({
|
|
29904
30098
|
servers: config.mcpServers,
|
|
29905
30099
|
workspace,
|
|
@@ -30016,6 +30210,7 @@ async function createProductionRuntime(options) {
|
|
|
30016
30210
|
protocolVersion: 2,
|
|
30017
30211
|
workspace,
|
|
30018
30212
|
...hookSessionId === void 0 ? {} : { sessionId: hookSessionId },
|
|
30213
|
+
...hookSessionId === void 0 || options.islandSessionTitle === void 0 ? {} : { sessionTitle: options.islandSessionTitle(hookSessionId) },
|
|
30019
30214
|
...islandControlMetadata === void 0 ? {} : {
|
|
30020
30215
|
islandControlEndpoint: islandControlMetadata.endpoint,
|
|
30021
30216
|
islandControlToken: islandControlMetadata.token,
|
|
@@ -30034,6 +30229,18 @@ async function createProductionRuntime(options) {
|
|
|
30034
30229
|
);
|
|
30035
30230
|
const registry = new ModelRegistry();
|
|
30036
30231
|
const diagnostics = [];
|
|
30232
|
+
let lastGlobalInstructionError;
|
|
30233
|
+
const refreshGlobalInstructions = async () => {
|
|
30234
|
+
if (globalInstructions === void 0) return;
|
|
30235
|
+
try {
|
|
30236
|
+
await globalInstructions.refresh();
|
|
30237
|
+
lastGlobalInstructionError = void 0;
|
|
30238
|
+
} catch (error) {
|
|
30239
|
+
const diagnostic = `Global instructions could not be refreshed: ${message(error)}`;
|
|
30240
|
+
if (diagnostic !== lastGlobalInstructionError) diagnostics.push(diagnostic);
|
|
30241
|
+
lastGlobalInstructionError = diagnostic;
|
|
30242
|
+
}
|
|
30243
|
+
};
|
|
30037
30244
|
const permissionPolicy = await loadPermissionPolicy({
|
|
30038
30245
|
workspace,
|
|
30039
30246
|
home,
|
|
@@ -30137,8 +30344,8 @@ async function createProductionRuntime(options) {
|
|
|
30137
30344
|
const registeredProviders = registration.registered;
|
|
30138
30345
|
let effectiveLlm = registration.effectiveLlm;
|
|
30139
30346
|
const pluginHost = new PluginHost({
|
|
30140
|
-
globalPluginDirs: [
|
|
30141
|
-
projectPluginDirs: [
|
|
30347
|
+
globalPluginDirs: [join39(home, ".flavor-code", "plugins")],
|
|
30348
|
+
projectPluginDirs: [join39(workspace, ".flavor", "plugins")],
|
|
30142
30349
|
config,
|
|
30143
30350
|
// Existing project plugins (including astgraph and superharness) use Node.js
|
|
30144
30351
|
// built-ins. The Worker/vm sandbox deliberately blocks those imports, so it
|
|
@@ -30247,7 +30454,7 @@ async function createProductionRuntime(options) {
|
|
|
30247
30454
|
const address = palSocketAddress({
|
|
30248
30455
|
platform: process.platform,
|
|
30249
30456
|
userScope: home,
|
|
30250
|
-
runtimeDir:
|
|
30457
|
+
runtimeDir: join39(tmpdir4(), `flavor-code-pals-${createHash22("sha256").update(home).digest("hex").slice(0, 16)}`)
|
|
30251
30458
|
});
|
|
30252
30459
|
const baseCollaborationClient = collaboration.client ?? collaboration.createClient?.({
|
|
30253
30460
|
instanceId: collaboration.instanceId,
|
|
@@ -30358,8 +30565,8 @@ async function createProductionRuntime(options) {
|
|
|
30358
30565
|
diagnostics.push(`MCP servers could not start: ${message(error)}`);
|
|
30359
30566
|
});
|
|
30360
30567
|
const skills = new SkillRegistry({
|
|
30361
|
-
globalRoots: [
|
|
30362
|
-
projectRoots: [
|
|
30568
|
+
globalRoots: [join39(home, ".flavor-code", "skills")],
|
|
30569
|
+
projectRoots: [join39(workspace, ".flavor", "skills"), ...pluginSkillRoots],
|
|
30363
30570
|
authorizeResource: async () => true,
|
|
30364
30571
|
disabledNames: config.skills.disabled
|
|
30365
30572
|
});
|
|
@@ -30367,7 +30574,7 @@ async function createProductionRuntime(options) {
|
|
|
30367
30574
|
skillsReady.catch(() => void 0);
|
|
30368
30575
|
tools.push(createSkillTool(skills), createSkillResourceTool(skills));
|
|
30369
30576
|
if (options.extraTools !== void 0) tools.push(...options.extraTools);
|
|
30370
|
-
const flavor = await optionalText2(
|
|
30577
|
+
const flavor = await optionalText2(join39(workspace, "FLAVOR.md"));
|
|
30371
30578
|
const instructionBaseline = await workspaceInstructions.baseline();
|
|
30372
30579
|
let memoryContext;
|
|
30373
30580
|
if (memoryStore !== void 0) {
|
|
@@ -30607,6 +30814,7 @@ async function createProductionRuntime(options) {
|
|
|
30607
30814
|
...memoryContext === void 0 ? {} : { memory: memoryContext },
|
|
30608
30815
|
...taskState === void 0 ? {} : { taskState },
|
|
30609
30816
|
userMemory: () => userMemoryContext ?? "",
|
|
30817
|
+
...globalInstructions === void 0 ? {} : { globalInstructions: () => globalInstructions.content ?? "" },
|
|
30610
30818
|
...compactAtChars === void 0 ? {} : { compactAtChars },
|
|
30611
30819
|
toolOutputChars,
|
|
30612
30820
|
compaction,
|
|
@@ -30644,6 +30852,7 @@ async function createProductionRuntime(options) {
|
|
|
30644
30852
|
softLimitFactor: config.maxIterations.softLimitFactor,
|
|
30645
30853
|
extendIterations: config.maxIterations.extendBy,
|
|
30646
30854
|
hasActiveProgress,
|
|
30855
|
+
...globalInstructions === void 0 ? {} : { beforeModelCall: refreshGlobalInstructions },
|
|
30647
30856
|
afterToolSuccess: async (_tool, paths, _input, _output, context) => workspaceInstructions.discover(paths, context.ownerId ?? context.agent),
|
|
30648
30857
|
toolJournal: {
|
|
30649
30858
|
start: (tool, input, retrySafe) => harnessJournal.startTool(tool, input, retrySafe),
|
|
@@ -30964,7 +31173,7 @@ async function createProductionRuntime(options) {
|
|
|
30964
31173
|
evolveService.toolDefinition(),
|
|
30965
31174
|
...mcpTools
|
|
30966
31175
|
];
|
|
30967
|
-
const loopFlavor = await optionalText2(
|
|
31176
|
+
const loopFlavor = await optionalText2(join39(input.workspace, "FLAVOR.md"));
|
|
30968
31177
|
const loopInstructionBaseline = await loopInstructions.baseline();
|
|
30969
31178
|
const loopEnvironment = createPromptEnvironment({
|
|
30970
31179
|
now: /* @__PURE__ */ new Date(),
|
|
@@ -31004,6 +31213,7 @@ async function createProductionRuntime(options) {
|
|
|
31004
31213
|
...loopInstructionBaseline === "" ? {} : { workspaceInstructions: loopInstructionBaseline },
|
|
31005
31214
|
...memoryContext === void 0 ? {} : { memory: memoryContext },
|
|
31006
31215
|
userMemory: () => userMemoryContext ?? "",
|
|
31216
|
+
...globalInstructions === void 0 ? {} : { globalInstructions: () => globalInstructions.content ?? "" },
|
|
31007
31217
|
...compactAtChars === void 0 ? {} : { compactAtChars },
|
|
31008
31218
|
toolOutputChars,
|
|
31009
31219
|
compaction,
|
|
@@ -31037,6 +31247,7 @@ async function createProductionRuntime(options) {
|
|
|
31037
31247
|
softLimitFactor: config.maxIterations.softLimitFactor,
|
|
31038
31248
|
extendIterations: config.maxIterations.extendBy,
|
|
31039
31249
|
loopMode: true,
|
|
31250
|
+
...globalInstructions === void 0 ? {} : { beforeModelCall: refreshGlobalInstructions },
|
|
31040
31251
|
afterToolSuccess: async (_tool, paths, _input, _output, context) => loopInstructions.discover(paths, context.ownerId ?? context.agent),
|
|
31041
31252
|
toolJournal: {
|
|
31042
31253
|
start: (tool, input2, retrySafe) => harnessJournal.startTool(tool, input2, retrySafe),
|
|
@@ -31422,7 +31633,7 @@ async function createProductionRuntime(options) {
|
|
|
31422
31633
|
const root = resolve36(workspace);
|
|
31423
31634
|
const abs = resolve36(root, path);
|
|
31424
31635
|
if (abs !== root && !abs.startsWith(root + sep25)) throw new Error(`path outside workspace: ${path}`);
|
|
31425
|
-
return
|
|
31636
|
+
return readFile30(abs, "utf8");
|
|
31426
31637
|
},
|
|
31427
31638
|
history: async (path, limit) => fileHistory(workspace, path, limit),
|
|
31428
31639
|
questions,
|
|
@@ -31434,7 +31645,7 @@ async function createProductionRuntime(options) {
|
|
|
31434
31645
|
}, query, focus, signal),
|
|
31435
31646
|
audit: async (toolFilter) => {
|
|
31436
31647
|
try {
|
|
31437
|
-
const raw = await
|
|
31648
|
+
const raw = await readFile30(auditLogger.path, "utf8");
|
|
31438
31649
|
const lines = raw.trim().split("\n").filter((line) => line.length > 0);
|
|
31439
31650
|
const entries = lines.map((line) => {
|
|
31440
31651
|
try {
|
|
@@ -31474,7 +31685,7 @@ ${summary2}`;
|
|
|
31474
31685
|
doctor: async () => formatDoctorReport(await runDoctor({ workspace, home, environment })),
|
|
31475
31686
|
usage: async () => {
|
|
31476
31687
|
try {
|
|
31477
|
-
const raw = await
|
|
31688
|
+
const raw = await readFile30(usageLogPath(), "utf8");
|
|
31478
31689
|
return formatUsageSummary(summarizeUsage(parseUsageEntries(raw)));
|
|
31479
31690
|
} catch (error) {
|
|
31480
31691
|
if (typeof error === "object" && error !== null && "code" in error && error.code === "ENOENT") {
|
|
@@ -31553,6 +31764,28 @@ ${summary2}`;
|
|
|
31553
31764
|
Path: ${memoryStore.path}` : `Path: ${memoryStore.path}
|
|
31554
31765
|
|
|
31555
31766
|
${renderMemoryDocument(entries)}`;
|
|
31767
|
+
},
|
|
31768
|
+
global: async (command2) => {
|
|
31769
|
+
const path = join39(home, ".flavor-code", "GLOBAL.md");
|
|
31770
|
+
if (globalInstructions === void 0) return `Global instructions are disabled.
|
|
31771
|
+
Path: ${path}`;
|
|
31772
|
+
if (command2.action === "show") {
|
|
31773
|
+
await globalInstructions.refresh();
|
|
31774
|
+
return globalInstructions.content === void 0 ? `No global instructions stored.
|
|
31775
|
+
Path: ${path}` : `Global instructions (enabled)
|
|
31776
|
+
Path: ${path}
|
|
31777
|
+
|
|
31778
|
+
${globalInstructions.content}`;
|
|
31779
|
+
}
|
|
31780
|
+
if (command2.action === "remember") {
|
|
31781
|
+
const result2 = await globalInstructions.remember(command2.text);
|
|
31782
|
+
return result2 === "added" ? `Remembered global rule.
|
|
31783
|
+
Path: ${path}` : `Global rule already exists.
|
|
31784
|
+
Path: ${path}`;
|
|
31785
|
+
}
|
|
31786
|
+
const result = await globalInstructions.forget(command2.text);
|
|
31787
|
+
return result === "removed" ? `Forgot global rule.
|
|
31788
|
+
Path: ${path}` : result === "ambiguous" ? "Multiple global rules match. Use the full text of one rule." : "No matching global rule found.";
|
|
31556
31789
|
},
|
|
31557
31790
|
refreshMemory: refreshMemoryState,
|
|
31558
31791
|
remember: async (type, text) => {
|
|
@@ -31627,7 +31860,7 @@ ${renderMemoryDocument(entries)}`;
|
|
|
31627
31860
|
return `Provider "${providerName}" is missing authorizationUrl, tokenUrl, or clientId.`;
|
|
31628
31861
|
}
|
|
31629
31862
|
try {
|
|
31630
|
-
const tokenStore = createFileTokenStore(
|
|
31863
|
+
const tokenStore = createFileTokenStore(join39(home, ".flavor-code", "auth.json"));
|
|
31631
31864
|
const credentialId = oauthCredentialId(oauthConfig.tokenUrl, oauthConfig.clientId);
|
|
31632
31865
|
await migrateLegacyOAuthToken(tokenStore, providerName, credentialId);
|
|
31633
31866
|
const oauth = new OAuthCallbackAuthProvider({
|
|
@@ -31675,7 +31908,7 @@ ${renderMemoryDocument(entries)}`;
|
|
|
31675
31908
|
}
|
|
31676
31909
|
},
|
|
31677
31910
|
async logout() {
|
|
31678
|
-
const tokenStore = createFileTokenStore(
|
|
31911
|
+
const tokenStore = createFileTokenStore(join39(home, ".flavor-code", "auth.json"));
|
|
31679
31912
|
const tokens2 = await tokenStore.load();
|
|
31680
31913
|
const hadCredentials = Object.keys(tokens2).length > 0 || effectiveLlm !== void 0;
|
|
31681
31914
|
if (!hadCredentials) {
|
|
@@ -31986,7 +32219,7 @@ async function workspaceFingerprint(workspace) {
|
|
|
31986
32219
|
hash.update(name);
|
|
31987
32220
|
if (remaining <= 0) continue;
|
|
31988
32221
|
try {
|
|
31989
|
-
const content = await
|
|
32222
|
+
const content = await readFile30(path);
|
|
31990
32223
|
const slice = content.subarray(0, remaining);
|
|
31991
32224
|
hash.update(slice);
|
|
31992
32225
|
remaining -= slice.length;
|
|
@@ -32257,7 +32490,7 @@ async function registerConfiguredAdapters(providers, registry, environment, diag
|
|
|
32257
32490
|
const configured = { ...providers };
|
|
32258
32491
|
if (configured.openai === void 0 && environment.OPENAI_API_KEY) configured.openai = { type: "openai", apiKey: environment.OPENAI_API_KEY };
|
|
32259
32492
|
if (configured.anthropic === void 0 && environment.ANTHROPIC_API_KEY) configured.anthropic = { type: "anthropic", apiKey: environment.ANTHROPIC_API_KEY };
|
|
32260
|
-
const oauthTokenStore = createFileTokenStore(
|
|
32493
|
+
const oauthTokenStore = createFileTokenStore(join39(home, ".flavor-code", "auth.json"));
|
|
32261
32494
|
const registered = [];
|
|
32262
32495
|
let effectiveLlm;
|
|
32263
32496
|
for (const [name, provider] of Object.entries(configured)) {
|
|
@@ -32566,7 +32799,7 @@ async function generateSleepReview(registry, modelId, prompt, signal) {
|
|
|
32566
32799
|
}
|
|
32567
32800
|
async function optionalText2(path) {
|
|
32568
32801
|
try {
|
|
32569
|
-
return await
|
|
32802
|
+
return await readFile30(path, "utf8");
|
|
32570
32803
|
} catch (error) {
|
|
32571
32804
|
if (typeof error === "object" && error !== null && "code" in error && error.code === "ENOENT") return void 0;
|
|
32572
32805
|
throw error;
|
|
@@ -32747,13 +32980,13 @@ function createMemoryTaskId() {
|
|
|
32747
32980
|
|
|
32748
32981
|
// src/desktop/runtime-controller.ts
|
|
32749
32982
|
import { createHash as createHash28 } from "crypto";
|
|
32750
|
-
import { mkdir as mkdir27, readFile as
|
|
32983
|
+
import { mkdir as mkdir27, readFile as readFile38, readdir as readdir13, rm as rm15, stat as stat17, writeFile as writeFile17 } from "fs/promises";
|
|
32751
32984
|
import { homedir as homedir5 } from "os";
|
|
32752
|
-
import { basename as basename10, join as
|
|
32985
|
+
import { basename as basename10, join as join49, resolve as resolve39 } from "path";
|
|
32753
32986
|
|
|
32754
32987
|
// src/d2c/workflow.ts
|
|
32755
32988
|
import { createHash as createHash23 } from "crypto";
|
|
32756
|
-
import { join as
|
|
32989
|
+
import { join as join40 } from "path";
|
|
32757
32990
|
import { z as z44 } from "zod";
|
|
32758
32991
|
|
|
32759
32992
|
// src/d2c/workflow-shared.ts
|
|
@@ -32899,7 +33132,7 @@ function reviewsFor(report, previous) {
|
|
|
32899
33132
|
function implementationDirectory(report) {
|
|
32900
33133
|
const source = report.implementation.source;
|
|
32901
33134
|
if (!/^https?:\/\//i.test(source)) return source;
|
|
32902
|
-
return
|
|
33135
|
+
return join40("src", "d2c-output", report.task);
|
|
32903
33136
|
}
|
|
32904
33137
|
function createWorkflow(report, framework) {
|
|
32905
33138
|
const now = (/* @__PURE__ */ new Date()).toISOString();
|
|
@@ -33023,7 +33256,7 @@ function applyQualityIssueDecision(workflow, issueId2, decision) {
|
|
|
33023
33256
|
};
|
|
33024
33257
|
}
|
|
33025
33258
|
function workflowPath(workspace, task) {
|
|
33026
|
-
return
|
|
33259
|
+
return join40(taskDir(workspace, task), "workflow.json");
|
|
33027
33260
|
}
|
|
33028
33261
|
async function readWorkflow(workspace, task) {
|
|
33029
33262
|
return (await readRecoverableFile(workflowPath(workspace, task), decodeWorkflow))?.value;
|
|
@@ -33276,8 +33509,8 @@ function confirmApiMapping(mapping, operationKey, operations) {
|
|
|
33276
33509
|
}
|
|
33277
33510
|
|
|
33278
33511
|
// src/d2c/modules.ts
|
|
33279
|
-
import { readFile as
|
|
33280
|
-
import { isAbsolute as isAbsolute18, join as
|
|
33512
|
+
import { readFile as readFile31 } from "fs/promises";
|
|
33513
|
+
import { isAbsolute as isAbsolute18, join as join41, normalize as normalize2, posix, sep as sep26, win32 as win322 } from "path";
|
|
33281
33514
|
import { z as z45 } from "zod";
|
|
33282
33515
|
var ModuleSchema = z45.object({
|
|
33283
33516
|
id: z45.string().regex(/^[A-Za-z0-9][A-Za-z0-9._-]{0,127}$/),
|
|
@@ -33296,13 +33529,13 @@ function safeSource(path) {
|
|
|
33296
33529
|
}
|
|
33297
33530
|
function d2cOutputDirectory(workspace, task) {
|
|
33298
33531
|
if (!D2C_TASK_PATTERN.test(task)) throw new Error(`Invalid D2C task: ${task}`);
|
|
33299
|
-
return
|
|
33532
|
+
return join41(workspace, "src", "d2c-output", task);
|
|
33300
33533
|
}
|
|
33301
33534
|
async function readD2cModules(workspace, task) {
|
|
33302
33535
|
const project = d2cOutputDirectory(workspace, task);
|
|
33303
33536
|
let raw;
|
|
33304
33537
|
try {
|
|
33305
|
-
raw = await
|
|
33538
|
+
raw = await readFile31(join41(project, "d2c.modules.json"), "utf8");
|
|
33306
33539
|
} catch {
|
|
33307
33540
|
return [{ id: "page", label: task, sourceFiles: ["src/App.vue", "src/App.jsx"], keywords: [task] }];
|
|
33308
33541
|
}
|
|
@@ -33322,8 +33555,8 @@ async function readD2cModules(workspace, task) {
|
|
|
33322
33555
|
}
|
|
33323
33556
|
|
|
33324
33557
|
// src/d2c/integration.ts
|
|
33325
|
-
import { mkdir as mkdir24, readFile as
|
|
33326
|
-
import { join as
|
|
33558
|
+
import { mkdir as mkdir24, readFile as readFile32, writeFile as writeFile13 } from "fs/promises";
|
|
33559
|
+
import { join as join42 } from "path";
|
|
33327
33560
|
function record2(value) {
|
|
33328
33561
|
return typeof value === "object" && value !== null && !Array.isArray(value) ? value : void 0;
|
|
33329
33562
|
}
|
|
@@ -33459,19 +33692,19 @@ async def reset_acceptance(request: Request):
|
|
|
33459
33692
|
}
|
|
33460
33693
|
async function generateIntegrationArtifacts(projectDir, document, mappings, options = {}) {
|
|
33461
33694
|
if (mappings.some((mapping) => mapping.status === "needs-confirmation")) throw new Error("Confirm every uncertain OpenAPI mapping before generating integration code");
|
|
33462
|
-
const packagePath =
|
|
33695
|
+
const packagePath = join42(projectDir, "package.json");
|
|
33463
33696
|
let pkg;
|
|
33464
33697
|
try {
|
|
33465
|
-
pkg = JSON.parse(await
|
|
33698
|
+
pkg = JSON.parse(await readFile32(packagePath, "utf8"));
|
|
33466
33699
|
} catch {
|
|
33467
33700
|
throw new Error(`D2C integration requires a valid package.json at ${packagePath}`);
|
|
33468
33701
|
}
|
|
33469
33702
|
const scripts = options.pythonServer ? { ...record2(pkg.scripts) ?? {} } : { ...record2(pkg.scripts) ?? {}, mock: "node mock/server.mjs" };
|
|
33470
33703
|
const dependencies2 = options.pythonServer ? { ...record2(pkg.dependencies) ?? {}, axios: "^1.7.9" } : { ...record2(pkg.dependencies) ?? {}, axios: "^1.7.9", express: "^5.2.1" };
|
|
33471
33704
|
const selected = [...new Set(mappings.map((mapping) => mapping.operationKey))].map((key) => document.operations.find((operation) => operation.id === key)).filter((operation) => operation !== void 0);
|
|
33472
|
-
const apiDir =
|
|
33473
|
-
const mockDir =
|
|
33474
|
-
const serverDir =
|
|
33705
|
+
const apiDir = join42(projectDir, "src", "api");
|
|
33706
|
+
const mockDir = join42(projectDir, "mock");
|
|
33707
|
+
const serverDir = join42(projectDir, "server");
|
|
33475
33708
|
await Promise.all([
|
|
33476
33709
|
mkdir24(apiDir, { recursive: true }),
|
|
33477
33710
|
...options.pythonServer ? [] : [mkdir24(mockDir, { recursive: true })],
|
|
@@ -33481,23 +33714,23 @@ async function generateIntegrationArtifacts(projectDir, document, mappings, opti
|
|
|
33481
33714
|
if (options.pythonServer) files.push("server/main.py", "server/requirements.txt", "server/README.md", "server/flavor-runtime.json");
|
|
33482
33715
|
else files.push("mock/server.mjs");
|
|
33483
33716
|
await Promise.all([
|
|
33484
|
-
writeFile13(
|
|
33717
|
+
writeFile13(join42(apiDir, "http.js"), `import axios from "axios";
|
|
33485
33718
|
|
|
33486
33719
|
export const http = axios.create({ baseURL: import.meta.env.VITE_API_BASE_URL || ${JSON.stringify(document.baseUrl ?? "")}, timeout: 15000 });
|
|
33487
33720
|
|
|
33488
33721
|
http.interceptors.response.use((response) => response, (error) => Promise.reject({ name: "ApiError", status: error.response?.status, message: error.response?.data?.message || error.message, cause: error }));
|
|
33489
33722
|
`),
|
|
33490
|
-
writeFile13(
|
|
33491
|
-
writeFile13(
|
|
33723
|
+
writeFile13(join42(apiDir, "d2c-api.js"), apiModule(selected)),
|
|
33724
|
+
writeFile13(join42(apiDir, "d2c-bindings.json"), `${JSON.stringify({ schema: 1, generatedAt: (/* @__PURE__ */ new Date()).toISOString(), mappings }, null, 2)}
|
|
33492
33725
|
`),
|
|
33493
|
-
...options.pythonServer ? [] : [writeFile13(
|
|
33726
|
+
...options.pythonServer ? [] : [writeFile13(join42(mockDir, "server.mjs"), mockModule({ ...document, operations: selected }))],
|
|
33494
33727
|
writeFile13(packagePath, `${JSON.stringify({ ...pkg, scripts, dependencies: dependencies2 }, null, 2)}
|
|
33495
33728
|
`),
|
|
33496
33729
|
...options.pythonServer ? [
|
|
33497
|
-
writeFile13(
|
|
33498
|
-
writeFile13(
|
|
33499
|
-
writeFile13(
|
|
33500
|
-
writeFile13(
|
|
33730
|
+
writeFile13(join42(serverDir, "main.py"), pythonServerModule({ ...document, operations: selected })),
|
|
33731
|
+
writeFile13(join42(serverDir, "requirements.txt"), "fastapi>=0.116,<1\nuvicorn[standard]>=0.35,<1\nsqlalchemy>=2.0,<3\n"),
|
|
33732
|
+
writeFile13(join42(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"),
|
|
33733
|
+
writeFile13(join42(serverDir, "flavor-runtime.json"), `${JSON.stringify({
|
|
33501
33734
|
schema: 1,
|
|
33502
33735
|
kind: "python-fastapi",
|
|
33503
33736
|
cwd: "server",
|
|
@@ -33516,9 +33749,9 @@ http.interceptors.response.use((response) => response, (error) => Promise.reject
|
|
|
33516
33749
|
|
|
33517
33750
|
// src/d2c/mock-runner.ts
|
|
33518
33751
|
import { spawn as spawn9 } from "child_process";
|
|
33519
|
-
import { access as access4, readFile as
|
|
33752
|
+
import { access as access4, readFile as readFile33 } from "fs/promises";
|
|
33520
33753
|
import { createServer as createServer5 } from "net";
|
|
33521
|
-
import { join as
|
|
33754
|
+
import { join as join43 } from "path";
|
|
33522
33755
|
async function exists3(path) {
|
|
33523
33756
|
try {
|
|
33524
33757
|
await access4(path);
|
|
@@ -33626,7 +33859,7 @@ ${output.trim()}` : ""}`);
|
|
|
33626
33859
|
}
|
|
33627
33860
|
}
|
|
33628
33861
|
async function runPythonBackend(projectDir, options) {
|
|
33629
|
-
const serverDir =
|
|
33862
|
+
const serverDir = join43(projectDir, "server");
|
|
33630
33863
|
if (options.installDependencies !== false) await installPython(serverDir, options.signal);
|
|
33631
33864
|
const selectedPort = await port();
|
|
33632
33865
|
const url = `http://127.0.0.1:${selectedPort}`;
|
|
@@ -33672,20 +33905,20 @@ ${managed.output().slice(-4e3)}`);
|
|
|
33672
33905
|
};
|
|
33673
33906
|
}
|
|
33674
33907
|
async function runD2cMockServer(projectDir, options = {}) {
|
|
33675
|
-
const runtimePath =
|
|
33908
|
+
const runtimePath = join43(projectDir, "server", "flavor-runtime.json");
|
|
33676
33909
|
if (await exists3(runtimePath)) {
|
|
33677
33910
|
let runtime;
|
|
33678
33911
|
try {
|
|
33679
|
-
runtime = JSON.parse(await
|
|
33912
|
+
runtime = JSON.parse(await readFile33(runtimePath, "utf8"));
|
|
33680
33913
|
} catch {
|
|
33681
33914
|
throw new Error(`E2E backend runtime manifest is invalid at ${runtimePath}`);
|
|
33682
33915
|
}
|
|
33683
33916
|
if (runtime.schema !== 1 || runtime.kind !== "python-fastapi") throw new Error("Unsupported E2E backend runtime manifest");
|
|
33684
33917
|
return runPythonBackend(projectDir, options);
|
|
33685
33918
|
}
|
|
33686
|
-
const serverPath =
|
|
33919
|
+
const serverPath = join43(projectDir, "mock", "server.mjs");
|
|
33687
33920
|
if (!await exists3(serverPath)) throw new Error(`D2C mock server is not generated at ${serverPath}`);
|
|
33688
|
-
if (options.installDependencies === false && !await exists3(
|
|
33921
|
+
if (options.installDependencies === false && !await exists3(join43(projectDir, "node_modules", "express", "package.json"))) {
|
|
33689
33922
|
throw new Error("D2C mock server dependencies are not installed");
|
|
33690
33923
|
}
|
|
33691
33924
|
if (options.installDependencies !== false) await install(projectDir, options.signal);
|
|
@@ -33727,13 +33960,13 @@ ${managed.output().slice(-4e3)}`);
|
|
|
33727
33960
|
|
|
33728
33961
|
// src/d2c/product.ts
|
|
33729
33962
|
import { createHash as createHash25, randomUUID as randomUUID27 } from "crypto";
|
|
33730
|
-
import { copyFile as copyFile2, mkdir as mkdir25, readFile as
|
|
33731
|
-
import { join as
|
|
33963
|
+
import { copyFile as copyFile2, mkdir as mkdir25, readFile as readFile35, rename as rename13, stat as stat15, unlink as unlink7, writeFile as writeFile15 } from "fs/promises";
|
|
33964
|
+
import { join as join44 } from "path";
|
|
33732
33965
|
import { z as z46 } from "zod";
|
|
33733
33966
|
|
|
33734
33967
|
// src/e2e/prd-governance.ts
|
|
33735
33968
|
import { createHash as createHash24, randomUUID as randomUUID26 } from "crypto";
|
|
33736
|
-
import { readFile as
|
|
33969
|
+
import { readFile as readFile34, rename as rename12, rm as rm13, writeFile as writeFile14 } from "fs/promises";
|
|
33737
33970
|
function hashPrd(markdown) {
|
|
33738
33971
|
return createHash24("sha256").update(markdown, "utf8").digest("hex");
|
|
33739
33972
|
}
|
|
@@ -33810,14 +34043,14 @@ function approvePrd(markdown, now = /* @__PURE__ */ new Date()) {
|
|
|
33810
34043
|
return { hash: hashPrd(markdown), approvedAt: now.toISOString(), criteria };
|
|
33811
34044
|
}
|
|
33812
34045
|
async function assertApprovedPrd(path, approved) {
|
|
33813
|
-
const current = await
|
|
34046
|
+
const current = await readFile34(path, "utf8");
|
|
33814
34047
|
if (hashPrd(current) !== approved.hash) {
|
|
33815
34048
|
throw new Error("PRD_LOCK_VIOLATION: confirmed PRD content changed after approval");
|
|
33816
34049
|
}
|
|
33817
34050
|
return approved;
|
|
33818
34051
|
}
|
|
33819
34052
|
async function updatePrdSectionFile(path, input) {
|
|
33820
|
-
const current = await
|
|
34053
|
+
const current = await readFile34(path, "utf8");
|
|
33821
34054
|
if (hashPrd(current) !== input.expectedHash) {
|
|
33822
34055
|
throw new Error("PRD_EDIT_CONFLICT: the PRD changed; refresh before saving this section");
|
|
33823
34056
|
}
|
|
@@ -33883,13 +34116,13 @@ var PlanSchema2 = z46.object({
|
|
|
33883
34116
|
}).strict();
|
|
33884
34117
|
function d2cProductDirectory(workspace, task) {
|
|
33885
34118
|
if (!D2C_TASK_PATTERN.test(task)) throw new Error("Invalid D2C task name");
|
|
33886
|
-
return
|
|
34119
|
+
return join44(taskDir(workspace, task), "product");
|
|
33887
34120
|
}
|
|
33888
34121
|
function d2cProductPrototypeDirectory(workspace, task) {
|
|
33889
|
-
return
|
|
34122
|
+
return join44(d2cProductDirectory(workspace, task), "prototype");
|
|
33890
34123
|
}
|
|
33891
34124
|
function planPath(workspace, task) {
|
|
33892
|
-
return
|
|
34125
|
+
return join44(taskDir(workspace, task), "product-plan.json");
|
|
33893
34126
|
}
|
|
33894
34127
|
function resolveD2cProductTechnology(requirement, fallbackFramework = "vue") {
|
|
33895
34128
|
const frontendCandidates = [
|
|
@@ -33981,14 +34214,14 @@ async function fileTimestamp(path) {
|
|
|
33981
34214
|
return info?.isFile() ? info.mtime.toISOString() : void 0;
|
|
33982
34215
|
}
|
|
33983
34216
|
async function fileHash(path) {
|
|
33984
|
-
const buffer = await
|
|
34217
|
+
const buffer = await readFile35(path).catch((error) => {
|
|
33985
34218
|
if (error.code === "ENOENT") return void 0;
|
|
33986
34219
|
throw error;
|
|
33987
34220
|
});
|
|
33988
34221
|
return buffer === void 0 ? void 0 : createHash25("sha256").update(buffer).digest("hex");
|
|
33989
34222
|
}
|
|
33990
34223
|
async function validateAndNormalizePrototypeManifest(path) {
|
|
33991
|
-
const raw = await
|
|
34224
|
+
const raw = await readFile35(path, "utf8").catch((error) => {
|
|
33992
34225
|
if (error.code === "ENOENT") return void 0;
|
|
33993
34226
|
throw error;
|
|
33994
34227
|
});
|
|
@@ -34064,7 +34297,7 @@ async function createD2cProductPlan(workspace, input, now = () => /* @__PURE__ *
|
|
|
34064
34297
|
});
|
|
34065
34298
|
}
|
|
34066
34299
|
async function readD2cProductPlan(workspace, task) {
|
|
34067
|
-
const raw = await
|
|
34300
|
+
const raw = await readFile35(planPath(workspace, task), "utf8").catch((error) => {
|
|
34068
34301
|
if (error.code === "ENOENT") return void 0;
|
|
34069
34302
|
throw error;
|
|
34070
34303
|
});
|
|
@@ -34072,16 +34305,16 @@ async function readD2cProductPlan(workspace, task) {
|
|
|
34072
34305
|
}
|
|
34073
34306
|
async function discoverD2cProductArtifacts(workspace, plan) {
|
|
34074
34307
|
const product = d2cProductDirectory(workspace, plan.task);
|
|
34075
|
-
const prdUpdatedAt = await fileTimestamp(
|
|
34076
|
-
const prdHash = await fileHash(
|
|
34077
|
-
const prototypeUpdatedAt = await fileTimestamp(
|
|
34078
|
-
const prototypeHash = await fileHash(
|
|
34079
|
-
const expectedManifest =
|
|
34308
|
+
const prdUpdatedAt = await fileTimestamp(join44(product, "prd.md"));
|
|
34309
|
+
const prdHash = await fileHash(join44(product, "prd.md"));
|
|
34310
|
+
const prototypeUpdatedAt = await fileTimestamp(join44(product, "prototype", "index.html"));
|
|
34311
|
+
const prototypeHash = await fileHash(join44(product, "prototype", "index.html"));
|
|
34312
|
+
const expectedManifest = join44(product, "prototype", "interaction-manifest.json");
|
|
34080
34313
|
let manifestUpdatedAt = await fileTimestamp(expectedManifest);
|
|
34081
34314
|
if (manifestUpdatedAt === void 0) {
|
|
34082
|
-
const misplacedManifest =
|
|
34315
|
+
const misplacedManifest = join44(product, "interaction-manifest.json");
|
|
34083
34316
|
if (await fileTimestamp(misplacedManifest) !== void 0) {
|
|
34084
|
-
await mkdir25(
|
|
34317
|
+
await mkdir25(join44(product, "prototype"), { recursive: true });
|
|
34085
34318
|
await copyFile2(misplacedManifest, expectedManifest);
|
|
34086
34319
|
manifestUpdatedAt = await fileTimestamp(expectedManifest);
|
|
34087
34320
|
}
|
|
@@ -34114,8 +34347,8 @@ async function readD2cProductPlanView(workspace, task) {
|
|
|
34114
34347
|
if (stored === void 0) return void 0;
|
|
34115
34348
|
const plan = await discoverD2cProductArtifacts(workspace, stored);
|
|
34116
34349
|
if (plan.revision !== stored.revision) await writeD2cProductPlan(workspace, plan);
|
|
34117
|
-
const prdMarkdown = plan.prd === void 0 ? void 0 : await
|
|
34118
|
-
const manifestPath =
|
|
34350
|
+
const prdMarkdown = plan.prd === void 0 ? void 0 : await readFile35(join44(taskDir(workspace, task), plan.prd.path), "utf8");
|
|
34351
|
+
const manifestPath = join44(d2cProductDirectory(workspace, task), "prototype", "interaction-manifest.json");
|
|
34119
34352
|
const validationError = plan.phase === "design-generating" && await fileTimestamp(manifestPath) !== void 0 ? await validateAndNormalizePrototypeManifest(manifestPath) : void 0;
|
|
34120
34353
|
return {
|
|
34121
34354
|
plan,
|
|
@@ -34205,9 +34438,9 @@ ${prdMarkdown.slice(0, 2e4)}`
|
|
|
34205
34438
|
|
|
34206
34439
|
// src/e2e/acceptance-baseline.ts
|
|
34207
34440
|
import { createHash as createHash26 } from "crypto";
|
|
34208
|
-
import { readFile as
|
|
34441
|
+
import { readFile as readFile36 } from "fs/promises";
|
|
34209
34442
|
async function fileArtifact(path) {
|
|
34210
|
-
const content = await
|
|
34443
|
+
const content = await readFile36(path);
|
|
34211
34444
|
return { path, hash: createHash26("sha256").update(content).digest("hex"), bytes: content.byteLength };
|
|
34212
34445
|
}
|
|
34213
34446
|
async function verifyAcceptanceArtifact(path, expected, label) {
|
|
@@ -34274,7 +34507,7 @@ function createAcceptanceEvidence(coverage, run, now = /* @__PURE__ */ new Date(
|
|
|
34274
34507
|
|
|
34275
34508
|
// src/e2e/delivery-run.ts
|
|
34276
34509
|
import { createHash as createHash27 } from "crypto";
|
|
34277
|
-
import { join as
|
|
34510
|
+
import { join as join45 } from "path";
|
|
34278
34511
|
import { z as z47 } from "zod";
|
|
34279
34512
|
var DELIVERY_NODE_IDS = ["requirement", "prd", "design", "d2c", "api", "acceptance", "delivery"];
|
|
34280
34513
|
var ArtifactSchema = z47.object({
|
|
@@ -34391,7 +34624,7 @@ function completeDeliveryNode(run, node, outputs, now = /* @__PURE__ */ new Date
|
|
|
34391
34624
|
return { ...run, nodes, updatedAt: timestamp2 };
|
|
34392
34625
|
}
|
|
34393
34626
|
function deliveryRunPath(workspace, task) {
|
|
34394
|
-
return
|
|
34627
|
+
return join45(taskDir(workspace, task), "delivery-run.json");
|
|
34395
34628
|
}
|
|
34396
34629
|
function decodeDeliveryRun(raw) {
|
|
34397
34630
|
return DeliveryRunSchema.parse(JSON.parse(raw));
|
|
@@ -34432,9 +34665,9 @@ async function updateDeliveryRun(workspace, task, expectedRevision, mutation) {
|
|
|
34432
34665
|
}
|
|
34433
34666
|
|
|
34434
34667
|
// src/skills/manager.ts
|
|
34435
|
-
import { lstat as
|
|
34668
|
+
import { lstat as lstat15, mkdir as mkdir26, readFile as readFile37, readdir as readdir12, realpath as realpath13, rm as rm14, writeFile as writeFile16 } from "fs/promises";
|
|
34436
34669
|
import { homedir as homedir4 } from "os";
|
|
34437
|
-
import { isAbsolute as isAbsolute19, join as
|
|
34670
|
+
import { isAbsolute as isAbsolute19, join as join46, relative as relative26, resolve as resolve37, sep as sep27 } from "path";
|
|
34438
34671
|
import { stringify } from "yaml";
|
|
34439
34672
|
var SkillManager = class {
|
|
34440
34673
|
#workspace;
|
|
@@ -34457,7 +34690,7 @@ var SkillManager = class {
|
|
|
34457
34690
|
async create(draft) {
|
|
34458
34691
|
const value = parseDraft(draft);
|
|
34459
34692
|
const projectRoot = this.#projectRoot();
|
|
34460
|
-
const directory =
|
|
34693
|
+
const directory = join46(projectRoot, value.name);
|
|
34461
34694
|
await mkdir26(projectRoot, { recursive: true, mode: 448 });
|
|
34462
34695
|
try {
|
|
34463
34696
|
await mkdir26(directory, { mode: 448 });
|
|
@@ -34466,7 +34699,7 @@ var SkillManager = class {
|
|
|
34466
34699
|
throw error;
|
|
34467
34700
|
}
|
|
34468
34701
|
try {
|
|
34469
|
-
await writeFile16(
|
|
34702
|
+
await writeFile16(join46(directory, "SKILL.md"), renderSkill(value), { encoding: "utf8", flag: "wx", mode: 384 });
|
|
34470
34703
|
await this.#persistEnabled(value.name, true);
|
|
34471
34704
|
return this.get(value.name);
|
|
34472
34705
|
} catch (error) {
|
|
@@ -34479,13 +34712,13 @@ var SkillManager = class {
|
|
|
34479
34712
|
const value = parseDraft(draft);
|
|
34480
34713
|
if (value.name !== validName) throw new Error("Rename is not supported; create a new skill instead");
|
|
34481
34714
|
const directory = this.#projectSkillDirectory(validName);
|
|
34482
|
-
const info = await
|
|
34715
|
+
const info = await lstat15(directory).catch((error) => {
|
|
34483
34716
|
if (isCode11(error, "ENOENT")) throw new Error(`Project skill not found: ${validName}`);
|
|
34484
34717
|
throw error;
|
|
34485
34718
|
});
|
|
34486
34719
|
if (!info.isDirectory() || info.isSymbolicLink()) throw new Error(`Project skill path is not a real directory: ${validName}`);
|
|
34487
|
-
const skillFile =
|
|
34488
|
-
const fileInfo = await
|
|
34720
|
+
const skillFile = join46(directory, "SKILL.md");
|
|
34721
|
+
const fileInfo = await lstat15(skillFile);
|
|
34489
34722
|
if (!fileInfo.isFile() || fileInfo.isSymbolicLink()) throw new Error(`Project skill file is not a real file: ${validName}`);
|
|
34490
34723
|
await writeFile16(skillFile, renderSkill(value), { encoding: "utf8", mode: 384 });
|
|
34491
34724
|
return this.get(validName);
|
|
@@ -34493,7 +34726,7 @@ var SkillManager = class {
|
|
|
34493
34726
|
async delete(name) {
|
|
34494
34727
|
const validName = parseName(name);
|
|
34495
34728
|
const directory = this.#projectSkillDirectory(validName);
|
|
34496
|
-
const info = await
|
|
34729
|
+
const info = await lstat15(directory).catch((error) => {
|
|
34497
34730
|
if (isCode11(error, "ENOENT")) throw new Error(`Project skill not found: ${validName}`);
|
|
34498
34731
|
throw error;
|
|
34499
34732
|
});
|
|
@@ -34516,12 +34749,12 @@ var SkillManager = class {
|
|
|
34516
34749
|
async #registry() {
|
|
34517
34750
|
const loaded = await loadConfig({ cwd: this.#workspace, home: this.#home });
|
|
34518
34751
|
const [globalPluginRoots, projectPluginRoots] = await Promise.all([
|
|
34519
|
-
discoverPluginSkillRoots(
|
|
34520
|
-
discoverPluginSkillRoots(
|
|
34752
|
+
discoverPluginSkillRoots(join46(this.#home, ".flavor-code", "plugins")),
|
|
34753
|
+
discoverPluginSkillRoots(join46(this.#workspace, ".flavor", "plugins"))
|
|
34521
34754
|
]);
|
|
34522
34755
|
return {
|
|
34523
34756
|
registry: new SkillRegistry({
|
|
34524
|
-
globalRoots: [
|
|
34757
|
+
globalRoots: [join46(this.#home, ".flavor-code", "skills"), ...globalPluginRoots],
|
|
34525
34758
|
projectRoots: [this.#projectRoot(), ...projectPluginRoots]
|
|
34526
34759
|
}),
|
|
34527
34760
|
disabled: new Set(loaded.config.skills.disabled)
|
|
@@ -34537,10 +34770,10 @@ var SkillManager = class {
|
|
|
34537
34770
|
};
|
|
34538
34771
|
}
|
|
34539
34772
|
#projectRoot() {
|
|
34540
|
-
return
|
|
34773
|
+
return join46(this.#workspace, ".flavor", "skills");
|
|
34541
34774
|
}
|
|
34542
34775
|
#projectSkillDirectory(name) {
|
|
34543
|
-
return
|
|
34776
|
+
return join46(this.#projectRoot(), name);
|
|
34544
34777
|
}
|
|
34545
34778
|
};
|
|
34546
34779
|
function parseName(value) {
|
|
@@ -34581,12 +34814,12 @@ async function discoverPluginSkillRoots(directory) {
|
|
|
34581
34814
|
const roots = [];
|
|
34582
34815
|
for (const entry2 of entries) {
|
|
34583
34816
|
if (!entry2.isDirectory() || entry2.isSymbolicLink()) continue;
|
|
34584
|
-
const pluginRoot = await realpath13(
|
|
34817
|
+
const pluginRoot = await realpath13(join46(directory, entry2.name));
|
|
34585
34818
|
try {
|
|
34586
|
-
const manifest = PluginManifestSchema.parse(JSON.parse(await
|
|
34819
|
+
const manifest = PluginManifestSchema.parse(JSON.parse(await readFile37(join46(pluginRoot, "flavor-plugin.json"), "utf8")));
|
|
34587
34820
|
for (const contribution of manifest.contributes.skillRoots) {
|
|
34588
34821
|
const candidate = resolve37(pluginRoot, contribution.path);
|
|
34589
|
-
const info = await
|
|
34822
|
+
const info = await lstat15(candidate);
|
|
34590
34823
|
if (!info.isDirectory() || info.isSymbolicLink()) continue;
|
|
34591
34824
|
const physical = await realpath13(candidate);
|
|
34592
34825
|
const relation = relative26(pluginRoot, physical);
|
|
@@ -34658,11 +34891,11 @@ async function createProjectMemoryManager(options) {
|
|
|
34658
34891
|
}
|
|
34659
34892
|
|
|
34660
34893
|
// src/mcp/config-manager.ts
|
|
34661
|
-
import { join as
|
|
34894
|
+
import { join as join47 } from "path";
|
|
34662
34895
|
var ProjectMcpConfigManager = class {
|
|
34663
34896
|
path;
|
|
34664
34897
|
constructor(workspace) {
|
|
34665
|
-
this.path =
|
|
34898
|
+
this.path = join47(workspace, ".flavor", "flavor.json");
|
|
34666
34899
|
}
|
|
34667
34900
|
async list() {
|
|
34668
34901
|
const document = await readRecoverableFile(this.path, (raw) => parseDocument2(this.path, raw));
|
|
@@ -34759,7 +34992,7 @@ function isRecord7(value) {
|
|
|
34759
34992
|
}
|
|
34760
34993
|
|
|
34761
34994
|
// src/desktop/model-config.ts
|
|
34762
|
-
import { join as
|
|
34995
|
+
import { join as join48 } from "path";
|
|
34763
34996
|
var DEFAULT_DESKTOP_MODELS = [
|
|
34764
34997
|
{
|
|
34765
34998
|
id: "anthropic:deepseek-v4-pro",
|
|
@@ -34780,7 +35013,7 @@ var DEFAULT_DESKTOP_MODELS = [
|
|
|
34780
35013
|
];
|
|
34781
35014
|
async function loadDesktopModels(workspace, home) {
|
|
34782
35015
|
try {
|
|
34783
|
-
const tokens2 = await createFileTokenStore(
|
|
35016
|
+
const tokens2 = await createFileTokenStore(join48(home, ".flavor-code", "auth.json")).load();
|
|
34784
35017
|
const pkceModels = [];
|
|
34785
35018
|
const knownPkce = /* @__PURE__ */ new Set();
|
|
34786
35019
|
for (const token of Object.values(tokens2)) {
|
|
@@ -34925,33 +35158,33 @@ function pngDataUrl(png) {
|
|
|
34925
35158
|
}
|
|
34926
35159
|
async function detectD2cFramework(workspace, task) {
|
|
34927
35160
|
try {
|
|
34928
|
-
const pkg = JSON.parse(await
|
|
35161
|
+
const pkg = JSON.parse(await readFile38(join49(d2cOutputDirectory(workspace, task), "package.json"), "utf8"));
|
|
34929
35162
|
return pkg.dependencies?.react === void 0 ? "vue" : "react";
|
|
34930
35163
|
} catch {
|
|
34931
35164
|
return "vue";
|
|
34932
35165
|
}
|
|
34933
35166
|
}
|
|
34934
35167
|
function integrationDirectory(workspace, task) {
|
|
34935
|
-
return
|
|
35168
|
+
return join49(workspace, ".flavor", "d2c", task, "integration");
|
|
34936
35169
|
}
|
|
34937
35170
|
var D2C_BACKEND_SOURCE_EXTENSIONS = /* @__PURE__ */ new Set([".cfg", ".env", ".ini", ".js", ".json", ".lock", ".mjs", ".py", ".toml", ".txt", ".yaml", ".yml"]);
|
|
34938
35171
|
var D2C_BACKEND_IGNORED_DIRECTORIES = /* @__PURE__ */ new Set([".git", ".venv", "__pycache__", "data", "node_modules", "venv"]);
|
|
34939
35172
|
async function d2cBackendSourceFingerprint(project) {
|
|
34940
|
-
const runtimeManifest =
|
|
35173
|
+
const runtimeManifest = join49(project, "server", "flavor-runtime.json");
|
|
34941
35174
|
const realBackend = (await stat17(runtimeManifest).catch(() => void 0))?.isFile() === true;
|
|
34942
|
-
const root = realBackend ?
|
|
35175
|
+
const root = realBackend ? join49(project, "server") : join49(project, "mock");
|
|
34943
35176
|
if ((await stat17(root).catch(() => void 0))?.isDirectory() !== true) return void 0;
|
|
34944
35177
|
const files = [];
|
|
34945
35178
|
const visit = async (directory) => {
|
|
34946
35179
|
const entries = await readdir13(directory, { withFileTypes: true });
|
|
34947
35180
|
for (const entry2 of entries) {
|
|
34948
35181
|
if (entry2.isDirectory()) {
|
|
34949
|
-
if (!D2C_BACKEND_IGNORED_DIRECTORIES.has(entry2.name)) await visit(
|
|
35182
|
+
if (!D2C_BACKEND_IGNORED_DIRECTORIES.has(entry2.name)) await visit(join49(directory, entry2.name));
|
|
34950
35183
|
continue;
|
|
34951
35184
|
}
|
|
34952
35185
|
if (!entry2.isFile()) continue;
|
|
34953
35186
|
const extension2 = entry2.name.includes(".") ? `.${entry2.name.split(".").pop().toLowerCase()}` : "";
|
|
34954
|
-
if (D2C_BACKEND_SOURCE_EXTENSIONS.has(extension2)) files.push(
|
|
35187
|
+
if (D2C_BACKEND_SOURCE_EXTENSIONS.has(extension2)) files.push(join49(directory, entry2.name));
|
|
34955
35188
|
}
|
|
34956
35189
|
};
|
|
34957
35190
|
await visit(root);
|
|
@@ -34959,7 +35192,7 @@ async function d2cBackendSourceFingerprint(project) {
|
|
|
34959
35192
|
for (const path of files.sort()) {
|
|
34960
35193
|
hash.update(path.slice(root.length));
|
|
34961
35194
|
hash.update("\0");
|
|
34962
|
-
hash.update(await
|
|
35195
|
+
hash.update(await readFile38(path));
|
|
34963
35196
|
hash.update("\0");
|
|
34964
35197
|
}
|
|
34965
35198
|
return hash.digest("hex");
|
|
@@ -35507,7 +35740,7 @@ var DesktopRuntimeController = class {
|
|
|
35507
35740
|
if (workflow === void 0 || !reviewProgress(workflow).complete || workflow.stage === "visual-review") {
|
|
35508
35741
|
throw new Error("Complete visual review before importing OpenAPI");
|
|
35509
35742
|
}
|
|
35510
|
-
const moduleManifest =
|
|
35743
|
+
const moduleManifest = join49(d2cOutputDirectory(workspace, task), "d2c.modules.json");
|
|
35511
35744
|
const moduleManifestInfo = await stat17(moduleManifest).catch(() => void 0);
|
|
35512
35745
|
if (moduleManifestInfo === void 0 || !moduleManifestInfo.isFile()) {
|
|
35513
35746
|
throw new Error("D2C implementation must write d2c.modules.json before API mapping; ask the agent to split the project into modules first");
|
|
@@ -35520,8 +35753,8 @@ var DesktopRuntimeController = class {
|
|
|
35520
35753
|
await mkdir27(dir, { recursive: true });
|
|
35521
35754
|
const hash = createHash28("sha256").update(raw).digest("hex");
|
|
35522
35755
|
await Promise.all([
|
|
35523
|
-
writeFile17(
|
|
35524
|
-
writeFile17(
|
|
35756
|
+
writeFile17(join49(dir, "swagger.json"), raw),
|
|
35757
|
+
writeFile17(join49(dir, "openapi.normalized.json"), `${JSON.stringify(document, null, 2)}
|
|
35525
35758
|
`)
|
|
35526
35759
|
]);
|
|
35527
35760
|
const next = {
|
|
@@ -35547,7 +35780,7 @@ var DesktopRuntimeController = class {
|
|
|
35547
35780
|
await this.#assertProductPrdLocked(workspace, task);
|
|
35548
35781
|
const info = await stat17(sourcePath);
|
|
35549
35782
|
if (!info.isFile() || info.size > 8 * 1024 * 1024) throw new Error("OpenAPI JSON must be a file no larger than 8 MiB");
|
|
35550
|
-
return this.#storeD2cOpenApi(workspace, task, basename10(sourcePath), await
|
|
35783
|
+
return this.#storeD2cOpenApi(workspace, task, basename10(sourcePath), await readFile38(sourcePath, "utf8"), false);
|
|
35551
35784
|
}
|
|
35552
35785
|
async getD2cIntegration(task) {
|
|
35553
35786
|
const workspace = this.#workspace;
|
|
@@ -35556,8 +35789,8 @@ var DesktopRuntimeController = class {
|
|
|
35556
35789
|
let workflow = await readWorkflow(workspace, task);
|
|
35557
35790
|
if (workflow?.openapi === void 0 && workflow !== void 0 && reviewProgress(workflow).complete && await readD2cProductPlanView(workspace, task) !== void 0) {
|
|
35558
35791
|
const modules = await readD2cModules(workspace, task);
|
|
35559
|
-
const productOpenApiPath =
|
|
35560
|
-
const authored = await
|
|
35792
|
+
const productOpenApiPath = join49(taskDir(workspace, task), "product", "openapi.json");
|
|
35793
|
+
const authored = await readFile38(productOpenApiPath, "utf8").catch(() => void 0);
|
|
35561
35794
|
const generated = authored === void 0;
|
|
35562
35795
|
const prepared = await this.#storeD2cOpenApi(
|
|
35563
35796
|
workspace,
|
|
@@ -35569,7 +35802,7 @@ var DesktopRuntimeController = class {
|
|
|
35569
35802
|
return prepared;
|
|
35570
35803
|
}
|
|
35571
35804
|
if (workflow?.openapi === void 0 || workflow.mappings === void 0) return void 0;
|
|
35572
|
-
const document = JSON.parse(await
|
|
35805
|
+
const document = JSON.parse(await readFile38(join49(integrationDirectory(workspace, task), "openapi.normalized.json"), "utf8"));
|
|
35573
35806
|
return { workflow, document, mappings: workflow.mappings };
|
|
35574
35807
|
}
|
|
35575
35808
|
async confirmD2cMapping(task, moduleId, operationKey) {
|
|
@@ -35636,7 +35869,7 @@ var DesktopRuntimeController = class {
|
|
|
35636
35869
|
].join("\n");
|
|
35637
35870
|
const delivery = await readDeliveryRun(workspace, task);
|
|
35638
35871
|
if (delivery?.nodes.api.status === "running") {
|
|
35639
|
-
const apiArtifacts = await Promise.all(generated.files.map(async (file) => artifactRef(file, await
|
|
35872
|
+
const apiArtifacts = await Promise.all(generated.files.map(async (file) => artifactRef(file, await readFile38(join49(project, file)))));
|
|
35640
35873
|
const completed = await updateDeliveryRun(
|
|
35641
35874
|
workspace,
|
|
35642
35875
|
task,
|
|
@@ -35663,8 +35896,8 @@ var DesktopRuntimeController = class {
|
|
|
35663
35896
|
try {
|
|
35664
35897
|
const fingerprint2 = await d2cBackendSourceFingerprint(project);
|
|
35665
35898
|
if (fingerprint2 !== void 0) this.#d2cMockSourceFingerprints.set(task, fingerprint2);
|
|
35666
|
-
const environmentPath =
|
|
35667
|
-
const existingEnvironment = await
|
|
35899
|
+
const environmentPath = join49(project, ".env.local");
|
|
35900
|
+
const existingEnvironment = await readFile38(environmentPath, "utf8").catch(() => "");
|
|
35668
35901
|
const environment = `${existingEnvironment.split(/\r?\n/).filter((line) => !/^\s*VITE_API_BASE_URL\s*=/.test(line)).join("\n").trim()}
|
|
35669
35902
|
VITE_API_BASE_URL=${running.url}
|
|
35670
35903
|
`.replace(/^\n/, "");
|
|
@@ -35744,8 +35977,8 @@ VITE_API_BASE_URL=${running.url}
|
|
|
35744
35977
|
const readyPreview = this.#d2cPreviews.get(task);
|
|
35745
35978
|
const readyMock = this.#d2cMocks.get(task);
|
|
35746
35979
|
if (readyPreview === void 0 || readyMock === void 0) throw new Error("D2C preview or backend service is unavailable after synchronization");
|
|
35747
|
-
const manifestPath =
|
|
35748
|
-
const manifest = parseInteractionManifest(await
|
|
35980
|
+
const manifestPath = join49(taskDir(workspace, task), "design", "interaction-manifest.json");
|
|
35981
|
+
const manifest = parseInteractionManifest(await readFile38(manifestPath, "utf8"));
|
|
35749
35982
|
const baseline = await this.#verifyAcceptanceBaseline(workspace, task);
|
|
35750
35983
|
const coverage = baseline === void 0 ? void 0 : verifyRequirementCoverage(baseline.approvedPrd.criteria, manifest);
|
|
35751
35984
|
let executableManifest = manifest;
|
|
@@ -35756,7 +35989,7 @@ VITE_API_BASE_URL=${running.url}
|
|
|
35756
35989
|
await mkdir27(planDir, { recursive: true });
|
|
35757
35990
|
try {
|
|
35758
35991
|
const observations = await this.#observeD2cPages(manifest, readyPreview.url);
|
|
35759
|
-
const prd = await
|
|
35992
|
+
const prd = await readFile38(join49(taskDir(workspace, task), "product", "prd.md"), "utf8").catch(() => void 0);
|
|
35760
35993
|
const apiContext = JSON.stringify({ openapi: workflow.openapi, mappings: workflow.mappings }, null, 2);
|
|
35761
35994
|
const plan = await this.#d2cJudge.planInteractions({ task, seed: manifest, observations, ...prd === void 0 ? {} : { prd }, apiContext });
|
|
35762
35995
|
executableManifest = mergeInteractionManifests(manifest, plan.manifest);
|
|
@@ -35767,9 +36000,9 @@ VITE_API_BASE_URL=${running.url}
|
|
|
35767
36000
|
plannedScenarios: executableManifest.pages.reduce((total, page) => total + page.scenarios.length, 0),
|
|
35768
36001
|
pageAnalyses: plan.pageAnalyses
|
|
35769
36002
|
};
|
|
35770
|
-
await writeFile17(
|
|
36003
|
+
await writeFile17(join49(planDir, "autonomous-interaction-plan.json"), `${JSON.stringify({ ...plan, manifest: executableManifest }, null, 2)}
|
|
35771
36004
|
`, { encoding: "utf8", mode: 384 });
|
|
35772
|
-
await rm15(
|
|
36005
|
+
await rm15(join49(planDir, "autonomous-interaction-diagnostic.json"), { force: true });
|
|
35773
36006
|
} catch (cause) {
|
|
35774
36007
|
const detail = message(cause);
|
|
35775
36008
|
review = {
|
|
@@ -35777,8 +36010,8 @@ VITE_API_BASE_URL=${running.url}
|
|
|
35777
36010
|
plannedScenarios: manifest.pages.reduce((total, page) => total + page.scenarios.length, 0),
|
|
35778
36011
|
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}`
|
|
35779
36012
|
};
|
|
35780
|
-
await rm15(
|
|
35781
|
-
await writeFile17(
|
|
36013
|
+
await rm15(join49(planDir, "autonomous-interaction-plan.json"), { force: true });
|
|
36014
|
+
await writeFile17(join49(planDir, "autonomous-interaction-diagnostic.json"), `${JSON.stringify({
|
|
35782
36015
|
schema: 1,
|
|
35783
36016
|
stage: "planning",
|
|
35784
36017
|
occurredAt: (/* @__PURE__ */ new Date()).toISOString(),
|
|
@@ -35792,13 +36025,13 @@ VITE_API_BASE_URL=${running.url}
|
|
|
35792
36025
|
let result = { ...rawResult, evidenceMode: review.mode };
|
|
35793
36026
|
if (coverage !== void 0) {
|
|
35794
36027
|
const evidence = createAcceptanceEvidence(coverage, result);
|
|
35795
|
-
await writeFile17(
|
|
36028
|
+
await writeFile17(join49(taskDir(workspace, task), "acceptance-evidence.json"), `${JSON.stringify({ ...evidence, baseline }, null, 2)}
|
|
35796
36029
|
`, { encoding: "utf8", mode: 384 });
|
|
35797
36030
|
if (!evidence.passed) result = { ...result, passed: false, failures: Math.max(1, result.failures) };
|
|
35798
36031
|
}
|
|
35799
36032
|
const dir = integrationDirectory(workspace, task);
|
|
35800
36033
|
await mkdir27(dir, { recursive: true });
|
|
35801
|
-
await writeFile17(
|
|
36034
|
+
await writeFile17(join49(dir, "interaction-results.json"), `${JSON.stringify(result, null, 2)}
|
|
35802
36035
|
`);
|
|
35803
36036
|
const next = await updateWorkflow(workspace, task, workflow.revision, (current) => applyInteractionRun(current, result));
|
|
35804
36037
|
return { workflow: next, result, review };
|
|
@@ -35811,7 +36044,7 @@ VITE_API_BASE_URL=${running.url}
|
|
|
35811
36044
|
if (mock === void 0 || !mock.exited() || result.failures === 0) return result;
|
|
35812
36045
|
const output = mock.output().trim();
|
|
35813
36046
|
try {
|
|
35814
|
-
const dir =
|
|
36047
|
+
const dir = join49(d2cOutputDirectory(workspace, task), "mock-server.log");
|
|
35815
36048
|
await writeFile17(dir, `${output}
|
|
35816
36049
|
`);
|
|
35817
36050
|
} catch {
|
|
@@ -35906,7 +36139,7 @@ VITE_API_BASE_URL=${running.url}
|
|
|
35906
36139
|
if (current === void 0) throw new Error("D2C product plan is unavailable");
|
|
35907
36140
|
if (current.plan.approvedPrd !== void 0) throw new Error("The approved PRD is immutable and cannot be edited");
|
|
35908
36141
|
if (current.plan.phase !== "prd-review") throw new Error("PRD sections can only be edited during PRD review");
|
|
35909
|
-
const path =
|
|
36142
|
+
const path = join49(taskDir(workspace, task), "product", "prd.md");
|
|
35910
36143
|
const updated = await updatePrdSectionFile(path, { sectionId, body, expectedHash });
|
|
35911
36144
|
const now = (/* @__PURE__ */ new Date()).toISOString();
|
|
35912
36145
|
await writeD2cProductPlan(workspace, {
|
|
@@ -35926,15 +36159,15 @@ VITE_API_BASE_URL=${running.url}
|
|
|
35926
36159
|
async #assertProductPrdLocked(workspace, task) {
|
|
35927
36160
|
const current = await readD2cProductPlanView(workspace, task);
|
|
35928
36161
|
if (current?.plan.approvedPrd === void 0) return;
|
|
35929
|
-
await assertApprovedPrd(
|
|
36162
|
+
await assertApprovedPrd(join49(taskDir(workspace, task), "product", "prd.md"), current.plan.approvedPrd);
|
|
35930
36163
|
}
|
|
35931
36164
|
async #acceptancePaths(workspace, task) {
|
|
35932
|
-
const product =
|
|
35933
|
-
const openapi =
|
|
36165
|
+
const product = join49(taskDir(workspace, task), "product");
|
|
36166
|
+
const openapi = join49(product, "openapi.json");
|
|
35934
36167
|
return {
|
|
35935
|
-
prd:
|
|
35936
|
-
prototype:
|
|
35937
|
-
interaction:
|
|
36168
|
+
prd: join49(product, "prd.md"),
|
|
36169
|
+
prototype: join49(product, "prototype", "index.html"),
|
|
36170
|
+
interaction: join49(product, "prototype", "interaction-manifest.json"),
|
|
35938
36171
|
...await stat17(openapi).catch(() => void 0) === void 0 ? {} : { openapi }
|
|
35939
36172
|
};
|
|
35940
36173
|
}
|
|
@@ -35942,17 +36175,17 @@ VITE_API_BASE_URL=${running.url}
|
|
|
35942
36175
|
const product = await readD2cProductPlanView(workspace, task);
|
|
35943
36176
|
if (product?.plan.approvedPrd === void 0) return void 0;
|
|
35944
36177
|
await this.#assertProductPrdLocked(workspace, task);
|
|
35945
|
-
const baseline = JSON.parse(await
|
|
36178
|
+
const baseline = JSON.parse(await readFile38(join49(taskDir(workspace, task), "acceptance-baseline.json"), "utf8"));
|
|
35946
36179
|
await verifyAcceptanceBaseline(await this.#acceptancePaths(workspace, task), baseline);
|
|
35947
|
-
const design =
|
|
36180
|
+
const design = join49(taskDir(workspace, task), "design");
|
|
35948
36181
|
await Promise.all([
|
|
35949
|
-
verifyAcceptanceArtifact(
|
|
35950
|
-
verifyAcceptanceArtifact(
|
|
36182
|
+
verifyAcceptanceArtifact(join49(design, "index.html"), baseline.artifacts.prototype, "runtime prototype"),
|
|
36183
|
+
verifyAcceptanceArtifact(join49(design, "interaction-manifest.json"), baseline.artifacts.interaction, "runtime interaction contract")
|
|
35951
36184
|
]);
|
|
35952
36185
|
const workflow = await readWorkflow(workspace, task);
|
|
35953
36186
|
if (workflow?.openapi !== void 0) {
|
|
35954
36187
|
await verifyAcceptanceArtifact(
|
|
35955
|
-
|
|
36188
|
+
join49(taskDir(workspace, task), "integration", "swagger.json"),
|
|
35956
36189
|
{ hash: workflow.openapi.hash },
|
|
35957
36190
|
"runtime OpenAPI contract"
|
|
35958
36191
|
);
|
|
@@ -35973,14 +36206,14 @@ VITE_API_BASE_URL=${running.url}
|
|
|
35973
36206
|
throw new Error("D2C PRD is empty and cannot be accepted");
|
|
35974
36207
|
}
|
|
35975
36208
|
if (stage === "design" && accepted) {
|
|
35976
|
-
const manifest =
|
|
35977
|
-
const normalizedManifest = parseInteractionManifest(await
|
|
36209
|
+
const manifest = join49(d2cProductPrototypeDirectory(workspace, task), "interaction-manifest.json");
|
|
36210
|
+
const normalizedManifest = parseInteractionManifest(await readFile38(manifest, "utf8"));
|
|
35978
36211
|
if (current.plan.approvedPrd === void 0) throw new Error("Approve the PRD before confirming the design");
|
|
35979
36212
|
verifyRequirementCoverage(current.plan.approvedPrd.criteria, normalizedManifest);
|
|
35980
36213
|
await writeFile17(manifest, `${JSON.stringify(normalizedManifest, null, 2)}
|
|
35981
36214
|
`, { encoding: "utf8", mode: 384 });
|
|
35982
36215
|
const baseline = await captureAcceptanceBaseline(await this.#acceptancePaths(workspace, task), current.plan.approvedPrd);
|
|
35983
|
-
await writeFile17(
|
|
36216
|
+
await writeFile17(join49(taskDir(workspace, task), "acceptance-baseline.json"), `${JSON.stringify(baseline, null, 2)}
|
|
35984
36217
|
`, { encoding: "utf8", mode: 384 });
|
|
35985
36218
|
}
|
|
35986
36219
|
const plan = await writeD2cProductPlan(
|
|
@@ -36014,8 +36247,8 @@ VITE_API_BASE_URL=${running.url}
|
|
|
36014
36247
|
const imported = await this.importD2cDesign(task, d2cProductPrototypeDirectory(workspace, task));
|
|
36015
36248
|
const delivery = await readDeliveryRun(workspace, task);
|
|
36016
36249
|
if (delivery !== void 0) {
|
|
36017
|
-
const prototype = await
|
|
36018
|
-
const interaction = await
|
|
36250
|
+
const prototype = await readFile38(join49(d2cProductPrototypeDirectory(workspace, task), "index.html"), "utf8");
|
|
36251
|
+
const interaction = await readFile38(join49(d2cProductPrototypeDirectory(workspace, task), "interaction-manifest.json"), "utf8");
|
|
36019
36252
|
const completed = await updateDeliveryRun(workspace, task, delivery.revision, (run) => completeDeliveryNode(run, "design", [
|
|
36020
36253
|
artifactRef("product/prototype/index.html", prototype),
|
|
36021
36254
|
artifactRef("product/prototype/interaction-manifest.json", interaction)
|
|
@@ -36099,10 +36332,10 @@ VITE_API_BASE_URL=${running.url}
|
|
|
36099
36332
|
const next = await updateWorkflow(workspace, task, workflow.revision, (current) => applyQualityJudgment(current, judgment));
|
|
36100
36333
|
const qualityContent = `${JSON.stringify(judgment, null, 2)}
|
|
36101
36334
|
`;
|
|
36102
|
-
await writeFile17(
|
|
36335
|
+
await writeFile17(join49(taskDir(workspace, task), "quality-judge.json"), qualityContent, { encoding: "utf8", mode: 384 });
|
|
36103
36336
|
const delivery = await readDeliveryRun(workspace, task);
|
|
36104
36337
|
if (next.stage === "completed" && delivery?.nodes.acceptance.status === "running") {
|
|
36105
|
-
const interactionContent = await
|
|
36338
|
+
const interactionContent = await readFile38(join49(integrationDirectory(workspace, task), "interaction-results.json"), "utf8");
|
|
36106
36339
|
const accepted = await updateDeliveryRun(workspace, task, delivery.revision, (run) => completeDeliveryNode(run, "acceptance", [
|
|
36107
36340
|
artifactRef("integration/interaction-results.json", interactionContent),
|
|
36108
36341
|
artifactRef("quality-judge.json", qualityContent)
|
|
@@ -36334,7 +36567,7 @@ async function loginShellEnvironment(environment, platform = process.platform, d
|
|
|
36334
36567
|
}
|
|
36335
36568
|
|
|
36336
36569
|
// src/desktop/git-manager.ts
|
|
36337
|
-
import { readFile as
|
|
36570
|
+
import { readFile as readFile39, rm as rm16 } from "fs/promises";
|
|
36338
36571
|
import { isAbsolute as isAbsolute21, relative as relative28, resolve as resolve40 } from "path";
|
|
36339
36572
|
function failure(action, stderr, stdout) {
|
|
36340
36573
|
return new Error(`${action}\u5931\u8D25\uFF1A${(stderr.trim() || stdout.trim() || "\u672A\u77E5 Git \u9519\u8BEF").split("\n")[0]}`);
|
|
@@ -36366,7 +36599,7 @@ async function desktopGitDiff(workspace, path, staged = false) {
|
|
|
36366
36599
|
if (file === void 0) throw new Error("\u8BE5\u6587\u4EF6\u5DF2\u4E0D\u5728\u53D8\u66F4\u5217\u8868\u4E2D");
|
|
36367
36600
|
if (file.untracked) {
|
|
36368
36601
|
const target = safeWorkspacePath(workspace, path);
|
|
36369
|
-
const value = await
|
|
36602
|
+
const value = await readFile39(target, "utf8");
|
|
36370
36603
|
return value.length > 12e4 ? `${value.slice(0, 12e4)}
|
|
36371
36604
|
\u2026[\u5185\u5BB9\u5DF2\u622A\u65AD]` : value;
|
|
36372
36605
|
}
|
|
@@ -36428,13 +36661,13 @@ async function desktopLastTurnDiff(workspace, sessionId, paths = []) {
|
|
|
36428
36661
|
return path.replaceAll("\\", "/");
|
|
36429
36662
|
}));
|
|
36430
36663
|
try {
|
|
36431
|
-
const tree = JSON.parse(await
|
|
36664
|
+
const tree = JSON.parse(await readFile39(resolve40(workspace, ".flavor", "session-trees", sessionId, "tree.json"), "utf8"));
|
|
36432
36665
|
const leaf = tree.nodes?.find((node) => node.id === tree.leafId);
|
|
36433
36666
|
const parent = leaf?.parentId == null ? void 0 : tree.nodes?.find((node) => node.id === leaf.parentId);
|
|
36434
36667
|
if (leaf === void 0 || parent === void 0) return desktopGitReviewDiff(workspace, { scope: "last-turn", paths });
|
|
36435
36668
|
const load = async (id) => {
|
|
36436
36669
|
if (!/^checkpoint-[A-Za-z0-9-]+$/.test(id)) throw new Error("Invalid checkpoint id");
|
|
36437
|
-
return JSON.parse(await
|
|
36670
|
+
return JSON.parse(await readFile39(resolve40(workspace, ".flavor", "checkpoints", "manifests", `${id}.json`), "utf8"));
|
|
36438
36671
|
};
|
|
36439
36672
|
const [before, after] = await Promise.all([load(parent.checkpointId), load(leaf.checkpointId)]);
|
|
36440
36673
|
const oldFiles = new Map(before.files.map((file) => [file.path, file.digest]));
|
|
@@ -36443,7 +36676,7 @@ async function desktopLastTurnDiff(workspace, sessionId, paths = []) {
|
|
|
36443
36676
|
const object = async (digest) => {
|
|
36444
36677
|
if (digest === void 0) return "";
|
|
36445
36678
|
if (!/^[a-f0-9]{64}$/.test(digest)) throw new Error("Invalid checkpoint object");
|
|
36446
|
-
const value = await
|
|
36679
|
+
const value = await readFile39(resolve40(workspace, ".flavor", "checkpoints", "objects", digest));
|
|
36447
36680
|
return value.includes(0) ? "[binary content]" : value.toString("utf8");
|
|
36448
36681
|
};
|
|
36449
36682
|
const chunks = await Promise.all(changed.map(async (path) => wholeFileDiff(path, await object(oldFiles.get(path)), await object(newFiles.get(path)))));
|
|
@@ -36482,8 +36715,8 @@ function safeGitTarget(value) {
|
|
|
36482
36715
|
|
|
36483
36716
|
// src/desktop/worktree-manager.ts
|
|
36484
36717
|
import { randomUUID as randomUUID28 } from "crypto";
|
|
36485
|
-
import { mkdir as mkdir28, readFile as
|
|
36486
|
-
import { basename as basename11, isAbsolute as isAbsolute22, join as
|
|
36718
|
+
import { mkdir as mkdir28, readFile as readFile40, readdir as readdir14, rm as rm17, stat as stat18, writeFile as writeFile18 } from "fs/promises";
|
|
36719
|
+
import { basename as basename11, isAbsolute as isAbsolute22, join as join50, relative as relative29, resolve as resolve41, sep as sep29 } from "path";
|
|
36487
36720
|
var DesktopWorktreeManager = class {
|
|
36488
36721
|
#repository;
|
|
36489
36722
|
#storage;
|
|
@@ -36529,7 +36762,7 @@ var DesktopWorktreeManager = class {
|
|
|
36529
36762
|
}
|
|
36530
36763
|
const values = await Promise.all(names.filter((name) => name.endsWith(".json")).map(async (name) => {
|
|
36531
36764
|
try {
|
|
36532
|
-
const stored = validateStored(JSON.parse(await
|
|
36765
|
+
const stored = validateStored(JSON.parse(await readFile40(join50(this.#storage, name), "utf8")), this.#repository, this.#storage);
|
|
36533
36766
|
if ((await stat18(stored.path).catch(() => void 0))?.isDirectory() !== true) return void 0;
|
|
36534
36767
|
const status = await git2(stored.path, ["status", "--porcelain"]);
|
|
36535
36768
|
const merged = await git2(this.#repository, ["merge-base", "--is-ancestor", stored.branch, "HEAD"]);
|
|
@@ -36563,7 +36796,7 @@ var DesktopWorktreeManager = class {
|
|
|
36563
36796
|
}
|
|
36564
36797
|
async #load(id) {
|
|
36565
36798
|
const path = this.#metadataPath(id);
|
|
36566
|
-
return validateStored(JSON.parse(await
|
|
36799
|
+
return validateStored(JSON.parse(await readFile40(path, "utf8")), this.#repository, this.#storage);
|
|
36567
36800
|
}
|
|
36568
36801
|
#metadataPath(id) {
|
|
36569
36802
|
if (!/^worktree-[a-z0-9._-]{1,80}$/.test(id)) throw new Error("Invalid worktree id");
|
|
@@ -36591,9 +36824,9 @@ function validateStored(value, repository, storage) {
|
|
|
36591
36824
|
}
|
|
36592
36825
|
|
|
36593
36826
|
// src/desktop/workbench-service.ts
|
|
36594
|
-
import { readFile as
|
|
36827
|
+
import { readFile as readFile41, readdir as readdir15, stat as stat19 } from "fs/promises";
|
|
36595
36828
|
import { homedir as homedir6 } from "os";
|
|
36596
|
-
import { join as
|
|
36829
|
+
import { join as join51, resolve as resolve42 } from "path";
|
|
36597
36830
|
var MAX_TEXT = 128e3;
|
|
36598
36831
|
var MAX_ITEMS = 200;
|
|
36599
36832
|
var DesktopWorkbenchService = class {
|
|
@@ -36626,11 +36859,11 @@ var DesktopWorkbenchService = class {
|
|
|
36626
36859
|
return url.toString().replace(/\/$/, input.trim().endsWith("/") ? "/" : "");
|
|
36627
36860
|
}
|
|
36628
36861
|
async #goals() {
|
|
36629
|
-
const root =
|
|
36862
|
+
const root = join51(this.#workspace, ".flavor", "goals");
|
|
36630
36863
|
const names = await readdir15(root).catch((error) => error.code === "ENOENT" ? [] : Promise.reject(error));
|
|
36631
36864
|
const values = await Promise.all(names.filter((name) => /^goal-[A-Za-z0-9._-]+\.json$/.test(name)).slice(0, MAX_ITEMS).map(async (name) => {
|
|
36632
36865
|
try {
|
|
36633
|
-
return GoalStateSchema.parse(JSON.parse(await boundedRead2(
|
|
36866
|
+
return GoalStateSchema.parse(JSON.parse(await boundedRead2(join51(root, name))));
|
|
36634
36867
|
} catch {
|
|
36635
36868
|
return void 0;
|
|
36636
36869
|
}
|
|
@@ -36640,7 +36873,7 @@ var DesktopWorkbenchService = class {
|
|
|
36640
36873
|
async #instructions() {
|
|
36641
36874
|
const names = ["FLAVOR.md", "AGENTS.md", "CLAUDE.md", "AGENTS.local.md", "CLAUDE.local.md"];
|
|
36642
36875
|
const items = await Promise.all(names.map(async (name) => {
|
|
36643
|
-
const path =
|
|
36876
|
+
const path = join51(this.#workspace, name);
|
|
36644
36877
|
try {
|
|
36645
36878
|
return { name, path, content: redactErrorText(await boundedRead2(path)) };
|
|
36646
36879
|
} catch (error) {
|
|
@@ -36653,9 +36886,9 @@ var DesktopWorkbenchService = class {
|
|
|
36653
36886
|
async #permissionFiles() {
|
|
36654
36887
|
const candidates = [
|
|
36655
36888
|
...process.env.FLAVOR_MANAGED_PERMISSIONS ? [{ tier: "managed", path: resolve42(process.env.FLAVOR_MANAGED_PERMISSIONS), content: "" }] : [],
|
|
36656
|
-
{ tier: "user", path:
|
|
36657
|
-
{ tier: "project", path:
|
|
36658
|
-
{ tier: "local", path:
|
|
36889
|
+
{ tier: "user", path: join51(this.#home, ".flavor-code", "permissions.json"), content: "" },
|
|
36890
|
+
{ tier: "project", path: join51(this.#workspace, ".flavor", "permissions.json"), content: "" },
|
|
36891
|
+
{ tier: "local", path: join51(this.#workspace, ".flavor", "permissions.local.json"), content: "" }
|
|
36659
36892
|
];
|
|
36660
36893
|
const values = await Promise.all(candidates.map(async (item) => {
|
|
36661
36894
|
try {
|
|
@@ -36670,7 +36903,7 @@ var DesktopWorkbenchService = class {
|
|
|
36670
36903
|
async #audit() {
|
|
36671
36904
|
let raw;
|
|
36672
36905
|
try {
|
|
36673
|
-
raw = await boundedRead2(
|
|
36906
|
+
raw = await boundedRead2(join51(this.#workspace, ".flavor", "audit.jsonl"));
|
|
36674
36907
|
} catch (error) {
|
|
36675
36908
|
if (error.code === "ENOENT") return [];
|
|
36676
36909
|
throw error;
|
|
@@ -36685,7 +36918,7 @@ var DesktopWorkbenchService = class {
|
|
|
36685
36918
|
}).reverse();
|
|
36686
36919
|
}
|
|
36687
36920
|
async #context(sessionId) {
|
|
36688
|
-
const usage = await readJsonLines2(
|
|
36921
|
+
const usage = await readJsonLines2(join51(this.#workspace, ".flavor", "usage.jsonl"));
|
|
36689
36922
|
if (sessionId === void 0) return { visibility: [], usage };
|
|
36690
36923
|
try {
|
|
36691
36924
|
const document = await new SessionStore({ workspace: this.#workspace }).load(sessionId);
|
|
@@ -36699,7 +36932,7 @@ async function boundedRead2(path) {
|
|
|
36699
36932
|
const info = await stat19(path);
|
|
36700
36933
|
if (!info.isFile()) throw new Error(`Expected a file: ${path}`);
|
|
36701
36934
|
if (info.size > MAX_TEXT) throw new Error(`Workbench file exceeds ${MAX_TEXT} bytes: ${path}`);
|
|
36702
|
-
return
|
|
36935
|
+
return readFile41(path, "utf8");
|
|
36703
36936
|
}
|
|
36704
36937
|
async function readJsonLines2(path) {
|
|
36705
36938
|
try {
|
|
@@ -36760,7 +36993,7 @@ function ensureBrowserUiWriteAllowed(spaceId) {
|
|
|
36760
36993
|
}
|
|
36761
36994
|
function logStartup(step, detail) {
|
|
36762
36995
|
try {
|
|
36763
|
-
const logPath =
|
|
36996
|
+
const logPath = join52(dirname23(process.execPath), "flavor-code-startup.log");
|
|
36764
36997
|
const ts = (/* @__PURE__ */ new Date()).toISOString();
|
|
36765
36998
|
appendFileSync(logPath, `[${ts}] ${step}${detail ? ` | ${detail}` : ""}
|
|
36766
36999
|
`);
|
|
@@ -36785,7 +37018,7 @@ var d2cEmbedded = createEmbeddedD2cAutomation(() => {
|
|
|
36785
37018
|
};
|
|
36786
37019
|
});
|
|
36787
37020
|
var d2cJudgeClient = createD2cJudgeClient();
|
|
36788
|
-
var judgeStore = () => createD2cJudgeConfigStore(
|
|
37021
|
+
var judgeStore = () => createD2cJudgeConfigStore(join52(app.getPath("userData"), "d2c-judge.json"));
|
|
36789
37022
|
function judgeImage(png) {
|
|
36790
37023
|
const image = nativeImage.createFromBuffer(png);
|
|
36791
37024
|
const size = image.getSize();
|
|
@@ -36811,7 +37044,7 @@ function sessionMetaKey(workspace, sessionId) {
|
|
|
36811
37044
|
}
|
|
36812
37045
|
function worktreeManager(workspace) {
|
|
36813
37046
|
const key = createHash29("sha256").update(resolve43(workspace)).digest("hex").slice(0, 20);
|
|
36814
|
-
return new DesktopWorktreeManager({ repository: workspace, storage:
|
|
37047
|
+
return new DesktopWorktreeManager({ repository: workspace, storage: join52(app.getPath("userData"), "task-worktrees", key) });
|
|
36815
37048
|
}
|
|
36816
37049
|
function activeWorkingDirectory() {
|
|
36817
37050
|
if (activeWorkspace === void 0) throw new Error("\u8BF7\u5148\u6253\u5F00\u9879\u76EE");
|
|
@@ -36942,6 +37175,14 @@ function createDesktopController(workspace, owner2) {
|
|
|
36942
37175
|
},
|
|
36943
37176
|
createRuntime: async (runtimeOptions) => createProductionRuntime({
|
|
36944
37177
|
...runtimeOptions,
|
|
37178
|
+
islandSessionTitle: (sessionId) => {
|
|
37179
|
+
const project = workspace ?? runtimeOptions.workspace;
|
|
37180
|
+
if (project === void 0) return void 0;
|
|
37181
|
+
const custom = workbench.sessionMeta[sessionMetaKey(project, sessionId)]?.title?.trim();
|
|
37182
|
+
if (custom) return custom;
|
|
37183
|
+
const summary2 = owner2?.snapshot.sessions.find((item) => item.sessionId === sessionId) ?? managedProjects.get(project)?.snapshot.sessions.find((item) => item.sessionId === sessionId);
|
|
37184
|
+
return summary2?.preview?.trim();
|
|
37185
|
+
},
|
|
36945
37186
|
islandControl: {
|
|
36946
37187
|
focus: () => {
|
|
36947
37188
|
const target = mainWindow;
|
|
@@ -36956,7 +37197,7 @@ function createDesktopController(workspace, owner2) {
|
|
|
36956
37197
|
alias: `desktop-${basename12(workspace ?? runtimeOptions.workspace ?? "flavor")}`.slice(0, 64),
|
|
36957
37198
|
optional: true,
|
|
36958
37199
|
startBroker: (address) => {
|
|
36959
|
-
const entry2 = app.isPackaged ?
|
|
37200
|
+
const entry2 = app.isPackaged ? join52(process.resourcesPath, "app.asar.unpacked", "dist", "desktop", "pals-broker-entry.js") : join52(moduleDirectory, "pals-broker-entry.js");
|
|
36960
37201
|
return spawnPalBrokerProcess({
|
|
36961
37202
|
address,
|
|
36962
37203
|
executable: process.execPath,
|
|
@@ -36985,11 +37226,11 @@ function createDesktopController(workspace, owner2) {
|
|
|
36985
37226
|
var detachedController = createDesktopController();
|
|
36986
37227
|
var controller = detachedController;
|
|
36987
37228
|
function statePath() {
|
|
36988
|
-
return
|
|
37229
|
+
return join52(app.getPath("userData"), "desktop-state.json");
|
|
36989
37230
|
}
|
|
36990
37231
|
async function loadPersistedProjects() {
|
|
36991
37232
|
try {
|
|
36992
|
-
const raw = await
|
|
37233
|
+
const raw = await readFile42(statePath(), "utf8");
|
|
36993
37234
|
if (raw.length > 1e5) return { projects: [] };
|
|
36994
37235
|
const parsed = JSON.parse(raw);
|
|
36995
37236
|
Object.assign(workbench, {
|
|
@@ -37166,7 +37407,7 @@ async function chooseAndOpenWorkspace() {
|
|
|
37166
37407
|
var cachedAppIconDataUrl;
|
|
37167
37408
|
function appIconDataUrl() {
|
|
37168
37409
|
if (cachedAppIconDataUrl !== void 0) return cachedAppIconDataUrl;
|
|
37169
|
-
const icon = nativeImage.createFromPath(
|
|
37410
|
+
const icon = nativeImage.createFromPath(join52(app.getAppPath(), "assets", "icon.png"));
|
|
37170
37411
|
if (icon.isEmpty()) return void 0;
|
|
37171
37412
|
cachedAppIconDataUrl = icon.toDataURL();
|
|
37172
37413
|
return cachedAppIconDataUrl;
|
|
@@ -37642,7 +37883,7 @@ function applicationMenu() {
|
|
|
37642
37883
|
]);
|
|
37643
37884
|
}
|
|
37644
37885
|
async function createWindow() {
|
|
37645
|
-
const rendererPath =
|
|
37886
|
+
const rendererPath = join52(app.getAppPath(), "dist", "desktop-renderer", "index.html");
|
|
37646
37887
|
mainWindow = new BrowserWindow2({
|
|
37647
37888
|
title: "Flavor Code",
|
|
37648
37889
|
width: 1280,
|
|
@@ -37653,14 +37894,14 @@ async function createWindow() {
|
|
|
37653
37894
|
show: false,
|
|
37654
37895
|
...desktopWindowChrome(),
|
|
37655
37896
|
webPreferences: {
|
|
37656
|
-
preload:
|
|
37897
|
+
preload: join52(moduleDirectory, "preload.cjs"),
|
|
37657
37898
|
contextIsolation: true,
|
|
37658
37899
|
nodeIntegration: false,
|
|
37659
37900
|
sandbox: false,
|
|
37660
37901
|
webSecurity: true
|
|
37661
37902
|
}
|
|
37662
37903
|
});
|
|
37663
|
-
const iconPath =
|
|
37904
|
+
const iconPath = join52(app.getAppPath(), "assets", "icon.png");
|
|
37664
37905
|
const icon = nativeImage.createFromPath(iconPath);
|
|
37665
37906
|
if (!icon.isEmpty()) {
|
|
37666
37907
|
mainWindow.setIcon(icon);
|