flavor-code 1.3.6 → 1.3.8
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 +22 -19
- package/README.zh-CN.md +22 -19
- package/dist/{app-CJSBYZAD.js → app-OLOUQDIG.js} +1 -1
- package/dist/{chunk-ZCEUFSHA.js → chunk-EJCI3VB5.js} +1 -1
- package/dist/{chunk-BR3CWUYR.js → chunk-GAQJZBOF.js} +413 -188
- package/dist/cli.js +3 -3
- package/dist/desktop/main.js +639 -405
- package/dist/island/control-server.d.ts +21 -0
- package/dist/production.d.ts +4 -0
- package/dist/sdk/index.js +2 -2
- package/package.json +1 -1
package/dist/desktop/main.js
CHANGED
|
@@ -1,8 +1,8 @@
|
|
|
1
1
|
// src/desktop/main.ts
|
|
2
2
|
import { appendFileSync } from "fs";
|
|
3
|
-
import { createHash as
|
|
3
|
+
import { createHash as createHash28, randomUUID as randomUUID29 } from "crypto";
|
|
4
4
|
import { readFile as readFile39, stat as stat18, writeFile as writeFile19 } from "fs/promises";
|
|
5
|
-
import { basename as basename11, dirname as dirname24, join as
|
|
5
|
+
import { basename as basename11, dirname as dirname24, join as join47, resolve as resolve40 } from "path";
|
|
6
6
|
import { fileURLToPath as fileURLToPath4 } from "url";
|
|
7
7
|
import { app, BrowserWindow as BrowserWindow2, clipboard, dialog, ipcMain, Menu, nativeImage, Notification, shell } from "electron";
|
|
8
8
|
|
|
@@ -4218,9 +4218,9 @@ ${summarizeReport(report2)}`).join("\n\n")
|
|
|
4218
4218
|
|
|
4219
4219
|
// src/production.ts
|
|
4220
4220
|
import { readFile as readFile27 } from "fs/promises";
|
|
4221
|
-
import { createHash as
|
|
4222
|
-
import { homedir as homedir2, release as osRelease, tmpdir as
|
|
4223
|
-
import { basename as basename8, join as
|
|
4221
|
+
import { createHash as createHash21, randomUUID as randomUUID25 } from "crypto";
|
|
4222
|
+
import { homedir as homedir2, release as osRelease, tmpdir as tmpdir4, version as osVersion } from "os";
|
|
4223
|
+
import { basename as basename8, join as join32, relative as relative23, resolve as resolve32, sep as sep23 } from "path";
|
|
4224
4224
|
|
|
4225
4225
|
// src/agent/subagents.ts
|
|
4226
4226
|
import { z as z10 } from "zod";
|
|
@@ -6568,6 +6568,7 @@ var AgentLoop = class {
|
|
|
6568
6568
|
attempt,
|
|
6569
6569
|
messages: modelRequest.messages
|
|
6570
6570
|
});
|
|
6571
|
+
const modelStartedAt = Date.now();
|
|
6571
6572
|
yield { type: "model-start", id: modelCallId };
|
|
6572
6573
|
try {
|
|
6573
6574
|
for await (const event of adapter.stream(modelRequest)) {
|
|
@@ -6626,6 +6627,13 @@ var AgentLoop = class {
|
|
|
6626
6627
|
completed,
|
|
6627
6628
|
agent: this.#options.agent,
|
|
6628
6629
|
providerError,
|
|
6630
|
+
durationMs: Math.max(0, Date.now() - modelStartedAt),
|
|
6631
|
+
...usage === void 0 ? {} : {
|
|
6632
|
+
inputTokens: usage.inputTokens,
|
|
6633
|
+
outputTokens: usage.outputTokens,
|
|
6634
|
+
...usage.cacheReadTokens === void 0 ? {} : { cacheReadTokens: usage.cacheReadTokens },
|
|
6635
|
+
...usage.cacheCreationTokens === void 0 ? {} : { cacheCreationTokens: usage.cacheCreationTokens }
|
|
6636
|
+
},
|
|
6629
6637
|
...terminalError === void 0 ? {} : {
|
|
6630
6638
|
errorCode: terminalError.code,
|
|
6631
6639
|
errorMessage: terminalError.message
|
|
@@ -8834,19 +8842,155 @@ function createIncidentReporter(options) {
|
|
|
8834
8842
|
};
|
|
8835
8843
|
}
|
|
8836
8844
|
|
|
8845
|
+
// src/island/control-server.ts
|
|
8846
|
+
import { createHash as createHash7, randomUUID as randomUUID10 } from "crypto";
|
|
8847
|
+
import { chmod, unlink as unlink2 } from "fs/promises";
|
|
8848
|
+
import { tmpdir } from "os";
|
|
8849
|
+
import { join as join9 } from "path";
|
|
8850
|
+
import { createServer as createServer2 } from "net";
|
|
8851
|
+
var MAX_REQUEST_BYTES = 64 * 1024;
|
|
8852
|
+
var SOCKET_TIMEOUT_MS = 1e4;
|
|
8853
|
+
function islandControlEndpoint(sessionId, platform = process.platform) {
|
|
8854
|
+
const digest = createHash7("sha256").update(`${process.pid}\0${sessionId}`).digest("hex").slice(0, 20);
|
|
8855
|
+
if (platform === "win32") return `\\\\.\\pipe\\flavor-island-control-${digest}`;
|
|
8856
|
+
const uid = typeof process.getuid === "function" ? process.getuid() : 0;
|
|
8857
|
+
return join9(tmpdir(), `flavor-island-control-${uid}-${digest}.sock`);
|
|
8858
|
+
}
|
|
8859
|
+
async function createIslandControlServer(options) {
|
|
8860
|
+
const platform = options.platform ?? process.platform;
|
|
8861
|
+
const endpoint2 = options.endpoint ?? islandControlEndpoint(options.sessionId, platform);
|
|
8862
|
+
const token = options.token ?? randomUUID10();
|
|
8863
|
+
const capabilities = [
|
|
8864
|
+
"abort",
|
|
8865
|
+
"steer",
|
|
8866
|
+
"follow_up",
|
|
8867
|
+
...options.focus === void 0 ? [] : ["focus"]
|
|
8868
|
+
];
|
|
8869
|
+
if (platform !== "win32") await unlink2(endpoint2).catch(() => void 0);
|
|
8870
|
+
const sockets = /* @__PURE__ */ new Set();
|
|
8871
|
+
const server = createServer2((socket) => {
|
|
8872
|
+
sockets.add(socket);
|
|
8873
|
+
socket.setTimeout(SOCKET_TIMEOUT_MS, () => socket.destroy());
|
|
8874
|
+
socket.on("close", () => sockets.delete(socket));
|
|
8875
|
+
socket.on("error", () => void 0);
|
|
8876
|
+
let buffer = Buffer.alloc(0);
|
|
8877
|
+
let handled = false;
|
|
8878
|
+
socket.on("data", (chunk) => {
|
|
8879
|
+
if (handled) return;
|
|
8880
|
+
const bytes = typeof chunk === "string" ? Buffer.from(chunk) : chunk;
|
|
8881
|
+
if (buffer.length + bytes.length > MAX_REQUEST_BYTES) {
|
|
8882
|
+
handled = true;
|
|
8883
|
+
write(socket, { ok: false, error: "request_too_large" });
|
|
8884
|
+
return;
|
|
8885
|
+
}
|
|
8886
|
+
buffer = Buffer.concat([buffer, bytes]);
|
|
8887
|
+
const newline = buffer.indexOf(10);
|
|
8888
|
+
if (newline < 0) return;
|
|
8889
|
+
handled = true;
|
|
8890
|
+
const line = buffer.subarray(0, newline).toString("utf8");
|
|
8891
|
+
void dispatch(line, socket, options, token);
|
|
8892
|
+
});
|
|
8893
|
+
});
|
|
8894
|
+
await new Promise((resolve41, reject) => {
|
|
8895
|
+
const onError = (error) => {
|
|
8896
|
+
server.removeListener("listening", onListening);
|
|
8897
|
+
reject(error);
|
|
8898
|
+
};
|
|
8899
|
+
const onListening = () => {
|
|
8900
|
+
server.removeListener("error", onError);
|
|
8901
|
+
resolve41();
|
|
8902
|
+
};
|
|
8903
|
+
server.once("error", onError);
|
|
8904
|
+
server.once("listening", onListening);
|
|
8905
|
+
server.listen(endpoint2);
|
|
8906
|
+
});
|
|
8907
|
+
if (platform !== "win32") await chmod(endpoint2, 384).catch(() => void 0);
|
|
8908
|
+
let closed = false;
|
|
8909
|
+
return {
|
|
8910
|
+
endpoint: endpoint2,
|
|
8911
|
+
token,
|
|
8912
|
+
capabilities,
|
|
8913
|
+
async close() {
|
|
8914
|
+
if (closed) return;
|
|
8915
|
+
closed = true;
|
|
8916
|
+
for (const socket of sockets) socket.destroy();
|
|
8917
|
+
await closeServer(server);
|
|
8918
|
+
if (platform !== "win32") await unlink2(endpoint2).catch(() => void 0);
|
|
8919
|
+
}
|
|
8920
|
+
};
|
|
8921
|
+
}
|
|
8922
|
+
async function dispatch(line, socket, options, token) {
|
|
8923
|
+
let request;
|
|
8924
|
+
try {
|
|
8925
|
+
const parsed = JSON.parse(line);
|
|
8926
|
+
if (typeof parsed !== "object" || parsed === null || Array.isArray(parsed)) throw new Error("invalid request");
|
|
8927
|
+
request = parsed;
|
|
8928
|
+
} catch {
|
|
8929
|
+
write(socket, { ok: false, error: "invalid_json" });
|
|
8930
|
+
return;
|
|
8931
|
+
}
|
|
8932
|
+
if (request.token !== token) {
|
|
8933
|
+
write(socket, { ok: false, error: "unauthorized" });
|
|
8934
|
+
return;
|
|
8935
|
+
}
|
|
8936
|
+
const command = request.command;
|
|
8937
|
+
try {
|
|
8938
|
+
if (command === "abort") {
|
|
8939
|
+
write(socket, { ok: true, result: options.session.interrupt() });
|
|
8940
|
+
} else if (command === "steer" || command === "follow_up") {
|
|
8941
|
+
const message2 = typeof request.message === "string" ? request.message.trim() : "";
|
|
8942
|
+
if (!message2 || message2.length > 1e5) {
|
|
8943
|
+
write(socket, { ok: false, error: "invalid_message" });
|
|
8944
|
+
return;
|
|
8945
|
+
}
|
|
8946
|
+
if (command === "steer") options.session.steer(message2);
|
|
8947
|
+
else options.session.followUp(message2);
|
|
8948
|
+
write(socket, { ok: true });
|
|
8949
|
+
} else if (command === "focus") {
|
|
8950
|
+
if (options.focus === void 0) {
|
|
8951
|
+
write(socket, { ok: false, error: "focus_unavailable" });
|
|
8952
|
+
return;
|
|
8953
|
+
}
|
|
8954
|
+
await options.focus();
|
|
8955
|
+
write(socket, { ok: true });
|
|
8956
|
+
} else {
|
|
8957
|
+
write(socket, { ok: false, error: "unknown_command" });
|
|
8958
|
+
}
|
|
8959
|
+
} catch (error) {
|
|
8960
|
+
write(socket, { ok: false, error: error instanceof Error ? error.message : String(error) });
|
|
8961
|
+
}
|
|
8962
|
+
}
|
|
8963
|
+
function write(socket, payload) {
|
|
8964
|
+
try {
|
|
8965
|
+
socket.end(`${JSON.stringify(payload)}
|
|
8966
|
+
`);
|
|
8967
|
+
} catch {
|
|
8968
|
+
socket.destroy();
|
|
8969
|
+
}
|
|
8970
|
+
}
|
|
8971
|
+
function closeServer(server) {
|
|
8972
|
+
return new Promise((resolve41) => {
|
|
8973
|
+
try {
|
|
8974
|
+
server.close(() => resolve41());
|
|
8975
|
+
} catch {
|
|
8976
|
+
resolve41();
|
|
8977
|
+
}
|
|
8978
|
+
});
|
|
8979
|
+
}
|
|
8980
|
+
|
|
8837
8981
|
// src/evolve/service.ts
|
|
8838
8982
|
import { spawn as spawn2 } from "child_process";
|
|
8839
8983
|
import { writeFile as writeFile7 } from "fs/promises";
|
|
8840
|
-
import { join as
|
|
8984
|
+
import { join as join12 } from "path";
|
|
8841
8985
|
import { z as z19 } from "zod";
|
|
8842
8986
|
|
|
8843
8987
|
// src/evolve/loader.ts
|
|
8844
8988
|
import { cp as cp2, mkdir as mkdir7, mkdtemp, readdir as readdir3, rm as rm4, stat as stat5, writeFile as writeFile5 } from "fs/promises";
|
|
8845
|
-
import { tmpdir } from "os";
|
|
8846
|
-
import { join as
|
|
8989
|
+
import { tmpdir as tmpdir2 } from "os";
|
|
8990
|
+
import { join as join10, resolve as resolve10 } from "path";
|
|
8847
8991
|
|
|
8848
8992
|
// src/plugins/host.ts
|
|
8849
|
-
import { createHash as
|
|
8993
|
+
import { createHash as createHash8 } from "crypto";
|
|
8850
8994
|
import { lstatSync, realpathSync as realpathSync3 } from "fs";
|
|
8851
8995
|
import { lstat as lstat3, open as open4, readFile as readFile7, readdir as readdir2, realpath as realpath6, stat as stat4, writeFile as writeFile4 } from "fs/promises";
|
|
8852
8996
|
import { isAbsolute as isAbsolute6, relative as relative7, resolve as resolve9, sep as sep7 } from "path";
|
|
@@ -9207,7 +9351,7 @@ var PluginHost = class {
|
|
|
9207
9351
|
const manifest = PluginManifestSchema.parse(rawManifest);
|
|
9208
9352
|
const entry2 = resolveContained(root, manifest.main, "Plugin entry");
|
|
9209
9353
|
const entrySnapshot = await snapshotEntry(root, entry2);
|
|
9210
|
-
const fingerprint2 =
|
|
9354
|
+
const fingerprint2 = createHash8("sha256").update(JSON.stringify(manifest)).update(await readFile7(entrySnapshot.physicalPath)).digest("hex");
|
|
9211
9355
|
return { manifest, root, source, entry: entry2, entrySnapshot, fingerprint: fingerprint2 };
|
|
9212
9356
|
} catch (error) {
|
|
9213
9357
|
this.#diagnose(diagnosticLabel, error, inputRoot);
|
|
@@ -9566,8 +9710,8 @@ function compare(left, right) {
|
|
|
9566
9710
|
}
|
|
9567
9711
|
|
|
9568
9712
|
// src/evolve/loader.ts
|
|
9569
|
-
var PLUGIN_DIR =
|
|
9570
|
-
var VERSIONS_DIR =
|
|
9713
|
+
var PLUGIN_DIR = join10(".flavor", "plugins");
|
|
9714
|
+
var VERSIONS_DIR = join10(".flavor", "plugins", ".versions");
|
|
9571
9715
|
function sanitizePluginName(tool) {
|
|
9572
9716
|
const base = String(tool ?? "fix").replace(/[^a-z0-9-]/gi, "-").toLowerCase();
|
|
9573
9717
|
return base.startsWith("fix-") ? base : `fix-${base}`;
|
|
@@ -9611,8 +9755,8 @@ export function activate(ctx) {
|
|
|
9611
9755
|
async function scaffoldFixPlugin(workspace, name) {
|
|
9612
9756
|
const dir = fixPluginDir(workspace, name);
|
|
9613
9757
|
await mkdir7(dir, { recursive: true });
|
|
9614
|
-
const manifestPath =
|
|
9615
|
-
const entryPath =
|
|
9758
|
+
const manifestPath = join10(dir, "flavor-plugin.json");
|
|
9759
|
+
const entryPath = join10(dir, "index.js");
|
|
9616
9760
|
try {
|
|
9617
9761
|
await writeFile5(manifestPath, SCAFFOLD_MANIFEST(name), { encoding: "utf8", flag: "wx" });
|
|
9618
9762
|
await writeFile5(entryPath, SCAFFOLD_ENTRY(name), { encoding: "utf8", flag: "wx" });
|
|
@@ -9632,9 +9776,9 @@ async function verifyFixPlugin(workspace, name) {
|
|
|
9632
9776
|
return { ok: false, provided: [], tools: [], commands: [], error: `Plugin "${name}" not found.` };
|
|
9633
9777
|
}
|
|
9634
9778
|
const collected = { tools: [], commands: [] };
|
|
9635
|
-
const sandbox = await mkdtemp(
|
|
9779
|
+
const sandbox = await mkdtemp(join10(tmpdir2(), "flavor-evolve-verify-"));
|
|
9636
9780
|
try {
|
|
9637
|
-
await cp2(pluginRoot,
|
|
9781
|
+
await cp2(pluginRoot, join10(sandbox, name), { recursive: true });
|
|
9638
9782
|
const host = new PluginHost({
|
|
9639
9783
|
projectPluginDirs: [sandbox],
|
|
9640
9784
|
sandbox: true,
|
|
@@ -9685,7 +9829,7 @@ async function verifyFixPlugin(workspace, name) {
|
|
|
9685
9829
|
}
|
|
9686
9830
|
async function snapshotFixPlugin(workspace, name) {
|
|
9687
9831
|
const pluginRoot = fixPluginDir(workspace, name);
|
|
9688
|
-
const snapshotRoot =
|
|
9832
|
+
const snapshotRoot = join10(versionsDirFor(workspace, name), timestamp());
|
|
9689
9833
|
await mkdir7(snapshotRoot, { recursive: true });
|
|
9690
9834
|
await cp2(pluginRoot, snapshotRoot, { recursive: true });
|
|
9691
9835
|
return snapshotRoot;
|
|
@@ -9706,7 +9850,7 @@ async function revertFixPlugin(workspace, name) {
|
|
|
9706
9850
|
}
|
|
9707
9851
|
const snapshots = entries.filter((entry2) => !entry2.startsWith(".")).sort();
|
|
9708
9852
|
if (snapshots.length === 0) throw new Error(`Plugin "${name}" has no snapshot to restore.`);
|
|
9709
|
-
const latest =
|
|
9853
|
+
const latest = join10(versionsRoot, snapshots[snapshots.length - 1]);
|
|
9710
9854
|
await rm4(pluginRoot, { recursive: true, force: true });
|
|
9711
9855
|
await mkdir7(pluginRoot, { recursive: true });
|
|
9712
9856
|
await cp2(latest, pluginRoot, { recursive: true });
|
|
@@ -9714,14 +9858,14 @@ async function revertFixPlugin(workspace, name) {
|
|
|
9714
9858
|
}
|
|
9715
9859
|
|
|
9716
9860
|
// src/evolve/store.ts
|
|
9717
|
-
import { createHash as
|
|
9861
|
+
import { createHash as createHash9 } from "crypto";
|
|
9718
9862
|
import { appendFile, mkdir as mkdir8, readFile as readFile8, rm as rm5, writeFile as writeFile6 } from "fs/promises";
|
|
9719
|
-
import { dirname as dirname8, join as
|
|
9863
|
+
import { dirname as dirname8, join as join11 } from "path";
|
|
9720
9864
|
function normalizeError(message2) {
|
|
9721
9865
|
return String(message2 ?? "").replace(/\s+/g, " ").replace(/"[^"]*"/g, '"\u2026"').replace(/`[^`]*`/g, '"\u2026"').trim().slice(0, 160);
|
|
9722
9866
|
}
|
|
9723
9867
|
function fingerprint(tool, errorCode, error) {
|
|
9724
|
-
return
|
|
9868
|
+
return createHash9("sha1").update(`${tool}::${errorCode ?? ""}::${normalizeError(error)}`).digest("hex").slice(0, 12);
|
|
9725
9869
|
}
|
|
9726
9870
|
function argKeys(args) {
|
|
9727
9871
|
if (args === null || typeof args !== "object") return [];
|
|
@@ -9789,12 +9933,12 @@ var EvolveStore = class {
|
|
|
9789
9933
|
#queue = Promise.resolve();
|
|
9790
9934
|
constructor(options) {
|
|
9791
9935
|
const { workspace, maxSignals = 400, maxRules = 20 } = options;
|
|
9792
|
-
this.dir =
|
|
9793
|
-
this.signalsFile =
|
|
9794
|
-
this.reflectionsFile =
|
|
9795
|
-
this.doneFile =
|
|
9796
|
-
this.verifiedFile =
|
|
9797
|
-
this.rulesFile =
|
|
9936
|
+
this.dir = join11(workspace, ".flavor", "evolve");
|
|
9937
|
+
this.signalsFile = join11(this.dir, "signals.jsonl");
|
|
9938
|
+
this.reflectionsFile = join11(this.dir, "reflections.jsonl");
|
|
9939
|
+
this.doneFile = join11(this.dir, "done.json");
|
|
9940
|
+
this.verifiedFile = join11(this.dir, "verified.json");
|
|
9941
|
+
this.rulesFile = join11(this.dir, "rules.json");
|
|
9798
9942
|
this.maxSignals = maxSignals;
|
|
9799
9943
|
this.maxRules = maxRules;
|
|
9800
9944
|
}
|
|
@@ -10333,7 +10477,7 @@ function createEvolveService(options) {
|
|
|
10333
10477
|
if (suggestion === void 0) return `No open suggestion with id "${suggestionId}". Use /evolve suggest to list them.`;
|
|
10334
10478
|
const name = sanitizePluginName(suggestion.tool);
|
|
10335
10479
|
const dir = await scaffoldFixPlugin(workspace, name);
|
|
10336
|
-
await writeFile7(
|
|
10480
|
+
await writeFile7(join12(dir, "PLAN.md"), buildPlan(suggestion, name, "See the repeated failure below; describe your fix in PLAN.md."), "utf8");
|
|
10337
10481
|
await snapshotFixPlugin(workspace, name);
|
|
10338
10482
|
return [
|
|
10339
10483
|
`suggestion ${suggestion.id}: ${suggestion.tool} x${suggestion.count} \u2014 ${suggestion.error}`,
|
|
@@ -10424,7 +10568,7 @@ ${result.stderr.slice(-4e3)}` : ""}`;
|
|
|
10424
10568
|
}
|
|
10425
10569
|
const name = sanitizePluginName(suggestion.tool);
|
|
10426
10570
|
const dir = await scaffoldFixPlugin(workspace, name);
|
|
10427
|
-
await writeFile7(
|
|
10571
|
+
await writeFile7(join12(dir, "PLAN.md"), buildPlan(suggestion, name, implementation), "utf8");
|
|
10428
10572
|
await snapshotFixPlugin(workspace, name);
|
|
10429
10573
|
return [
|
|
10430
10574
|
`Scaffolded fix plugin at ${dir} for suggestion [${suggestion.id}] (${suggestion.tool} x${suggestion.count}).`,
|
|
@@ -10449,7 +10593,7 @@ ${result.stderr.slice(-4e3)}` : ""}`;
|
|
|
10449
10593
|
// src/init/project.ts
|
|
10450
10594
|
import { lstat as lstat4, mkdir as mkdir9, readdir as readdir4, readFile as readFile9, realpath as realpath7, writeFile as writeFile8 } from "fs/promises";
|
|
10451
10595
|
import { copyFileSync, mkdirSync as mkdirSync2, readdirSync } from "fs";
|
|
10452
|
-
import { basename as basename4, dirname as dirname9, isAbsolute as isAbsolute7, join as
|
|
10596
|
+
import { basename as basename4, dirname as dirname9, isAbsolute as isAbsolute7, join as join13, relative as relative8, sep as sep8 } from "path";
|
|
10453
10597
|
import { fileURLToPath } from "url";
|
|
10454
10598
|
var GENERATED_START = "<!-- flavor-code:start -->";
|
|
10455
10599
|
var GENERATED_END = "<!-- flavor-code:end -->";
|
|
@@ -10571,7 +10715,7 @@ async function sampleSourceExtensions(cwd, root, directories) {
|
|
|
10571
10715
|
const extensions = /* @__PURE__ */ new Set();
|
|
10572
10716
|
const sampleDirectories = ["", ...directories].slice(0, 12);
|
|
10573
10717
|
for (const relativeDirectory of sampleDirectories) {
|
|
10574
|
-
const absoluteDirectory =
|
|
10718
|
+
const absoluteDirectory = join13(cwd, relativeDirectory);
|
|
10575
10719
|
if (!isContainedPath(root, await realpath7(absoluteDirectory))) continue;
|
|
10576
10720
|
let entries;
|
|
10577
10721
|
try {
|
|
@@ -10592,15 +10736,15 @@ async function inspectProject(cwd) {
|
|
|
10592
10736
|
const root = await realpath7(cwd);
|
|
10593
10737
|
const sourceDirectories = [];
|
|
10594
10738
|
for (const directory of SOURCE_DIRECTORIES) {
|
|
10595
|
-
if (await pathKind(
|
|
10739
|
+
if (await pathKind(join13(cwd, directory), root) === "directory") sourceDirectories.push(directory);
|
|
10596
10740
|
}
|
|
10597
10741
|
const instructionFiles = [];
|
|
10598
10742
|
for (const file of INSTRUCTION_FILES) {
|
|
10599
|
-
if (await pathKind(
|
|
10743
|
+
if (await pathKind(join13(cwd, file), root) === "file") instructionFiles.push(file);
|
|
10600
10744
|
}
|
|
10601
10745
|
const configFiles = [];
|
|
10602
10746
|
for (const file of CONFIG_FILES) {
|
|
10603
|
-
if (await pathKind(
|
|
10747
|
+
if (await pathKind(join13(cwd, file), root) === "file") configFiles.push(file);
|
|
10604
10748
|
}
|
|
10605
10749
|
const manifestNames = [
|
|
10606
10750
|
"package.json",
|
|
@@ -10613,7 +10757,7 @@ async function inspectProject(cwd) {
|
|
|
10613
10757
|
];
|
|
10614
10758
|
const present = /* @__PURE__ */ new Set();
|
|
10615
10759
|
for (const manifest of manifestNames) {
|
|
10616
|
-
if (await pathKind(
|
|
10760
|
+
if (await pathKind(join13(cwd, manifest), root) === "file") present.add(manifest);
|
|
10617
10761
|
}
|
|
10618
10762
|
const detectedLockfiles = JAVASCRIPT_LOCKFILES.filter(({ name }) => present.has(name));
|
|
10619
10763
|
const detectedJsManagers = [
|
|
@@ -10639,7 +10783,7 @@ async function inspectProject(cwd) {
|
|
|
10639
10783
|
let projectName = sanitizeProjectName(basename4(cwd)) ?? "project";
|
|
10640
10784
|
let declaredJsManager;
|
|
10641
10785
|
if (present.has("package.json")) {
|
|
10642
|
-
const text = await readTextIfSmall(
|
|
10786
|
+
const text = await readTextIfSmall(join13(cwd, "package.json"), root);
|
|
10643
10787
|
if (text === void 0) {
|
|
10644
10788
|
cautions.push("package.json was too large to inspect; its scripts were ignored.");
|
|
10645
10789
|
} else {
|
|
@@ -10693,8 +10837,8 @@ async function inspectProject(cwd) {
|
|
|
10693
10837
|
addScriptCommand(testCommands, scripts, jsManager, ["test"]);
|
|
10694
10838
|
addScriptCommand(lintCommands, scripts, jsManager, ["lint"]);
|
|
10695
10839
|
addScriptCommand(formatCommands, scripts, jsManager, ["format", "format:check"]);
|
|
10696
|
-
const pyproject = present.has("pyproject.toml") ? await readTextIfSmall(
|
|
10697
|
-
const requirements = present.has("requirements.txt") ? await readTextIfSmall(
|
|
10840
|
+
const pyproject = present.has("pyproject.toml") ? await readTextIfSmall(join13(cwd, "pyproject.toml"), root) : void 0;
|
|
10841
|
+
const requirements = present.has("requirements.txt") ? await readTextIfSmall(join13(cwd, "requirements.txt"), root) : void 0;
|
|
10698
10842
|
const pythonTools = detectPythonTools(pyproject, requirements, configFiles);
|
|
10699
10843
|
const pythonManager = pythonManagers[0];
|
|
10700
10844
|
const pythonPrefix = pythonManager === "uv" ? "uv run " : pythonManager === "poetry" ? "poetry run " : pythonManager === "pipenv" ? "pipenv run " : "";
|
|
@@ -10712,8 +10856,8 @@ async function inspectProject(cwd) {
|
|
|
10712
10856
|
if (present.has("pyproject.toml") || present.has("requirements.txt") || extensions.has("py")) {
|
|
10713
10857
|
languages.add("Python");
|
|
10714
10858
|
}
|
|
10715
|
-
const gitignorePath = await pathKind(
|
|
10716
|
-
const hasCodeGraph = await pathKind(
|
|
10859
|
+
const gitignorePath = await pathKind(join13(cwd, ".gitignore"), root) === "file" ? ".gitignore" : void 0;
|
|
10860
|
+
const hasCodeGraph = await pathKind(join13(cwd, ".flavor", "astgraph", "index.db"), root) === "file";
|
|
10717
10861
|
return {
|
|
10718
10862
|
cwd,
|
|
10719
10863
|
projectName,
|
|
@@ -10822,7 +10966,7 @@ function isEntryIgnored(lines, entry2) {
|
|
|
10822
10966
|
});
|
|
10823
10967
|
}
|
|
10824
10968
|
async function addSessionsToGitignore(cwd) {
|
|
10825
|
-
const path =
|
|
10969
|
+
const path = join13(cwd, ".gitignore");
|
|
10826
10970
|
const root = await realpath7(cwd);
|
|
10827
10971
|
await assertSafeManagedPath(path, root);
|
|
10828
10972
|
let existing;
|
|
@@ -10856,9 +11000,9 @@ async function assertSafeManagedPath(path, root) {
|
|
|
10856
11000
|
}
|
|
10857
11001
|
async function ensureFlavorDirectories(cwd) {
|
|
10858
11002
|
const root = await realpath7(cwd);
|
|
10859
|
-
const sessionsDir =
|
|
10860
|
-
const skillsDir =
|
|
10861
|
-
const pluginsDir =
|
|
11003
|
+
const sessionsDir = join13(cwd, ".flavor", "sessions");
|
|
11004
|
+
const skillsDir = join13(cwd, ".flavor", "skills");
|
|
11005
|
+
const pluginsDir = join13(cwd, ".flavor", "plugins");
|
|
10862
11006
|
await mkdir9(sessionsDir, { recursive: true, mode: 448 });
|
|
10863
11007
|
await mkdir9(skillsDir, { recursive: true, mode: 448 });
|
|
10864
11008
|
await mkdir9(pluginsDir, { recursive: true, mode: 448 });
|
|
@@ -10871,7 +11015,7 @@ async function ensureFlavorDirectories(cwd) {
|
|
|
10871
11015
|
}
|
|
10872
11016
|
}
|
|
10873
11017
|
async function createExampleFlavorConfig(cwd) {
|
|
10874
|
-
const configPath =
|
|
11018
|
+
const configPath = join13(cwd, ".flavor", "flavor.json");
|
|
10875
11019
|
const root = await realpath7(cwd);
|
|
10876
11020
|
await assertSafeManagedPath(configPath, root);
|
|
10877
11021
|
try {
|
|
@@ -10901,9 +11045,9 @@ async function createExampleFlavorConfig(cwd) {
|
|
|
10901
11045
|
};
|
|
10902
11046
|
await writeFile8(configPath, JSON.stringify(example, null, 2) + "\n");
|
|
10903
11047
|
}
|
|
10904
|
-
var ASTGRAPH_SRC_DIR =
|
|
11048
|
+
var ASTGRAPH_SRC_DIR = join13(dirname9(fileURLToPath(import.meta.url)), "astgraph");
|
|
10905
11049
|
async function copyAstgraphPlugin(cwd) {
|
|
10906
|
-
const pluginDir =
|
|
11050
|
+
const pluginDir = join13(cwd, ".flavor", "plugins", "astgraph");
|
|
10907
11051
|
try {
|
|
10908
11052
|
await lstat4(pluginDir);
|
|
10909
11053
|
return;
|
|
@@ -10915,8 +11059,8 @@ async function copyAstgraphPlugin(cwd) {
|
|
|
10915
11059
|
function copyDirectory(from, to) {
|
|
10916
11060
|
mkdirSync2(to, { recursive: true, mode: 448 });
|
|
10917
11061
|
for (const entry2 of readdirSync(from, { withFileTypes: true })) {
|
|
10918
|
-
const source =
|
|
10919
|
-
const target =
|
|
11062
|
+
const source = join13(from, entry2.name);
|
|
11063
|
+
const target = join13(to, entry2.name);
|
|
10920
11064
|
if (entry2.isDirectory()) copyDirectory(source, target);
|
|
10921
11065
|
else if (entry2.isFile()) copyFileSync(source, target);
|
|
10922
11066
|
}
|
|
@@ -10926,7 +11070,7 @@ async function initializeFlavor(cwd) {
|
|
|
10926
11070
|
await copyAstgraphPlugin(cwd);
|
|
10927
11071
|
await createExampleFlavorConfig(cwd);
|
|
10928
11072
|
const facts = await inspectProject(cwd);
|
|
10929
|
-
const path =
|
|
11073
|
+
const path = join13(cwd, "FLAVOR.md");
|
|
10930
11074
|
const root = await realpath7(cwd);
|
|
10931
11075
|
await assertSafeManagedPath(path, root);
|
|
10932
11076
|
let existing;
|
|
@@ -11342,9 +11486,9 @@ ${evidence.summary}`;
|
|
|
11342
11486
|
};
|
|
11343
11487
|
|
|
11344
11488
|
// src/goal/orchestrator.ts
|
|
11345
|
-
import { createHash as
|
|
11489
|
+
import { createHash as createHash11 } from "crypto";
|
|
11346
11490
|
import { writeFile as writeFile9, mkdir as mkdir10 } from "fs/promises";
|
|
11347
|
-
import { join as
|
|
11491
|
+
import { join as join15 } from "path";
|
|
11348
11492
|
|
|
11349
11493
|
// src/goal/types.ts
|
|
11350
11494
|
import { z as z21 } from "zod";
|
|
@@ -11507,9 +11651,9 @@ function extractJson(text) {
|
|
|
11507
11651
|
}
|
|
11508
11652
|
|
|
11509
11653
|
// src/goal/classifier.ts
|
|
11510
|
-
import { createHash as
|
|
11654
|
+
import { createHash as createHash10 } from "crypto";
|
|
11511
11655
|
import { readFile as readFile10 } from "fs/promises";
|
|
11512
|
-
import { join as
|
|
11656
|
+
import { join as join14 } from "path";
|
|
11513
11657
|
function buildSkepticPrompt(evidence, plan, index) {
|
|
11514
11658
|
const criteriaList = plan.criteria.map((c) => ` ${c.id}. [${c.type}] ${c.description}`).join("\n");
|
|
11515
11659
|
return [
|
|
@@ -11682,11 +11826,11 @@ function deduplicateGaps(gaps) {
|
|
|
11682
11826
|
}
|
|
11683
11827
|
function computeFingerprint(gaps) {
|
|
11684
11828
|
const content = gaps.map((g) => `${g.criterion}:${g.description}`).sort().join("\n");
|
|
11685
|
-
return
|
|
11829
|
+
return createHash10("sha256").update(content).digest("hex").slice(0, 16);
|
|
11686
11830
|
}
|
|
11687
|
-
async function collectEvidence(workspace, objective, finalResponse, priorGaps, contractHash2 =
|
|
11831
|
+
async function collectEvidence(workspace, objective, finalResponse, priorGaps, contractHash2 = createHash10("sha256").update(objective).digest("hex"), hostVerification) {
|
|
11688
11832
|
const workspaceState = await collectWorkspaceState(workspace);
|
|
11689
|
-
const planPath2 =
|
|
11833
|
+
const planPath2 = join14(workspace, ".flavor", "goal-plan.md");
|
|
11690
11834
|
let planFile = null;
|
|
11691
11835
|
try {
|
|
11692
11836
|
planFile = await readFile10(planPath2, "utf8");
|
|
@@ -11713,7 +11857,7 @@ async function collectWorkspaceState(workspace) {
|
|
|
11713
11857
|
return {
|
|
11714
11858
|
changedFiles: [],
|
|
11715
11859
|
status: ["not-a-git-worktree"],
|
|
11716
|
-
diffHash:
|
|
11860
|
+
diffHash: createHash10("sha256").update("not-a-git-worktree").digest("hex")
|
|
11717
11861
|
};
|
|
11718
11862
|
}
|
|
11719
11863
|
const status = statusResult.stdout.split(/\r?\n/).map((line) => line.trimEnd()).filter(Boolean);
|
|
@@ -11730,7 +11874,7 @@ async function collectWorkspaceState(workspace) {
|
|
|
11730
11874
|
return {
|
|
11731
11875
|
changedFiles,
|
|
11732
11876
|
status,
|
|
11733
|
-
diffHash:
|
|
11877
|
+
diffHash: createHash10("sha256").update(diff.stdout).update("\nUNTRACKED\n").update(untracked).digest("hex")
|
|
11734
11878
|
};
|
|
11735
11879
|
}
|
|
11736
11880
|
|
|
@@ -11862,7 +12006,7 @@ var GoalOrchestrator = class {
|
|
|
11862
12006
|
type: "not_achieved",
|
|
11863
12007
|
gaps,
|
|
11864
12008
|
summary: hostVerification.summary,
|
|
11865
|
-
fingerprint:
|
|
12009
|
+
fingerprint: createHash11("sha256").update(hostVerification.summary).digest("hex").slice(0, 16)
|
|
11866
12010
|
};
|
|
11867
12011
|
}
|
|
11868
12012
|
} else try {
|
|
@@ -11936,11 +12080,11 @@ var GoalOrchestrator = class {
|
|
|
11936
12080
|
}
|
|
11937
12081
|
};
|
|
11938
12082
|
function contractHash(objective, plan) {
|
|
11939
|
-
return
|
|
12083
|
+
return createHash11("sha256").update(JSON.stringify({ objective, plan })).digest("hex");
|
|
11940
12084
|
}
|
|
11941
12085
|
async function writePlanFile(workspace, plan) {
|
|
11942
|
-
const dir =
|
|
11943
|
-
const planPath2 =
|
|
12086
|
+
const dir = join15(workspace, ".flavor");
|
|
12087
|
+
const planPath2 = join15(dir, "goal-plan.md");
|
|
11944
12088
|
await mkdir10(dir, { recursive: true });
|
|
11945
12089
|
const criteriaText = plan.criteria.map((c) => `- [${c.type}] AC-${c.id}: ${c.description}`).join("\n");
|
|
11946
12090
|
const content = [
|
|
@@ -12016,9 +12160,9 @@ function formatPriorGaps(gaps) {
|
|
|
12016
12160
|
|
|
12017
12161
|
// src/goal/store.ts
|
|
12018
12162
|
import { constants as constants3 } from "fs";
|
|
12019
|
-
import { randomUUID as
|
|
12163
|
+
import { randomUUID as randomUUID11 } from "crypto";
|
|
12020
12164
|
import { lstat as lstat5, mkdir as mkdir11, open as open5, rename as rename4, rm as rm6 } from "fs/promises";
|
|
12021
|
-
import { dirname as dirname10, join as
|
|
12165
|
+
import { dirname as dirname10, join as join16, relative as relative9, resolve as resolve11, sep as sep9 } from "path";
|
|
12022
12166
|
var DEFAULT_MAX_BYTES = 1024 * 1024;
|
|
12023
12167
|
var GoalStore = class {
|
|
12024
12168
|
#workspace;
|
|
@@ -12026,7 +12170,7 @@ var GoalStore = class {
|
|
|
12026
12170
|
#maxBytes;
|
|
12027
12171
|
constructor(options) {
|
|
12028
12172
|
this.#workspace = resolve11(options.workspace);
|
|
12029
|
-
this.#root =
|
|
12173
|
+
this.#root = join16(this.#workspace, ".flavor", "goals");
|
|
12030
12174
|
this.#maxBytes = options.maxBytes ?? DEFAULT_MAX_BYTES;
|
|
12031
12175
|
}
|
|
12032
12176
|
async save(input) {
|
|
@@ -12034,13 +12178,13 @@ var GoalStore = class {
|
|
|
12034
12178
|
await assertNoSymlink(this.#workspace, dirname10(this.#root));
|
|
12035
12179
|
await mkdir11(this.#root, { recursive: true, mode: 448 });
|
|
12036
12180
|
await assertNoSymlink(this.#workspace, this.#root);
|
|
12037
|
-
const target =
|
|
12181
|
+
const target = join16(this.#root, `${state.id}.json`);
|
|
12038
12182
|
const body = `${JSON.stringify(state, null, 2)}
|
|
12039
12183
|
`;
|
|
12040
12184
|
if (Buffer.byteLength(body) > this.#maxBytes) {
|
|
12041
12185
|
throw new Error(`Goal state exceeds maximum size of ${this.#maxBytes} bytes`);
|
|
12042
12186
|
}
|
|
12043
|
-
const temporary =
|
|
12187
|
+
const temporary = join16(this.#root, `.${state.id}.${process.pid}.${randomUUID11()}.tmp`);
|
|
12044
12188
|
let handle;
|
|
12045
12189
|
try {
|
|
12046
12190
|
handle = await open5(temporary, constants3.O_CREAT | constants3.O_EXCL | constants3.O_WRONLY, 384);
|
|
@@ -12081,7 +12225,7 @@ function isCode4(error, code) {
|
|
|
12081
12225
|
|
|
12082
12226
|
// src/loop/isolation.ts
|
|
12083
12227
|
import { appendFile as appendFile2, mkdir as mkdir12, readFile as readFile11 } from "fs/promises";
|
|
12084
|
-
import { join as
|
|
12228
|
+
import { join as join17, resolve as resolve12 } from "path";
|
|
12085
12229
|
var CODE_CHANGE = /\b(?:fix|implement|add|update|change|modify|refactor|build|create|remove|delete|write|migrate)\b|修复|实现|添加|更新|修改|重构|创建|删除|迁移/i;
|
|
12086
12230
|
var READ_ONLY = /\b(?:review|analy[sz]e|inspect|explain|research|investigate|audit|summari[sz]e|report)\b|调研|分析|审查|检查|解释|研究|总结|报告/i;
|
|
12087
12231
|
function inferGoalIntent(goal) {
|
|
@@ -12104,8 +12248,8 @@ async function prepareLoopWorkspace(options) {
|
|
|
12104
12248
|
return { kind: "needs_human", reason: "The workspace has uncommitted changes that a new worktree would omit." };
|
|
12105
12249
|
}
|
|
12106
12250
|
options.signal?.throwIfAborted();
|
|
12107
|
-
const worktrees =
|
|
12108
|
-
const path =
|
|
12251
|
+
const worktrees = join17(root, ".worktrees");
|
|
12252
|
+
const path = join17(worktrees, `loop-${options.loopId}`);
|
|
12109
12253
|
const branch = `loop/${options.loopId}`;
|
|
12110
12254
|
try {
|
|
12111
12255
|
await ensureWorktreeDirectoryIgnored(root);
|
|
@@ -12135,8 +12279,8 @@ async function ensureWorktreeDirectoryIgnored(root) {
|
|
|
12135
12279
|
const commonDirectory = await git(root, ["rev-parse", "--git-common-dir"]);
|
|
12136
12280
|
if (commonDirectory.code !== 0) throw new Error(commonDirectory.stderr.trim() || "Git common directory is unavailable");
|
|
12137
12281
|
const gitDirectory = resolve12(root, commonDirectory.stdout.trim());
|
|
12138
|
-
const infoDirectory =
|
|
12139
|
-
const excludePath =
|
|
12282
|
+
const infoDirectory = join17(gitDirectory, "info");
|
|
12283
|
+
const excludePath = join17(infoDirectory, "exclude");
|
|
12140
12284
|
await mkdir12(infoDirectory, { recursive: true });
|
|
12141
12285
|
let existing = "";
|
|
12142
12286
|
try {
|
|
@@ -12152,8 +12296,8 @@ async function ensureWorktreeDirectoryIgnored(root) {
|
|
|
12152
12296
|
// src/loop/store.ts
|
|
12153
12297
|
import { constants as constants4 } from "fs";
|
|
12154
12298
|
import { lstat as lstat6, mkdir as mkdir13, open as open6, readFile as readFile12, realpath as realpath8, rename as rename5, rm as rm7, stat as stat6 } from "fs/promises";
|
|
12155
|
-
import { dirname as dirname11, join as
|
|
12156
|
-
import { randomUUID as
|
|
12299
|
+
import { dirname as dirname11, join as join18, relative as relative10, resolve as resolve13, sep as sep10 } from "path";
|
|
12300
|
+
import { randomUUID as randomUUID12 } from "crypto";
|
|
12157
12301
|
var LOOP_ID = /^[A-Za-z0-9][A-Za-z0-9._-]{0,127}$/;
|
|
12158
12302
|
var DEFAULT_MAX_BYTES2 = 5 * 1024 * 1024;
|
|
12159
12303
|
var LoopStore = class {
|
|
@@ -12163,7 +12307,7 @@ var LoopStore = class {
|
|
|
12163
12307
|
#appendTails = /* @__PURE__ */ new Map();
|
|
12164
12308
|
constructor(options) {
|
|
12165
12309
|
this.#workspace = resolve13(options.workspace);
|
|
12166
|
-
this.#root =
|
|
12310
|
+
this.#root = join18(this.#workspace, ".flavor", "loops");
|
|
12167
12311
|
this.#maxBytes = options.maxBytes ?? DEFAULT_MAX_BYTES2;
|
|
12168
12312
|
}
|
|
12169
12313
|
async create(state) {
|
|
@@ -12180,11 +12324,11 @@ var LoopStore = class {
|
|
|
12180
12324
|
const state = LoopStateSchema.parse(input);
|
|
12181
12325
|
await this.#assertWorkspace(state.workspace);
|
|
12182
12326
|
const directory = await this.#prepare(state.loopId);
|
|
12183
|
-
const target =
|
|
12327
|
+
const target = join18(directory, "state.json");
|
|
12184
12328
|
const body = `${JSON.stringify(state, null, 2)}
|
|
12185
12329
|
`;
|
|
12186
12330
|
if (Buffer.byteLength(body) > this.#maxBytes) throw new Error(`Loop state exceeds maximum size of ${this.#maxBytes} bytes`);
|
|
12187
|
-
const temporary =
|
|
12331
|
+
const temporary = join18(directory, `.state.${process.pid}.${randomUUID12()}.tmp`);
|
|
12188
12332
|
let handle;
|
|
12189
12333
|
try {
|
|
12190
12334
|
handle = await open6(temporary, constants4.O_CREAT | constants4.O_EXCL | constants4.O_WRONLY, 384);
|
|
@@ -12200,7 +12344,7 @@ var LoopStore = class {
|
|
|
12200
12344
|
}
|
|
12201
12345
|
async load(loopId) {
|
|
12202
12346
|
const directory = await this.#safeExisting(loopId);
|
|
12203
|
-
const target =
|
|
12347
|
+
const target = join18(directory, "state.json");
|
|
12204
12348
|
try {
|
|
12205
12349
|
const metadata = await stat6(target);
|
|
12206
12350
|
if (!metadata.isFile() || metadata.size > this.#maxBytes) throw new Error("Loop state is not a bounded regular file");
|
|
@@ -12210,7 +12354,7 @@ var LoopStore = class {
|
|
|
12210
12354
|
return state;
|
|
12211
12355
|
} catch (error) {
|
|
12212
12356
|
if (isCode5(error, "ENOENT")) throw new Error(`Loop "${loopId}" was not found`);
|
|
12213
|
-
const quarantine = `${target}.corrupt-${Date.now()}-${
|
|
12357
|
+
const quarantine = `${target}.corrupt-${Date.now()}-${randomUUID12().slice(0, 8)}`;
|
|
12214
12358
|
await rename5(target, quarantine).catch(() => void 0);
|
|
12215
12359
|
throw new Error(`Loop "${loopId}" is corrupt and was quarantined: ${message(error)}`);
|
|
12216
12360
|
}
|
|
@@ -12220,7 +12364,7 @@ var LoopStore = class {
|
|
|
12220
12364
|
const previous = this.#appendTails.get(event.loopId) ?? Promise.resolve();
|
|
12221
12365
|
const next = previous.catch(() => void 0).then(async () => {
|
|
12222
12366
|
const directory = await this.#prepare(event.loopId);
|
|
12223
|
-
const path =
|
|
12367
|
+
const path = join18(directory, "events.jsonl");
|
|
12224
12368
|
const handle = await open6(path, constants4.O_CREAT | constants4.O_APPEND | constants4.O_WRONLY, 384);
|
|
12225
12369
|
try {
|
|
12226
12370
|
await handle.writeFile(`${JSON.stringify(event)}
|
|
@@ -12237,7 +12381,7 @@ var LoopStore = class {
|
|
|
12237
12381
|
await assertNoSymlink2(this.#workspace, dirname11(this.#root));
|
|
12238
12382
|
await mkdir13(this.#root, { recursive: true, mode: 448 });
|
|
12239
12383
|
await assertNoSymlink2(this.#workspace, this.#root);
|
|
12240
|
-
const directory =
|
|
12384
|
+
const directory = join18(this.#root, loopId);
|
|
12241
12385
|
await mkdir13(directory, { recursive: true, mode: 448 });
|
|
12242
12386
|
await assertNoSymlink2(this.#workspace, directory);
|
|
12243
12387
|
return directory;
|
|
@@ -12245,7 +12389,7 @@ var LoopStore = class {
|
|
|
12245
12389
|
async #safeExisting(loopId) {
|
|
12246
12390
|
validateId(loopId);
|
|
12247
12391
|
await assertNoSymlink2(this.#workspace, this.#root);
|
|
12248
|
-
const directory =
|
|
12392
|
+
const directory = join18(this.#root, loopId);
|
|
12249
12393
|
await assertNoSymlink2(this.#workspace, directory);
|
|
12250
12394
|
return directory;
|
|
12251
12395
|
}
|
|
@@ -12294,7 +12438,7 @@ function isCode5(error, code) {
|
|
|
12294
12438
|
|
|
12295
12439
|
// src/loop/verifier.ts
|
|
12296
12440
|
import { readFile as readFile13 } from "fs/promises";
|
|
12297
|
-
import { join as
|
|
12441
|
+
import { join as join19 } from "path";
|
|
12298
12442
|
|
|
12299
12443
|
// src/tools/shell.ts
|
|
12300
12444
|
import { spawn as spawn3, spawnSync } from "child_process";
|
|
@@ -12852,13 +12996,13 @@ var SCRIPT_PRIORITY = ["test", "typecheck", "lint", "build", "smoke:install"];
|
|
|
12852
12996
|
async function inferVerificationPlan(workspace) {
|
|
12853
12997
|
const commands = [];
|
|
12854
12998
|
const seen = /* @__PURE__ */ new Set();
|
|
12855
|
-
const packageJson = await optionalJson(
|
|
12999
|
+
const packageJson = await optionalJson(join19(workspace, "package.json"));
|
|
12856
13000
|
const scripts = isRecord3(packageJson?.scripts) ? packageJson.scripts : {};
|
|
12857
13001
|
for (const name of SCRIPT_PRIORITY) {
|
|
12858
13002
|
if (typeof scripts[name] !== "string" || isUnconditionalPassThrough(scripts[name])) continue;
|
|
12859
13003
|
add(commands, seen, npmScript(name));
|
|
12860
13004
|
}
|
|
12861
|
-
const flavor = await optionalText(
|
|
13005
|
+
const flavor = await optionalText(join19(workspace, "FLAVOR.md"));
|
|
12862
13006
|
if (flavor !== void 0) {
|
|
12863
13007
|
for (const match of flavor.matchAll(/^- `([^`]+)`\s*$/gm)) {
|
|
12864
13008
|
const parsed = parseTrustedCommand(match[1] ?? "");
|
|
@@ -12995,11 +13139,11 @@ function isEnvTruthy(value) {
|
|
|
12995
13139
|
|
|
12996
13140
|
// src/utils/log.ts
|
|
12997
13141
|
import { appendFile as appendFile3, mkdir as mkdir14 } from "fs/promises";
|
|
12998
|
-
import { dirname as dirname12, join as
|
|
13142
|
+
import { dirname as dirname12, join as join20, resolve as resolve15 } from "path";
|
|
12999
13143
|
var pending = Promise.resolve();
|
|
13000
13144
|
var usagePending = Promise.resolve();
|
|
13001
13145
|
function usageLogPath() {
|
|
13002
|
-
return process.env.FLAVOR_USAGE_FILE ??
|
|
13146
|
+
return process.env.FLAVOR_USAGE_FILE ?? join20(process.cwd(), ".flavor", "usage.jsonl");
|
|
13003
13147
|
}
|
|
13004
13148
|
var usageSessionId = "unknown";
|
|
13005
13149
|
var truncateOnNextAppend = false;
|
|
@@ -13028,7 +13172,7 @@ var AuditLogger = class {
|
|
|
13028
13172
|
#path;
|
|
13029
13173
|
#closed = false;
|
|
13030
13174
|
constructor(workspace) {
|
|
13031
|
-
this.#path =
|
|
13175
|
+
this.#path = join20(resolve15(workspace), ".flavor", "audit.jsonl");
|
|
13032
13176
|
}
|
|
13033
13177
|
get path() {
|
|
13034
13178
|
return this.#path;
|
|
@@ -13626,7 +13770,7 @@ var ModelRegistry = class {
|
|
|
13626
13770
|
};
|
|
13627
13771
|
|
|
13628
13772
|
// src/mcp/client.ts
|
|
13629
|
-
import { createHash as
|
|
13773
|
+
import { createHash as createHash12 } from "crypto";
|
|
13630
13774
|
import { z as z24 } from "zod";
|
|
13631
13775
|
var McpManager = class {
|
|
13632
13776
|
#workspace;
|
|
@@ -13793,7 +13937,7 @@ function mcpToolName(serverName, remoteName) {
|
|
|
13793
13937
|
const safe = remoteName.replace(/[^A-Za-z0-9_-]/g, "_") || "tool";
|
|
13794
13938
|
const direct = `${prefix}${safe}`;
|
|
13795
13939
|
if (direct.length <= 64 && safe === remoteName) return direct;
|
|
13796
|
-
const suffix = `__${
|
|
13940
|
+
const suffix = `__${createHash12("sha256").update(remoteName).digest("hex").slice(0, 8)}`;
|
|
13797
13941
|
return `${prefix}${safe.slice(0, Math.max(1, 64 - prefix.length - suffix.length))}${suffix}`;
|
|
13798
13942
|
}
|
|
13799
13943
|
function mcpErrorText(content) {
|
|
@@ -13824,7 +13968,7 @@ import {
|
|
|
13824
13968
|
|
|
13825
13969
|
// src/utils/version.ts
|
|
13826
13970
|
import { readFileSync as readFileSync2 } from "fs";
|
|
13827
|
-
import { dirname as dirname13, join as
|
|
13971
|
+
import { dirname as dirname13, join as join21 } from "path";
|
|
13828
13972
|
import { fileURLToPath as fileURLToPath2 } from "url";
|
|
13829
13973
|
var cached;
|
|
13830
13974
|
function packageVersion() {
|
|
@@ -13832,7 +13976,7 @@ function packageVersion() {
|
|
|
13832
13976
|
let directory = dirname13(fileURLToPath2(import.meta.url));
|
|
13833
13977
|
for (; ; ) {
|
|
13834
13978
|
try {
|
|
13835
|
-
const manifest = JSON.parse(readFileSync2(
|
|
13979
|
+
const manifest = JSON.parse(readFileSync2(join21(directory, "package.json"), "utf8"));
|
|
13836
13980
|
if (typeof manifest.version === "string" && manifest.version.length > 0) {
|
|
13837
13981
|
cached = manifest.version;
|
|
13838
13982
|
return cached;
|
|
@@ -13885,14 +14029,14 @@ async function connectSdkMcpClient(input, dependencies2 = DEFAULT_DEPENDENCIES)
|
|
|
13885
14029
|
}
|
|
13886
14030
|
|
|
13887
14031
|
// src/auth/oauth.ts
|
|
13888
|
-
import { randomBytes as randomBytes2, createHash as
|
|
13889
|
-
import { createServer as
|
|
14032
|
+
import { randomBytes as randomBytes2, createHash as createHash14 } from "crypto";
|
|
14033
|
+
import { createServer as createServer3 } from "http";
|
|
13890
14034
|
import { URL as URL2, URLSearchParams } from "url";
|
|
13891
14035
|
|
|
13892
14036
|
// src/auth/oauth-config.ts
|
|
13893
|
-
import { createHash as
|
|
14037
|
+
import { createHash as createHash13 } from "crypto";
|
|
13894
14038
|
function oauthCredentialId(tokenUrl, clientId) {
|
|
13895
|
-
const digest =
|
|
14039
|
+
const digest = createHash13("sha256").update(`${tokenUrl}\0${clientId}`, "utf8").digest("base64url");
|
|
13896
14040
|
return `oauth:${digest}`;
|
|
13897
14041
|
}
|
|
13898
14042
|
function parseOAuthLlmConfig(value) {
|
|
@@ -14069,7 +14213,7 @@ var OAuthCallbackAuthProvider = class {
|
|
|
14069
14213
|
serverResolve = resolve41;
|
|
14070
14214
|
serverReject = reject;
|
|
14071
14215
|
});
|
|
14072
|
-
const server =
|
|
14216
|
+
const server = createServer3((req, res) => {
|
|
14073
14217
|
const url = new URL2(req.url ?? "/", `http://${this.#callbackHost}:${server.address().port}`);
|
|
14074
14218
|
if (url.searchParams.has("error")) {
|
|
14075
14219
|
const error = url.searchParams.get("error") ?? "access_denied";
|
|
@@ -14149,7 +14293,7 @@ var OAuthCallbackAuthProvider = class {
|
|
|
14149
14293
|
return randomBytes2(32).toString("base64url");
|
|
14150
14294
|
}
|
|
14151
14295
|
#computeCodeChallenge(verifier) {
|
|
14152
|
-
return
|
|
14296
|
+
return createHash14("sha256").update(verifier).digest("base64url");
|
|
14153
14297
|
}
|
|
14154
14298
|
#generateState() {
|
|
14155
14299
|
return randomBytes2(32).toString("base64url");
|
|
@@ -14260,7 +14404,7 @@ function isStoredToken(value) {
|
|
|
14260
14404
|
|
|
14261
14405
|
// src/permissions/policy.ts
|
|
14262
14406
|
import { readFile as readFile16 } from "fs/promises";
|
|
14263
|
-
import { join as
|
|
14407
|
+
import { join as join22, resolve as resolve17 } from "path";
|
|
14264
14408
|
import { z as z25 } from "zod";
|
|
14265
14409
|
var DecisionSchema2 = z25.enum(["allow", "ask", "deny"]);
|
|
14266
14410
|
var PrefixSchema = z25.array(z25.string().min(1).max(1024)).min(1).max(64);
|
|
@@ -14297,9 +14441,9 @@ var CompiledPermissionPolicy = class {
|
|
|
14297
14441
|
async function loadPermissionPolicy(options) {
|
|
14298
14442
|
const sources = [
|
|
14299
14443
|
...options.managedPath === void 0 ? [] : [{ tier: "managed", path: resolve17(options.managedPath) }],
|
|
14300
|
-
{ tier: "user", path:
|
|
14301
|
-
{ tier: "project", path:
|
|
14302
|
-
{ tier: "local", path:
|
|
14444
|
+
{ tier: "user", path: join22(resolve17(options.home), ".flavor-code", "permissions.json") },
|
|
14445
|
+
{ tier: "project", path: join22(resolve17(options.workspace), ".flavor", "permissions.json") },
|
|
14446
|
+
{ tier: "local", path: join22(resolve17(options.workspace), ".flavor", "permissions.local.json") }
|
|
14303
14447
|
];
|
|
14304
14448
|
const rules = [];
|
|
14305
14449
|
for (const source of sources) {
|
|
@@ -15068,8 +15212,8 @@ function createSkillResourceTool(registry) {
|
|
|
15068
15212
|
// src/session/store.ts
|
|
15069
15213
|
import { constants as constants6 } from "fs";
|
|
15070
15214
|
import { lstat as lstat8, mkdir as mkdir15, open as open7, readFile as readFile17, readdir as readdir6, realpath as realpath10, rename as rename6, rm as rm8, stat as stat7 } from "fs/promises";
|
|
15071
|
-
import { basename as basename5, dirname as dirname15, join as
|
|
15072
|
-
import { randomUUID as
|
|
15215
|
+
import { basename as basename5, dirname as dirname15, join as join23, relative as relative13, resolve as resolve19, sep as sep13 } from "path";
|
|
15216
|
+
import { randomUUID as randomUUID13 } from "crypto";
|
|
15073
15217
|
import { z as z27 } from "zod";
|
|
15074
15218
|
|
|
15075
15219
|
// src/ui/transcript.ts
|
|
@@ -15725,7 +15869,7 @@ var SessionStore = class {
|
|
|
15725
15869
|
#maxSessions;
|
|
15726
15870
|
constructor(options) {
|
|
15727
15871
|
this.#workspace = resolve19(options.workspace);
|
|
15728
|
-
this.#sessions =
|
|
15872
|
+
this.#sessions = join23(this.#workspace, ".flavor", "sessions");
|
|
15729
15873
|
this.#maxBytes = options.maxBytes ?? DEFAULT_MAX_SESSION_BYTES;
|
|
15730
15874
|
if (!Number.isSafeInteger(this.#maxBytes) || this.#maxBytes < 256) throw new Error("maxBytes must be an integer of at least 256");
|
|
15731
15875
|
this.#maxSessions = options.maxSessions ?? 50;
|
|
@@ -15757,7 +15901,7 @@ var SessionStore = class {
|
|
|
15757
15901
|
const body = `${lines.join("\n")}
|
|
15758
15902
|
`;
|
|
15759
15903
|
if (Buffer.byteLength(body) > this.#maxBytes) throw new Error(`Session exceeds maximum size of ${this.#maxBytes} bytes`);
|
|
15760
|
-
const temporary =
|
|
15904
|
+
const temporary = join23(this.#sessions, `.${document.sessionId}.${process.pid}.${randomUUID13()}.tmp`);
|
|
15761
15905
|
let handle;
|
|
15762
15906
|
try {
|
|
15763
15907
|
handle = await open7(temporary, constants6.O_CREAT | constants6.O_EXCL | constants6.O_WRONLY, 384);
|
|
@@ -15874,7 +16018,7 @@ var SessionStore = class {
|
|
|
15874
16018
|
const metadata = await lstat8(path);
|
|
15875
16019
|
if (!metadata.isFile()) throw new Error("Session path is not a regular file");
|
|
15876
16020
|
await rm8(path);
|
|
15877
|
-
await rm8(
|
|
16021
|
+
await rm8(join23(this.#sessions, `${sessionId}.events.jsonl`), { force: true }).catch(() => void 0);
|
|
15878
16022
|
} catch (error) {
|
|
15879
16023
|
if (!isCode8(error, "ENOENT")) throw error;
|
|
15880
16024
|
}
|
|
@@ -15891,7 +16035,7 @@ var SessionStore = class {
|
|
|
15891
16035
|
const excess = entries.slice(this.#maxSessions);
|
|
15892
16036
|
for (const entry2 of excess) {
|
|
15893
16037
|
await rm8(this.#path(entry2.sessionId), { force: true }).catch(() => void 0);
|
|
15894
|
-
await rm8(
|
|
16038
|
+
await rm8(join23(this.#sessions, `${entry2.sessionId}.events.jsonl`), { force: true }).catch(() => void 0);
|
|
15895
16039
|
}
|
|
15896
16040
|
}
|
|
15897
16041
|
async #assertWorkspace(stored) {
|
|
@@ -15913,7 +16057,7 @@ var SessionStore = class {
|
|
|
15913
16057
|
if (!isWithin5(canonicalRoot, canonicalSessions)) throw new Error("Session directory escapes the workspace");
|
|
15914
16058
|
}
|
|
15915
16059
|
async #quarantine(path) {
|
|
15916
|
-
const target = `${path}.corrupt-${Date.now()}-${
|
|
16060
|
+
const target = `${path}.corrupt-${Date.now()}-${randomUUID13().slice(0, 8)}`;
|
|
15917
16061
|
await rename6(path, target);
|
|
15918
16062
|
return target;
|
|
15919
16063
|
}
|
|
@@ -16132,15 +16276,15 @@ function sanitize2(input, seen = /* @__PURE__ */ new WeakSet()) {
|
|
|
16132
16276
|
}
|
|
16133
16277
|
|
|
16134
16278
|
// src/session/tree.ts
|
|
16135
|
-
import { randomUUID as
|
|
16279
|
+
import { randomUUID as randomUUID15 } from "crypto";
|
|
16136
16280
|
import { mkdir as mkdir17, readFile as readFile19, rename as rename8, writeFile as writeFile11 } from "fs/promises";
|
|
16137
|
-
import { dirname as dirname17, join as
|
|
16281
|
+
import { dirname as dirname17, join as join25, resolve as resolve21 } from "path";
|
|
16138
16282
|
|
|
16139
16283
|
// src/session/checkpoint.ts
|
|
16140
|
-
import { createHash as
|
|
16284
|
+
import { createHash as createHash15, randomUUID as randomUUID14 } from "crypto";
|
|
16141
16285
|
import { execFile as execFile2 } from "child_process";
|
|
16142
16286
|
import { lstat as lstat9, mkdir as mkdir16, open as open8, readFile as readFile18, readdir as readdir7, rename as rename7, rm as rm9, writeFile as writeFile10 } from "fs/promises";
|
|
16143
|
-
import { dirname as dirname16, join as
|
|
16287
|
+
import { dirname as dirname16, join as join24, relative as relative14, resolve as resolve20, sep as sep14 } from "path";
|
|
16144
16288
|
var EXCLUDED = /* @__PURE__ */ new Set([".git", ".flavor", "node_modules", "dist", "build", "release", ".worktrees"]);
|
|
16145
16289
|
var MAX_FILE_BYTES = 16 * 1024 * 1024;
|
|
16146
16290
|
var MAX_TOTAL_BYTES = 128 * 1024 * 1024;
|
|
@@ -16153,9 +16297,9 @@ var WorkspaceCheckpointStore = class {
|
|
|
16153
16297
|
#maxTotalBytes;
|
|
16154
16298
|
constructor(options) {
|
|
16155
16299
|
this.#workspace = resolve20(options.workspace);
|
|
16156
|
-
this.#root =
|
|
16157
|
-
this.#objects =
|
|
16158
|
-
this.#manifests =
|
|
16300
|
+
this.#root = join24(this.#workspace, ".flavor", "checkpoints");
|
|
16301
|
+
this.#objects = join24(this.#root, "objects");
|
|
16302
|
+
this.#manifests = join24(this.#root, "manifests");
|
|
16159
16303
|
this.#maxFileBytes = options.maxFileBytes ?? MAX_FILE_BYTES;
|
|
16160
16304
|
this.#maxTotalBytes = options.maxTotalBytes ?? MAX_TOTAL_BYTES;
|
|
16161
16305
|
}
|
|
@@ -16174,8 +16318,8 @@ var WorkspaceCheckpointStore = class {
|
|
|
16174
16318
|
total += info.size;
|
|
16175
16319
|
if (total > this.#maxTotalBytes) throw new Error("Checkpoint exceeds total size limit");
|
|
16176
16320
|
const content = await readFile18(absolute);
|
|
16177
|
-
const digest =
|
|
16178
|
-
const objectPath =
|
|
16321
|
+
const digest = createHash15("sha256").update(content).digest("hex");
|
|
16322
|
+
const objectPath = join24(this.#objects, digest);
|
|
16179
16323
|
try {
|
|
16180
16324
|
const handle = await open8(objectPath, "wx");
|
|
16181
16325
|
try {
|
|
@@ -16190,17 +16334,17 @@ var WorkspaceCheckpointStore = class {
|
|
|
16190
16334
|
}
|
|
16191
16335
|
const checkpoint = {
|
|
16192
16336
|
version: 1,
|
|
16193
|
-
id: `checkpoint-${Date.now()}-${
|
|
16337
|
+
id: `checkpoint-${Date.now()}-${randomUUID14().slice(0, 8)}`,
|
|
16194
16338
|
createdAt: (/* @__PURE__ */ new Date()).toISOString(),
|
|
16195
16339
|
...label === void 0 || label.trim().length === 0 ? {} : { label: label.trim() },
|
|
16196
16340
|
files
|
|
16197
16341
|
};
|
|
16198
|
-
await atomicJson(
|
|
16342
|
+
await atomicJson(join24(this.#manifests, `${checkpoint.id}.json`), checkpoint);
|
|
16199
16343
|
return checkpoint;
|
|
16200
16344
|
}
|
|
16201
16345
|
async load(id) {
|
|
16202
16346
|
if (!/^checkpoint-[A-Za-z0-9-]+$/.test(id)) throw new Error("Invalid checkpoint id");
|
|
16203
|
-
const value = JSON.parse(await readFile18(
|
|
16347
|
+
const value = JSON.parse(await readFile18(join24(this.#manifests, `${id}.json`), "utf8"));
|
|
16204
16348
|
if (value.version !== 1 || value.id !== id || !Array.isArray(value.files)) throw new Error("Invalid checkpoint manifest");
|
|
16205
16349
|
for (const file of value.files) {
|
|
16206
16350
|
contained(this.#workspace, file.path);
|
|
@@ -16212,8 +16356,8 @@ var WorkspaceCheckpointStore = class {
|
|
|
16212
16356
|
const checkpoint = await this.load(id);
|
|
16213
16357
|
const objects = /* @__PURE__ */ new Map();
|
|
16214
16358
|
for (const file of checkpoint.files) {
|
|
16215
|
-
const object = await readFile18(
|
|
16216
|
-
if (object.length !== file.size ||
|
|
16359
|
+
const object = await readFile18(join24(this.#objects, file.digest));
|
|
16360
|
+
if (object.length !== file.size || createHash15("sha256").update(object).digest("hex") !== file.digest) {
|
|
16217
16361
|
throw new Error(`Checkpoint object is corrupt: ${file.digest}`);
|
|
16218
16362
|
}
|
|
16219
16363
|
objects.set(file.digest, object);
|
|
@@ -16227,7 +16371,7 @@ var WorkspaceCheckpointStore = class {
|
|
|
16227
16371
|
const target = contained(this.#workspace, file.path);
|
|
16228
16372
|
const object = objects.get(file.digest);
|
|
16229
16373
|
await mkdir16(dirname16(target), { recursive: true });
|
|
16230
|
-
const temporary = `${target}.${process.pid}.${
|
|
16374
|
+
const temporary = `${target}.${process.pid}.${randomUUID14()}.tmp`;
|
|
16231
16375
|
await writeFile10(temporary, object, { mode: file.mode });
|
|
16232
16376
|
await rename7(temporary, target);
|
|
16233
16377
|
}
|
|
@@ -16235,7 +16379,7 @@ var WorkspaceCheckpointStore = class {
|
|
|
16235
16379
|
}
|
|
16236
16380
|
};
|
|
16237
16381
|
async function discoverFiles(workspace) {
|
|
16238
|
-
if (await exists2(
|
|
16382
|
+
if (await exists2(join24(workspace, ".git"))) {
|
|
16239
16383
|
const listed = await gitFiles(workspace);
|
|
16240
16384
|
return listed.filter((path) => !EXCLUDED.has(path.split("/")[0] ?? "")).sort();
|
|
16241
16385
|
}
|
|
@@ -16244,7 +16388,7 @@ async function discoverFiles(workspace) {
|
|
|
16244
16388
|
const entries = await readdir7(directory, { withFileTypes: true });
|
|
16245
16389
|
for (const entry2 of entries) {
|
|
16246
16390
|
if (EXCLUDED.has(entry2.name)) continue;
|
|
16247
|
-
const absolute =
|
|
16391
|
+
const absolute = join24(directory, entry2.name);
|
|
16248
16392
|
const path = normalizeRelative(relative14(workspace, absolute));
|
|
16249
16393
|
if (entry2.isSymbolicLink()) throw new Error(`Checkpoint refuses symbolic link: ${path}`);
|
|
16250
16394
|
if (entry2.isDirectory()) await walk(absolute);
|
|
@@ -16299,7 +16443,7 @@ function contained(workspace, path) {
|
|
|
16299
16443
|
}
|
|
16300
16444
|
async function atomicJson(path, value) {
|
|
16301
16445
|
await mkdir16(dirname16(path), { recursive: true });
|
|
16302
|
-
const temporary = `${path}.${process.pid}.${
|
|
16446
|
+
const temporary = `${path}.${process.pid}.${randomUUID14()}.tmp`;
|
|
16303
16447
|
await writeFile10(temporary, `${JSON.stringify(value)}
|
|
16304
16448
|
`, { encoding: "utf8", mode: 384 });
|
|
16305
16449
|
await rename7(temporary, path);
|
|
@@ -16317,14 +16461,14 @@ var SessionHistory = class _SessionHistory {
|
|
|
16317
16461
|
#leafId;
|
|
16318
16462
|
#unrevert;
|
|
16319
16463
|
constructor(options, document) {
|
|
16320
|
-
this.#path =
|
|
16464
|
+
this.#path = join25(resolve21(options.workspace), ".flavor", "session-trees", options.sessionId, "tree.json");
|
|
16321
16465
|
this.#checkpoints = new WorkspaceCheckpointStore({ workspace: options.workspace });
|
|
16322
16466
|
this.#restoreContext = options.restoreContext;
|
|
16323
16467
|
this.#nodes = document.nodes;
|
|
16324
16468
|
this.#leafId = document.leafId;
|
|
16325
16469
|
}
|
|
16326
16470
|
static async open(options) {
|
|
16327
|
-
const path =
|
|
16471
|
+
const path = join25(resolve21(options.workspace), ".flavor", "session-trees", options.sessionId, "tree.json");
|
|
16328
16472
|
let document = { version: 1, sessionId: options.sessionId, leafId: null, nodes: [] };
|
|
16329
16473
|
try {
|
|
16330
16474
|
const parsed = JSON.parse(await readFile19(path, "utf8"));
|
|
@@ -16344,7 +16488,7 @@ var SessionHistory = class _SessionHistory {
|
|
|
16344
16488
|
async append(input) {
|
|
16345
16489
|
const checkpoint = await this.#checkpoints.create(input.label);
|
|
16346
16490
|
const node = {
|
|
16347
|
-
id: `turn-${Date.now()}-${
|
|
16491
|
+
id: `turn-${Date.now()}-${randomUUID15().slice(0, 8)}`,
|
|
16348
16492
|
parentId: this.#leafId,
|
|
16349
16493
|
createdAt: (/* @__PURE__ */ new Date()).toISOString(),
|
|
16350
16494
|
prompt: input.prompt,
|
|
@@ -16402,7 +16546,7 @@ var SessionHistory = class _SessionHistory {
|
|
|
16402
16546
|
nodes: this.#nodes
|
|
16403
16547
|
};
|
|
16404
16548
|
await mkdir17(dirname17(this.#path), { recursive: true });
|
|
16405
|
-
const temporary = `${this.#path}.${process.pid}.${
|
|
16549
|
+
const temporary = `${this.#path}.${process.pid}.${randomUUID15()}.tmp`;
|
|
16406
16550
|
await writeFile11(temporary, `${JSON.stringify(document)}
|
|
16407
16551
|
`, { encoding: "utf8", mode: 384 });
|
|
16408
16552
|
await rename8(temporary, this.#path);
|
|
@@ -16428,9 +16572,9 @@ function isCode10(error, code) {
|
|
|
16428
16572
|
|
|
16429
16573
|
// src/sleep/organizer.ts
|
|
16430
16574
|
import { constants as constants7 } from "fs";
|
|
16431
|
-
import { randomUUID as
|
|
16575
|
+
import { randomUUID as randomUUID16 } from "crypto";
|
|
16432
16576
|
import { lstat as lstat10, mkdir as mkdir18, open as open9, readdir as readdir8, realpath as realpath11, rename as rename9, rm as rm10 } from "fs/promises";
|
|
16433
|
-
import { isAbsolute as isAbsolute10, join as
|
|
16577
|
+
import { isAbsolute as isAbsolute10, join as join26, relative as relative15, resolve as resolve22, sep as sep15 } from "path";
|
|
16434
16578
|
import { z as z28 } from "zod";
|
|
16435
16579
|
var DateKeySchema = z28.string().regex(/^\d{4}-\d{2}-\d{2}$/);
|
|
16436
16580
|
var ReviewSchema = z28.object({
|
|
@@ -16485,7 +16629,7 @@ var ProjectSleepOrganizer = class {
|
|
|
16485
16629
|
#generate;
|
|
16486
16630
|
constructor(options) {
|
|
16487
16631
|
this.#workspace = resolve22(options.workspace);
|
|
16488
|
-
this.#sleepDirectory =
|
|
16632
|
+
this.#sleepDirectory = join26(this.#workspace, ".flavor", "sleep");
|
|
16489
16633
|
this.#sessions = options.sessions ?? new SessionStore({ workspace: this.#workspace });
|
|
16490
16634
|
this.#generate = options.generate;
|
|
16491
16635
|
}
|
|
@@ -16494,7 +16638,7 @@ var ProjectSleepOrganizer = class {
|
|
|
16494
16638
|
signal.throwIfAborted();
|
|
16495
16639
|
await this.#prepareDirectory();
|
|
16496
16640
|
if (await this.#hasReport(date)) return { status: "exists", date };
|
|
16497
|
-
const lockPath =
|
|
16641
|
+
const lockPath = join26(this.#sleepDirectory, `.${date}.lock`);
|
|
16498
16642
|
let lock;
|
|
16499
16643
|
try {
|
|
16500
16644
|
lock = await open9(lockPath, constants7.O_CREAT | constants7.O_EXCL | constants7.O_WRONLY, 384);
|
|
@@ -16529,7 +16673,7 @@ Previous output was invalid. Repair it and return strict JSON only.`;
|
|
|
16529
16673
|
}
|
|
16530
16674
|
if (review === void 0) throw lastError;
|
|
16531
16675
|
const filename2 = `${date}-${filenameSummary(review.title)}.md`;
|
|
16532
|
-
const path =
|
|
16676
|
+
const path = join26(this.#sleepDirectory, filename2);
|
|
16533
16677
|
const markdown = renderSleepReport(date, review, documents);
|
|
16534
16678
|
await this.#atomicWrite(path, markdown);
|
|
16535
16679
|
return { status: "written", date, path, sessionCount: documents.length };
|
|
@@ -16571,7 +16715,7 @@ Previous output was invalid. Repair it and return strict JSON only.`;
|
|
|
16571
16715
|
});
|
|
16572
16716
|
}
|
|
16573
16717
|
async #prepareDirectory() {
|
|
16574
|
-
const flavorDirectory =
|
|
16718
|
+
const flavorDirectory = join26(this.#workspace, ".flavor");
|
|
16575
16719
|
await assertNotSymlink(flavorDirectory);
|
|
16576
16720
|
await mkdir18(flavorDirectory, { recursive: true, mode: 448 });
|
|
16577
16721
|
await assertNotSymlink(this.#sleepDirectory);
|
|
@@ -16585,7 +16729,7 @@ Previous output was invalid. Repair it and return strict JSON only.`;
|
|
|
16585
16729
|
return (await readdir8(this.#sleepDirectory, { withFileTypes: true })).some((entry2) => entry2.isFile() && entry2.name.startsWith(prefix) && entry2.name.endsWith(".md"));
|
|
16586
16730
|
}
|
|
16587
16731
|
async #atomicWrite(path, content) {
|
|
16588
|
-
const temporary =
|
|
16732
|
+
const temporary = join26(this.#sleepDirectory, `.${randomUUID16()}.tmp`);
|
|
16589
16733
|
let handle;
|
|
16590
16734
|
try {
|
|
16591
16735
|
handle = await open9(temporary, constants7.O_CREAT | constants7.O_EXCL | constants7.O_WRONLY, 384);
|
|
@@ -16961,7 +17105,7 @@ function computeSessionStats(documents) {
|
|
|
16961
17105
|
import { constants as constants8 } from "fs";
|
|
16962
17106
|
import { mkdir as mkdir19, open as open10, readFile as readFile20, realpath as realpath12, rename as rename10, rm as rm11, stat as stat8 } from "fs/promises";
|
|
16963
17107
|
import { dirname as dirname18, isAbsolute as isAbsolute11, relative as relative16, resolve as resolve23, sep as sep16 } from "path";
|
|
16964
|
-
import { randomUUID as
|
|
17108
|
+
import { randomUUID as randomUUID17 } from "crypto";
|
|
16965
17109
|
import { z as z29 } from "zod";
|
|
16966
17110
|
|
|
16967
17111
|
// src/tools/file-diff.ts
|
|
@@ -17314,7 +17458,7 @@ function createPathGuard(workspace) {
|
|
|
17314
17458
|
async function atomicWrite(path, content, signal, expectedVersion) {
|
|
17315
17459
|
abortIfNeeded(signal);
|
|
17316
17460
|
await mkdir19(dirname18(path), { recursive: true });
|
|
17317
|
-
const temporary = `${path}.${
|
|
17461
|
+
const temporary = `${path}.${randomUUID17()}.tmp`;
|
|
17318
17462
|
try {
|
|
17319
17463
|
const handle = await open10(temporary, constants8.O_CREAT | constants8.O_EXCL | constants8.O_WRONLY, 384);
|
|
17320
17464
|
try {
|
|
@@ -19511,9 +19655,9 @@ function createTodoWriteTool() {
|
|
|
19511
19655
|
}
|
|
19512
19656
|
|
|
19513
19657
|
// src/tools/managed.ts
|
|
19514
|
-
import { randomUUID as
|
|
19515
|
-
import { link, lstat as lstat11, mkdir as mkdir20, readFile as readFile22, readdir as readdir9, unlink as
|
|
19516
|
-
import { dirname as dirname21, join as
|
|
19658
|
+
import { randomUUID as randomUUID18 } from "crypto";
|
|
19659
|
+
import { link, lstat as lstat11, mkdir as mkdir20, readFile as readFile22, readdir as readdir9, unlink as unlink3, writeFile as writeFile12 } from "fs/promises";
|
|
19660
|
+
import { dirname as dirname21, join as join27, resolve as resolve26 } from "path";
|
|
19517
19661
|
import { z as z37 } from "zod";
|
|
19518
19662
|
var MANAGED_TOOL_VERSION = 1;
|
|
19519
19663
|
var MAX_RECORD_BYTES = 256 * 1024;
|
|
@@ -19556,8 +19700,8 @@ var ManagedToolStore = class {
|
|
|
19556
19700
|
#diagnostics = [];
|
|
19557
19701
|
constructor(options) {
|
|
19558
19702
|
this.#workspace = resolve26(options.workspace);
|
|
19559
|
-
this.#projectRoot =
|
|
19560
|
-
this.#globalRoot =
|
|
19703
|
+
this.#projectRoot = join27(this.#workspace, ".flavor", "tools");
|
|
19704
|
+
this.#globalRoot = join27(resolve26(options.home), ".flavor-code", "tools");
|
|
19561
19705
|
this.#now = options.now ?? (() => (/* @__PURE__ */ new Date()).toISOString());
|
|
19562
19706
|
}
|
|
19563
19707
|
get diagnostics() {
|
|
@@ -19604,7 +19748,7 @@ var ManagedToolStore = class {
|
|
|
19604
19748
|
version: MANAGED_TOOL_VERSION,
|
|
19605
19749
|
createdAt: this.#now()
|
|
19606
19750
|
});
|
|
19607
|
-
const path =
|
|
19751
|
+
const path = join27(this.directory(input.scope), filename(input.name));
|
|
19608
19752
|
const definition = compileDefinition(record3, input.scope, this.#workspace);
|
|
19609
19753
|
await writeExclusive(path, `${JSON.stringify(record3, void 0, 2)}
|
|
19610
19754
|
`);
|
|
@@ -19620,7 +19764,7 @@ var ManagedToolStore = class {
|
|
|
19620
19764
|
const entry2 = matches[0];
|
|
19621
19765
|
const info = await lstat11(entry2.path);
|
|
19622
19766
|
if (!info.isFile() || info.isSymbolicLink()) throw new Error(`Managed tool path is no longer a regular file: ${entry2.path}`);
|
|
19623
|
-
await
|
|
19767
|
+
await unlink3(entry2.path);
|
|
19624
19768
|
this.#entries.delete(scopedKey(entry2.scope, entry2.record.name));
|
|
19625
19769
|
return summary(entry2, false);
|
|
19626
19770
|
}
|
|
@@ -19629,7 +19773,7 @@ var ManagedToolStore = class {
|
|
|
19629
19773
|
if (!parsed.success) return [];
|
|
19630
19774
|
const matches = this.#matching(parsed.data.name).filter((entry2) => parsed.data.scope === void 0 || entry2.scope === parsed.data.scope);
|
|
19631
19775
|
if (matches.length > 0) return matches.map((entry2) => entry2.path);
|
|
19632
|
-
return [
|
|
19776
|
+
return [join27(this.directory(parsed.data.scope ?? "project"), filename(parsed.data.name))];
|
|
19633
19777
|
}
|
|
19634
19778
|
async #loadDirectory(scope2) {
|
|
19635
19779
|
const root = this.directory(scope2);
|
|
@@ -19642,7 +19786,7 @@ var ManagedToolStore = class {
|
|
|
19642
19786
|
return;
|
|
19643
19787
|
}
|
|
19644
19788
|
for (const name of names) {
|
|
19645
|
-
const path =
|
|
19789
|
+
const path = join27(root, name);
|
|
19646
19790
|
try {
|
|
19647
19791
|
const info = await lstat11(path);
|
|
19648
19792
|
if (!info.isFile() || info.isSymbolicLink()) throw new Error("Managed tool record must be a regular file");
|
|
@@ -19798,13 +19942,13 @@ ${sourceUrl}`
|
|
|
19798
19942
|
}
|
|
19799
19943
|
async function writeExclusive(path, content) {
|
|
19800
19944
|
await mkdir20(dirname21(path), { recursive: true });
|
|
19801
|
-
const temporary = `${path}.${
|
|
19945
|
+
const temporary = `${path}.${randomUUID18()}.tmp`;
|
|
19802
19946
|
try {
|
|
19803
19947
|
await writeFile12(temporary, content, { encoding: "utf8", flag: "wx", mode: 384 });
|
|
19804
19948
|
await link(temporary, path);
|
|
19805
19949
|
} finally {
|
|
19806
19950
|
try {
|
|
19807
|
-
await
|
|
19951
|
+
await unlink3(temporary);
|
|
19808
19952
|
} catch {
|
|
19809
19953
|
}
|
|
19810
19954
|
}
|
|
@@ -20752,6 +20896,8 @@ var FlavorSession = class {
|
|
|
20752
20896
|
this.#activeSubmission = submission;
|
|
20753
20897
|
this.#interrupted = false;
|
|
20754
20898
|
let outcome = "completed";
|
|
20899
|
+
let assistantSummary = "";
|
|
20900
|
+
let deliverables = [];
|
|
20755
20901
|
try {
|
|
20756
20902
|
const decision = await this.#services.hooks.emit({
|
|
20757
20903
|
version: 1,
|
|
@@ -20778,6 +20924,8 @@ var FlavorSession = class {
|
|
|
20778
20924
|
...submission.initialUserMessage === void 0 ? {} : { initialUserMessage: submission.initialUserMessage }
|
|
20779
20925
|
})) {
|
|
20780
20926
|
this.#services.output(event);
|
|
20927
|
+
if (event.type === "text") assistantSummary = `${assistantSummary}${event.text}`.slice(-2e3);
|
|
20928
|
+
if (event.type === "deliverables") deliverables = event.files.slice(0, 100);
|
|
20781
20929
|
if (event.type === "error") outcome = "failed";
|
|
20782
20930
|
}
|
|
20783
20931
|
if (controller2.signal.aborted) outcome = "cancelled";
|
|
@@ -20802,7 +20950,15 @@ var FlavorSession = class {
|
|
|
20802
20950
|
this.#activeSubmission = void 0;
|
|
20803
20951
|
if (submission.coWorkPlanningKey !== void 0) this.#finishActivePlanning(submission.coWorkPlanningKey);
|
|
20804
20952
|
try {
|
|
20805
|
-
await this.#services.hooks.emit({
|
|
20953
|
+
await this.#services.hooks.emit({
|
|
20954
|
+
version: 1,
|
|
20955
|
+
type: "Stop",
|
|
20956
|
+
payload: {
|
|
20957
|
+
outcome,
|
|
20958
|
+
...assistantSummary.trim() ? { summary: assistantSummary.trim() } : {},
|
|
20959
|
+
...deliverables.length === 0 ? {} : { deliverables }
|
|
20960
|
+
}
|
|
20961
|
+
});
|
|
20806
20962
|
} catch (error) {
|
|
20807
20963
|
try {
|
|
20808
20964
|
this.#services.output({ type: "error", error: { code: "unknown", message: message(error) } });
|
|
@@ -21535,7 +21691,7 @@ function buildWarningMessages(report, languageTag) {
|
|
|
21535
21691
|
}
|
|
21536
21692
|
|
|
21537
21693
|
// src/hallucination/sliding-window.ts
|
|
21538
|
-
import { createHash as
|
|
21694
|
+
import { createHash as createHash16 } from "crypto";
|
|
21539
21695
|
var SlidingWindow = class {
|
|
21540
21696
|
#windowSize;
|
|
21541
21697
|
#threshold;
|
|
@@ -21551,7 +21707,7 @@ var SlidingWindow = class {
|
|
|
21551
21707
|
}
|
|
21552
21708
|
hash(toolName, params) {
|
|
21553
21709
|
const sorted = JSON.stringify(params, sortedKeys(params));
|
|
21554
|
-
return
|
|
21710
|
+
return createHash16("sha256").update(`${toolName}:${sorted}`).digest("hex");
|
|
21555
21711
|
}
|
|
21556
21712
|
push(toolName, params) {
|
|
21557
21713
|
const hash = this.hash(toolName, params);
|
|
@@ -21906,9 +22062,9 @@ ${total}`;
|
|
|
21906
22062
|
}
|
|
21907
22063
|
|
|
21908
22064
|
// src/memory/store.ts
|
|
21909
|
-
import { createHash as
|
|
22065
|
+
import { createHash as createHash17 } from "crypto";
|
|
21910
22066
|
import { readFile as readFile23, rm as rm12 } from "fs/promises";
|
|
21911
|
-
import { resolve as resolve27, join as
|
|
22067
|
+
import { resolve as resolve27, join as join28, relative as relative19, sep as sep19 } from "path";
|
|
21912
22068
|
|
|
21913
22069
|
// src/memory/similarity.ts
|
|
21914
22070
|
function normalizeForSimilarity(value) {
|
|
@@ -22011,9 +22167,9 @@ var MemoryStore = class {
|
|
|
22011
22167
|
if (!Number.isSafeInteger(options.maxEntries) || options.maxEntries < 1) throw new Error("maxEntries must be an integer of at least 1");
|
|
22012
22168
|
if (!Number.isSafeInteger(options.maxEntryChars) || options.maxEntryChars < 1) throw new Error("maxEntryChars must be an integer of at least 1");
|
|
22013
22169
|
this.workspace = resolve27(options.workspace);
|
|
22014
|
-
this.#memoryRoot =
|
|
22015
|
-
this.path =
|
|
22016
|
-
this.#behaviorPath =
|
|
22170
|
+
this.#memoryRoot = join28(this.workspace, ".flavor", "memory");
|
|
22171
|
+
this.path = join28(this.#memoryRoot, "MEMORY.md");
|
|
22172
|
+
this.#behaviorPath = join28(this.#memoryRoot, "behavior.json");
|
|
22017
22173
|
this.#maxEntries = options.maxEntries;
|
|
22018
22174
|
this.#maxEntryChars = options.maxEntryChars;
|
|
22019
22175
|
}
|
|
@@ -22392,7 +22548,7 @@ function sanitizeTopicKey(value) {
|
|
|
22392
22548
|
return value.normalize("NFKC").toLocaleLowerCase().replace(/[^a-z0-9._-]+/g, "-").replace(/^-+|-+$/g, "").slice(0, 128);
|
|
22393
22549
|
}
|
|
22394
22550
|
function memoryId(type, content) {
|
|
22395
|
-
return
|
|
22551
|
+
return createHash17("sha256").update(`${type}\0${normalizeMemoryContent(content).toLocaleLowerCase()}`, "utf8").digest("hex").slice(0, 12);
|
|
22396
22552
|
}
|
|
22397
22553
|
function assertTaskId(taskId) {
|
|
22398
22554
|
if (!TASK_ID.test(taskId)) throw new Error(`Invalid memory task id: ${taskId}`);
|
|
@@ -22802,7 +22958,7 @@ function createExecutionEnvironment(workspace, config) {
|
|
|
22802
22958
|
|
|
22803
22959
|
// src/ide/client.ts
|
|
22804
22960
|
import { readFile as readFile24, readdir as readdir10, stat as stat10 } from "fs/promises";
|
|
22805
|
-
import { basename as basename7, join as
|
|
22961
|
+
import { basename as basename7, join as join29, relative as relative21, resolve as resolve29, sep as sep21 } from "path";
|
|
22806
22962
|
var FlavorIdeClient = class {
|
|
22807
22963
|
#workspace;
|
|
22808
22964
|
#home;
|
|
@@ -22898,7 +23054,7 @@ var FlavorIdeClient = class {
|
|
|
22898
23054
|
};
|
|
22899
23055
|
if (await this.#healthy(configured)) return configured;
|
|
22900
23056
|
}
|
|
22901
|
-
const directory =
|
|
23057
|
+
const directory = join29(this.#home, ".flavor-code", "ide");
|
|
22902
23058
|
let entries;
|
|
22903
23059
|
try {
|
|
22904
23060
|
entries = (await readdir10(directory)).filter((name) => name.endsWith(".lock"));
|
|
@@ -22906,7 +23062,7 @@ var FlavorIdeClient = class {
|
|
|
22906
23062
|
return void 0;
|
|
22907
23063
|
}
|
|
22908
23064
|
const candidates = (await Promise.all(entries.map(async (name) => {
|
|
22909
|
-
const path =
|
|
23065
|
+
const path = join29(directory, name);
|
|
22910
23066
|
try {
|
|
22911
23067
|
const [contents, info] = await Promise.all([readFile24(path, "utf8"), stat10(path)]);
|
|
22912
23068
|
const lock = parseLockfile(contents);
|
|
@@ -23026,7 +23182,7 @@ function relativeDisplay(workspace, filePath) {
|
|
|
23026
23182
|
}
|
|
23027
23183
|
|
|
23028
23184
|
// src/jobs/registry.ts
|
|
23029
|
-
import { randomUUID as
|
|
23185
|
+
import { randomUUID as randomUUID19 } from "crypto";
|
|
23030
23186
|
var JobRegistry = class {
|
|
23031
23187
|
#jobs = /* @__PURE__ */ new Map();
|
|
23032
23188
|
#maxOutputChars;
|
|
@@ -23040,7 +23196,7 @@ var JobRegistry = class {
|
|
|
23040
23196
|
}
|
|
23041
23197
|
create(input) {
|
|
23042
23198
|
this.#pruneCompleted();
|
|
23043
|
-
const id = `job-${
|
|
23199
|
+
const id = `job-${randomUUID19().slice(0, 12)}`;
|
|
23044
23200
|
const now = (/* @__PURE__ */ new Date()).toISOString();
|
|
23045
23201
|
let settle;
|
|
23046
23202
|
const done = new Promise((resolve41) => {
|
|
@@ -23166,7 +23322,7 @@ function snapshot2(job) {
|
|
|
23166
23322
|
}
|
|
23167
23323
|
|
|
23168
23324
|
// src/terminal/service.ts
|
|
23169
|
-
import { randomUUID as
|
|
23325
|
+
import { randomUUID as randomUUID20 } from "crypto";
|
|
23170
23326
|
import { createRequire as nodeCreateRequire } from "module";
|
|
23171
23327
|
import { isAbsolute as isAbsolute14, relative as relative22, resolve as resolve30, sep as sep22 } from "path";
|
|
23172
23328
|
var TerminalService = class {
|
|
@@ -23184,7 +23340,7 @@ var TerminalService = class {
|
|
|
23184
23340
|
open(input) {
|
|
23185
23341
|
const cwd = workspacePath(this.#root, input.cwd ?? ".");
|
|
23186
23342
|
const shell2 = input.shell ?? defaultShell();
|
|
23187
|
-
const id = `term-${
|
|
23343
|
+
const id = `term-${randomUUID20().slice(0, 12)}`;
|
|
23188
23344
|
const pty = this.#factory(shell2, input.args ?? [], {
|
|
23189
23345
|
cwd,
|
|
23190
23346
|
cols: input.columns ?? 100,
|
|
@@ -23394,10 +23550,10 @@ function createTerminalTools(terminals, workspace = process.cwd()) {
|
|
|
23394
23550
|
}
|
|
23395
23551
|
|
|
23396
23552
|
// src/pals/address.ts
|
|
23397
|
-
import { createHash as
|
|
23553
|
+
import { createHash as createHash18 } from "crypto";
|
|
23398
23554
|
function opaqueUserScope(userScope) {
|
|
23399
23555
|
if (userScope.length === 0) throw new Error("User scope must not be empty");
|
|
23400
|
-
const digest =
|
|
23556
|
+
const digest = createHash18("sha256").update(userScope, "utf8").digest("hex").slice(0, 16);
|
|
23401
23557
|
return `u-${digest}`;
|
|
23402
23558
|
}
|
|
23403
23559
|
function palSocketAddress(options) {
|
|
@@ -23410,32 +23566,32 @@ function palSocketAddress(options) {
|
|
|
23410
23566
|
}
|
|
23411
23567
|
|
|
23412
23568
|
// src/pals/broker-cli.ts
|
|
23413
|
-
import { createHash as
|
|
23569
|
+
import { createHash as createHash20 } from "crypto";
|
|
23414
23570
|
import { Socket as Socket2 } from "net";
|
|
23415
|
-
import { tmpdir as
|
|
23416
|
-
import { join as
|
|
23571
|
+
import { tmpdir as tmpdir3 } from "os";
|
|
23572
|
+
import { join as join31 } from "path";
|
|
23417
23573
|
import { spawn as spawn7 } from "child_process";
|
|
23418
23574
|
|
|
23419
23575
|
// src/pals/broker.ts
|
|
23420
|
-
import { createHash as
|
|
23421
|
-
import { chmod as
|
|
23422
|
-
import { createServer as
|
|
23576
|
+
import { createHash as createHash19, randomUUID as randomUUID23, timingSafeEqual } from "crypto";
|
|
23577
|
+
import { chmod as chmod3, lstat as lstat12, mkdir as mkdir23, unlink as unlink6 } from "fs/promises";
|
|
23578
|
+
import { createServer as createServer4, Socket } from "net";
|
|
23423
23579
|
import { dirname as dirname23 } from "path";
|
|
23424
23580
|
|
|
23425
23581
|
// src/pals/auth.ts
|
|
23426
|
-
import { randomBytes as randomBytes3, randomUUID as
|
|
23427
|
-
import { chmod, link as link2, mkdir as mkdir21, open as open12, readFile as readFile25, unlink as
|
|
23582
|
+
import { randomBytes as randomBytes3, randomUUID as randomUUID21 } from "crypto";
|
|
23583
|
+
import { chmod as chmod2, link as link2, mkdir as mkdir21, open as open12, readFile as readFile25, unlink as unlink4 } from "fs/promises";
|
|
23428
23584
|
import { homedir } from "os";
|
|
23429
|
-
import { join as
|
|
23585
|
+
import { join as join30 } from "path";
|
|
23430
23586
|
var PAL_AUTH_TOKEN_BYTES = 32;
|
|
23431
23587
|
var TOKEN_PATTERN = /^[a-f0-9]{64}$/;
|
|
23432
23588
|
async function loadOrCreatePalAuthToken(options = {}) {
|
|
23433
|
-
const directory =
|
|
23434
|
-
const tokenPath =
|
|
23589
|
+
const directory = join30(options.home ?? homedir(), ".flavor-code", "pals");
|
|
23590
|
+
const tokenPath = join30(directory, "auth-token");
|
|
23435
23591
|
await mkdir21(directory, { recursive: true, mode: 448 });
|
|
23436
|
-
await
|
|
23592
|
+
await chmod2(directory, 448);
|
|
23437
23593
|
const token = (options.random ?? randomBytes3)(PAL_AUTH_TOKEN_BYTES).toString("hex");
|
|
23438
|
-
const temporaryPath =
|
|
23594
|
+
const temporaryPath = join30(directory, `.auth-token-${process.pid}-${randomUUID21()}.tmp`);
|
|
23439
23595
|
let handle;
|
|
23440
23596
|
try {
|
|
23441
23597
|
handle = await open12(temporaryPath, "wx", 384);
|
|
@@ -23454,13 +23610,13 @@ async function loadOrCreatePalAuthToken(options = {}) {
|
|
|
23454
23610
|
}
|
|
23455
23611
|
} finally {
|
|
23456
23612
|
await handle?.close();
|
|
23457
|
-
await
|
|
23613
|
+
await unlink4(temporaryPath).catch((error) => {
|
|
23458
23614
|
if (error.code !== "ENOENT") throw error;
|
|
23459
23615
|
});
|
|
23460
23616
|
}
|
|
23461
23617
|
const stored = (await readFile25(tokenPath, "utf8")).trim();
|
|
23462
23618
|
if (!TOKEN_PATTERN.test(stored)) throw new Error("Pals authentication token store is invalid");
|
|
23463
|
-
await
|
|
23619
|
+
await chmod2(tokenPath, 384);
|
|
23464
23620
|
return stored;
|
|
23465
23621
|
}
|
|
23466
23622
|
function validatePalAuthToken(token) {
|
|
@@ -23469,8 +23625,8 @@ function validatePalAuthToken(token) {
|
|
|
23469
23625
|
}
|
|
23470
23626
|
|
|
23471
23627
|
// src/pals/lifecycle.ts
|
|
23472
|
-
import { randomUUID as
|
|
23473
|
-
import { mkdir as mkdir22, open as open13, readFile as readFile26, rename as rename11, stat as stat11, unlink as
|
|
23628
|
+
import { randomUUID as randomUUID22 } from "crypto";
|
|
23629
|
+
import { mkdir as mkdir22, open as open13, readFile as readFile26, rename as rename11, stat as stat11, unlink as unlink5 } from "fs/promises";
|
|
23474
23630
|
import { dirname as dirname22 } from "path";
|
|
23475
23631
|
function defaultProcessAlive(pid) {
|
|
23476
23632
|
if (!Number.isSafeInteger(pid) || pid <= 0) return true;
|
|
@@ -23495,7 +23651,7 @@ async function acquirePalFileLock(options) {
|
|
|
23495
23651
|
await mkdir22(dirname22(options.path), { recursive: true, mode: 448 });
|
|
23496
23652
|
const pid = options.pid ?? process.pid;
|
|
23497
23653
|
const now = options.now ?? Date.now;
|
|
23498
|
-
const nonce =
|
|
23654
|
+
const nonce = randomUUID22();
|
|
23499
23655
|
const metadata = { pid, createdAt: now(), nonce };
|
|
23500
23656
|
let handle;
|
|
23501
23657
|
try {
|
|
@@ -23520,7 +23676,7 @@ async function acquirePalFileLock(options) {
|
|
|
23520
23676
|
if (openError.code !== "EEXIST") throw openError;
|
|
23521
23677
|
return void 0;
|
|
23522
23678
|
} finally {
|
|
23523
|
-
await
|
|
23679
|
+
await unlink5(tombstone).catch((unlinkError) => {
|
|
23524
23680
|
if (unlinkError.code !== "ENOENT") throw unlinkError;
|
|
23525
23681
|
});
|
|
23526
23682
|
}
|
|
@@ -23534,7 +23690,7 @@ async function acquirePalFileLock(options) {
|
|
|
23534
23690
|
const release = async () => {
|
|
23535
23691
|
const current = parseMetadata(await readFile26(options.path, "utf8").catch(() => ""));
|
|
23536
23692
|
if (current?.nonce !== nonce) return;
|
|
23537
|
-
await
|
|
23693
|
+
await unlink5(options.path).catch((error) => {
|
|
23538
23694
|
if (error.code !== "ENOENT") throw error;
|
|
23539
23695
|
});
|
|
23540
23696
|
};
|
|
@@ -23564,8 +23720,8 @@ function canConnect(address) {
|
|
|
23564
23720
|
});
|
|
23565
23721
|
}
|
|
23566
23722
|
function startupLockPath(address) {
|
|
23567
|
-
const digest =
|
|
23568
|
-
return
|
|
23723
|
+
const digest = createHash20("sha256").update(address, "utf8").digest("hex").slice(0, 24);
|
|
23724
|
+
return join31(tmpdir3(), `flavor-code-pals-${digest}.startup.lock`);
|
|
23569
23725
|
}
|
|
23570
23726
|
async function spawnProductionBroker(address) {
|
|
23571
23727
|
const entry2 = process.argv[1];
|
|
@@ -23614,7 +23770,7 @@ async function ensureOnce(options) {
|
|
|
23614
23770
|
}
|
|
23615
23771
|
|
|
23616
23772
|
// src/pals/client.ts
|
|
23617
|
-
import { randomUUID as
|
|
23773
|
+
import { randomUUID as randomUUID24 } from "crypto";
|
|
23618
23774
|
import { Socket as Socket3 } from "net";
|
|
23619
23775
|
var DEFAULT_REQUEST_TIMEOUT_MS = 5e3;
|
|
23620
23776
|
var DEFAULT_HEARTBEAT_INTERVAL_MS = 1e4;
|
|
@@ -23690,7 +23846,7 @@ var PalClient = class {
|
|
|
23690
23846
|
return this.starting;
|
|
23691
23847
|
}
|
|
23692
23848
|
async list() {
|
|
23693
|
-
const data2 = await this.request({ version: 1, type: "list", requestId:
|
|
23849
|
+
const data2 = await this.request({ version: 1, type: "list", requestId: randomUUID24() });
|
|
23694
23850
|
return PalPresenceListSchema.parse(data2);
|
|
23695
23851
|
}
|
|
23696
23852
|
async rename(alias) {
|
|
@@ -23703,38 +23859,38 @@ var PalClient = class {
|
|
|
23703
23859
|
throw error;
|
|
23704
23860
|
}
|
|
23705
23861
|
}
|
|
23706
|
-
async sendTask(target, message2, messageId =
|
|
23707
|
-
const data2 = await this.request({ version: 1, type: "task", requestId:
|
|
23862
|
+
async sendTask(target, message2, messageId = randomUUID24()) {
|
|
23863
|
+
const data2 = await this.request({ version: 1, type: "task", requestId: randomUUID24(), messageId, target, goal: message2 });
|
|
23708
23864
|
return DeliveryReceiptSchema.parse(data2);
|
|
23709
23865
|
}
|
|
23710
|
-
async sendChat(target, message2, messageId =
|
|
23711
|
-
const data2 = await this.request({ version: 1, type: "chat", requestId:
|
|
23866
|
+
async sendChat(target, message2, messageId = randomUUID24()) {
|
|
23867
|
+
const data2 = await this.request({ version: 1, type: "chat", requestId: randomUUID24(), messageId, target, message: message2 });
|
|
23712
23868
|
return DeliveryReceiptSchema.parse(data2);
|
|
23713
23869
|
}
|
|
23714
23870
|
async startCoWork(input) {
|
|
23715
23871
|
const data2 = await this.request({
|
|
23716
23872
|
version: 1,
|
|
23717
23873
|
type: "cowork-propose",
|
|
23718
|
-
requestId:
|
|
23719
|
-
coWorkId: input.coWorkId ??
|
|
23874
|
+
requestId: randomUUID24(),
|
|
23875
|
+
coWorkId: input.coWorkId ?? randomUUID24(),
|
|
23720
23876
|
goal: input.goal,
|
|
23721
23877
|
participants: input.participants
|
|
23722
23878
|
});
|
|
23723
23879
|
return CoWorkSnapshotSchema.parse(data2);
|
|
23724
23880
|
}
|
|
23725
23881
|
async coWorkAction(action) {
|
|
23726
|
-
const request = { version: 1, requestId:
|
|
23882
|
+
const request = { version: 1, requestId: randomUUID24(), ...action };
|
|
23727
23883
|
return CoWorkSnapshotSchema.parse(await this.request(request));
|
|
23728
23884
|
}
|
|
23729
23885
|
async coWorkStatus(coWorkId) {
|
|
23730
|
-
const data2 = await this.request({ version: 1, type: "cowork-get", requestId:
|
|
23886
|
+
const data2 = await this.request({ version: 1, type: "cowork-get", requestId: randomUUID24(), coWorkId });
|
|
23731
23887
|
return CoWorkSnapshotSchema.parse(data2);
|
|
23732
23888
|
}
|
|
23733
23889
|
async integrateCoWork(input) {
|
|
23734
23890
|
return this.coWorkAction({ type: "cowork-integration", ...input });
|
|
23735
23891
|
}
|
|
23736
23892
|
async cancelCoWork(coWorkId, reason) {
|
|
23737
|
-
const data2 = await this.request({ version: 1, type: "cowork-cancel", requestId:
|
|
23893
|
+
const data2 = await this.request({ version: 1, type: "cowork-cancel", requestId: randomUUID24(), coWorkId, reason });
|
|
23738
23894
|
return CoWorkSnapshotSchema.parse(data2);
|
|
23739
23895
|
}
|
|
23740
23896
|
subscribe(listener) {
|
|
@@ -23746,7 +23902,7 @@ var PalClient = class {
|
|
|
23746
23902
|
if (this.registered && this.socket !== void 0) {
|
|
23747
23903
|
let disconnectTimer;
|
|
23748
23904
|
await Promise.race([
|
|
23749
|
-
this.request({ version: 1, type: "disconnect", requestId:
|
|
23905
|
+
this.request({ version: 1, type: "disconnect", requestId: randomUUID24() }).catch(() => void 0),
|
|
23750
23906
|
new Promise((resolve41) => {
|
|
23751
23907
|
disconnectTimer = setTimeout(resolve41, Math.min(this.requestTimeoutMs, 250));
|
|
23752
23908
|
})
|
|
@@ -23837,7 +23993,7 @@ var PalClient = class {
|
|
|
23837
23993
|
const data2 = await this.request({
|
|
23838
23994
|
version: 1,
|
|
23839
23995
|
type: "register",
|
|
23840
|
-
requestId:
|
|
23996
|
+
requestId: randomUUID24(),
|
|
23841
23997
|
authToken: this.authToken,
|
|
23842
23998
|
presence: { version: 1, ...this.registration, connectedAt: now, lastSeenAt: now }
|
|
23843
23999
|
});
|
|
@@ -23949,7 +24105,7 @@ var PalClient = class {
|
|
|
23949
24105
|
if (!this.registered) return;
|
|
23950
24106
|
if (this.heartbeatPending) return;
|
|
23951
24107
|
this.heartbeatPending = true;
|
|
23952
|
-
void this.request({ version: 1, type: "heartbeat", requestId:
|
|
24108
|
+
void this.request({ version: 1, type: "heartbeat", requestId: randomUUID24() }).catch(() => void 0).finally(() => {
|
|
23953
24109
|
this.heartbeatPending = false;
|
|
23954
24110
|
});
|
|
23955
24111
|
}, this.heartbeatIntervalMs);
|
|
@@ -24236,7 +24392,7 @@ var ApprovalBridge = class {
|
|
|
24236
24392
|
request(request, signal = new AbortController().signal) {
|
|
24237
24393
|
if (this.#settle !== void 0) return Promise.resolve("deny");
|
|
24238
24394
|
if (signal.aborted) return Promise.resolve("deny");
|
|
24239
|
-
this.#pending = { id:
|
|
24395
|
+
this.#pending = { id: randomUUID25(), ...request };
|
|
24240
24396
|
this.#onChange?.();
|
|
24241
24397
|
return new Promise((resolvePromise) => {
|
|
24242
24398
|
this.#settle = resolvePromise;
|
|
@@ -24278,7 +24434,7 @@ async function createProductionRuntime(options) {
|
|
|
24278
24434
|
maxEntries: config.memory.maxEntries,
|
|
24279
24435
|
maxEntryChars: config.memory.maxEntryChars
|
|
24280
24436
|
}) : void 0;
|
|
24281
|
-
const autoStoredContents =
|
|
24437
|
+
const autoStoredContents = /* @__PURE__ */ new Map();
|
|
24282
24438
|
let memoryBehavior = DEFAULT_MEMORY_BEHAVIOR;
|
|
24283
24439
|
if (memoryStore !== void 0) {
|
|
24284
24440
|
try {
|
|
@@ -24356,11 +24512,17 @@ async function createProductionRuntime(options) {
|
|
|
24356
24512
|
environment.ANTHROPIC_API_KEY
|
|
24357
24513
|
].filter((value) => typeof value === "string" && value.length > 0);
|
|
24358
24514
|
let hookSessionId;
|
|
24515
|
+
let islandControlMetadata;
|
|
24359
24516
|
const hooks = new HookBus({
|
|
24360
24517
|
eventContext: () => ({
|
|
24361
24518
|
protocolVersion: 2,
|
|
24362
24519
|
workspace,
|
|
24363
|
-
...hookSessionId === void 0 ? {} : { sessionId: hookSessionId }
|
|
24520
|
+
...hookSessionId === void 0 ? {} : { sessionId: hookSessionId },
|
|
24521
|
+
...islandControlMetadata === void 0 ? {} : {
|
|
24522
|
+
islandControlEndpoint: islandControlMetadata.endpoint,
|
|
24523
|
+
islandControlToken: islandControlMetadata.token,
|
|
24524
|
+
islandControlCapabilities: islandControlMetadata.capabilities
|
|
24525
|
+
}
|
|
24364
24526
|
})
|
|
24365
24527
|
});
|
|
24366
24528
|
hooks.on(
|
|
@@ -24475,8 +24637,8 @@ async function createProductionRuntime(options) {
|
|
|
24475
24637
|
const registeredProviders = registration.registered;
|
|
24476
24638
|
let effectiveLlm = registration.effectiveLlm;
|
|
24477
24639
|
const pluginHost = new PluginHost({
|
|
24478
|
-
globalPluginDirs: [
|
|
24479
|
-
projectPluginDirs: [
|
|
24640
|
+
globalPluginDirs: [join32(home, ".flavor-code", "plugins")],
|
|
24641
|
+
projectPluginDirs: [join32(workspace, ".flavor", "plugins")],
|
|
24480
24642
|
config,
|
|
24481
24643
|
// Existing project plugins (including astgraph and superharness) use Node.js
|
|
24482
24644
|
// built-ins. The Worker/vm sandbox deliberately blocks those imports, so it
|
|
@@ -24550,6 +24712,7 @@ async function createProductionRuntime(options) {
|
|
|
24550
24712
|
const pendingCollaborationEvents = [];
|
|
24551
24713
|
let collaborationEventPump;
|
|
24552
24714
|
let collaborationEventsOpen = true;
|
|
24715
|
+
let islandControlServer;
|
|
24553
24716
|
const pumpCollaborationEvents = () => {
|
|
24554
24717
|
if (collaborationEventPump !== void 0 || collaborationSession === void 0 || deliverCollaborationEvent === void 0) return;
|
|
24555
24718
|
collaborationEventPump = (async () => {
|
|
@@ -24583,7 +24746,7 @@ async function createProductionRuntime(options) {
|
|
|
24583
24746
|
const address = palSocketAddress({
|
|
24584
24747
|
platform: process.platform,
|
|
24585
24748
|
userScope: home,
|
|
24586
|
-
runtimeDir:
|
|
24749
|
+
runtimeDir: join32(tmpdir4(), `flavor-code-pals-${createHash21("sha256").update(home).digest("hex").slice(0, 16)}`)
|
|
24587
24750
|
});
|
|
24588
24751
|
const baseCollaborationClient = collaboration.client ?? collaboration.createClient?.({
|
|
24589
24752
|
instanceId: collaboration.instanceId,
|
|
@@ -24694,8 +24857,8 @@ async function createProductionRuntime(options) {
|
|
|
24694
24857
|
diagnostics.push(`MCP servers could not start: ${message(error)}`);
|
|
24695
24858
|
});
|
|
24696
24859
|
const skills = new SkillRegistry({
|
|
24697
|
-
globalRoots: [
|
|
24698
|
-
projectRoots: [
|
|
24860
|
+
globalRoots: [join32(home, ".flavor-code", "skills")],
|
|
24861
|
+
projectRoots: [join32(workspace, ".flavor", "skills"), ...pluginSkillRoots],
|
|
24699
24862
|
authorizeResource: async () => true,
|
|
24700
24863
|
disabledNames: config.skills.disabled
|
|
24701
24864
|
});
|
|
@@ -24703,7 +24866,7 @@ async function createProductionRuntime(options) {
|
|
|
24703
24866
|
skillsReady.catch(() => void 0);
|
|
24704
24867
|
tools.push(createSkillTool(skills), createSkillResourceTool(skills));
|
|
24705
24868
|
if (options.extraTools !== void 0) tools.push(...options.extraTools);
|
|
24706
|
-
const flavor = await optionalText2(
|
|
24869
|
+
const flavor = await optionalText2(join32(workspace, "FLAVOR.md"));
|
|
24707
24870
|
const instructionBaseline = await workspaceInstructions.baseline();
|
|
24708
24871
|
let memoryContext;
|
|
24709
24872
|
if (memoryStore !== void 0) {
|
|
@@ -24727,7 +24890,7 @@ async function createProductionRuntime(options) {
|
|
|
24727
24890
|
let taskResults = { ...recovered?.tasks.results ?? {} };
|
|
24728
24891
|
const subagentStartedAt = {};
|
|
24729
24892
|
const subagentElapsedMs = {};
|
|
24730
|
-
let sessionId = recovered?.sessionId ?? `session-${(/* @__PURE__ */ new Date()).toISOString().replace(/[^0-9]/g, "").slice(0, 17)}-${
|
|
24893
|
+
let sessionId = recovered?.sessionId ?? `session-${(/* @__PURE__ */ new Date()).toISOString().replace(/[^0-9]/g, "").slice(0, 17)}-${randomUUID25().slice(0, 8)}`;
|
|
24731
24894
|
hookSessionId = sessionId;
|
|
24732
24895
|
let harnessJournal = new HarnessJournal({ workspace, sessionId });
|
|
24733
24896
|
const harnessRecovery = harnessJournal.recover();
|
|
@@ -25022,7 +25185,7 @@ async function createProductionRuntime(options) {
|
|
|
25022
25185
|
});
|
|
25023
25186
|
const memoryCoordinator = memoryStore !== void 0 ? new MemoryCoordinator({
|
|
25024
25187
|
review: (taskId, candidates) => {
|
|
25025
|
-
memoryReviews.offer(taskId, candidates);
|
|
25188
|
+
if (memoryLifecycle.taskId === taskId) memoryReviews.offer(taskId, candidates);
|
|
25026
25189
|
},
|
|
25027
25190
|
remember: async (taskId, candidates) => {
|
|
25028
25191
|
let stored = 0;
|
|
@@ -25030,7 +25193,9 @@ async function createProductionRuntime(options) {
|
|
|
25030
25193
|
const result = await memoryStore.rememberForTask(taskId, candidate);
|
|
25031
25194
|
if (result.added) {
|
|
25032
25195
|
stored += 1;
|
|
25033
|
-
autoStoredContents.
|
|
25196
|
+
const contents = autoStoredContents.get(taskId) ?? [];
|
|
25197
|
+
contents.push(candidate.content);
|
|
25198
|
+
autoStoredContents.set(taskId, contents);
|
|
25034
25199
|
}
|
|
25035
25200
|
}
|
|
25036
25201
|
if (stored > 0) await refreshMemoryState();
|
|
@@ -25047,34 +25212,85 @@ async function createProductionRuntime(options) {
|
|
|
25047
25212
|
if (memoryCoordinator !== void 0) {
|
|
25048
25213
|
memoryCoordinator.onError = (error) => diagnostics.push(`Long-term memory extraction failed: ${message(error)}`);
|
|
25049
25214
|
}
|
|
25215
|
+
const backgroundMemoryTasks = /* @__PURE__ */ new Map();
|
|
25216
|
+
const memoryTaskKey = (task) => `${task.taskId}:${task.transcriptHash}`;
|
|
25217
|
+
const captureMemoryTask = () => {
|
|
25218
|
+
const messageStart = memoryLifecycle.messageStart ?? 0;
|
|
25219
|
+
const messages = harness.main.context.snapshot().messages.slice(messageStart);
|
|
25220
|
+
return {
|
|
25221
|
+
taskId: memoryLifecycle.taskId ?? sessionId,
|
|
25222
|
+
messageStart,
|
|
25223
|
+
messages,
|
|
25224
|
+
transcriptHash: memoryTranscriptHash(messages)
|
|
25225
|
+
};
|
|
25226
|
+
};
|
|
25227
|
+
const evaluateMemoryTask = async (task) => {
|
|
25228
|
+
autoStoredContents.delete(task.taskId);
|
|
25229
|
+
const finalization = memoryCoordinator === void 0 || !config.memory.autoExtract || options.approvalPolicy === "deny" ? { evaluated: true, candidates: false, stored: 0 } : await memoryCoordinator.finalize(task.taskId, task.messages);
|
|
25230
|
+
if (!finalization.evaluated) {
|
|
25231
|
+
if (memoryLifecycle.taskId === task.taskId && memoryLifecycle.transcriptHash === task.transcriptHash) {
|
|
25232
|
+
memoryLifecycle = { status: "active", taskId: task.taskId, messageStart: task.messageStart };
|
|
25233
|
+
await persist();
|
|
25234
|
+
}
|
|
25235
|
+
return "Long-term-memory evaluation failed; retry /finish after checking diagnostics.";
|
|
25236
|
+
}
|
|
25237
|
+
if (memoryLifecycle.taskId === task.taskId) {
|
|
25238
|
+
memoryLifecycle = {
|
|
25239
|
+
status: "completed",
|
|
25240
|
+
taskId: task.taskId,
|
|
25241
|
+
messageStart: task.messageStart,
|
|
25242
|
+
finalizedAt: (/* @__PURE__ */ new Date()).toISOString(),
|
|
25243
|
+
transcriptHash: task.transcriptHash
|
|
25244
|
+
};
|
|
25245
|
+
await persist();
|
|
25246
|
+
}
|
|
25247
|
+
if (finalization.candidates && finalization.stored > 0) {
|
|
25248
|
+
const storedText = autoStoredContents.get(task.taskId)?.[0] ?? "a high-confidence memory";
|
|
25249
|
+
autoStoredContents.delete(task.taskId);
|
|
25250
|
+
return `Long-term memory updated. Stored high-confidence entry: "${storedText}". Run /forget to remove it if undesired.`;
|
|
25251
|
+
}
|
|
25252
|
+
autoStoredContents.delete(task.taskId);
|
|
25253
|
+
return finalization.candidates ? "Long-term-memory evaluation completed. Review the generated candidates before anything is stored." : "Long-term-memory evaluation completed; no durable candidates passed the threshold.";
|
|
25254
|
+
};
|
|
25050
25255
|
const finalizeMemoryTask = async (manual = false) => {
|
|
25051
|
-
const
|
|
25052
|
-
const
|
|
25053
|
-
|
|
25054
|
-
|
|
25256
|
+
const task = captureMemoryTask();
|
|
25257
|
+
const key = memoryTaskKey(task);
|
|
25258
|
+
if (memoryLifecycle.status === "completed" && memoryLifecycle.transcriptHash === task.transcriptHash) {
|
|
25259
|
+
const backgroundResult = await backgroundMemoryTasks.get(key);
|
|
25260
|
+
if (backgroundResult?.startsWith("Long-term-memory evaluation failed")) return backgroundResult;
|
|
25055
25261
|
return "This conversation segment was already evaluated for long-term memory.";
|
|
25056
25262
|
}
|
|
25057
25263
|
if (!manual && memoryBehavior.autoExtractPaused) {
|
|
25058
25264
|
return "Automatic long-term-memory extraction is paused after repeated dismissals; use /finish, /remember, or an explicit \u201Cremember\u201D request to store memory manually.";
|
|
25059
25265
|
}
|
|
25060
|
-
|
|
25061
|
-
|
|
25062
|
-
|
|
25063
|
-
|
|
25064
|
-
|
|
25266
|
+
return evaluateMemoryTask(task);
|
|
25267
|
+
};
|
|
25268
|
+
const scheduleAutomaticMemoryTask = () => {
|
|
25269
|
+
const task = captureMemoryTask();
|
|
25270
|
+
const key = memoryTaskKey(task);
|
|
25065
25271
|
memoryLifecycle = {
|
|
25066
25272
|
status: "completed",
|
|
25067
|
-
taskId:
|
|
25068
|
-
messageStart:
|
|
25273
|
+
taskId: task.taskId,
|
|
25274
|
+
messageStart: task.messageStart,
|
|
25069
25275
|
finalizedAt: (/* @__PURE__ */ new Date()).toISOString(),
|
|
25070
|
-
transcriptHash
|
|
25276
|
+
transcriptHash: task.transcriptHash
|
|
25071
25277
|
};
|
|
25072
|
-
|
|
25073
|
-
|
|
25074
|
-
|
|
25075
|
-
|
|
25076
|
-
|
|
25077
|
-
|
|
25278
|
+
const operation = evaluateMemoryTask(task).then((result) => {
|
|
25279
|
+
if (result.startsWith("Long-term-memory evaluation failed")) {
|
|
25280
|
+
emitOutput({ type: "notice", message: "Automatic long-term-memory evaluation failed; use /finish to retry after checking diagnostics." });
|
|
25281
|
+
} else if (result.startsWith("Long-term-memory evaluation completed. Review") || result.startsWith("Long-term memory updated.")) {
|
|
25282
|
+
emitOutput({ type: "notice", message: result });
|
|
25283
|
+
}
|
|
25284
|
+
return result;
|
|
25285
|
+
}).finally(() => {
|
|
25286
|
+
if (backgroundMemoryTasks.get(key) === operation) backgroundMemoryTasks.delete(key);
|
|
25287
|
+
});
|
|
25288
|
+
backgroundMemoryTasks.set(key, operation);
|
|
25289
|
+
void operation.catch(() => void 0);
|
|
25290
|
+
};
|
|
25291
|
+
const flushMemoryTasks = async () => {
|
|
25292
|
+
await memoryCoordinator?.flush();
|
|
25293
|
+
await Promise.allSettled([...backgroundMemoryTasks.values()]);
|
|
25078
25294
|
};
|
|
25079
25295
|
let explicitMemoryRequest;
|
|
25080
25296
|
let automaticMemoryTask = false;
|
|
@@ -25114,6 +25330,7 @@ async function createProductionRuntime(options) {
|
|
|
25114
25330
|
explicit.taskId,
|
|
25115
25331
|
harness.main.context.snapshot().messages.slice(explicit.messageStart)
|
|
25116
25332
|
);
|
|
25333
|
+
autoStoredContents.delete(explicit.taskId);
|
|
25117
25334
|
if (!result.evaluated) {
|
|
25118
25335
|
emitOutput({ type: "notice", message: "Explicit long-term-memory request could not be analyzed; nothing was stored." });
|
|
25119
25336
|
} else if (result.stored > 0) {
|
|
@@ -25129,12 +25346,7 @@ async function createProductionRuntime(options) {
|
|
|
25129
25346
|
}
|
|
25130
25347
|
}
|
|
25131
25348
|
if (explicit === void 0 && automatic && event.payload.outcome === "completed" && memoryCoordinator !== void 0 && config.memory.autoExtract && options.approvalPolicy !== "deny" && !memoryBehavior.autoExtractPaused) {
|
|
25132
|
-
|
|
25133
|
-
if (result.startsWith("Long-term-memory evaluation failed")) {
|
|
25134
|
-
emitOutput({ type: "notice", message: "Automatic long-term-memory evaluation failed; use /finish to retry after checking diagnostics." });
|
|
25135
|
-
} else if (result.startsWith("Long-term-memory evaluation completed. Review") || result.startsWith("Long-term memory updated.")) {
|
|
25136
|
-
emitOutput({ type: "notice", message: result });
|
|
25137
|
-
}
|
|
25349
|
+
scheduleAutomaticMemoryTask();
|
|
25138
25350
|
}
|
|
25139
25351
|
if (event.payload.outcome === "cancelled" && (taskPlan !== void 0 || taskGraph !== void 0)) {
|
|
25140
25352
|
interruptedTaskPlanNeedsReassessment = true;
|
|
@@ -25149,14 +25361,9 @@ async function createProductionRuntime(options) {
|
|
|
25149
25361
|
emitOutput({ type: "tasks-cleared" });
|
|
25150
25362
|
await persist();
|
|
25151
25363
|
return { decision: "allow" };
|
|
25152
|
-
}, {
|
|
25153
|
-
// This handler awaits long-term-memory model calls; the 10s default timeout
|
|
25154
|
-
// would abort them mid-flight and surface a TimeoutError through the Stop hook.
|
|
25155
|
-
timeoutMs: 3e5,
|
|
25156
|
-
failurePolicy: "allow"
|
|
25157
|
-
});
|
|
25364
|
+
}, { failurePolicy: "allow" });
|
|
25158
25365
|
hooks.on("SessionEnd", async () => {
|
|
25159
|
-
await
|
|
25366
|
+
await flushMemoryTasks();
|
|
25160
25367
|
await persist();
|
|
25161
25368
|
return { decision: "allow" };
|
|
25162
25369
|
}, { timeoutMs: 3e5, failurePolicy: "allow" });
|
|
@@ -25237,7 +25444,7 @@ async function createProductionRuntime(options) {
|
|
|
25237
25444
|
evolveService.toolDefinition(),
|
|
25238
25445
|
...mcpTools
|
|
25239
25446
|
];
|
|
25240
|
-
const loopFlavor = await optionalText2(
|
|
25447
|
+
const loopFlavor = await optionalText2(join32(input.workspace, "FLAVOR.md"));
|
|
25241
25448
|
const loopInstructionBaseline = await loopInstructions.baseline();
|
|
25242
25449
|
const loopEnvironment = createPromptEnvironment({
|
|
25243
25450
|
now: /* @__PURE__ */ new Date(),
|
|
@@ -25376,7 +25583,7 @@ async function createProductionRuntime(options) {
|
|
|
25376
25583
|
return answers[0] === "Continue" ? "approved" : "rejected";
|
|
25377
25584
|
},
|
|
25378
25585
|
fingerprint: workspaceFingerprint,
|
|
25379
|
-
idFactory: () => `loop-${(/* @__PURE__ */ new Date()).toISOString().replace(/[^0-9]/g, "").slice(0, 17)}-${
|
|
25586
|
+
idFactory: () => `loop-${(/* @__PURE__ */ new Date()).toISOString().replace(/[^0-9]/g, "").slice(0, 17)}-${randomUUID25().slice(0, 8)}`
|
|
25380
25587
|
});
|
|
25381
25588
|
const goalStore = new GoalStore({ workspace });
|
|
25382
25589
|
const goalOrchestrator = new GoalOrchestrator({
|
|
@@ -25395,7 +25602,7 @@ async function createProductionRuntime(options) {
|
|
|
25395
25602
|
executionEnvironment
|
|
25396
25603
|
),
|
|
25397
25604
|
now: () => (/* @__PURE__ */ new Date()).toISOString(),
|
|
25398
|
-
idFactory: () => `goal-${(/* @__PURE__ */ new Date()).toISOString().replace(/[^0-9]/g, "").slice(0, 17)}-${
|
|
25605
|
+
idFactory: () => `goal-${(/* @__PURE__ */ new Date()).toISOString().replace(/[^0-9]/g, "").slice(0, 17)}-${randomUUID25().slice(0, 8)}`,
|
|
25399
25606
|
runWorker: ({ workspace: goalWorkspace, prompt, signal }) => runLoopWorker({ workspace: goalWorkspace, prompt, signal })
|
|
25400
25607
|
});
|
|
25401
25608
|
let sessionHistory = await SessionHistory.open({
|
|
@@ -25666,7 +25873,7 @@ ${summary2}`;
|
|
|
25666
25873
|
for (const key of Object.keys(subagentStartedAt)) delete subagentStartedAt[key];
|
|
25667
25874
|
for (const key of Object.keys(subagentElapsedMs)) delete subagentElapsedMs[key];
|
|
25668
25875
|
const previousIdeSessionId = ideSessionId;
|
|
25669
|
-
sessionId = `session-${(/* @__PURE__ */ new Date()).toISOString().replace(/[^0-9]/g, "").slice(0, 17)}-${
|
|
25876
|
+
sessionId = `session-${(/* @__PURE__ */ new Date()).toISOString().replace(/[^0-9]/g, "").slice(0, 17)}-${randomUUID25().slice(0, 8)}`;
|
|
25670
25877
|
hookSessionId = sessionId;
|
|
25671
25878
|
harnessJournal = new HarnessJournal({ workspace, sessionId });
|
|
25672
25879
|
setUsageSession(sessionId);
|
|
@@ -25767,7 +25974,7 @@ ${renderMemoryDocument(entries)}`;
|
|
|
25767
25974
|
return `Provider "${providerName}" is missing authorizationUrl, tokenUrl, or clientId.`;
|
|
25768
25975
|
}
|
|
25769
25976
|
try {
|
|
25770
|
-
const tokenStore = createFileTokenStore(
|
|
25977
|
+
const tokenStore = createFileTokenStore(join32(home, ".flavor-code", "auth.json"));
|
|
25771
25978
|
const credentialId = oauthCredentialId(oauthConfig.tokenUrl, oauthConfig.clientId);
|
|
25772
25979
|
await migrateLegacyOAuthToken(tokenStore, providerName, credentialId);
|
|
25773
25980
|
const oauth = new OAuthCallbackAuthProvider({
|
|
@@ -25810,7 +26017,7 @@ ${renderMemoryDocument(entries)}`;
|
|
|
25810
26017
|
}
|
|
25811
26018
|
},
|
|
25812
26019
|
async logout() {
|
|
25813
|
-
const tokenStore = createFileTokenStore(
|
|
26020
|
+
const tokenStore = createFileTokenStore(join32(home, ".flavor-code", "auth.json"));
|
|
25814
26021
|
const tokens2 = await tokenStore.load();
|
|
25815
26022
|
const hadCredentials = Object.keys(tokens2).length > 0 || effectiveLlm !== void 0;
|
|
25816
26023
|
if (!hadCredentials) {
|
|
@@ -25867,6 +26074,22 @@ ${renderMemoryDocument(entries)}`;
|
|
|
25867
26074
|
sleepScheduler.start();
|
|
25868
26075
|
}
|
|
25869
26076
|
const session = new FlavorSession(services);
|
|
26077
|
+
if (pluginHost.loadedPlugins.some((plugin) => plugin.name === "flavor-island")) {
|
|
26078
|
+
try {
|
|
26079
|
+
islandControlServer = await createIslandControlServer({
|
|
26080
|
+
sessionId,
|
|
26081
|
+
session,
|
|
26082
|
+
...options.islandControl?.focus === void 0 ? {} : { focus: options.islandControl.focus }
|
|
26083
|
+
});
|
|
26084
|
+
islandControlMetadata = {
|
|
26085
|
+
endpoint: islandControlServer.endpoint,
|
|
26086
|
+
token: islandControlServer.token,
|
|
26087
|
+
capabilities: islandControlServer.capabilities
|
|
26088
|
+
};
|
|
26089
|
+
} catch (error) {
|
|
26090
|
+
diagnostics.push(`Flavor Island control channel unavailable: ${message(error)}`);
|
|
26091
|
+
}
|
|
26092
|
+
}
|
|
25870
26093
|
collaborationSession = session;
|
|
25871
26094
|
pumpCollaborationEvents();
|
|
25872
26095
|
const authorization = {
|
|
@@ -25897,11 +26120,12 @@ ${renderMemoryDocument(entries)}`;
|
|
|
25897
26120
|
if (collaborationEventPump !== void 0) await boundedStep(stepTimeoutMs, diagnostics, "collaboration-event-pump", collaborationEventPump);
|
|
25898
26121
|
mcpDiscarded = true;
|
|
25899
26122
|
if (sleepScheduler !== void 0) await boundedStep(stepTimeoutMs, diagnostics, "sleep-scheduler", sleepScheduler.dispose());
|
|
25900
|
-
if (memoryCoordinator !== void 0) await boundedStep(stepTimeoutMs, diagnostics, "memory-flush",
|
|
26123
|
+
if (memoryCoordinator !== void 0) await boundedStep(stepTimeoutMs, diagnostics, "memory-flush", flushMemoryTasks());
|
|
25901
26124
|
await boundedStep(stepTimeoutMs, diagnostics, "persist", persist());
|
|
25902
26125
|
if (persistTail !== void 0) await boundedStep(stepTimeoutMs, diagnostics, "persist-tail", persistTail);
|
|
25903
26126
|
if (ideSessionId !== void 0) await boundedStep(stepTimeoutMs, diagnostics, "ide-end-session", ide.endSession(ideSessionId));
|
|
25904
26127
|
if (executionEnvironment !== void 0) await boundedStep(stepTimeoutMs, diagnostics, "execution-environment", executionEnvironment.dispose());
|
|
26128
|
+
if (islandControlServer !== void 0) await boundedStep(stepTimeoutMs, diagnostics, "island-control", islandControlServer.close());
|
|
25905
26129
|
terminals.dispose();
|
|
25906
26130
|
await boundedStep(stepTimeoutMs, diagnostics, "jobs", jobs.dispose());
|
|
25907
26131
|
auditLogger.close();
|
|
@@ -25915,6 +26139,7 @@ ${renderMemoryDocument(entries)}`;
|
|
|
25915
26139
|
collaborationEventsOpen = false;
|
|
25916
26140
|
mcpDiscarded = true;
|
|
25917
26141
|
memoryReviews.dispose();
|
|
26142
|
+
await islandControlServer?.close().catch(() => void 0);
|
|
25918
26143
|
try {
|
|
25919
26144
|
const stepTimeoutMs = options.shutdownStepTimeoutMs ?? DEFAULT_SHUTDOWN_STEP_TIMEOUT_MS;
|
|
25920
26145
|
if (ideSessionId !== void 0) await boundedStep(stepTimeoutMs, diagnostics, "ide-end-session", ide.endSession(ideSessionId));
|
|
@@ -26052,7 +26277,7 @@ function terminalProgressState(status) {
|
|
|
26052
26277
|
return "failed";
|
|
26053
26278
|
}
|
|
26054
26279
|
async function workspaceFingerprint(workspace) {
|
|
26055
|
-
const hash =
|
|
26280
|
+
const hash = createHash21("sha256");
|
|
26056
26281
|
const diff = await execFileNoThrow(
|
|
26057
26282
|
"git",
|
|
26058
26283
|
["-C", workspace, "diff", "--no-ext-diff", "--binary", "HEAD"],
|
|
@@ -26287,7 +26512,7 @@ async function registerConfiguredAdapters(providers, registry, environment, diag
|
|
|
26287
26512
|
const configured = { ...providers };
|
|
26288
26513
|
if (configured.openai === void 0 && environment.OPENAI_API_KEY) configured.openai = { type: "openai", apiKey: environment.OPENAI_API_KEY };
|
|
26289
26514
|
if (configured.anthropic === void 0 && environment.ANTHROPIC_API_KEY) configured.anthropic = { type: "anthropic", apiKey: environment.ANTHROPIC_API_KEY };
|
|
26290
|
-
const oauthTokenStore = createFileTokenStore(
|
|
26515
|
+
const oauthTokenStore = createFileTokenStore(join32(home, ".flavor-code", "auth.json"));
|
|
26291
26516
|
const registered = [];
|
|
26292
26517
|
let effectiveLlm;
|
|
26293
26518
|
for (const [name, provider] of Object.entries(configured)) {
|
|
@@ -26706,21 +26931,21 @@ async function* runGoalSession(orchestrator, goal, signal) {
|
|
|
26706
26931
|
}
|
|
26707
26932
|
function memoryTranscriptHash(messages) {
|
|
26708
26933
|
const visible = messages.filter((item) => item.role === "user" || item.role === "assistant").map((item) => `${item.role}\0${modelContentText(item.content).trim()}`).join("\n");
|
|
26709
|
-
return
|
|
26934
|
+
return createHash21("sha256").update(visible, "utf8").digest("hex");
|
|
26710
26935
|
}
|
|
26711
26936
|
function createMemoryTaskId() {
|
|
26712
|
-
return `memory-${
|
|
26937
|
+
return `memory-${randomUUID25()}`;
|
|
26713
26938
|
}
|
|
26714
26939
|
|
|
26715
26940
|
// src/desktop/runtime-controller.ts
|
|
26716
|
-
import { createHash as
|
|
26941
|
+
import { createHash as createHash27 } from "crypto";
|
|
26717
26942
|
import { mkdir as mkdir27, readFile as readFile35, readdir as readdir12, rm as rm15, stat as stat14, writeFile as writeFile17 } from "fs/promises";
|
|
26718
26943
|
import { homedir as homedir4 } from "os";
|
|
26719
|
-
import { basename as basename9, join as
|
|
26944
|
+
import { basename as basename9, join as join42, resolve as resolve35 } from "path";
|
|
26720
26945
|
|
|
26721
26946
|
// src/d2c/workflow.ts
|
|
26722
|
-
import { createHash as
|
|
26723
|
-
import { join as
|
|
26947
|
+
import { createHash as createHash22 } from "crypto";
|
|
26948
|
+
import { join as join33 } from "path";
|
|
26724
26949
|
import { z as z42 } from "zod";
|
|
26725
26950
|
|
|
26726
26951
|
// src/d2c/workflow-shared.ts
|
|
@@ -26834,7 +27059,7 @@ function pageId(report) {
|
|
|
26834
27059
|
return report.page?.id ?? "index";
|
|
26835
27060
|
}
|
|
26836
27061
|
function issueSignature(issue, kind) {
|
|
26837
|
-
return
|
|
27062
|
+
return createHash22("sha256").update(JSON.stringify({ kind, ...issue })).digest("hex");
|
|
26838
27063
|
}
|
|
26839
27064
|
function reviewsFor(report, previous) {
|
|
26840
27065
|
const prior = new Map(previous?.reviews.map((item) => [`${item.pageId}:${item.fingerprint}`, item]) ?? []);
|
|
@@ -26866,7 +27091,7 @@ function reviewsFor(report, previous) {
|
|
|
26866
27091
|
function implementationDirectory(report) {
|
|
26867
27092
|
const source = report.implementation.source;
|
|
26868
27093
|
if (!/^https?:\/\//i.test(source)) return source;
|
|
26869
|
-
return
|
|
27094
|
+
return join33("src", "d2c-output", report.task);
|
|
26870
27095
|
}
|
|
26871
27096
|
function createWorkflow(report, framework) {
|
|
26872
27097
|
const now = (/* @__PURE__ */ new Date()).toISOString();
|
|
@@ -26990,7 +27215,7 @@ function applyQualityIssueDecision(workflow, issueId2, decision) {
|
|
|
26990
27215
|
};
|
|
26991
27216
|
}
|
|
26992
27217
|
function workflowPath(workspace, task) {
|
|
26993
|
-
return
|
|
27218
|
+
return join33(taskDir(workspace, task), "workflow.json");
|
|
26994
27219
|
}
|
|
26995
27220
|
async function readWorkflow(workspace, task) {
|
|
26996
27221
|
return (await readRecoverableFile(workflowPath(workspace, task), decodeWorkflow))?.value;
|
|
@@ -27244,7 +27469,7 @@ function confirmApiMapping(mapping, operationKey, operations) {
|
|
|
27244
27469
|
|
|
27245
27470
|
// src/d2c/modules.ts
|
|
27246
27471
|
import { readFile as readFile28 } from "fs/promises";
|
|
27247
|
-
import { isAbsolute as isAbsolute15, join as
|
|
27472
|
+
import { isAbsolute as isAbsolute15, join as join34, normalize as normalize2, posix, sep as sep24, win32 as win322 } from "path";
|
|
27248
27473
|
import { z as z43 } from "zod";
|
|
27249
27474
|
var ModuleSchema = z43.object({
|
|
27250
27475
|
id: z43.string().regex(/^[A-Za-z0-9][A-Za-z0-9._-]{0,127}$/),
|
|
@@ -27263,13 +27488,13 @@ function safeSource(path) {
|
|
|
27263
27488
|
}
|
|
27264
27489
|
function d2cOutputDirectory(workspace, task) {
|
|
27265
27490
|
if (!D2C_TASK_PATTERN.test(task)) throw new Error(`Invalid D2C task: ${task}`);
|
|
27266
|
-
return
|
|
27491
|
+
return join34(workspace, "src", "d2c-output", task);
|
|
27267
27492
|
}
|
|
27268
27493
|
async function readD2cModules(workspace, task) {
|
|
27269
27494
|
const project = d2cOutputDirectory(workspace, task);
|
|
27270
27495
|
let raw;
|
|
27271
27496
|
try {
|
|
27272
|
-
raw = await readFile28(
|
|
27497
|
+
raw = await readFile28(join34(project, "d2c.modules.json"), "utf8");
|
|
27273
27498
|
} catch {
|
|
27274
27499
|
return [{ id: "page", label: task, sourceFiles: ["src/App.vue", "src/App.jsx"], keywords: [task] }];
|
|
27275
27500
|
}
|
|
@@ -27290,7 +27515,7 @@ async function readD2cModules(workspace, task) {
|
|
|
27290
27515
|
|
|
27291
27516
|
// src/d2c/integration.ts
|
|
27292
27517
|
import { mkdir as mkdir24, readFile as readFile29, writeFile as writeFile13 } from "fs/promises";
|
|
27293
|
-
import { join as
|
|
27518
|
+
import { join as join35 } from "path";
|
|
27294
27519
|
function record2(value) {
|
|
27295
27520
|
return typeof value === "object" && value !== null && !Array.isArray(value) ? value : void 0;
|
|
27296
27521
|
}
|
|
@@ -27426,7 +27651,7 @@ async def reset_acceptance(request: Request):
|
|
|
27426
27651
|
}
|
|
27427
27652
|
async function generateIntegrationArtifacts(projectDir, document, mappings, options = {}) {
|
|
27428
27653
|
if (mappings.some((mapping) => mapping.status === "needs-confirmation")) throw new Error("Confirm every uncertain OpenAPI mapping before generating integration code");
|
|
27429
|
-
const packagePath =
|
|
27654
|
+
const packagePath = join35(projectDir, "package.json");
|
|
27430
27655
|
let pkg;
|
|
27431
27656
|
try {
|
|
27432
27657
|
pkg = JSON.parse(await readFile29(packagePath, "utf8"));
|
|
@@ -27436,9 +27661,9 @@ async function generateIntegrationArtifacts(projectDir, document, mappings, opti
|
|
|
27436
27661
|
const scripts = options.pythonServer ? { ...record2(pkg.scripts) ?? {} } : { ...record2(pkg.scripts) ?? {}, mock: "node mock/server.mjs" };
|
|
27437
27662
|
const dependencies2 = options.pythonServer ? { ...record2(pkg.dependencies) ?? {}, axios: "^1.7.9" } : { ...record2(pkg.dependencies) ?? {}, axios: "^1.7.9", express: "^5.2.1" };
|
|
27438
27663
|
const selected = [...new Set(mappings.map((mapping) => mapping.operationKey))].map((key) => document.operations.find((operation) => operation.id === key)).filter((operation) => operation !== void 0);
|
|
27439
|
-
const apiDir =
|
|
27440
|
-
const mockDir =
|
|
27441
|
-
const serverDir =
|
|
27664
|
+
const apiDir = join35(projectDir, "src", "api");
|
|
27665
|
+
const mockDir = join35(projectDir, "mock");
|
|
27666
|
+
const serverDir = join35(projectDir, "server");
|
|
27442
27667
|
await Promise.all([
|
|
27443
27668
|
mkdir24(apiDir, { recursive: true }),
|
|
27444
27669
|
...options.pythonServer ? [] : [mkdir24(mockDir, { recursive: true })],
|
|
@@ -27448,23 +27673,23 @@ async function generateIntegrationArtifacts(projectDir, document, mappings, opti
|
|
|
27448
27673
|
if (options.pythonServer) files.push("server/main.py", "server/requirements.txt", "server/README.md", "server/flavor-runtime.json");
|
|
27449
27674
|
else files.push("mock/server.mjs");
|
|
27450
27675
|
await Promise.all([
|
|
27451
|
-
writeFile13(
|
|
27676
|
+
writeFile13(join35(apiDir, "http.js"), `import axios from "axios";
|
|
27452
27677
|
|
|
27453
27678
|
export const http = axios.create({ baseURL: import.meta.env.VITE_API_BASE_URL || ${JSON.stringify(document.baseUrl ?? "")}, timeout: 15000 });
|
|
27454
27679
|
|
|
27455
27680
|
http.interceptors.response.use((response) => response, (error) => Promise.reject({ name: "ApiError", status: error.response?.status, message: error.response?.data?.message || error.message, cause: error }));
|
|
27456
27681
|
`),
|
|
27457
|
-
writeFile13(
|
|
27458
|
-
writeFile13(
|
|
27682
|
+
writeFile13(join35(apiDir, "d2c-api.js"), apiModule(selected)),
|
|
27683
|
+
writeFile13(join35(apiDir, "d2c-bindings.json"), `${JSON.stringify({ schema: 1, generatedAt: (/* @__PURE__ */ new Date()).toISOString(), mappings }, null, 2)}
|
|
27459
27684
|
`),
|
|
27460
|
-
...options.pythonServer ? [] : [writeFile13(
|
|
27685
|
+
...options.pythonServer ? [] : [writeFile13(join35(mockDir, "server.mjs"), mockModule({ ...document, operations: selected }))],
|
|
27461
27686
|
writeFile13(packagePath, `${JSON.stringify({ ...pkg, scripts, dependencies: dependencies2 }, null, 2)}
|
|
27462
27687
|
`),
|
|
27463
27688
|
...options.pythonServer ? [
|
|
27464
|
-
writeFile13(
|
|
27465
|
-
writeFile13(
|
|
27466
|
-
writeFile13(
|
|
27467
|
-
writeFile13(
|
|
27689
|
+
writeFile13(join35(serverDir, "main.py"), pythonServerModule({ ...document, operations: selected })),
|
|
27690
|
+
writeFile13(join35(serverDir, "requirements.txt"), "fastapi>=0.116,<1\nuvicorn[standard]>=0.35,<1\nsqlalchemy>=2.0,<3\n"),
|
|
27691
|
+
writeFile13(join35(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"),
|
|
27692
|
+
writeFile13(join35(serverDir, "flavor-runtime.json"), `${JSON.stringify({
|
|
27468
27693
|
schema: 1,
|
|
27469
27694
|
kind: "python-fastapi",
|
|
27470
27695
|
cwd: "server",
|
|
@@ -27484,8 +27709,8 @@ http.interceptors.response.use((response) => response, (error) => Promise.reject
|
|
|
27484
27709
|
// src/d2c/mock-runner.ts
|
|
27485
27710
|
import { spawn as spawn8 } from "child_process";
|
|
27486
27711
|
import { access as access3, readFile as readFile30 } from "fs/promises";
|
|
27487
|
-
import { createServer as
|
|
27488
|
-
import { join as
|
|
27712
|
+
import { createServer as createServer5 } from "net";
|
|
27713
|
+
import { join as join36 } from "path";
|
|
27489
27714
|
async function exists3(path) {
|
|
27490
27715
|
try {
|
|
27491
27716
|
await access3(path);
|
|
@@ -27496,7 +27721,7 @@ async function exists3(path) {
|
|
|
27496
27721
|
}
|
|
27497
27722
|
async function port() {
|
|
27498
27723
|
return new Promise((resolvePromise, rejectPromise) => {
|
|
27499
|
-
const server =
|
|
27724
|
+
const server = createServer5();
|
|
27500
27725
|
server.once("error", rejectPromise);
|
|
27501
27726
|
server.listen(0, "127.0.0.1", () => {
|
|
27502
27727
|
const address = server.address();
|
|
@@ -27593,7 +27818,7 @@ ${output.trim()}` : ""}`);
|
|
|
27593
27818
|
}
|
|
27594
27819
|
}
|
|
27595
27820
|
async function runPythonBackend(projectDir, options) {
|
|
27596
|
-
const serverDir =
|
|
27821
|
+
const serverDir = join36(projectDir, "server");
|
|
27597
27822
|
if (options.installDependencies !== false) await installPython(serverDir, options.signal);
|
|
27598
27823
|
const selectedPort = await port();
|
|
27599
27824
|
const url = `http://127.0.0.1:${selectedPort}`;
|
|
@@ -27639,7 +27864,7 @@ ${managed.output().slice(-4e3)}`);
|
|
|
27639
27864
|
};
|
|
27640
27865
|
}
|
|
27641
27866
|
async function runD2cMockServer(projectDir, options = {}) {
|
|
27642
|
-
const runtimePath =
|
|
27867
|
+
const runtimePath = join36(projectDir, "server", "flavor-runtime.json");
|
|
27643
27868
|
if (await exists3(runtimePath)) {
|
|
27644
27869
|
let runtime;
|
|
27645
27870
|
try {
|
|
@@ -27650,9 +27875,9 @@ async function runD2cMockServer(projectDir, options = {}) {
|
|
|
27650
27875
|
if (runtime.schema !== 1 || runtime.kind !== "python-fastapi") throw new Error("Unsupported E2E backend runtime manifest");
|
|
27651
27876
|
return runPythonBackend(projectDir, options);
|
|
27652
27877
|
}
|
|
27653
|
-
const serverPath =
|
|
27878
|
+
const serverPath = join36(projectDir, "mock", "server.mjs");
|
|
27654
27879
|
if (!await exists3(serverPath)) throw new Error(`D2C mock server is not generated at ${serverPath}`);
|
|
27655
|
-
if (options.installDependencies === false && !await exists3(
|
|
27880
|
+
if (options.installDependencies === false && !await exists3(join36(projectDir, "node_modules", "express", "package.json"))) {
|
|
27656
27881
|
throw new Error("D2C mock server dependencies are not installed");
|
|
27657
27882
|
}
|
|
27658
27883
|
if (options.installDependencies !== false) await install(projectDir, options.signal);
|
|
@@ -27693,16 +27918,16 @@ ${managed.output().slice(-4e3)}`);
|
|
|
27693
27918
|
}
|
|
27694
27919
|
|
|
27695
27920
|
// src/d2c/product.ts
|
|
27696
|
-
import { createHash as
|
|
27697
|
-
import { copyFile as copyFile2, mkdir as mkdir25, readFile as readFile32, rename as rename13, stat as stat12, unlink as
|
|
27698
|
-
import { join as
|
|
27921
|
+
import { createHash as createHash24, randomUUID as randomUUID27 } from "crypto";
|
|
27922
|
+
import { copyFile as copyFile2, mkdir as mkdir25, readFile as readFile32, rename as rename13, stat as stat12, unlink as unlink7, writeFile as writeFile15 } from "fs/promises";
|
|
27923
|
+
import { join as join37 } from "path";
|
|
27699
27924
|
import { z as z44 } from "zod";
|
|
27700
27925
|
|
|
27701
27926
|
// src/e2e/prd-governance.ts
|
|
27702
|
-
import { createHash as
|
|
27927
|
+
import { createHash as createHash23, randomUUID as randomUUID26 } from "crypto";
|
|
27703
27928
|
import { readFile as readFile31, rename as rename12, rm as rm13, writeFile as writeFile14 } from "fs/promises";
|
|
27704
27929
|
function hashPrd(markdown) {
|
|
27705
|
-
return
|
|
27930
|
+
return createHash23("sha256").update(markdown, "utf8").digest("hex");
|
|
27706
27931
|
}
|
|
27707
27932
|
function parsePrdSections(markdown) {
|
|
27708
27933
|
const headings = [...markdown.matchAll(/^(#{2,6})[ \t]+(.+?)[ \t]*$/gm)];
|
|
@@ -27799,7 +28024,7 @@ ${body}
|
|
|
27799
28024
|
|
|
27800
28025
|
`;
|
|
27801
28026
|
const markdown = `${current.slice(0, section.contentStart)}${replacement}${current.slice(section.contentEnd)}`;
|
|
27802
|
-
const temporary = `${path}.${process.pid}.${
|
|
28027
|
+
const temporary = `${path}.${process.pid}.${randomUUID26()}.tmp`;
|
|
27803
28028
|
try {
|
|
27804
28029
|
await writeFile14(temporary, markdown, { encoding: "utf8", flag: "wx", mode: 384 });
|
|
27805
28030
|
await rename12(temporary, path);
|
|
@@ -27813,7 +28038,7 @@ ${body}
|
|
|
27813
28038
|
var RelativePrdPath = "product/prd.md";
|
|
27814
28039
|
var RelativePrototypePath = "product/prototype/index.html";
|
|
27815
28040
|
var RelativeInteractionPath = "product/prototype/interaction-manifest.json";
|
|
27816
|
-
var defaultPlanFileOperations = { rename: rename13, copyFile: copyFile2, unlink:
|
|
28041
|
+
var defaultPlanFileOperations = { rename: rename13, copyFile: copyFile2, unlink: unlink7 };
|
|
27817
28042
|
var planWriteQueues = /* @__PURE__ */ new Map();
|
|
27818
28043
|
var PlanSchema2 = z44.object({
|
|
27819
28044
|
schema: z44.literal(1),
|
|
@@ -27850,13 +28075,13 @@ var PlanSchema2 = z44.object({
|
|
|
27850
28075
|
}).strict();
|
|
27851
28076
|
function d2cProductDirectory(workspace, task) {
|
|
27852
28077
|
if (!D2C_TASK_PATTERN.test(task)) throw new Error("Invalid D2C task name");
|
|
27853
|
-
return
|
|
28078
|
+
return join37(taskDir(workspace, task), "product");
|
|
27854
28079
|
}
|
|
27855
28080
|
function d2cProductPrototypeDirectory(workspace, task) {
|
|
27856
|
-
return
|
|
28081
|
+
return join37(d2cProductDirectory(workspace, task), "prototype");
|
|
27857
28082
|
}
|
|
27858
28083
|
function planPath(workspace, task) {
|
|
27859
|
-
return
|
|
28084
|
+
return join37(taskDir(workspace, task), "product-plan.json");
|
|
27860
28085
|
}
|
|
27861
28086
|
function resolveD2cProductTechnology(requirement, fallbackFramework = "vue") {
|
|
27862
28087
|
const frontendCandidates = [
|
|
@@ -27952,7 +28177,7 @@ async function fileHash(path) {
|
|
|
27952
28177
|
if (error.code === "ENOENT") return void 0;
|
|
27953
28178
|
throw error;
|
|
27954
28179
|
});
|
|
27955
|
-
return buffer === void 0 ? void 0 :
|
|
28180
|
+
return buffer === void 0 ? void 0 : createHash24("sha256").update(buffer).digest("hex");
|
|
27956
28181
|
}
|
|
27957
28182
|
async function validateAndNormalizePrototypeManifest(path) {
|
|
27958
28183
|
const raw = await readFile32(path, "utf8").catch((error) => {
|
|
@@ -27999,7 +28224,7 @@ async function writeD2cProductPlan(workspace, plan, operations = defaultPlanFile
|
|
|
27999
28224
|
const target = planPath(workspace, parsed.task);
|
|
28000
28225
|
await serializePlanWrite(target, async () => {
|
|
28001
28226
|
await mkdir25(taskDir(workspace, parsed.task), { recursive: true });
|
|
28002
|
-
const temporary = `${target}.${process.pid}.${
|
|
28227
|
+
const temporary = `${target}.${process.pid}.${randomUUID27()}.tmp`;
|
|
28003
28228
|
await writeFile15(temporary, `${JSON.stringify(parsed, null, 2)}
|
|
28004
28229
|
`, { encoding: "utf8", mode: 384 });
|
|
28005
28230
|
await replaceD2cProductPlanFile(temporary, target, operations);
|
|
@@ -28039,16 +28264,16 @@ async function readD2cProductPlan(workspace, task) {
|
|
|
28039
28264
|
}
|
|
28040
28265
|
async function discoverD2cProductArtifacts(workspace, plan) {
|
|
28041
28266
|
const product = d2cProductDirectory(workspace, plan.task);
|
|
28042
|
-
const prdUpdatedAt = await fileTimestamp(
|
|
28043
|
-
const prdHash = await fileHash(
|
|
28044
|
-
const prototypeUpdatedAt = await fileTimestamp(
|
|
28045
|
-
const prototypeHash = await fileHash(
|
|
28046
|
-
const expectedManifest =
|
|
28267
|
+
const prdUpdatedAt = await fileTimestamp(join37(product, "prd.md"));
|
|
28268
|
+
const prdHash = await fileHash(join37(product, "prd.md"));
|
|
28269
|
+
const prototypeUpdatedAt = await fileTimestamp(join37(product, "prototype", "index.html"));
|
|
28270
|
+
const prototypeHash = await fileHash(join37(product, "prototype", "index.html"));
|
|
28271
|
+
const expectedManifest = join37(product, "prototype", "interaction-manifest.json");
|
|
28047
28272
|
let manifestUpdatedAt = await fileTimestamp(expectedManifest);
|
|
28048
28273
|
if (manifestUpdatedAt === void 0) {
|
|
28049
|
-
const misplacedManifest =
|
|
28274
|
+
const misplacedManifest = join37(product, "interaction-manifest.json");
|
|
28050
28275
|
if (await fileTimestamp(misplacedManifest) !== void 0) {
|
|
28051
|
-
await mkdir25(
|
|
28276
|
+
await mkdir25(join37(product, "prototype"), { recursive: true });
|
|
28052
28277
|
await copyFile2(misplacedManifest, expectedManifest);
|
|
28053
28278
|
manifestUpdatedAt = await fileTimestamp(expectedManifest);
|
|
28054
28279
|
}
|
|
@@ -28081,8 +28306,8 @@ async function readD2cProductPlanView(workspace, task) {
|
|
|
28081
28306
|
if (stored === void 0) return void 0;
|
|
28082
28307
|
const plan = await discoverD2cProductArtifacts(workspace, stored);
|
|
28083
28308
|
if (plan.revision !== stored.revision) await writeD2cProductPlan(workspace, plan);
|
|
28084
|
-
const prdMarkdown = plan.prd === void 0 ? void 0 : await readFile32(
|
|
28085
|
-
const manifestPath =
|
|
28309
|
+
const prdMarkdown = plan.prd === void 0 ? void 0 : await readFile32(join37(taskDir(workspace, task), plan.prd.path), "utf8");
|
|
28310
|
+
const manifestPath = join37(d2cProductDirectory(workspace, task), "prototype", "interaction-manifest.json");
|
|
28086
28311
|
const validationError = plan.phase === "design-generating" && await fileTimestamp(manifestPath) !== void 0 ? await validateAndNormalizePrototypeManifest(manifestPath) : void 0;
|
|
28087
28312
|
return {
|
|
28088
28313
|
plan,
|
|
@@ -28171,11 +28396,11 @@ ${prdMarkdown.slice(0, 2e4)}`
|
|
|
28171
28396
|
}
|
|
28172
28397
|
|
|
28173
28398
|
// src/e2e/acceptance-baseline.ts
|
|
28174
|
-
import { createHash as
|
|
28399
|
+
import { createHash as createHash25 } from "crypto";
|
|
28175
28400
|
import { readFile as readFile33 } from "fs/promises";
|
|
28176
28401
|
async function fileArtifact(path) {
|
|
28177
28402
|
const content = await readFile33(path);
|
|
28178
|
-
return { path, hash:
|
|
28403
|
+
return { path, hash: createHash25("sha256").update(content).digest("hex"), bytes: content.byteLength };
|
|
28179
28404
|
}
|
|
28180
28405
|
async function verifyAcceptanceArtifact(path, expected, label) {
|
|
28181
28406
|
const current = await fileArtifact(path);
|
|
@@ -28240,8 +28465,8 @@ function createAcceptanceEvidence(coverage, run, now = /* @__PURE__ */ new Date(
|
|
|
28240
28465
|
}
|
|
28241
28466
|
|
|
28242
28467
|
// src/e2e/delivery-run.ts
|
|
28243
|
-
import { createHash as
|
|
28244
|
-
import { join as
|
|
28468
|
+
import { createHash as createHash26 } from "crypto";
|
|
28469
|
+
import { join as join38 } from "path";
|
|
28245
28470
|
import { z as z45 } from "zod";
|
|
28246
28471
|
var DELIVERY_NODE_IDS = ["requirement", "prd", "design", "d2c", "api", "acceptance", "delivery"];
|
|
28247
28472
|
var ArtifactSchema = z45.object({
|
|
@@ -28279,7 +28504,7 @@ var dependencies = {
|
|
|
28279
28504
|
};
|
|
28280
28505
|
function artifactRef(path, content, now = /* @__PURE__ */ new Date()) {
|
|
28281
28506
|
const bytes = typeof content === "string" ? Buffer.from(content, "utf8") : Buffer.from(content);
|
|
28282
|
-
return { path, hash:
|
|
28507
|
+
return { path, hash: createHash26("sha256").update(bytes).digest("hex"), bytes: bytes.byteLength, createdAt: now.toISOString() };
|
|
28283
28508
|
}
|
|
28284
28509
|
function emptyNode(id) {
|
|
28285
28510
|
return { id, status: "pending", attempt: 0, inputs: [], outputs: [] };
|
|
@@ -28358,7 +28583,7 @@ function completeDeliveryNode(run, node, outputs, now = /* @__PURE__ */ new Date
|
|
|
28358
28583
|
return { ...run, nodes, updatedAt: timestamp2 };
|
|
28359
28584
|
}
|
|
28360
28585
|
function deliveryRunPath(workspace, task) {
|
|
28361
|
-
return
|
|
28586
|
+
return join38(taskDir(workspace, task), "delivery-run.json");
|
|
28362
28587
|
}
|
|
28363
28588
|
function decodeDeliveryRun(raw) {
|
|
28364
28589
|
return DeliveryRunSchema.parse(JSON.parse(raw));
|
|
@@ -28401,7 +28626,7 @@ async function updateDeliveryRun(workspace, task, expectedRevision, mutation) {
|
|
|
28401
28626
|
// src/skills/manager.ts
|
|
28402
28627
|
import { lstat as lstat13, mkdir as mkdir26, readFile as readFile34, readdir as readdir11, realpath as realpath13, rm as rm14, writeFile as writeFile16 } from "fs/promises";
|
|
28403
28628
|
import { homedir as homedir3 } from "os";
|
|
28404
|
-
import { isAbsolute as isAbsolute16, join as
|
|
28629
|
+
import { isAbsolute as isAbsolute16, join as join39, relative as relative24, resolve as resolve33, sep as sep25 } from "path";
|
|
28405
28630
|
import { stringify } from "yaml";
|
|
28406
28631
|
var SkillManager = class {
|
|
28407
28632
|
#workspace;
|
|
@@ -28424,7 +28649,7 @@ var SkillManager = class {
|
|
|
28424
28649
|
async create(draft) {
|
|
28425
28650
|
const value = parseDraft(draft);
|
|
28426
28651
|
const projectRoot = this.#projectRoot();
|
|
28427
|
-
const directory =
|
|
28652
|
+
const directory = join39(projectRoot, value.name);
|
|
28428
28653
|
await mkdir26(projectRoot, { recursive: true, mode: 448 });
|
|
28429
28654
|
try {
|
|
28430
28655
|
await mkdir26(directory, { mode: 448 });
|
|
@@ -28433,7 +28658,7 @@ var SkillManager = class {
|
|
|
28433
28658
|
throw error;
|
|
28434
28659
|
}
|
|
28435
28660
|
try {
|
|
28436
|
-
await writeFile16(
|
|
28661
|
+
await writeFile16(join39(directory, "SKILL.md"), renderSkill(value), { encoding: "utf8", flag: "wx", mode: 384 });
|
|
28437
28662
|
await this.#persistEnabled(value.name, true);
|
|
28438
28663
|
return this.get(value.name);
|
|
28439
28664
|
} catch (error) {
|
|
@@ -28451,7 +28676,7 @@ var SkillManager = class {
|
|
|
28451
28676
|
throw error;
|
|
28452
28677
|
});
|
|
28453
28678
|
if (!info.isDirectory() || info.isSymbolicLink()) throw new Error(`Project skill path is not a real directory: ${validName}`);
|
|
28454
|
-
const skillFile =
|
|
28679
|
+
const skillFile = join39(directory, "SKILL.md");
|
|
28455
28680
|
const fileInfo = await lstat13(skillFile);
|
|
28456
28681
|
if (!fileInfo.isFile() || fileInfo.isSymbolicLink()) throw new Error(`Project skill file is not a real file: ${validName}`);
|
|
28457
28682
|
await writeFile16(skillFile, renderSkill(value), { encoding: "utf8", mode: 384 });
|
|
@@ -28483,12 +28708,12 @@ var SkillManager = class {
|
|
|
28483
28708
|
async #registry() {
|
|
28484
28709
|
const loaded = await loadConfig({ cwd: this.#workspace, home: this.#home });
|
|
28485
28710
|
const [globalPluginRoots, projectPluginRoots] = await Promise.all([
|
|
28486
|
-
discoverPluginSkillRoots(
|
|
28487
|
-
discoverPluginSkillRoots(
|
|
28711
|
+
discoverPluginSkillRoots(join39(this.#home, ".flavor-code", "plugins")),
|
|
28712
|
+
discoverPluginSkillRoots(join39(this.#workspace, ".flavor", "plugins"))
|
|
28488
28713
|
]);
|
|
28489
28714
|
return {
|
|
28490
28715
|
registry: new SkillRegistry({
|
|
28491
|
-
globalRoots: [
|
|
28716
|
+
globalRoots: [join39(this.#home, ".flavor-code", "skills"), ...globalPluginRoots],
|
|
28492
28717
|
projectRoots: [this.#projectRoot(), ...projectPluginRoots]
|
|
28493
28718
|
}),
|
|
28494
28719
|
disabled: new Set(loaded.config.skills.disabled)
|
|
@@ -28504,10 +28729,10 @@ var SkillManager = class {
|
|
|
28504
28729
|
};
|
|
28505
28730
|
}
|
|
28506
28731
|
#projectRoot() {
|
|
28507
|
-
return
|
|
28732
|
+
return join39(this.#workspace, ".flavor", "skills");
|
|
28508
28733
|
}
|
|
28509
28734
|
#projectSkillDirectory(name) {
|
|
28510
|
-
return
|
|
28735
|
+
return join39(this.#projectRoot(), name);
|
|
28511
28736
|
}
|
|
28512
28737
|
};
|
|
28513
28738
|
function parseName(value) {
|
|
@@ -28548,9 +28773,9 @@ async function discoverPluginSkillRoots(directory) {
|
|
|
28548
28773
|
const roots = [];
|
|
28549
28774
|
for (const entry2 of entries) {
|
|
28550
28775
|
if (!entry2.isDirectory() || entry2.isSymbolicLink()) continue;
|
|
28551
|
-
const pluginRoot = await realpath13(
|
|
28776
|
+
const pluginRoot = await realpath13(join39(directory, entry2.name));
|
|
28552
28777
|
try {
|
|
28553
|
-
const manifest = PluginManifestSchema.parse(JSON.parse(await readFile34(
|
|
28778
|
+
const manifest = PluginManifestSchema.parse(JSON.parse(await readFile34(join39(pluginRoot, "flavor-plugin.json"), "utf8")));
|
|
28554
28779
|
for (const contribution of manifest.contributes.skillRoots) {
|
|
28555
28780
|
const candidate = resolve33(pluginRoot, contribution.path);
|
|
28556
28781
|
const info = await lstat13(candidate);
|
|
@@ -28625,11 +28850,11 @@ async function createProjectMemoryManager(options) {
|
|
|
28625
28850
|
}
|
|
28626
28851
|
|
|
28627
28852
|
// src/mcp/config-manager.ts
|
|
28628
|
-
import { join as
|
|
28853
|
+
import { join as join40 } from "path";
|
|
28629
28854
|
var ProjectMcpConfigManager = class {
|
|
28630
28855
|
path;
|
|
28631
28856
|
constructor(workspace) {
|
|
28632
|
-
this.path =
|
|
28857
|
+
this.path = join40(workspace, ".flavor", "flavor.json");
|
|
28633
28858
|
}
|
|
28634
28859
|
async list() {
|
|
28635
28860
|
const document = await readRecoverableFile(this.path, (raw) => parseDocument2(this.path, raw));
|
|
@@ -28726,7 +28951,7 @@ function isRecord7(value) {
|
|
|
28726
28951
|
}
|
|
28727
28952
|
|
|
28728
28953
|
// src/desktop/model-config.ts
|
|
28729
|
-
import { join as
|
|
28954
|
+
import { join as join41 } from "path";
|
|
28730
28955
|
var DEFAULT_DESKTOP_MODELS = [
|
|
28731
28956
|
{
|
|
28732
28957
|
id: "anthropic:deepseek-v4-pro",
|
|
@@ -28747,7 +28972,7 @@ var DEFAULT_DESKTOP_MODELS = [
|
|
|
28747
28972
|
];
|
|
28748
28973
|
async function loadDesktopModels(workspace, home) {
|
|
28749
28974
|
try {
|
|
28750
|
-
const tokens2 = await createFileTokenStore(
|
|
28975
|
+
const tokens2 = await createFileTokenStore(join41(home, ".flavor-code", "auth.json")).load();
|
|
28751
28976
|
const pkceModels = [];
|
|
28752
28977
|
const knownPkce = /* @__PURE__ */ new Set();
|
|
28753
28978
|
for (const token of Object.values(tokens2)) {
|
|
@@ -28816,7 +29041,7 @@ function protocolLabel(type) {
|
|
|
28816
29041
|
// src/desktop/d2c-product-preview.ts
|
|
28817
29042
|
import { createReadStream as createReadStream2 } from "fs";
|
|
28818
29043
|
import { realpath as realpath14, stat as stat13 } from "fs/promises";
|
|
28819
|
-
import { createServer as
|
|
29044
|
+
import { createServer as createServer6 } from "http";
|
|
28820
29045
|
import { extname as extname3, isAbsolute as isAbsolute17, relative as relative25, resolve as resolve34, sep as sep26 } from "path";
|
|
28821
29046
|
var MIME_TYPES = {
|
|
28822
29047
|
".css": "text/css; charset=utf-8",
|
|
@@ -28842,7 +29067,7 @@ async function startD2cProductPreview(rootDirectory) {
|
|
|
28842
29067
|
const root = await realpath14(resolve34(rootDirectory));
|
|
28843
29068
|
const rootInfo = await stat13(root);
|
|
28844
29069
|
if (!rootInfo.isDirectory()) throw new Error("D2C product prototype directory is unavailable");
|
|
28845
|
-
const server =
|
|
29070
|
+
const server = createServer6(async (request, response2) => {
|
|
28846
29071
|
response2.setHeader("cache-control", "no-store");
|
|
28847
29072
|
response2.setHeader("x-content-type-options", "nosniff");
|
|
28848
29073
|
response2.setHeader("content-security-policy", "default-src 'self' data: blob:; script-src 'self' 'unsafe-inline'; style-src 'self' 'unsafe-inline'; img-src 'self' data: blob:; font-src 'self' data:; connect-src 'none'; object-src 'none'; base-uri 'none'; form-action 'none'");
|
|
@@ -28892,37 +29117,37 @@ function pngDataUrl(png) {
|
|
|
28892
29117
|
}
|
|
28893
29118
|
async function detectD2cFramework(workspace, task) {
|
|
28894
29119
|
try {
|
|
28895
|
-
const pkg = JSON.parse(await readFile35(
|
|
29120
|
+
const pkg = JSON.parse(await readFile35(join42(d2cOutputDirectory(workspace, task), "package.json"), "utf8"));
|
|
28896
29121
|
return pkg.dependencies?.react === void 0 ? "vue" : "react";
|
|
28897
29122
|
} catch {
|
|
28898
29123
|
return "vue";
|
|
28899
29124
|
}
|
|
28900
29125
|
}
|
|
28901
29126
|
function integrationDirectory(workspace, task) {
|
|
28902
|
-
return
|
|
29127
|
+
return join42(workspace, ".flavor", "d2c", task, "integration");
|
|
28903
29128
|
}
|
|
28904
29129
|
var D2C_BACKEND_SOURCE_EXTENSIONS = /* @__PURE__ */ new Set([".cfg", ".env", ".ini", ".js", ".json", ".lock", ".mjs", ".py", ".toml", ".txt", ".yaml", ".yml"]);
|
|
28905
29130
|
var D2C_BACKEND_IGNORED_DIRECTORIES = /* @__PURE__ */ new Set([".git", ".venv", "__pycache__", "data", "node_modules", "venv"]);
|
|
28906
29131
|
async function d2cBackendSourceFingerprint(project) {
|
|
28907
|
-
const runtimeManifest =
|
|
29132
|
+
const runtimeManifest = join42(project, "server", "flavor-runtime.json");
|
|
28908
29133
|
const realBackend = (await stat14(runtimeManifest).catch(() => void 0))?.isFile() === true;
|
|
28909
|
-
const root = realBackend ?
|
|
29134
|
+
const root = realBackend ? join42(project, "server") : join42(project, "mock");
|
|
28910
29135
|
if ((await stat14(root).catch(() => void 0))?.isDirectory() !== true) return void 0;
|
|
28911
29136
|
const files = [];
|
|
28912
29137
|
const visit = async (directory) => {
|
|
28913
29138
|
const entries = await readdir12(directory, { withFileTypes: true });
|
|
28914
29139
|
for (const entry2 of entries) {
|
|
28915
29140
|
if (entry2.isDirectory()) {
|
|
28916
|
-
if (!D2C_BACKEND_IGNORED_DIRECTORIES.has(entry2.name)) await visit(
|
|
29141
|
+
if (!D2C_BACKEND_IGNORED_DIRECTORIES.has(entry2.name)) await visit(join42(directory, entry2.name));
|
|
28917
29142
|
continue;
|
|
28918
29143
|
}
|
|
28919
29144
|
if (!entry2.isFile()) continue;
|
|
28920
29145
|
const extension2 = entry2.name.includes(".") ? `.${entry2.name.split(".").pop().toLowerCase()}` : "";
|
|
28921
|
-
if (D2C_BACKEND_SOURCE_EXTENSIONS.has(extension2)) files.push(
|
|
29146
|
+
if (D2C_BACKEND_SOURCE_EXTENSIONS.has(extension2)) files.push(join42(directory, entry2.name));
|
|
28922
29147
|
}
|
|
28923
29148
|
};
|
|
28924
29149
|
await visit(root);
|
|
28925
|
-
const hash =
|
|
29150
|
+
const hash = createHash27("sha256");
|
|
28926
29151
|
for (const path of files.sort()) {
|
|
28927
29152
|
hash.update(path.slice(root.length));
|
|
28928
29153
|
hash.update("\0");
|
|
@@ -29468,7 +29693,7 @@ var DesktopRuntimeController = class {
|
|
|
29468
29693
|
if (workflow === void 0 || !reviewProgress(workflow).complete || workflow.stage === "visual-review") {
|
|
29469
29694
|
throw new Error("Complete visual review before importing OpenAPI");
|
|
29470
29695
|
}
|
|
29471
|
-
const moduleManifest =
|
|
29696
|
+
const moduleManifest = join42(d2cOutputDirectory(workspace, task), "d2c.modules.json");
|
|
29472
29697
|
const moduleManifestInfo = await stat14(moduleManifest).catch(() => void 0);
|
|
29473
29698
|
if (moduleManifestInfo === void 0 || !moduleManifestInfo.isFile()) {
|
|
29474
29699
|
throw new Error("D2C implementation must write d2c.modules.json before API mapping; ask the agent to split the project into modules first");
|
|
@@ -29479,10 +29704,10 @@ var DesktopRuntimeController = class {
|
|
|
29479
29704
|
const mappings = generated ? matched.map((mapping) => ({ ...mapping, status: "confirmed" })) : matched;
|
|
29480
29705
|
const dir = integrationDirectory(workspace, task);
|
|
29481
29706
|
await mkdir27(dir, { recursive: true });
|
|
29482
|
-
const hash =
|
|
29707
|
+
const hash = createHash27("sha256").update(raw).digest("hex");
|
|
29483
29708
|
await Promise.all([
|
|
29484
|
-
writeFile17(
|
|
29485
|
-
writeFile17(
|
|
29709
|
+
writeFile17(join42(dir, "swagger.json"), raw),
|
|
29710
|
+
writeFile17(join42(dir, "openapi.normalized.json"), `${JSON.stringify(document, null, 2)}
|
|
29486
29711
|
`)
|
|
29487
29712
|
]);
|
|
29488
29713
|
const next = {
|
|
@@ -29517,7 +29742,7 @@ var DesktopRuntimeController = class {
|
|
|
29517
29742
|
let workflow = await readWorkflow(workspace, task);
|
|
29518
29743
|
if (workflow?.openapi === void 0 && workflow !== void 0 && reviewProgress(workflow).complete && await readD2cProductPlanView(workspace, task) !== void 0) {
|
|
29519
29744
|
const modules = await readD2cModules(workspace, task);
|
|
29520
|
-
const productOpenApiPath =
|
|
29745
|
+
const productOpenApiPath = join42(taskDir(workspace, task), "product", "openapi.json");
|
|
29521
29746
|
const authored = await readFile35(productOpenApiPath, "utf8").catch(() => void 0);
|
|
29522
29747
|
const generated = authored === void 0;
|
|
29523
29748
|
const prepared = await this.#storeD2cOpenApi(
|
|
@@ -29530,7 +29755,7 @@ var DesktopRuntimeController = class {
|
|
|
29530
29755
|
return prepared;
|
|
29531
29756
|
}
|
|
29532
29757
|
if (workflow?.openapi === void 0 || workflow.mappings === void 0) return void 0;
|
|
29533
|
-
const document = JSON.parse(await readFile35(
|
|
29758
|
+
const document = JSON.parse(await readFile35(join42(integrationDirectory(workspace, task), "openapi.normalized.json"), "utf8"));
|
|
29534
29759
|
return { workflow, document, mappings: workflow.mappings };
|
|
29535
29760
|
}
|
|
29536
29761
|
async confirmD2cMapping(task, moduleId, operationKey) {
|
|
@@ -29597,7 +29822,7 @@ var DesktopRuntimeController = class {
|
|
|
29597
29822
|
].join("\n");
|
|
29598
29823
|
const delivery = await readDeliveryRun(workspace, task);
|
|
29599
29824
|
if (delivery?.nodes.api.status === "running") {
|
|
29600
|
-
const apiArtifacts = await Promise.all(generated.files.map(async (file) => artifactRef(file, await readFile35(
|
|
29825
|
+
const apiArtifacts = await Promise.all(generated.files.map(async (file) => artifactRef(file, await readFile35(join42(project, file)))));
|
|
29601
29826
|
const completed = await updateDeliveryRun(
|
|
29602
29827
|
workspace,
|
|
29603
29828
|
task,
|
|
@@ -29624,7 +29849,7 @@ var DesktopRuntimeController = class {
|
|
|
29624
29849
|
try {
|
|
29625
29850
|
const fingerprint2 = await d2cBackendSourceFingerprint(project);
|
|
29626
29851
|
if (fingerprint2 !== void 0) this.#d2cMockSourceFingerprints.set(task, fingerprint2);
|
|
29627
|
-
const environmentPath =
|
|
29852
|
+
const environmentPath = join42(project, ".env.local");
|
|
29628
29853
|
const existingEnvironment = await readFile35(environmentPath, "utf8").catch(() => "");
|
|
29629
29854
|
const environment = `${existingEnvironment.split(/\r?\n/).filter((line) => !/^\s*VITE_API_BASE_URL\s*=/.test(line)).join("\n").trim()}
|
|
29630
29855
|
VITE_API_BASE_URL=${running.url}
|
|
@@ -29705,7 +29930,7 @@ VITE_API_BASE_URL=${running.url}
|
|
|
29705
29930
|
const readyPreview = this.#d2cPreviews.get(task);
|
|
29706
29931
|
const readyMock = this.#d2cMocks.get(task);
|
|
29707
29932
|
if (readyPreview === void 0 || readyMock === void 0) throw new Error("D2C preview or backend service is unavailable after synchronization");
|
|
29708
|
-
const manifestPath =
|
|
29933
|
+
const manifestPath = join42(taskDir(workspace, task), "design", "interaction-manifest.json");
|
|
29709
29934
|
const manifest = parseInteractionManifest(await readFile35(manifestPath, "utf8"));
|
|
29710
29935
|
const baseline = await this.#verifyAcceptanceBaseline(workspace, task);
|
|
29711
29936
|
const coverage = baseline === void 0 ? void 0 : verifyRequirementCoverage(baseline.approvedPrd.criteria, manifest);
|
|
@@ -29717,7 +29942,7 @@ VITE_API_BASE_URL=${running.url}
|
|
|
29717
29942
|
await mkdir27(planDir, { recursive: true });
|
|
29718
29943
|
try {
|
|
29719
29944
|
const observations = await this.#observeD2cPages(manifest, readyPreview.url);
|
|
29720
|
-
const prd = await readFile35(
|
|
29945
|
+
const prd = await readFile35(join42(taskDir(workspace, task), "product", "prd.md"), "utf8").catch(() => void 0);
|
|
29721
29946
|
const apiContext = JSON.stringify({ openapi: workflow.openapi, mappings: workflow.mappings }, null, 2);
|
|
29722
29947
|
const plan = await this.#d2cJudge.planInteractions({ task, seed: manifest, observations, ...prd === void 0 ? {} : { prd }, apiContext });
|
|
29723
29948
|
executableManifest = mergeInteractionManifests(manifest, plan.manifest);
|
|
@@ -29728,9 +29953,9 @@ VITE_API_BASE_URL=${running.url}
|
|
|
29728
29953
|
plannedScenarios: executableManifest.pages.reduce((total, page) => total + page.scenarios.length, 0),
|
|
29729
29954
|
pageAnalyses: plan.pageAnalyses
|
|
29730
29955
|
};
|
|
29731
|
-
await writeFile17(
|
|
29956
|
+
await writeFile17(join42(planDir, "autonomous-interaction-plan.json"), `${JSON.stringify({ ...plan, manifest: executableManifest }, null, 2)}
|
|
29732
29957
|
`, { encoding: "utf8", mode: 384 });
|
|
29733
|
-
await rm15(
|
|
29958
|
+
await rm15(join42(planDir, "autonomous-interaction-diagnostic.json"), { force: true });
|
|
29734
29959
|
} catch (cause) {
|
|
29735
29960
|
const detail = message(cause);
|
|
29736
29961
|
review = {
|
|
@@ -29738,8 +29963,8 @@ VITE_API_BASE_URL=${running.url}
|
|
|
29738
29963
|
plannedScenarios: manifest.pages.reduce((total, page) => total + page.scenarios.length, 0),
|
|
29739
29964
|
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}`
|
|
29740
29965
|
};
|
|
29741
|
-
await rm15(
|
|
29742
|
-
await writeFile17(
|
|
29966
|
+
await rm15(join42(planDir, "autonomous-interaction-plan.json"), { force: true });
|
|
29967
|
+
await writeFile17(join42(planDir, "autonomous-interaction-diagnostic.json"), `${JSON.stringify({
|
|
29743
29968
|
schema: 1,
|
|
29744
29969
|
stage: "planning",
|
|
29745
29970
|
occurredAt: (/* @__PURE__ */ new Date()).toISOString(),
|
|
@@ -29753,13 +29978,13 @@ VITE_API_BASE_URL=${running.url}
|
|
|
29753
29978
|
let result = { ...rawResult, evidenceMode: review.mode };
|
|
29754
29979
|
if (coverage !== void 0) {
|
|
29755
29980
|
const evidence = createAcceptanceEvidence(coverage, result);
|
|
29756
|
-
await writeFile17(
|
|
29981
|
+
await writeFile17(join42(taskDir(workspace, task), "acceptance-evidence.json"), `${JSON.stringify({ ...evidence, baseline }, null, 2)}
|
|
29757
29982
|
`, { encoding: "utf8", mode: 384 });
|
|
29758
29983
|
if (!evidence.passed) result = { ...result, passed: false, failures: Math.max(1, result.failures) };
|
|
29759
29984
|
}
|
|
29760
29985
|
const dir = integrationDirectory(workspace, task);
|
|
29761
29986
|
await mkdir27(dir, { recursive: true });
|
|
29762
|
-
await writeFile17(
|
|
29987
|
+
await writeFile17(join42(dir, "interaction-results.json"), `${JSON.stringify(result, null, 2)}
|
|
29763
29988
|
`);
|
|
29764
29989
|
const next = await updateWorkflow(workspace, task, workflow.revision, (current) => applyInteractionRun(current, result));
|
|
29765
29990
|
return { workflow: next, result, review };
|
|
@@ -29772,7 +29997,7 @@ VITE_API_BASE_URL=${running.url}
|
|
|
29772
29997
|
if (mock === void 0 || !mock.exited() || result.failures === 0) return result;
|
|
29773
29998
|
const output = mock.output().trim();
|
|
29774
29999
|
try {
|
|
29775
|
-
const dir =
|
|
30000
|
+
const dir = join42(d2cOutputDirectory(workspace, task), "mock-server.log");
|
|
29776
30001
|
await writeFile17(dir, `${output}
|
|
29777
30002
|
`);
|
|
29778
30003
|
} catch {
|
|
@@ -29867,7 +30092,7 @@ VITE_API_BASE_URL=${running.url}
|
|
|
29867
30092
|
if (current === void 0) throw new Error("D2C product plan is unavailable");
|
|
29868
30093
|
if (current.plan.approvedPrd !== void 0) throw new Error("The approved PRD is immutable and cannot be edited");
|
|
29869
30094
|
if (current.plan.phase !== "prd-review") throw new Error("PRD sections can only be edited during PRD review");
|
|
29870
|
-
const path =
|
|
30095
|
+
const path = join42(taskDir(workspace, task), "product", "prd.md");
|
|
29871
30096
|
const updated = await updatePrdSectionFile(path, { sectionId, body, expectedHash });
|
|
29872
30097
|
const now = (/* @__PURE__ */ new Date()).toISOString();
|
|
29873
30098
|
await writeD2cProductPlan(workspace, {
|
|
@@ -29887,15 +30112,15 @@ VITE_API_BASE_URL=${running.url}
|
|
|
29887
30112
|
async #assertProductPrdLocked(workspace, task) {
|
|
29888
30113
|
const current = await readD2cProductPlanView(workspace, task);
|
|
29889
30114
|
if (current?.plan.approvedPrd === void 0) return;
|
|
29890
|
-
await assertApprovedPrd(
|
|
30115
|
+
await assertApprovedPrd(join42(taskDir(workspace, task), "product", "prd.md"), current.plan.approvedPrd);
|
|
29891
30116
|
}
|
|
29892
30117
|
async #acceptancePaths(workspace, task) {
|
|
29893
|
-
const product =
|
|
29894
|
-
const openapi =
|
|
30118
|
+
const product = join42(taskDir(workspace, task), "product");
|
|
30119
|
+
const openapi = join42(product, "openapi.json");
|
|
29895
30120
|
return {
|
|
29896
|
-
prd:
|
|
29897
|
-
prototype:
|
|
29898
|
-
interaction:
|
|
30121
|
+
prd: join42(product, "prd.md"),
|
|
30122
|
+
prototype: join42(product, "prototype", "index.html"),
|
|
30123
|
+
interaction: join42(product, "prototype", "interaction-manifest.json"),
|
|
29899
30124
|
...await stat14(openapi).catch(() => void 0) === void 0 ? {} : { openapi }
|
|
29900
30125
|
};
|
|
29901
30126
|
}
|
|
@@ -29903,17 +30128,17 @@ VITE_API_BASE_URL=${running.url}
|
|
|
29903
30128
|
const product = await readD2cProductPlanView(workspace, task);
|
|
29904
30129
|
if (product?.plan.approvedPrd === void 0) return void 0;
|
|
29905
30130
|
await this.#assertProductPrdLocked(workspace, task);
|
|
29906
|
-
const baseline = JSON.parse(await readFile35(
|
|
30131
|
+
const baseline = JSON.parse(await readFile35(join42(taskDir(workspace, task), "acceptance-baseline.json"), "utf8"));
|
|
29907
30132
|
await verifyAcceptanceBaseline(await this.#acceptancePaths(workspace, task), baseline);
|
|
29908
|
-
const design =
|
|
30133
|
+
const design = join42(taskDir(workspace, task), "design");
|
|
29909
30134
|
await Promise.all([
|
|
29910
|
-
verifyAcceptanceArtifact(
|
|
29911
|
-
verifyAcceptanceArtifact(
|
|
30135
|
+
verifyAcceptanceArtifact(join42(design, "index.html"), baseline.artifacts.prototype, "runtime prototype"),
|
|
30136
|
+
verifyAcceptanceArtifact(join42(design, "interaction-manifest.json"), baseline.artifacts.interaction, "runtime interaction contract")
|
|
29912
30137
|
]);
|
|
29913
30138
|
const workflow = await readWorkflow(workspace, task);
|
|
29914
30139
|
if (workflow?.openapi !== void 0) {
|
|
29915
30140
|
await verifyAcceptanceArtifact(
|
|
29916
|
-
|
|
30141
|
+
join42(taskDir(workspace, task), "integration", "swagger.json"),
|
|
29917
30142
|
{ hash: workflow.openapi.hash },
|
|
29918
30143
|
"runtime OpenAPI contract"
|
|
29919
30144
|
);
|
|
@@ -29934,14 +30159,14 @@ VITE_API_BASE_URL=${running.url}
|
|
|
29934
30159
|
throw new Error("D2C PRD is empty and cannot be accepted");
|
|
29935
30160
|
}
|
|
29936
30161
|
if (stage === "design" && accepted) {
|
|
29937
|
-
const manifest =
|
|
30162
|
+
const manifest = join42(d2cProductPrototypeDirectory(workspace, task), "interaction-manifest.json");
|
|
29938
30163
|
const normalizedManifest = parseInteractionManifest(await readFile35(manifest, "utf8"));
|
|
29939
30164
|
if (current.plan.approvedPrd === void 0) throw new Error("Approve the PRD before confirming the design");
|
|
29940
30165
|
verifyRequirementCoverage(current.plan.approvedPrd.criteria, normalizedManifest);
|
|
29941
30166
|
await writeFile17(manifest, `${JSON.stringify(normalizedManifest, null, 2)}
|
|
29942
30167
|
`, { encoding: "utf8", mode: 384 });
|
|
29943
30168
|
const baseline = await captureAcceptanceBaseline(await this.#acceptancePaths(workspace, task), current.plan.approvedPrd);
|
|
29944
|
-
await writeFile17(
|
|
30169
|
+
await writeFile17(join42(taskDir(workspace, task), "acceptance-baseline.json"), `${JSON.stringify(baseline, null, 2)}
|
|
29945
30170
|
`, { encoding: "utf8", mode: 384 });
|
|
29946
30171
|
}
|
|
29947
30172
|
const plan = await writeD2cProductPlan(
|
|
@@ -29975,8 +30200,8 @@ VITE_API_BASE_URL=${running.url}
|
|
|
29975
30200
|
const imported = await this.importD2cDesign(task, d2cProductPrototypeDirectory(workspace, task));
|
|
29976
30201
|
const delivery = await readDeliveryRun(workspace, task);
|
|
29977
30202
|
if (delivery !== void 0) {
|
|
29978
|
-
const prototype = await readFile35(
|
|
29979
|
-
const interaction = await readFile35(
|
|
30203
|
+
const prototype = await readFile35(join42(d2cProductPrototypeDirectory(workspace, task), "index.html"), "utf8");
|
|
30204
|
+
const interaction = await readFile35(join42(d2cProductPrototypeDirectory(workspace, task), "interaction-manifest.json"), "utf8");
|
|
29980
30205
|
const completed = await updateDeliveryRun(workspace, task, delivery.revision, (run) => completeDeliveryNode(run, "design", [
|
|
29981
30206
|
artifactRef("product/prototype/index.html", prototype),
|
|
29982
30207
|
artifactRef("product/prototype/interaction-manifest.json", interaction)
|
|
@@ -30060,10 +30285,10 @@ VITE_API_BASE_URL=${running.url}
|
|
|
30060
30285
|
const next = await updateWorkflow(workspace, task, workflow.revision, (current) => applyQualityJudgment(current, judgment));
|
|
30061
30286
|
const qualityContent = `${JSON.stringify(judgment, null, 2)}
|
|
30062
30287
|
`;
|
|
30063
|
-
await writeFile17(
|
|
30288
|
+
await writeFile17(join42(taskDir(workspace, task), "quality-judge.json"), qualityContent, { encoding: "utf8", mode: 384 });
|
|
30064
30289
|
const delivery = await readDeliveryRun(workspace, task);
|
|
30065
30290
|
if (next.stage === "completed" && delivery?.nodes.acceptance.status === "running") {
|
|
30066
|
-
const interactionContent = await readFile35(
|
|
30291
|
+
const interactionContent = await readFile35(join42(integrationDirectory(workspace, task), "interaction-results.json"), "utf8");
|
|
30067
30292
|
const accepted = await updateDeliveryRun(workspace, task, delivery.revision, (run) => completeDeliveryNode(run, "acceptance", [
|
|
30068
30293
|
artifactRef("integration/interaction-results.json", interactionContent),
|
|
30069
30294
|
artifactRef("quality-judge.json", qualityContent)
|
|
@@ -30245,14 +30470,14 @@ function desktopWindowChrome(platform = process.platform) {
|
|
|
30245
30470
|
|
|
30246
30471
|
// src/utils/crash-guard.ts
|
|
30247
30472
|
import { mkdirSync as mkdirSync3, writeFileSync } from "fs";
|
|
30248
|
-
import { join as
|
|
30473
|
+
import { join as join43 } from "path";
|
|
30249
30474
|
var installed = false;
|
|
30250
30475
|
function installCrashGuard(options = {}) {
|
|
30251
30476
|
if (installed) return;
|
|
30252
30477
|
installed = true;
|
|
30253
30478
|
const crashLogDir = () => {
|
|
30254
30479
|
try {
|
|
30255
|
-
return
|
|
30480
|
+
return join43((options.workspace ?? process.cwd)(), ".flavor");
|
|
30256
30481
|
} catch {
|
|
30257
30482
|
return process.cwd();
|
|
30258
30483
|
}
|
|
@@ -30261,7 +30486,7 @@ function installCrashGuard(options = {}) {
|
|
|
30261
30486
|
try {
|
|
30262
30487
|
const dir = crashLogDir();
|
|
30263
30488
|
mkdirSync3(dir, { recursive: true });
|
|
30264
|
-
const path =
|
|
30489
|
+
const path = join43(dir, `crash-${(/* @__PURE__ */ new Date()).toISOString().replace(/[^0-9]/g, "")}-${process.pid}.log`);
|
|
30265
30490
|
writeFileSync(path, [
|
|
30266
30491
|
`kind: ${kind}`,
|
|
30267
30492
|
`version: ${process.version}`,
|
|
@@ -30452,9 +30677,9 @@ function safeGitTarget(value) {
|
|
|
30452
30677
|
}
|
|
30453
30678
|
|
|
30454
30679
|
// src/desktop/worktree-manager.ts
|
|
30455
|
-
import { randomUUID as
|
|
30680
|
+
import { randomUUID as randomUUID28 } from "crypto";
|
|
30456
30681
|
import { mkdir as mkdir28, readFile as readFile37, readdir as readdir13, rm as rm17, stat as stat15, writeFile as writeFile18 } from "fs/promises";
|
|
30457
|
-
import { basename as basename10, isAbsolute as isAbsolute19, join as
|
|
30682
|
+
import { basename as basename10, isAbsolute as isAbsolute19, join as join44, relative as relative27, resolve as resolve37, sep as sep27 } from "path";
|
|
30458
30683
|
var DesktopWorktreeManager = class {
|
|
30459
30684
|
#repository;
|
|
30460
30685
|
#storage;
|
|
@@ -30465,7 +30690,7 @@ var DesktopWorktreeManager = class {
|
|
|
30465
30690
|
async create(label) {
|
|
30466
30691
|
if (!await isGitRepository(this.#repository)) throw new Error("Task worktrees require a Git repository");
|
|
30467
30692
|
const safe = label.trim().toLowerCase().replace(/[^a-z0-9._-]+/g, "-").replace(/^-+|-+$/g, "").slice(0, 36) || "task";
|
|
30468
|
-
const suffix =
|
|
30693
|
+
const suffix = randomUUID28().slice(0, 8);
|
|
30469
30694
|
const id = `worktree-${safe}-${suffix}`;
|
|
30470
30695
|
const branch = `flavor/desktop-${safe}-${suffix}`;
|
|
30471
30696
|
const path = this.#safeStoragePath(id);
|
|
@@ -30500,7 +30725,7 @@ var DesktopWorktreeManager = class {
|
|
|
30500
30725
|
}
|
|
30501
30726
|
const values = await Promise.all(names.filter((name) => name.endsWith(".json")).map(async (name) => {
|
|
30502
30727
|
try {
|
|
30503
|
-
const stored = validateStored(JSON.parse(await readFile37(
|
|
30728
|
+
const stored = validateStored(JSON.parse(await readFile37(join44(this.#storage, name), "utf8")), this.#repository, this.#storage);
|
|
30504
30729
|
if ((await stat15(stored.path).catch(() => void 0))?.isDirectory() !== true) return void 0;
|
|
30505
30730
|
const status = await git2(stored.path, ["status", "--porcelain"]);
|
|
30506
30731
|
const merged = await git2(this.#repository, ["merge-base", "--is-ancestor", stored.branch, "HEAD"]);
|
|
@@ -30564,7 +30789,7 @@ function validateStored(value, repository, storage) {
|
|
|
30564
30789
|
// src/desktop/workbench-service.ts
|
|
30565
30790
|
import { readFile as readFile38, readdir as readdir14, stat as stat16 } from "fs/promises";
|
|
30566
30791
|
import { homedir as homedir5 } from "os";
|
|
30567
|
-
import { join as
|
|
30792
|
+
import { join as join45, resolve as resolve38 } from "path";
|
|
30568
30793
|
var MAX_TEXT = 128e3;
|
|
30569
30794
|
var MAX_ITEMS = 200;
|
|
30570
30795
|
var DesktopWorkbenchService = class {
|
|
@@ -30597,11 +30822,11 @@ var DesktopWorkbenchService = class {
|
|
|
30597
30822
|
return url.toString().replace(/\/$/, input.trim().endsWith("/") ? "/" : "");
|
|
30598
30823
|
}
|
|
30599
30824
|
async #goals() {
|
|
30600
|
-
const root =
|
|
30825
|
+
const root = join45(this.#workspace, ".flavor", "goals");
|
|
30601
30826
|
const names = await readdir14(root).catch((error) => error.code === "ENOENT" ? [] : Promise.reject(error));
|
|
30602
30827
|
const values = await Promise.all(names.filter((name) => /^goal-[A-Za-z0-9._-]+\.json$/.test(name)).slice(0, MAX_ITEMS).map(async (name) => {
|
|
30603
30828
|
try {
|
|
30604
|
-
return GoalStateSchema.parse(JSON.parse(await boundedRead2(
|
|
30829
|
+
return GoalStateSchema.parse(JSON.parse(await boundedRead2(join45(root, name))));
|
|
30605
30830
|
} catch {
|
|
30606
30831
|
return void 0;
|
|
30607
30832
|
}
|
|
@@ -30611,7 +30836,7 @@ var DesktopWorkbenchService = class {
|
|
|
30611
30836
|
async #instructions() {
|
|
30612
30837
|
const names = ["FLAVOR.md", "AGENTS.md", "CLAUDE.md", "AGENTS.local.md", "CLAUDE.local.md"];
|
|
30613
30838
|
const items = await Promise.all(names.map(async (name) => {
|
|
30614
|
-
const path =
|
|
30839
|
+
const path = join45(this.#workspace, name);
|
|
30615
30840
|
try {
|
|
30616
30841
|
return { name, path, content: redactErrorText(await boundedRead2(path)) };
|
|
30617
30842
|
} catch (error) {
|
|
@@ -30624,9 +30849,9 @@ var DesktopWorkbenchService = class {
|
|
|
30624
30849
|
async #permissionFiles() {
|
|
30625
30850
|
const candidates = [
|
|
30626
30851
|
...process.env.FLAVOR_MANAGED_PERMISSIONS ? [{ tier: "managed", path: resolve38(process.env.FLAVOR_MANAGED_PERMISSIONS), content: "" }] : [],
|
|
30627
|
-
{ tier: "user", path:
|
|
30628
|
-
{ tier: "project", path:
|
|
30629
|
-
{ tier: "local", path:
|
|
30852
|
+
{ tier: "user", path: join45(this.#home, ".flavor-code", "permissions.json"), content: "" },
|
|
30853
|
+
{ tier: "project", path: join45(this.#workspace, ".flavor", "permissions.json"), content: "" },
|
|
30854
|
+
{ tier: "local", path: join45(this.#workspace, ".flavor", "permissions.local.json"), content: "" }
|
|
30630
30855
|
];
|
|
30631
30856
|
const values = await Promise.all(candidates.map(async (item) => {
|
|
30632
30857
|
try {
|
|
@@ -30641,7 +30866,7 @@ var DesktopWorkbenchService = class {
|
|
|
30641
30866
|
async #audit() {
|
|
30642
30867
|
let raw;
|
|
30643
30868
|
try {
|
|
30644
|
-
raw = await boundedRead2(
|
|
30869
|
+
raw = await boundedRead2(join45(this.#workspace, ".flavor", "audit.jsonl"));
|
|
30645
30870
|
} catch (error) {
|
|
30646
30871
|
if (error.code === "ENOENT") return [];
|
|
30647
30872
|
throw error;
|
|
@@ -30656,7 +30881,7 @@ var DesktopWorkbenchService = class {
|
|
|
30656
30881
|
}).reverse();
|
|
30657
30882
|
}
|
|
30658
30883
|
async #context(sessionId) {
|
|
30659
|
-
const usage = await readJsonLines2(
|
|
30884
|
+
const usage = await readJsonLines2(join45(this.#workspace, ".flavor", "usage.jsonl"));
|
|
30660
30885
|
if (sessionId === void 0) return { visibility: [], usage };
|
|
30661
30886
|
try {
|
|
30662
30887
|
const document = await new SessionStore({ workspace: this.#workspace }).load(sessionId);
|
|
@@ -30697,12 +30922,12 @@ function redact2(value, key = "") {
|
|
|
30697
30922
|
// src/desktop/astgraph-service.ts
|
|
30698
30923
|
import { stat as stat17 } from "fs/promises";
|
|
30699
30924
|
import { createRequire as nodeCreateRequire2 } from "module";
|
|
30700
|
-
import { join as
|
|
30925
|
+
import { join as join46, resolve as resolve39 } from "path";
|
|
30701
30926
|
var FIELDS = "id, kind, name, qualified_name, file_path, language, start_line, end_line, signature";
|
|
30702
30927
|
var DesktopAstGraphService = class {
|
|
30703
30928
|
#path;
|
|
30704
30929
|
constructor(workspace) {
|
|
30705
|
-
this.#path =
|
|
30930
|
+
this.#path = join46(resolve39(workspace), ".flavor", "astgraph", "index.db");
|
|
30706
30931
|
}
|
|
30707
30932
|
async status() {
|
|
30708
30933
|
if ((await stat17(this.#path).catch(() => void 0))?.isFile() !== true) {
|
|
@@ -30779,7 +31004,7 @@ var appMenu;
|
|
|
30779
31004
|
var quitting = false;
|
|
30780
31005
|
function logStartup(step, detail) {
|
|
30781
31006
|
try {
|
|
30782
|
-
const logPath =
|
|
31007
|
+
const logPath = join47(dirname24(process.execPath), "flavor-code-startup.log");
|
|
30783
31008
|
const ts = (/* @__PURE__ */ new Date()).toISOString();
|
|
30784
31009
|
appendFileSync(logPath, `[${ts}] ${step}${detail ? ` | ${detail}` : ""}
|
|
30785
31010
|
`);
|
|
@@ -30804,7 +31029,7 @@ var d2cEmbedded = createEmbeddedD2cAutomation(() => {
|
|
|
30804
31029
|
};
|
|
30805
31030
|
});
|
|
30806
31031
|
var d2cJudgeClient = createD2cJudgeClient();
|
|
30807
|
-
var judgeStore = () => createD2cJudgeConfigStore(
|
|
31032
|
+
var judgeStore = () => createD2cJudgeConfigStore(join47(app.getPath("userData"), "d2c-judge.json"));
|
|
30808
31033
|
function judgeImage(png) {
|
|
30809
31034
|
const image = nativeImage.createFromBuffer(png);
|
|
30810
31035
|
const size = image.getSize();
|
|
@@ -30829,8 +31054,8 @@ function sessionMetaKey(workspace, sessionId) {
|
|
|
30829
31054
|
return `${workspace}\0${sessionId}`;
|
|
30830
31055
|
}
|
|
30831
31056
|
function worktreeManager(workspace) {
|
|
30832
|
-
const key =
|
|
30833
|
-
return new DesktopWorktreeManager({ repository: workspace, storage:
|
|
31057
|
+
const key = createHash28("sha256").update(resolve40(workspace)).digest("hex").slice(0, 20);
|
|
31058
|
+
return new DesktopWorktreeManager({ repository: workspace, storage: join47(app.getPath("userData"), "task-worktrees", key) });
|
|
30834
31059
|
}
|
|
30835
31060
|
function activeWorkingDirectory() {
|
|
30836
31061
|
if (activeWorkspace === void 0) throw new Error("\u8BF7\u5148\u6253\u5F00\u9879\u76EE");
|
|
@@ -30929,7 +31154,7 @@ function emitManagedDesktopEvent(workspace, owner2, event) {
|
|
|
30929
31154
|
}
|
|
30930
31155
|
}
|
|
30931
31156
|
function createDesktopController(workspace, owner2) {
|
|
30932
|
-
const collaborationId =
|
|
31157
|
+
const collaborationId = randomUUID29();
|
|
30933
31158
|
return new DesktopRuntimeController({
|
|
30934
31159
|
emit: (event) => emitManagedDesktopEvent(workspace, owner2, event),
|
|
30935
31160
|
runD2cInteractionTests: (manifest, baseUrl, mockUrl) => d2cEmbedded.run(manifest, baseUrl, mockUrl),
|
|
@@ -30961,12 +31186,21 @@ function createDesktopController(workspace, owner2) {
|
|
|
30961
31186
|
},
|
|
30962
31187
|
createRuntime: async (runtimeOptions) => createProductionRuntime({
|
|
30963
31188
|
...runtimeOptions,
|
|
31189
|
+
islandControl: {
|
|
31190
|
+
focus: () => {
|
|
31191
|
+
const target = mainWindow;
|
|
31192
|
+
if (target === void 0 || target.isDestroyed()) return;
|
|
31193
|
+
if (target.isMinimized()) target.restore();
|
|
31194
|
+
target.show();
|
|
31195
|
+
target.focus();
|
|
31196
|
+
}
|
|
31197
|
+
},
|
|
30964
31198
|
collaboration: {
|
|
30965
31199
|
instanceId: collaborationId,
|
|
30966
31200
|
alias: `desktop-${basename11(workspace ?? runtimeOptions.workspace ?? "flavor")}`.slice(0, 64),
|
|
30967
31201
|
optional: true,
|
|
30968
31202
|
startBroker: (address) => {
|
|
30969
|
-
const entry2 = app.isPackaged ?
|
|
31203
|
+
const entry2 = app.isPackaged ? join47(process.resourcesPath, "app.asar.unpacked", "dist", "desktop", "pals-broker-entry.js") : join47(moduleDirectory, "pals-broker-entry.js");
|
|
30970
31204
|
return spawnPalBrokerProcess({
|
|
30971
31205
|
address,
|
|
30972
31206
|
executable: process.execPath,
|
|
@@ -30988,7 +31222,7 @@ function createDesktopController(workspace, owner2) {
|
|
|
30988
31222
|
var detachedController = createDesktopController();
|
|
30989
31223
|
var controller = detachedController;
|
|
30990
31224
|
function statePath() {
|
|
30991
|
-
return
|
|
31225
|
+
return join47(app.getPath("userData"), "desktop-state.json");
|
|
30992
31226
|
}
|
|
30993
31227
|
async function loadPersistedProjects() {
|
|
30994
31228
|
try {
|
|
@@ -31161,7 +31395,7 @@ async function chooseAndOpenWorkspace() {
|
|
|
31161
31395
|
var cachedAppIconDataUrl;
|
|
31162
31396
|
function appIconDataUrl() {
|
|
31163
31397
|
if (cachedAppIconDataUrl !== void 0) return cachedAppIconDataUrl;
|
|
31164
|
-
const icon = nativeImage.createFromPath(
|
|
31398
|
+
const icon = nativeImage.createFromPath(join47(app.getAppPath(), "assets", "icon.png"));
|
|
31165
31399
|
if (icon.isEmpty()) return void 0;
|
|
31166
31400
|
cachedAppIconDataUrl = icon.toDataURL();
|
|
31167
31401
|
return cachedAppIconDataUrl;
|
|
@@ -31579,7 +31813,7 @@ function applicationMenu() {
|
|
|
31579
31813
|
]);
|
|
31580
31814
|
}
|
|
31581
31815
|
async function createWindow() {
|
|
31582
|
-
const rendererPath =
|
|
31816
|
+
const rendererPath = join47(app.getAppPath(), "dist", "desktop-renderer", "index.html");
|
|
31583
31817
|
mainWindow = new BrowserWindow2({
|
|
31584
31818
|
title: "Flavor Code",
|
|
31585
31819
|
width: 1280,
|
|
@@ -31590,14 +31824,14 @@ async function createWindow() {
|
|
|
31590
31824
|
show: false,
|
|
31591
31825
|
...desktopWindowChrome(),
|
|
31592
31826
|
webPreferences: {
|
|
31593
|
-
preload:
|
|
31827
|
+
preload: join47(moduleDirectory, "preload.cjs"),
|
|
31594
31828
|
contextIsolation: true,
|
|
31595
31829
|
nodeIntegration: false,
|
|
31596
31830
|
sandbox: false,
|
|
31597
31831
|
webSecurity: true
|
|
31598
31832
|
}
|
|
31599
31833
|
});
|
|
31600
|
-
const iconPath =
|
|
31834
|
+
const iconPath = join47(app.getAppPath(), "assets", "icon.png");
|
|
31601
31835
|
const icon = nativeImage.createFromPath(iconPath);
|
|
31602
31836
|
if (!icon.isEmpty()) {
|
|
31603
31837
|
mainWindow.setIcon(icon);
|