flavor-code 1.3.19 → 1.3.21
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +434 -434
- package/README.zh-CN.md +431 -431
- package/dist/{app-EVMUBQRE.js → app-Q4M7GLFX.js} +69 -65
- package/dist/{chunk-HJOCNKPN.js → chunk-RPFWB45Y.js} +2 -1
- package/dist/{chunk-3HWWQV2A.js → chunk-SUSLIHXO.js} +4 -0
- package/dist/{chunk-6LDYDQ3P.js → chunk-VEUSROUQ.js} +1 -1
- package/dist/{chunk-PF457XCH.js → chunk-Z62P4LL7.js} +533 -259
- package/dist/{claude-ink-3K3NG7BF.js → claude-ink-6CEG2XZZ.js} +1 -1
- package/dist/cli-main.js +26 -81
- 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
|
@@ -18,7 +18,7 @@ import {
|
|
|
18
18
|
redactConfig,
|
|
19
19
|
setProjectMcpServerDisabled,
|
|
20
20
|
updateProtectedFile
|
|
21
|
-
} from "./chunk-
|
|
21
|
+
} from "./chunk-RPFWB45Y.js";
|
|
22
22
|
import {
|
|
23
23
|
AuditLogger,
|
|
24
24
|
appendUsageLog,
|
|
@@ -72,12 +72,12 @@ function normalizeProviderError(error) {
|
|
|
72
72
|
|
|
73
73
|
// src/utils/async.ts
|
|
74
74
|
function withTimeout(promise, ms) {
|
|
75
|
-
return new Promise((
|
|
75
|
+
return new Promise((resolve30, reject) => {
|
|
76
76
|
let settled = false;
|
|
77
77
|
const timer = setTimeout(() => {
|
|
78
78
|
if (settled) return;
|
|
79
79
|
settled = true;
|
|
80
|
-
|
|
80
|
+
resolve30({ timedOut: true, value: void 0 });
|
|
81
81
|
}, ms);
|
|
82
82
|
timer.unref?.();
|
|
83
83
|
promise.then(
|
|
@@ -85,7 +85,7 @@ function withTimeout(promise, ms) {
|
|
|
85
85
|
if (!settled) {
|
|
86
86
|
settled = true;
|
|
87
87
|
clearTimeout(timer);
|
|
88
|
-
|
|
88
|
+
resolve30({ timedOut: false, value });
|
|
89
89
|
}
|
|
90
90
|
},
|
|
91
91
|
(error) => {
|
|
@@ -100,13 +100,13 @@ function withTimeout(promise, ms) {
|
|
|
100
100
|
}
|
|
101
101
|
function awaitWithSignal(promise, signal) {
|
|
102
102
|
signal.throwIfAborted();
|
|
103
|
-
return new Promise((
|
|
103
|
+
return new Promise((resolve30, reject) => {
|
|
104
104
|
const onAbort = () => reject(signal.reason);
|
|
105
105
|
signal.addEventListener("abort", onAbort, { once: true });
|
|
106
106
|
promise.then(
|
|
107
107
|
(value) => {
|
|
108
108
|
signal.removeEventListener("abort", onAbort);
|
|
109
|
-
|
|
109
|
+
resolve30(value);
|
|
110
110
|
},
|
|
111
111
|
(error) => {
|
|
112
112
|
signal.removeEventListener("abort", onAbort);
|
|
@@ -2733,7 +2733,7 @@ var SearchSpawnError = class extends Error {
|
|
|
2733
2733
|
}
|
|
2734
2734
|
fallbackSafe;
|
|
2735
2735
|
};
|
|
2736
|
-
function delimitedParser(
|
|
2736
|
+
function delimitedParser(delimiter2, onRecord) {
|
|
2737
2737
|
let pending = Buffer.alloc(0);
|
|
2738
2738
|
let stopped = false;
|
|
2739
2739
|
return {
|
|
@@ -2742,7 +2742,7 @@ function delimitedParser(delimiter, onRecord) {
|
|
|
2742
2742
|
pending = pending.length === 0 ? chunk : Buffer.concat([pending, chunk]);
|
|
2743
2743
|
let offset = 0;
|
|
2744
2744
|
while (true) {
|
|
2745
|
-
const end = pending.indexOf(
|
|
2745
|
+
const end = pending.indexOf(delimiter2, offset);
|
|
2746
2746
|
if (end < 0) break;
|
|
2747
2747
|
if (!onRecord(pending.subarray(offset, end))) {
|
|
2748
2748
|
stopped = true;
|
|
@@ -4194,9 +4194,9 @@ Address this now (for example run the required verification or fix), then finish
|
|
|
4194
4194
|
}
|
|
4195
4195
|
#waitForPlanningIdle(key) {
|
|
4196
4196
|
if (!this.#activePlanning.has(key)) return Promise.resolve();
|
|
4197
|
-
return new Promise((
|
|
4197
|
+
return new Promise((resolve30) => {
|
|
4198
4198
|
const check = () => {
|
|
4199
|
-
if (!this.#activePlanning.has(key))
|
|
4199
|
+
if (!this.#activePlanning.has(key)) resolve30();
|
|
4200
4200
|
else this.#planningIdleWaiters.add(check);
|
|
4201
4201
|
};
|
|
4202
4202
|
this.#planningIdleWaiters.add(check);
|
|
@@ -4303,6 +4303,101 @@ function requiredPals(service) {
|
|
|
4303
4303
|
return service;
|
|
4304
4304
|
}
|
|
4305
4305
|
|
|
4306
|
+
// src/utils/crash-guard.ts
|
|
4307
|
+
import { mkdirSync as mkdirSync2, writeFileSync } from "fs";
|
|
4308
|
+
import { join as join5 } from "path";
|
|
4309
|
+
var installed = false;
|
|
4310
|
+
var crashing = false;
|
|
4311
|
+
var crashCleanups = /* @__PURE__ */ new Set();
|
|
4312
|
+
function registerCrashCleanup(cleanup) {
|
|
4313
|
+
crashCleanups.add(cleanup);
|
|
4314
|
+
return () => {
|
|
4315
|
+
crashCleanups.delete(cleanup);
|
|
4316
|
+
};
|
|
4317
|
+
}
|
|
4318
|
+
async function runCrashCleanups(timeoutMs) {
|
|
4319
|
+
if (!Number.isFinite(timeoutMs) || timeoutMs < 0) throw new Error("Crash cleanup timeout must be non-negative");
|
|
4320
|
+
const cleanup = Promise.allSettled([...crashCleanups].map(async (callback) => callback()));
|
|
4321
|
+
if (timeoutMs === 0) return;
|
|
4322
|
+
let timer;
|
|
4323
|
+
try {
|
|
4324
|
+
await Promise.race([
|
|
4325
|
+
cleanup,
|
|
4326
|
+
new Promise((resolvePromise) => {
|
|
4327
|
+
timer = setTimeout(resolvePromise, timeoutMs);
|
|
4328
|
+
})
|
|
4329
|
+
]);
|
|
4330
|
+
} finally {
|
|
4331
|
+
if (timer !== void 0) clearTimeout(timer);
|
|
4332
|
+
}
|
|
4333
|
+
}
|
|
4334
|
+
function installCrashGuard(options = {}) {
|
|
4335
|
+
if (installed) return;
|
|
4336
|
+
installed = true;
|
|
4337
|
+
const crashLogDir = () => {
|
|
4338
|
+
try {
|
|
4339
|
+
return join5((options.workspace ?? process.cwd)(), ".flavor");
|
|
4340
|
+
} catch {
|
|
4341
|
+
return process.cwd();
|
|
4342
|
+
}
|
|
4343
|
+
};
|
|
4344
|
+
const writeCrashLog = (kind, detail) => {
|
|
4345
|
+
try {
|
|
4346
|
+
const dir = crashLogDir();
|
|
4347
|
+
mkdirSync2(dir, { recursive: true });
|
|
4348
|
+
const path = join5(dir, `crash-${(/* @__PURE__ */ new Date()).toISOString().replace(/[^0-9]/g, "")}-${process.pid}.log`);
|
|
4349
|
+
writeFileSync(path, [
|
|
4350
|
+
`kind: ${kind}`,
|
|
4351
|
+
`version: ${process.version}`,
|
|
4352
|
+
`platform: ${process.platform}`,
|
|
4353
|
+
`time: ${(/* @__PURE__ */ new Date()).toISOString()}`,
|
|
4354
|
+
"",
|
|
4355
|
+
detail
|
|
4356
|
+
].join("\n"), { encoding: "utf8", mode: 384 });
|
|
4357
|
+
return path;
|
|
4358
|
+
} catch {
|
|
4359
|
+
return void 0;
|
|
4360
|
+
}
|
|
4361
|
+
};
|
|
4362
|
+
const restoreTerminal = () => {
|
|
4363
|
+
try {
|
|
4364
|
+
if (process.stdout.isTTY === true) process.stdout.write("\x1B[?25h\x1B[?1049l");
|
|
4365
|
+
} catch {
|
|
4366
|
+
}
|
|
4367
|
+
};
|
|
4368
|
+
const describe = (reason) => {
|
|
4369
|
+
if (reason instanceof Error) return `${reason.name}: ${reason.message}
|
|
4370
|
+
${reason.stack ?? ""}`;
|
|
4371
|
+
return String(reason);
|
|
4372
|
+
};
|
|
4373
|
+
const crash = (kind, reason) => {
|
|
4374
|
+
if (crashing) {
|
|
4375
|
+
process.exit(1);
|
|
4376
|
+
return;
|
|
4377
|
+
}
|
|
4378
|
+
crashing = true;
|
|
4379
|
+
const detail = describe(reason);
|
|
4380
|
+
const logPath = writeCrashLog(kind, detail);
|
|
4381
|
+
restoreTerminal();
|
|
4382
|
+
try {
|
|
4383
|
+
const safe = redactErrorText(detail.split("\n").slice(0, 3).join("\n"));
|
|
4384
|
+
process.stderr.write(`flavor crashed (${kind}): ${safe}
|
|
4385
|
+
`);
|
|
4386
|
+
if (logPath !== void 0) process.stderr.write(`crash log: ${logPath}
|
|
4387
|
+
`);
|
|
4388
|
+
} catch {
|
|
4389
|
+
}
|
|
4390
|
+
const cleanupTimeoutMs = options.cleanupTimeoutMs ?? 1e3;
|
|
4391
|
+
const watchdog = setTimeout(() => process.exit(1), cleanupTimeoutMs + 50);
|
|
4392
|
+
void runCrashCleanups(cleanupTimeoutMs).finally(() => {
|
|
4393
|
+
clearTimeout(watchdog);
|
|
4394
|
+
process.exit(1);
|
|
4395
|
+
});
|
|
4396
|
+
};
|
|
4397
|
+
process.on("unhandledRejection", (reason) => crash("unhandledRejection", reason));
|
|
4398
|
+
process.on("uncaughtException", (error) => crash("uncaughtException", error));
|
|
4399
|
+
}
|
|
4400
|
+
|
|
4306
4401
|
// src/execution/docker.ts
|
|
4307
4402
|
import { spawn as spawn2 } from "child_process";
|
|
4308
4403
|
import { relative as relative5, resolve as resolve5, sep as sep5, win32 } from "path";
|
|
@@ -4401,7 +4496,7 @@ function runInvocation(invocation, signal, timeoutMs) {
|
|
|
4401
4496
|
import { createHash as createHash3 } from "crypto";
|
|
4402
4497
|
import { Socket as Socket2 } from "net";
|
|
4403
4498
|
import { tmpdir } from "os";
|
|
4404
|
-
import { join as
|
|
4499
|
+
import { join as join7 } from "path";
|
|
4405
4500
|
import { spawn as spawn3 } from "child_process";
|
|
4406
4501
|
|
|
4407
4502
|
// src/pals/broker.ts
|
|
@@ -4414,16 +4509,16 @@ import { dirname as dirname8 } from "path";
|
|
|
4414
4509
|
import { randomBytes, randomUUID as randomUUID3 } from "crypto";
|
|
4415
4510
|
import { chmod, link, mkdir as mkdir4, open as open3, readFile as readFile4, unlink } from "fs/promises";
|
|
4416
4511
|
import { homedir } from "os";
|
|
4417
|
-
import { join as
|
|
4512
|
+
import { join as join6 } from "path";
|
|
4418
4513
|
var PAL_AUTH_TOKEN_BYTES = 32;
|
|
4419
4514
|
var TOKEN_PATTERN = /^[a-f0-9]{64}$/;
|
|
4420
4515
|
async function loadOrCreatePalAuthToken(options = {}) {
|
|
4421
|
-
const directory =
|
|
4422
|
-
const tokenPath =
|
|
4516
|
+
const directory = join6(options.home ?? homedir(), ".flavor-code", "pals");
|
|
4517
|
+
const tokenPath = join6(directory, "auth-token");
|
|
4423
4518
|
await mkdir4(directory, { recursive: true, mode: 448 });
|
|
4424
4519
|
await chmod(directory, 448);
|
|
4425
4520
|
const token = (options.random ?? randomBytes)(PAL_AUTH_TOKEN_BYTES).toString("hex");
|
|
4426
|
-
const temporaryPath =
|
|
4521
|
+
const temporaryPath = join6(directory, `.auth-token-${process.pid}-${randomUUID3()}.tmp`);
|
|
4427
4522
|
let handle;
|
|
4428
4523
|
try {
|
|
4429
4524
|
handle = await open3(temporaryPath, "wx", 384);
|
|
@@ -4544,12 +4639,12 @@ var BrokerStateError = class extends Error {
|
|
|
4544
4639
|
};
|
|
4545
4640
|
var WINDOWS_PIPE_PREFIX = "\\\\.\\pipe\\";
|
|
4546
4641
|
function canConnect(address) {
|
|
4547
|
-
return new Promise((
|
|
4642
|
+
return new Promise((resolve30) => {
|
|
4548
4643
|
const socket = new Socket();
|
|
4549
4644
|
const finish = (connected) => {
|
|
4550
4645
|
socket.removeAllListeners();
|
|
4551
4646
|
socket.destroy();
|
|
4552
|
-
|
|
4647
|
+
resolve30(connected);
|
|
4553
4648
|
};
|
|
4554
4649
|
socket.once("connect", () => finish(true));
|
|
4555
4650
|
socket.once("error", () => finish(false));
|
|
@@ -4598,7 +4693,7 @@ var PalBrokerServer = class {
|
|
|
4598
4693
|
this.authToken ??= await loadOrCreatePalAuthToken(this.authHome === void 0 ? {} : { home: this.authHome });
|
|
4599
4694
|
try {
|
|
4600
4695
|
await this.prepareAddress();
|
|
4601
|
-
await new Promise((
|
|
4696
|
+
await new Promise((resolve30, reject) => {
|
|
4602
4697
|
const onError = (error) => {
|
|
4603
4698
|
this.server.off("listening", onListening);
|
|
4604
4699
|
reject(error);
|
|
@@ -4607,7 +4702,7 @@ var PalBrokerServer = class {
|
|
|
4607
4702
|
this.server.off("error", onError);
|
|
4608
4703
|
this.started = true;
|
|
4609
4704
|
this.ownsAddress = true;
|
|
4610
|
-
|
|
4705
|
+
resolve30();
|
|
4611
4706
|
};
|
|
4612
4707
|
this.server.once("error", onError);
|
|
4613
4708
|
this.server.once("listening", onListening);
|
|
@@ -4635,7 +4730,7 @@ var PalBrokerServer = class {
|
|
|
4635
4730
|
const ownsAddress = this.ownsAddress;
|
|
4636
4731
|
for (const connection of this.connections) connection.socket.destroy();
|
|
4637
4732
|
if (this.started) {
|
|
4638
|
-
await new Promise((
|
|
4733
|
+
await new Promise((resolve30) => this.server.close(() => resolve30()));
|
|
4639
4734
|
this.started = false;
|
|
4640
4735
|
}
|
|
4641
4736
|
this.ownsAddress = false;
|
|
@@ -5304,15 +5399,15 @@ var PalBrokerState = class {
|
|
|
5304
5399
|
// src/pals/broker-cli.ts
|
|
5305
5400
|
var startupAttempts = /* @__PURE__ */ new Map();
|
|
5306
5401
|
function delay(milliseconds) {
|
|
5307
|
-
return new Promise((
|
|
5402
|
+
return new Promise((resolve30) => setTimeout(resolve30, milliseconds));
|
|
5308
5403
|
}
|
|
5309
5404
|
function canConnect2(address) {
|
|
5310
|
-
return new Promise((
|
|
5405
|
+
return new Promise((resolve30) => {
|
|
5311
5406
|
const socket = new Socket2();
|
|
5312
5407
|
const finish = (connected) => {
|
|
5313
5408
|
socket.removeAllListeners();
|
|
5314
5409
|
socket.destroy();
|
|
5315
|
-
|
|
5410
|
+
resolve30(connected);
|
|
5316
5411
|
};
|
|
5317
5412
|
socket.once("connect", () => finish(true));
|
|
5318
5413
|
socket.once("error", () => finish(false));
|
|
@@ -5321,7 +5416,7 @@ function canConnect2(address) {
|
|
|
5321
5416
|
}
|
|
5322
5417
|
function startupLockPath(address) {
|
|
5323
5418
|
const digest = createHash3("sha256").update(address, "utf8").digest("hex").slice(0, 24);
|
|
5324
|
-
return
|
|
5419
|
+
return join7(tmpdir(), `flavor-code-pals-${digest}.startup.lock`);
|
|
5325
5420
|
}
|
|
5326
5421
|
async function spawnProductionBroker(address) {
|
|
5327
5422
|
const entry2 = process.argv[1];
|
|
@@ -5355,8 +5450,8 @@ async function runPalBroker(options) {
|
|
|
5355
5450
|
const sweepIntervalMs = options.sweepIntervalMs ?? 5e3;
|
|
5356
5451
|
const idleShutdownMs = options.idleShutdownMs ?? 3e4;
|
|
5357
5452
|
let resolveClosed;
|
|
5358
|
-
const closed = new Promise((
|
|
5359
|
-
resolveClosed =
|
|
5453
|
+
const closed = new Promise((resolve30) => {
|
|
5454
|
+
resolveClosed = resolve30;
|
|
5360
5455
|
});
|
|
5361
5456
|
let closing;
|
|
5362
5457
|
const timer = setInterval(() => {
|
|
@@ -5397,10 +5492,10 @@ async function ensureOnce(options) {
|
|
|
5397
5492
|
|
|
5398
5493
|
// src/production.ts
|
|
5399
5494
|
import { readFile as readFile22 } from "fs/promises";
|
|
5400
|
-
import { mkdirSync as
|
|
5495
|
+
import { mkdirSync as mkdirSync4, readFileSync as readFileSync3, writeFileSync as writeFileSync2 } from "fs";
|
|
5401
5496
|
import { createHash as createHash18, randomUUID as randomUUID20 } from "crypto";
|
|
5402
5497
|
import { homedir as homedir2, release as osRelease, tmpdir as tmpdir4, version as osVersion } from "os";
|
|
5403
|
-
import { basename as basename6, dirname as dirname19, join as
|
|
5498
|
+
import { basename as basename6, dirname as dirname19, join as join27, relative as relative20, resolve as resolve29, sep as sep20 } from "path";
|
|
5404
5499
|
|
|
5405
5500
|
// src/agent/subagents.ts
|
|
5406
5501
|
import { z as z5 } from "zod";
|
|
@@ -7311,10 +7406,10 @@ function sanitize(message2, secrets) {
|
|
|
7311
7406
|
}
|
|
7312
7407
|
function waitForRetry(delayMs, signal) {
|
|
7313
7408
|
if (signal?.aborted) return Promise.reject(signal.reason);
|
|
7314
|
-
return new Promise((
|
|
7409
|
+
return new Promise((resolve30, reject) => {
|
|
7315
7410
|
const timer = setTimeout(() => {
|
|
7316
7411
|
signal?.removeEventListener("abort", onAbort);
|
|
7317
|
-
|
|
7412
|
+
resolve30();
|
|
7318
7413
|
}, delayMs);
|
|
7319
7414
|
const onAbort = () => {
|
|
7320
7415
|
clearTimeout(timer);
|
|
@@ -7952,10 +8047,10 @@ function serializeToolInput(input) {
|
|
|
7952
8047
|
}
|
|
7953
8048
|
function waitForRetry2(delayMs, signal) {
|
|
7954
8049
|
if (signal?.aborted) return Promise.reject(signal.reason);
|
|
7955
|
-
return new Promise((
|
|
8050
|
+
return new Promise((resolve30, reject) => {
|
|
7956
8051
|
const timer = setTimeout(() => {
|
|
7957
8052
|
signal?.removeEventListener("abort", onAbort);
|
|
7958
|
-
|
|
8053
|
+
resolve30();
|
|
7959
8054
|
}, delayMs);
|
|
7960
8055
|
const onAbort = () => {
|
|
7961
8056
|
clearTimeout(timer);
|
|
@@ -8033,7 +8128,7 @@ function redactClassifierText(value) {
|
|
|
8033
8128
|
// src/tools/runtime.ts
|
|
8034
8129
|
import { randomUUID as randomUUID7 } from "crypto";
|
|
8035
8130
|
import { mkdir as mkdir7, writeFile as writeFile4 } from "fs/promises";
|
|
8036
|
-
import { basename as basename2, join as
|
|
8131
|
+
import { basename as basename2, join as join8, resolve as resolve7 } from "path";
|
|
8037
8132
|
import { z as z9 } from "zod";
|
|
8038
8133
|
|
|
8039
8134
|
// src/tools/types.ts
|
|
@@ -8340,10 +8435,10 @@ var ToolRuntime = class {
|
|
|
8340
8435
|
const reason = perToolExceeded && turnExceeded ? "per_tool_and_turn_limit" : perToolExceeded ? "per_tool_limit" : "turn_limit";
|
|
8341
8436
|
const preview = headTailPreview(serialized, previewChars);
|
|
8342
8437
|
this.#turnOutputChars += preview.length;
|
|
8343
|
-
const directory =
|
|
8438
|
+
const directory = join8(this.#workspace, ".flavor", "tool-results");
|
|
8344
8439
|
await mkdir7(directory, { recursive: true });
|
|
8345
8440
|
const filename2 = `${(/* @__PURE__ */ new Date()).toISOString().replaceAll(":", "-")}-${randomUUID7()}-${safeFilename(tool)}.txt`;
|
|
8346
|
-
const savedTo =
|
|
8441
|
+
const savedTo = join8(directory, filename2);
|
|
8347
8442
|
await writeFile4(savedTo, serialized, "utf8");
|
|
8348
8443
|
return {
|
|
8349
8444
|
truncated: true,
|
|
@@ -8581,8 +8676,8 @@ function toModelTool(tool) {
|
|
|
8581
8676
|
|
|
8582
8677
|
// src/harness/journal.ts
|
|
8583
8678
|
import { createHash as createHash6, randomUUID as randomUUID8 } from "crypto";
|
|
8584
|
-
import { closeSync, constants, existsSync as existsSync2, fsyncSync, mkdirSync as
|
|
8585
|
-
import { join as
|
|
8679
|
+
import { closeSync, constants, existsSync as existsSync2, fsyncSync, mkdirSync as mkdirSync3, openSync, readFileSync as readFileSync2, realpathSync as realpathSync2, renameSync, statSync, truncateSync, unlinkSync, writeSync } from "fs";
|
|
8680
|
+
import { join as join9, relative as relative7, resolve as resolve8, sep as sep7 } from "path";
|
|
8586
8681
|
import { z as z10 } from "zod";
|
|
8587
8682
|
|
|
8588
8683
|
// src/harness/invariants.ts
|
|
@@ -8672,14 +8767,14 @@ var HarnessJournal = class {
|
|
|
8672
8767
|
#records = [];
|
|
8673
8768
|
constructor(options) {
|
|
8674
8769
|
const workspace = resolve8(options.workspace);
|
|
8675
|
-
const directory =
|
|
8770
|
+
const directory = join9(workspace, ".flavor", "sessions");
|
|
8676
8771
|
assertSessionId(options.sessionId);
|
|
8677
|
-
|
|
8772
|
+
mkdirSync3(directory, { recursive: true, mode: 448 });
|
|
8678
8773
|
assertWithin(workspace, directory);
|
|
8679
8774
|
const canonicalWorkspace = realpathSync2.native(workspace);
|
|
8680
8775
|
const canonicalDirectory = realpathSync2.native(directory);
|
|
8681
8776
|
if (!isWithin2(canonicalWorkspace, canonicalDirectory)) throw new Error("Harness journal directory escapes the workspace");
|
|
8682
|
-
this.#path =
|
|
8777
|
+
this.#path = join9(canonicalDirectory, `${options.sessionId}.events.jsonl`);
|
|
8683
8778
|
this.#now = options.now ?? (() => (/* @__PURE__ */ new Date()).toISOString());
|
|
8684
8779
|
this.#maxBytes = options.maxBytes ?? MAX_JOURNAL_BYTES;
|
|
8685
8780
|
if (!Number.isSafeInteger(this.#maxBytes) || this.#maxBytes < 1024) throw new Error("maxBytes must be an integer of at least 1024");
|
|
@@ -9055,6 +9150,64 @@ var HookDecisionSchema = z11.object({
|
|
|
9055
9150
|
additionalContext: z11.string().optional()
|
|
9056
9151
|
});
|
|
9057
9152
|
|
|
9153
|
+
// src/utils/spawn-executable.ts
|
|
9154
|
+
import { existsSync as existsSync3, statSync as statSync2 } from "fs";
|
|
9155
|
+
import { extname, isAbsolute as isAbsolute5, resolve as resolve9 } from "path";
|
|
9156
|
+
var WINDOWS_EXECUTABLE = /\.(?:com|exe)$/iu;
|
|
9157
|
+
var WINDOWS_CMD_SHIM = /node_modules[\\/].bin[\\/][^\\/]+\.cmd$/iu;
|
|
9158
|
+
var CMD_META = /([()\][%!^"`<>&|;, *?])/gu;
|
|
9159
|
+
function prepareSpawnInvocation(command, args, options = {}) {
|
|
9160
|
+
if ((options.platform ?? process.platform) !== "win32") return { command, args: [...args] };
|
|
9161
|
+
const env = options.env ?? process.env;
|
|
9162
|
+
const commandFile = resolveWindowsCommand(command, options.cwd, env);
|
|
9163
|
+
if (commandFile === void 0 || WINDOWS_EXECUTABLE.test(commandFile)) {
|
|
9164
|
+
return { command: commandFile ?? command, args: [...args] };
|
|
9165
|
+
}
|
|
9166
|
+
const doubleEscape = WINDOWS_CMD_SHIM.test(commandFile);
|
|
9167
|
+
const shellCommand = [escapeCmdCommand(commandFile), ...args.map((arg) => escapeCmdArgument(arg, doubleEscape))].join(" ");
|
|
9168
|
+
return {
|
|
9169
|
+
command: environmentValue(env, "ComSpec") ?? "cmd.exe",
|
|
9170
|
+
args: ["/d", "/s", "/c", `"${shellCommand}"`],
|
|
9171
|
+
windowsVerbatimArguments: true
|
|
9172
|
+
};
|
|
9173
|
+
}
|
|
9174
|
+
function resolveWindowsCommand(command, cwd, env) {
|
|
9175
|
+
const pathValue = environmentValue(env, "PATH") ?? "";
|
|
9176
|
+
const pathExt = (environmentValue(env, "PATHEXT") ?? ".COM;.EXE;.BAT;.CMD").split(";").filter(Boolean);
|
|
9177
|
+
const hasPath = isAbsolute5(command) || /[\\/]/u.test(command);
|
|
9178
|
+
const bases = hasPath ? [isAbsolute5(command) ? command : resolve9(cwd ?? process.cwd(), command)] : pathValue.split(";").filter(Boolean).map((entry2) => resolve9(unquote(entry2), command));
|
|
9179
|
+
for (const base of bases) {
|
|
9180
|
+
const candidates = extname(base).length > 0 ? [base] : [base, ...pathExt.map((extension) => `${base}${extension}`)];
|
|
9181
|
+
for (const candidate of candidates) {
|
|
9182
|
+
if (isRegularFile(candidate)) return resolve9(candidate);
|
|
9183
|
+
}
|
|
9184
|
+
}
|
|
9185
|
+
return void 0;
|
|
9186
|
+
}
|
|
9187
|
+
function environmentValue(env, name) {
|
|
9188
|
+
const key = Object.keys(env).find((candidate) => candidate.toLowerCase() === name.toLowerCase());
|
|
9189
|
+
return key === void 0 ? void 0 : env[key];
|
|
9190
|
+
}
|
|
9191
|
+
function unquote(value) {
|
|
9192
|
+
const trimmed = value.trim();
|
|
9193
|
+
return trimmed.startsWith('"') && trimmed.endsWith('"') ? trimmed.slice(1, -1) : trimmed;
|
|
9194
|
+
}
|
|
9195
|
+
function isRegularFile(path) {
|
|
9196
|
+
try {
|
|
9197
|
+
return existsSync3(path) && statSync2(path).isFile();
|
|
9198
|
+
} catch {
|
|
9199
|
+
return false;
|
|
9200
|
+
}
|
|
9201
|
+
}
|
|
9202
|
+
function escapeCmdCommand(value) {
|
|
9203
|
+
return value.replace(CMD_META, "^$1");
|
|
9204
|
+
}
|
|
9205
|
+
function escapeCmdArgument(value, doubleEscapeMetaCharacters) {
|
|
9206
|
+
let escaped = value.replace(/(?=(\\+?)?)\1"/gu, '$1$1\\"').replace(/(?=(\\+?)?)\1$/gu, "$1$1");
|
|
9207
|
+
escaped = `"${escaped}"`.replace(CMD_META, "^$1");
|
|
9208
|
+
return doubleEscapeMetaCharacters ? escaped.replace(CMD_META, "^$1") : escaped;
|
|
9209
|
+
}
|
|
9210
|
+
|
|
9058
9211
|
// src/hooks/bus.ts
|
|
9059
9212
|
var DEFAULT_TIMEOUT_MS = 1e4;
|
|
9060
9213
|
var DEFAULT_MAX_OUTPUT_BYTES = 1048576;
|
|
@@ -9161,13 +9314,16 @@ function decoratePayload(payload, envelope) {
|
|
|
9161
9314
|
return envelope === void 0 ? payload : { ...payload, ...envelope };
|
|
9162
9315
|
}
|
|
9163
9316
|
function runShellHandler(descriptor, event, signal) {
|
|
9164
|
-
return new Promise((
|
|
9165
|
-
const
|
|
9166
|
-
|
|
9317
|
+
return new Promise((resolve30, reject) => {
|
|
9318
|
+
const env = { ...process.env, ...descriptor.env };
|
|
9319
|
+
const invocation = prepareSpawnInvocation(descriptor.command, descriptor.args ?? [], { env });
|
|
9320
|
+
const child = spawn4(invocation.command, invocation.args, {
|
|
9321
|
+
env,
|
|
9167
9322
|
shell: false,
|
|
9168
9323
|
detached: process.platform !== "win32",
|
|
9169
9324
|
windowsHide: true,
|
|
9170
|
-
stdio: ["pipe", "pipe", "pipe"]
|
|
9325
|
+
stdio: ["pipe", "pipe", "pipe"],
|
|
9326
|
+
...invocation.windowsVerbatimArguments === void 0 ? {} : { windowsVerbatimArguments: invocation.windowsVerbatimArguments }
|
|
9171
9327
|
});
|
|
9172
9328
|
const maxOutputBytes = descriptor.maxOutputBytes ?? DEFAULT_MAX_OUTPUT_BYTES;
|
|
9173
9329
|
let stdout = "";
|
|
@@ -9204,7 +9360,7 @@ function runShellHandler(descriptor, event, signal) {
|
|
|
9204
9360
|
return;
|
|
9205
9361
|
}
|
|
9206
9362
|
try {
|
|
9207
|
-
|
|
9363
|
+
resolve30(HookDecisionSchema.parse(JSON.parse(stdout)));
|
|
9208
9364
|
} catch (error) {
|
|
9209
9365
|
reject(error);
|
|
9210
9366
|
}
|
|
@@ -9356,7 +9512,7 @@ function createIncidentReporter(options) {
|
|
|
9356
9512
|
import { createHash as createHash7, randomUUID as randomUUID10 } from "crypto";
|
|
9357
9513
|
import { chmod as chmod3, unlink as unlink4 } from "fs/promises";
|
|
9358
9514
|
import { tmpdir as tmpdir2 } from "os";
|
|
9359
|
-
import { join as
|
|
9515
|
+
import { join as join10 } from "path";
|
|
9360
9516
|
import { createServer as createServer2 } from "net";
|
|
9361
9517
|
var MAX_REQUEST_BYTES = 64 * 1024;
|
|
9362
9518
|
var SOCKET_TIMEOUT_MS = 1e4;
|
|
@@ -9364,7 +9520,7 @@ function islandControlEndpoint(sessionId, platform = process.platform) {
|
|
|
9364
9520
|
const digest = createHash7("sha256").update(`${process.pid}\0${sessionId}`).digest("hex").slice(0, 20);
|
|
9365
9521
|
if (platform === "win32") return `\\\\.\\pipe\\flavor-island-control-${digest}`;
|
|
9366
9522
|
const uid = typeof process.getuid === "function" ? process.getuid() : 0;
|
|
9367
|
-
return
|
|
9523
|
+
return join10(tmpdir2(), `flavor-island-control-${uid}-${digest}.sock`);
|
|
9368
9524
|
}
|
|
9369
9525
|
async function createIslandControlServer(options) {
|
|
9370
9526
|
const platform = options.platform ?? process.platform;
|
|
@@ -9401,14 +9557,14 @@ async function createIslandControlServer(options) {
|
|
|
9401
9557
|
void dispatch(line, socket, options, token);
|
|
9402
9558
|
});
|
|
9403
9559
|
});
|
|
9404
|
-
await new Promise((
|
|
9560
|
+
await new Promise((resolve30, reject) => {
|
|
9405
9561
|
const onError = (error) => {
|
|
9406
9562
|
server.removeListener("listening", onListening);
|
|
9407
9563
|
reject(error);
|
|
9408
9564
|
};
|
|
9409
9565
|
const onListening = () => {
|
|
9410
9566
|
server.removeListener("error", onError);
|
|
9411
|
-
|
|
9567
|
+
resolve30();
|
|
9412
9568
|
};
|
|
9413
9569
|
server.once("error", onError);
|
|
9414
9570
|
server.once("listening", onListening);
|
|
@@ -9479,11 +9635,11 @@ function write(socket, payload) {
|
|
|
9479
9635
|
}
|
|
9480
9636
|
}
|
|
9481
9637
|
function closeServer(server) {
|
|
9482
|
-
return new Promise((
|
|
9638
|
+
return new Promise((resolve30) => {
|
|
9483
9639
|
try {
|
|
9484
|
-
server.close(() =>
|
|
9640
|
+
server.close(() => resolve30());
|
|
9485
9641
|
} catch {
|
|
9486
|
-
|
|
9642
|
+
resolve30();
|
|
9487
9643
|
}
|
|
9488
9644
|
});
|
|
9489
9645
|
}
|
|
@@ -9491,19 +9647,19 @@ function closeServer(server) {
|
|
|
9491
9647
|
// src/evolve/service.ts
|
|
9492
9648
|
import { spawn as spawn5 } from "child_process";
|
|
9493
9649
|
import { writeFile as writeFile8 } from "fs/promises";
|
|
9494
|
-
import { join as
|
|
9650
|
+
import { join as join13 } from "path";
|
|
9495
9651
|
import { z as z14 } from "zod";
|
|
9496
9652
|
|
|
9497
9653
|
// src/evolve/loader.ts
|
|
9498
9654
|
import { cp, mkdir as mkdir8, mkdtemp, readdir as readdir4, rm as rm2, stat as stat4, writeFile as writeFile6 } from "fs/promises";
|
|
9499
9655
|
import { tmpdir as tmpdir3 } from "os";
|
|
9500
|
-
import { join as
|
|
9656
|
+
import { join as join11, resolve as resolve11 } from "path";
|
|
9501
9657
|
|
|
9502
9658
|
// src/plugins/host.ts
|
|
9503
9659
|
import { createHash as createHash8 } from "crypto";
|
|
9504
9660
|
import { lstatSync, realpathSync as realpathSync3 } from "fs";
|
|
9505
9661
|
import { lstat as lstat5, open as open5, readFile as readFile7, readdir as readdir3, realpath as realpath4, stat as stat3, writeFile as writeFile5 } from "fs/promises";
|
|
9506
|
-
import { isAbsolute as
|
|
9662
|
+
import { isAbsolute as isAbsolute6, relative as relative8, resolve as resolve10, sep as sep8 } from "path";
|
|
9507
9663
|
import { pathToFileURL } from "url";
|
|
9508
9664
|
import { z as z13 } from "zod";
|
|
9509
9665
|
|
|
@@ -9821,7 +9977,7 @@ var PluginHost = class {
|
|
|
9821
9977
|
const roots = [];
|
|
9822
9978
|
for (const directory of directories) {
|
|
9823
9979
|
try {
|
|
9824
|
-
for (const entry2 of await readdir3(directory, { withFileTypes: true })) if (entry2.isDirectory()) roots.push(
|
|
9980
|
+
for (const entry2 of await readdir3(directory, { withFileTypes: true })) if (entry2.isDirectory()) roots.push(resolve10(directory, entry2.name));
|
|
9825
9981
|
} catch (error) {
|
|
9826
9982
|
this.#diagnose(directory, error, directory);
|
|
9827
9983
|
}
|
|
@@ -9854,7 +10010,7 @@ var PluginHost = class {
|
|
|
9854
10010
|
const rootInfo = await lstat5(inputRoot);
|
|
9855
10011
|
if (!rootInfo.isDirectory() || rootInfo.isSymbolicLink()) throw new Error("Plugin root must be a static directory");
|
|
9856
10012
|
const root = await realpath4(inputRoot);
|
|
9857
|
-
const rawManifest = await readManifest(
|
|
10013
|
+
const rawManifest = await readManifest(resolve10(root, MANIFEST));
|
|
9858
10014
|
if (typeof rawManifest === "object" && rawManifest !== null && "name" in rawManifest && typeof rawManifest.name === "string") {
|
|
9859
10015
|
diagnosticLabel = rawManifest.name;
|
|
9860
10016
|
}
|
|
@@ -10040,7 +10196,7 @@ var PluginHost = class {
|
|
|
10040
10196
|
assertActive();
|
|
10041
10197
|
const permission = `filesystem:${operation}`;
|
|
10042
10198
|
if (!manifest.permissions.includes(permission)) throw new Error(`Plugin ${manifest.name} lacks ${permission} permission`);
|
|
10043
|
-
const path =
|
|
10199
|
+
const path = resolve10(rawPath);
|
|
10044
10200
|
const authorized = await this.#options.authorizeFilesystem?.({ plugin: manifest.name, operation, path });
|
|
10045
10201
|
assertActive();
|
|
10046
10202
|
if (authorized !== true) throw new Error(`Filesystem ${operation} denied for plugin ${manifest.name}`);
|
|
@@ -10171,14 +10327,14 @@ function assertSnapshot(expected, actual, label) {
|
|
|
10171
10327
|
for (const key of ["dev", "ino", "mode", "size", "mtimeNs", "ctimeNs"]) if (expected[key] !== actual[key]) throw new Error(`${label} changed during validation`);
|
|
10172
10328
|
}
|
|
10173
10329
|
function resolveContained(root, value, label) {
|
|
10174
|
-
if (
|
|
10175
|
-
const candidate =
|
|
10330
|
+
if (isAbsolute6(value)) throw new Error(`${label} must be relative to the plugin root`);
|
|
10331
|
+
const candidate = resolve10(root, value);
|
|
10176
10332
|
assertContained(root, candidate, label);
|
|
10177
10333
|
return candidate;
|
|
10178
10334
|
}
|
|
10179
10335
|
function assertContained(root, candidate, label) {
|
|
10180
10336
|
const delta = relative8(root, candidate);
|
|
10181
|
-
if (delta === ".." || delta.startsWith(`..${sep8}`) ||
|
|
10337
|
+
if (delta === ".." || delta.startsWith(`..${sep8}`) || isAbsolute6(delta)) throw new Error(`${label} escapes the plugin root`);
|
|
10182
10338
|
}
|
|
10183
10339
|
function scopedLogger(logger, plugin) {
|
|
10184
10340
|
const prefix = `[plugin:${plugin}] `;
|
|
@@ -10221,17 +10377,17 @@ function compare(left, right) {
|
|
|
10221
10377
|
}
|
|
10222
10378
|
|
|
10223
10379
|
// src/evolve/loader.ts
|
|
10224
|
-
var PLUGIN_DIR =
|
|
10225
|
-
var VERSIONS_DIR =
|
|
10380
|
+
var PLUGIN_DIR = join11(".flavor", "plugins");
|
|
10381
|
+
var VERSIONS_DIR = join11(".flavor", "plugins", ".versions");
|
|
10226
10382
|
function sanitizePluginName(tool) {
|
|
10227
10383
|
const base = String(tool ?? "fix").replace(/[^a-z0-9-]/gi, "-").toLowerCase();
|
|
10228
10384
|
return base.startsWith("fix-") ? base : `fix-${base}`;
|
|
10229
10385
|
}
|
|
10230
10386
|
function fixPluginDir(workspace, name) {
|
|
10231
|
-
return
|
|
10387
|
+
return resolve11(workspace, PLUGIN_DIR, name);
|
|
10232
10388
|
}
|
|
10233
10389
|
function versionsDirFor(workspace, name) {
|
|
10234
|
-
return
|
|
10390
|
+
return resolve11(workspace, VERSIONS_DIR, name);
|
|
10235
10391
|
}
|
|
10236
10392
|
function timestamp() {
|
|
10237
10393
|
return (/* @__PURE__ */ new Date()).toISOString().replaceAll(":", "-");
|
|
@@ -10266,8 +10422,8 @@ export function activate(ctx) {
|
|
|
10266
10422
|
async function scaffoldFixPlugin(workspace, name) {
|
|
10267
10423
|
const dir = fixPluginDir(workspace, name);
|
|
10268
10424
|
await mkdir8(dir, { recursive: true });
|
|
10269
|
-
const manifestPath =
|
|
10270
|
-
const entryPath =
|
|
10425
|
+
const manifestPath = join11(dir, "flavor-plugin.json");
|
|
10426
|
+
const entryPath = join11(dir, "index.js");
|
|
10271
10427
|
try {
|
|
10272
10428
|
await writeFile6(manifestPath, SCAFFOLD_MANIFEST(name), { encoding: "utf8", flag: "wx" });
|
|
10273
10429
|
await writeFile6(entryPath, SCAFFOLD_ENTRY(name), { encoding: "utf8", flag: "wx" });
|
|
@@ -10287,9 +10443,9 @@ async function verifyFixPlugin(workspace, name) {
|
|
|
10287
10443
|
return { ok: false, provided: [], tools: [], commands: [], error: `Plugin "${name}" not found.` };
|
|
10288
10444
|
}
|
|
10289
10445
|
const collected = { tools: [], commands: [] };
|
|
10290
|
-
const sandbox = await mkdtemp(
|
|
10446
|
+
const sandbox = await mkdtemp(join11(tmpdir3(), "flavor-evolve-verify-"));
|
|
10291
10447
|
try {
|
|
10292
|
-
await cp(pluginRoot,
|
|
10448
|
+
await cp(pluginRoot, join11(sandbox, name), { recursive: true });
|
|
10293
10449
|
const host = new PluginHost({
|
|
10294
10450
|
projectPluginDirs: [sandbox],
|
|
10295
10451
|
sandbox: true,
|
|
@@ -10340,7 +10496,7 @@ async function verifyFixPlugin(workspace, name) {
|
|
|
10340
10496
|
}
|
|
10341
10497
|
async function snapshotFixPlugin(workspace, name) {
|
|
10342
10498
|
const pluginRoot = fixPluginDir(workspace, name);
|
|
10343
|
-
const snapshotRoot =
|
|
10499
|
+
const snapshotRoot = join11(versionsDirFor(workspace, name), timestamp());
|
|
10344
10500
|
await mkdir8(snapshotRoot, { recursive: true });
|
|
10345
10501
|
await cp(pluginRoot, snapshotRoot, { recursive: true });
|
|
10346
10502
|
return snapshotRoot;
|
|
@@ -10361,7 +10517,7 @@ async function revertFixPlugin(workspace, name) {
|
|
|
10361
10517
|
}
|
|
10362
10518
|
const snapshots = entries.filter((entry2) => !entry2.startsWith(".")).sort();
|
|
10363
10519
|
if (snapshots.length === 0) throw new Error(`Plugin "${name}" has no snapshot to restore.`);
|
|
10364
|
-
const latest =
|
|
10520
|
+
const latest = join11(versionsRoot, snapshots[snapshots.length - 1]);
|
|
10365
10521
|
await rm2(pluginRoot, { recursive: true, force: true });
|
|
10366
10522
|
await mkdir8(pluginRoot, { recursive: true });
|
|
10367
10523
|
await cp(latest, pluginRoot, { recursive: true });
|
|
@@ -10371,7 +10527,7 @@ async function revertFixPlugin(workspace, name) {
|
|
|
10371
10527
|
// src/evolve/store.ts
|
|
10372
10528
|
import { createHash as createHash9 } from "crypto";
|
|
10373
10529
|
import { appendFile, mkdir as mkdir9, readFile as readFile8, rm as rm3, writeFile as writeFile7 } from "fs/promises";
|
|
10374
|
-
import { dirname as dirname11, join as
|
|
10530
|
+
import { dirname as dirname11, join as join12 } from "path";
|
|
10375
10531
|
function normalizeError(message2) {
|
|
10376
10532
|
return String(message2 ?? "").replace(/\s+/g, " ").replace(/"[^"]*"/g, '"\u2026"').replace(/`[^`]*`/g, '"\u2026"').trim().slice(0, 160);
|
|
10377
10533
|
}
|
|
@@ -10444,12 +10600,12 @@ var EvolveStore = class {
|
|
|
10444
10600
|
#queue = Promise.resolve();
|
|
10445
10601
|
constructor(options) {
|
|
10446
10602
|
const { workspace, maxSignals = 400, maxRules = 20 } = options;
|
|
10447
|
-
this.dir =
|
|
10448
|
-
this.signalsFile =
|
|
10449
|
-
this.reflectionsFile =
|
|
10450
|
-
this.doneFile =
|
|
10451
|
-
this.verifiedFile =
|
|
10452
|
-
this.rulesFile =
|
|
10603
|
+
this.dir = join12(workspace, ".flavor", "evolve");
|
|
10604
|
+
this.signalsFile = join12(this.dir, "signals.jsonl");
|
|
10605
|
+
this.reflectionsFile = join12(this.dir, "reflections.jsonl");
|
|
10606
|
+
this.doneFile = join12(this.dir, "done.json");
|
|
10607
|
+
this.verifiedFile = join12(this.dir, "verified.json");
|
|
10608
|
+
this.rulesFile = join12(this.dir, "rules.json");
|
|
10453
10609
|
this.maxSignals = maxSignals;
|
|
10454
10610
|
this.maxRules = maxRules;
|
|
10455
10611
|
}
|
|
@@ -10668,7 +10824,7 @@ var USAGE = [
|
|
|
10668
10824
|
" clear reset signals, done markers, and verified markers"
|
|
10669
10825
|
].join("\n");
|
|
10670
10826
|
function runCommand(command, cwd, timeoutMs) {
|
|
10671
|
-
return new Promise((
|
|
10827
|
+
return new Promise((resolve30) => {
|
|
10672
10828
|
const isWindows = process.platform === "win32";
|
|
10673
10829
|
const child = spawn5(isWindows ? "cmd.exe" : "/bin/sh", isWindows ? ["/d", "/s", "/c", command] : ["-c", command], {
|
|
10674
10830
|
cwd,
|
|
@@ -10678,7 +10834,7 @@ function runCommand(command, cwd, timeoutMs) {
|
|
|
10678
10834
|
let stderr = "";
|
|
10679
10835
|
const timer = setTimeout(() => {
|
|
10680
10836
|
child.kill();
|
|
10681
|
-
|
|
10837
|
+
resolve30({ ok: false, stdout, stderr: `${stderr}
|
|
10682
10838
|
[killed: exceeded ${timeoutMs}ms]`, code: "timeout" });
|
|
10683
10839
|
}, timeoutMs);
|
|
10684
10840
|
child.stdout.on("data", (chunk) => {
|
|
@@ -10689,12 +10845,12 @@ function runCommand(command, cwd, timeoutMs) {
|
|
|
10689
10845
|
});
|
|
10690
10846
|
child.on("error", (error) => {
|
|
10691
10847
|
clearTimeout(timer);
|
|
10692
|
-
|
|
10848
|
+
resolve30({ ok: false, stdout, stderr: `${stderr}
|
|
10693
10849
|
${error.message}`, code: "spawn" });
|
|
10694
10850
|
});
|
|
10695
10851
|
child.on("close", (code) => {
|
|
10696
10852
|
clearTimeout(timer);
|
|
10697
|
-
|
|
10853
|
+
resolve30({ ok: code === 0, stdout, stderr, code: code ?? "unknown" });
|
|
10698
10854
|
});
|
|
10699
10855
|
});
|
|
10700
10856
|
}
|
|
@@ -10988,7 +11144,7 @@ function createEvolveService(options) {
|
|
|
10988
11144
|
if (suggestion === void 0) return `No open suggestion with id "${suggestionId}". Use /evolve suggest to list them.`;
|
|
10989
11145
|
const name = sanitizePluginName(suggestion.tool);
|
|
10990
11146
|
const dir = await scaffoldFixPlugin(workspace, name);
|
|
10991
|
-
await writeFile8(
|
|
11147
|
+
await writeFile8(join13(dir, "PLAN.md"), buildPlan(suggestion, name, "See the repeated failure below; describe your fix in PLAN.md."), "utf8");
|
|
10992
11148
|
await snapshotFixPlugin(workspace, name);
|
|
10993
11149
|
return [
|
|
10994
11150
|
`suggestion ${suggestion.id}: ${suggestion.tool} x${suggestion.count} \u2014 ${suggestion.error}`,
|
|
@@ -11079,7 +11235,7 @@ ${result.stderr.slice(-4e3)}` : ""}`;
|
|
|
11079
11235
|
}
|
|
11080
11236
|
const name = sanitizePluginName(suggestion.tool);
|
|
11081
11237
|
const dir = await scaffoldFixPlugin(workspace, name);
|
|
11082
|
-
await writeFile8(
|
|
11238
|
+
await writeFile8(join13(dir, "PLAN.md"), buildPlan(suggestion, name, implementation), "utf8");
|
|
11083
11239
|
await snapshotFixPlugin(workspace, name);
|
|
11084
11240
|
return [
|
|
11085
11241
|
`Scaffolded fix plugin at ${dir} for suggestion [${suggestion.id}] (${suggestion.tool} x${suggestion.count}).`,
|
|
@@ -11502,7 +11658,7 @@ ${evidence.summary}`;
|
|
|
11502
11658
|
// src/goal/orchestrator.ts
|
|
11503
11659
|
import { createHash as createHash11 } from "crypto";
|
|
11504
11660
|
import { writeFile as writeFile9, mkdir as mkdir10 } from "fs/promises";
|
|
11505
|
-
import { join as
|
|
11661
|
+
import { join as join15 } from "path";
|
|
11506
11662
|
|
|
11507
11663
|
// src/goal/types.ts
|
|
11508
11664
|
import { z as z16 } from "zod";
|
|
@@ -11667,7 +11823,7 @@ function extractJson(text) {
|
|
|
11667
11823
|
// src/goal/classifier.ts
|
|
11668
11824
|
import { createHash as createHash10 } from "crypto";
|
|
11669
11825
|
import { readFile as readFile9 } from "fs/promises";
|
|
11670
|
-
import { join as
|
|
11826
|
+
import { join as join14 } from "path";
|
|
11671
11827
|
function buildSkepticPrompt(evidence, plan, index) {
|
|
11672
11828
|
const criteriaList = plan.criteria.map((c) => ` ${c.id}. [${c.type}] ${c.description}`).join("\n");
|
|
11673
11829
|
return [
|
|
@@ -11844,7 +12000,7 @@ function computeFingerprint(gaps) {
|
|
|
11844
12000
|
}
|
|
11845
12001
|
async function collectEvidence(workspace, objective, finalResponse, priorGaps, contractHash2 = createHash10("sha256").update(objective).digest("hex"), hostVerification) {
|
|
11846
12002
|
const workspaceState = await collectWorkspaceState(workspace);
|
|
11847
|
-
const planPath =
|
|
12003
|
+
const planPath = join14(workspace, ".flavor", "goal-plan.md");
|
|
11848
12004
|
let planFile = null;
|
|
11849
12005
|
try {
|
|
11850
12006
|
planFile = await readFile9(planPath, "utf8");
|
|
@@ -12097,8 +12253,8 @@ function contractHash(objective, plan) {
|
|
|
12097
12253
|
return createHash11("sha256").update(JSON.stringify({ objective, plan })).digest("hex");
|
|
12098
12254
|
}
|
|
12099
12255
|
async function writePlanFile(workspace, plan) {
|
|
12100
|
-
const dir =
|
|
12101
|
-
const planPath =
|
|
12256
|
+
const dir = join15(workspace, ".flavor");
|
|
12257
|
+
const planPath = join15(dir, "goal-plan.md");
|
|
12102
12258
|
await mkdir10(dir, { recursive: true });
|
|
12103
12259
|
const criteriaText = plan.criteria.map((c) => `- [${c.type}] AC-${c.id}: ${c.description}`).join("\n");
|
|
12104
12260
|
const content = [
|
|
@@ -12176,15 +12332,15 @@ function formatPriorGaps(gaps) {
|
|
|
12176
12332
|
import { constants as constants2 } from "fs";
|
|
12177
12333
|
import { randomUUID as randomUUID11 } from "crypto";
|
|
12178
12334
|
import { lstat as lstat6, mkdir as mkdir11, open as open6, rename as rename4, rm as rm4 } from "fs/promises";
|
|
12179
|
-
import { dirname as dirname12, join as
|
|
12335
|
+
import { dirname as dirname12, join as join16, relative as relative9, resolve as resolve12, sep as sep9 } from "path";
|
|
12180
12336
|
var DEFAULT_MAX_BYTES = 1024 * 1024;
|
|
12181
12337
|
var GoalStore = class {
|
|
12182
12338
|
#workspace;
|
|
12183
12339
|
#root;
|
|
12184
12340
|
#maxBytes;
|
|
12185
12341
|
constructor(options) {
|
|
12186
|
-
this.#workspace =
|
|
12187
|
-
this.#root =
|
|
12342
|
+
this.#workspace = resolve12(options.workspace);
|
|
12343
|
+
this.#root = join16(this.#workspace, ".flavor", "goals");
|
|
12188
12344
|
this.#maxBytes = options.maxBytes ?? DEFAULT_MAX_BYTES;
|
|
12189
12345
|
}
|
|
12190
12346
|
async save(input) {
|
|
@@ -12192,13 +12348,13 @@ var GoalStore = class {
|
|
|
12192
12348
|
await assertNoSymlink(this.#workspace, dirname12(this.#root));
|
|
12193
12349
|
await mkdir11(this.#root, { recursive: true, mode: 448 });
|
|
12194
12350
|
await assertNoSymlink(this.#workspace, this.#root);
|
|
12195
|
-
const target =
|
|
12351
|
+
const target = join16(this.#root, `${state.id}.json`);
|
|
12196
12352
|
const body = `${JSON.stringify(state, null, 2)}
|
|
12197
12353
|
`;
|
|
12198
12354
|
if (Buffer.byteLength(body) > this.#maxBytes) {
|
|
12199
12355
|
throw new Error(`Goal state exceeds maximum size of ${this.#maxBytes} bytes`);
|
|
12200
12356
|
}
|
|
12201
|
-
const temporary =
|
|
12357
|
+
const temporary = join16(this.#root, `.${state.id}.${process.pid}.${randomUUID11()}.tmp`);
|
|
12202
12358
|
let handle;
|
|
12203
12359
|
try {
|
|
12204
12360
|
handle = await open6(temporary, constants2.O_CREAT | constants2.O_EXCL | constants2.O_WRONLY, 384);
|
|
@@ -12214,8 +12370,8 @@ var GoalStore = class {
|
|
|
12214
12370
|
}
|
|
12215
12371
|
};
|
|
12216
12372
|
async function assertNoSymlink(root, target) {
|
|
12217
|
-
let cursor =
|
|
12218
|
-
const stop =
|
|
12373
|
+
let cursor = resolve12(target);
|
|
12374
|
+
const stop = resolve12(root);
|
|
12219
12375
|
const value = relative9(stop, cursor);
|
|
12220
12376
|
if (value === ".." || value.startsWith(`..${sep9}`) || value.includes(":")) {
|
|
12221
12377
|
throw new Error("Goal path escapes workspace");
|
|
@@ -12239,7 +12395,7 @@ function isCode3(error, code) {
|
|
|
12239
12395
|
|
|
12240
12396
|
// src/loop/isolation.ts
|
|
12241
12397
|
import { appendFile as appendFile2, mkdir as mkdir12, readFile as readFile10 } from "fs/promises";
|
|
12242
|
-
import { join as
|
|
12398
|
+
import { join as join17, resolve as resolve13 } from "path";
|
|
12243
12399
|
var CODE_CHANGE = /\b(?:fix|implement|add|update|change|modify|refactor|build|create|remove|delete|write|migrate)\b|修复|实现|添加|更新|修改|重构|创建|删除|迁移/i;
|
|
12244
12400
|
var READ_ONLY = /\b(?:review|analy[sz]e|inspect|explain|research|investigate|audit|summari[sz]e|report)\b|调研|分析|审查|检查|解释|研究|总结|报告/i;
|
|
12245
12401
|
function inferGoalIntent(goal) {
|
|
@@ -12249,7 +12405,7 @@ function inferGoalIntent(goal) {
|
|
|
12249
12405
|
}
|
|
12250
12406
|
async function prepareLoopWorkspace(options) {
|
|
12251
12407
|
options.signal?.throwIfAborted();
|
|
12252
|
-
const root =
|
|
12408
|
+
const root = resolve13(options.root);
|
|
12253
12409
|
const intent = inferGoalIntent(options.goal);
|
|
12254
12410
|
if (intent === "read_only") return { kind: "ready", workspace: { root, mode: "current" } };
|
|
12255
12411
|
const inside = await git(root, ["rev-parse", "--is-inside-work-tree"]);
|
|
@@ -12262,8 +12418,8 @@ async function prepareLoopWorkspace(options) {
|
|
|
12262
12418
|
return { kind: "needs_human", reason: "The workspace has uncommitted changes that a new worktree would omit." };
|
|
12263
12419
|
}
|
|
12264
12420
|
options.signal?.throwIfAborted();
|
|
12265
|
-
const worktrees =
|
|
12266
|
-
const path =
|
|
12421
|
+
const worktrees = join17(root, ".worktrees");
|
|
12422
|
+
const path = join17(worktrees, `loop-${options.loopId}`);
|
|
12267
12423
|
const branch = `loop/${options.loopId}`;
|
|
12268
12424
|
try {
|
|
12269
12425
|
await ensureWorktreeDirectoryIgnored(root);
|
|
@@ -12292,9 +12448,9 @@ async function ensureWorktreeDirectoryIgnored(root) {
|
|
|
12292
12448
|
if (ignored2.code === 0) return;
|
|
12293
12449
|
const commonDirectory = await git(root, ["rev-parse", "--git-common-dir"]);
|
|
12294
12450
|
if (commonDirectory.code !== 0) throw new Error(commonDirectory.stderr.trim() || "Git common directory is unavailable");
|
|
12295
|
-
const gitDirectory =
|
|
12296
|
-
const infoDirectory =
|
|
12297
|
-
const excludePath =
|
|
12451
|
+
const gitDirectory = resolve13(root, commonDirectory.stdout.trim());
|
|
12452
|
+
const infoDirectory = join17(gitDirectory, "info");
|
|
12453
|
+
const excludePath = join17(infoDirectory, "exclude");
|
|
12298
12454
|
await mkdir12(infoDirectory, { recursive: true });
|
|
12299
12455
|
let existing = "";
|
|
12300
12456
|
try {
|
|
@@ -12310,7 +12466,7 @@ async function ensureWorktreeDirectoryIgnored(root) {
|
|
|
12310
12466
|
// src/loop/store.ts
|
|
12311
12467
|
import { constants as constants3 } from "fs";
|
|
12312
12468
|
import { lstat as lstat7, mkdir as mkdir13, open as open7, readFile as readFile11, realpath as realpath5, rename as rename5, rm as rm5, stat as stat5 } from "fs/promises";
|
|
12313
|
-
import { dirname as dirname13, join as
|
|
12469
|
+
import { dirname as dirname13, join as join18, relative as relative10, resolve as resolve14, sep as sep10 } from "path";
|
|
12314
12470
|
import { randomUUID as randomUUID12 } from "crypto";
|
|
12315
12471
|
var LOOP_ID = /^[A-Za-z0-9][A-Za-z0-9._-]{0,127}$/;
|
|
12316
12472
|
var DEFAULT_MAX_BYTES2 = 5 * 1024 * 1024;
|
|
@@ -12320,8 +12476,8 @@ var LoopStore = class {
|
|
|
12320
12476
|
#maxBytes;
|
|
12321
12477
|
#appendTails = /* @__PURE__ */ new Map();
|
|
12322
12478
|
constructor(options) {
|
|
12323
|
-
this.#workspace =
|
|
12324
|
-
this.#root =
|
|
12479
|
+
this.#workspace = resolve14(options.workspace);
|
|
12480
|
+
this.#root = join18(this.#workspace, ".flavor", "loops");
|
|
12325
12481
|
this.#maxBytes = options.maxBytes ?? DEFAULT_MAX_BYTES2;
|
|
12326
12482
|
}
|
|
12327
12483
|
async create(state) {
|
|
@@ -12338,11 +12494,11 @@ var LoopStore = class {
|
|
|
12338
12494
|
const state = LoopStateSchema.parse(input);
|
|
12339
12495
|
await this.#assertWorkspace(state.workspace);
|
|
12340
12496
|
const directory = await this.#prepare(state.loopId);
|
|
12341
|
-
const target =
|
|
12497
|
+
const target = join18(directory, "state.json");
|
|
12342
12498
|
const body = `${JSON.stringify(state, null, 2)}
|
|
12343
12499
|
`;
|
|
12344
12500
|
if (Buffer.byteLength(body) > this.#maxBytes) throw new Error(`Loop state exceeds maximum size of ${this.#maxBytes} bytes`);
|
|
12345
|
-
const temporary =
|
|
12501
|
+
const temporary = join18(directory, `.state.${process.pid}.${randomUUID12()}.tmp`);
|
|
12346
12502
|
let handle;
|
|
12347
12503
|
try {
|
|
12348
12504
|
handle = await open7(temporary, constants3.O_CREAT | constants3.O_EXCL | constants3.O_WRONLY, 384);
|
|
@@ -12358,7 +12514,7 @@ var LoopStore = class {
|
|
|
12358
12514
|
}
|
|
12359
12515
|
async load(loopId) {
|
|
12360
12516
|
const directory = await this.#safeExisting(loopId);
|
|
12361
|
-
const target =
|
|
12517
|
+
const target = join18(directory, "state.json");
|
|
12362
12518
|
try {
|
|
12363
12519
|
const metadata = await stat5(target);
|
|
12364
12520
|
if (!metadata.isFile() || metadata.size > this.#maxBytes) throw new Error("Loop state is not a bounded regular file");
|
|
@@ -12378,7 +12534,7 @@ var LoopStore = class {
|
|
|
12378
12534
|
const previous = this.#appendTails.get(event.loopId) ?? Promise.resolve();
|
|
12379
12535
|
const next = previous.catch(() => void 0).then(async () => {
|
|
12380
12536
|
const directory = await this.#prepare(event.loopId);
|
|
12381
|
-
const path =
|
|
12537
|
+
const path = join18(directory, "events.jsonl");
|
|
12382
12538
|
const handle = await open7(path, constants3.O_CREAT | constants3.O_APPEND | constants3.O_WRONLY, 384);
|
|
12383
12539
|
try {
|
|
12384
12540
|
await handle.writeFile(`${JSON.stringify(event)}
|
|
@@ -12395,7 +12551,7 @@ var LoopStore = class {
|
|
|
12395
12551
|
await assertNoSymlink2(this.#workspace, dirname13(this.#root));
|
|
12396
12552
|
await mkdir13(this.#root, { recursive: true, mode: 448 });
|
|
12397
12553
|
await assertNoSymlink2(this.#workspace, this.#root);
|
|
12398
|
-
const directory =
|
|
12554
|
+
const directory = join18(this.#root, loopId);
|
|
12399
12555
|
await mkdir13(directory, { recursive: true, mode: 448 });
|
|
12400
12556
|
await assertNoSymlink2(this.#workspace, directory);
|
|
12401
12557
|
return directory;
|
|
@@ -12403,7 +12559,7 @@ var LoopStore = class {
|
|
|
12403
12559
|
async #safeExisting(loopId) {
|
|
12404
12560
|
validateId(loopId);
|
|
12405
12561
|
await assertNoSymlink2(this.#workspace, this.#root);
|
|
12406
|
-
const directory =
|
|
12562
|
+
const directory = join18(this.#root, loopId);
|
|
12407
12563
|
await assertNoSymlink2(this.#workspace, directory);
|
|
12408
12564
|
return directory;
|
|
12409
12565
|
}
|
|
@@ -12419,8 +12575,8 @@ function validateId(loopId) {
|
|
|
12419
12575
|
if (!LOOP_ID.test(loopId)) throw new Error("Invalid loop id");
|
|
12420
12576
|
}
|
|
12421
12577
|
async function assertNoSymlink2(root, target) {
|
|
12422
|
-
let cursor =
|
|
12423
|
-
const stop =
|
|
12578
|
+
let cursor = resolve14(target);
|
|
12579
|
+
const stop = resolve14(root);
|
|
12424
12580
|
if (!isWithin3(stop, cursor)) throw new Error("Loop path escapes workspace");
|
|
12425
12581
|
const chain = [];
|
|
12426
12582
|
while (cursor !== stop) {
|
|
@@ -12436,14 +12592,14 @@ async function assertNoSymlink2(root, target) {
|
|
|
12436
12592
|
}
|
|
12437
12593
|
}
|
|
12438
12594
|
function isWithin3(root, path) {
|
|
12439
|
-
const value = relative10(
|
|
12595
|
+
const value = relative10(resolve14(root), resolve14(path));
|
|
12440
12596
|
return value === "" || !value.startsWith(`..${sep10}`) && value !== ".." && !value.includes(":");
|
|
12441
12597
|
}
|
|
12442
12598
|
async function canonical(path) {
|
|
12443
12599
|
try {
|
|
12444
|
-
return await realpath5(
|
|
12600
|
+
return await realpath5(resolve14(path));
|
|
12445
12601
|
} catch {
|
|
12446
|
-
return
|
|
12602
|
+
return resolve14(path);
|
|
12447
12603
|
}
|
|
12448
12604
|
}
|
|
12449
12605
|
function isCode4(error, code) {
|
|
@@ -12452,11 +12608,11 @@ function isCode4(error, code) {
|
|
|
12452
12608
|
|
|
12453
12609
|
// src/loop/verifier.ts
|
|
12454
12610
|
import { readFile as readFile12 } from "fs/promises";
|
|
12455
|
-
import { join as
|
|
12611
|
+
import { join as join19 } from "path";
|
|
12456
12612
|
|
|
12457
12613
|
// src/tools/shell.ts
|
|
12458
12614
|
import { spawn as spawn6, spawnSync } from "child_process";
|
|
12459
|
-
import { isAbsolute as
|
|
12615
|
+
import { isAbsolute as isAbsolute7, relative as relative11, resolve as resolve15, sep as sep11 } from "path";
|
|
12460
12616
|
import { z as z18 } from "zod";
|
|
12461
12617
|
|
|
12462
12618
|
// src/tools/jobs.ts
|
|
@@ -12609,7 +12765,7 @@ function buildWindowsCommandLine(kind, commandLine, command, args) {
|
|
|
12609
12765
|
return `chcp 65001>nul & ${executable} -NoLogo -NoProfile -NonInteractive -ExecutionPolicy Bypass -EncodedCommand ${encoded}`;
|
|
12610
12766
|
}
|
|
12611
12767
|
function createShellTool(workspace, options = {}) {
|
|
12612
|
-
const root =
|
|
12768
|
+
const root = resolve15(workspace);
|
|
12613
12769
|
const maxBytes = options.maxOutputBytes ?? DEFAULT_MAX_OUTPUT_BYTES2;
|
|
12614
12770
|
const defaultTimeoutMs = options.defaultTimeoutMs ?? DEFAULT_SHELL_TIMEOUT_MS;
|
|
12615
12771
|
if (!Number.isSafeInteger(maxBytes) || maxBytes <= 0) throw new Error("maxOutputBytes must be a positive integer");
|
|
@@ -12908,9 +13064,9 @@ function backgroundFlag(value) {
|
|
|
12908
13064
|
return value === true || value === "true";
|
|
12909
13065
|
}
|
|
12910
13066
|
function workingDirectory(root, cwd = ".") {
|
|
12911
|
-
const candidate =
|
|
13067
|
+
const candidate = resolve15(root, cwd);
|
|
12912
13068
|
const delta = relative11(root, candidate);
|
|
12913
|
-
if (delta === ".." || delta.startsWith(`..${sep11}`) ||
|
|
13069
|
+
if (delta === ".." || delta.startsWith(`..${sep11}`) || isAbsolute7(delta)) throw new Error("Working directory is outside the workspace");
|
|
12914
13070
|
return candidate;
|
|
12915
13071
|
}
|
|
12916
13072
|
async function terminateTree(pid) {
|
|
@@ -13020,13 +13176,13 @@ var SCRIPT_PRIORITY = ["test", "typecheck", "lint", "build", "smoke:install"];
|
|
|
13020
13176
|
async function inferVerificationPlan(workspace) {
|
|
13021
13177
|
const commands = [];
|
|
13022
13178
|
const seen = /* @__PURE__ */ new Set();
|
|
13023
|
-
const packageJson = await optionalJson(
|
|
13179
|
+
const packageJson = await optionalJson(join19(workspace, "package.json"));
|
|
13024
13180
|
const scripts = isRecord2(packageJson?.scripts) ? packageJson.scripts : {};
|
|
13025
13181
|
for (const name of SCRIPT_PRIORITY) {
|
|
13026
13182
|
if (typeof scripts[name] !== "string" || isUnconditionalPassThrough(scripts[name])) continue;
|
|
13027
13183
|
add(commands, seen, npmScript(name));
|
|
13028
13184
|
}
|
|
13029
|
-
const flavor = await optionalText(
|
|
13185
|
+
const flavor = await optionalText(join19(workspace, "FLAVOR.md"));
|
|
13030
13186
|
if (flavor !== void 0) {
|
|
13031
13187
|
for (const match of flavor.matchAll(/^- `([^`]+)`\s*$/gm)) {
|
|
13032
13188
|
const parsed = parseTrustedCommand(match[1] ?? "");
|
|
@@ -13993,7 +14149,7 @@ function isRecord3(value) {
|
|
|
13993
14149
|
}
|
|
13994
14150
|
|
|
13995
14151
|
// src/mcp/sdk.ts
|
|
13996
|
-
import { resolve as
|
|
14152
|
+
import { resolve as resolve16 } from "path";
|
|
13997
14153
|
import { Client } from "@modelcontextprotocol/sdk/client/index.js";
|
|
13998
14154
|
import {
|
|
13999
14155
|
getDefaultEnvironment,
|
|
@@ -14015,7 +14171,7 @@ async function connectSdkMcpClient(input, dependencies = DEFAULT_DEPENDENCIES) {
|
|
|
14015
14171
|
command: config.command,
|
|
14016
14172
|
args: [...config.args],
|
|
14017
14173
|
env: { ...dependencies.defaultEnvironment(), ...config.env },
|
|
14018
|
-
cwd:
|
|
14174
|
+
cwd: resolve16(input.workspace, config.cwd ?? "."),
|
|
14019
14175
|
// MCP servers frequently log startup banners to stderr. Inheriting that
|
|
14020
14176
|
// stream corrupts Ink's input area, so connection errors are surfaced
|
|
14021
14177
|
// through McpManager diagnostics instead.
|
|
@@ -14220,8 +14376,8 @@ var OAuthCallbackAuthProvider = class {
|
|
|
14220
14376
|
async #startCallbackServer(expectedState, signal) {
|
|
14221
14377
|
let serverResolve;
|
|
14222
14378
|
let serverReject;
|
|
14223
|
-
const codePromise = new Promise((
|
|
14224
|
-
serverResolve =
|
|
14379
|
+
const codePromise = new Promise((resolve30, reject) => {
|
|
14380
|
+
serverResolve = resolve30;
|
|
14225
14381
|
serverReject = reject;
|
|
14226
14382
|
});
|
|
14227
14383
|
const server = createServer3((req, res) => {
|
|
@@ -14255,7 +14411,7 @@ var OAuthCallbackAuthProvider = class {
|
|
|
14255
14411
|
);
|
|
14256
14412
|
serverResolve(code);
|
|
14257
14413
|
});
|
|
14258
|
-
return new Promise((
|
|
14414
|
+
return new Promise((resolve30, reject) => {
|
|
14259
14415
|
server.once("error", (error) => {
|
|
14260
14416
|
reject(new Error(`OAuth callback server failed: ${error.message}`));
|
|
14261
14417
|
});
|
|
@@ -14272,7 +14428,7 @@ var OAuthCallbackAuthProvider = class {
|
|
|
14272
14428
|
signal.addEventListener("abort", () => {
|
|
14273
14429
|
serverReject(new Error("OAuth flow aborted"));
|
|
14274
14430
|
}, { once: true });
|
|
14275
|
-
|
|
14431
|
+
resolve30({ redirectUri, promise: codePromise });
|
|
14276
14432
|
});
|
|
14277
14433
|
});
|
|
14278
14434
|
}
|
|
@@ -14415,7 +14571,7 @@ function isStoredToken(value) {
|
|
|
14415
14571
|
|
|
14416
14572
|
// src/permissions/policy.ts
|
|
14417
14573
|
import { readFile as readFile15 } from "fs/promises";
|
|
14418
|
-
import { join as
|
|
14574
|
+
import { join as join20, resolve as resolve17 } from "path";
|
|
14419
14575
|
import { z as z20 } from "zod";
|
|
14420
14576
|
var DecisionSchema2 = z20.enum(["allow", "ask", "deny"]);
|
|
14421
14577
|
var PrefixSchema = z20.array(z20.string().min(1).max(1024)).min(1).max(64);
|
|
@@ -14451,10 +14607,10 @@ var CompiledPermissionPolicy = class {
|
|
|
14451
14607
|
};
|
|
14452
14608
|
async function loadPermissionPolicy(options) {
|
|
14453
14609
|
const sources = [
|
|
14454
|
-
...options.managedPath === void 0 ? [] : [{ tier: "managed", path:
|
|
14455
|
-
{ tier: "user", path:
|
|
14456
|
-
{ tier: "project", path:
|
|
14457
|
-
{ tier: "local", path:
|
|
14610
|
+
...options.managedPath === void 0 ? [] : [{ tier: "managed", path: resolve17(options.managedPath) }],
|
|
14611
|
+
{ tier: "user", path: join20(resolve17(options.home), ".flavor-code", "permissions.json") },
|
|
14612
|
+
{ tier: "project", path: join20(resolve17(options.workspace), ".flavor", "permissions.json") },
|
|
14613
|
+
{ tier: "local", path: join20(resolve17(options.workspace), ".flavor", "permissions.local.json") }
|
|
14458
14614
|
];
|
|
14459
14615
|
const rules = [];
|
|
14460
14616
|
for (const source of sources) {
|
|
@@ -14676,7 +14832,7 @@ function data(value) {
|
|
|
14676
14832
|
// src/skills/registry.ts
|
|
14677
14833
|
import { constants as constants4 } from "fs";
|
|
14678
14834
|
import { lstat as lstat8, open as defaultOpen, readdir as readdir5, realpath as realpath6 } from "fs/promises";
|
|
14679
|
-
import { isAbsolute as
|
|
14835
|
+
import { isAbsolute as isAbsolute8, relative as relative12, resolve as resolve18, sep as sep12 } from "path";
|
|
14680
14836
|
import { marked } from "marked";
|
|
14681
14837
|
import { parseDocument } from "yaml";
|
|
14682
14838
|
var SKILL_NAME = /^[a-z0-9]+(?:-[a-z0-9]+)*$/;
|
|
@@ -14801,13 +14957,13 @@ var SkillRegistry = class {
|
|
|
14801
14957
|
const record = await this.#recordFor(skill);
|
|
14802
14958
|
const normalized = normalizeReference(reference);
|
|
14803
14959
|
const parts = normalized.split("/");
|
|
14804
|
-
if (
|
|
14960
|
+
if (isAbsolute8(reference) || parts.includes("..") || parts.length !== 2 || !RESOURCE_DIRECTORIES.has(parts[0] ?? "") || !RESOURCE_NAME.test(parts[1] ?? "")) {
|
|
14805
14961
|
throw new Error(`Resource reference escapes the skill root or is not a direct resource: ${reference}`);
|
|
14806
14962
|
}
|
|
14807
14963
|
const body = await this.loadBody(record.metadata);
|
|
14808
14964
|
const references = extractResourceReferences(body);
|
|
14809
14965
|
if (!references.has(normalized)) throw new Error(`Skill resource is not directly referenced: ${reference}`);
|
|
14810
|
-
const candidate =
|
|
14966
|
+
const candidate = resolve18(record.metadata.root, ...parts);
|
|
14811
14967
|
const file = await openVerifiedFile(candidate, record.metadata.root, "Resource", this.#options.openFile);
|
|
14812
14968
|
try {
|
|
14813
14969
|
if (file.snapshot.size > BigInt(this.#options.maxResourceBytes)) throw new Error(`Skill resource is too large: ${reference}`);
|
|
@@ -14836,7 +14992,7 @@ var SkillRegistry = class {
|
|
|
14836
14992
|
async #discoverSource(roots, source) {
|
|
14837
14993
|
const found = [];
|
|
14838
14994
|
for (const configuredRoot of roots) {
|
|
14839
|
-
const root =
|
|
14995
|
+
const root = resolve18(configuredRoot);
|
|
14840
14996
|
let entries;
|
|
14841
14997
|
let physicalRoot = root;
|
|
14842
14998
|
try {
|
|
@@ -14851,13 +15007,13 @@ var SkillRegistry = class {
|
|
|
14851
15007
|
}
|
|
14852
15008
|
entries.sort((left, right) => compareCodePoints(left.name, right.name));
|
|
14853
15009
|
for (const entry2 of entries) {
|
|
14854
|
-
const skillRoot =
|
|
15010
|
+
const skillRoot = resolve18(root, entry2.name);
|
|
14855
15011
|
try {
|
|
14856
15012
|
if (entry2.isSymbolicLink()) throw new Error("Symlinked skill directories are not allowed");
|
|
14857
15013
|
if (!entry2.isDirectory()) continue;
|
|
14858
15014
|
const physicalSkillRoot = await realpath6(skillRoot);
|
|
14859
15015
|
if (!isWithin4(physicalRoot, physicalSkillRoot)) throw new Error("Skill directory escapes its registry root");
|
|
14860
|
-
const skillFile =
|
|
15016
|
+
const skillFile = resolve18(skillRoot, "SKILL.md");
|
|
14861
15017
|
const parsed = await readFrontmatterFile(
|
|
14862
15018
|
skillFile,
|
|
14863
15019
|
physicalSkillRoot,
|
|
@@ -15059,7 +15215,7 @@ function decodeUtf82(content, label) {
|
|
|
15059
15215
|
}
|
|
15060
15216
|
function isWithin4(root, candidate) {
|
|
15061
15217
|
const delta = relative12(root, candidate);
|
|
15062
|
-
return delta === "" || delta !== ".." && !delta.startsWith(`..${sep12}`) && !
|
|
15218
|
+
return delta === "" || delta !== ".." && !delta.startsWith(`..${sep12}`) && !isAbsolute8(delta);
|
|
15063
15219
|
}
|
|
15064
15220
|
function isMissing2(error) {
|
|
15065
15221
|
return typeof error === "object" && error !== null && "code" in error && error.code === "ENOENT";
|
|
@@ -15223,7 +15379,7 @@ function createSkillResourceTool(registry) {
|
|
|
15223
15379
|
// src/session/store.ts
|
|
15224
15380
|
import { constants as constants5 } from "fs";
|
|
15225
15381
|
import { lstat as lstat9, mkdir as mkdir14, open as open8, readFile as readFile16, readdir as readdir6, realpath as realpath7, rename as rename6, rm as rm6, stat as stat6 } from "fs/promises";
|
|
15226
|
-
import { basename as basename3, dirname as dirname15, join as
|
|
15382
|
+
import { basename as basename3, dirname as dirname15, join as join21, relative as relative13, resolve as resolve19, sep as sep13 } from "path";
|
|
15227
15383
|
import { randomUUID as randomUUID13 } from "crypto";
|
|
15228
15384
|
import { z as z22 } from "zod";
|
|
15229
15385
|
var SESSION_VERSION = 4;
|
|
@@ -15343,14 +15499,15 @@ var SessionDocumentSchema = SessionBaseSchema.extend({
|
|
|
15343
15499
|
}).strict(),
|
|
15344
15500
|
timeline: TimelineSchema
|
|
15345
15501
|
}).strict();
|
|
15502
|
+
var INVALID_SESSION_LOCK_STALE_MS = 3e4;
|
|
15346
15503
|
var SessionStore = class {
|
|
15347
15504
|
#workspace;
|
|
15348
15505
|
#sessions;
|
|
15349
15506
|
#maxBytes;
|
|
15350
15507
|
#maxSessions;
|
|
15351
15508
|
constructor(options) {
|
|
15352
|
-
this.#workspace =
|
|
15353
|
-
this.#sessions =
|
|
15509
|
+
this.#workspace = resolve19(options.workspace);
|
|
15510
|
+
this.#sessions = join21(this.#workspace, ".flavor", "sessions");
|
|
15354
15511
|
this.#maxBytes = options.maxBytes ?? DEFAULT_MAX_SESSION_BYTES;
|
|
15355
15512
|
if (!Number.isSafeInteger(this.#maxBytes) || this.#maxBytes < 256) throw new Error("maxBytes must be an integer of at least 256");
|
|
15356
15513
|
this.#maxSessions = options.maxSessions ?? 50;
|
|
@@ -15382,7 +15539,7 @@ var SessionStore = class {
|
|
|
15382
15539
|
const body = `${lines.join("\n")}
|
|
15383
15540
|
`;
|
|
15384
15541
|
if (Buffer.byteLength(body) > this.#maxBytes) throw new Error(`Session exceeds maximum size of ${this.#maxBytes} bytes`);
|
|
15385
|
-
const temporary =
|
|
15542
|
+
const temporary = join21(this.#sessions, `.${document.sessionId}.${process.pid}.${randomUUID13()}.tmp`);
|
|
15386
15543
|
let handle;
|
|
15387
15544
|
try {
|
|
15388
15545
|
handle = await open8(temporary, constants5.O_CREAT | constants5.O_EXCL | constants5.O_WRONLY, 384);
|
|
@@ -15397,6 +15554,47 @@ var SessionStore = class {
|
|
|
15397
15554
|
}
|
|
15398
15555
|
await this.#prune().catch(() => void 0);
|
|
15399
15556
|
}
|
|
15557
|
+
/** Hold this lease for the full lifetime of a writable session runtime. */
|
|
15558
|
+
async acquireLease(sessionId) {
|
|
15559
|
+
SessionIdSchema.parse(sessionId);
|
|
15560
|
+
await this.#prepareDirectory();
|
|
15561
|
+
const path = join21(this.#sessions, `.${sessionId}.lock`);
|
|
15562
|
+
const token = JSON.stringify({ pid: process.pid, nonce: randomUUID13(), createdAt: (/* @__PURE__ */ new Date()).toISOString() });
|
|
15563
|
+
for (let attempt = 0; attempt < 3; attempt += 1) {
|
|
15564
|
+
let handle;
|
|
15565
|
+
let created = false;
|
|
15566
|
+
try {
|
|
15567
|
+
handle = await open8(path, constants5.O_CREAT | constants5.O_EXCL | constants5.O_WRONLY, 384);
|
|
15568
|
+
created = true;
|
|
15569
|
+
await handle.writeFile(token, "utf8");
|
|
15570
|
+
await handle.sync();
|
|
15571
|
+
await handle.close();
|
|
15572
|
+
handle = void 0;
|
|
15573
|
+
let released = false;
|
|
15574
|
+
return {
|
|
15575
|
+
sessionId,
|
|
15576
|
+
release: async () => {
|
|
15577
|
+
if (released) return;
|
|
15578
|
+
try {
|
|
15579
|
+
if (await readFile16(path, "utf8") === token) await rm6(path, { force: true });
|
|
15580
|
+
} catch (error) {
|
|
15581
|
+
if (!isCode7(error, "ENOENT")) throw error;
|
|
15582
|
+
}
|
|
15583
|
+
released = true;
|
|
15584
|
+
}
|
|
15585
|
+
};
|
|
15586
|
+
} catch (error) {
|
|
15587
|
+
await handle?.close().catch(() => void 0);
|
|
15588
|
+
if (created) await rm6(path, { force: true }).catch(() => void 0);
|
|
15589
|
+
if (!isCode7(error, "EEXIST")) throw error;
|
|
15590
|
+
const owner2 = await reclaimStaleSessionLock(path);
|
|
15591
|
+
if (owner2 !== void 0) {
|
|
15592
|
+
throw new Error(`Session "${sessionId}" is already active in process ${owner2}. Close that session before resuming it here.`);
|
|
15593
|
+
}
|
|
15594
|
+
}
|
|
15595
|
+
}
|
|
15596
|
+
throw new Error(`Session "${sessionId}" could not acquire its writer lease because the lock kept changing`);
|
|
15597
|
+
}
|
|
15400
15598
|
async load(sessionId) {
|
|
15401
15599
|
if (sessionId === void 0) {
|
|
15402
15600
|
const [latest] = await this.list();
|
|
@@ -15494,29 +15692,37 @@ var SessionStore = class {
|
|
|
15494
15692
|
if (isCode7(error, "ENOENT")) return;
|
|
15495
15693
|
throw error;
|
|
15496
15694
|
}
|
|
15695
|
+
const owner2 = await reclaimStaleSessionLock(join21(this.#sessions, `.${sessionId}.lock`));
|
|
15696
|
+
if (owner2 !== void 0) throw new Error(`Session "${sessionId}" is active in process ${owner2} and cannot be deleted`);
|
|
15497
15697
|
const path = this.#path(sessionId);
|
|
15498
15698
|
try {
|
|
15499
15699
|
const metadata = await lstat9(path);
|
|
15500
15700
|
if (!metadata.isFile()) throw new Error("Session path is not a regular file");
|
|
15501
15701
|
await rm6(path);
|
|
15502
|
-
await rm6(
|
|
15702
|
+
await rm6(join21(this.#sessions, `${sessionId}.events.jsonl`), { force: true }).catch(() => void 0);
|
|
15503
15703
|
} catch (error) {
|
|
15504
15704
|
if (!isCode7(error, "ENOENT")) throw error;
|
|
15505
15705
|
}
|
|
15506
15706
|
}
|
|
15507
15707
|
#path(sessionId) {
|
|
15508
15708
|
SessionIdSchema.parse(sessionId);
|
|
15509
|
-
const path =
|
|
15709
|
+
const path = resolve19(this.#sessions, `${sessionId}.jsonl`);
|
|
15510
15710
|
if (!isWithin5(this.#sessions, path)) throw new Error("Invalid session id");
|
|
15511
15711
|
return path;
|
|
15512
15712
|
}
|
|
15513
15713
|
async #prune() {
|
|
15514
15714
|
const entries = await this.list();
|
|
15515
15715
|
if (entries.length <= this.#maxSessions) return;
|
|
15516
|
-
|
|
15517
|
-
for (const entry2 of
|
|
15716
|
+
let retained = 0;
|
|
15717
|
+
for (const entry2 of entries) {
|
|
15718
|
+
const owner2 = await reclaimStaleSessionLock(join21(this.#sessions, `.${entry2.sessionId}.lock`));
|
|
15719
|
+
if (owner2 !== void 0) continue;
|
|
15720
|
+
if (retained < this.#maxSessions) {
|
|
15721
|
+
retained += 1;
|
|
15722
|
+
continue;
|
|
15723
|
+
}
|
|
15518
15724
|
await rm6(this.#path(entry2.sessionId), { force: true }).catch(() => void 0);
|
|
15519
|
-
await rm6(
|
|
15725
|
+
await rm6(join21(this.#sessions, `${entry2.sessionId}.events.jsonl`), { force: true }).catch(() => void 0);
|
|
15520
15726
|
}
|
|
15521
15727
|
}
|
|
15522
15728
|
async #assertWorkspace(stored) {
|
|
@@ -15562,6 +15768,47 @@ function storedSessionVersion(raw) {
|
|
|
15562
15768
|
const candidate = first.__meta === true ? first.version : JSON.parse(trimmed).version;
|
|
15563
15769
|
return typeof candidate === "number" && Number.isInteger(candidate) ? candidate : 0;
|
|
15564
15770
|
}
|
|
15771
|
+
async function reclaimStaleSessionLock(path) {
|
|
15772
|
+
try {
|
|
15773
|
+
const token = await readFile16(path, "utf8");
|
|
15774
|
+
const metadata = await stat6(path);
|
|
15775
|
+
const owner2 = parseSessionLockOwner(token);
|
|
15776
|
+
if (owner2 !== void 0 && isProcessAlive(owner2)) return owner2;
|
|
15777
|
+
if (owner2 === void 0 && Date.now() - metadata.mtimeMs <= INVALID_SESSION_LOCK_STALE_MS) {
|
|
15778
|
+
throw new Error(`Session writer lock ${path} is incomplete or invalid; retry after the owning process exits`);
|
|
15779
|
+
}
|
|
15780
|
+
if (await readFile16(path, "utf8") !== token) return void 0;
|
|
15781
|
+
const stale = `${path}.stale-${process.pid}-${randomUUID13()}`;
|
|
15782
|
+
try {
|
|
15783
|
+
await rename6(path, stale);
|
|
15784
|
+
await rm6(stale, { force: true });
|
|
15785
|
+
} catch (error) {
|
|
15786
|
+
if (!isCode7(error, "ENOENT")) throw error;
|
|
15787
|
+
}
|
|
15788
|
+
return void 0;
|
|
15789
|
+
} catch (error) {
|
|
15790
|
+
if (isCode7(error, "ENOENT")) return void 0;
|
|
15791
|
+
throw error;
|
|
15792
|
+
}
|
|
15793
|
+
}
|
|
15794
|
+
function parseSessionLockOwner(token) {
|
|
15795
|
+
try {
|
|
15796
|
+
const value = JSON.parse(token);
|
|
15797
|
+
if (typeof value !== "object" || value === null || !("pid" in value)) return void 0;
|
|
15798
|
+
const pid = value.pid;
|
|
15799
|
+
return typeof pid === "number" && Number.isSafeInteger(pid) && pid > 0 ? pid : void 0;
|
|
15800
|
+
} catch {
|
|
15801
|
+
return void 0;
|
|
15802
|
+
}
|
|
15803
|
+
}
|
|
15804
|
+
function isProcessAlive(pid) {
|
|
15805
|
+
try {
|
|
15806
|
+
process.kill(pid, 0);
|
|
15807
|
+
return true;
|
|
15808
|
+
} catch (error) {
|
|
15809
|
+
return isCode7(error, "EPERM");
|
|
15810
|
+
}
|
|
15811
|
+
}
|
|
15565
15812
|
async function boundedRead(path, maxBytes) {
|
|
15566
15813
|
const metadata = await stat6(path);
|
|
15567
15814
|
if (!metadata.isFile()) throw new Error("Session path is not a regular file");
|
|
@@ -15708,8 +15955,8 @@ function isRecord5(value) {
|
|
|
15708
15955
|
return typeof value === "object" && value !== null && !Array.isArray(value);
|
|
15709
15956
|
}
|
|
15710
15957
|
async function assertNoSymlink3(root, target) {
|
|
15711
|
-
let cursor =
|
|
15712
|
-
const stop =
|
|
15958
|
+
let cursor = resolve19(target);
|
|
15959
|
+
const stop = resolve19(root);
|
|
15713
15960
|
if (!isWithin5(stop, cursor)) throw new Error("Session path escapes the workspace");
|
|
15714
15961
|
const chain = [];
|
|
15715
15962
|
while (cursor !== stop) {
|
|
@@ -15725,14 +15972,14 @@ async function assertNoSymlink3(root, target) {
|
|
|
15725
15972
|
}
|
|
15726
15973
|
}
|
|
15727
15974
|
function isWithin5(root, path) {
|
|
15728
|
-
const value = relative13(
|
|
15975
|
+
const value = relative13(resolve19(root), resolve19(path));
|
|
15729
15976
|
return value === "" || !value.startsWith(`..${sep13}`) && value !== ".." && !value.includes(":");
|
|
15730
15977
|
}
|
|
15731
15978
|
async function canonical2(path) {
|
|
15732
15979
|
try {
|
|
15733
|
-
return await realpath7(
|
|
15980
|
+
return await realpath7(resolve19(path));
|
|
15734
15981
|
} catch {
|
|
15735
|
-
return
|
|
15982
|
+
return resolve19(path);
|
|
15736
15983
|
}
|
|
15737
15984
|
}
|
|
15738
15985
|
function isCode7(error, code) {
|
|
@@ -15760,7 +16007,7 @@ function sanitize2(input, seen = /* @__PURE__ */ new WeakSet()) {
|
|
|
15760
16007
|
import { constants as constants6 } from "fs";
|
|
15761
16008
|
import { randomUUID as randomUUID14 } from "crypto";
|
|
15762
16009
|
import { lstat as lstat10, mkdir as mkdir15, open as open9, readdir as readdir7, realpath as realpath8, rename as rename7, rm as rm7 } from "fs/promises";
|
|
15763
|
-
import { isAbsolute as
|
|
16010
|
+
import { isAbsolute as isAbsolute9, join as join22, relative as relative14, resolve as resolve20, sep as sep14 } from "path";
|
|
15764
16011
|
import { z as z23 } from "zod";
|
|
15765
16012
|
var DateKeySchema = z23.string().regex(/^\d{4}-\d{2}-\d{2}$/);
|
|
15766
16013
|
var ReviewSchema = z23.object({
|
|
@@ -15814,8 +16061,8 @@ var ProjectSleepOrganizer = class {
|
|
|
15814
16061
|
#sessions;
|
|
15815
16062
|
#generate;
|
|
15816
16063
|
constructor(options) {
|
|
15817
|
-
this.#workspace =
|
|
15818
|
-
this.#sleepDirectory =
|
|
16064
|
+
this.#workspace = resolve20(options.workspace);
|
|
16065
|
+
this.#sleepDirectory = join22(this.#workspace, ".flavor", "sleep");
|
|
15819
16066
|
this.#sessions = options.sessions ?? new SessionStore({ workspace: this.#workspace });
|
|
15820
16067
|
this.#generate = options.generate;
|
|
15821
16068
|
}
|
|
@@ -15824,7 +16071,7 @@ var ProjectSleepOrganizer = class {
|
|
|
15824
16071
|
signal.throwIfAborted();
|
|
15825
16072
|
await this.#prepareDirectory();
|
|
15826
16073
|
if (await this.#hasReport(date)) return { status: "exists", date };
|
|
15827
|
-
const lockPath =
|
|
16074
|
+
const lockPath = join22(this.#sleepDirectory, `.${date}.lock`);
|
|
15828
16075
|
let lock;
|
|
15829
16076
|
try {
|
|
15830
16077
|
lock = await open9(lockPath, constants6.O_CREAT | constants6.O_EXCL | constants6.O_WRONLY, 384);
|
|
@@ -15859,7 +16106,7 @@ Previous output was invalid. Repair it and return strict JSON only.`;
|
|
|
15859
16106
|
}
|
|
15860
16107
|
if (review === void 0) throw lastError;
|
|
15861
16108
|
const filename2 = `${date}-${filenameSummary(review.title)}.md`;
|
|
15862
|
-
const path =
|
|
16109
|
+
const path = join22(this.#sleepDirectory, filename2);
|
|
15863
16110
|
const markdown = renderSleepReport(date, review, documents);
|
|
15864
16111
|
await this.#atomicWrite(path, markdown);
|
|
15865
16112
|
return { status: "written", date, path, sessionCount: documents.length };
|
|
@@ -15901,7 +16148,7 @@ Previous output was invalid. Repair it and return strict JSON only.`;
|
|
|
15901
16148
|
});
|
|
15902
16149
|
}
|
|
15903
16150
|
async #prepareDirectory() {
|
|
15904
|
-
const flavorDirectory =
|
|
16151
|
+
const flavorDirectory = join22(this.#workspace, ".flavor");
|
|
15905
16152
|
await assertNotSymlink(flavorDirectory);
|
|
15906
16153
|
await mkdir15(flavorDirectory, { recursive: true, mode: 448 });
|
|
15907
16154
|
await assertNotSymlink(this.#sleepDirectory);
|
|
@@ -15915,7 +16162,7 @@ Previous output was invalid. Repair it and return strict JSON only.`;
|
|
|
15915
16162
|
return (await readdir7(this.#sleepDirectory, { withFileTypes: true })).some((entry2) => entry2.isFile() && entry2.name.startsWith(prefix) && entry2.name.endsWith(".md"));
|
|
15916
16163
|
}
|
|
15917
16164
|
async #atomicWrite(path, content) {
|
|
15918
|
-
const temporary =
|
|
16165
|
+
const temporary = join22(this.#sleepDirectory, `.${randomUUID14()}.tmp`);
|
|
15919
16166
|
let handle;
|
|
15920
16167
|
try {
|
|
15921
16168
|
handle = await open9(temporary, constants6.O_CREAT | constants6.O_EXCL | constants6.O_WRONLY, 384);
|
|
@@ -16239,8 +16486,8 @@ async function assertNotSymlink(path) {
|
|
|
16239
16486
|
function isWithin6(root, candidate) {
|
|
16240
16487
|
const path = relative14(root, candidate);
|
|
16241
16488
|
if (path === "") return true;
|
|
16242
|
-
if (path === ".." || path.startsWith(`..${sep14}`) ||
|
|
16243
|
-
return process.platform !== "win32" ||
|
|
16489
|
+
if (path === ".." || path.startsWith(`..${sep14}`) || isAbsolute9(path)) return false;
|
|
16490
|
+
return process.platform !== "win32" || resolve20(root, path).toLowerCase().startsWith(`${resolve20(root).toLowerCase()}${sep14}`);
|
|
16244
16491
|
}
|
|
16245
16492
|
function hasCode(error, code) {
|
|
16246
16493
|
return typeof error === "object" && error !== null && "code" in error && error.code === code;
|
|
@@ -16290,7 +16537,7 @@ function computeSessionStats(documents) {
|
|
|
16290
16537
|
// src/tools/files.ts
|
|
16291
16538
|
import { constants as constants7 } from "fs";
|
|
16292
16539
|
import { mkdir as mkdir16, open as open10, readFile as readFile17, realpath as realpath9, rename as rename8, rm as rm8, stat as stat7 } from "fs/promises";
|
|
16293
|
-
import { dirname as dirname16, isAbsolute as
|
|
16540
|
+
import { dirname as dirname16, isAbsolute as isAbsolute10, relative as relative15, resolve as resolve21, sep as sep15 } from "path";
|
|
16294
16541
|
import { randomUUID as randomUUID15 } from "crypto";
|
|
16295
16542
|
import { z as z24 } from "zod";
|
|
16296
16543
|
|
|
@@ -16607,9 +16854,9 @@ function createApplyPatchTool(workspace, options = {}) {
|
|
|
16607
16854
|
};
|
|
16608
16855
|
}
|
|
16609
16856
|
function createPathGuard(workspace) {
|
|
16610
|
-
const root =
|
|
16857
|
+
const root = resolve21(workspace);
|
|
16611
16858
|
const lexical = (input) => {
|
|
16612
|
-
const candidate =
|
|
16859
|
+
const candidate = resolve21(root, input);
|
|
16613
16860
|
if (!within2(root, candidate)) throw new Error("Path is outside the workspace");
|
|
16614
16861
|
return candidate;
|
|
16615
16862
|
};
|
|
@@ -16779,7 +17026,7 @@ async function readBounded2(path, maxBytes, signal, openFile2) {
|
|
|
16779
17026
|
}
|
|
16780
17027
|
function within2(root, candidate) {
|
|
16781
17028
|
const delta = relative15(root, candidate);
|
|
16782
|
-
return delta === "" || !delta.startsWith(`..${sep15}`) && delta !== ".." && !
|
|
17029
|
+
return delta === "" || !delta.startsWith(`..${sep15}`) && delta !== ".." && !isAbsolute10(delta);
|
|
16783
17030
|
}
|
|
16784
17031
|
function forceFlag(value) {
|
|
16785
17032
|
return value === true || value === "true";
|
|
@@ -17310,9 +17557,9 @@ function charset(contentType) {
|
|
|
17310
17557
|
|
|
17311
17558
|
// src/tools/lsp.ts
|
|
17312
17559
|
import { spawn as spawn7 } from "child_process";
|
|
17313
|
-
import { existsSync as
|
|
17560
|
+
import { existsSync as existsSync4 } from "fs";
|
|
17314
17561
|
import { access, constants as constants8, readFile as readFile18 } from "fs/promises";
|
|
17315
|
-
import { basename as basename4, dirname as dirname17, extname, isAbsolute as
|
|
17562
|
+
import { basename as basename4, dirname as dirname17, extname as extname2, isAbsolute as isAbsolute11, relative as relative16, resolve as resolve22, sep as sep16 } from "path";
|
|
17316
17563
|
import { fileURLToPath as fileURLToPath3, pathToFileURL as pathToFileURL2 } from "url";
|
|
17317
17564
|
import { z as z26 } from "zod";
|
|
17318
17565
|
var LSP_REQUEST_TIMEOUT_MS = 15e3;
|
|
@@ -17326,10 +17573,12 @@ var LspConnection = class {
|
|
|
17326
17573
|
#disposed = false;
|
|
17327
17574
|
#initError;
|
|
17328
17575
|
constructor(command, args, cwd) {
|
|
17329
|
-
|
|
17576
|
+
const invocation = prepareSpawnInvocation(command, args, { cwd });
|
|
17577
|
+
this.#process = spawn7(invocation.command, invocation.args, {
|
|
17330
17578
|
cwd,
|
|
17331
17579
|
stdio: ["pipe", "pipe", "pipe"],
|
|
17332
|
-
windowsHide: true
|
|
17580
|
+
windowsHide: true,
|
|
17581
|
+
...invocation.windowsVerbatimArguments === void 0 ? {} : { windowsVerbatimArguments: invocation.windowsVerbatimArguments }
|
|
17333
17582
|
});
|
|
17334
17583
|
this.#process.on("error", (err) => {
|
|
17335
17584
|
this.#initError = new Error(`LSP server "${command}" failed to start: ${err.message}`);
|
|
@@ -17411,8 +17660,8 @@ var LspConnection = class {
|
|
|
17411
17660
|
const id = this.#nextId++;
|
|
17412
17661
|
const request = { jsonrpc: "2.0", id, method, params };
|
|
17413
17662
|
const timeout = AbortSignal.timeout(LSP_REQUEST_TIMEOUT_MS);
|
|
17414
|
-
const promise = new Promise((
|
|
17415
|
-
this.#pending.set(id, { resolve:
|
|
17663
|
+
const promise = new Promise((resolve30, reject) => {
|
|
17664
|
+
this.#pending.set(id, { resolve: resolve30, reject });
|
|
17416
17665
|
timeout.addEventListener("abort", () => {
|
|
17417
17666
|
if (this.#pending.has(id)) {
|
|
17418
17667
|
this.#pending.delete(id);
|
|
@@ -17528,7 +17777,7 @@ var LspBuffer = class {
|
|
|
17528
17777
|
}
|
|
17529
17778
|
}
|
|
17530
17779
|
};
|
|
17531
|
-
var TYPESCRIPT_CLI =
|
|
17780
|
+
var TYPESCRIPT_CLI = resolve22(
|
|
17532
17781
|
dirname17(fileURLToPath3(import.meta.resolve("typescript/package.json"))),
|
|
17533
17782
|
"lib",
|
|
17534
17783
|
"tsc.js"
|
|
@@ -17570,7 +17819,7 @@ var RealLspManager = class {
|
|
|
17570
17819
|
#serverConfigs;
|
|
17571
17820
|
#onStatus;
|
|
17572
17821
|
constructor(options) {
|
|
17573
|
-
this.#workspace =
|
|
17822
|
+
this.#workspace = resolve22(options.workspace);
|
|
17574
17823
|
this.#serverConfigs = options.serverConfigs ?? KNOWN_SERVERS;
|
|
17575
17824
|
this.#onStatus = options.onStatus;
|
|
17576
17825
|
}
|
|
@@ -17619,7 +17868,7 @@ var RealLspManager = class {
|
|
|
17619
17868
|
if (pending !== void 0) return pending;
|
|
17620
17869
|
const config = this.#serverConfigs.find((c) => c.language === language);
|
|
17621
17870
|
if (config === void 0) return void 0;
|
|
17622
|
-
const hasRoot = config.rootFiles.some((file) =>
|
|
17871
|
+
const hasRoot = config.rootFiles.some((file) => existsSync4(resolve22(this.#workspace, file)));
|
|
17623
17872
|
if (!hasRoot) return void 0;
|
|
17624
17873
|
const startPromise = this.#startServer(config);
|
|
17625
17874
|
this.#pendingStarts.set(language, startPromise);
|
|
@@ -17662,10 +17911,10 @@ var LspDiagnosticsInput = z26.object({
|
|
|
17662
17911
|
file: z26.string().min(1)
|
|
17663
17912
|
});
|
|
17664
17913
|
function createLspTools(workspace, options = {}) {
|
|
17665
|
-
const root =
|
|
17914
|
+
const root = resolve22(workspace);
|
|
17666
17915
|
const manager = options.manager ?? new RealLspManager({ workspace: root, onStatus: options.onStatus });
|
|
17667
17916
|
const guard = (input) => {
|
|
17668
|
-
const candidate =
|
|
17917
|
+
const candidate = resolve22(root, input);
|
|
17669
17918
|
if (!isWithin7(root, candidate)) throw new Error("Path is outside the workspace");
|
|
17670
17919
|
return candidate;
|
|
17671
17920
|
};
|
|
@@ -17729,7 +17978,7 @@ function createLspTools(workspace, options = {}) {
|
|
|
17729
17978
|
return [findRefs, hover, diagnostics];
|
|
17730
17979
|
}
|
|
17731
17980
|
function languageIdForPath(filePath) {
|
|
17732
|
-
switch (
|
|
17981
|
+
switch (extname2(filePath).toLowerCase()) {
|
|
17733
17982
|
case ".tsx":
|
|
17734
17983
|
return "typescriptreact";
|
|
17735
17984
|
case ".mts":
|
|
@@ -17742,7 +17991,7 @@ function languageIdForPath(filePath) {
|
|
|
17742
17991
|
}
|
|
17743
17992
|
function isWithin7(root, candidate) {
|
|
17744
17993
|
const delta = relative16(root, candidate);
|
|
17745
|
-
return delta === "" || !delta.startsWith(`..${sep16}`) && delta !== ".." && !
|
|
17994
|
+
return delta === "" || !delta.startsWith(`..${sep16}`) && delta !== ".." && !isAbsolute11(delta);
|
|
17746
17995
|
}
|
|
17747
17996
|
function uriToPath(uri) {
|
|
17748
17997
|
try {
|
|
@@ -18130,12 +18379,12 @@ ${explanation}` : "";
|
|
|
18130
18379
|
// src/desktop/astgraph-service.ts
|
|
18131
18380
|
import { stat as stat8 } from "fs/promises";
|
|
18132
18381
|
import { createRequire as nodeCreateRequire } from "module";
|
|
18133
|
-
import { join as
|
|
18382
|
+
import { join as join23, resolve as resolve23 } from "path";
|
|
18134
18383
|
var FIELDS = "id, kind, name, qualified_name, file_path, language, start_line, end_line, signature";
|
|
18135
18384
|
var DesktopAstGraphService = class {
|
|
18136
18385
|
#path;
|
|
18137
18386
|
constructor(workspace) {
|
|
18138
|
-
this.#path =
|
|
18387
|
+
this.#path = join23(resolve23(workspace), ".flavor", "astgraph", "index.db");
|
|
18139
18388
|
}
|
|
18140
18389
|
async status() {
|
|
18141
18390
|
if ((await stat8(this.#path).catch(() => void 0))?.isFile() !== true) {
|
|
@@ -18297,7 +18546,7 @@ var QuestionBridge = class {
|
|
|
18297
18546
|
}
|
|
18298
18547
|
}
|
|
18299
18548
|
#askLocally(questions, signal) {
|
|
18300
|
-
return new Promise((
|
|
18549
|
+
return new Promise((resolve30, reject) => {
|
|
18301
18550
|
const onAbort = () => {
|
|
18302
18551
|
this.#pending = void 0;
|
|
18303
18552
|
this.#active = false;
|
|
@@ -18311,7 +18560,7 @@ var QuestionBridge = class {
|
|
|
18311
18560
|
this.#pending = void 0;
|
|
18312
18561
|
this.#active = false;
|
|
18313
18562
|
this.#onChange?.();
|
|
18314
|
-
|
|
18563
|
+
resolve30(answers);
|
|
18315
18564
|
},
|
|
18316
18565
|
reject: (error) => {
|
|
18317
18566
|
signal.removeEventListener("abort", onAbort);
|
|
@@ -18404,7 +18653,7 @@ function createTodoWriteTool() {
|
|
|
18404
18653
|
// src/tools/managed.ts
|
|
18405
18654
|
import { randomUUID as randomUUID16 } from "crypto";
|
|
18406
18655
|
import { link as link2, lstat as lstat11, mkdir as mkdir17, readFile as readFile19, readdir as readdir8, unlink as unlink5, writeFile as writeFile10 } from "fs/promises";
|
|
18407
|
-
import { dirname as dirname18, join as
|
|
18656
|
+
import { dirname as dirname18, join as join24, resolve as resolve24 } from "path";
|
|
18408
18657
|
import { z as z31 } from "zod";
|
|
18409
18658
|
var MANAGED_TOOL_VERSION = 1;
|
|
18410
18659
|
var MAX_RECORD_BYTES = 256 * 1024;
|
|
@@ -18446,9 +18695,9 @@ var ManagedToolStore = class {
|
|
|
18446
18695
|
#entries = /* @__PURE__ */ new Map();
|
|
18447
18696
|
#diagnostics = [];
|
|
18448
18697
|
constructor(options) {
|
|
18449
|
-
this.#workspace =
|
|
18450
|
-
this.#projectRoot =
|
|
18451
|
-
this.#globalRoot =
|
|
18698
|
+
this.#workspace = resolve24(options.workspace);
|
|
18699
|
+
this.#projectRoot = join24(this.#workspace, ".flavor", "tools");
|
|
18700
|
+
this.#globalRoot = join24(resolve24(options.home), ".flavor-code", "tools");
|
|
18452
18701
|
this.#now = options.now ?? (() => (/* @__PURE__ */ new Date()).toISOString());
|
|
18453
18702
|
}
|
|
18454
18703
|
get diagnostics() {
|
|
@@ -18495,7 +18744,7 @@ var ManagedToolStore = class {
|
|
|
18495
18744
|
version: MANAGED_TOOL_VERSION,
|
|
18496
18745
|
createdAt: this.#now()
|
|
18497
18746
|
});
|
|
18498
|
-
const path =
|
|
18747
|
+
const path = join24(this.directory(input.scope), filename(input.name));
|
|
18499
18748
|
const definition = compileDefinition(record, input.scope, this.#workspace);
|
|
18500
18749
|
await writeExclusive(path, `${JSON.stringify(record, void 0, 2)}
|
|
18501
18750
|
`);
|
|
@@ -18520,7 +18769,7 @@ var ManagedToolStore = class {
|
|
|
18520
18769
|
if (!parsed.success) return [];
|
|
18521
18770
|
const matches = this.#matching(parsed.data.name).filter((entry2) => parsed.data.scope === void 0 || entry2.scope === parsed.data.scope);
|
|
18522
18771
|
if (matches.length > 0) return matches.map((entry2) => entry2.path);
|
|
18523
|
-
return [
|
|
18772
|
+
return [join24(this.directory(parsed.data.scope ?? "project"), filename(parsed.data.name))];
|
|
18524
18773
|
}
|
|
18525
18774
|
async #loadDirectory(scope2) {
|
|
18526
18775
|
const root = this.directory(scope2);
|
|
@@ -18533,7 +18782,7 @@ var ManagedToolStore = class {
|
|
|
18533
18782
|
return;
|
|
18534
18783
|
}
|
|
18535
18784
|
for (const name of names) {
|
|
18536
|
-
const path =
|
|
18785
|
+
const path = join24(root, name);
|
|
18537
18786
|
try {
|
|
18538
18787
|
const info = await lstat11(path);
|
|
18539
18788
|
if (!info.isFile() || info.isSymbolicLink()) throw new Error("Managed tool record must be a regular file");
|
|
@@ -19452,7 +19701,7 @@ ${total}`;
|
|
|
19452
19701
|
// src/memory/store.ts
|
|
19453
19702
|
import { createHash as createHash16 } from "crypto";
|
|
19454
19703
|
import { readFile as readFile20, rm as rm9 } from "fs/promises";
|
|
19455
|
-
import { resolve as
|
|
19704
|
+
import { resolve as resolve25, join as join25, relative as relative17, sep as sep17 } from "path";
|
|
19456
19705
|
|
|
19457
19706
|
// src/memory/similarity.ts
|
|
19458
19707
|
function normalizeForSimilarity(value) {
|
|
@@ -19554,10 +19803,10 @@ var MemoryStore = class {
|
|
|
19554
19803
|
constructor(options) {
|
|
19555
19804
|
if (!Number.isSafeInteger(options.maxEntries) || options.maxEntries < 1) throw new Error("maxEntries must be an integer of at least 1");
|
|
19556
19805
|
if (!Number.isSafeInteger(options.maxEntryChars) || options.maxEntryChars < 1) throw new Error("maxEntryChars must be an integer of at least 1");
|
|
19557
|
-
this.workspace =
|
|
19558
|
-
this.#memoryRoot =
|
|
19559
|
-
this.path =
|
|
19560
|
-
this.#behaviorPath =
|
|
19806
|
+
this.workspace = resolve25(options.workspace);
|
|
19807
|
+
this.#memoryRoot = join25(this.workspace, ".flavor", "memory");
|
|
19808
|
+
this.path = join25(this.#memoryRoot, "MEMORY.md");
|
|
19809
|
+
this.#behaviorPath = join25(this.#memoryRoot, "behavior.json");
|
|
19561
19810
|
this.#maxEntries = options.maxEntries;
|
|
19562
19811
|
this.#maxEntryChars = options.maxEntryChars;
|
|
19563
19812
|
}
|
|
@@ -19780,7 +20029,7 @@ var MemoryStore = class {
|
|
|
19780
20029
|
}
|
|
19781
20030
|
#resolveContentPath(contentPath) {
|
|
19782
20031
|
if (!/^tasks\/[A-Za-z0-9][A-Za-z0-9._-]{0,127}\.md$/.test(contentPath)) throw new Error("Invalid memory content path");
|
|
19783
|
-
const path =
|
|
20032
|
+
const path = resolve25(this.#memoryRoot, ...contentPath.split("/"));
|
|
19784
20033
|
const rel = relative17(this.#memoryRoot, path);
|
|
19785
20034
|
if (rel === ".." || rel.startsWith(`..${sep17}`)) throw new Error("Memory content path escapes the memory directory");
|
|
19786
20035
|
return path;
|
|
@@ -20252,7 +20501,7 @@ function createExecutionEnvironment(workspace, config) {
|
|
|
20252
20501
|
|
|
20253
20502
|
// src/ide/client.ts
|
|
20254
20503
|
import { readFile as readFile21, readdir as readdir9, stat as stat9 } from "fs/promises";
|
|
20255
|
-
import { basename as basename5, join as
|
|
20504
|
+
import { basename as basename5, join as join26, relative as relative18, resolve as resolve26, sep as sep18 } from "path";
|
|
20256
20505
|
var FlavorIdeClient = class {
|
|
20257
20506
|
#workspace;
|
|
20258
20507
|
#home;
|
|
@@ -20263,8 +20512,8 @@ var FlavorIdeClient = class {
|
|
|
20263
20512
|
#sessionId;
|
|
20264
20513
|
#eventTail = Promise.resolve();
|
|
20265
20514
|
constructor(options) {
|
|
20266
|
-
this.#workspace =
|
|
20267
|
-
this.#home =
|
|
20515
|
+
this.#workspace = resolve26(options.workspace);
|
|
20516
|
+
this.#home = resolve26(options.home);
|
|
20268
20517
|
this.#environment = options.environment ?? process.env;
|
|
20269
20518
|
this.#fetch = options.fetch ?? fetch;
|
|
20270
20519
|
}
|
|
@@ -20348,7 +20597,7 @@ var FlavorIdeClient = class {
|
|
|
20348
20597
|
};
|
|
20349
20598
|
if (await this.#healthy(configured)) return configured;
|
|
20350
20599
|
}
|
|
20351
|
-
const directory =
|
|
20600
|
+
const directory = join26(this.#home, ".flavor-code", "ide");
|
|
20352
20601
|
let entries;
|
|
20353
20602
|
try {
|
|
20354
20603
|
entries = (await readdir9(directory)).filter((name) => name.endsWith(".lock"));
|
|
@@ -20356,7 +20605,7 @@ var FlavorIdeClient = class {
|
|
|
20356
20605
|
return void 0;
|
|
20357
20606
|
}
|
|
20358
20607
|
const candidates = (await Promise.all(entries.map(async (name) => {
|
|
20359
|
-
const path =
|
|
20608
|
+
const path = join26(directory, name);
|
|
20360
20609
|
try {
|
|
20361
20610
|
const [contents, info] = await Promise.all([readFile21(path, "utf8"), stat9(path)]);
|
|
20362
20611
|
const lock = parseLockfile(contents);
|
|
@@ -20462,8 +20711,8 @@ function parsePort(value) {
|
|
|
20462
20711
|
return Number.isInteger(port) && port > 0 && port <= 65535 ? port : void 0;
|
|
20463
20712
|
}
|
|
20464
20713
|
function isWithin8(parent, child) {
|
|
20465
|
-
const base = normalize(
|
|
20466
|
-
const target = normalize(
|
|
20714
|
+
const base = normalize(resolve26(parent));
|
|
20715
|
+
const target = normalize(resolve26(child));
|
|
20467
20716
|
return target === base || target.startsWith(`${base}${sep18}`);
|
|
20468
20717
|
}
|
|
20469
20718
|
function normalize(path) {
|
|
@@ -20493,8 +20742,8 @@ var JobRegistry = class {
|
|
|
20493
20742
|
const id = `job-${randomUUID17().slice(0, 12)}`;
|
|
20494
20743
|
const now = (/* @__PURE__ */ new Date()).toISOString();
|
|
20495
20744
|
let settle;
|
|
20496
|
-
const done = new Promise((
|
|
20497
|
-
settle =
|
|
20745
|
+
const done = new Promise((resolve30) => {
|
|
20746
|
+
settle = resolve30;
|
|
20498
20747
|
});
|
|
20499
20748
|
const record = {
|
|
20500
20749
|
id,
|
|
@@ -20618,7 +20867,7 @@ function snapshot2(job) {
|
|
|
20618
20867
|
// src/terminal/service.ts
|
|
20619
20868
|
import { randomUUID as randomUUID18 } from "crypto";
|
|
20620
20869
|
import { createRequire as nodeCreateRequire2 } from "module";
|
|
20621
|
-
import { isAbsolute as
|
|
20870
|
+
import { isAbsolute as isAbsolute12, relative as relative19, resolve as resolve27, sep as sep19 } from "path";
|
|
20622
20871
|
var TerminalService = class {
|
|
20623
20872
|
#root;
|
|
20624
20873
|
#factory;
|
|
@@ -20626,7 +20875,7 @@ var TerminalService = class {
|
|
|
20626
20875
|
#maxOutputChars;
|
|
20627
20876
|
#sessions = /* @__PURE__ */ new Map();
|
|
20628
20877
|
constructor(workspace, options = {}) {
|
|
20629
|
-
this.#root =
|
|
20878
|
+
this.#root = resolve27(workspace);
|
|
20630
20879
|
this.#factory = options.factory ?? defaultFactory;
|
|
20631
20880
|
this.#jobs = options.jobs;
|
|
20632
20881
|
this.#maxOutputChars = options.maxOutputChars ?? 2e5;
|
|
@@ -20744,15 +20993,15 @@ function cleanEnvironment(environment) {
|
|
|
20744
20993
|
return Object.fromEntries(Object.entries(environment).filter((entry2) => entry2[1] !== void 0));
|
|
20745
20994
|
}
|
|
20746
20995
|
function workspacePath(root, input) {
|
|
20747
|
-
const path =
|
|
20996
|
+
const path = resolve27(root, input);
|
|
20748
20997
|
const delta = relative19(root, path);
|
|
20749
|
-
if (delta === ".." || delta.startsWith(`..${sep19}`) ||
|
|
20998
|
+
if (delta === ".." || delta.startsWith(`..${sep19}`) || isAbsolute12(delta)) throw new Error("Terminal cwd is outside the workspace");
|
|
20750
20999
|
return path;
|
|
20751
21000
|
}
|
|
20752
21001
|
|
|
20753
21002
|
// src/tools/terminal.ts
|
|
20754
21003
|
import { z as z33 } from "zod";
|
|
20755
|
-
import { resolve as
|
|
21004
|
+
import { resolve as resolve28 } from "path";
|
|
20756
21005
|
var Id = z33.string().regex(/^term-[a-f0-9-]+$/);
|
|
20757
21006
|
var OpenInput = z33.object({ cwd: z33.string().optional(), shell: z33.string().optional(), args: z33.array(z33.string()).optional(), columns: z33.coerce.number().int().min(10).max(500).optional(), rows: z33.coerce.number().int().min(2).max(300).optional() });
|
|
20758
21007
|
var OptionalBoolean = z33.union([z33.boolean(), z33.string().refine((value) => value === "true" || value === "false")]);
|
|
@@ -20766,8 +21015,8 @@ function createTerminalTools(terminals, workspace = process.cwd()) {
|
|
|
20766
21015
|
name: "TerminalOpen",
|
|
20767
21016
|
description: "Open a persistent interactive pseudo-terminal in the workspace",
|
|
20768
21017
|
inputSchema: OpenInput,
|
|
20769
|
-
paths: (input) => [
|
|
20770
|
-
permissions: (input) => ({ paths: [
|
|
21018
|
+
paths: (input) => [resolve28(workspace, input.cwd ?? ".")],
|
|
21019
|
+
permissions: (input) => ({ paths: [resolve28(workspace, input.cwd ?? ".")], command: input.shell ?? "system-shell", args: input.args ?? [], cwd: resolve28(workspace, input.cwd ?? ".") }),
|
|
20771
21020
|
execute: async (input, _signal, context) => terminals.open({
|
|
20772
21021
|
owner: owner(context),
|
|
20773
21022
|
...input.cwd === void 0 ? {} : { cwd: input.cwd },
|
|
@@ -20993,8 +21242,8 @@ var PalClient = class {
|
|
|
20993
21242
|
let disconnectTimer;
|
|
20994
21243
|
await Promise.race([
|
|
20995
21244
|
this.request({ version: 1, type: "disconnect", requestId: randomUUID19() }).catch(() => void 0),
|
|
20996
|
-
new Promise((
|
|
20997
|
-
disconnectTimer = setTimeout(
|
|
21245
|
+
new Promise((resolve30) => {
|
|
21246
|
+
disconnectTimer = setTimeout(resolve30, Math.min(this.requestTimeoutMs, 250));
|
|
20998
21247
|
})
|
|
20999
21248
|
]);
|
|
21000
21249
|
if (disconnectTimer !== void 0) clearTimeout(disconnectTimer);
|
|
@@ -21040,7 +21289,7 @@ var PalClient = class {
|
|
|
21040
21289
|
return presence;
|
|
21041
21290
|
}
|
|
21042
21291
|
openSocket() {
|
|
21043
|
-
return new Promise((
|
|
21292
|
+
return new Promise((resolve30, reject) => {
|
|
21044
21293
|
let settled = false;
|
|
21045
21294
|
const rejectConnection = (error) => {
|
|
21046
21295
|
if (settled) return;
|
|
@@ -21063,7 +21312,7 @@ var PalClient = class {
|
|
|
21063
21312
|
settled = true;
|
|
21064
21313
|
if (this.connectingReject === rejectConnection) this.connectingReject = void 0;
|
|
21065
21314
|
socket.off("error", onInitialError);
|
|
21066
|
-
|
|
21315
|
+
resolve30();
|
|
21067
21316
|
};
|
|
21068
21317
|
const onInitialError = (error) => {
|
|
21069
21318
|
socket.off("connect", onConnect);
|
|
@@ -21094,12 +21343,12 @@ var PalClient = class {
|
|
|
21094
21343
|
if (this.closed || socket === void 0 || socket.destroyed || !socket.writable) {
|
|
21095
21344
|
return Promise.reject(new Error(this.closed ? "Pal client is closed" : "Pal client is not connected"));
|
|
21096
21345
|
}
|
|
21097
|
-
return new Promise((
|
|
21346
|
+
return new Promise((resolve30, reject) => {
|
|
21098
21347
|
const timer = setTimeout(() => {
|
|
21099
21348
|
this.pending.delete(request.requestId);
|
|
21100
21349
|
reject(new Error(`Pal request '${request.type}' timed out`));
|
|
21101
21350
|
}, this.requestTimeoutMs);
|
|
21102
|
-
this.pending.set(request.requestId, { resolve:
|
|
21351
|
+
this.pending.set(request.requestId, { resolve: resolve30, reject, timer });
|
|
21103
21352
|
let frame;
|
|
21104
21353
|
try {
|
|
21105
21354
|
frame = encodeControlFrame(BrokerRequestSchema.parse(request));
|
|
@@ -21503,8 +21752,8 @@ var ApprovalBridge = class {
|
|
|
21503
21752
|
}
|
|
21504
21753
|
};
|
|
21505
21754
|
async function createProductionRuntime(options) {
|
|
21506
|
-
const workspace =
|
|
21507
|
-
const home =
|
|
21755
|
+
const workspace = resolve29(options.workspace ?? process.cwd());
|
|
21756
|
+
const home = resolve29(options.home ?? homedir2());
|
|
21508
21757
|
const environment = options.environment ?? process.env;
|
|
21509
21758
|
const ide = new FlavorIdeClient({ workspace, home, environment });
|
|
21510
21759
|
const ideReady = ide.initialize();
|
|
@@ -21747,8 +21996,8 @@ async function createProductionRuntime(options) {
|
|
|
21747
21996
|
const registeredProviders = registration.registered;
|
|
21748
21997
|
let effectiveLlm = registration.effectiveLlm;
|
|
21749
21998
|
const pluginHost = new PluginHost({
|
|
21750
|
-
globalPluginDirs: [
|
|
21751
|
-
projectPluginDirs: [
|
|
21999
|
+
globalPluginDirs: [join27(home, ".flavor-code", "plugins")],
|
|
22000
|
+
projectPluginDirs: [join27(workspace, ".flavor", "plugins")],
|
|
21752
22001
|
config,
|
|
21753
22002
|
// Existing project plugins (including astgraph and superharness) use Node.js
|
|
21754
22003
|
// built-ins. The Worker/vm sandbox deliberately blocks those imports, so it
|
|
@@ -21823,6 +22072,7 @@ async function createProductionRuntime(options) {
|
|
|
21823
22072
|
let collaborationEventPump;
|
|
21824
22073
|
let collaborationEventsOpen = true;
|
|
21825
22074
|
let islandControlServer;
|
|
22075
|
+
let sessionLease;
|
|
21826
22076
|
const pumpCollaborationEvents = () => {
|
|
21827
22077
|
if (collaborationEventPump !== void 0 || collaborationSession === void 0 || deliverCollaborationEvent === void 0) return;
|
|
21828
22078
|
collaborationEventPump = (async () => {
|
|
@@ -21856,7 +22106,7 @@ async function createProductionRuntime(options) {
|
|
|
21856
22106
|
const address = palSocketAddress({
|
|
21857
22107
|
platform: process.platform,
|
|
21858
22108
|
userScope: home,
|
|
21859
|
-
runtimeDir:
|
|
22109
|
+
runtimeDir: join27(tmpdir4(), `flavor-code-pals-${createHash18("sha256").update(home).digest("hex").slice(0, 16)}`)
|
|
21860
22110
|
});
|
|
21861
22111
|
const baseCollaborationClient = collaboration.client ?? collaboration.createClient?.({
|
|
21862
22112
|
instanceId: collaboration.instanceId,
|
|
@@ -21967,8 +22217,8 @@ async function createProductionRuntime(options) {
|
|
|
21967
22217
|
diagnostics.push(`MCP servers could not start: ${message(error)}`);
|
|
21968
22218
|
});
|
|
21969
22219
|
const skills = new SkillRegistry({
|
|
21970
|
-
globalRoots: [
|
|
21971
|
-
projectRoots: [
|
|
22220
|
+
globalRoots: [join27(home, ".flavor-code", "skills")],
|
|
22221
|
+
projectRoots: [join27(workspace, ".flavor", "skills"), ...pluginSkillRoots],
|
|
21972
22222
|
authorizeResource: async () => true,
|
|
21973
22223
|
disabledNames: config.skills.disabled
|
|
21974
22224
|
});
|
|
@@ -21976,7 +22226,7 @@ async function createProductionRuntime(options) {
|
|
|
21976
22226
|
skillsReady.catch(() => void 0);
|
|
21977
22227
|
tools.push(createSkillTool(skills), createSkillResourceTool(skills));
|
|
21978
22228
|
if (options.extraTools !== void 0) tools.push(...options.extraTools);
|
|
21979
|
-
const flavor = await optionalText2(
|
|
22229
|
+
const flavor = await optionalText2(join27(workspace, "FLAVOR.md"));
|
|
21980
22230
|
const instructionBaseline = await workspaceInstructions.baseline();
|
|
21981
22231
|
let memoryContext;
|
|
21982
22232
|
if (memoryStore !== void 0) {
|
|
@@ -22001,6 +22251,7 @@ async function createProductionRuntime(options) {
|
|
|
22001
22251
|
const subagentStartedAt = {};
|
|
22002
22252
|
const subagentElapsedMs = {};
|
|
22003
22253
|
let sessionId = recovered?.sessionId ?? `session-${(/* @__PURE__ */ new Date()).toISOString().replace(/[^0-9]/g, "").slice(0, 17)}-${randomUUID20().slice(0, 8)}`;
|
|
22254
|
+
sessionLease = await sessionStore.acquireLease(sessionId);
|
|
22004
22255
|
hookSessionId = sessionId;
|
|
22005
22256
|
let harnessJournal = new HarnessJournal({ workspace, sessionId });
|
|
22006
22257
|
const harnessRecovery = harnessJournal.recover();
|
|
@@ -22318,7 +22569,7 @@ async function createProductionRuntime(options) {
|
|
|
22318
22569
|
maxEntryChars: config.memory.maxEntryChars,
|
|
22319
22570
|
scoreThreshold: config.memory.scoreThreshold,
|
|
22320
22571
|
autoStoreThreshold: config.memory.autoStoreThreshold,
|
|
22321
|
-
maxCandidates:
|
|
22572
|
+
maxCandidates: config.memory.maxCandidatesPerTask,
|
|
22322
22573
|
...config.language === void 0 ? {} : { language: config.language },
|
|
22323
22574
|
generate: (prompt, signal) => generateMemoryExtraction(registry, childModel, prompt, signal)
|
|
22324
22575
|
}) : void 0;
|
|
@@ -22557,7 +22808,7 @@ async function createProductionRuntime(options) {
|
|
|
22557
22808
|
evolveService.toolDefinition(),
|
|
22558
22809
|
...mcpTools
|
|
22559
22810
|
];
|
|
22560
|
-
const loopFlavor = await optionalText2(
|
|
22811
|
+
const loopFlavor = await optionalText2(join27(input.workspace, "FLAVOR.md"));
|
|
22561
22812
|
const loopInstructionBaseline = await loopInstructions.baseline();
|
|
22562
22813
|
const loopEnvironment = createPromptEnvironment({
|
|
22563
22814
|
now: /* @__PURE__ */ new Date(),
|
|
@@ -22916,8 +23167,8 @@ async function createProductionRuntime(options) {
|
|
|
22916
23167
|
explain: (query, focus, signal, onText) => runExplain({
|
|
22917
23168
|
graph: new DesktopAstGraphService(workspace),
|
|
22918
23169
|
readFile: async (path) => {
|
|
22919
|
-
const root =
|
|
22920
|
-
const abs =
|
|
23170
|
+
const root = resolve29(workspace);
|
|
23171
|
+
const abs = resolve29(root, path);
|
|
22921
23172
|
if (abs !== root && !abs.startsWith(root + sep20)) throw new Error(`path outside workspace: ${path}`);
|
|
22922
23173
|
return readFile22(abs, "utf8");
|
|
22923
23174
|
},
|
|
@@ -22998,7 +23249,12 @@ ${summary2}`;
|
|
|
22998
23249
|
for (const key of Object.keys(subagentStartedAt)) delete subagentStartedAt[key];
|
|
22999
23250
|
for (const key of Object.keys(subagentElapsedMs)) delete subagentElapsedMs[key];
|
|
23000
23251
|
const previousIdeSessionId = ideSessionId;
|
|
23001
|
-
|
|
23252
|
+
const nextSessionId = `session-${(/* @__PURE__ */ new Date()).toISOString().replace(/[^0-9]/g, "").slice(0, 17)}-${randomUUID20().slice(0, 8)}`;
|
|
23253
|
+
const nextLease = await sessionStore.acquireLease(nextSessionId);
|
|
23254
|
+
const previousLease = sessionLease;
|
|
23255
|
+
sessionLease = nextLease;
|
|
23256
|
+
sessionId = nextSessionId;
|
|
23257
|
+
await previousLease?.release();
|
|
23002
23258
|
hookSessionId = sessionId;
|
|
23003
23259
|
harnessJournal = new HarnessJournal({ workspace, sessionId });
|
|
23004
23260
|
setUsageSession(sessionId);
|
|
@@ -23118,7 +23374,7 @@ ${renderMemoryDocument(entries)}`;
|
|
|
23118
23374
|
return `Provider "${providerName}" is missing authorizationUrl, tokenUrl, or clientId.`;
|
|
23119
23375
|
}
|
|
23120
23376
|
try {
|
|
23121
|
-
const tokenStore = createFileTokenStore(
|
|
23377
|
+
const tokenStore = createFileTokenStore(join27(home, ".flavor-code", "auth.json"));
|
|
23122
23378
|
const credentialId = oauthCredentialId(oauthConfig.tokenUrl, oauthConfig.clientId);
|
|
23123
23379
|
await migrateLegacyOAuthToken(tokenStore, providerName, credentialId);
|
|
23124
23380
|
const oauth = new OAuthCallbackAuthProvider({
|
|
@@ -23165,7 +23421,7 @@ ${renderMemoryDocument(entries)}`;
|
|
|
23165
23421
|
}
|
|
23166
23422
|
},
|
|
23167
23423
|
async logout() {
|
|
23168
|
-
const tokenStore = createFileTokenStore(
|
|
23424
|
+
const tokenStore = createFileTokenStore(join27(home, ".flavor-code", "auth.json"));
|
|
23169
23425
|
const tokens = await tokenStore.load();
|
|
23170
23426
|
const hadCredentials = Object.keys(tokens).length > 0 || effectiveLlm !== void 0;
|
|
23171
23427
|
if (!hadCredentials) {
|
|
@@ -23245,24 +23501,13 @@ ${renderMemoryDocument(entries)}`;
|
|
|
23245
23501
|
setPermissionProfile: (profile) => harness.setPermissionProfile(profile)
|
|
23246
23502
|
};
|
|
23247
23503
|
let disposed = false;
|
|
23248
|
-
|
|
23249
|
-
|
|
23250
|
-
|
|
23251
|
-
|
|
23252
|
-
|
|
23253
|
-
|
|
23254
|
-
|
|
23255
|
-
jobs: { list: () => jobs.list(), read: (id, owner2, cursor) => jobs.read(id, owner2, cursor), subscribe: (listener) => jobs.subscribe(listener) },
|
|
23256
|
-
get sessionId() {
|
|
23257
|
-
return sessionId;
|
|
23258
|
-
},
|
|
23259
|
-
get diagnostics() {
|
|
23260
|
-
return diagnostics.map((item) => redactSecrets(item, secrets));
|
|
23261
|
-
},
|
|
23262
|
-
async dispose() {
|
|
23263
|
-
if (disposed) return;
|
|
23264
|
-
disposed = true;
|
|
23265
|
-
const stepTimeoutMs = options.shutdownStepTimeoutMs ?? DEFAULT_SHUTDOWN_STEP_TIMEOUT_MS;
|
|
23504
|
+
let unregisterCrashCleanup = () => void 0;
|
|
23505
|
+
const disposeRuntime = async () => {
|
|
23506
|
+
if (disposed) return;
|
|
23507
|
+
disposed = true;
|
|
23508
|
+
unregisterCrashCleanup();
|
|
23509
|
+
const stepTimeoutMs = options.shutdownStepTimeoutMs ?? DEFAULT_SHUTDOWN_STEP_TIMEOUT_MS;
|
|
23510
|
+
try {
|
|
23266
23511
|
collaborationEventsOpen = false;
|
|
23267
23512
|
unsubscribeCollaboration?.();
|
|
23268
23513
|
if (collaborationEventPump !== void 0) await boundedStep(stepTimeoutMs, diagnostics, "collaboration-event-pump", collaborationEventPump);
|
|
@@ -23281,8 +23526,29 @@ ${renderMemoryDocument(entries)}`;
|
|
|
23281
23526
|
memoryReviews.dispose();
|
|
23282
23527
|
if (collaborationClient !== void 0) await boundedStep(stepTimeoutMs, diagnostics, "collaboration-close", collaborationClient.close());
|
|
23283
23528
|
await cleanupProduction(approvals, questions, pluginHost, mcpManager, harness, { stepTimeoutMs });
|
|
23529
|
+
} finally {
|
|
23530
|
+
const lease = sessionLease;
|
|
23531
|
+
sessionLease = void 0;
|
|
23532
|
+
await lease?.release();
|
|
23284
23533
|
}
|
|
23285
23534
|
};
|
|
23535
|
+
unregisterCrashCleanup = registerCrashCleanup(disposeRuntime);
|
|
23536
|
+
return {
|
|
23537
|
+
session,
|
|
23538
|
+
services,
|
|
23539
|
+
authorization,
|
|
23540
|
+
approvals,
|
|
23541
|
+
memoryReviews,
|
|
23542
|
+
restoredTranscript,
|
|
23543
|
+
jobs: { list: () => jobs.list(), read: (id, owner2, cursor) => jobs.read(id, owner2, cursor), subscribe: (listener) => jobs.subscribe(listener) },
|
|
23544
|
+
get sessionId() {
|
|
23545
|
+
return sessionId;
|
|
23546
|
+
},
|
|
23547
|
+
get diagnostics() {
|
|
23548
|
+
return diagnostics.map((item) => redactSecrets(item, secrets));
|
|
23549
|
+
},
|
|
23550
|
+
dispose: disposeRuntime
|
|
23551
|
+
};
|
|
23286
23552
|
} catch (primaryError) {
|
|
23287
23553
|
collaborationEventsOpen = false;
|
|
23288
23554
|
mcpDiscarded = true;
|
|
@@ -23303,6 +23569,12 @@ ${renderMemoryDocument(entries)}`;
|
|
|
23303
23569
|
} catch (cleanupError) {
|
|
23304
23570
|
attachCleanupError(primaryError, cleanupError);
|
|
23305
23571
|
}
|
|
23572
|
+
try {
|
|
23573
|
+
await sessionLease?.release();
|
|
23574
|
+
} catch (leaseError) {
|
|
23575
|
+
attachCleanupError(primaryError, leaseError);
|
|
23576
|
+
}
|
|
23577
|
+
sessionLease = void 0;
|
|
23306
23578
|
throw primaryError;
|
|
23307
23579
|
}
|
|
23308
23580
|
}
|
|
@@ -23441,8 +23713,8 @@ async function workspaceFingerprint(workspace) {
|
|
|
23441
23713
|
let remaining = 5 * 1024 * 1024;
|
|
23442
23714
|
for (const name of untracked.stdout.split("\0").filter(Boolean).sort()) {
|
|
23443
23715
|
if (name === ".flavor/loops" || name.startsWith(".flavor/loops/")) continue;
|
|
23444
|
-
const path =
|
|
23445
|
-
const relativePath = relative20(
|
|
23716
|
+
const path = resolve29(workspace, name);
|
|
23717
|
+
const relativePath = relative20(resolve29(workspace), path);
|
|
23446
23718
|
if (relativePath === ".." || relativePath.startsWith(`..${sep20}`)) continue;
|
|
23447
23719
|
hash.update(name);
|
|
23448
23720
|
if (remaining <= 0) continue;
|
|
@@ -23575,8 +23847,8 @@ function requestMemoryRestart(workspace, sessionId, emitOutput) {
|
|
|
23575
23847
|
previous = parseMemoryRestartMarker(readFileSync3(markerPath, "utf8"));
|
|
23576
23848
|
} catch {
|
|
23577
23849
|
}
|
|
23578
|
-
|
|
23579
|
-
|
|
23850
|
+
mkdirSync4(dirname19(markerPath), { recursive: true });
|
|
23851
|
+
writeFileSync2(markerPath, `${JSON.stringify(nextMemoryRestartMarker(previous, sessionId, /* @__PURE__ */ new Date()))}
|
|
23580
23852
|
`, "utf8");
|
|
23581
23853
|
} catch {
|
|
23582
23854
|
}
|
|
@@ -23677,7 +23949,7 @@ async function registerConfiguredAdapters(providers, registry, environment, diag
|
|
|
23677
23949
|
const configured = { ...providers };
|
|
23678
23950
|
if (configured.openai === void 0 && environment.OPENAI_API_KEY) configured.openai = { type: "openai", apiKey: environment.OPENAI_API_KEY };
|
|
23679
23951
|
if (configured.anthropic === void 0 && environment.ANTHROPIC_API_KEY) configured.anthropic = { type: "anthropic", apiKey: environment.ANTHROPIC_API_KEY };
|
|
23680
|
-
const oauthTokenStore = createFileTokenStore(
|
|
23952
|
+
const oauthTokenStore = createFileTokenStore(join27(home, ".flavor-code", "auth.json"));
|
|
23681
23953
|
const registered = [];
|
|
23682
23954
|
let effectiveLlm;
|
|
23683
23955
|
for (const [name, provider] of Object.entries(configured)) {
|
|
@@ -24113,6 +24385,7 @@ export {
|
|
|
24113
24385
|
isDestructiveTool,
|
|
24114
24386
|
redactErrorText,
|
|
24115
24387
|
redactSecrets,
|
|
24388
|
+
prepareSpawnInvocation,
|
|
24116
24389
|
PluginManifestSchema,
|
|
24117
24390
|
initializeFlavor,
|
|
24118
24391
|
DEFAULT_MAX_OUTPUT_BYTES2 as DEFAULT_MAX_OUTPUT_BYTES,
|
|
@@ -24132,6 +24405,7 @@ export {
|
|
|
24132
24405
|
COMMAND_DESCRIPTIONS,
|
|
24133
24406
|
AgentMessageQueue,
|
|
24134
24407
|
FlavorSession,
|
|
24408
|
+
installCrashGuard,
|
|
24135
24409
|
classifyMemoryHeat,
|
|
24136
24410
|
MemoryStore,
|
|
24137
24411
|
buildDockerInvocation,
|