flavor-code 1.3.19 → 1.3.20
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/{app-EVMUBQRE.js → app-EGG3BYQQ.js} +2 -2
- package/dist/{chunk-HJOCNKPN.js → chunk-RPFWB45Y.js} +2 -1
- package/dist/{chunk-6LDYDQ3P.js → chunk-VEUSROUQ.js} +1 -1
- package/dist/{chunk-PF457XCH.js → chunk-Z62P4LL7.js} +533 -259
- package/dist/cli-main.js +25 -80
- package/dist/config/schema.d.ts +1 -1
- package/dist/desktop/main.js +640 -425
- package/dist/desktop-renderer/assets/{index-CxOMqdQf.js → index-D75ozXnZ.js} +2 -2
- package/dist/desktop-renderer/assets/{interactive-terminal-BK8GS4b3.js → interactive-terminal-CTFo4fK9.js} +1 -1
- package/dist/desktop-renderer/index.html +1 -1
- package/dist/{load-DELQW42K.js → load-4FWNXE6F.js} +1 -1
- package/dist/sdk/index.js +3 -3
- package/dist/session/store.d.ts +6 -0
- package/dist/utils/crash-guard.d.ts +9 -0
- package/dist/utils/spawn-executable.d.ts +16 -0
- package/package.json +1 -1
package/dist/desktop/main.js
CHANGED
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
import { appendFileSync } from "fs";
|
|
3
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 dirname25, join as join48, resolve as
|
|
5
|
+
import { basename as basename11, dirname as dirname25, join as join48, resolve as resolve41 } 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
|
|
|
@@ -272,7 +272,8 @@ var FlavorConfigSchema = z2.object({
|
|
|
272
272
|
autoStoreThreshold: z2.number().int().min(0).max(12).default(11),
|
|
273
273
|
ignoreStreakLimit: z2.number().int().min(2).max(100).default(5),
|
|
274
274
|
reviewAutoDismissSeconds: z2.number().int().min(0).max(300).default(5),
|
|
275
|
-
|
|
275
|
+
// One candidate per task is a deliberate memory-quality boundary, not a tunable range.
|
|
276
|
+
maxCandidatesPerTask: z2.literal(1).default(1),
|
|
276
277
|
retrievalTopK: z2.number().int().min(1).max(20).default(5),
|
|
277
278
|
maxEntries: z2.number().int().min(1).max(1e4).default(200),
|
|
278
279
|
maxEntryChars: z2.number().int().min(32).max(2e4).default(1e3),
|
|
@@ -1989,7 +1990,7 @@ function createEmbeddedD2cAutomation(getHost, options = {}) {
|
|
|
1989
1990
|
const ready = await candidate.executeJavaScript("document.readyState", false).catch(() => void 0);
|
|
1990
1991
|
if (ready === "complete" || ready === "interactive") return candidate;
|
|
1991
1992
|
}
|
|
1992
|
-
await new Promise((
|
|
1993
|
+
await new Promise((resolve42) => setTimeout(resolve42, pollIntervalMs));
|
|
1993
1994
|
}
|
|
1994
1995
|
throw new Error(`D2C embedded preview did not navigate within ${navigationTimeoutMs} ms: ${url}${observedUrl === void 0 ? "" : `; last observed ${observedUrl}`}${observedFrames.length === 0 ? "; no live frames" : `; live frames ${observedFrames.join(", ")}`}`);
|
|
1995
1996
|
};
|
|
@@ -2004,13 +2005,13 @@ function createEmbeddedD2cAutomation(getHost, options = {}) {
|
|
|
2004
2005
|
while (Date.now() <= deadline) {
|
|
2005
2006
|
last = await frame(baseUrl).executeJavaScript(assertionScript(step), true);
|
|
2006
2007
|
if (last.passed) return last;
|
|
2007
|
-
await new Promise((
|
|
2008
|
+
await new Promise((resolve42) => setTimeout(resolve42, 50));
|
|
2008
2009
|
}
|
|
2009
2010
|
return last;
|
|
2010
2011
|
};
|
|
2011
2012
|
const settleAfterAction = async (previousUrl) => {
|
|
2012
2013
|
const origin = new URL(previousUrl).origin;
|
|
2013
|
-
await new Promise((
|
|
2014
|
+
await new Promise((resolve42) => setTimeout(resolve42, Math.min(180, Math.max(20, visualDelayMs * 0.25))));
|
|
2014
2015
|
const deadline = Date.now() + Math.min(2e3, navigationTimeoutMs);
|
|
2015
2016
|
while (Date.now() <= deadline) {
|
|
2016
2017
|
const candidate = host().mainFrame.framesInSubtree.find((item) => !item.isDestroyed() && sameOrigin(item.url, origin));
|
|
@@ -2018,7 +2019,7 @@ function createEmbeddedD2cAutomation(getHost, options = {}) {
|
|
|
2018
2019
|
const ready = await candidate.executeJavaScript("document.readyState", false).catch(() => void 0);
|
|
2019
2020
|
if (ready === "complete" || ready === "interactive") return candidate;
|
|
2020
2021
|
}
|
|
2021
|
-
await new Promise((
|
|
2022
|
+
await new Promise((resolve42) => setTimeout(resolve42, pollIntervalMs));
|
|
2022
2023
|
}
|
|
2023
2024
|
throw new Error("D2C preview frame was unavailable after an interaction action");
|
|
2024
2025
|
};
|
|
@@ -2105,7 +2106,7 @@ function createEmbeddedD2cAutomation(getHost, options = {}) {
|
|
|
2105
2106
|
return result;
|
|
2106
2107
|
},
|
|
2107
2108
|
settle: async () => {
|
|
2108
|
-
await new Promise((
|
|
2109
|
+
await new Promise((resolve42) => setTimeout(resolve42, 150));
|
|
2109
2110
|
requests = await frame(activeUrl).executeJavaScript(`performance.getEntriesByType("resource").filter((entry) => {
|
|
2110
2111
|
try { return ["fetch", "xmlhttprequest"].includes(entry.initiatorType) && new URL(entry.name).origin === ${JSON.stringify(mockOrigin)}; } catch { return false; }
|
|
2111
2112
|
}).length`, false).catch(() => 0);
|
|
@@ -2191,7 +2192,7 @@ function responseText(protocol, value) {
|
|
|
2191
2192
|
}
|
|
2192
2193
|
throw new Error("D2C Judge response did not contain text output");
|
|
2193
2194
|
}
|
|
2194
|
-
function createD2cJudgeClient(fetcher = globalThis.fetch, retryDelay = (ms) => new Promise((
|
|
2195
|
+
function createD2cJudgeClient(fetcher = globalThis.fetch, retryDelay = (ms) => new Promise((resolve42) => setTimeout(resolve42, ms))) {
|
|
2195
2196
|
const request = async (config, prompt, images, maxTokens) => {
|
|
2196
2197
|
const openAi = config.protocol === "openai-compatible";
|
|
2197
2198
|
const imageContent = openAi ? images.map((image) => ({ type: "image_url", image_url: { url: dataUrl(image), detail: "high" } })) : images.map((image) => ({ type: "image", source: { type: "base64", media_type: "image/png", data: image.toString("base64") } }));
|
|
@@ -2550,7 +2551,7 @@ import { z as z7 } from "zod";
|
|
|
2550
2551
|
import { Worker } from "worker_threads";
|
|
2551
2552
|
function comparePngsInWorker(left, right, signal) {
|
|
2552
2553
|
signal?.throwIfAborted();
|
|
2553
|
-
return new Promise((
|
|
2554
|
+
return new Promise((resolve42, reject) => {
|
|
2554
2555
|
const worker = new Worker(new URL("./pixel-worker.js", import.meta.url));
|
|
2555
2556
|
let settled = false;
|
|
2556
2557
|
const finish = (callback) => {
|
|
@@ -2568,7 +2569,7 @@ function comparePngsInWorker(left, right, signal) {
|
|
|
2568
2569
|
finish(() => reject(new Error(value.error ?? "D2C pixel worker failed")));
|
|
2569
2570
|
return;
|
|
2570
2571
|
}
|
|
2571
|
-
finish(() =>
|
|
2572
|
+
finish(() => resolve42({ ...value.result, heatmapPng: Buffer.from(value.result.heatmapPng) }));
|
|
2572
2573
|
});
|
|
2573
2574
|
worker.once("error", (error) => finish(() => reject(error)));
|
|
2574
2575
|
worker.once("exit", (code) => {
|
|
@@ -4225,10 +4226,10 @@ ${summarizeReport(report2)}`).join("\n\n")
|
|
|
4225
4226
|
|
|
4226
4227
|
// src/production.ts
|
|
4227
4228
|
import { readFile as readFile27 } from "fs/promises";
|
|
4228
|
-
import { mkdirSync as
|
|
4229
|
+
import { mkdirSync as mkdirSync4, readFileSync as readFileSync3, writeFileSync as writeFileSync2 } from "fs";
|
|
4229
4230
|
import { createHash as createHash21, randomUUID as randomUUID25 } from "crypto";
|
|
4230
4231
|
import { homedir as homedir2, release as osRelease, tmpdir as tmpdir4, version as osVersion } from "os";
|
|
4231
|
-
import { basename as basename8, dirname as dirname24, join as
|
|
4232
|
+
import { basename as basename8, dirname as dirname24, join as join35, relative as relative23, resolve as resolve34, sep as sep23 } from "path";
|
|
4232
4233
|
|
|
4233
4234
|
// src/agent/subagents.ts
|
|
4234
4235
|
import { z as z10 } from "zod";
|
|
@@ -4441,12 +4442,12 @@ function hasCycle(nodes) {
|
|
|
4441
4442
|
|
|
4442
4443
|
// src/utils/async.ts
|
|
4443
4444
|
function withTimeout(promise, ms) {
|
|
4444
|
-
return new Promise((
|
|
4445
|
+
return new Promise((resolve42, reject) => {
|
|
4445
4446
|
let settled = false;
|
|
4446
4447
|
const timer = setTimeout(() => {
|
|
4447
4448
|
if (settled) return;
|
|
4448
4449
|
settled = true;
|
|
4449
|
-
|
|
4450
|
+
resolve42({ timedOut: true, value: void 0 });
|
|
4450
4451
|
}, ms);
|
|
4451
4452
|
timer.unref?.();
|
|
4452
4453
|
promise.then(
|
|
@@ -4454,7 +4455,7 @@ function withTimeout(promise, ms) {
|
|
|
4454
4455
|
if (!settled) {
|
|
4455
4456
|
settled = true;
|
|
4456
4457
|
clearTimeout(timer);
|
|
4457
|
-
|
|
4458
|
+
resolve42({ timedOut: false, value });
|
|
4458
4459
|
}
|
|
4459
4460
|
},
|
|
4460
4461
|
(error) => {
|
|
@@ -4469,13 +4470,13 @@ function withTimeout(promise, ms) {
|
|
|
4469
4470
|
}
|
|
4470
4471
|
function awaitWithSignal3(promise, signal) {
|
|
4471
4472
|
signal.throwIfAborted();
|
|
4472
|
-
return new Promise((
|
|
4473
|
+
return new Promise((resolve42, reject) => {
|
|
4473
4474
|
const onAbort = () => reject(signal.reason);
|
|
4474
4475
|
signal.addEventListener("abort", onAbort, { once: true });
|
|
4475
4476
|
promise.then(
|
|
4476
4477
|
(value) => {
|
|
4477
4478
|
signal.removeEventListener("abort", onAbort);
|
|
4478
|
-
|
|
4479
|
+
resolve42(value);
|
|
4479
4480
|
},
|
|
4480
4481
|
(error) => {
|
|
4481
4482
|
signal.removeEventListener("abort", onAbort);
|
|
@@ -6524,10 +6525,10 @@ function sanitize(message2, secrets) {
|
|
|
6524
6525
|
}
|
|
6525
6526
|
function waitForRetry(delayMs, signal) {
|
|
6526
6527
|
if (signal?.aborted) return Promise.reject(signal.reason);
|
|
6527
|
-
return new Promise((
|
|
6528
|
+
return new Promise((resolve42, reject) => {
|
|
6528
6529
|
const timer = setTimeout(() => {
|
|
6529
6530
|
signal?.removeEventListener("abort", onAbort);
|
|
6530
|
-
|
|
6531
|
+
resolve42();
|
|
6531
6532
|
}, delayMs);
|
|
6532
6533
|
const onAbort = () => {
|
|
6533
6534
|
clearTimeout(timer);
|
|
@@ -7165,10 +7166,10 @@ function serializeToolInput(input) {
|
|
|
7165
7166
|
}
|
|
7166
7167
|
function waitForRetry2(delayMs, signal) {
|
|
7167
7168
|
if (signal?.aborted) return Promise.reject(signal.reason);
|
|
7168
|
-
return new Promise((
|
|
7169
|
+
return new Promise((resolve42, reject) => {
|
|
7169
7170
|
const timer = setTimeout(() => {
|
|
7170
7171
|
signal?.removeEventListener("abort", onAbort);
|
|
7171
|
-
|
|
7172
|
+
resolve42();
|
|
7172
7173
|
}, delayMs);
|
|
7173
7174
|
const onAbort = () => {
|
|
7174
7175
|
clearTimeout(timer);
|
|
@@ -8713,6 +8714,64 @@ var HookDecisionSchema = z16.object({
|
|
|
8713
8714
|
additionalContext: z16.string().optional()
|
|
8714
8715
|
});
|
|
8715
8716
|
|
|
8717
|
+
// src/utils/spawn-executable.ts
|
|
8718
|
+
import { existsSync as existsSync3, statSync as statSync2 } from "fs";
|
|
8719
|
+
import { extname as extname2, isAbsolute as isAbsolute6, resolve as resolve9 } from "path";
|
|
8720
|
+
var WINDOWS_EXECUTABLE = /\.(?:com|exe)$/iu;
|
|
8721
|
+
var WINDOWS_CMD_SHIM = /node_modules[\\/].bin[\\/][^\\/]+\.cmd$/iu;
|
|
8722
|
+
var CMD_META = /([()\][%!^"`<>&|;, *?])/gu;
|
|
8723
|
+
function prepareSpawnInvocation(command, args, options = {}) {
|
|
8724
|
+
if ((options.platform ?? process.platform) !== "win32") return { command, args: [...args] };
|
|
8725
|
+
const env = options.env ?? process.env;
|
|
8726
|
+
const commandFile = resolveWindowsCommand(command, options.cwd, env);
|
|
8727
|
+
if (commandFile === void 0 || WINDOWS_EXECUTABLE.test(commandFile)) {
|
|
8728
|
+
return { command: commandFile ?? command, args: [...args] };
|
|
8729
|
+
}
|
|
8730
|
+
const doubleEscape = WINDOWS_CMD_SHIM.test(commandFile);
|
|
8731
|
+
const shellCommand = [escapeCmdCommand(commandFile), ...args.map((arg) => escapeCmdArgument(arg, doubleEscape))].join(" ");
|
|
8732
|
+
return {
|
|
8733
|
+
command: environmentValue(env, "ComSpec") ?? "cmd.exe",
|
|
8734
|
+
args: ["/d", "/s", "/c", `"${shellCommand}"`],
|
|
8735
|
+
windowsVerbatimArguments: true
|
|
8736
|
+
};
|
|
8737
|
+
}
|
|
8738
|
+
function resolveWindowsCommand(command, cwd, env) {
|
|
8739
|
+
const pathValue = environmentValue(env, "PATH") ?? "";
|
|
8740
|
+
const pathExt = (environmentValue(env, "PATHEXT") ?? ".COM;.EXE;.BAT;.CMD").split(";").filter(Boolean);
|
|
8741
|
+
const hasPath = isAbsolute6(command) || /[\\/]/u.test(command);
|
|
8742
|
+
const bases = hasPath ? [isAbsolute6(command) ? command : resolve9(cwd ?? process.cwd(), command)] : pathValue.split(";").filter(Boolean).map((entry2) => resolve9(unquote(entry2), command));
|
|
8743
|
+
for (const base of bases) {
|
|
8744
|
+
const candidates = extname2(base).length > 0 ? [base] : [base, ...pathExt.map((extension2) => `${base}${extension2}`)];
|
|
8745
|
+
for (const candidate of candidates) {
|
|
8746
|
+
if (isRegularFile(candidate)) return resolve9(candidate);
|
|
8747
|
+
}
|
|
8748
|
+
}
|
|
8749
|
+
return void 0;
|
|
8750
|
+
}
|
|
8751
|
+
function environmentValue(env, name) {
|
|
8752
|
+
const key = Object.keys(env).find((candidate) => candidate.toLowerCase() === name.toLowerCase());
|
|
8753
|
+
return key === void 0 ? void 0 : env[key];
|
|
8754
|
+
}
|
|
8755
|
+
function unquote(value) {
|
|
8756
|
+
const trimmed = value.trim();
|
|
8757
|
+
return trimmed.startsWith('"') && trimmed.endsWith('"') ? trimmed.slice(1, -1) : trimmed;
|
|
8758
|
+
}
|
|
8759
|
+
function isRegularFile(path) {
|
|
8760
|
+
try {
|
|
8761
|
+
return existsSync3(path) && statSync2(path).isFile();
|
|
8762
|
+
} catch {
|
|
8763
|
+
return false;
|
|
8764
|
+
}
|
|
8765
|
+
}
|
|
8766
|
+
function escapeCmdCommand(value) {
|
|
8767
|
+
return value.replace(CMD_META, "^$1");
|
|
8768
|
+
}
|
|
8769
|
+
function escapeCmdArgument(value, doubleEscapeMetaCharacters) {
|
|
8770
|
+
let escaped = value.replace(/(?=(\\+?)?)\1"/gu, '$1$1\\"').replace(/(?=(\\+?)?)\1$/gu, "$1$1");
|
|
8771
|
+
escaped = `"${escaped}"`.replace(CMD_META, "^$1");
|
|
8772
|
+
return doubleEscapeMetaCharacters ? escaped.replace(CMD_META, "^$1") : escaped;
|
|
8773
|
+
}
|
|
8774
|
+
|
|
8716
8775
|
// src/hooks/bus.ts
|
|
8717
8776
|
var DEFAULT_TIMEOUT_MS = 1e4;
|
|
8718
8777
|
var DEFAULT_MAX_OUTPUT_BYTES = 1048576;
|
|
@@ -8819,13 +8878,16 @@ function decoratePayload(payload, envelope) {
|
|
|
8819
8878
|
return envelope === void 0 ? payload : { ...payload, ...envelope };
|
|
8820
8879
|
}
|
|
8821
8880
|
function runShellHandler(descriptor, event, signal) {
|
|
8822
|
-
return new Promise((
|
|
8823
|
-
const
|
|
8824
|
-
|
|
8881
|
+
return new Promise((resolve42, reject) => {
|
|
8882
|
+
const env = { ...process.env, ...descriptor.env };
|
|
8883
|
+
const invocation = prepareSpawnInvocation(descriptor.command, descriptor.args ?? [], { env });
|
|
8884
|
+
const child = spawn(invocation.command, invocation.args, {
|
|
8885
|
+
env,
|
|
8825
8886
|
shell: false,
|
|
8826
8887
|
detached: process.platform !== "win32",
|
|
8827
8888
|
windowsHide: true,
|
|
8828
|
-
stdio: ["pipe", "pipe", "pipe"]
|
|
8889
|
+
stdio: ["pipe", "pipe", "pipe"],
|
|
8890
|
+
...invocation.windowsVerbatimArguments === void 0 ? {} : { windowsVerbatimArguments: invocation.windowsVerbatimArguments }
|
|
8829
8891
|
});
|
|
8830
8892
|
const maxOutputBytes = descriptor.maxOutputBytes ?? DEFAULT_MAX_OUTPUT_BYTES;
|
|
8831
8893
|
let stdout = "";
|
|
@@ -8862,7 +8924,7 @@ function runShellHandler(descriptor, event, signal) {
|
|
|
8862
8924
|
return;
|
|
8863
8925
|
}
|
|
8864
8926
|
try {
|
|
8865
|
-
|
|
8927
|
+
resolve42(HookDecisionSchema.parse(JSON.parse(stdout)));
|
|
8866
8928
|
} catch (error) {
|
|
8867
8929
|
reject(error);
|
|
8868
8930
|
}
|
|
@@ -8901,7 +8963,7 @@ function zodRecord(payload) {
|
|
|
8901
8963
|
// src/utils/execFileNoThrow.ts
|
|
8902
8964
|
import { execFile } from "child_process";
|
|
8903
8965
|
function execFileNoThrow(file, args, options = {}) {
|
|
8904
|
-
return new Promise((
|
|
8966
|
+
return new Promise((resolve42) => {
|
|
8905
8967
|
const child = execFile(file, args, {
|
|
8906
8968
|
timeout: options.timeout,
|
|
8907
8969
|
cwd: options.useCwd === false ? void 0 : process.cwd(),
|
|
@@ -8909,7 +8971,7 @@ function execFileNoThrow(file, args, options = {}) {
|
|
|
8909
8971
|
encoding: "utf8",
|
|
8910
8972
|
windowsHide: true
|
|
8911
8973
|
}, (error, stdout, stderr) => {
|
|
8912
|
-
|
|
8974
|
+
resolve42({
|
|
8913
8975
|
stdout,
|
|
8914
8976
|
stderr,
|
|
8915
8977
|
code: typeof error?.code === "number" ? error.code : error ? 1 : 0,
|
|
@@ -9081,14 +9143,14 @@ async function createIslandControlServer(options) {
|
|
|
9081
9143
|
void dispatch(line, socket, options, token);
|
|
9082
9144
|
});
|
|
9083
9145
|
});
|
|
9084
|
-
await new Promise((
|
|
9146
|
+
await new Promise((resolve42, reject) => {
|
|
9085
9147
|
const onError = (error) => {
|
|
9086
9148
|
server.removeListener("listening", onListening);
|
|
9087
9149
|
reject(error);
|
|
9088
9150
|
};
|
|
9089
9151
|
const onListening = () => {
|
|
9090
9152
|
server.removeListener("error", onError);
|
|
9091
|
-
|
|
9153
|
+
resolve42();
|
|
9092
9154
|
};
|
|
9093
9155
|
server.once("error", onError);
|
|
9094
9156
|
server.once("listening", onListening);
|
|
@@ -9159,11 +9221,11 @@ function write(socket, payload) {
|
|
|
9159
9221
|
}
|
|
9160
9222
|
}
|
|
9161
9223
|
function closeServer(server) {
|
|
9162
|
-
return new Promise((
|
|
9224
|
+
return new Promise((resolve42) => {
|
|
9163
9225
|
try {
|
|
9164
|
-
server.close(() =>
|
|
9226
|
+
server.close(() => resolve42());
|
|
9165
9227
|
} catch {
|
|
9166
|
-
|
|
9228
|
+
resolve42();
|
|
9167
9229
|
}
|
|
9168
9230
|
});
|
|
9169
9231
|
}
|
|
@@ -9177,13 +9239,13 @@ import { z as z19 } from "zod";
|
|
|
9177
9239
|
// src/evolve/loader.ts
|
|
9178
9240
|
import { cp as cp2, mkdir as mkdir7, mkdtemp, readdir as readdir3, rm as rm4, stat as stat5, writeFile as writeFile5 } from "fs/promises";
|
|
9179
9241
|
import { tmpdir as tmpdir2 } from "os";
|
|
9180
|
-
import { join as join10, resolve as
|
|
9242
|
+
import { join as join10, resolve as resolve11 } from "path";
|
|
9181
9243
|
|
|
9182
9244
|
// src/plugins/host.ts
|
|
9183
9245
|
import { createHash as createHash8 } from "crypto";
|
|
9184
9246
|
import { lstatSync, realpathSync as realpathSync3 } from "fs";
|
|
9185
9247
|
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";
|
|
9186
|
-
import { isAbsolute as
|
|
9248
|
+
import { isAbsolute as isAbsolute7, relative as relative7, resolve as resolve10, sep as sep7 } from "path";
|
|
9187
9249
|
import { pathToFileURL as pathToFileURL2 } from "url";
|
|
9188
9250
|
import { z as z18 } from "zod";
|
|
9189
9251
|
|
|
@@ -9501,7 +9563,7 @@ var PluginHost = class {
|
|
|
9501
9563
|
const roots = [];
|
|
9502
9564
|
for (const directory of directories) {
|
|
9503
9565
|
try {
|
|
9504
|
-
for (const entry2 of await readdir2(directory, { withFileTypes: true })) if (entry2.isDirectory()) roots.push(
|
|
9566
|
+
for (const entry2 of await readdir2(directory, { withFileTypes: true })) if (entry2.isDirectory()) roots.push(resolve10(directory, entry2.name));
|
|
9505
9567
|
} catch (error) {
|
|
9506
9568
|
this.#diagnose(directory, error, directory);
|
|
9507
9569
|
}
|
|
@@ -9534,7 +9596,7 @@ var PluginHost = class {
|
|
|
9534
9596
|
const rootInfo = await lstat3(inputRoot);
|
|
9535
9597
|
if (!rootInfo.isDirectory() || rootInfo.isSymbolicLink()) throw new Error("Plugin root must be a static directory");
|
|
9536
9598
|
const root = await realpath6(inputRoot);
|
|
9537
|
-
const rawManifest = await readManifest2(
|
|
9599
|
+
const rawManifest = await readManifest2(resolve10(root, MANIFEST));
|
|
9538
9600
|
if (typeof rawManifest === "object" && rawManifest !== null && "name" in rawManifest && typeof rawManifest.name === "string") {
|
|
9539
9601
|
diagnosticLabel = rawManifest.name;
|
|
9540
9602
|
}
|
|
@@ -9720,7 +9782,7 @@ var PluginHost = class {
|
|
|
9720
9782
|
assertActive();
|
|
9721
9783
|
const permission = `filesystem:${operation}`;
|
|
9722
9784
|
if (!manifest.permissions.includes(permission)) throw new Error(`Plugin ${manifest.name} lacks ${permission} permission`);
|
|
9723
|
-
const path =
|
|
9785
|
+
const path = resolve10(rawPath);
|
|
9724
9786
|
const authorized = await this.#options.authorizeFilesystem?.({ plugin: manifest.name, operation, path });
|
|
9725
9787
|
assertActive();
|
|
9726
9788
|
if (authorized !== true) throw new Error(`Filesystem ${operation} denied for plugin ${manifest.name}`);
|
|
@@ -9851,14 +9913,14 @@ function assertSnapshot(expected, actual, label) {
|
|
|
9851
9913
|
for (const key of ["dev", "ino", "mode", "size", "mtimeNs", "ctimeNs"]) if (expected[key] !== actual[key]) throw new Error(`${label} changed during validation`);
|
|
9852
9914
|
}
|
|
9853
9915
|
function resolveContained(root, value, label) {
|
|
9854
|
-
if (
|
|
9855
|
-
const candidate =
|
|
9916
|
+
if (isAbsolute7(value)) throw new Error(`${label} must be relative to the plugin root`);
|
|
9917
|
+
const candidate = resolve10(root, value);
|
|
9856
9918
|
assertContained2(root, candidate, label);
|
|
9857
9919
|
return candidate;
|
|
9858
9920
|
}
|
|
9859
9921
|
function assertContained2(root, candidate, label) {
|
|
9860
9922
|
const delta = relative7(root, candidate);
|
|
9861
|
-
if (delta === ".." || delta.startsWith(`..${sep7}`) ||
|
|
9923
|
+
if (delta === ".." || delta.startsWith(`..${sep7}`) || isAbsolute7(delta)) throw new Error(`${label} escapes the plugin root`);
|
|
9862
9924
|
}
|
|
9863
9925
|
function scopedLogger(logger, plugin) {
|
|
9864
9926
|
const prefix = `[plugin:${plugin}] `;
|
|
@@ -9908,10 +9970,10 @@ function sanitizePluginName(tool) {
|
|
|
9908
9970
|
return base.startsWith("fix-") ? base : `fix-${base}`;
|
|
9909
9971
|
}
|
|
9910
9972
|
function fixPluginDir(workspace, name) {
|
|
9911
|
-
return
|
|
9973
|
+
return resolve11(workspace, PLUGIN_DIR, name);
|
|
9912
9974
|
}
|
|
9913
9975
|
function versionsDirFor(workspace, name) {
|
|
9914
|
-
return
|
|
9976
|
+
return resolve11(workspace, VERSIONS_DIR, name);
|
|
9915
9977
|
}
|
|
9916
9978
|
function timestamp() {
|
|
9917
9979
|
return (/* @__PURE__ */ new Date()).toISOString().replaceAll(":", "-");
|
|
@@ -10348,7 +10410,7 @@ var USAGE = [
|
|
|
10348
10410
|
" clear reset signals, done markers, and verified markers"
|
|
10349
10411
|
].join("\n");
|
|
10350
10412
|
function runCommand(command, cwd, timeoutMs) {
|
|
10351
|
-
return new Promise((
|
|
10413
|
+
return new Promise((resolve42) => {
|
|
10352
10414
|
const isWindows = process.platform === "win32";
|
|
10353
10415
|
const child = spawn2(isWindows ? "cmd.exe" : "/bin/sh", isWindows ? ["/d", "/s", "/c", command] : ["-c", command], {
|
|
10354
10416
|
cwd,
|
|
@@ -10358,7 +10420,7 @@ function runCommand(command, cwd, timeoutMs) {
|
|
|
10358
10420
|
let stderr = "";
|
|
10359
10421
|
const timer = setTimeout(() => {
|
|
10360
10422
|
child.kill();
|
|
10361
|
-
|
|
10423
|
+
resolve42({ ok: false, stdout, stderr: `${stderr}
|
|
10362
10424
|
[killed: exceeded ${timeoutMs}ms]`, code: "timeout" });
|
|
10363
10425
|
}, timeoutMs);
|
|
10364
10426
|
child.stdout.on("data", (chunk) => {
|
|
@@ -10369,12 +10431,12 @@ function runCommand(command, cwd, timeoutMs) {
|
|
|
10369
10431
|
});
|
|
10370
10432
|
child.on("error", (error) => {
|
|
10371
10433
|
clearTimeout(timer);
|
|
10372
|
-
|
|
10434
|
+
resolve42({ ok: false, stdout, stderr: `${stderr}
|
|
10373
10435
|
${error.message}`, code: "spawn" });
|
|
10374
10436
|
});
|
|
10375
10437
|
child.on("close", (code) => {
|
|
10376
10438
|
clearTimeout(timer);
|
|
10377
|
-
|
|
10439
|
+
resolve42({ ok: code === 0, stdout, stderr, code: code ?? "unknown" });
|
|
10378
10440
|
});
|
|
10379
10441
|
});
|
|
10380
10442
|
}
|
|
@@ -10784,7 +10846,7 @@ ${result.stderr.slice(-4e3)}` : ""}`;
|
|
|
10784
10846
|
// src/init/project.ts
|
|
10785
10847
|
import { lstat as lstat4, mkdir as mkdir9, readdir as readdir4, readFile as readFile9, realpath as realpath7, writeFile as writeFile8 } from "fs/promises";
|
|
10786
10848
|
import { copyFileSync, mkdirSync as mkdirSync2, readdirSync } from "fs";
|
|
10787
|
-
import { basename as basename4, dirname as dirname9, isAbsolute as
|
|
10849
|
+
import { basename as basename4, dirname as dirname9, isAbsolute as isAbsolute8, join as join13, relative as relative8, sep as sep8 } from "path";
|
|
10788
10850
|
import { fileURLToPath } from "url";
|
|
10789
10851
|
var GENERATED_START = "<!-- flavor-code:start -->";
|
|
10790
10852
|
var GENERATED_END = "<!-- flavor-code:end -->";
|
|
@@ -10834,7 +10896,7 @@ var CONFIG_FILES = [
|
|
|
10834
10896
|
];
|
|
10835
10897
|
function isContainedPath(root, candidate) {
|
|
10836
10898
|
const pathFromRoot = relative8(root, candidate);
|
|
10837
|
-
return pathFromRoot === "" || pathFromRoot !== ".." && !pathFromRoot.startsWith(`..${sep8}`) && !
|
|
10899
|
+
return pathFromRoot === "" || pathFromRoot !== ".." && !pathFromRoot.startsWith(`..${sep8}`) && !isAbsolute8(pathFromRoot);
|
|
10838
10900
|
}
|
|
10839
10901
|
async function pathKind(path, root) {
|
|
10840
10902
|
try {
|
|
@@ -12353,14 +12415,14 @@ function formatPriorGaps(gaps) {
|
|
|
12353
12415
|
import { constants as constants3 } from "fs";
|
|
12354
12416
|
import { randomUUID as randomUUID11 } from "crypto";
|
|
12355
12417
|
import { lstat as lstat5, mkdir as mkdir11, open as open5, rename as rename4, rm as rm6 } from "fs/promises";
|
|
12356
|
-
import { dirname as dirname10, join as join16, relative as relative9, resolve as
|
|
12418
|
+
import { dirname as dirname10, join as join16, relative as relative9, resolve as resolve12, sep as sep9 } from "path";
|
|
12357
12419
|
var DEFAULT_MAX_BYTES = 1024 * 1024;
|
|
12358
12420
|
var GoalStore = class {
|
|
12359
12421
|
#workspace;
|
|
12360
12422
|
#root;
|
|
12361
12423
|
#maxBytes;
|
|
12362
12424
|
constructor(options) {
|
|
12363
|
-
this.#workspace =
|
|
12425
|
+
this.#workspace = resolve12(options.workspace);
|
|
12364
12426
|
this.#root = join16(this.#workspace, ".flavor", "goals");
|
|
12365
12427
|
this.#maxBytes = options.maxBytes ?? DEFAULT_MAX_BYTES;
|
|
12366
12428
|
}
|
|
@@ -12391,8 +12453,8 @@ var GoalStore = class {
|
|
|
12391
12453
|
}
|
|
12392
12454
|
};
|
|
12393
12455
|
async function assertNoSymlink(root, target) {
|
|
12394
|
-
let cursor =
|
|
12395
|
-
const stop =
|
|
12456
|
+
let cursor = resolve12(target);
|
|
12457
|
+
const stop = resolve12(root);
|
|
12396
12458
|
const value = relative9(stop, cursor);
|
|
12397
12459
|
if (value === ".." || value.startsWith(`..${sep9}`) || value.includes(":")) {
|
|
12398
12460
|
throw new Error("Goal path escapes workspace");
|
|
@@ -12416,7 +12478,7 @@ function isCode4(error, code) {
|
|
|
12416
12478
|
|
|
12417
12479
|
// src/loop/isolation.ts
|
|
12418
12480
|
import { appendFile as appendFile2, mkdir as mkdir12, readFile as readFile11 } from "fs/promises";
|
|
12419
|
-
import { join as join17, resolve as
|
|
12481
|
+
import { join as join17, resolve as resolve13 } from "path";
|
|
12420
12482
|
var CODE_CHANGE = /\b(?:fix|implement|add|update|change|modify|refactor|build|create|remove|delete|write|migrate)\b|修复|实现|添加|更新|修改|重构|创建|删除|迁移/i;
|
|
12421
12483
|
var READ_ONLY = /\b(?:review|analy[sz]e|inspect|explain|research|investigate|audit|summari[sz]e|report)\b|调研|分析|审查|检查|解释|研究|总结|报告/i;
|
|
12422
12484
|
function inferGoalIntent(goal) {
|
|
@@ -12426,7 +12488,7 @@ function inferGoalIntent(goal) {
|
|
|
12426
12488
|
}
|
|
12427
12489
|
async function prepareLoopWorkspace(options) {
|
|
12428
12490
|
options.signal?.throwIfAborted();
|
|
12429
|
-
const root =
|
|
12491
|
+
const root = resolve13(options.root);
|
|
12430
12492
|
const intent = inferGoalIntent(options.goal);
|
|
12431
12493
|
if (intent === "read_only") return { kind: "ready", workspace: { root, mode: "current" } };
|
|
12432
12494
|
const inside = await git(root, ["rev-parse", "--is-inside-work-tree"]);
|
|
@@ -12469,7 +12531,7 @@ async function ensureWorktreeDirectoryIgnored(root) {
|
|
|
12469
12531
|
if (ignored2.code === 0) return;
|
|
12470
12532
|
const commonDirectory = await git(root, ["rev-parse", "--git-common-dir"]);
|
|
12471
12533
|
if (commonDirectory.code !== 0) throw new Error(commonDirectory.stderr.trim() || "Git common directory is unavailable");
|
|
12472
|
-
const gitDirectory =
|
|
12534
|
+
const gitDirectory = resolve13(root, commonDirectory.stdout.trim());
|
|
12473
12535
|
const infoDirectory = join17(gitDirectory, "info");
|
|
12474
12536
|
const excludePath = join17(infoDirectory, "exclude");
|
|
12475
12537
|
await mkdir12(infoDirectory, { recursive: true });
|
|
@@ -12487,7 +12549,7 @@ async function ensureWorktreeDirectoryIgnored(root) {
|
|
|
12487
12549
|
// src/loop/store.ts
|
|
12488
12550
|
import { constants as constants4 } from "fs";
|
|
12489
12551
|
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";
|
|
12490
|
-
import { dirname as dirname11, join as join18, relative as relative10, resolve as
|
|
12552
|
+
import { dirname as dirname11, join as join18, relative as relative10, resolve as resolve14, sep as sep10 } from "path";
|
|
12491
12553
|
import { randomUUID as randomUUID12 } from "crypto";
|
|
12492
12554
|
var LOOP_ID = /^[A-Za-z0-9][A-Za-z0-9._-]{0,127}$/;
|
|
12493
12555
|
var DEFAULT_MAX_BYTES2 = 5 * 1024 * 1024;
|
|
@@ -12497,7 +12559,7 @@ var LoopStore = class {
|
|
|
12497
12559
|
#maxBytes;
|
|
12498
12560
|
#appendTails = /* @__PURE__ */ new Map();
|
|
12499
12561
|
constructor(options) {
|
|
12500
|
-
this.#workspace =
|
|
12562
|
+
this.#workspace = resolve14(options.workspace);
|
|
12501
12563
|
this.#root = join18(this.#workspace, ".flavor", "loops");
|
|
12502
12564
|
this.#maxBytes = options.maxBytes ?? DEFAULT_MAX_BYTES2;
|
|
12503
12565
|
}
|
|
@@ -12596,8 +12658,8 @@ function validateId(loopId) {
|
|
|
12596
12658
|
if (!LOOP_ID.test(loopId)) throw new Error("Invalid loop id");
|
|
12597
12659
|
}
|
|
12598
12660
|
async function assertNoSymlink2(root, target) {
|
|
12599
|
-
let cursor =
|
|
12600
|
-
const stop =
|
|
12661
|
+
let cursor = resolve14(target);
|
|
12662
|
+
const stop = resolve14(root);
|
|
12601
12663
|
if (!isWithin3(stop, cursor)) throw new Error("Loop path escapes workspace");
|
|
12602
12664
|
const chain = [];
|
|
12603
12665
|
while (cursor !== stop) {
|
|
@@ -12613,14 +12675,14 @@ async function assertNoSymlink2(root, target) {
|
|
|
12613
12675
|
}
|
|
12614
12676
|
}
|
|
12615
12677
|
function isWithin3(root, path) {
|
|
12616
|
-
const value = relative10(
|
|
12678
|
+
const value = relative10(resolve14(root), resolve14(path));
|
|
12617
12679
|
return value === "" || !value.startsWith(`..${sep10}`) && value !== ".." && !value.includes(":");
|
|
12618
12680
|
}
|
|
12619
12681
|
async function canonical(path) {
|
|
12620
12682
|
try {
|
|
12621
|
-
return await realpath8(
|
|
12683
|
+
return await realpath8(resolve14(path));
|
|
12622
12684
|
} catch {
|
|
12623
|
-
return
|
|
12685
|
+
return resolve14(path);
|
|
12624
12686
|
}
|
|
12625
12687
|
}
|
|
12626
12688
|
function isCode5(error, code) {
|
|
@@ -12633,7 +12695,7 @@ import { join as join19 } from "path";
|
|
|
12633
12695
|
|
|
12634
12696
|
// src/tools/shell.ts
|
|
12635
12697
|
import { spawn as spawn3, spawnSync } from "child_process";
|
|
12636
|
-
import { isAbsolute as
|
|
12698
|
+
import { isAbsolute as isAbsolute9, relative as relative11, resolve as resolve15, sep as sep11 } from "path";
|
|
12637
12699
|
import { z as z23 } from "zod";
|
|
12638
12700
|
|
|
12639
12701
|
// src/tools/jobs.ts
|
|
@@ -12786,7 +12848,7 @@ function buildWindowsCommandLine(kind, commandLine, command, args) {
|
|
|
12786
12848
|
return `chcp 65001>nul & ${executable} -NoLogo -NoProfile -NonInteractive -ExecutionPolicy Bypass -EncodedCommand ${encoded}`;
|
|
12787
12849
|
}
|
|
12788
12850
|
function createShellTool(workspace, options = {}) {
|
|
12789
|
-
const root =
|
|
12851
|
+
const root = resolve15(workspace);
|
|
12790
12852
|
const maxBytes = options.maxOutputBytes ?? DEFAULT_MAX_OUTPUT_BYTES2;
|
|
12791
12853
|
const defaultTimeoutMs = options.defaultTimeoutMs ?? DEFAULT_SHELL_TIMEOUT_MS;
|
|
12792
12854
|
if (!Number.isSafeInteger(maxBytes) || maxBytes <= 0) throw new Error("maxOutputBytes must be a positive integer");
|
|
@@ -13085,9 +13147,9 @@ function backgroundFlag(value) {
|
|
|
13085
13147
|
return value === true || value === "true";
|
|
13086
13148
|
}
|
|
13087
13149
|
function workingDirectory(root, cwd = ".") {
|
|
13088
|
-
const candidate =
|
|
13150
|
+
const candidate = resolve15(root, cwd);
|
|
13089
13151
|
const delta = relative11(root, candidate);
|
|
13090
|
-
if (delta === ".." || delta.startsWith(`..${sep11}`) ||
|
|
13152
|
+
if (delta === ".." || delta.startsWith(`..${sep11}`) || isAbsolute9(delta)) throw new Error("Working directory is outside the workspace");
|
|
13091
13153
|
return candidate;
|
|
13092
13154
|
}
|
|
13093
13155
|
async function terminateTree(pid) {
|
|
@@ -13340,7 +13402,7 @@ function isEnvTruthy(value) {
|
|
|
13340
13402
|
|
|
13341
13403
|
// src/utils/log.ts
|
|
13342
13404
|
import { appendFile as appendFile3, mkdir as mkdir14 } from "fs/promises";
|
|
13343
|
-
import { dirname as dirname12, join as join20, resolve as
|
|
13405
|
+
import { dirname as dirname12, join as join20, resolve as resolve16 } from "path";
|
|
13344
13406
|
var pending = Promise.resolve();
|
|
13345
13407
|
var usagePending = Promise.resolve();
|
|
13346
13408
|
function usageLogPath() {
|
|
@@ -13373,7 +13435,7 @@ var AuditLogger = class {
|
|
|
13373
13435
|
#path;
|
|
13374
13436
|
#closed = false;
|
|
13375
13437
|
constructor(workspace) {
|
|
13376
|
-
this.#path = join20(
|
|
13438
|
+
this.#path = join20(resolve16(workspace), ".flavor", "audit.jsonl");
|
|
13377
13439
|
}
|
|
13378
13440
|
get path() {
|
|
13379
13441
|
return this.#path;
|
|
@@ -14235,7 +14297,7 @@ function isRecord4(value) {
|
|
|
14235
14297
|
}
|
|
14236
14298
|
|
|
14237
14299
|
// src/mcp/sdk.ts
|
|
14238
|
-
import { resolve as
|
|
14300
|
+
import { resolve as resolve17 } from "path";
|
|
14239
14301
|
import { Client } from "@modelcontextprotocol/sdk/client/index.js";
|
|
14240
14302
|
import {
|
|
14241
14303
|
getDefaultEnvironment,
|
|
@@ -14283,7 +14345,7 @@ async function connectSdkMcpClient(input, dependencies2 = DEFAULT_DEPENDENCIES)
|
|
|
14283
14345
|
command: config.command,
|
|
14284
14346
|
args: [...config.args],
|
|
14285
14347
|
env: { ...dependencies2.defaultEnvironment(), ...config.env },
|
|
14286
|
-
cwd:
|
|
14348
|
+
cwd: resolve17(input.workspace, config.cwd ?? "."),
|
|
14287
14349
|
// MCP servers frequently log startup banners to stderr. Inheriting that
|
|
14288
14350
|
// stream corrupts Ink's input area, so connection errors are surfaced
|
|
14289
14351
|
// through McpManager diagnostics instead.
|
|
@@ -14488,8 +14550,8 @@ var OAuthCallbackAuthProvider = class {
|
|
|
14488
14550
|
async #startCallbackServer(expectedState, signal) {
|
|
14489
14551
|
let serverResolve;
|
|
14490
14552
|
let serverReject;
|
|
14491
|
-
const codePromise = new Promise((
|
|
14492
|
-
serverResolve =
|
|
14553
|
+
const codePromise = new Promise((resolve42, reject) => {
|
|
14554
|
+
serverResolve = resolve42;
|
|
14493
14555
|
serverReject = reject;
|
|
14494
14556
|
});
|
|
14495
14557
|
const server = createServer3((req, res) => {
|
|
@@ -14523,7 +14585,7 @@ var OAuthCallbackAuthProvider = class {
|
|
|
14523
14585
|
);
|
|
14524
14586
|
serverResolve(code);
|
|
14525
14587
|
});
|
|
14526
|
-
return new Promise((
|
|
14588
|
+
return new Promise((resolve42, reject) => {
|
|
14527
14589
|
server.once("error", (error) => {
|
|
14528
14590
|
reject(new Error(`OAuth callback server failed: ${error.message}`));
|
|
14529
14591
|
});
|
|
@@ -14540,7 +14602,7 @@ var OAuthCallbackAuthProvider = class {
|
|
|
14540
14602
|
signal.addEventListener("abort", () => {
|
|
14541
14603
|
serverReject(new Error("OAuth flow aborted"));
|
|
14542
14604
|
}, { once: true });
|
|
14543
|
-
|
|
14605
|
+
resolve42({ redirectUri, promise: codePromise });
|
|
14544
14606
|
});
|
|
14545
14607
|
});
|
|
14546
14608
|
}
|
|
@@ -14683,7 +14745,7 @@ function isStoredToken(value) {
|
|
|
14683
14745
|
|
|
14684
14746
|
// src/permissions/policy.ts
|
|
14685
14747
|
import { readFile as readFile16 } from "fs/promises";
|
|
14686
|
-
import { join as join22, resolve as
|
|
14748
|
+
import { join as join22, resolve as resolve18 } from "path";
|
|
14687
14749
|
import { z as z25 } from "zod";
|
|
14688
14750
|
var DecisionSchema2 = z25.enum(["allow", "ask", "deny"]);
|
|
14689
14751
|
var PrefixSchema = z25.array(z25.string().min(1).max(1024)).min(1).max(64);
|
|
@@ -14719,10 +14781,10 @@ var CompiledPermissionPolicy = class {
|
|
|
14719
14781
|
};
|
|
14720
14782
|
async function loadPermissionPolicy(options) {
|
|
14721
14783
|
const sources = [
|
|
14722
|
-
...options.managedPath === void 0 ? [] : [{ tier: "managed", path:
|
|
14723
|
-
{ tier: "user", path: join22(
|
|
14724
|
-
{ tier: "project", path: join22(
|
|
14725
|
-
{ tier: "local", path: join22(
|
|
14784
|
+
...options.managedPath === void 0 ? [] : [{ tier: "managed", path: resolve18(options.managedPath) }],
|
|
14785
|
+
{ tier: "user", path: join22(resolve18(options.home), ".flavor-code", "permissions.json") },
|
|
14786
|
+
{ tier: "project", path: join22(resolve18(options.workspace), ".flavor", "permissions.json") },
|
|
14787
|
+
{ tier: "local", path: join22(resolve18(options.workspace), ".flavor", "permissions.local.json") }
|
|
14726
14788
|
];
|
|
14727
14789
|
const rules = [];
|
|
14728
14790
|
for (const source of sources) {
|
|
@@ -14944,7 +15006,7 @@ function data(value) {
|
|
|
14944
15006
|
// src/skills/registry.ts
|
|
14945
15007
|
import { constants as constants5 } from "fs";
|
|
14946
15008
|
import { lstat as lstat7, open as defaultOpen, readdir as readdir5, realpath as realpath9 } from "fs/promises";
|
|
14947
|
-
import { isAbsolute as
|
|
15009
|
+
import { isAbsolute as isAbsolute10, relative as relative12, resolve as resolve19, sep as sep12 } from "path";
|
|
14948
15010
|
import { marked } from "marked";
|
|
14949
15011
|
import { parseDocument } from "yaml";
|
|
14950
15012
|
var SKILL_NAME = /^[a-z0-9]+(?:-[a-z0-9]+)*$/;
|
|
@@ -15069,13 +15131,13 @@ var SkillRegistry = class {
|
|
|
15069
15131
|
const record3 = await this.#recordFor(skill);
|
|
15070
15132
|
const normalized = normalizeReference(reference);
|
|
15071
15133
|
const parts = normalized.split("/");
|
|
15072
|
-
if (
|
|
15134
|
+
if (isAbsolute10(reference) || parts.includes("..") || parts.length !== 2 || !RESOURCE_DIRECTORIES.has(parts[0] ?? "") || !RESOURCE_NAME.test(parts[1] ?? "")) {
|
|
15073
15135
|
throw new Error(`Resource reference escapes the skill root or is not a direct resource: ${reference}`);
|
|
15074
15136
|
}
|
|
15075
15137
|
const body = await this.loadBody(record3.metadata);
|
|
15076
15138
|
const references = extractResourceReferences(body);
|
|
15077
15139
|
if (!references.has(normalized)) throw new Error(`Skill resource is not directly referenced: ${reference}`);
|
|
15078
|
-
const candidate =
|
|
15140
|
+
const candidate = resolve19(record3.metadata.root, ...parts);
|
|
15079
15141
|
const file = await openVerifiedFile(candidate, record3.metadata.root, "Resource", this.#options.openFile);
|
|
15080
15142
|
try {
|
|
15081
15143
|
if (file.snapshot.size > BigInt(this.#options.maxResourceBytes)) throw new Error(`Skill resource is too large: ${reference}`);
|
|
@@ -15104,7 +15166,7 @@ var SkillRegistry = class {
|
|
|
15104
15166
|
async #discoverSource(roots, source) {
|
|
15105
15167
|
const found = [];
|
|
15106
15168
|
for (const configuredRoot of roots) {
|
|
15107
|
-
const root =
|
|
15169
|
+
const root = resolve19(configuredRoot);
|
|
15108
15170
|
let entries;
|
|
15109
15171
|
let physicalRoot = root;
|
|
15110
15172
|
try {
|
|
@@ -15119,13 +15181,13 @@ var SkillRegistry = class {
|
|
|
15119
15181
|
}
|
|
15120
15182
|
entries.sort((left, right) => compareCodePoints(left.name, right.name));
|
|
15121
15183
|
for (const entry2 of entries) {
|
|
15122
|
-
const skillRoot =
|
|
15184
|
+
const skillRoot = resolve19(root, entry2.name);
|
|
15123
15185
|
try {
|
|
15124
15186
|
if (entry2.isSymbolicLink()) throw new Error("Symlinked skill directories are not allowed");
|
|
15125
15187
|
if (!entry2.isDirectory()) continue;
|
|
15126
15188
|
const physicalSkillRoot = await realpath9(skillRoot);
|
|
15127
15189
|
if (!isWithin4(physicalRoot, physicalSkillRoot)) throw new Error("Skill directory escapes its registry root");
|
|
15128
|
-
const skillFile =
|
|
15190
|
+
const skillFile = resolve19(skillRoot, "SKILL.md");
|
|
15129
15191
|
const parsed = await readFrontmatterFile(
|
|
15130
15192
|
skillFile,
|
|
15131
15193
|
physicalSkillRoot,
|
|
@@ -15327,7 +15389,7 @@ function decodeUtf8(content, label) {
|
|
|
15327
15389
|
}
|
|
15328
15390
|
function isWithin4(root, candidate) {
|
|
15329
15391
|
const delta = relative12(root, candidate);
|
|
15330
|
-
return delta === "" || delta !== ".." && !delta.startsWith(`..${sep12}`) && !
|
|
15392
|
+
return delta === "" || delta !== ".." && !delta.startsWith(`..${sep12}`) && !isAbsolute10(delta);
|
|
15331
15393
|
}
|
|
15332
15394
|
function isMissing(error) {
|
|
15333
15395
|
return typeof error === "object" && error !== null && "code" in error && error.code === "ENOENT";
|
|
@@ -15491,7 +15553,7 @@ function createSkillResourceTool(registry) {
|
|
|
15491
15553
|
// src/session/store.ts
|
|
15492
15554
|
import { constants as constants6 } from "fs";
|
|
15493
15555
|
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";
|
|
15494
|
-
import { basename as basename5, dirname as dirname15, join as join23, relative as relative13, resolve as
|
|
15556
|
+
import { basename as basename5, dirname as dirname15, join as join23, relative as relative13, resolve as resolve20, sep as sep13 } from "path";
|
|
15495
15557
|
import { randomUUID as randomUUID13 } from "crypto";
|
|
15496
15558
|
import { z as z27 } from "zod";
|
|
15497
15559
|
|
|
@@ -16179,13 +16241,14 @@ var SessionDocumentSchema = SessionBaseSchema.extend({
|
|
|
16179
16241
|
}).strict(),
|
|
16180
16242
|
timeline: TimelineSchema
|
|
16181
16243
|
}).strict();
|
|
16244
|
+
var INVALID_SESSION_LOCK_STALE_MS = 3e4;
|
|
16182
16245
|
var SessionStore = class {
|
|
16183
16246
|
#workspace;
|
|
16184
16247
|
#sessions;
|
|
16185
16248
|
#maxBytes;
|
|
16186
16249
|
#maxSessions;
|
|
16187
16250
|
constructor(options) {
|
|
16188
|
-
this.#workspace =
|
|
16251
|
+
this.#workspace = resolve20(options.workspace);
|
|
16189
16252
|
this.#sessions = join23(this.#workspace, ".flavor", "sessions");
|
|
16190
16253
|
this.#maxBytes = options.maxBytes ?? DEFAULT_MAX_SESSION_BYTES;
|
|
16191
16254
|
if (!Number.isSafeInteger(this.#maxBytes) || this.#maxBytes < 256) throw new Error("maxBytes must be an integer of at least 256");
|
|
@@ -16233,6 +16296,47 @@ var SessionStore = class {
|
|
|
16233
16296
|
}
|
|
16234
16297
|
await this.#prune().catch(() => void 0);
|
|
16235
16298
|
}
|
|
16299
|
+
/** Hold this lease for the full lifetime of a writable session runtime. */
|
|
16300
|
+
async acquireLease(sessionId) {
|
|
16301
|
+
SessionIdSchema.parse(sessionId);
|
|
16302
|
+
await this.#prepareDirectory();
|
|
16303
|
+
const path = join23(this.#sessions, `.${sessionId}.lock`);
|
|
16304
|
+
const token = JSON.stringify({ pid: process.pid, nonce: randomUUID13(), createdAt: (/* @__PURE__ */ new Date()).toISOString() });
|
|
16305
|
+
for (let attempt = 0; attempt < 3; attempt += 1) {
|
|
16306
|
+
let handle;
|
|
16307
|
+
let created = false;
|
|
16308
|
+
try {
|
|
16309
|
+
handle = await open7(path, constants6.O_CREAT | constants6.O_EXCL | constants6.O_WRONLY, 384);
|
|
16310
|
+
created = true;
|
|
16311
|
+
await handle.writeFile(token, "utf8");
|
|
16312
|
+
await handle.sync();
|
|
16313
|
+
await handle.close();
|
|
16314
|
+
handle = void 0;
|
|
16315
|
+
let released = false;
|
|
16316
|
+
return {
|
|
16317
|
+
sessionId,
|
|
16318
|
+
release: async () => {
|
|
16319
|
+
if (released) return;
|
|
16320
|
+
try {
|
|
16321
|
+
if (await readFile17(path, "utf8") === token) await rm8(path, { force: true });
|
|
16322
|
+
} catch (error) {
|
|
16323
|
+
if (!isCode8(error, "ENOENT")) throw error;
|
|
16324
|
+
}
|
|
16325
|
+
released = true;
|
|
16326
|
+
}
|
|
16327
|
+
};
|
|
16328
|
+
} catch (error) {
|
|
16329
|
+
await handle?.close().catch(() => void 0);
|
|
16330
|
+
if (created) await rm8(path, { force: true }).catch(() => void 0);
|
|
16331
|
+
if (!isCode8(error, "EEXIST")) throw error;
|
|
16332
|
+
const owner2 = await reclaimStaleSessionLock(path);
|
|
16333
|
+
if (owner2 !== void 0) {
|
|
16334
|
+
throw new Error(`Session "${sessionId}" is already active in process ${owner2}. Close that session before resuming it here.`);
|
|
16335
|
+
}
|
|
16336
|
+
}
|
|
16337
|
+
}
|
|
16338
|
+
throw new Error(`Session "${sessionId}" could not acquire its writer lease because the lock kept changing`);
|
|
16339
|
+
}
|
|
16236
16340
|
async load(sessionId) {
|
|
16237
16341
|
if (sessionId === void 0) {
|
|
16238
16342
|
const [latest] = await this.list();
|
|
@@ -16330,6 +16434,8 @@ var SessionStore = class {
|
|
|
16330
16434
|
if (isCode8(error, "ENOENT")) return;
|
|
16331
16435
|
throw error;
|
|
16332
16436
|
}
|
|
16437
|
+
const owner2 = await reclaimStaleSessionLock(join23(this.#sessions, `.${sessionId}.lock`));
|
|
16438
|
+
if (owner2 !== void 0) throw new Error(`Session "${sessionId}" is active in process ${owner2} and cannot be deleted`);
|
|
16333
16439
|
const path = this.#path(sessionId);
|
|
16334
16440
|
try {
|
|
16335
16441
|
const metadata = await lstat8(path);
|
|
@@ -16342,15 +16448,21 @@ var SessionStore = class {
|
|
|
16342
16448
|
}
|
|
16343
16449
|
#path(sessionId) {
|
|
16344
16450
|
SessionIdSchema.parse(sessionId);
|
|
16345
|
-
const path =
|
|
16451
|
+
const path = resolve20(this.#sessions, `${sessionId}.jsonl`);
|
|
16346
16452
|
if (!isWithin5(this.#sessions, path)) throw new Error("Invalid session id");
|
|
16347
16453
|
return path;
|
|
16348
16454
|
}
|
|
16349
16455
|
async #prune() {
|
|
16350
16456
|
const entries = await this.list();
|
|
16351
16457
|
if (entries.length <= this.#maxSessions) return;
|
|
16352
|
-
|
|
16353
|
-
for (const entry2 of
|
|
16458
|
+
let retained = 0;
|
|
16459
|
+
for (const entry2 of entries) {
|
|
16460
|
+
const owner2 = await reclaimStaleSessionLock(join23(this.#sessions, `.${entry2.sessionId}.lock`));
|
|
16461
|
+
if (owner2 !== void 0) continue;
|
|
16462
|
+
if (retained < this.#maxSessions) {
|
|
16463
|
+
retained += 1;
|
|
16464
|
+
continue;
|
|
16465
|
+
}
|
|
16354
16466
|
await rm8(this.#path(entry2.sessionId), { force: true }).catch(() => void 0);
|
|
16355
16467
|
await rm8(join23(this.#sessions, `${entry2.sessionId}.events.jsonl`), { force: true }).catch(() => void 0);
|
|
16356
16468
|
}
|
|
@@ -16398,6 +16510,47 @@ function storedSessionVersion(raw) {
|
|
|
16398
16510
|
const candidate = first.__meta === true ? first.version : JSON.parse(trimmed).version;
|
|
16399
16511
|
return typeof candidate === "number" && Number.isInteger(candidate) ? candidate : 0;
|
|
16400
16512
|
}
|
|
16513
|
+
async function reclaimStaleSessionLock(path) {
|
|
16514
|
+
try {
|
|
16515
|
+
const token = await readFile17(path, "utf8");
|
|
16516
|
+
const metadata = await stat7(path);
|
|
16517
|
+
const owner2 = parseSessionLockOwner(token);
|
|
16518
|
+
if (owner2 !== void 0 && isProcessAlive2(owner2)) return owner2;
|
|
16519
|
+
if (owner2 === void 0 && Date.now() - metadata.mtimeMs <= INVALID_SESSION_LOCK_STALE_MS) {
|
|
16520
|
+
throw new Error(`Session writer lock ${path} is incomplete or invalid; retry after the owning process exits`);
|
|
16521
|
+
}
|
|
16522
|
+
if (await readFile17(path, "utf8") !== token) return void 0;
|
|
16523
|
+
const stale = `${path}.stale-${process.pid}-${randomUUID13()}`;
|
|
16524
|
+
try {
|
|
16525
|
+
await rename6(path, stale);
|
|
16526
|
+
await rm8(stale, { force: true });
|
|
16527
|
+
} catch (error) {
|
|
16528
|
+
if (!isCode8(error, "ENOENT")) throw error;
|
|
16529
|
+
}
|
|
16530
|
+
return void 0;
|
|
16531
|
+
} catch (error) {
|
|
16532
|
+
if (isCode8(error, "ENOENT")) return void 0;
|
|
16533
|
+
throw error;
|
|
16534
|
+
}
|
|
16535
|
+
}
|
|
16536
|
+
function parseSessionLockOwner(token) {
|
|
16537
|
+
try {
|
|
16538
|
+
const value = JSON.parse(token);
|
|
16539
|
+
if (typeof value !== "object" || value === null || !("pid" in value)) return void 0;
|
|
16540
|
+
const pid = value.pid;
|
|
16541
|
+
return typeof pid === "number" && Number.isSafeInteger(pid) && pid > 0 ? pid : void 0;
|
|
16542
|
+
} catch {
|
|
16543
|
+
return void 0;
|
|
16544
|
+
}
|
|
16545
|
+
}
|
|
16546
|
+
function isProcessAlive2(pid) {
|
|
16547
|
+
try {
|
|
16548
|
+
process.kill(pid, 0);
|
|
16549
|
+
return true;
|
|
16550
|
+
} catch (error) {
|
|
16551
|
+
return isCode8(error, "EPERM");
|
|
16552
|
+
}
|
|
16553
|
+
}
|
|
16401
16554
|
async function boundedRead(path, maxBytes) {
|
|
16402
16555
|
const metadata = await stat7(path);
|
|
16403
16556
|
if (!metadata.isFile()) throw new Error("Session path is not a regular file");
|
|
@@ -16544,8 +16697,8 @@ function isRecord6(value) {
|
|
|
16544
16697
|
return typeof value === "object" && value !== null && !Array.isArray(value);
|
|
16545
16698
|
}
|
|
16546
16699
|
async function assertNoSymlink3(root, target) {
|
|
16547
|
-
let cursor =
|
|
16548
|
-
const stop =
|
|
16700
|
+
let cursor = resolve20(target);
|
|
16701
|
+
const stop = resolve20(root);
|
|
16549
16702
|
if (!isWithin5(stop, cursor)) throw new Error("Session path escapes the workspace");
|
|
16550
16703
|
const chain = [];
|
|
16551
16704
|
while (cursor !== stop) {
|
|
@@ -16561,14 +16714,14 @@ async function assertNoSymlink3(root, target) {
|
|
|
16561
16714
|
}
|
|
16562
16715
|
}
|
|
16563
16716
|
function isWithin5(root, path) {
|
|
16564
|
-
const value = relative13(
|
|
16717
|
+
const value = relative13(resolve20(root), resolve20(path));
|
|
16565
16718
|
return value === "" || !value.startsWith(`..${sep13}`) && value !== ".." && !value.includes(":");
|
|
16566
16719
|
}
|
|
16567
16720
|
async function canonical2(path) {
|
|
16568
16721
|
try {
|
|
16569
|
-
return await realpath10(
|
|
16722
|
+
return await realpath10(resolve20(path));
|
|
16570
16723
|
} catch {
|
|
16571
|
-
return
|
|
16724
|
+
return resolve20(path);
|
|
16572
16725
|
}
|
|
16573
16726
|
}
|
|
16574
16727
|
function isCode8(error, code) {
|
|
@@ -16595,13 +16748,13 @@ function sanitize2(input, seen = /* @__PURE__ */ new WeakSet()) {
|
|
|
16595
16748
|
// src/session/tree.ts
|
|
16596
16749
|
import { randomUUID as randomUUID15 } from "crypto";
|
|
16597
16750
|
import { mkdir as mkdir17, readFile as readFile19, rename as rename8, writeFile as writeFile11 } from "fs/promises";
|
|
16598
|
-
import { dirname as dirname17, join as join25, resolve as
|
|
16751
|
+
import { dirname as dirname17, join as join25, resolve as resolve22 } from "path";
|
|
16599
16752
|
|
|
16600
16753
|
// src/session/checkpoint.ts
|
|
16601
16754
|
import { createHash as createHash15, randomUUID as randomUUID14 } from "crypto";
|
|
16602
16755
|
import { execFile as execFile2 } from "child_process";
|
|
16603
16756
|
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";
|
|
16604
|
-
import { dirname as dirname16, join as join24, relative as relative14, resolve as
|
|
16757
|
+
import { dirname as dirname16, join as join24, relative as relative14, resolve as resolve21, sep as sep14 } from "path";
|
|
16605
16758
|
var EXCLUDED = /* @__PURE__ */ new Set([".git", ".flavor", "node_modules", "dist", "build", "release", ".worktrees"]);
|
|
16606
16759
|
var MAX_FILE_BYTES = 16 * 1024 * 1024;
|
|
16607
16760
|
var MAX_TOTAL_BYTES = 128 * 1024 * 1024;
|
|
@@ -16613,7 +16766,7 @@ var WorkspaceCheckpointStore = class {
|
|
|
16613
16766
|
#maxFileBytes;
|
|
16614
16767
|
#maxTotalBytes;
|
|
16615
16768
|
constructor(options) {
|
|
16616
|
-
this.#workspace =
|
|
16769
|
+
this.#workspace = resolve21(options.workspace);
|
|
16617
16770
|
this.#root = join24(this.#workspace, ".flavor", "checkpoints");
|
|
16618
16771
|
this.#objects = join24(this.#root, "objects");
|
|
16619
16772
|
this.#manifests = join24(this.#root, "manifests");
|
|
@@ -16751,9 +16904,9 @@ function normalizeRelative(path) {
|
|
|
16751
16904
|
}
|
|
16752
16905
|
function contained(workspace, path) {
|
|
16753
16906
|
if (path.length === 0 || path.includes("\0")) throw new Error("Invalid checkpoint path");
|
|
16754
|
-
const absolute =
|
|
16907
|
+
const absolute = resolve21(workspace, path);
|
|
16755
16908
|
const delta = relative14(workspace, absolute);
|
|
16756
|
-
if (delta === ".." || delta.startsWith(`..${sep14}`) || delta === "" ||
|
|
16909
|
+
if (delta === ".." || delta.startsWith(`..${sep14}`) || delta === "" || resolve21(absolute) !== absolute) {
|
|
16757
16910
|
throw new Error(`Checkpoint path escapes workspace: ${path}`);
|
|
16758
16911
|
}
|
|
16759
16912
|
return absolute;
|
|
@@ -16780,14 +16933,14 @@ var SessionHistory = class _SessionHistory {
|
|
|
16780
16933
|
#leafId;
|
|
16781
16934
|
#unrevert;
|
|
16782
16935
|
constructor(options, document) {
|
|
16783
|
-
this.#path = join25(
|
|
16936
|
+
this.#path = join25(resolve22(options.workspace), ".flavor", "session-trees", options.sessionId, "tree.json");
|
|
16784
16937
|
this.#checkpoints = new WorkspaceCheckpointStore({ workspace: options.workspace });
|
|
16785
16938
|
this.#restoreContext = options.restoreContext;
|
|
16786
16939
|
this.#nodes = boundedNodes(document.nodes);
|
|
16787
16940
|
this.#leafId = document.leafId !== null && this.#nodes.some((node) => node.id === document.leafId) ? document.leafId : this.#nodes.at(-1)?.id ?? null;
|
|
16788
16941
|
}
|
|
16789
16942
|
static async open(options) {
|
|
16790
|
-
const path = join25(
|
|
16943
|
+
const path = join25(resolve22(options.workspace), ".flavor", "session-trees", options.sessionId, "tree.json");
|
|
16791
16944
|
let document = { version: 1, sessionId: options.sessionId, leafId: null, nodes: [] };
|
|
16792
16945
|
try {
|
|
16793
16946
|
const parsed = JSON.parse(await readFile19(path, "utf8"));
|
|
@@ -16945,7 +17098,7 @@ function parseMemoryRestartMarker(json) {
|
|
|
16945
17098
|
import { constants as constants7 } from "fs";
|
|
16946
17099
|
import { randomUUID as randomUUID16 } from "crypto";
|
|
16947
17100
|
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";
|
|
16948
|
-
import { isAbsolute as
|
|
17101
|
+
import { isAbsolute as isAbsolute11, join as join27, relative as relative15, resolve as resolve23, sep as sep15 } from "path";
|
|
16949
17102
|
import { z as z28 } from "zod";
|
|
16950
17103
|
var DateKeySchema = z28.string().regex(/^\d{4}-\d{2}-\d{2}$/);
|
|
16951
17104
|
var ReviewSchema = z28.object({
|
|
@@ -16999,7 +17152,7 @@ var ProjectSleepOrganizer = class {
|
|
|
16999
17152
|
#sessions;
|
|
17000
17153
|
#generate;
|
|
17001
17154
|
constructor(options) {
|
|
17002
|
-
this.#workspace =
|
|
17155
|
+
this.#workspace = resolve23(options.workspace);
|
|
17003
17156
|
this.#sleepDirectory = join27(this.#workspace, ".flavor", "sleep");
|
|
17004
17157
|
this.#sessions = options.sessions ?? new SessionStore({ workspace: this.#workspace });
|
|
17005
17158
|
this.#generate = options.generate;
|
|
@@ -17424,8 +17577,8 @@ async function assertNotSymlink(path) {
|
|
|
17424
17577
|
function isWithin6(root, candidate) {
|
|
17425
17578
|
const path = relative15(root, candidate);
|
|
17426
17579
|
if (path === "") return true;
|
|
17427
|
-
if (path === ".." || path.startsWith(`..${sep15}`) ||
|
|
17428
|
-
return process.platform !== "win32" ||
|
|
17580
|
+
if (path === ".." || path.startsWith(`..${sep15}`) || isAbsolute11(path)) return false;
|
|
17581
|
+
return process.platform !== "win32" || resolve23(root, path).toLowerCase().startsWith(`${resolve23(root).toLowerCase()}${sep15}`);
|
|
17429
17582
|
}
|
|
17430
17583
|
function hasCode(error, code) {
|
|
17431
17584
|
return typeof error === "object" && error !== null && "code" in error && error.code === code;
|
|
@@ -17475,7 +17628,7 @@ function computeSessionStats(documents) {
|
|
|
17475
17628
|
// src/tools/files.ts
|
|
17476
17629
|
import { constants as constants8 } from "fs";
|
|
17477
17630
|
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";
|
|
17478
|
-
import { dirname as dirname18, isAbsolute as
|
|
17631
|
+
import { dirname as dirname18, isAbsolute as isAbsolute12, relative as relative16, resolve as resolve24, sep as sep16 } from "path";
|
|
17479
17632
|
import { randomUUID as randomUUID17 } from "crypto";
|
|
17480
17633
|
import { z as z29 } from "zod";
|
|
17481
17634
|
|
|
@@ -17792,9 +17945,9 @@ function createApplyPatchTool(workspace, options = {}) {
|
|
|
17792
17945
|
};
|
|
17793
17946
|
}
|
|
17794
17947
|
function createPathGuard(workspace) {
|
|
17795
|
-
const root =
|
|
17948
|
+
const root = resolve24(workspace);
|
|
17796
17949
|
const lexical = (input) => {
|
|
17797
|
-
const candidate =
|
|
17950
|
+
const candidate = resolve24(root, input);
|
|
17798
17951
|
if (!within2(root, candidate)) throw new Error("Path is outside the workspace");
|
|
17799
17952
|
return candidate;
|
|
17800
17953
|
};
|
|
@@ -17964,7 +18117,7 @@ async function readBounded2(path, maxBytes, signal, openFile2) {
|
|
|
17964
18117
|
}
|
|
17965
18118
|
function within2(root, candidate) {
|
|
17966
18119
|
const delta = relative16(root, candidate);
|
|
17967
|
-
return delta === "" || !delta.startsWith(`..${sep16}`) && delta !== ".." && !
|
|
18120
|
+
return delta === "" || !delta.startsWith(`..${sep16}`) && delta !== ".." && !isAbsolute12(delta);
|
|
17968
18121
|
}
|
|
17969
18122
|
function forceFlag(value) {
|
|
17970
18123
|
return value === true || value === "true";
|
|
@@ -18188,7 +18341,7 @@ function matchesLines(source, start, expected) {
|
|
|
18188
18341
|
// src/tools/search.ts
|
|
18189
18342
|
import { spawn as spawn4 } from "child_process";
|
|
18190
18343
|
import { open as open11, opendir, stat as stat9 } from "fs/promises";
|
|
18191
|
-
import { dirname as dirname19, isAbsolute as
|
|
18344
|
+
import { dirname as dirname19, isAbsolute as isAbsolute13, relative as relative17, resolve as resolve25, sep as sep17 } from "path";
|
|
18192
18345
|
import { rgPath as bundledRgPath } from "@vscode/ripgrep";
|
|
18193
18346
|
import createIgnore from "ignore";
|
|
18194
18347
|
import { z as z30 } from "zod";
|
|
@@ -18288,7 +18441,7 @@ var GrepInput = z30.object({
|
|
|
18288
18441
|
limit: z30.coerce.number().int().positive().max(1e5).nullable().optional()
|
|
18289
18442
|
});
|
|
18290
18443
|
function createGlobTool(workspace, options = {}) {
|
|
18291
|
-
const root =
|
|
18444
|
+
const root = resolve25(workspace);
|
|
18292
18445
|
const resources = searchResources(options);
|
|
18293
18446
|
return {
|
|
18294
18447
|
name: "Glob",
|
|
@@ -18325,7 +18478,7 @@ function createGlobTool(workspace, options = {}) {
|
|
|
18325
18478
|
};
|
|
18326
18479
|
}
|
|
18327
18480
|
function createGrepTool(workspace, options = {}) {
|
|
18328
|
-
const root =
|
|
18481
|
+
const root = resolve25(workspace);
|
|
18329
18482
|
const resources = searchResources(options);
|
|
18330
18483
|
return {
|
|
18331
18484
|
name: "Grep",
|
|
@@ -18371,7 +18524,7 @@ async function rgFiles(root, start, pattern, signal, executable, argsPrefix, lim
|
|
|
18371
18524
|
const paths = [];
|
|
18372
18525
|
const parser = delimitedParser(0, (record3) => {
|
|
18373
18526
|
if (record3.length === 0) return true;
|
|
18374
|
-
const path = normalizedRelative(root,
|
|
18527
|
+
const path = normalizedRelative(root, resolve25(root, decodeUtf82(record3, "ripgrep path")));
|
|
18375
18528
|
if (!matcher.test(path) || ignored(path, false, ignoreLayers)) return true;
|
|
18376
18529
|
retainTopK(paths, path, limit, comparePaths);
|
|
18377
18530
|
return true;
|
|
@@ -18436,7 +18589,7 @@ async function hydrateContext(root, matches, context, signal, maxFileBytes, maxT
|
|
|
18436
18589
|
let totalBytes = 0;
|
|
18437
18590
|
for (const [path, pathMatches] of byPath) {
|
|
18438
18591
|
abort(signal);
|
|
18439
|
-
const absolute =
|
|
18592
|
+
const absolute = resolve25(root, path);
|
|
18440
18593
|
const contents = await readBoundedFile(absolute, maxFileBytes, signal);
|
|
18441
18594
|
if (contents === void 0) continue;
|
|
18442
18595
|
totalBytes += contents.length;
|
|
@@ -18463,7 +18616,7 @@ async function nodeGrep(root, start, glob, type, expression, context, signal, ma
|
|
|
18463
18616
|
if (searchedBytes >= maxSearchBytes) break;
|
|
18464
18617
|
if (matcher !== void 0 && !matcher.test(path)) continue;
|
|
18465
18618
|
if (extension2 !== void 0 && !extension2.some((suffix) => path.endsWith(suffix))) continue;
|
|
18466
|
-
const absolute =
|
|
18619
|
+
const absolute = resolve25(root, path);
|
|
18467
18620
|
const bytes = await readBoundedFile(absolute, Math.min(maxFileBytes, maxSearchBytes - searchedBytes), signal);
|
|
18468
18621
|
if (bytes === void 0) continue;
|
|
18469
18622
|
searchedBytes += bytes.length;
|
|
@@ -18503,7 +18656,7 @@ async function nodeFiles(root, start, signal, accept, limit, resources, ignoreBu
|
|
|
18503
18656
|
for (let index = 0; index < segments.length; index += 1) {
|
|
18504
18657
|
noteIgnoreDirectory(ignoreBudget);
|
|
18505
18658
|
await loadIgnoreFiles(ancestor, normalizedRelative(root, ancestor), ignores, ignoreBudget, signal);
|
|
18506
|
-
ancestor =
|
|
18659
|
+
ancestor = resolve25(ancestor, segments[index]);
|
|
18507
18660
|
}
|
|
18508
18661
|
}
|
|
18509
18662
|
async function walk(directory) {
|
|
@@ -18522,7 +18675,7 @@ async function nodeFiles(root, start, signal, accept, limit, resources, ignoreBu
|
|
|
18522
18675
|
entries.sort((a, b) => comparePaths(a.isDirectory() ? `${a.name}/` : a.name, b.isDirectory() ? `${b.name}/` : b.name));
|
|
18523
18676
|
for (const entry2 of entries) {
|
|
18524
18677
|
abort(signal);
|
|
18525
|
-
const absolute =
|
|
18678
|
+
const absolute = resolve25(directory, entry2.name);
|
|
18526
18679
|
const path = normalizedRelative(root, absolute);
|
|
18527
18680
|
if (entry2.name === ".git" || !includeIgnored && ignored(path, entry2.isDirectory(), ignores)) continue;
|
|
18528
18681
|
if (entry2.isDirectory()) await walk(absolute);
|
|
@@ -18542,7 +18695,7 @@ async function collectIgnoreLayers(root, start, signal, resources, ignoreBudget)
|
|
|
18542
18695
|
for (let index = 0; index < segments.length; index += 1) {
|
|
18543
18696
|
noteIgnoreDirectory(ignoreBudget);
|
|
18544
18697
|
await loadIgnoreFiles(ancestor, normalizedRelative(root, ancestor), layers, ignoreBudget, signal);
|
|
18545
|
-
ancestor =
|
|
18698
|
+
ancestor = resolve25(ancestor, segments[index]);
|
|
18546
18699
|
}
|
|
18547
18700
|
async function walk(directory) {
|
|
18548
18701
|
abort(signal);
|
|
@@ -18556,7 +18709,7 @@ async function collectIgnoreLayers(root, start, signal, resources, ignoreBudget)
|
|
|
18556
18709
|
noteIgnoreEntry(ignoreBudget);
|
|
18557
18710
|
if (count > resources.maxEntriesPerDirectory) throw new Error(`Per-directory entry limit of ${resources.maxEntriesPerDirectory} exceeded: ${relativeDirectory}`);
|
|
18558
18711
|
if (!entry2.isDirectory() || entry2.name === ".git") continue;
|
|
18559
|
-
const absolute =
|
|
18712
|
+
const absolute = resolve25(directory, entry2.name);
|
|
18560
18713
|
const path = normalizedRelative(root, absolute);
|
|
18561
18714
|
if (!ignored(path, true, layers)) await walk(absolute);
|
|
18562
18715
|
}
|
|
@@ -18566,7 +18719,7 @@ async function collectIgnoreLayers(root, start, signal, resources, ignoreBudget)
|
|
|
18566
18719
|
}
|
|
18567
18720
|
async function loadIgnoreFiles(directory, base, rules, budget, signal) {
|
|
18568
18721
|
for (const name of [".gitignore", ".ignore"]) {
|
|
18569
|
-
const path =
|
|
18722
|
+
const path = resolve25(directory, name);
|
|
18570
18723
|
const contents = await readOptionalIgnoreFile(path, budget.limits.maxIgnoreFileBytes, signal);
|
|
18571
18724
|
if (contents === void 0) continue;
|
|
18572
18725
|
budget.totalBytes += contents.length;
|
|
@@ -18672,9 +18825,9 @@ function searchPath(path) {
|
|
|
18672
18825
|
return path?.trim().toLowerCase() === "null" ? void 0 : path ?? void 0;
|
|
18673
18826
|
}
|
|
18674
18827
|
function scope(root, path = ".") {
|
|
18675
|
-
const candidate =
|
|
18828
|
+
const candidate = resolve25(root, path);
|
|
18676
18829
|
const delta = relative17(root, candidate);
|
|
18677
|
-
if (delta === ".." || delta.startsWith(`..${sep17}`) ||
|
|
18830
|
+
if (delta === ".." || delta.startsWith(`..${sep17}`) || isAbsolute13(delta)) throw new Error("Path is outside the workspace");
|
|
18678
18831
|
return candidate;
|
|
18679
18832
|
}
|
|
18680
18833
|
async function resolveGrepPath(root, path = ".") {
|
|
@@ -18723,7 +18876,7 @@ var SearchSpawnError = class extends Error {
|
|
|
18723
18876
|
}
|
|
18724
18877
|
fallbackSafe;
|
|
18725
18878
|
};
|
|
18726
|
-
function delimitedParser(
|
|
18879
|
+
function delimitedParser(delimiter2, onRecord) {
|
|
18727
18880
|
let pending2 = Buffer.alloc(0);
|
|
18728
18881
|
let stopped = false;
|
|
18729
18882
|
return {
|
|
@@ -18732,7 +18885,7 @@ function delimitedParser(delimiter, onRecord) {
|
|
|
18732
18885
|
pending2 = pending2.length === 0 ? chunk : Buffer.concat([pending2, chunk]);
|
|
18733
18886
|
let offset = 0;
|
|
18734
18887
|
while (true) {
|
|
18735
|
-
const end = pending2.indexOf(
|
|
18888
|
+
const end = pending2.indexOf(delimiter2, offset);
|
|
18736
18889
|
if (end < 0) break;
|
|
18737
18890
|
if (!onRecord(pending2.subarray(offset, end))) {
|
|
18738
18891
|
stopped = true;
|
|
@@ -19184,9 +19337,9 @@ function charset(contentType) {
|
|
|
19184
19337
|
|
|
19185
19338
|
// src/tools/lsp.ts
|
|
19186
19339
|
import { spawn as spawn5 } from "child_process";
|
|
19187
|
-
import { existsSync as
|
|
19340
|
+
import { existsSync as existsSync4 } from "fs";
|
|
19188
19341
|
import { access as access2, constants as constants9, readFile as readFile21 } from "fs/promises";
|
|
19189
|
-
import { basename as basename6, dirname as dirname20, extname as
|
|
19342
|
+
import { basename as basename6, dirname as dirname20, extname as extname3, isAbsolute as isAbsolute14, relative as relative18, resolve as resolve26, sep as sep18 } from "path";
|
|
19190
19343
|
import { fileURLToPath as fileURLToPath3, pathToFileURL as pathToFileURL3 } from "url";
|
|
19191
19344
|
import { z as z32 } from "zod";
|
|
19192
19345
|
var LSP_REQUEST_TIMEOUT_MS = 15e3;
|
|
@@ -19200,10 +19353,12 @@ var LspConnection = class {
|
|
|
19200
19353
|
#disposed = false;
|
|
19201
19354
|
#initError;
|
|
19202
19355
|
constructor(command, args, cwd) {
|
|
19203
|
-
|
|
19356
|
+
const invocation = prepareSpawnInvocation(command, args, { cwd });
|
|
19357
|
+
this.#process = spawn5(invocation.command, invocation.args, {
|
|
19204
19358
|
cwd,
|
|
19205
19359
|
stdio: ["pipe", "pipe", "pipe"],
|
|
19206
|
-
windowsHide: true
|
|
19360
|
+
windowsHide: true,
|
|
19361
|
+
...invocation.windowsVerbatimArguments === void 0 ? {} : { windowsVerbatimArguments: invocation.windowsVerbatimArguments }
|
|
19207
19362
|
});
|
|
19208
19363
|
this.#process.on("error", (err) => {
|
|
19209
19364
|
this.#initError = new Error(`LSP server "${command}" failed to start: ${err.message}`);
|
|
@@ -19285,8 +19440,8 @@ var LspConnection = class {
|
|
|
19285
19440
|
const id = this.#nextId++;
|
|
19286
19441
|
const request = { jsonrpc: "2.0", id, method, params };
|
|
19287
19442
|
const timeout = AbortSignal.timeout(LSP_REQUEST_TIMEOUT_MS);
|
|
19288
|
-
const promise = new Promise((
|
|
19289
|
-
this.#pending.set(id, { resolve:
|
|
19443
|
+
const promise = new Promise((resolve42, reject) => {
|
|
19444
|
+
this.#pending.set(id, { resolve: resolve42, reject });
|
|
19290
19445
|
timeout.addEventListener("abort", () => {
|
|
19291
19446
|
if (this.#pending.has(id)) {
|
|
19292
19447
|
this.#pending.delete(id);
|
|
@@ -19402,7 +19557,7 @@ var LspBuffer = class {
|
|
|
19402
19557
|
}
|
|
19403
19558
|
}
|
|
19404
19559
|
};
|
|
19405
|
-
var TYPESCRIPT_CLI =
|
|
19560
|
+
var TYPESCRIPT_CLI = resolve26(
|
|
19406
19561
|
dirname20(fileURLToPath3(import.meta.resolve("typescript/package.json"))),
|
|
19407
19562
|
"lib",
|
|
19408
19563
|
"tsc.js"
|
|
@@ -19444,7 +19599,7 @@ var RealLspManager = class {
|
|
|
19444
19599
|
#serverConfigs;
|
|
19445
19600
|
#onStatus;
|
|
19446
19601
|
constructor(options) {
|
|
19447
|
-
this.#workspace =
|
|
19602
|
+
this.#workspace = resolve26(options.workspace);
|
|
19448
19603
|
this.#serverConfigs = options.serverConfigs ?? KNOWN_SERVERS;
|
|
19449
19604
|
this.#onStatus = options.onStatus;
|
|
19450
19605
|
}
|
|
@@ -19493,7 +19648,7 @@ var RealLspManager = class {
|
|
|
19493
19648
|
if (pending2 !== void 0) return pending2;
|
|
19494
19649
|
const config = this.#serverConfigs.find((c) => c.language === language);
|
|
19495
19650
|
if (config === void 0) return void 0;
|
|
19496
|
-
const hasRoot = config.rootFiles.some((file) =>
|
|
19651
|
+
const hasRoot = config.rootFiles.some((file) => existsSync4(resolve26(this.#workspace, file)));
|
|
19497
19652
|
if (!hasRoot) return void 0;
|
|
19498
19653
|
const startPromise = this.#startServer(config);
|
|
19499
19654
|
this.#pendingStarts.set(language, startPromise);
|
|
@@ -19536,10 +19691,10 @@ var LspDiagnosticsInput = z32.object({
|
|
|
19536
19691
|
file: z32.string().min(1)
|
|
19537
19692
|
});
|
|
19538
19693
|
function createLspTools(workspace, options = {}) {
|
|
19539
|
-
const root =
|
|
19694
|
+
const root = resolve26(workspace);
|
|
19540
19695
|
const manager = options.manager ?? new RealLspManager({ workspace: root, onStatus: options.onStatus });
|
|
19541
19696
|
const guard = (input) => {
|
|
19542
|
-
const candidate =
|
|
19697
|
+
const candidate = resolve26(root, input);
|
|
19543
19698
|
if (!isWithin7(root, candidate)) throw new Error("Path is outside the workspace");
|
|
19544
19699
|
return candidate;
|
|
19545
19700
|
};
|
|
@@ -19603,7 +19758,7 @@ function createLspTools(workspace, options = {}) {
|
|
|
19603
19758
|
return [findRefs, hover, diagnostics];
|
|
19604
19759
|
}
|
|
19605
19760
|
function languageIdForPath(filePath) {
|
|
19606
|
-
switch (
|
|
19761
|
+
switch (extname3(filePath).toLowerCase()) {
|
|
19607
19762
|
case ".tsx":
|
|
19608
19763
|
return "typescriptreact";
|
|
19609
19764
|
case ".mts":
|
|
@@ -19616,7 +19771,7 @@ function languageIdForPath(filePath) {
|
|
|
19616
19771
|
}
|
|
19617
19772
|
function isWithin7(root, candidate) {
|
|
19618
19773
|
const delta = relative18(root, candidate);
|
|
19619
|
-
return delta === "" || !delta.startsWith(`..${sep18}`) && delta !== ".." && !
|
|
19774
|
+
return delta === "" || !delta.startsWith(`..${sep18}`) && delta !== ".." && !isAbsolute14(delta);
|
|
19620
19775
|
}
|
|
19621
19776
|
function uriToPath(uri) {
|
|
19622
19777
|
try {
|
|
@@ -20004,12 +20159,12 @@ ${explanation}` : "";
|
|
|
20004
20159
|
// src/desktop/astgraph-service.ts
|
|
20005
20160
|
import { stat as stat10 } from "fs/promises";
|
|
20006
20161
|
import { createRequire as nodeCreateRequire } from "module";
|
|
20007
|
-
import { join as join28, resolve as
|
|
20162
|
+
import { join as join28, resolve as resolve27 } from "path";
|
|
20008
20163
|
var FIELDS = "id, kind, name, qualified_name, file_path, language, start_line, end_line, signature";
|
|
20009
20164
|
var DesktopAstGraphService = class {
|
|
20010
20165
|
#path;
|
|
20011
20166
|
constructor(workspace) {
|
|
20012
|
-
this.#path = join28(
|
|
20167
|
+
this.#path = join28(resolve27(workspace), ".flavor", "astgraph", "index.db");
|
|
20013
20168
|
}
|
|
20014
20169
|
async status() {
|
|
20015
20170
|
if ((await stat10(this.#path).catch(() => void 0))?.isFile() !== true) {
|
|
@@ -20171,7 +20326,7 @@ var QuestionBridge = class {
|
|
|
20171
20326
|
}
|
|
20172
20327
|
}
|
|
20173
20328
|
#askLocally(questions, signal) {
|
|
20174
|
-
return new Promise((
|
|
20329
|
+
return new Promise((resolve42, reject) => {
|
|
20175
20330
|
const onAbort = () => {
|
|
20176
20331
|
this.#pending = void 0;
|
|
20177
20332
|
this.#active = false;
|
|
@@ -20185,7 +20340,7 @@ var QuestionBridge = class {
|
|
|
20185
20340
|
this.#pending = void 0;
|
|
20186
20341
|
this.#active = false;
|
|
20187
20342
|
this.#onChange?.();
|
|
20188
|
-
|
|
20343
|
+
resolve42(answers);
|
|
20189
20344
|
},
|
|
20190
20345
|
reject: (error) => {
|
|
20191
20346
|
signal.removeEventListener("abort", onAbort);
|
|
@@ -20278,7 +20433,7 @@ function createTodoWriteTool() {
|
|
|
20278
20433
|
// src/tools/managed.ts
|
|
20279
20434
|
import { randomUUID as randomUUID18 } from "crypto";
|
|
20280
20435
|
import { link, lstat as lstat11, mkdir as mkdir20, readFile as readFile22, readdir as readdir9, unlink as unlink3, writeFile as writeFile12 } from "fs/promises";
|
|
20281
|
-
import { dirname as dirname21, join as join29, resolve as
|
|
20436
|
+
import { dirname as dirname21, join as join29, resolve as resolve28 } from "path";
|
|
20282
20437
|
import { z as z37 } from "zod";
|
|
20283
20438
|
var MANAGED_TOOL_VERSION = 1;
|
|
20284
20439
|
var MAX_RECORD_BYTES = 256 * 1024;
|
|
@@ -20320,9 +20475,9 @@ var ManagedToolStore = class {
|
|
|
20320
20475
|
#entries = /* @__PURE__ */ new Map();
|
|
20321
20476
|
#diagnostics = [];
|
|
20322
20477
|
constructor(options) {
|
|
20323
|
-
this.#workspace =
|
|
20478
|
+
this.#workspace = resolve28(options.workspace);
|
|
20324
20479
|
this.#projectRoot = join29(this.#workspace, ".flavor", "tools");
|
|
20325
|
-
this.#globalRoot = join29(
|
|
20480
|
+
this.#globalRoot = join29(resolve28(options.home), ".flavor-code", "tools");
|
|
20326
20481
|
this.#now = options.now ?? (() => (/* @__PURE__ */ new Date()).toISOString());
|
|
20327
20482
|
}
|
|
20328
20483
|
get diagnostics() {
|
|
@@ -21874,9 +22029,9 @@ Address this now (for example run the required verification or fix), then finish
|
|
|
21874
22029
|
}
|
|
21875
22030
|
#waitForPlanningIdle(key) {
|
|
21876
22031
|
if (!this.#activePlanning.has(key)) return Promise.resolve();
|
|
21877
|
-
return new Promise((
|
|
22032
|
+
return new Promise((resolve42) => {
|
|
21878
22033
|
const check2 = () => {
|
|
21879
|
-
if (!this.#activePlanning.has(key))
|
|
22034
|
+
if (!this.#activePlanning.has(key)) resolve42();
|
|
21880
22035
|
else this.#planningIdleWaiters.add(check2);
|
|
21881
22036
|
};
|
|
21882
22037
|
this.#planningIdleWaiters.add(check2);
|
|
@@ -22025,6 +22180,101 @@ var LANG_LABELS = /* @__PURE__ */ new Map([
|
|
|
22025
22180
|
["id-ID", "Indonesian (id-ID)"]
|
|
22026
22181
|
]);
|
|
22027
22182
|
|
|
22183
|
+
// src/utils/crash-guard.ts
|
|
22184
|
+
import { mkdirSync as mkdirSync3, writeFileSync } from "fs";
|
|
22185
|
+
import { join as join30 } from "path";
|
|
22186
|
+
var installed = false;
|
|
22187
|
+
var crashing = false;
|
|
22188
|
+
var crashCleanups = /* @__PURE__ */ new Set();
|
|
22189
|
+
function registerCrashCleanup(cleanup) {
|
|
22190
|
+
crashCleanups.add(cleanup);
|
|
22191
|
+
return () => {
|
|
22192
|
+
crashCleanups.delete(cleanup);
|
|
22193
|
+
};
|
|
22194
|
+
}
|
|
22195
|
+
async function runCrashCleanups(timeoutMs) {
|
|
22196
|
+
if (!Number.isFinite(timeoutMs) || timeoutMs < 0) throw new Error("Crash cleanup timeout must be non-negative");
|
|
22197
|
+
const cleanup = Promise.allSettled([...crashCleanups].map(async (callback) => callback()));
|
|
22198
|
+
if (timeoutMs === 0) return;
|
|
22199
|
+
let timer;
|
|
22200
|
+
try {
|
|
22201
|
+
await Promise.race([
|
|
22202
|
+
cleanup,
|
|
22203
|
+
new Promise((resolvePromise) => {
|
|
22204
|
+
timer = setTimeout(resolvePromise, timeoutMs);
|
|
22205
|
+
})
|
|
22206
|
+
]);
|
|
22207
|
+
} finally {
|
|
22208
|
+
if (timer !== void 0) clearTimeout(timer);
|
|
22209
|
+
}
|
|
22210
|
+
}
|
|
22211
|
+
function installCrashGuard(options = {}) {
|
|
22212
|
+
if (installed) return;
|
|
22213
|
+
installed = true;
|
|
22214
|
+
const crashLogDir = () => {
|
|
22215
|
+
try {
|
|
22216
|
+
return join30((options.workspace ?? process.cwd)(), ".flavor");
|
|
22217
|
+
} catch {
|
|
22218
|
+
return process.cwd();
|
|
22219
|
+
}
|
|
22220
|
+
};
|
|
22221
|
+
const writeCrashLog = (kind, detail) => {
|
|
22222
|
+
try {
|
|
22223
|
+
const dir = crashLogDir();
|
|
22224
|
+
mkdirSync3(dir, { recursive: true });
|
|
22225
|
+
const path = join30(dir, `crash-${(/* @__PURE__ */ new Date()).toISOString().replace(/[^0-9]/g, "")}-${process.pid}.log`);
|
|
22226
|
+
writeFileSync(path, [
|
|
22227
|
+
`kind: ${kind}`,
|
|
22228
|
+
`version: ${process.version}`,
|
|
22229
|
+
`platform: ${process.platform}`,
|
|
22230
|
+
`time: ${(/* @__PURE__ */ new Date()).toISOString()}`,
|
|
22231
|
+
"",
|
|
22232
|
+
detail
|
|
22233
|
+
].join("\n"), { encoding: "utf8", mode: 384 });
|
|
22234
|
+
return path;
|
|
22235
|
+
} catch {
|
|
22236
|
+
return void 0;
|
|
22237
|
+
}
|
|
22238
|
+
};
|
|
22239
|
+
const restoreTerminal = () => {
|
|
22240
|
+
try {
|
|
22241
|
+
if (process.stdout.isTTY === true) process.stdout.write("\x1B[?25h\x1B[?1049l");
|
|
22242
|
+
} catch {
|
|
22243
|
+
}
|
|
22244
|
+
};
|
|
22245
|
+
const describe = (reason) => {
|
|
22246
|
+
if (reason instanceof Error) return `${reason.name}: ${reason.message}
|
|
22247
|
+
${reason.stack ?? ""}`;
|
|
22248
|
+
return String(reason);
|
|
22249
|
+
};
|
|
22250
|
+
const crash = (kind, reason) => {
|
|
22251
|
+
if (crashing) {
|
|
22252
|
+
process.exit(1);
|
|
22253
|
+
return;
|
|
22254
|
+
}
|
|
22255
|
+
crashing = true;
|
|
22256
|
+
const detail = describe(reason);
|
|
22257
|
+
const logPath = writeCrashLog(kind, detail);
|
|
22258
|
+
restoreTerminal();
|
|
22259
|
+
try {
|
|
22260
|
+
const safe = redactErrorText(detail.split("\n").slice(0, 3).join("\n"));
|
|
22261
|
+
process.stderr.write(`flavor crashed (${kind}): ${safe}
|
|
22262
|
+
`);
|
|
22263
|
+
if (logPath !== void 0) process.stderr.write(`crash log: ${logPath}
|
|
22264
|
+
`);
|
|
22265
|
+
} catch {
|
|
22266
|
+
}
|
|
22267
|
+
const cleanupTimeoutMs = options.cleanupTimeoutMs ?? 1e3;
|
|
22268
|
+
const watchdog = setTimeout(() => process.exit(1), cleanupTimeoutMs + 50);
|
|
22269
|
+
void runCrashCleanups(cleanupTimeoutMs).finally(() => {
|
|
22270
|
+
clearTimeout(watchdog);
|
|
22271
|
+
process.exit(1);
|
|
22272
|
+
});
|
|
22273
|
+
};
|
|
22274
|
+
process.on("unhandledRejection", (reason) => crash("unhandledRejection", reason));
|
|
22275
|
+
process.on("uncaughtException", (error) => crash("uncaughtException", error));
|
|
22276
|
+
}
|
|
22277
|
+
|
|
22028
22278
|
// src/hallucination/confidence.ts
|
|
22029
22279
|
import { z as z39 } from "zod";
|
|
22030
22280
|
|
|
@@ -22744,7 +22994,7 @@ ${total}`;
|
|
|
22744
22994
|
// src/memory/store.ts
|
|
22745
22995
|
import { createHash as createHash17 } from "crypto";
|
|
22746
22996
|
import { readFile as readFile23, rm as rm12 } from "fs/promises";
|
|
22747
|
-
import { resolve as
|
|
22997
|
+
import { resolve as resolve29, join as join31, relative as relative19, sep as sep19 } from "path";
|
|
22748
22998
|
|
|
22749
22999
|
// src/memory/similarity.ts
|
|
22750
23000
|
function normalizeForSimilarity(value) {
|
|
@@ -22846,10 +23096,10 @@ var MemoryStore = class {
|
|
|
22846
23096
|
constructor(options) {
|
|
22847
23097
|
if (!Number.isSafeInteger(options.maxEntries) || options.maxEntries < 1) throw new Error("maxEntries must be an integer of at least 1");
|
|
22848
23098
|
if (!Number.isSafeInteger(options.maxEntryChars) || options.maxEntryChars < 1) throw new Error("maxEntryChars must be an integer of at least 1");
|
|
22849
|
-
this.workspace =
|
|
22850
|
-
this.#memoryRoot =
|
|
22851
|
-
this.path =
|
|
22852
|
-
this.#behaviorPath =
|
|
23099
|
+
this.workspace = resolve29(options.workspace);
|
|
23100
|
+
this.#memoryRoot = join31(this.workspace, ".flavor", "memory");
|
|
23101
|
+
this.path = join31(this.#memoryRoot, "MEMORY.md");
|
|
23102
|
+
this.#behaviorPath = join31(this.#memoryRoot, "behavior.json");
|
|
22853
23103
|
this.#maxEntries = options.maxEntries;
|
|
22854
23104
|
this.#maxEntryChars = options.maxEntryChars;
|
|
22855
23105
|
}
|
|
@@ -23072,7 +23322,7 @@ var MemoryStore = class {
|
|
|
23072
23322
|
}
|
|
23073
23323
|
#resolveContentPath(contentPath) {
|
|
23074
23324
|
if (!/^tasks\/[A-Za-z0-9][A-Za-z0-9._-]{0,127}\.md$/.test(contentPath)) throw new Error("Invalid memory content path");
|
|
23075
|
-
const path =
|
|
23325
|
+
const path = resolve29(this.#memoryRoot, ...contentPath.split("/"));
|
|
23076
23326
|
const rel = relative19(this.#memoryRoot, path);
|
|
23077
23327
|
if (rel === ".." || rel.startsWith(`..${sep19}`)) throw new Error("Memory content path escapes the memory directory");
|
|
23078
23328
|
return path;
|
|
@@ -23532,12 +23782,12 @@ var MemoryReviewBridge = class {
|
|
|
23532
23782
|
|
|
23533
23783
|
// src/execution/docker.ts
|
|
23534
23784
|
import { spawn as spawn6 } from "child_process";
|
|
23535
|
-
import { relative as relative20, resolve as
|
|
23785
|
+
import { relative as relative20, resolve as resolve30, sep as sep20, win32 } from "path";
|
|
23536
23786
|
function buildDockerInvocation(options) {
|
|
23537
23787
|
const workspace = options.workspace;
|
|
23538
23788
|
const cwd = options.request.cwd;
|
|
23539
23789
|
const windows = /^[A-Za-z]:[\\/]/.test(workspace);
|
|
23540
|
-
const delta = windows ? win32.relative(workspace, cwd) : relative20(
|
|
23790
|
+
const delta = windows ? win32.relative(workspace, cwd) : relative20(resolve30(workspace), resolve30(cwd));
|
|
23541
23791
|
if (delta === ".." || delta.startsWith(`..${windows ? "\\" : sep20}`)) throw new Error("Docker cwd escapes workspace");
|
|
23542
23792
|
const containerCwd = delta.length === 0 ? "/workspace" : `/workspace/${delta.replaceAll("\\", "/")}`;
|
|
23543
23793
|
const args = [
|
|
@@ -23638,7 +23888,7 @@ function createExecutionEnvironment(workspace, config) {
|
|
|
23638
23888
|
|
|
23639
23889
|
// src/ide/client.ts
|
|
23640
23890
|
import { readFile as readFile24, readdir as readdir10, stat as stat11 } from "fs/promises";
|
|
23641
|
-
import { basename as basename7, join as
|
|
23891
|
+
import { basename as basename7, join as join32, relative as relative21, resolve as resolve31, sep as sep21 } from "path";
|
|
23642
23892
|
var FlavorIdeClient = class {
|
|
23643
23893
|
#workspace;
|
|
23644
23894
|
#home;
|
|
@@ -23649,8 +23899,8 @@ var FlavorIdeClient = class {
|
|
|
23649
23899
|
#sessionId;
|
|
23650
23900
|
#eventTail = Promise.resolve();
|
|
23651
23901
|
constructor(options) {
|
|
23652
|
-
this.#workspace =
|
|
23653
|
-
this.#home =
|
|
23902
|
+
this.#workspace = resolve31(options.workspace);
|
|
23903
|
+
this.#home = resolve31(options.home);
|
|
23654
23904
|
this.#environment = options.environment ?? process.env;
|
|
23655
23905
|
this.#fetch = options.fetch ?? fetch;
|
|
23656
23906
|
}
|
|
@@ -23734,7 +23984,7 @@ var FlavorIdeClient = class {
|
|
|
23734
23984
|
};
|
|
23735
23985
|
if (await this.#healthy(configured)) return configured;
|
|
23736
23986
|
}
|
|
23737
|
-
const directory =
|
|
23987
|
+
const directory = join32(this.#home, ".flavor-code", "ide");
|
|
23738
23988
|
let entries;
|
|
23739
23989
|
try {
|
|
23740
23990
|
entries = (await readdir10(directory)).filter((name) => name.endsWith(".lock"));
|
|
@@ -23742,7 +23992,7 @@ var FlavorIdeClient = class {
|
|
|
23742
23992
|
return void 0;
|
|
23743
23993
|
}
|
|
23744
23994
|
const candidates = (await Promise.all(entries.map(async (name) => {
|
|
23745
|
-
const path =
|
|
23995
|
+
const path = join32(directory, name);
|
|
23746
23996
|
try {
|
|
23747
23997
|
const [contents, info] = await Promise.all([readFile24(path, "utf8"), stat11(path)]);
|
|
23748
23998
|
const lock = parseLockfile(contents);
|
|
@@ -23848,8 +24098,8 @@ function parsePort(value) {
|
|
|
23848
24098
|
return Number.isInteger(port2) && port2 > 0 && port2 <= 65535 ? port2 : void 0;
|
|
23849
24099
|
}
|
|
23850
24100
|
function isWithin8(parent, child) {
|
|
23851
|
-
const base = normalize(
|
|
23852
|
-
const target = normalize(
|
|
24101
|
+
const base = normalize(resolve31(parent));
|
|
24102
|
+
const target = normalize(resolve31(child));
|
|
23853
24103
|
return target === base || target.startsWith(`${base}${sep21}`);
|
|
23854
24104
|
}
|
|
23855
24105
|
function normalize(path) {
|
|
@@ -23879,8 +24129,8 @@ var JobRegistry = class {
|
|
|
23879
24129
|
const id = `job-${randomUUID19().slice(0, 12)}`;
|
|
23880
24130
|
const now = (/* @__PURE__ */ new Date()).toISOString();
|
|
23881
24131
|
let settle;
|
|
23882
|
-
const done = new Promise((
|
|
23883
|
-
settle =
|
|
24132
|
+
const done = new Promise((resolve42) => {
|
|
24133
|
+
settle = resolve42;
|
|
23884
24134
|
});
|
|
23885
24135
|
const record3 = {
|
|
23886
24136
|
id,
|
|
@@ -24004,7 +24254,7 @@ function snapshot2(job) {
|
|
|
24004
24254
|
// src/terminal/service.ts
|
|
24005
24255
|
import { randomUUID as randomUUID20 } from "crypto";
|
|
24006
24256
|
import { createRequire as nodeCreateRequire2 } from "module";
|
|
24007
|
-
import { isAbsolute as
|
|
24257
|
+
import { isAbsolute as isAbsolute15, relative as relative22, resolve as resolve32, sep as sep22 } from "path";
|
|
24008
24258
|
var TerminalService = class {
|
|
24009
24259
|
#root;
|
|
24010
24260
|
#factory;
|
|
@@ -24012,7 +24262,7 @@ var TerminalService = class {
|
|
|
24012
24262
|
#maxOutputChars;
|
|
24013
24263
|
#sessions = /* @__PURE__ */ new Map();
|
|
24014
24264
|
constructor(workspace, options = {}) {
|
|
24015
|
-
this.#root =
|
|
24265
|
+
this.#root = resolve32(workspace);
|
|
24016
24266
|
this.#factory = options.factory ?? defaultFactory;
|
|
24017
24267
|
this.#jobs = options.jobs;
|
|
24018
24268
|
this.#maxOutputChars = options.maxOutputChars ?? 2e5;
|
|
@@ -24130,15 +24380,15 @@ function cleanEnvironment(environment) {
|
|
|
24130
24380
|
return Object.fromEntries(Object.entries(environment).filter((entry2) => entry2[1] !== void 0));
|
|
24131
24381
|
}
|
|
24132
24382
|
function workspacePath(root, input) {
|
|
24133
|
-
const path =
|
|
24383
|
+
const path = resolve32(root, input);
|
|
24134
24384
|
const delta = relative22(root, path);
|
|
24135
|
-
if (delta === ".." || delta.startsWith(`..${sep22}`) ||
|
|
24385
|
+
if (delta === ".." || delta.startsWith(`..${sep22}`) || isAbsolute15(delta)) throw new Error("Terminal cwd is outside the workspace");
|
|
24136
24386
|
return path;
|
|
24137
24387
|
}
|
|
24138
24388
|
|
|
24139
24389
|
// src/tools/terminal.ts
|
|
24140
24390
|
import { z as z40 } from "zod";
|
|
24141
|
-
import { resolve as
|
|
24391
|
+
import { resolve as resolve33 } from "path";
|
|
24142
24392
|
var Id = z40.string().regex(/^term-[a-f0-9-]+$/);
|
|
24143
24393
|
var OpenInput = z40.object({ cwd: z40.string().optional(), shell: z40.string().optional(), args: z40.array(z40.string()).optional(), columns: z40.coerce.number().int().min(10).max(500).optional(), rows: z40.coerce.number().int().min(2).max(300).optional() });
|
|
24144
24394
|
var OptionalBoolean = z40.union([z40.boolean(), z40.string().refine((value) => value === "true" || value === "false")]);
|
|
@@ -24152,8 +24402,8 @@ function createTerminalTools(terminals, workspace = process.cwd()) {
|
|
|
24152
24402
|
name: "TerminalOpen",
|
|
24153
24403
|
description: "Open a persistent interactive pseudo-terminal in the workspace",
|
|
24154
24404
|
inputSchema: OpenInput,
|
|
24155
|
-
paths: (input) => [
|
|
24156
|
-
permissions: (input) => ({ paths: [
|
|
24405
|
+
paths: (input) => [resolve33(workspace, input.cwd ?? ".")],
|
|
24406
|
+
permissions: (input) => ({ paths: [resolve33(workspace, input.cwd ?? ".")], command: input.shell ?? "system-shell", args: input.args ?? [], cwd: resolve33(workspace, input.cwd ?? ".") }),
|
|
24157
24407
|
execute: async (input, _signal, context) => terminals.open({
|
|
24158
24408
|
owner: owner(context),
|
|
24159
24409
|
...input.cwd === void 0 ? {} : { cwd: input.cwd },
|
|
@@ -24249,7 +24499,7 @@ function palSocketAddress(options) {
|
|
|
24249
24499
|
import { createHash as createHash20 } from "crypto";
|
|
24250
24500
|
import { Socket as Socket2 } from "net";
|
|
24251
24501
|
import { tmpdir as tmpdir3 } from "os";
|
|
24252
|
-
import { join as
|
|
24502
|
+
import { join as join34 } from "path";
|
|
24253
24503
|
import { spawn as spawn7 } from "child_process";
|
|
24254
24504
|
|
|
24255
24505
|
// src/pals/broker.ts
|
|
@@ -24262,16 +24512,16 @@ import { dirname as dirname23 } from "path";
|
|
|
24262
24512
|
import { randomBytes as randomBytes3, randomUUID as randomUUID21 } from "crypto";
|
|
24263
24513
|
import { chmod as chmod2, link as link2, mkdir as mkdir21, open as open12, readFile as readFile25, unlink as unlink4 } from "fs/promises";
|
|
24264
24514
|
import { homedir } from "os";
|
|
24265
|
-
import { join as
|
|
24515
|
+
import { join as join33 } from "path";
|
|
24266
24516
|
var PAL_AUTH_TOKEN_BYTES = 32;
|
|
24267
24517
|
var TOKEN_PATTERN = /^[a-f0-9]{64}$/;
|
|
24268
24518
|
async function loadOrCreatePalAuthToken(options = {}) {
|
|
24269
|
-
const directory =
|
|
24270
|
-
const tokenPath =
|
|
24519
|
+
const directory = join33(options.home ?? homedir(), ".flavor-code", "pals");
|
|
24520
|
+
const tokenPath = join33(directory, "auth-token");
|
|
24271
24521
|
await mkdir21(directory, { recursive: true, mode: 448 });
|
|
24272
24522
|
await chmod2(directory, 448);
|
|
24273
24523
|
const token = (options.random ?? randomBytes3)(PAL_AUTH_TOKEN_BYTES).toString("hex");
|
|
24274
|
-
const temporaryPath =
|
|
24524
|
+
const temporaryPath = join33(directory, `.auth-token-${process.pid}-${randomUUID21()}.tmp`);
|
|
24275
24525
|
let handle;
|
|
24276
24526
|
try {
|
|
24277
24527
|
handle = await open12(temporaryPath, "wx", 384);
|
|
@@ -24384,15 +24634,15 @@ async function acquirePalFileLock(options) {
|
|
|
24384
24634
|
// src/pals/broker-cli.ts
|
|
24385
24635
|
var startupAttempts = /* @__PURE__ */ new Map();
|
|
24386
24636
|
function delay3(milliseconds) {
|
|
24387
|
-
return new Promise((
|
|
24637
|
+
return new Promise((resolve42) => setTimeout(resolve42, milliseconds));
|
|
24388
24638
|
}
|
|
24389
24639
|
function canConnect(address) {
|
|
24390
|
-
return new Promise((
|
|
24640
|
+
return new Promise((resolve42) => {
|
|
24391
24641
|
const socket = new Socket2();
|
|
24392
24642
|
const finish = (connected) => {
|
|
24393
24643
|
socket.removeAllListeners();
|
|
24394
24644
|
socket.destroy();
|
|
24395
|
-
|
|
24645
|
+
resolve42(connected);
|
|
24396
24646
|
};
|
|
24397
24647
|
socket.once("connect", () => finish(true));
|
|
24398
24648
|
socket.once("error", () => finish(false));
|
|
@@ -24401,7 +24651,7 @@ function canConnect(address) {
|
|
|
24401
24651
|
}
|
|
24402
24652
|
function startupLockPath(address) {
|
|
24403
24653
|
const digest = createHash20("sha256").update(address, "utf8").digest("hex").slice(0, 24);
|
|
24404
|
-
return
|
|
24654
|
+
return join34(tmpdir3(), `flavor-code-pals-${digest}.startup.lock`);
|
|
24405
24655
|
}
|
|
24406
24656
|
async function spawnProductionBroker(address) {
|
|
24407
24657
|
const entry2 = process.argv[1];
|
|
@@ -24583,8 +24833,8 @@ var PalClient = class {
|
|
|
24583
24833
|
let disconnectTimer;
|
|
24584
24834
|
await Promise.race([
|
|
24585
24835
|
this.request({ version: 1, type: "disconnect", requestId: randomUUID24() }).catch(() => void 0),
|
|
24586
|
-
new Promise((
|
|
24587
|
-
disconnectTimer = setTimeout(
|
|
24836
|
+
new Promise((resolve42) => {
|
|
24837
|
+
disconnectTimer = setTimeout(resolve42, Math.min(this.requestTimeoutMs, 250));
|
|
24588
24838
|
})
|
|
24589
24839
|
]);
|
|
24590
24840
|
if (disconnectTimer !== void 0) clearTimeout(disconnectTimer);
|
|
@@ -24630,7 +24880,7 @@ var PalClient = class {
|
|
|
24630
24880
|
return presence;
|
|
24631
24881
|
}
|
|
24632
24882
|
openSocket() {
|
|
24633
|
-
return new Promise((
|
|
24883
|
+
return new Promise((resolve42, reject) => {
|
|
24634
24884
|
let settled = false;
|
|
24635
24885
|
const rejectConnection = (error) => {
|
|
24636
24886
|
if (settled) return;
|
|
@@ -24653,7 +24903,7 @@ var PalClient = class {
|
|
|
24653
24903
|
settled = true;
|
|
24654
24904
|
if (this.connectingReject === rejectConnection) this.connectingReject = void 0;
|
|
24655
24905
|
socket.off("error", onInitialError);
|
|
24656
|
-
|
|
24906
|
+
resolve42();
|
|
24657
24907
|
};
|
|
24658
24908
|
const onInitialError = (error) => {
|
|
24659
24909
|
socket.off("connect", onConnect);
|
|
@@ -24684,12 +24934,12 @@ var PalClient = class {
|
|
|
24684
24934
|
if (this.closed || socket === void 0 || socket.destroyed || !socket.writable) {
|
|
24685
24935
|
return Promise.reject(new Error(this.closed ? "Pal client is closed" : "Pal client is not connected"));
|
|
24686
24936
|
}
|
|
24687
|
-
return new Promise((
|
|
24937
|
+
return new Promise((resolve42, reject) => {
|
|
24688
24938
|
const timer = setTimeout(() => {
|
|
24689
24939
|
this.pending.delete(request.requestId);
|
|
24690
24940
|
reject(new Error(`Pal request '${request.type}' timed out`));
|
|
24691
24941
|
}, this.requestTimeoutMs);
|
|
24692
|
-
this.pending.set(request.requestId, { resolve:
|
|
24942
|
+
this.pending.set(request.requestId, { resolve: resolve42, reject, timer });
|
|
24693
24943
|
let frame;
|
|
24694
24944
|
try {
|
|
24695
24945
|
frame = encodeControlFrame(BrokerRequestSchema.parse(request));
|
|
@@ -25093,8 +25343,8 @@ var ApprovalBridge = class {
|
|
|
25093
25343
|
}
|
|
25094
25344
|
};
|
|
25095
25345
|
async function createProductionRuntime(options) {
|
|
25096
|
-
const workspace =
|
|
25097
|
-
const home =
|
|
25346
|
+
const workspace = resolve34(options.workspace ?? process.cwd());
|
|
25347
|
+
const home = resolve34(options.home ?? homedir2());
|
|
25098
25348
|
const environment = options.environment ?? process.env;
|
|
25099
25349
|
const ide = new FlavorIdeClient({ workspace, home, environment });
|
|
25100
25350
|
const ideReady = ide.initialize();
|
|
@@ -25337,8 +25587,8 @@ async function createProductionRuntime(options) {
|
|
|
25337
25587
|
const registeredProviders = registration.registered;
|
|
25338
25588
|
let effectiveLlm = registration.effectiveLlm;
|
|
25339
25589
|
const pluginHost = new PluginHost({
|
|
25340
|
-
globalPluginDirs: [
|
|
25341
|
-
projectPluginDirs: [
|
|
25590
|
+
globalPluginDirs: [join35(home, ".flavor-code", "plugins")],
|
|
25591
|
+
projectPluginDirs: [join35(workspace, ".flavor", "plugins")],
|
|
25342
25592
|
config,
|
|
25343
25593
|
// Existing project plugins (including astgraph and superharness) use Node.js
|
|
25344
25594
|
// built-ins. The Worker/vm sandbox deliberately blocks those imports, so it
|
|
@@ -25413,6 +25663,7 @@ async function createProductionRuntime(options) {
|
|
|
25413
25663
|
let collaborationEventPump;
|
|
25414
25664
|
let collaborationEventsOpen = true;
|
|
25415
25665
|
let islandControlServer;
|
|
25666
|
+
let sessionLease;
|
|
25416
25667
|
const pumpCollaborationEvents = () => {
|
|
25417
25668
|
if (collaborationEventPump !== void 0 || collaborationSession === void 0 || deliverCollaborationEvent === void 0) return;
|
|
25418
25669
|
collaborationEventPump = (async () => {
|
|
@@ -25446,7 +25697,7 @@ async function createProductionRuntime(options) {
|
|
|
25446
25697
|
const address = palSocketAddress({
|
|
25447
25698
|
platform: process.platform,
|
|
25448
25699
|
userScope: home,
|
|
25449
|
-
runtimeDir:
|
|
25700
|
+
runtimeDir: join35(tmpdir4(), `flavor-code-pals-${createHash21("sha256").update(home).digest("hex").slice(0, 16)}`)
|
|
25450
25701
|
});
|
|
25451
25702
|
const baseCollaborationClient = collaboration.client ?? collaboration.createClient?.({
|
|
25452
25703
|
instanceId: collaboration.instanceId,
|
|
@@ -25557,8 +25808,8 @@ async function createProductionRuntime(options) {
|
|
|
25557
25808
|
diagnostics.push(`MCP servers could not start: ${message(error)}`);
|
|
25558
25809
|
});
|
|
25559
25810
|
const skills = new SkillRegistry({
|
|
25560
|
-
globalRoots: [
|
|
25561
|
-
projectRoots: [
|
|
25811
|
+
globalRoots: [join35(home, ".flavor-code", "skills")],
|
|
25812
|
+
projectRoots: [join35(workspace, ".flavor", "skills"), ...pluginSkillRoots],
|
|
25562
25813
|
authorizeResource: async () => true,
|
|
25563
25814
|
disabledNames: config.skills.disabled
|
|
25564
25815
|
});
|
|
@@ -25566,7 +25817,7 @@ async function createProductionRuntime(options) {
|
|
|
25566
25817
|
skillsReady.catch(() => void 0);
|
|
25567
25818
|
tools.push(createSkillTool(skills), createSkillResourceTool(skills));
|
|
25568
25819
|
if (options.extraTools !== void 0) tools.push(...options.extraTools);
|
|
25569
|
-
const flavor = await optionalText2(
|
|
25820
|
+
const flavor = await optionalText2(join35(workspace, "FLAVOR.md"));
|
|
25570
25821
|
const instructionBaseline = await workspaceInstructions.baseline();
|
|
25571
25822
|
let memoryContext;
|
|
25572
25823
|
if (memoryStore !== void 0) {
|
|
@@ -25591,6 +25842,7 @@ async function createProductionRuntime(options) {
|
|
|
25591
25842
|
const subagentStartedAt = {};
|
|
25592
25843
|
const subagentElapsedMs = {};
|
|
25593
25844
|
let sessionId = recovered?.sessionId ?? `session-${(/* @__PURE__ */ new Date()).toISOString().replace(/[^0-9]/g, "").slice(0, 17)}-${randomUUID25().slice(0, 8)}`;
|
|
25845
|
+
sessionLease = await sessionStore.acquireLease(sessionId);
|
|
25594
25846
|
hookSessionId = sessionId;
|
|
25595
25847
|
let harnessJournal = new HarnessJournal({ workspace, sessionId });
|
|
25596
25848
|
const harnessRecovery = harnessJournal.recover();
|
|
@@ -25908,7 +26160,7 @@ async function createProductionRuntime(options) {
|
|
|
25908
26160
|
maxEntryChars: config.memory.maxEntryChars,
|
|
25909
26161
|
scoreThreshold: config.memory.scoreThreshold,
|
|
25910
26162
|
autoStoreThreshold: config.memory.autoStoreThreshold,
|
|
25911
|
-
maxCandidates:
|
|
26163
|
+
maxCandidates: config.memory.maxCandidatesPerTask,
|
|
25912
26164
|
...config.language === void 0 ? {} : { language: config.language },
|
|
25913
26165
|
generate: (prompt, signal) => generateMemoryExtraction(registry, childModel, prompt, signal)
|
|
25914
26166
|
}) : void 0;
|
|
@@ -26147,7 +26399,7 @@ async function createProductionRuntime(options) {
|
|
|
26147
26399
|
evolveService.toolDefinition(),
|
|
26148
26400
|
...mcpTools
|
|
26149
26401
|
];
|
|
26150
|
-
const loopFlavor = await optionalText2(
|
|
26402
|
+
const loopFlavor = await optionalText2(join35(input.workspace, "FLAVOR.md"));
|
|
26151
26403
|
const loopInstructionBaseline = await loopInstructions.baseline();
|
|
26152
26404
|
const loopEnvironment = createPromptEnvironment({
|
|
26153
26405
|
now: /* @__PURE__ */ new Date(),
|
|
@@ -26506,8 +26758,8 @@ async function createProductionRuntime(options) {
|
|
|
26506
26758
|
explain: (query, focus, signal, onText) => runExplain({
|
|
26507
26759
|
graph: new DesktopAstGraphService(workspace),
|
|
26508
26760
|
readFile: async (path) => {
|
|
26509
|
-
const root =
|
|
26510
|
-
const abs =
|
|
26761
|
+
const root = resolve34(workspace);
|
|
26762
|
+
const abs = resolve34(root, path);
|
|
26511
26763
|
if (abs !== root && !abs.startsWith(root + sep23)) throw new Error(`path outside workspace: ${path}`);
|
|
26512
26764
|
return readFile27(abs, "utf8");
|
|
26513
26765
|
},
|
|
@@ -26588,7 +26840,12 @@ ${summary2}`;
|
|
|
26588
26840
|
for (const key of Object.keys(subagentStartedAt)) delete subagentStartedAt[key];
|
|
26589
26841
|
for (const key of Object.keys(subagentElapsedMs)) delete subagentElapsedMs[key];
|
|
26590
26842
|
const previousIdeSessionId = ideSessionId;
|
|
26591
|
-
|
|
26843
|
+
const nextSessionId = `session-${(/* @__PURE__ */ new Date()).toISOString().replace(/[^0-9]/g, "").slice(0, 17)}-${randomUUID25().slice(0, 8)}`;
|
|
26844
|
+
const nextLease = await sessionStore.acquireLease(nextSessionId);
|
|
26845
|
+
const previousLease = sessionLease;
|
|
26846
|
+
sessionLease = nextLease;
|
|
26847
|
+
sessionId = nextSessionId;
|
|
26848
|
+
await previousLease?.release();
|
|
26592
26849
|
hookSessionId = sessionId;
|
|
26593
26850
|
harnessJournal = new HarnessJournal({ workspace, sessionId });
|
|
26594
26851
|
setUsageSession(sessionId);
|
|
@@ -26708,7 +26965,7 @@ ${renderMemoryDocument(entries)}`;
|
|
|
26708
26965
|
return `Provider "${providerName}" is missing authorizationUrl, tokenUrl, or clientId.`;
|
|
26709
26966
|
}
|
|
26710
26967
|
try {
|
|
26711
|
-
const tokenStore = createFileTokenStore(
|
|
26968
|
+
const tokenStore = createFileTokenStore(join35(home, ".flavor-code", "auth.json"));
|
|
26712
26969
|
const credentialId = oauthCredentialId(oauthConfig.tokenUrl, oauthConfig.clientId);
|
|
26713
26970
|
await migrateLegacyOAuthToken(tokenStore, providerName, credentialId);
|
|
26714
26971
|
const oauth = new OAuthCallbackAuthProvider({
|
|
@@ -26755,7 +27012,7 @@ ${renderMemoryDocument(entries)}`;
|
|
|
26755
27012
|
}
|
|
26756
27013
|
},
|
|
26757
27014
|
async logout() {
|
|
26758
|
-
const tokenStore = createFileTokenStore(
|
|
27015
|
+
const tokenStore = createFileTokenStore(join35(home, ".flavor-code", "auth.json"));
|
|
26759
27016
|
const tokens2 = await tokenStore.load();
|
|
26760
27017
|
const hadCredentials = Object.keys(tokens2).length > 0 || effectiveLlm !== void 0;
|
|
26761
27018
|
if (!hadCredentials) {
|
|
@@ -26835,24 +27092,13 @@ ${renderMemoryDocument(entries)}`;
|
|
|
26835
27092
|
setPermissionProfile: (profile) => harness.setPermissionProfile(profile)
|
|
26836
27093
|
};
|
|
26837
27094
|
let disposed = false;
|
|
26838
|
-
|
|
26839
|
-
|
|
26840
|
-
|
|
26841
|
-
|
|
26842
|
-
|
|
26843
|
-
|
|
26844
|
-
|
|
26845
|
-
jobs: { list: () => jobs.list(), read: (id, owner2, cursor) => jobs.read(id, owner2, cursor), subscribe: (listener) => jobs.subscribe(listener) },
|
|
26846
|
-
get sessionId() {
|
|
26847
|
-
return sessionId;
|
|
26848
|
-
},
|
|
26849
|
-
get diagnostics() {
|
|
26850
|
-
return diagnostics.map((item) => redactSecrets(item, secrets));
|
|
26851
|
-
},
|
|
26852
|
-
async dispose() {
|
|
26853
|
-
if (disposed) return;
|
|
26854
|
-
disposed = true;
|
|
26855
|
-
const stepTimeoutMs = options.shutdownStepTimeoutMs ?? DEFAULT_SHUTDOWN_STEP_TIMEOUT_MS;
|
|
27095
|
+
let unregisterCrashCleanup = () => void 0;
|
|
27096
|
+
const disposeRuntime = async () => {
|
|
27097
|
+
if (disposed) return;
|
|
27098
|
+
disposed = true;
|
|
27099
|
+
unregisterCrashCleanup();
|
|
27100
|
+
const stepTimeoutMs = options.shutdownStepTimeoutMs ?? DEFAULT_SHUTDOWN_STEP_TIMEOUT_MS;
|
|
27101
|
+
try {
|
|
26856
27102
|
collaborationEventsOpen = false;
|
|
26857
27103
|
unsubscribeCollaboration?.();
|
|
26858
27104
|
if (collaborationEventPump !== void 0) await boundedStep(stepTimeoutMs, diagnostics, "collaboration-event-pump", collaborationEventPump);
|
|
@@ -26871,8 +27117,29 @@ ${renderMemoryDocument(entries)}`;
|
|
|
26871
27117
|
memoryReviews.dispose();
|
|
26872
27118
|
if (collaborationClient !== void 0) await boundedStep(stepTimeoutMs, diagnostics, "collaboration-close", collaborationClient.close());
|
|
26873
27119
|
await cleanupProduction(approvals, questions, pluginHost, mcpManager, harness, { stepTimeoutMs });
|
|
27120
|
+
} finally {
|
|
27121
|
+
const lease = sessionLease;
|
|
27122
|
+
sessionLease = void 0;
|
|
27123
|
+
await lease?.release();
|
|
26874
27124
|
}
|
|
26875
27125
|
};
|
|
27126
|
+
unregisterCrashCleanup = registerCrashCleanup(disposeRuntime);
|
|
27127
|
+
return {
|
|
27128
|
+
session,
|
|
27129
|
+
services,
|
|
27130
|
+
authorization,
|
|
27131
|
+
approvals,
|
|
27132
|
+
memoryReviews,
|
|
27133
|
+
restoredTranscript,
|
|
27134
|
+
jobs: { list: () => jobs.list(), read: (id, owner2, cursor) => jobs.read(id, owner2, cursor), subscribe: (listener) => jobs.subscribe(listener) },
|
|
27135
|
+
get sessionId() {
|
|
27136
|
+
return sessionId;
|
|
27137
|
+
},
|
|
27138
|
+
get diagnostics() {
|
|
27139
|
+
return diagnostics.map((item) => redactSecrets(item, secrets));
|
|
27140
|
+
},
|
|
27141
|
+
dispose: disposeRuntime
|
|
27142
|
+
};
|
|
26876
27143
|
} catch (primaryError) {
|
|
26877
27144
|
collaborationEventsOpen = false;
|
|
26878
27145
|
mcpDiscarded = true;
|
|
@@ -26893,6 +27160,12 @@ ${renderMemoryDocument(entries)}`;
|
|
|
26893
27160
|
} catch (cleanupError) {
|
|
26894
27161
|
attachCleanupError(primaryError, cleanupError);
|
|
26895
27162
|
}
|
|
27163
|
+
try {
|
|
27164
|
+
await sessionLease?.release();
|
|
27165
|
+
} catch (leaseError) {
|
|
27166
|
+
attachCleanupError(primaryError, leaseError);
|
|
27167
|
+
}
|
|
27168
|
+
sessionLease = void 0;
|
|
26896
27169
|
throw primaryError;
|
|
26897
27170
|
}
|
|
26898
27171
|
}
|
|
@@ -27031,8 +27304,8 @@ async function workspaceFingerprint(workspace) {
|
|
|
27031
27304
|
let remaining = 5 * 1024 * 1024;
|
|
27032
27305
|
for (const name of untracked.stdout.split("\0").filter(Boolean).sort()) {
|
|
27033
27306
|
if (name === ".flavor/loops" || name.startsWith(".flavor/loops/")) continue;
|
|
27034
|
-
const path =
|
|
27035
|
-
const relativePath = relative23(
|
|
27307
|
+
const path = resolve34(workspace, name);
|
|
27308
|
+
const relativePath = relative23(resolve34(workspace), path);
|
|
27036
27309
|
if (relativePath === ".." || relativePath.startsWith(`..${sep23}`)) continue;
|
|
27037
27310
|
hash.update(name);
|
|
27038
27311
|
if (remaining <= 0) continue;
|
|
@@ -27165,8 +27438,8 @@ function requestMemoryRestart(workspace, sessionId, emitOutput) {
|
|
|
27165
27438
|
previous = parseMemoryRestartMarker(readFileSync3(markerPath, "utf8"));
|
|
27166
27439
|
} catch {
|
|
27167
27440
|
}
|
|
27168
|
-
|
|
27169
|
-
|
|
27441
|
+
mkdirSync4(dirname24(markerPath), { recursive: true });
|
|
27442
|
+
writeFileSync2(markerPath, `${JSON.stringify(nextMemoryRestartMarker(previous, sessionId, /* @__PURE__ */ new Date()))}
|
|
27170
27443
|
`, "utf8");
|
|
27171
27444
|
} catch {
|
|
27172
27445
|
}
|
|
@@ -27267,7 +27540,7 @@ async function registerConfiguredAdapters(providers, registry, environment, diag
|
|
|
27267
27540
|
const configured = { ...providers };
|
|
27268
27541
|
if (configured.openai === void 0 && environment.OPENAI_API_KEY) configured.openai = { type: "openai", apiKey: environment.OPENAI_API_KEY };
|
|
27269
27542
|
if (configured.anthropic === void 0 && environment.ANTHROPIC_API_KEY) configured.anthropic = { type: "anthropic", apiKey: environment.ANTHROPIC_API_KEY };
|
|
27270
|
-
const oauthTokenStore = createFileTokenStore(
|
|
27543
|
+
const oauthTokenStore = createFileTokenStore(join35(home, ".flavor-code", "auth.json"));
|
|
27271
27544
|
const registered = [];
|
|
27272
27545
|
let effectiveLlm;
|
|
27273
27546
|
for (const [name, provider] of Object.entries(configured)) {
|
|
@@ -27700,11 +27973,11 @@ function createMemoryTaskId() {
|
|
|
27700
27973
|
import { createHash as createHash27 } from "crypto";
|
|
27701
27974
|
import { mkdir as mkdir27, readFile as readFile35, readdir as readdir12, rm as rm15, stat as stat15, writeFile as writeFile17 } from "fs/promises";
|
|
27702
27975
|
import { homedir as homedir4 } from "os";
|
|
27703
|
-
import { basename as basename9, join as
|
|
27976
|
+
import { basename as basename9, join as join45, resolve as resolve37 } from "path";
|
|
27704
27977
|
|
|
27705
27978
|
// src/d2c/workflow.ts
|
|
27706
27979
|
import { createHash as createHash22 } from "crypto";
|
|
27707
|
-
import { join as
|
|
27980
|
+
import { join as join36 } from "path";
|
|
27708
27981
|
import { z as z42 } from "zod";
|
|
27709
27982
|
|
|
27710
27983
|
// src/d2c/workflow-shared.ts
|
|
@@ -27850,7 +28123,7 @@ function reviewsFor(report, previous) {
|
|
|
27850
28123
|
function implementationDirectory(report) {
|
|
27851
28124
|
const source = report.implementation.source;
|
|
27852
28125
|
if (!/^https?:\/\//i.test(source)) return source;
|
|
27853
|
-
return
|
|
28126
|
+
return join36("src", "d2c-output", report.task);
|
|
27854
28127
|
}
|
|
27855
28128
|
function createWorkflow(report, framework) {
|
|
27856
28129
|
const now = (/* @__PURE__ */ new Date()).toISOString();
|
|
@@ -27974,7 +28247,7 @@ function applyQualityIssueDecision(workflow, issueId2, decision) {
|
|
|
27974
28247
|
};
|
|
27975
28248
|
}
|
|
27976
28249
|
function workflowPath(workspace, task) {
|
|
27977
|
-
return
|
|
28250
|
+
return join36(taskDir(workspace, task), "workflow.json");
|
|
27978
28251
|
}
|
|
27979
28252
|
async function readWorkflow(workspace, task) {
|
|
27980
28253
|
return (await readRecoverableFile(workflowPath(workspace, task), decodeWorkflow))?.value;
|
|
@@ -28228,7 +28501,7 @@ function confirmApiMapping(mapping, operationKey, operations) {
|
|
|
28228
28501
|
|
|
28229
28502
|
// src/d2c/modules.ts
|
|
28230
28503
|
import { readFile as readFile28 } from "fs/promises";
|
|
28231
|
-
import { isAbsolute as
|
|
28504
|
+
import { isAbsolute as isAbsolute16, join as join37, normalize as normalize2, posix, sep as sep24, win32 as win322 } from "path";
|
|
28232
28505
|
import { z as z43 } from "zod";
|
|
28233
28506
|
var ModuleSchema = z43.object({
|
|
28234
28507
|
id: z43.string().regex(/^[A-Za-z0-9][A-Za-z0-9._-]{0,127}$/),
|
|
@@ -28240,20 +28513,20 @@ var ModuleSchema = z43.object({
|
|
|
28240
28513
|
}).strict();
|
|
28241
28514
|
var ManifestSchema3 = z43.object({ schema: z43.literal(1), modules: z43.array(ModuleSchema).min(1).max(500) }).passthrough();
|
|
28242
28515
|
function safeSource(path) {
|
|
28243
|
-
if (
|
|
28516
|
+
if (isAbsolute16(path) || posix.isAbsolute(path) || win322.isAbsolute(path) || path.includes("\0")) return false;
|
|
28244
28517
|
const value = normalize2(path);
|
|
28245
28518
|
const portableSegments = path.replaceAll("\\", "/").split("/");
|
|
28246
28519
|
return value !== ".." && !value.startsWith(`..${sep24}`) && !portableSegments.includes("..");
|
|
28247
28520
|
}
|
|
28248
28521
|
function d2cOutputDirectory(workspace, task) {
|
|
28249
28522
|
if (!D2C_TASK_PATTERN.test(task)) throw new Error(`Invalid D2C task: ${task}`);
|
|
28250
|
-
return
|
|
28523
|
+
return join37(workspace, "src", "d2c-output", task);
|
|
28251
28524
|
}
|
|
28252
28525
|
async function readD2cModules(workspace, task) {
|
|
28253
28526
|
const project = d2cOutputDirectory(workspace, task);
|
|
28254
28527
|
let raw;
|
|
28255
28528
|
try {
|
|
28256
|
-
raw = await readFile28(
|
|
28529
|
+
raw = await readFile28(join37(project, "d2c.modules.json"), "utf8");
|
|
28257
28530
|
} catch {
|
|
28258
28531
|
return [{ id: "page", label: task, sourceFiles: ["src/App.vue", "src/App.jsx"], keywords: [task] }];
|
|
28259
28532
|
}
|
|
@@ -28274,7 +28547,7 @@ async function readD2cModules(workspace, task) {
|
|
|
28274
28547
|
|
|
28275
28548
|
// src/d2c/integration.ts
|
|
28276
28549
|
import { mkdir as mkdir24, readFile as readFile29, writeFile as writeFile13 } from "fs/promises";
|
|
28277
|
-
import { join as
|
|
28550
|
+
import { join as join38 } from "path";
|
|
28278
28551
|
function record2(value) {
|
|
28279
28552
|
return typeof value === "object" && value !== null && !Array.isArray(value) ? value : void 0;
|
|
28280
28553
|
}
|
|
@@ -28410,7 +28683,7 @@ async def reset_acceptance(request: Request):
|
|
|
28410
28683
|
}
|
|
28411
28684
|
async function generateIntegrationArtifacts(projectDir, document, mappings, options = {}) {
|
|
28412
28685
|
if (mappings.some((mapping) => mapping.status === "needs-confirmation")) throw new Error("Confirm every uncertain OpenAPI mapping before generating integration code");
|
|
28413
|
-
const packagePath =
|
|
28686
|
+
const packagePath = join38(projectDir, "package.json");
|
|
28414
28687
|
let pkg;
|
|
28415
28688
|
try {
|
|
28416
28689
|
pkg = JSON.parse(await readFile29(packagePath, "utf8"));
|
|
@@ -28420,9 +28693,9 @@ async function generateIntegrationArtifacts(projectDir, document, mappings, opti
|
|
|
28420
28693
|
const scripts = options.pythonServer ? { ...record2(pkg.scripts) ?? {} } : { ...record2(pkg.scripts) ?? {}, mock: "node mock/server.mjs" };
|
|
28421
28694
|
const dependencies2 = options.pythonServer ? { ...record2(pkg.dependencies) ?? {}, axios: "^1.7.9" } : { ...record2(pkg.dependencies) ?? {}, axios: "^1.7.9", express: "^5.2.1" };
|
|
28422
28695
|
const selected = [...new Set(mappings.map((mapping) => mapping.operationKey))].map((key) => document.operations.find((operation) => operation.id === key)).filter((operation) => operation !== void 0);
|
|
28423
|
-
const apiDir =
|
|
28424
|
-
const mockDir =
|
|
28425
|
-
const serverDir =
|
|
28696
|
+
const apiDir = join38(projectDir, "src", "api");
|
|
28697
|
+
const mockDir = join38(projectDir, "mock");
|
|
28698
|
+
const serverDir = join38(projectDir, "server");
|
|
28426
28699
|
await Promise.all([
|
|
28427
28700
|
mkdir24(apiDir, { recursive: true }),
|
|
28428
28701
|
...options.pythonServer ? [] : [mkdir24(mockDir, { recursive: true })],
|
|
@@ -28432,23 +28705,23 @@ async function generateIntegrationArtifacts(projectDir, document, mappings, opti
|
|
|
28432
28705
|
if (options.pythonServer) files.push("server/main.py", "server/requirements.txt", "server/README.md", "server/flavor-runtime.json");
|
|
28433
28706
|
else files.push("mock/server.mjs");
|
|
28434
28707
|
await Promise.all([
|
|
28435
|
-
writeFile13(
|
|
28708
|
+
writeFile13(join38(apiDir, "http.js"), `import axios from "axios";
|
|
28436
28709
|
|
|
28437
28710
|
export const http = axios.create({ baseURL: import.meta.env.VITE_API_BASE_URL || ${JSON.stringify(document.baseUrl ?? "")}, timeout: 15000 });
|
|
28438
28711
|
|
|
28439
28712
|
http.interceptors.response.use((response) => response, (error) => Promise.reject({ name: "ApiError", status: error.response?.status, message: error.response?.data?.message || error.message, cause: error }));
|
|
28440
28713
|
`),
|
|
28441
|
-
writeFile13(
|
|
28442
|
-
writeFile13(
|
|
28714
|
+
writeFile13(join38(apiDir, "d2c-api.js"), apiModule(selected)),
|
|
28715
|
+
writeFile13(join38(apiDir, "d2c-bindings.json"), `${JSON.stringify({ schema: 1, generatedAt: (/* @__PURE__ */ new Date()).toISOString(), mappings }, null, 2)}
|
|
28443
28716
|
`),
|
|
28444
|
-
...options.pythonServer ? [] : [writeFile13(
|
|
28717
|
+
...options.pythonServer ? [] : [writeFile13(join38(mockDir, "server.mjs"), mockModule({ ...document, operations: selected }))],
|
|
28445
28718
|
writeFile13(packagePath, `${JSON.stringify({ ...pkg, scripts, dependencies: dependencies2 }, null, 2)}
|
|
28446
28719
|
`),
|
|
28447
28720
|
...options.pythonServer ? [
|
|
28448
|
-
writeFile13(
|
|
28449
|
-
writeFile13(
|
|
28450
|
-
writeFile13(
|
|
28451
|
-
writeFile13(
|
|
28721
|
+
writeFile13(join38(serverDir, "main.py"), pythonServerModule({ ...document, operations: selected })),
|
|
28722
|
+
writeFile13(join38(serverDir, "requirements.txt"), "fastapi>=0.116,<1\nuvicorn[standard]>=0.35,<1\nsqlalchemy>=2.0,<3\n"),
|
|
28723
|
+
writeFile13(join38(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"),
|
|
28724
|
+
writeFile13(join38(serverDir, "flavor-runtime.json"), `${JSON.stringify({
|
|
28452
28725
|
schema: 1,
|
|
28453
28726
|
kind: "python-fastapi",
|
|
28454
28727
|
cwd: "server",
|
|
@@ -28469,7 +28742,7 @@ http.interceptors.response.use((response) => response, (error) => Promise.reject
|
|
|
28469
28742
|
import { spawn as spawn8 } from "child_process";
|
|
28470
28743
|
import { access as access3, readFile as readFile30 } from "fs/promises";
|
|
28471
28744
|
import { createServer as createServer5 } from "net";
|
|
28472
|
-
import { join as
|
|
28745
|
+
import { join as join39 } from "path";
|
|
28473
28746
|
async function exists3(path) {
|
|
28474
28747
|
try {
|
|
28475
28748
|
await access3(path);
|
|
@@ -28577,7 +28850,7 @@ ${output.trim()}` : ""}`);
|
|
|
28577
28850
|
}
|
|
28578
28851
|
}
|
|
28579
28852
|
async function runPythonBackend(projectDir, options) {
|
|
28580
|
-
const serverDir =
|
|
28853
|
+
const serverDir = join39(projectDir, "server");
|
|
28581
28854
|
if (options.installDependencies !== false) await installPython(serverDir, options.signal);
|
|
28582
28855
|
const selectedPort = await port();
|
|
28583
28856
|
const url = `http://127.0.0.1:${selectedPort}`;
|
|
@@ -28623,7 +28896,7 @@ ${managed.output().slice(-4e3)}`);
|
|
|
28623
28896
|
};
|
|
28624
28897
|
}
|
|
28625
28898
|
async function runD2cMockServer(projectDir, options = {}) {
|
|
28626
|
-
const runtimePath =
|
|
28899
|
+
const runtimePath = join39(projectDir, "server", "flavor-runtime.json");
|
|
28627
28900
|
if (await exists3(runtimePath)) {
|
|
28628
28901
|
let runtime;
|
|
28629
28902
|
try {
|
|
@@ -28634,9 +28907,9 @@ async function runD2cMockServer(projectDir, options = {}) {
|
|
|
28634
28907
|
if (runtime.schema !== 1 || runtime.kind !== "python-fastapi") throw new Error("Unsupported E2E backend runtime manifest");
|
|
28635
28908
|
return runPythonBackend(projectDir, options);
|
|
28636
28909
|
}
|
|
28637
|
-
const serverPath =
|
|
28910
|
+
const serverPath = join39(projectDir, "mock", "server.mjs");
|
|
28638
28911
|
if (!await exists3(serverPath)) throw new Error(`D2C mock server is not generated at ${serverPath}`);
|
|
28639
|
-
if (options.installDependencies === false && !await exists3(
|
|
28912
|
+
if (options.installDependencies === false && !await exists3(join39(projectDir, "node_modules", "express", "package.json"))) {
|
|
28640
28913
|
throw new Error("D2C mock server dependencies are not installed");
|
|
28641
28914
|
}
|
|
28642
28915
|
if (options.installDependencies !== false) await install(projectDir, options.signal);
|
|
@@ -28679,7 +28952,7 @@ ${managed.output().slice(-4e3)}`);
|
|
|
28679
28952
|
// src/d2c/product.ts
|
|
28680
28953
|
import { createHash as createHash24, randomUUID as randomUUID27 } from "crypto";
|
|
28681
28954
|
import { copyFile as copyFile2, mkdir as mkdir25, readFile as readFile32, rename as rename13, stat as stat13, unlink as unlink7, writeFile as writeFile15 } from "fs/promises";
|
|
28682
|
-
import { join as
|
|
28955
|
+
import { join as join40 } from "path";
|
|
28683
28956
|
import { z as z44 } from "zod";
|
|
28684
28957
|
|
|
28685
28958
|
// src/e2e/prd-governance.ts
|
|
@@ -28834,13 +29107,13 @@ var PlanSchema2 = z44.object({
|
|
|
28834
29107
|
}).strict();
|
|
28835
29108
|
function d2cProductDirectory(workspace, task) {
|
|
28836
29109
|
if (!D2C_TASK_PATTERN.test(task)) throw new Error("Invalid D2C task name");
|
|
28837
|
-
return
|
|
29110
|
+
return join40(taskDir(workspace, task), "product");
|
|
28838
29111
|
}
|
|
28839
29112
|
function d2cProductPrototypeDirectory(workspace, task) {
|
|
28840
|
-
return
|
|
29113
|
+
return join40(d2cProductDirectory(workspace, task), "prototype");
|
|
28841
29114
|
}
|
|
28842
29115
|
function planPath(workspace, task) {
|
|
28843
|
-
return
|
|
29116
|
+
return join40(taskDir(workspace, task), "product-plan.json");
|
|
28844
29117
|
}
|
|
28845
29118
|
function resolveD2cProductTechnology(requirement, fallbackFramework = "vue") {
|
|
28846
29119
|
const frontendCandidates = [
|
|
@@ -29023,16 +29296,16 @@ async function readD2cProductPlan(workspace, task) {
|
|
|
29023
29296
|
}
|
|
29024
29297
|
async function discoverD2cProductArtifacts(workspace, plan) {
|
|
29025
29298
|
const product = d2cProductDirectory(workspace, plan.task);
|
|
29026
|
-
const prdUpdatedAt = await fileTimestamp(
|
|
29027
|
-
const prdHash = await fileHash(
|
|
29028
|
-
const prototypeUpdatedAt = await fileTimestamp(
|
|
29029
|
-
const prototypeHash = await fileHash(
|
|
29030
|
-
const expectedManifest =
|
|
29299
|
+
const prdUpdatedAt = await fileTimestamp(join40(product, "prd.md"));
|
|
29300
|
+
const prdHash = await fileHash(join40(product, "prd.md"));
|
|
29301
|
+
const prototypeUpdatedAt = await fileTimestamp(join40(product, "prototype", "index.html"));
|
|
29302
|
+
const prototypeHash = await fileHash(join40(product, "prototype", "index.html"));
|
|
29303
|
+
const expectedManifest = join40(product, "prototype", "interaction-manifest.json");
|
|
29031
29304
|
let manifestUpdatedAt = await fileTimestamp(expectedManifest);
|
|
29032
29305
|
if (manifestUpdatedAt === void 0) {
|
|
29033
|
-
const misplacedManifest =
|
|
29306
|
+
const misplacedManifest = join40(product, "interaction-manifest.json");
|
|
29034
29307
|
if (await fileTimestamp(misplacedManifest) !== void 0) {
|
|
29035
|
-
await mkdir25(
|
|
29308
|
+
await mkdir25(join40(product, "prototype"), { recursive: true });
|
|
29036
29309
|
await copyFile2(misplacedManifest, expectedManifest);
|
|
29037
29310
|
manifestUpdatedAt = await fileTimestamp(expectedManifest);
|
|
29038
29311
|
}
|
|
@@ -29065,8 +29338,8 @@ async function readD2cProductPlanView(workspace, task) {
|
|
|
29065
29338
|
if (stored === void 0) return void 0;
|
|
29066
29339
|
const plan = await discoverD2cProductArtifacts(workspace, stored);
|
|
29067
29340
|
if (plan.revision !== stored.revision) await writeD2cProductPlan(workspace, plan);
|
|
29068
|
-
const prdMarkdown = plan.prd === void 0 ? void 0 : await readFile32(
|
|
29069
|
-
const manifestPath =
|
|
29341
|
+
const prdMarkdown = plan.prd === void 0 ? void 0 : await readFile32(join40(taskDir(workspace, task), plan.prd.path), "utf8");
|
|
29342
|
+
const manifestPath = join40(d2cProductDirectory(workspace, task), "prototype", "interaction-manifest.json");
|
|
29070
29343
|
const validationError = plan.phase === "design-generating" && await fileTimestamp(manifestPath) !== void 0 ? await validateAndNormalizePrototypeManifest(manifestPath) : void 0;
|
|
29071
29344
|
return {
|
|
29072
29345
|
plan,
|
|
@@ -29225,7 +29498,7 @@ function createAcceptanceEvidence(coverage, run, now = /* @__PURE__ */ new Date(
|
|
|
29225
29498
|
|
|
29226
29499
|
// src/e2e/delivery-run.ts
|
|
29227
29500
|
import { createHash as createHash26 } from "crypto";
|
|
29228
|
-
import { join as
|
|
29501
|
+
import { join as join41 } from "path";
|
|
29229
29502
|
import { z as z45 } from "zod";
|
|
29230
29503
|
var DELIVERY_NODE_IDS = ["requirement", "prd", "design", "d2c", "api", "acceptance", "delivery"];
|
|
29231
29504
|
var ArtifactSchema = z45.object({
|
|
@@ -29342,7 +29615,7 @@ function completeDeliveryNode(run, node, outputs, now = /* @__PURE__ */ new Date
|
|
|
29342
29615
|
return { ...run, nodes, updatedAt: timestamp2 };
|
|
29343
29616
|
}
|
|
29344
29617
|
function deliveryRunPath(workspace, task) {
|
|
29345
|
-
return
|
|
29618
|
+
return join41(taskDir(workspace, task), "delivery-run.json");
|
|
29346
29619
|
}
|
|
29347
29620
|
function decodeDeliveryRun(raw) {
|
|
29348
29621
|
return DeliveryRunSchema.parse(JSON.parse(raw));
|
|
@@ -29385,14 +29658,14 @@ async function updateDeliveryRun(workspace, task, expectedRevision, mutation) {
|
|
|
29385
29658
|
// src/skills/manager.ts
|
|
29386
29659
|
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";
|
|
29387
29660
|
import { homedir as homedir3 } from "os";
|
|
29388
|
-
import { isAbsolute as
|
|
29661
|
+
import { isAbsolute as isAbsolute17, join as join42, relative as relative24, resolve as resolve35, sep as sep25 } from "path";
|
|
29389
29662
|
import { stringify } from "yaml";
|
|
29390
29663
|
var SkillManager = class {
|
|
29391
29664
|
#workspace;
|
|
29392
29665
|
#home;
|
|
29393
29666
|
constructor(options) {
|
|
29394
|
-
this.#workspace =
|
|
29395
|
-
this.#home =
|
|
29667
|
+
this.#workspace = resolve35(options.workspace);
|
|
29668
|
+
this.#home = resolve35(options.home ?? homedir3());
|
|
29396
29669
|
}
|
|
29397
29670
|
async list() {
|
|
29398
29671
|
const { registry, disabled } = await this.#registry();
|
|
@@ -29408,7 +29681,7 @@ var SkillManager = class {
|
|
|
29408
29681
|
async create(draft) {
|
|
29409
29682
|
const value = parseDraft(draft);
|
|
29410
29683
|
const projectRoot = this.#projectRoot();
|
|
29411
|
-
const directory =
|
|
29684
|
+
const directory = join42(projectRoot, value.name);
|
|
29412
29685
|
await mkdir26(projectRoot, { recursive: true, mode: 448 });
|
|
29413
29686
|
try {
|
|
29414
29687
|
await mkdir26(directory, { mode: 448 });
|
|
@@ -29417,7 +29690,7 @@ var SkillManager = class {
|
|
|
29417
29690
|
throw error;
|
|
29418
29691
|
}
|
|
29419
29692
|
try {
|
|
29420
|
-
await writeFile16(
|
|
29693
|
+
await writeFile16(join42(directory, "SKILL.md"), renderSkill(value), { encoding: "utf8", flag: "wx", mode: 384 });
|
|
29421
29694
|
await this.#persistEnabled(value.name, true);
|
|
29422
29695
|
return this.get(value.name);
|
|
29423
29696
|
} catch (error) {
|
|
@@ -29435,7 +29708,7 @@ var SkillManager = class {
|
|
|
29435
29708
|
throw error;
|
|
29436
29709
|
});
|
|
29437
29710
|
if (!info.isDirectory() || info.isSymbolicLink()) throw new Error(`Project skill path is not a real directory: ${validName}`);
|
|
29438
|
-
const skillFile =
|
|
29711
|
+
const skillFile = join42(directory, "SKILL.md");
|
|
29439
29712
|
const fileInfo = await lstat13(skillFile);
|
|
29440
29713
|
if (!fileInfo.isFile() || fileInfo.isSymbolicLink()) throw new Error(`Project skill file is not a real file: ${validName}`);
|
|
29441
29714
|
await writeFile16(skillFile, renderSkill(value), { encoding: "utf8", mode: 384 });
|
|
@@ -29467,12 +29740,12 @@ var SkillManager = class {
|
|
|
29467
29740
|
async #registry() {
|
|
29468
29741
|
const loaded = await loadConfig({ cwd: this.#workspace, home: this.#home });
|
|
29469
29742
|
const [globalPluginRoots, projectPluginRoots] = await Promise.all([
|
|
29470
|
-
discoverPluginSkillRoots(
|
|
29471
|
-
discoverPluginSkillRoots(
|
|
29743
|
+
discoverPluginSkillRoots(join42(this.#home, ".flavor-code", "plugins")),
|
|
29744
|
+
discoverPluginSkillRoots(join42(this.#workspace, ".flavor", "plugins"))
|
|
29472
29745
|
]);
|
|
29473
29746
|
return {
|
|
29474
29747
|
registry: new SkillRegistry({
|
|
29475
|
-
globalRoots: [
|
|
29748
|
+
globalRoots: [join42(this.#home, ".flavor-code", "skills"), ...globalPluginRoots],
|
|
29476
29749
|
projectRoots: [this.#projectRoot(), ...projectPluginRoots]
|
|
29477
29750
|
}),
|
|
29478
29751
|
disabled: new Set(loaded.config.skills.disabled)
|
|
@@ -29488,10 +29761,10 @@ var SkillManager = class {
|
|
|
29488
29761
|
};
|
|
29489
29762
|
}
|
|
29490
29763
|
#projectRoot() {
|
|
29491
|
-
return
|
|
29764
|
+
return join42(this.#workspace, ".flavor", "skills");
|
|
29492
29765
|
}
|
|
29493
29766
|
#projectSkillDirectory(name) {
|
|
29494
|
-
return
|
|
29767
|
+
return join42(this.#projectRoot(), name);
|
|
29495
29768
|
}
|
|
29496
29769
|
};
|
|
29497
29770
|
function parseName(value) {
|
|
@@ -29532,16 +29805,16 @@ async function discoverPluginSkillRoots(directory) {
|
|
|
29532
29805
|
const roots = [];
|
|
29533
29806
|
for (const entry2 of entries) {
|
|
29534
29807
|
if (!entry2.isDirectory() || entry2.isSymbolicLink()) continue;
|
|
29535
|
-
const pluginRoot = await realpath13(
|
|
29808
|
+
const pluginRoot = await realpath13(join42(directory, entry2.name));
|
|
29536
29809
|
try {
|
|
29537
|
-
const manifest = PluginManifestSchema.parse(JSON.parse(await readFile34(
|
|
29810
|
+
const manifest = PluginManifestSchema.parse(JSON.parse(await readFile34(join42(pluginRoot, "flavor-plugin.json"), "utf8")));
|
|
29538
29811
|
for (const contribution of manifest.contributes.skillRoots) {
|
|
29539
|
-
const candidate =
|
|
29812
|
+
const candidate = resolve35(pluginRoot, contribution.path);
|
|
29540
29813
|
const info = await lstat13(candidate);
|
|
29541
29814
|
if (!info.isDirectory() || info.isSymbolicLink()) continue;
|
|
29542
29815
|
const physical = await realpath13(candidate);
|
|
29543
29816
|
const relation = relative24(pluginRoot, physical);
|
|
29544
|
-
if (relation === ".." || relation.startsWith(`..${sep25}`) ||
|
|
29817
|
+
if (relation === ".." || relation.startsWith(`..${sep25}`) || isAbsolute17(relation)) continue;
|
|
29545
29818
|
roots.push(physical);
|
|
29546
29819
|
}
|
|
29547
29820
|
} catch {
|
|
@@ -29609,11 +29882,11 @@ async function createProjectMemoryManager(options) {
|
|
|
29609
29882
|
}
|
|
29610
29883
|
|
|
29611
29884
|
// src/mcp/config-manager.ts
|
|
29612
|
-
import { join as
|
|
29885
|
+
import { join as join43 } from "path";
|
|
29613
29886
|
var ProjectMcpConfigManager = class {
|
|
29614
29887
|
path;
|
|
29615
29888
|
constructor(workspace) {
|
|
29616
|
-
this.path =
|
|
29889
|
+
this.path = join43(workspace, ".flavor", "flavor.json");
|
|
29617
29890
|
}
|
|
29618
29891
|
async list() {
|
|
29619
29892
|
const document = await readRecoverableFile(this.path, (raw) => parseDocument2(this.path, raw));
|
|
@@ -29710,7 +29983,7 @@ function isRecord7(value) {
|
|
|
29710
29983
|
}
|
|
29711
29984
|
|
|
29712
29985
|
// src/desktop/model-config.ts
|
|
29713
|
-
import { join as
|
|
29986
|
+
import { join as join44 } from "path";
|
|
29714
29987
|
var DEFAULT_DESKTOP_MODELS = [
|
|
29715
29988
|
{
|
|
29716
29989
|
id: "anthropic:deepseek-v4-pro",
|
|
@@ -29731,7 +30004,7 @@ var DEFAULT_DESKTOP_MODELS = [
|
|
|
29731
30004
|
];
|
|
29732
30005
|
async function loadDesktopModels(workspace, home) {
|
|
29733
30006
|
try {
|
|
29734
|
-
const tokens2 = await createFileTokenStore(
|
|
30007
|
+
const tokens2 = await createFileTokenStore(join44(home, ".flavor-code", "auth.json")).load();
|
|
29735
30008
|
const pkceModels = [];
|
|
29736
30009
|
const knownPkce = /* @__PURE__ */ new Set();
|
|
29737
30010
|
for (const token of Object.values(tokens2)) {
|
|
@@ -29801,7 +30074,7 @@ function protocolLabel(type) {
|
|
|
29801
30074
|
import { createReadStream as createReadStream2 } from "fs";
|
|
29802
30075
|
import { realpath as realpath14, stat as stat14 } from "fs/promises";
|
|
29803
30076
|
import { createServer as createServer6 } from "http";
|
|
29804
|
-
import { extname as
|
|
30077
|
+
import { extname as extname4, isAbsolute as isAbsolute18, relative as relative25, resolve as resolve36, sep as sep26 } from "path";
|
|
29805
30078
|
var MIME_TYPES = {
|
|
29806
30079
|
".css": "text/css; charset=utf-8",
|
|
29807
30080
|
".gif": "image/gif",
|
|
@@ -29820,10 +30093,10 @@ function sendNotFound(response2) {
|
|
|
29820
30093
|
}
|
|
29821
30094
|
function isInside(root, candidate) {
|
|
29822
30095
|
const delta = relative25(root, candidate);
|
|
29823
|
-
return delta === "" || !delta.startsWith(`..${sep26}`) && delta !== ".." && !
|
|
30096
|
+
return delta === "" || !delta.startsWith(`..${sep26}`) && delta !== ".." && !isAbsolute18(delta);
|
|
29824
30097
|
}
|
|
29825
30098
|
async function startD2cProductPreview(rootDirectory) {
|
|
29826
|
-
const root = await realpath14(
|
|
30099
|
+
const root = await realpath14(resolve36(rootDirectory));
|
|
29827
30100
|
const rootInfo = await stat14(root);
|
|
29828
30101
|
if (!rootInfo.isDirectory()) throw new Error("D2C product prototype directory is unavailable");
|
|
29829
30102
|
const server = createServer6(async (request, response2) => {
|
|
@@ -29835,7 +30108,7 @@ async function startD2cProductPreview(rootDirectory) {
|
|
|
29835
30108
|
let pathname = decodeURIComponent(url.pathname);
|
|
29836
30109
|
if (pathname === "/") pathname = "/index.html";
|
|
29837
30110
|
if (pathname.includes("\0")) return sendNotFound(response2);
|
|
29838
|
-
const unresolved =
|
|
30111
|
+
const unresolved = resolve36(root, `.${pathname}`);
|
|
29839
30112
|
if (!isInside(root, unresolved)) return sendNotFound(response2);
|
|
29840
30113
|
const target = await realpath14(unresolved).catch(() => void 0);
|
|
29841
30114
|
if (target === void 0 || !isInside(root, target)) return sendNotFound(response2);
|
|
@@ -29843,7 +30116,7 @@ async function startD2cProductPreview(rootDirectory) {
|
|
|
29843
30116
|
if (!info.isFile()) return sendNotFound(response2);
|
|
29844
30117
|
response2.writeHead(200, {
|
|
29845
30118
|
"content-length": info.size,
|
|
29846
|
-
"content-type": MIME_TYPES[
|
|
30119
|
+
"content-type": MIME_TYPES[extname4(target).toLowerCase()] ?? "application/octet-stream"
|
|
29847
30120
|
});
|
|
29848
30121
|
createReadStream2(target).on("error", () => response2.destroy()).pipe(response2);
|
|
29849
30122
|
} catch {
|
|
@@ -29876,33 +30149,33 @@ function pngDataUrl(png) {
|
|
|
29876
30149
|
}
|
|
29877
30150
|
async function detectD2cFramework(workspace, task) {
|
|
29878
30151
|
try {
|
|
29879
|
-
const pkg = JSON.parse(await readFile35(
|
|
30152
|
+
const pkg = JSON.parse(await readFile35(join45(d2cOutputDirectory(workspace, task), "package.json"), "utf8"));
|
|
29880
30153
|
return pkg.dependencies?.react === void 0 ? "vue" : "react";
|
|
29881
30154
|
} catch {
|
|
29882
30155
|
return "vue";
|
|
29883
30156
|
}
|
|
29884
30157
|
}
|
|
29885
30158
|
function integrationDirectory(workspace, task) {
|
|
29886
|
-
return
|
|
30159
|
+
return join45(workspace, ".flavor", "d2c", task, "integration");
|
|
29887
30160
|
}
|
|
29888
30161
|
var D2C_BACKEND_SOURCE_EXTENSIONS = /* @__PURE__ */ new Set([".cfg", ".env", ".ini", ".js", ".json", ".lock", ".mjs", ".py", ".toml", ".txt", ".yaml", ".yml"]);
|
|
29889
30162
|
var D2C_BACKEND_IGNORED_DIRECTORIES = /* @__PURE__ */ new Set([".git", ".venv", "__pycache__", "data", "node_modules", "venv"]);
|
|
29890
30163
|
async function d2cBackendSourceFingerprint(project) {
|
|
29891
|
-
const runtimeManifest =
|
|
30164
|
+
const runtimeManifest = join45(project, "server", "flavor-runtime.json");
|
|
29892
30165
|
const realBackend = (await stat15(runtimeManifest).catch(() => void 0))?.isFile() === true;
|
|
29893
|
-
const root = realBackend ?
|
|
30166
|
+
const root = realBackend ? join45(project, "server") : join45(project, "mock");
|
|
29894
30167
|
if ((await stat15(root).catch(() => void 0))?.isDirectory() !== true) return void 0;
|
|
29895
30168
|
const files = [];
|
|
29896
30169
|
const visit = async (directory) => {
|
|
29897
30170
|
const entries = await readdir12(directory, { withFileTypes: true });
|
|
29898
30171
|
for (const entry2 of entries) {
|
|
29899
30172
|
if (entry2.isDirectory()) {
|
|
29900
|
-
if (!D2C_BACKEND_IGNORED_DIRECTORIES.has(entry2.name)) await visit(
|
|
30173
|
+
if (!D2C_BACKEND_IGNORED_DIRECTORIES.has(entry2.name)) await visit(join45(directory, entry2.name));
|
|
29901
30174
|
continue;
|
|
29902
30175
|
}
|
|
29903
30176
|
if (!entry2.isFile()) continue;
|
|
29904
30177
|
const extension2 = entry2.name.includes(".") ? `.${entry2.name.split(".").pop().toLowerCase()}` : "";
|
|
29905
|
-
if (D2C_BACKEND_SOURCE_EXTENSIONS.has(extension2)) files.push(
|
|
30178
|
+
if (D2C_BACKEND_SOURCE_EXTENSIONS.has(extension2)) files.push(join45(directory, entry2.name));
|
|
29906
30179
|
}
|
|
29907
30180
|
};
|
|
29908
30181
|
await visit(root);
|
|
@@ -29954,7 +30227,7 @@ var DesktopRuntimeController = class {
|
|
|
29954
30227
|
/** Backend source digest observed when the managed process was started. */
|
|
29955
30228
|
#d2cMockSourceFingerprints = /* @__PURE__ */ new Map();
|
|
29956
30229
|
constructor(options) {
|
|
29957
|
-
this.#home =
|
|
30230
|
+
this.#home = resolve37(options.home ?? homedir4());
|
|
29958
30231
|
this.#createRuntime = options.createRuntime ?? (async (runtimeOptions) => createProductionRuntime(runtimeOptions));
|
|
29959
30232
|
this.#createTerminalService = options.createTerminalService ?? ((workspace) => new TerminalService(workspace));
|
|
29960
30233
|
this.#listSessions = options.listSessions ?? (async (workspace) => {
|
|
@@ -30029,7 +30302,7 @@ var DesktopRuntimeController = class {
|
|
|
30029
30302
|
};
|
|
30030
30303
|
}
|
|
30031
30304
|
async openWorkspace(path) {
|
|
30032
|
-
const workspace =
|
|
30305
|
+
const workspace = resolve37(path);
|
|
30033
30306
|
if (workspace !== this.#workspace) {
|
|
30034
30307
|
await this.#disposeRuntime();
|
|
30035
30308
|
await this.#stopAllD2cPreviews();
|
|
@@ -30456,7 +30729,7 @@ var DesktopRuntimeController = class {
|
|
|
30456
30729
|
if (workflow === void 0 || !reviewProgress(workflow).complete || workflow.stage === "visual-review") {
|
|
30457
30730
|
throw new Error("Complete visual review before importing OpenAPI");
|
|
30458
30731
|
}
|
|
30459
|
-
const moduleManifest =
|
|
30732
|
+
const moduleManifest = join45(d2cOutputDirectory(workspace, task), "d2c.modules.json");
|
|
30460
30733
|
const moduleManifestInfo = await stat15(moduleManifest).catch(() => void 0);
|
|
30461
30734
|
if (moduleManifestInfo === void 0 || !moduleManifestInfo.isFile()) {
|
|
30462
30735
|
throw new Error("D2C implementation must write d2c.modules.json before API mapping; ask the agent to split the project into modules first");
|
|
@@ -30469,8 +30742,8 @@ var DesktopRuntimeController = class {
|
|
|
30469
30742
|
await mkdir27(dir, { recursive: true });
|
|
30470
30743
|
const hash = createHash27("sha256").update(raw).digest("hex");
|
|
30471
30744
|
await Promise.all([
|
|
30472
|
-
writeFile17(
|
|
30473
|
-
writeFile17(
|
|
30745
|
+
writeFile17(join45(dir, "swagger.json"), raw),
|
|
30746
|
+
writeFile17(join45(dir, "openapi.normalized.json"), `${JSON.stringify(document, null, 2)}
|
|
30474
30747
|
`)
|
|
30475
30748
|
]);
|
|
30476
30749
|
const next = {
|
|
@@ -30505,7 +30778,7 @@ var DesktopRuntimeController = class {
|
|
|
30505
30778
|
let workflow = await readWorkflow(workspace, task);
|
|
30506
30779
|
if (workflow?.openapi === void 0 && workflow !== void 0 && reviewProgress(workflow).complete && await readD2cProductPlanView(workspace, task) !== void 0) {
|
|
30507
30780
|
const modules = await readD2cModules(workspace, task);
|
|
30508
|
-
const productOpenApiPath =
|
|
30781
|
+
const productOpenApiPath = join45(taskDir(workspace, task), "product", "openapi.json");
|
|
30509
30782
|
const authored = await readFile35(productOpenApiPath, "utf8").catch(() => void 0);
|
|
30510
30783
|
const generated = authored === void 0;
|
|
30511
30784
|
const prepared = await this.#storeD2cOpenApi(
|
|
@@ -30518,7 +30791,7 @@ var DesktopRuntimeController = class {
|
|
|
30518
30791
|
return prepared;
|
|
30519
30792
|
}
|
|
30520
30793
|
if (workflow?.openapi === void 0 || workflow.mappings === void 0) return void 0;
|
|
30521
|
-
const document = JSON.parse(await readFile35(
|
|
30794
|
+
const document = JSON.parse(await readFile35(join45(integrationDirectory(workspace, task), "openapi.normalized.json"), "utf8"));
|
|
30522
30795
|
return { workflow, document, mappings: workflow.mappings };
|
|
30523
30796
|
}
|
|
30524
30797
|
async confirmD2cMapping(task, moduleId, operationKey) {
|
|
@@ -30585,7 +30858,7 @@ var DesktopRuntimeController = class {
|
|
|
30585
30858
|
].join("\n");
|
|
30586
30859
|
const delivery = await readDeliveryRun(workspace, task);
|
|
30587
30860
|
if (delivery?.nodes.api.status === "running") {
|
|
30588
|
-
const apiArtifacts = await Promise.all(generated.files.map(async (file) => artifactRef(file, await readFile35(
|
|
30861
|
+
const apiArtifacts = await Promise.all(generated.files.map(async (file) => artifactRef(file, await readFile35(join45(project, file)))));
|
|
30589
30862
|
const completed = await updateDeliveryRun(
|
|
30590
30863
|
workspace,
|
|
30591
30864
|
task,
|
|
@@ -30612,7 +30885,7 @@ var DesktopRuntimeController = class {
|
|
|
30612
30885
|
try {
|
|
30613
30886
|
const fingerprint2 = await d2cBackendSourceFingerprint(project);
|
|
30614
30887
|
if (fingerprint2 !== void 0) this.#d2cMockSourceFingerprints.set(task, fingerprint2);
|
|
30615
|
-
const environmentPath =
|
|
30888
|
+
const environmentPath = join45(project, ".env.local");
|
|
30616
30889
|
const existingEnvironment = await readFile35(environmentPath, "utf8").catch(() => "");
|
|
30617
30890
|
const environment = `${existingEnvironment.split(/\r?\n/).filter((line) => !/^\s*VITE_API_BASE_URL\s*=/.test(line)).join("\n").trim()}
|
|
30618
30891
|
VITE_API_BASE_URL=${running.url}
|
|
@@ -30693,7 +30966,7 @@ VITE_API_BASE_URL=${running.url}
|
|
|
30693
30966
|
const readyPreview = this.#d2cPreviews.get(task);
|
|
30694
30967
|
const readyMock = this.#d2cMocks.get(task);
|
|
30695
30968
|
if (readyPreview === void 0 || readyMock === void 0) throw new Error("D2C preview or backend service is unavailable after synchronization");
|
|
30696
|
-
const manifestPath =
|
|
30969
|
+
const manifestPath = join45(taskDir(workspace, task), "design", "interaction-manifest.json");
|
|
30697
30970
|
const manifest = parseInteractionManifest(await readFile35(manifestPath, "utf8"));
|
|
30698
30971
|
const baseline = await this.#verifyAcceptanceBaseline(workspace, task);
|
|
30699
30972
|
const coverage = baseline === void 0 ? void 0 : verifyRequirementCoverage(baseline.approvedPrd.criteria, manifest);
|
|
@@ -30705,7 +30978,7 @@ VITE_API_BASE_URL=${running.url}
|
|
|
30705
30978
|
await mkdir27(planDir, { recursive: true });
|
|
30706
30979
|
try {
|
|
30707
30980
|
const observations = await this.#observeD2cPages(manifest, readyPreview.url);
|
|
30708
|
-
const prd = await readFile35(
|
|
30981
|
+
const prd = await readFile35(join45(taskDir(workspace, task), "product", "prd.md"), "utf8").catch(() => void 0);
|
|
30709
30982
|
const apiContext = JSON.stringify({ openapi: workflow.openapi, mappings: workflow.mappings }, null, 2);
|
|
30710
30983
|
const plan = await this.#d2cJudge.planInteractions({ task, seed: manifest, observations, ...prd === void 0 ? {} : { prd }, apiContext });
|
|
30711
30984
|
executableManifest = mergeInteractionManifests(manifest, plan.manifest);
|
|
@@ -30716,9 +30989,9 @@ VITE_API_BASE_URL=${running.url}
|
|
|
30716
30989
|
plannedScenarios: executableManifest.pages.reduce((total, page) => total + page.scenarios.length, 0),
|
|
30717
30990
|
pageAnalyses: plan.pageAnalyses
|
|
30718
30991
|
};
|
|
30719
|
-
await writeFile17(
|
|
30992
|
+
await writeFile17(join45(planDir, "autonomous-interaction-plan.json"), `${JSON.stringify({ ...plan, manifest: executableManifest }, null, 2)}
|
|
30720
30993
|
`, { encoding: "utf8", mode: 384 });
|
|
30721
|
-
await rm15(
|
|
30994
|
+
await rm15(join45(planDir, "autonomous-interaction-diagnostic.json"), { force: true });
|
|
30722
30995
|
} catch (cause) {
|
|
30723
30996
|
const detail = message(cause);
|
|
30724
30997
|
review = {
|
|
@@ -30726,8 +30999,8 @@ VITE_API_BASE_URL=${running.url}
|
|
|
30726
30999
|
plannedScenarios: manifest.pages.reduce((total, page) => total + page.scenarios.length, 0),
|
|
30727
31000
|
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}`
|
|
30728
31001
|
};
|
|
30729
|
-
await rm15(
|
|
30730
|
-
await writeFile17(
|
|
31002
|
+
await rm15(join45(planDir, "autonomous-interaction-plan.json"), { force: true });
|
|
31003
|
+
await writeFile17(join45(planDir, "autonomous-interaction-diagnostic.json"), `${JSON.stringify({
|
|
30731
31004
|
schema: 1,
|
|
30732
31005
|
stage: "planning",
|
|
30733
31006
|
occurredAt: (/* @__PURE__ */ new Date()).toISOString(),
|
|
@@ -30741,13 +31014,13 @@ VITE_API_BASE_URL=${running.url}
|
|
|
30741
31014
|
let result = { ...rawResult, evidenceMode: review.mode };
|
|
30742
31015
|
if (coverage !== void 0) {
|
|
30743
31016
|
const evidence = createAcceptanceEvidence(coverage, result);
|
|
30744
|
-
await writeFile17(
|
|
31017
|
+
await writeFile17(join45(taskDir(workspace, task), "acceptance-evidence.json"), `${JSON.stringify({ ...evidence, baseline }, null, 2)}
|
|
30745
31018
|
`, { encoding: "utf8", mode: 384 });
|
|
30746
31019
|
if (!evidence.passed) result = { ...result, passed: false, failures: Math.max(1, result.failures) };
|
|
30747
31020
|
}
|
|
30748
31021
|
const dir = integrationDirectory(workspace, task);
|
|
30749
31022
|
await mkdir27(dir, { recursive: true });
|
|
30750
|
-
await writeFile17(
|
|
31023
|
+
await writeFile17(join45(dir, "interaction-results.json"), `${JSON.stringify(result, null, 2)}
|
|
30751
31024
|
`);
|
|
30752
31025
|
const next = await updateWorkflow(workspace, task, workflow.revision, (current) => applyInteractionRun(current, result));
|
|
30753
31026
|
return { workflow: next, result, review };
|
|
@@ -30760,7 +31033,7 @@ VITE_API_BASE_URL=${running.url}
|
|
|
30760
31033
|
if (mock === void 0 || !mock.exited() || result.failures === 0) return result;
|
|
30761
31034
|
const output = mock.output().trim();
|
|
30762
31035
|
try {
|
|
30763
|
-
const dir =
|
|
31036
|
+
const dir = join45(d2cOutputDirectory(workspace, task), "mock-server.log");
|
|
30764
31037
|
await writeFile17(dir, `${output}
|
|
30765
31038
|
`);
|
|
30766
31039
|
} catch {
|
|
@@ -30855,7 +31128,7 @@ VITE_API_BASE_URL=${running.url}
|
|
|
30855
31128
|
if (current === void 0) throw new Error("D2C product plan is unavailable");
|
|
30856
31129
|
if (current.plan.approvedPrd !== void 0) throw new Error("The approved PRD is immutable and cannot be edited");
|
|
30857
31130
|
if (current.plan.phase !== "prd-review") throw new Error("PRD sections can only be edited during PRD review");
|
|
30858
|
-
const path =
|
|
31131
|
+
const path = join45(taskDir(workspace, task), "product", "prd.md");
|
|
30859
31132
|
const updated = await updatePrdSectionFile(path, { sectionId, body, expectedHash });
|
|
30860
31133
|
const now = (/* @__PURE__ */ new Date()).toISOString();
|
|
30861
31134
|
await writeD2cProductPlan(workspace, {
|
|
@@ -30875,15 +31148,15 @@ VITE_API_BASE_URL=${running.url}
|
|
|
30875
31148
|
async #assertProductPrdLocked(workspace, task) {
|
|
30876
31149
|
const current = await readD2cProductPlanView(workspace, task);
|
|
30877
31150
|
if (current?.plan.approvedPrd === void 0) return;
|
|
30878
|
-
await assertApprovedPrd(
|
|
31151
|
+
await assertApprovedPrd(join45(taskDir(workspace, task), "product", "prd.md"), current.plan.approvedPrd);
|
|
30879
31152
|
}
|
|
30880
31153
|
async #acceptancePaths(workspace, task) {
|
|
30881
|
-
const product =
|
|
30882
|
-
const openapi =
|
|
31154
|
+
const product = join45(taskDir(workspace, task), "product");
|
|
31155
|
+
const openapi = join45(product, "openapi.json");
|
|
30883
31156
|
return {
|
|
30884
|
-
prd:
|
|
30885
|
-
prototype:
|
|
30886
|
-
interaction:
|
|
31157
|
+
prd: join45(product, "prd.md"),
|
|
31158
|
+
prototype: join45(product, "prototype", "index.html"),
|
|
31159
|
+
interaction: join45(product, "prototype", "interaction-manifest.json"),
|
|
30887
31160
|
...await stat15(openapi).catch(() => void 0) === void 0 ? {} : { openapi }
|
|
30888
31161
|
};
|
|
30889
31162
|
}
|
|
@@ -30891,17 +31164,17 @@ VITE_API_BASE_URL=${running.url}
|
|
|
30891
31164
|
const product = await readD2cProductPlanView(workspace, task);
|
|
30892
31165
|
if (product?.plan.approvedPrd === void 0) return void 0;
|
|
30893
31166
|
await this.#assertProductPrdLocked(workspace, task);
|
|
30894
|
-
const baseline = JSON.parse(await readFile35(
|
|
31167
|
+
const baseline = JSON.parse(await readFile35(join45(taskDir(workspace, task), "acceptance-baseline.json"), "utf8"));
|
|
30895
31168
|
await verifyAcceptanceBaseline(await this.#acceptancePaths(workspace, task), baseline);
|
|
30896
|
-
const design =
|
|
31169
|
+
const design = join45(taskDir(workspace, task), "design");
|
|
30897
31170
|
await Promise.all([
|
|
30898
|
-
verifyAcceptanceArtifact(
|
|
30899
|
-
verifyAcceptanceArtifact(
|
|
31171
|
+
verifyAcceptanceArtifact(join45(design, "index.html"), baseline.artifacts.prototype, "runtime prototype"),
|
|
31172
|
+
verifyAcceptanceArtifact(join45(design, "interaction-manifest.json"), baseline.artifacts.interaction, "runtime interaction contract")
|
|
30900
31173
|
]);
|
|
30901
31174
|
const workflow = await readWorkflow(workspace, task);
|
|
30902
31175
|
if (workflow?.openapi !== void 0) {
|
|
30903
31176
|
await verifyAcceptanceArtifact(
|
|
30904
|
-
|
|
31177
|
+
join45(taskDir(workspace, task), "integration", "swagger.json"),
|
|
30905
31178
|
{ hash: workflow.openapi.hash },
|
|
30906
31179
|
"runtime OpenAPI contract"
|
|
30907
31180
|
);
|
|
@@ -30922,14 +31195,14 @@ VITE_API_BASE_URL=${running.url}
|
|
|
30922
31195
|
throw new Error("D2C PRD is empty and cannot be accepted");
|
|
30923
31196
|
}
|
|
30924
31197
|
if (stage === "design" && accepted) {
|
|
30925
|
-
const manifest =
|
|
31198
|
+
const manifest = join45(d2cProductPrototypeDirectory(workspace, task), "interaction-manifest.json");
|
|
30926
31199
|
const normalizedManifest = parseInteractionManifest(await readFile35(manifest, "utf8"));
|
|
30927
31200
|
if (current.plan.approvedPrd === void 0) throw new Error("Approve the PRD before confirming the design");
|
|
30928
31201
|
verifyRequirementCoverage(current.plan.approvedPrd.criteria, normalizedManifest);
|
|
30929
31202
|
await writeFile17(manifest, `${JSON.stringify(normalizedManifest, null, 2)}
|
|
30930
31203
|
`, { encoding: "utf8", mode: 384 });
|
|
30931
31204
|
const baseline = await captureAcceptanceBaseline(await this.#acceptancePaths(workspace, task), current.plan.approvedPrd);
|
|
30932
|
-
await writeFile17(
|
|
31205
|
+
await writeFile17(join45(taskDir(workspace, task), "acceptance-baseline.json"), `${JSON.stringify(baseline, null, 2)}
|
|
30933
31206
|
`, { encoding: "utf8", mode: 384 });
|
|
30934
31207
|
}
|
|
30935
31208
|
const plan = await writeD2cProductPlan(
|
|
@@ -30963,8 +31236,8 @@ VITE_API_BASE_URL=${running.url}
|
|
|
30963
31236
|
const imported = await this.importD2cDesign(task, d2cProductPrototypeDirectory(workspace, task));
|
|
30964
31237
|
const delivery = await readDeliveryRun(workspace, task);
|
|
30965
31238
|
if (delivery !== void 0) {
|
|
30966
|
-
const prototype = await readFile35(
|
|
30967
|
-
const interaction = await readFile35(
|
|
31239
|
+
const prototype = await readFile35(join45(d2cProductPrototypeDirectory(workspace, task), "index.html"), "utf8");
|
|
31240
|
+
const interaction = await readFile35(join45(d2cProductPrototypeDirectory(workspace, task), "interaction-manifest.json"), "utf8");
|
|
30968
31241
|
const completed = await updateDeliveryRun(workspace, task, delivery.revision, (run) => completeDeliveryNode(run, "design", [
|
|
30969
31242
|
artifactRef("product/prototype/index.html", prototype),
|
|
30970
31243
|
artifactRef("product/prototype/interaction-manifest.json", interaction)
|
|
@@ -31048,10 +31321,10 @@ VITE_API_BASE_URL=${running.url}
|
|
|
31048
31321
|
const next = await updateWorkflow(workspace, task, workflow.revision, (current) => applyQualityJudgment(current, judgment));
|
|
31049
31322
|
const qualityContent = `${JSON.stringify(judgment, null, 2)}
|
|
31050
31323
|
`;
|
|
31051
|
-
await writeFile17(
|
|
31324
|
+
await writeFile17(join45(taskDir(workspace, task), "quality-judge.json"), qualityContent, { encoding: "utf8", mode: 384 });
|
|
31052
31325
|
const delivery = await readDeliveryRun(workspace, task);
|
|
31053
31326
|
if (next.stage === "completed" && delivery?.nodes.acceptance.status === "running") {
|
|
31054
|
-
const interactionContent = await readFile35(
|
|
31327
|
+
const interactionContent = await readFile35(join45(integrationDirectory(workspace, task), "interaction-results.json"), "utf8");
|
|
31055
31328
|
const accepted = await updateDeliveryRun(workspace, task, delivery.revision, (run) => completeDeliveryNode(run, "acceptance", [
|
|
31056
31329
|
artifactRef("integration/interaction-results.json", interactionContent),
|
|
31057
31330
|
artifactRef("quality-judge.json", qualityContent)
|
|
@@ -31231,70 +31504,9 @@ function desktopWindowChrome(platform = process.platform) {
|
|
|
31231
31504
|
};
|
|
31232
31505
|
}
|
|
31233
31506
|
|
|
31234
|
-
// src/utils/crash-guard.ts
|
|
31235
|
-
import { mkdirSync as mkdirSync4, writeFileSync as writeFileSync2 } from "fs";
|
|
31236
|
-
import { join as join45 } from "path";
|
|
31237
|
-
var installed = false;
|
|
31238
|
-
function installCrashGuard(options = {}) {
|
|
31239
|
-
if (installed) return;
|
|
31240
|
-
installed = true;
|
|
31241
|
-
const crashLogDir = () => {
|
|
31242
|
-
try {
|
|
31243
|
-
return join45((options.workspace ?? process.cwd)(), ".flavor");
|
|
31244
|
-
} catch {
|
|
31245
|
-
return process.cwd();
|
|
31246
|
-
}
|
|
31247
|
-
};
|
|
31248
|
-
const writeCrashLog = (kind, detail) => {
|
|
31249
|
-
try {
|
|
31250
|
-
const dir = crashLogDir();
|
|
31251
|
-
mkdirSync4(dir, { recursive: true });
|
|
31252
|
-
const path = join45(dir, `crash-${(/* @__PURE__ */ new Date()).toISOString().replace(/[^0-9]/g, "")}-${process.pid}.log`);
|
|
31253
|
-
writeFileSync2(path, [
|
|
31254
|
-
`kind: ${kind}`,
|
|
31255
|
-
`version: ${process.version}`,
|
|
31256
|
-
`platform: ${process.platform}`,
|
|
31257
|
-
`time: ${(/* @__PURE__ */ new Date()).toISOString()}`,
|
|
31258
|
-
"",
|
|
31259
|
-
detail
|
|
31260
|
-
].join("\n"), { encoding: "utf8", mode: 384 });
|
|
31261
|
-
return path;
|
|
31262
|
-
} catch {
|
|
31263
|
-
return void 0;
|
|
31264
|
-
}
|
|
31265
|
-
};
|
|
31266
|
-
const restoreTerminal = () => {
|
|
31267
|
-
try {
|
|
31268
|
-
if (process.stdout.isTTY === true) process.stdout.write("\x1B[?25h\x1B[?1049l");
|
|
31269
|
-
} catch {
|
|
31270
|
-
}
|
|
31271
|
-
};
|
|
31272
|
-
const describe = (reason) => {
|
|
31273
|
-
if (reason instanceof Error) return `${reason.name}: ${reason.message}
|
|
31274
|
-
${reason.stack ?? ""}`;
|
|
31275
|
-
return String(reason);
|
|
31276
|
-
};
|
|
31277
|
-
const crash = (kind, reason) => {
|
|
31278
|
-
const detail = describe(reason);
|
|
31279
|
-
const logPath = writeCrashLog(kind, detail);
|
|
31280
|
-
restoreTerminal();
|
|
31281
|
-
try {
|
|
31282
|
-
const safe = redactErrorText(detail.split("\n").slice(0, 3).join("\n"));
|
|
31283
|
-
process.stderr.write(`flavor crashed (${kind}): ${safe}
|
|
31284
|
-
`);
|
|
31285
|
-
if (logPath !== void 0) process.stderr.write(`crash log: ${logPath}
|
|
31286
|
-
`);
|
|
31287
|
-
} catch {
|
|
31288
|
-
}
|
|
31289
|
-
process.exit(1);
|
|
31290
|
-
};
|
|
31291
|
-
process.on("unhandledRejection", (reason) => crash("unhandledRejection", reason));
|
|
31292
|
-
process.on("uncaughtException", (error) => crash("uncaughtException", error));
|
|
31293
|
-
}
|
|
31294
|
-
|
|
31295
31507
|
// src/desktop/git-manager.ts
|
|
31296
31508
|
import { readFile as readFile36, rm as rm16 } from "fs/promises";
|
|
31297
|
-
import { isAbsolute as
|
|
31509
|
+
import { isAbsolute as isAbsolute19, relative as relative26, resolve as resolve38 } from "path";
|
|
31298
31510
|
function failure(action, stderr, stdout) {
|
|
31299
31511
|
return new Error(`${action}\u5931\u8D25\uFF1A${(stderr.trim() || stdout.trim() || "\u672A\u77E5 Git \u9519\u8BEF").split("\n")[0]}`);
|
|
31300
31512
|
}
|
|
@@ -31387,13 +31599,13 @@ async function desktopLastTurnDiff(workspace, sessionId, paths = []) {
|
|
|
31387
31599
|
return path.replaceAll("\\", "/");
|
|
31388
31600
|
}));
|
|
31389
31601
|
try {
|
|
31390
|
-
const tree = JSON.parse(await readFile36(
|
|
31602
|
+
const tree = JSON.parse(await readFile36(resolve38(workspace, ".flavor", "session-trees", sessionId, "tree.json"), "utf8"));
|
|
31391
31603
|
const leaf = tree.nodes?.find((node) => node.id === tree.leafId);
|
|
31392
31604
|
const parent = leaf?.parentId == null ? void 0 : tree.nodes?.find((node) => node.id === leaf.parentId);
|
|
31393
31605
|
if (leaf === void 0 || parent === void 0) return desktopGitReviewDiff(workspace, { scope: "last-turn", paths });
|
|
31394
31606
|
const load = async (id) => {
|
|
31395
31607
|
if (!/^checkpoint-[A-Za-z0-9-]+$/.test(id)) throw new Error("Invalid checkpoint id");
|
|
31396
|
-
return JSON.parse(await readFile36(
|
|
31608
|
+
return JSON.parse(await readFile36(resolve38(workspace, ".flavor", "checkpoints", "manifests", `${id}.json`), "utf8"));
|
|
31397
31609
|
};
|
|
31398
31610
|
const [before, after] = await Promise.all([load(parent.checkpointId), load(leaf.checkpointId)]);
|
|
31399
31611
|
const oldFiles = new Map(before.files.map((file) => [file.path, file.digest]));
|
|
@@ -31402,7 +31614,7 @@ async function desktopLastTurnDiff(workspace, sessionId, paths = []) {
|
|
|
31402
31614
|
const object = async (digest) => {
|
|
31403
31615
|
if (digest === void 0) return "";
|
|
31404
31616
|
if (!/^[a-f0-9]{64}$/.test(digest)) throw new Error("Invalid checkpoint object");
|
|
31405
|
-
const value = await readFile36(
|
|
31617
|
+
const value = await readFile36(resolve38(workspace, ".flavor", "checkpoints", "objects", digest));
|
|
31406
31618
|
return value.includes(0) ? "[binary content]" : value.toString("utf8");
|
|
31407
31619
|
};
|
|
31408
31620
|
const chunks = await Promise.all(changed.map(async (path) => wholeFileDiff(path, await object(oldFiles.get(path)), await object(newFiles.get(path)))));
|
|
@@ -31426,11 +31638,11 @@ async function assertListed(workspace, path) {
|
|
|
31426
31638
|
return file;
|
|
31427
31639
|
}
|
|
31428
31640
|
function safeWorkspacePath(workspace, path) {
|
|
31429
|
-
if (
|
|
31430
|
-
const root =
|
|
31431
|
-
const target =
|
|
31641
|
+
if (isAbsolute19(path)) throw new Error("Git \u6587\u4EF6\u8DEF\u5F84\u5FC5\u987B\u662F\u9879\u76EE\u5185\u7684\u76F8\u5BF9\u8DEF\u5F84");
|
|
31642
|
+
const root = resolve38(workspace);
|
|
31643
|
+
const target = resolve38(root, path);
|
|
31432
31644
|
const child = relative26(root, target);
|
|
31433
|
-
if (child === "" || child.startsWith("..") ||
|
|
31645
|
+
if (child === "" || child.startsWith("..") || isAbsolute19(child)) throw new Error("\u62D2\u7EDD\u8BBF\u95EE\u9879\u76EE\u4E4B\u5916\u7684\u8DEF\u5F84");
|
|
31434
31646
|
return target;
|
|
31435
31647
|
}
|
|
31436
31648
|
function safeGitTarget(value) {
|
|
@@ -31442,13 +31654,13 @@ function safeGitTarget(value) {
|
|
|
31442
31654
|
// src/desktop/worktree-manager.ts
|
|
31443
31655
|
import { randomUUID as randomUUID28 } from "crypto";
|
|
31444
31656
|
import { mkdir as mkdir28, readFile as readFile37, readdir as readdir13, rm as rm17, stat as stat16, writeFile as writeFile18 } from "fs/promises";
|
|
31445
|
-
import { basename as basename10, isAbsolute as
|
|
31657
|
+
import { basename as basename10, isAbsolute as isAbsolute20, join as join46, relative as relative27, resolve as resolve39, sep as sep27 } from "path";
|
|
31446
31658
|
var DesktopWorktreeManager = class {
|
|
31447
31659
|
#repository;
|
|
31448
31660
|
#storage;
|
|
31449
31661
|
constructor(options) {
|
|
31450
|
-
this.#repository =
|
|
31451
|
-
this.#storage =
|
|
31662
|
+
this.#repository = resolve39(options.repository);
|
|
31663
|
+
this.#storage = resolve39(options.storage);
|
|
31452
31664
|
}
|
|
31453
31665
|
async create(label) {
|
|
31454
31666
|
if (!await isGitRepository(this.#repository)) throw new Error("Task worktrees require a Git repository");
|
|
@@ -31529,21 +31741,21 @@ var DesktopWorktreeManager = class {
|
|
|
31529
31741
|
return this.#safeStoragePath(`${id}.json`);
|
|
31530
31742
|
}
|
|
31531
31743
|
#safeStoragePath(name) {
|
|
31532
|
-
const target =
|
|
31744
|
+
const target = resolve39(this.#storage, name);
|
|
31533
31745
|
const delta = relative27(this.#storage, target);
|
|
31534
|
-
if (delta === "" || delta === ".." || delta.startsWith(`..${sep27}`) ||
|
|
31746
|
+
if (delta === "" || delta === ".." || delta.startsWith(`..${sep27}`) || isAbsolute20(delta)) throw new Error("Worktree path escapes storage");
|
|
31535
31747
|
return target;
|
|
31536
31748
|
}
|
|
31537
31749
|
};
|
|
31538
31750
|
function validateStored(value, repository, storage) {
|
|
31539
31751
|
if (typeof value !== "object" || value === null) throw new Error("Invalid worktree metadata");
|
|
31540
31752
|
const item = value;
|
|
31541
|
-
if (item.version !== 1 || item.mode !== "worktree" || typeof item.id !== "string" || typeof item.path !== "string" || typeof item.branch !== "string" || typeof item.createdAt !== "string" ||
|
|
31753
|
+
if (item.version !== 1 || item.mode !== "worktree" || typeof item.id !== "string" || typeof item.path !== "string" || typeof item.branch !== "string" || typeof item.createdAt !== "string" || resolve39(item.repository ?? "") !== repository) {
|
|
31542
31754
|
throw new Error("Invalid worktree metadata");
|
|
31543
31755
|
}
|
|
31544
|
-
const path =
|
|
31756
|
+
const path = resolve39(item.path);
|
|
31545
31757
|
const delta = relative27(storage, path);
|
|
31546
|
-
if (delta === "" || delta === ".." || delta.startsWith(`..${sep27}`) ||
|
|
31758
|
+
if (delta === "" || delta === ".." || delta.startsWith(`..${sep27}`) || isAbsolute20(delta) || basename10(path) !== item.id) {
|
|
31547
31759
|
throw new Error("Worktree metadata escapes storage");
|
|
31548
31760
|
}
|
|
31549
31761
|
return { version: 1, id: item.id, mode: "worktree", repository, path, branch: item.branch, createdAt: item.createdAt };
|
|
@@ -31552,15 +31764,15 @@ function validateStored(value, repository, storage) {
|
|
|
31552
31764
|
// src/desktop/workbench-service.ts
|
|
31553
31765
|
import { readFile as readFile38, readdir as readdir14, stat as stat17 } from "fs/promises";
|
|
31554
31766
|
import { homedir as homedir5 } from "os";
|
|
31555
|
-
import { join as join47, resolve as
|
|
31767
|
+
import { join as join47, resolve as resolve40 } from "path";
|
|
31556
31768
|
var MAX_TEXT = 128e3;
|
|
31557
31769
|
var MAX_ITEMS = 200;
|
|
31558
31770
|
var DesktopWorkbenchService = class {
|
|
31559
31771
|
#workspace;
|
|
31560
31772
|
#home;
|
|
31561
31773
|
constructor(workspace, home = homedir5()) {
|
|
31562
|
-
this.#workspace =
|
|
31563
|
-
this.#home =
|
|
31774
|
+
this.#workspace = resolve40(workspace);
|
|
31775
|
+
this.#home = resolve40(home);
|
|
31564
31776
|
}
|
|
31565
31777
|
async inspect(sessionId) {
|
|
31566
31778
|
const [goals, instructions, permissionFiles, audit, context] = await Promise.all([
|
|
@@ -31611,7 +31823,7 @@ var DesktopWorkbenchService = class {
|
|
|
31611
31823
|
}
|
|
31612
31824
|
async #permissionFiles() {
|
|
31613
31825
|
const candidates = [
|
|
31614
|
-
...process.env.FLAVOR_MANAGED_PERMISSIONS ? [{ tier: "managed", path:
|
|
31826
|
+
...process.env.FLAVOR_MANAGED_PERMISSIONS ? [{ tier: "managed", path: resolve40(process.env.FLAVOR_MANAGED_PERMISSIONS), content: "" }] : [],
|
|
31615
31827
|
{ tier: "user", path: join47(this.#home, ".flavor-code", "permissions.json"), content: "" },
|
|
31616
31828
|
{ tier: "project", path: join47(this.#workspace, ".flavor", "permissions.json"), content: "" },
|
|
31617
31829
|
{ tier: "local", path: join47(this.#workspace, ".flavor", "permissions.local.json"), content: "" }
|
|
@@ -31741,7 +31953,7 @@ function sessionMetaKey(workspace, sessionId) {
|
|
|
31741
31953
|
return `${workspace}\0${sessionId}`;
|
|
31742
31954
|
}
|
|
31743
31955
|
function worktreeManager(workspace) {
|
|
31744
|
-
const key = createHash28("sha256").update(
|
|
31956
|
+
const key = createHash28("sha256").update(resolve41(workspace)).digest("hex").slice(0, 20);
|
|
31745
31957
|
return new DesktopWorktreeManager({ repository: workspace, storage: join48(app.getPath("userData"), "task-worktrees", key) });
|
|
31746
31958
|
}
|
|
31747
31959
|
function activeWorkingDirectory() {
|
|
@@ -31947,6 +32159,9 @@ async function savePersistedProjects() {
|
|
|
31947
32159
|
await writeFile19(statePath(), `${JSON.stringify({ workspace: activeWorkspace, projects: projectOrder, ...workbench })}
|
|
31948
32160
|
`, { encoding: "utf8", mode: 384 });
|
|
31949
32161
|
}
|
|
32162
|
+
function savePersistedProjectsInBackground() {
|
|
32163
|
+
void savePersistedProjects().catch((error) => logStartup("persist-projects-failed", String(error)));
|
|
32164
|
+
}
|
|
31950
32165
|
function validRecord(value) {
|
|
31951
32166
|
return typeof value === "object" && value !== null && !Array.isArray(value) ? value : {};
|
|
31952
32167
|
}
|
|
@@ -31954,7 +32169,7 @@ async function assertDirectory(path) {
|
|
|
31954
32169
|
if (!(await stat18(path)).isDirectory()) throw new Error("The selected project is not a directory");
|
|
31955
32170
|
}
|
|
31956
32171
|
async function openWorkspace(path) {
|
|
31957
|
-
const workspace =
|
|
32172
|
+
const workspace = resolve41(path);
|
|
31958
32173
|
await assertDirectory(workspace);
|
|
31959
32174
|
let project = managedProjects.get(workspace);
|
|
31960
32175
|
if (project === void 0) {
|
|
@@ -31965,10 +32180,10 @@ async function openWorkspace(path) {
|
|
|
31965
32180
|
activeWorkspace = workspace;
|
|
31966
32181
|
controller = nextController;
|
|
31967
32182
|
project.snapshot = await nextController.openWorkspace(workspace);
|
|
31968
|
-
for (const saved of workbench.taskCheckouts.filter((item) =>
|
|
32183
|
+
for (const saved of workbench.taskCheckouts.filter((item) => resolve41(item.workspace) === workspace)) {
|
|
31969
32184
|
try {
|
|
31970
32185
|
await assertDirectory(saved.workingDirectory);
|
|
31971
|
-
const task = { environment: "worktree", workingDirectory:
|
|
32186
|
+
const task = { environment: "worktree", workingDirectory: resolve41(saved.workingDirectory), worktreeId: saved.worktreeId };
|
|
31972
32187
|
task.controller = createDesktopController(workspace, task);
|
|
31973
32188
|
task.snapshot = await task.controller.openWorkspace(task.workingDirectory);
|
|
31974
32189
|
task.sessionId = saved.sessionId;
|
|
@@ -32002,7 +32217,7 @@ function trackTaskTransition(workspace, task, previous, next) {
|
|
|
32002
32217
|
const needsAttention = next.approval !== void 0 || (next.questions?.length ?? 0) > 0;
|
|
32003
32218
|
const hadAttention = previous?.approval !== void 0 || (previous?.questions?.length ?? 0) > 0;
|
|
32004
32219
|
if (needsAttention && !hadAttention) addActivity(workspace, sessionId, "attention", "\u4EFB\u52A1\u7B49\u5F85\u4F60\u7684\u786E\u8BA4");
|
|
32005
|
-
|
|
32220
|
+
savePersistedProjectsInBackground();
|
|
32006
32221
|
}
|
|
32007
32222
|
function addActivity(workspace, sessionId, kind, title, detail) {
|
|
32008
32223
|
const item = { id: `${Date.now()}-${Math.random().toString(36).slice(2, 8)}`, workspace, ...sessionId === void 0 ? {} : { sessionId }, kind, title, ...detail === void 0 ? {} : { detail }, createdAt: (/* @__PURE__ */ new Date()).toISOString(), unread: true };
|
|
@@ -32014,7 +32229,7 @@ function addActivity(workspace, sessionId, kind, title, detail) {
|
|
|
32014
32229
|
});
|
|
32015
32230
|
notification.show();
|
|
32016
32231
|
}
|
|
32017
|
-
|
|
32232
|
+
savePersistedProjectsInBackground();
|
|
32018
32233
|
}
|
|
32019
32234
|
async function startManagedSession(resumeSession, environment = "local") {
|
|
32020
32235
|
if (activeWorkspace === void 0) throw new Error("Open a project before starting a session");
|
|
@@ -32098,7 +32313,7 @@ function installIpcHandlers() {
|
|
|
32098
32313
|
} catch {
|
|
32099
32314
|
}
|
|
32100
32315
|
}
|
|
32101
|
-
if (persisted.workspace !== void 0 && managedProjects.has(
|
|
32316
|
+
if (persisted.workspace !== void 0 && managedProjects.has(resolve41(persisted.workspace))) {
|
|
32102
32317
|
try {
|
|
32103
32318
|
return await openWorkspace(persisted.workspace);
|
|
32104
32319
|
} catch {
|
|
@@ -32159,7 +32374,7 @@ function installIpcHandlers() {
|
|
|
32159
32374
|
});
|
|
32160
32375
|
ipcMain.handle(DESKTOP_CHANNELS.updateProject, async (_event, value) => {
|
|
32161
32376
|
const input = UpdateProjectInputSchema.parse(value);
|
|
32162
|
-
const workspace =
|
|
32377
|
+
const workspace = resolve41(input.workspace);
|
|
32163
32378
|
if (!managedProjects.has(workspace)) throw new Error("\u9879\u76EE\u672A\u6253\u5F00");
|
|
32164
32379
|
workbench.projectMeta[workspace] = { ...workbench.projectMeta[workspace], ...input.label === void 0 ? {} : { label: input.label || void 0 }, ...input.pinned === void 0 ? {} : { pinned: input.pinned } };
|
|
32165
32380
|
await savePersistedProjects();
|
|
@@ -32167,7 +32382,7 @@ function installIpcHandlers() {
|
|
|
32167
32382
|
});
|
|
32168
32383
|
ipcMain.handle(DESKTOP_CHANNELS.closeProject, async (_event, value) => {
|
|
32169
32384
|
const input = CloseProjectInputSchema.parse(value);
|
|
32170
|
-
const workspace =
|
|
32385
|
+
const workspace = resolve41(input.workspace);
|
|
32171
32386
|
const project = managedProjects.get(workspace);
|
|
32172
32387
|
if (project === void 0) return decorateSnapshot(controller.snapshot());
|
|
32173
32388
|
if ([...project.tasks].some((task) => task.snapshot.activeSession?.busy) && !input.force) throw new Error("\u9879\u76EE\u4E2D\u4ECD\u6709\u4EFB\u52A1\u8FD0\u884C\uFF1B\u786E\u8BA4\u540E\u518D\u5173\u95ED\u9879\u76EE");
|
|
@@ -32183,12 +32398,12 @@ function installIpcHandlers() {
|
|
|
32183
32398
|
return decorateSnapshot(controller.snapshot());
|
|
32184
32399
|
});
|
|
32185
32400
|
ipcMain.handle(DESKTOP_CHANNELS.revealProject, async (_event, value) => {
|
|
32186
|
-
const workspace =
|
|
32401
|
+
const workspace = resolve41(ProjectPathInputSchema.parse(value).workspace);
|
|
32187
32402
|
await assertDirectory(workspace);
|
|
32188
32403
|
await shell.openPath(workspace);
|
|
32189
32404
|
});
|
|
32190
32405
|
ipcMain.handle(DESKTOP_CHANNELS.copyProjectPath, async (_event, value) => {
|
|
32191
|
-
clipboard.writeText(
|
|
32406
|
+
clipboard.writeText(resolve41(ProjectPathInputSchema.parse(value).workspace));
|
|
32192
32407
|
});
|
|
32193
32408
|
ipcMain.handle(DESKTOP_CHANNELS.dismissRecovery, async (_event, value) => {
|
|
32194
32409
|
const { sessionId } = DeleteSessionInputSchema.parse(value);
|