flavor-code 1.2.20 → 1.3.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +18 -11
- package/README.zh-CN.md +18 -11
- package/dist/agent/loop.d.ts +11 -1
- package/dist/{app-5D5RSGQX.js → app-F6FEEF46.js} +1 -1
- package/dist/{chunk-A7PMQ22O.js → chunk-DFSMJ5SX.js} +1 -1
- package/dist/{chunk-AXLUVJKC.js → chunk-Q5EQ4T3F.js} +1797 -771
- package/dist/cli.js +21 -5
- package/dist/context/manager.d.ts +32 -0
- package/dist/desktop/main.js +3548 -1392
- package/dist/desktop/pals-broker-entry.js +1308 -0
- package/dist/desktop/preload.cjs +91 -2
- package/dist/desktop-renderer/assets/index--vSisKu7.css +1 -0
- package/dist/desktop-renderer/assets/index-ByWADjgv.js +154 -0
- package/dist/desktop-renderer/assets/interactive-terminal-BAwIPsif.js +35 -0
- package/dist/desktop-renderer/index.html +3 -3
- package/dist/goal/classifier.d.ts +2 -2
- package/dist/goal/orchestrator.d.ts +2 -1
- package/dist/goal/types.d.ts +33 -0
- package/dist/harness/invariants.d.ts +4 -0
- package/dist/harness/journal.d.ts +109 -0
- package/dist/harness/local.d.ts +6 -2
- package/dist/pals/broker-cli.d.ts +8 -0
- package/dist/permissions/engine.d.ts +2 -0
- package/dist/permissions/policy.d.ts +38 -0
- package/dist/plugins/types.d.ts +22 -2
- package/dist/production.d.ts +9 -1
- package/dist/sdk/index.js +2 -2
- package/dist/session/store.d.ts +30 -2
- package/dist/tools/runtime.d.ts +5 -0
- package/dist/tools/types.d.ts +3 -0
- package/dist/ui/session.d.ts +13 -0
- package/package.json +9 -5
- package/dist/desktop-renderer/assets/index-BmXr_vX3.js +0 -151
- package/dist/desktop-renderer/assets/index-DEL8gXBO.css +0 -1
|
@@ -64,12 +64,12 @@ function normalizeProviderError(error) {
|
|
|
64
64
|
|
|
65
65
|
// src/utils/async.ts
|
|
66
66
|
function withTimeout(promise, ms) {
|
|
67
|
-
return new Promise((
|
|
67
|
+
return new Promise((resolve28, reject) => {
|
|
68
68
|
let settled = false;
|
|
69
69
|
const timer = setTimeout(() => {
|
|
70
70
|
if (settled) return;
|
|
71
71
|
settled = true;
|
|
72
|
-
|
|
72
|
+
resolve28({ timedOut: true, value: void 0 });
|
|
73
73
|
}, ms);
|
|
74
74
|
timer.unref?.();
|
|
75
75
|
promise.then(
|
|
@@ -77,7 +77,7 @@ function withTimeout(promise, ms) {
|
|
|
77
77
|
if (!settled) {
|
|
78
78
|
settled = true;
|
|
79
79
|
clearTimeout(timer);
|
|
80
|
-
|
|
80
|
+
resolve28({ timedOut: false, value });
|
|
81
81
|
}
|
|
82
82
|
},
|
|
83
83
|
(error) => {
|
|
@@ -92,13 +92,13 @@ function withTimeout(promise, ms) {
|
|
|
92
92
|
}
|
|
93
93
|
function awaitWithSignal(promise, signal) {
|
|
94
94
|
signal.throwIfAborted();
|
|
95
|
-
return new Promise((
|
|
95
|
+
return new Promise((resolve28, reject) => {
|
|
96
96
|
const onAbort = () => reject(signal.reason);
|
|
97
97
|
signal.addEventListener("abort", onAbort, { once: true });
|
|
98
98
|
promise.then(
|
|
99
99
|
(value) => {
|
|
100
100
|
signal.removeEventListener("abort", onAbort);
|
|
101
|
-
|
|
101
|
+
resolve28(value);
|
|
102
102
|
},
|
|
103
103
|
(error) => {
|
|
104
104
|
signal.removeEventListener("abort", onAbort);
|
|
@@ -156,12 +156,14 @@ var PermissionEngine = class {
|
|
|
156
156
|
#workspace;
|
|
157
157
|
#mode;
|
|
158
158
|
#profile;
|
|
159
|
+
#policy;
|
|
159
160
|
constructor(options) {
|
|
160
161
|
const root = resolve(options.workspace);
|
|
161
162
|
this.#lexicalWorkspace = root;
|
|
162
163
|
this.#workspace = existsSync(root) ? realpathSync.native(root) : root;
|
|
163
164
|
this.#mode = canonicalMode(options.mode ?? "default");
|
|
164
165
|
this.#profile = options.profile ?? "standard";
|
|
166
|
+
this.#policy = options.policy;
|
|
165
167
|
}
|
|
166
168
|
get mode() {
|
|
167
169
|
return this.#mode;
|
|
@@ -176,6 +178,14 @@ var PermissionEngine = class {
|
|
|
176
178
|
this.#profile = profile;
|
|
177
179
|
}
|
|
178
180
|
decide(request) {
|
|
181
|
+
const builtIn = this.#builtInDecision(request);
|
|
182
|
+
if (builtIn.decision === "deny") return builtIn;
|
|
183
|
+
const policy = this.#policy?.decide(request);
|
|
184
|
+
if (policy === void 0) return builtIn;
|
|
185
|
+
if (policy.decision === "allow") return builtIn.decision === "ask" ? policy : builtIn;
|
|
186
|
+
return policy;
|
|
187
|
+
}
|
|
188
|
+
#builtInDecision(request) {
|
|
179
189
|
if (request.tool === "Task") return request.agent === "main" ? { decision: "allow" } : { decision: "deny", reason: "Task delegation is restricted to the main agent" };
|
|
180
190
|
if (CONTROL_TOOLS.has(request.tool)) return { decision: "allow" };
|
|
181
191
|
if (COLLABORATION_CONTROL_TOOLS.has(request.tool)) return request.agent === "main" ? { decision: "allow" } : { decision: "deny", reason: "Cross-instance collaboration is restricted to the main agent" };
|
|
@@ -3298,10 +3308,21 @@ var FlavorSession = class {
|
|
|
3298
3308
|
#activePlanning = /* @__PURE__ */ new Set();
|
|
3299
3309
|
#planningIdleWaiters = /* @__PURE__ */ new Set();
|
|
3300
3310
|
#pendingCoWorkStarts = [];
|
|
3311
|
+
#activeSteeringDurableIds = /* @__PURE__ */ new Set();
|
|
3312
|
+
#durableResumeStarted = false;
|
|
3301
3313
|
#permissionBaseline;
|
|
3302
3314
|
#activeSubmission;
|
|
3303
3315
|
constructor(services) {
|
|
3304
3316
|
this.#services = services;
|
|
3317
|
+
for (const item of services.durableQueue?.recover() ?? []) {
|
|
3318
|
+
const submission = recoveredSubmission(item.payload);
|
|
3319
|
+
if (submission === void 0) {
|
|
3320
|
+
services.durableQueue?.ack(item.id);
|
|
3321
|
+
continue;
|
|
3322
|
+
}
|
|
3323
|
+
this.#queue.enqueue(item.kind, submission.text);
|
|
3324
|
+
this.#queuedSubmissions[item.kind].push({ ...submission, durableId: item.id });
|
|
3325
|
+
}
|
|
3305
3326
|
}
|
|
3306
3327
|
get active() {
|
|
3307
3328
|
return this.#active !== void 0;
|
|
@@ -3311,6 +3332,11 @@ var FlavorSession = class {
|
|
|
3311
3332
|
}
|
|
3312
3333
|
clearQueue() {
|
|
3313
3334
|
const snapshot4 = this.#queue.clear();
|
|
3335
|
+
for (const kind of ["steer", "followUp"]) {
|
|
3336
|
+
for (const submission of this.#queuedSubmissions[kind]) {
|
|
3337
|
+
if (submission.durableId !== void 0) this.#services.durableQueue?.ack(submission.durableId);
|
|
3338
|
+
}
|
|
3339
|
+
}
|
|
3314
3340
|
this.#queuedSubmissions.steer.length = 0;
|
|
3315
3341
|
this.#queuedSubmissions.followUp.length = 0;
|
|
3316
3342
|
return snapshot4;
|
|
@@ -3452,6 +3478,7 @@ var FlavorSession = class {
|
|
|
3452
3478
|
}).then(async (decision) => {
|
|
3453
3479
|
if (decision.additionalContext !== void 0) await this.#services.addContext?.(decision.additionalContext);
|
|
3454
3480
|
this.#started = true;
|
|
3481
|
+
this.#resumeDurableQueue();
|
|
3455
3482
|
});
|
|
3456
3483
|
return this.#startPromise;
|
|
3457
3484
|
}
|
|
@@ -3466,6 +3493,7 @@ var FlavorSession = class {
|
|
|
3466
3493
|
const operation = this.#submissionTail.catch(() => {
|
|
3467
3494
|
}).then(() => this.#runSubmissionChain(submission)).finally(() => {
|
|
3468
3495
|
this.#pendingSubmissions -= 1;
|
|
3496
|
+
if (this.#pendingSubmissions === 0) this.#resumeDurableQueue();
|
|
3469
3497
|
});
|
|
3470
3498
|
this.#submissionTail = operation;
|
|
3471
3499
|
return operation;
|
|
@@ -3476,6 +3504,7 @@ var FlavorSession = class {
|
|
|
3476
3504
|
while (pending.length > 0) {
|
|
3477
3505
|
const submission = pending.shift();
|
|
3478
3506
|
if (!this.#isSubmissionCurrent(submission) || submission.controlOnly === true) {
|
|
3507
|
+
this.#ackDurable(submission);
|
|
3479
3508
|
pending.push(...this.#drainMessages("steer"), ...this.#drainMessages("followUp"));
|
|
3480
3509
|
continue;
|
|
3481
3510
|
}
|
|
@@ -3489,10 +3518,17 @@ var FlavorSession = class {
|
|
|
3489
3518
|
const prompt = submission.text;
|
|
3490
3519
|
await this.start();
|
|
3491
3520
|
if (this.#closed) throw new Error("Session is closed");
|
|
3492
|
-
if (!this.#isSubmissionCurrent(submission))
|
|
3521
|
+
if (!this.#isSubmissionCurrent(submission)) {
|
|
3522
|
+
this.#ackDurable(submission);
|
|
3523
|
+
return;
|
|
3524
|
+
}
|
|
3493
3525
|
if (submission.coWorkPlanningKey !== void 0) {
|
|
3494
|
-
if (!await this.#activatePlanningGate(submission.coWorkPlanningKey))
|
|
3526
|
+
if (!await this.#activatePlanningGate(submission.coWorkPlanningKey)) {
|
|
3527
|
+
this.#ackDurable(submission);
|
|
3528
|
+
return;
|
|
3529
|
+
}
|
|
3495
3530
|
}
|
|
3531
|
+
if (submission.durableId !== void 0) this.#services.durableQueue?.claim(submission.durableId);
|
|
3496
3532
|
const controller = new AbortController();
|
|
3497
3533
|
this.#active = controller;
|
|
3498
3534
|
this.#activeSubmission = submission;
|
|
@@ -3519,7 +3555,7 @@ var FlavorSession = class {
|
|
|
3519
3555
|
const command = parseSlashCommand(prompt, this.#services.pluginCommands().map(({ name }) => name), skillNames);
|
|
3520
3556
|
if (command !== null) await this.#dispatch(command, controller.signal);
|
|
3521
3557
|
else for await (const event of this.#services.run(prompt, controller.signal, {
|
|
3522
|
-
getSteeringMessages: () => this.#drainMessages("steer").map((item) => item.text),
|
|
3558
|
+
getSteeringMessages: () => this.#drainMessages("steer", true).map((item) => item.text),
|
|
3523
3559
|
...decision.additionalContext === void 0 ? {} : { additionalContext: decision.additionalContext },
|
|
3524
3560
|
...submission.initialUserMessage === void 0 ? {} : { initialUserMessage: submission.initialUserMessage }
|
|
3525
3561
|
})) {
|
|
@@ -3555,6 +3591,8 @@ var FlavorSession = class {
|
|
|
3555
3591
|
} catch {
|
|
3556
3592
|
}
|
|
3557
3593
|
}
|
|
3594
|
+
this.#ackDurable(submission);
|
|
3595
|
+
this.#ackActiveSteering();
|
|
3558
3596
|
}
|
|
3559
3597
|
}
|
|
3560
3598
|
}
|
|
@@ -3708,12 +3746,42 @@ var FlavorSession = class {
|
|
|
3708
3746
|
});
|
|
3709
3747
|
}
|
|
3710
3748
|
#enqueueMessage(kind, submission) {
|
|
3749
|
+
const durableId = submission.durableId ?? this.#services.durableQueue?.admit(kind, durablePayload(submission));
|
|
3711
3750
|
this.#queue.enqueue(kind, submission.text);
|
|
3712
|
-
this.#queuedSubmissions[kind].push(submission);
|
|
3751
|
+
this.#queuedSubmissions[kind].push(durableId === void 0 ? submission : { ...submission, durableId });
|
|
3713
3752
|
}
|
|
3714
|
-
#drainMessages(kind) {
|
|
3753
|
+
#drainMessages(kind, activeSteering = false) {
|
|
3715
3754
|
const prompts = this.#queue.drain(kind);
|
|
3716
|
-
return prompts.map((prompt) =>
|
|
3755
|
+
return prompts.map((prompt) => {
|
|
3756
|
+
const submission = this.#queuedSubmissions[kind].shift() ?? normalizeRequiredSubmission(prompt);
|
|
3757
|
+
if (activeSteering && submission.durableId !== void 0) {
|
|
3758
|
+
this.#services.durableQueue?.claim(submission.durableId);
|
|
3759
|
+
this.#activeSteeringDurableIds.add(submission.durableId);
|
|
3760
|
+
}
|
|
3761
|
+
return submission;
|
|
3762
|
+
});
|
|
3763
|
+
}
|
|
3764
|
+
#resumeDurableQueue() {
|
|
3765
|
+
if (this.#durableResumeStarted || this.#closed || this.active || this.#pendingSubmissions > 0) return;
|
|
3766
|
+
if (!this.#queue.hasPending) return;
|
|
3767
|
+
this.#durableResumeStarted = true;
|
|
3768
|
+
const submissions = [...this.#drainMessages("steer"), ...this.#drainMessages("followUp")];
|
|
3769
|
+
if (submissions.length === 0) return;
|
|
3770
|
+
this.#notice("Resuming a queued prompt recovered from the durable harness journal.");
|
|
3771
|
+
for (const submission of submissions) {
|
|
3772
|
+
this.#enqueueNormalizedSubmission(submission).catch((error) => {
|
|
3773
|
+
this.#services.output({ type: "error", error: { code: "unknown", message: message(error) } });
|
|
3774
|
+
});
|
|
3775
|
+
}
|
|
3776
|
+
}
|
|
3777
|
+
#ackDurable(submission) {
|
|
3778
|
+
if (submission.durableId === void 0) return;
|
|
3779
|
+
this.#services.durableQueue?.ack(submission.durableId);
|
|
3780
|
+
this.#activeSteeringDurableIds.delete(submission.durableId);
|
|
3781
|
+
}
|
|
3782
|
+
#ackActiveSteering() {
|
|
3783
|
+
for (const id of this.#activeSteeringDurableIds) this.#services.durableQueue?.ack(id);
|
|
3784
|
+
this.#activeSteeringDurableIds.clear();
|
|
3717
3785
|
}
|
|
3718
3786
|
#outputQueuedSubmission(submission) {
|
|
3719
3787
|
const origin = submission.remoteOrigin;
|
|
@@ -3752,9 +3820,9 @@ var FlavorSession = class {
|
|
|
3752
3820
|
}
|
|
3753
3821
|
#waitForPlanningIdle(key) {
|
|
3754
3822
|
if (!this.#activePlanning.has(key)) return Promise.resolve();
|
|
3755
|
-
return new Promise((
|
|
3823
|
+
return new Promise((resolve28) => {
|
|
3756
3824
|
const check = () => {
|
|
3757
|
-
if (!this.#activePlanning.has(key))
|
|
3825
|
+
if (!this.#activePlanning.has(key)) resolve28();
|
|
3758
3826
|
else this.#planningIdleWaiters.add(check);
|
|
3759
3827
|
};
|
|
3760
3828
|
this.#planningIdleWaiters.add(check);
|
|
@@ -3808,6 +3876,23 @@ function normalizeRequiredSubmission(input) {
|
|
|
3808
3876
|
if (normalized === void 0) throw new Error("Cannot queue an empty message");
|
|
3809
3877
|
return normalized;
|
|
3810
3878
|
}
|
|
3879
|
+
function durablePayload(submission) {
|
|
3880
|
+
const { durableId: _durableId, ...payload } = submission;
|
|
3881
|
+
return structuredClone(payload);
|
|
3882
|
+
}
|
|
3883
|
+
function recoveredSubmission(value) {
|
|
3884
|
+
if (typeof value !== "object" || value === null) return void 0;
|
|
3885
|
+
const input = value;
|
|
3886
|
+
if (typeof input.text !== "string" || input.text.trim().length === 0) return void 0;
|
|
3887
|
+
if (typeof input.displayText !== "string") return void 0;
|
|
3888
|
+
if (input.controlOnly !== void 0 && typeof input.controlOnly !== "boolean") return void 0;
|
|
3889
|
+
if (input.coWorkPlanningKey !== void 0 && typeof input.coWorkPlanningKey !== "string") return void 0;
|
|
3890
|
+
if (input.remoteOrigin !== void 0) {
|
|
3891
|
+
const origin = input.remoteOrigin;
|
|
3892
|
+
if (typeof origin.senderId !== "string" || typeof origin.senderAlias !== "string" || typeof origin.prompt !== "string") return void 0;
|
|
3893
|
+
}
|
|
3894
|
+
return structuredClone(input);
|
|
3895
|
+
}
|
|
3811
3896
|
function coWorkKey(coWorkId, epoch) {
|
|
3812
3897
|
return `${coWorkId}:${epoch}`;
|
|
3813
3898
|
}
|
|
@@ -4085,12 +4170,12 @@ var BrokerStateError = class extends Error {
|
|
|
4085
4170
|
};
|
|
4086
4171
|
var WINDOWS_PIPE_PREFIX = "\\\\.\\pipe\\";
|
|
4087
4172
|
function canConnect(address) {
|
|
4088
|
-
return new Promise((
|
|
4173
|
+
return new Promise((resolve28) => {
|
|
4089
4174
|
const socket = new Socket();
|
|
4090
4175
|
const finish = (connected) => {
|
|
4091
4176
|
socket.removeAllListeners();
|
|
4092
4177
|
socket.destroy();
|
|
4093
|
-
|
|
4178
|
+
resolve28(connected);
|
|
4094
4179
|
};
|
|
4095
4180
|
socket.once("connect", () => finish(true));
|
|
4096
4181
|
socket.once("error", () => finish(false));
|
|
@@ -4139,7 +4224,7 @@ var PalBrokerServer = class {
|
|
|
4139
4224
|
this.authToken ??= await loadOrCreatePalAuthToken(this.authHome === void 0 ? {} : { home: this.authHome });
|
|
4140
4225
|
try {
|
|
4141
4226
|
await this.prepareAddress();
|
|
4142
|
-
await new Promise((
|
|
4227
|
+
await new Promise((resolve28, reject) => {
|
|
4143
4228
|
const onError = (error) => {
|
|
4144
4229
|
this.server.off("listening", onListening);
|
|
4145
4230
|
reject(error);
|
|
@@ -4148,7 +4233,7 @@ var PalBrokerServer = class {
|
|
|
4148
4233
|
this.server.off("error", onError);
|
|
4149
4234
|
this.started = true;
|
|
4150
4235
|
this.ownsAddress = true;
|
|
4151
|
-
|
|
4236
|
+
resolve28();
|
|
4152
4237
|
};
|
|
4153
4238
|
this.server.once("error", onError);
|
|
4154
4239
|
this.server.once("listening", onListening);
|
|
@@ -4176,7 +4261,7 @@ var PalBrokerServer = class {
|
|
|
4176
4261
|
const ownsAddress = this.ownsAddress;
|
|
4177
4262
|
for (const connection of this.connections) connection.socket.destroy();
|
|
4178
4263
|
if (this.started) {
|
|
4179
|
-
await new Promise((
|
|
4264
|
+
await new Promise((resolve28) => this.server.close(() => resolve28()));
|
|
4180
4265
|
this.started = false;
|
|
4181
4266
|
}
|
|
4182
4267
|
this.ownsAddress = false;
|
|
@@ -4845,15 +4930,15 @@ var PalBrokerState = class {
|
|
|
4845
4930
|
// src/pals/broker-cli.ts
|
|
4846
4931
|
var startupAttempts = /* @__PURE__ */ new Map();
|
|
4847
4932
|
function delay(milliseconds) {
|
|
4848
|
-
return new Promise((
|
|
4933
|
+
return new Promise((resolve28) => setTimeout(resolve28, milliseconds));
|
|
4849
4934
|
}
|
|
4850
4935
|
function canConnect2(address) {
|
|
4851
|
-
return new Promise((
|
|
4936
|
+
return new Promise((resolve28) => {
|
|
4852
4937
|
const socket = new Socket2();
|
|
4853
4938
|
const finish = (connected) => {
|
|
4854
4939
|
socket.removeAllListeners();
|
|
4855
4940
|
socket.destroy();
|
|
4856
|
-
|
|
4941
|
+
resolve28(connected);
|
|
4857
4942
|
};
|
|
4858
4943
|
socket.once("connect", () => finish(true));
|
|
4859
4944
|
socket.once("error", () => finish(false));
|
|
@@ -4867,18 +4952,25 @@ function startupLockPath(address) {
|
|
|
4867
4952
|
async function spawnProductionBroker(address) {
|
|
4868
4953
|
const entry2 = process.argv[1];
|
|
4869
4954
|
if (entry2 === void 0) throw new Error("Cannot auto-start pals broker without a CLI entry point");
|
|
4955
|
+
return spawnPalBrokerProcess({ address, executable: process.execPath, args: [entry2, "--pals-broker", address] });
|
|
4956
|
+
}
|
|
4957
|
+
async function spawnPalBrokerProcess(options) {
|
|
4958
|
+
if (options.executable.trim() === "" || options.args.length === 0) throw new Error("Pals broker process requires an executable and entry arguments");
|
|
4959
|
+
const address = options.address;
|
|
4870
4960
|
const lockPath = startupLockPath(address);
|
|
4871
4961
|
const lock = await acquirePalFileLock({ path: lockPath, endpointLive: () => canConnect2(address) });
|
|
4872
4962
|
if (lock === void 0) return;
|
|
4873
4963
|
try {
|
|
4874
|
-
const child = spawn3(
|
|
4964
|
+
const child = spawn3(options.executable, [...options.args], {
|
|
4875
4965
|
detached: true,
|
|
4876
4966
|
stdio: "ignore",
|
|
4877
|
-
windowsHide: true
|
|
4967
|
+
windowsHide: true,
|
|
4968
|
+
...options.environment === void 0 ? {} : { env: options.environment }
|
|
4878
4969
|
});
|
|
4879
4970
|
child.unref();
|
|
4880
4971
|
const deadline = Date.now() + 5e3;
|
|
4881
4972
|
while (Date.now() < deadline && !await canConnect2(address)) await delay(25);
|
|
4973
|
+
if (!await canConnect2(address)) throw new Error(`Pals broker process did not become ready at '${address}'`);
|
|
4882
4974
|
} finally {
|
|
4883
4975
|
await lock.release();
|
|
4884
4976
|
}
|
|
@@ -4889,8 +4981,8 @@ async function runPalBroker(options) {
|
|
|
4889
4981
|
const sweepIntervalMs = options.sweepIntervalMs ?? 5e3;
|
|
4890
4982
|
const idleShutdownMs = options.idleShutdownMs ?? 3e4;
|
|
4891
4983
|
let resolveClosed;
|
|
4892
|
-
const closed = new Promise((
|
|
4893
|
-
resolveClosed =
|
|
4984
|
+
const closed = new Promise((resolve28) => {
|
|
4985
|
+
resolveClosed = resolve28;
|
|
4894
4986
|
});
|
|
4895
4987
|
let closing;
|
|
4896
4988
|
const timer = setInterval(() => {
|
|
@@ -4930,10 +5022,10 @@ async function ensureOnce(options) {
|
|
|
4930
5022
|
}
|
|
4931
5023
|
|
|
4932
5024
|
// src/production.ts
|
|
4933
|
-
import { readFile as
|
|
4934
|
-
import { createHash as
|
|
5025
|
+
import { readFile as readFile22 } from "fs/promises";
|
|
5026
|
+
import { createHash as createHash17, randomUUID as randomUUID18 } from "crypto";
|
|
4935
5027
|
import { homedir as homedir2, release as osRelease, tmpdir as tmpdir3, version as osVersion } from "os";
|
|
4936
|
-
import { basename as basename6, join as
|
|
5028
|
+
import { basename as basename6, join as join24, relative as relative20, resolve as resolve27, sep as sep20 } from "path";
|
|
4937
5029
|
|
|
4938
5030
|
// src/agent/subagents.ts
|
|
4939
5031
|
import { z as z5 } from "zod";
|
|
@@ -5475,6 +5567,9 @@ function createTaskPlanTools(options) {
|
|
|
5475
5567
|
return [planTool, updateTool];
|
|
5476
5568
|
}
|
|
5477
5569
|
|
|
5570
|
+
// src/context/manager.ts
|
|
5571
|
+
import { createHash as createHash4, randomUUID as randomUUID6 } from "crypto";
|
|
5572
|
+
|
|
5478
5573
|
// src/context/compaction.ts
|
|
5479
5574
|
var DEFAULT_COMPACTION_POLICY = {
|
|
5480
5575
|
windowTokens: 2e5,
|
|
@@ -5686,6 +5781,9 @@ var ContextManager = class _ContextManager {
|
|
|
5686
5781
|
#lastCompactProgress;
|
|
5687
5782
|
#forkPinnedBoundary = false;
|
|
5688
5783
|
#forkCompactBoundary = false;
|
|
5784
|
+
#epoch;
|
|
5785
|
+
#visibilityLog = [];
|
|
5786
|
+
#activeTransientSystem = /* @__PURE__ */ new Map();
|
|
5689
5787
|
constructor(options) {
|
|
5690
5788
|
if (options.compactAtChars !== void 0 && options.compactAtChars <= 0) throw new Error("compactAtChars must be positive");
|
|
5691
5789
|
if (options.toolOutputChars <= 0) throw new Error("toolOutputChars must be positive");
|
|
@@ -5712,6 +5810,7 @@ var ContextManager = class _ContextManager {
|
|
|
5712
5810
|
this.#summarize = options.summarize;
|
|
5713
5811
|
this.#onCompactProgress = options.onCompactProgress;
|
|
5714
5812
|
this.#hooks = options.hooks;
|
|
5813
|
+
this.#epoch = this.#createEpoch();
|
|
5715
5814
|
}
|
|
5716
5815
|
clear() {
|
|
5717
5816
|
this.#compact = void 0;
|
|
@@ -5722,6 +5821,9 @@ var ContextManager = class _ContextManager {
|
|
|
5722
5821
|
this.#consecutiveAutoCompactFailures = 0;
|
|
5723
5822
|
this.#forkPinnedBoundary = false;
|
|
5724
5823
|
this.#forkCompactBoundary = false;
|
|
5824
|
+
this.#epoch = this.#createEpoch();
|
|
5825
|
+
this.#visibilityLog = [];
|
|
5826
|
+
this.#activeTransientSystem.clear();
|
|
5725
5827
|
}
|
|
5726
5828
|
/**
|
|
5727
5829
|
* Create an isolated child context whose model-visible prefix is identical to
|
|
@@ -5748,6 +5850,7 @@ var ContextManager = class _ContextManager {
|
|
|
5748
5850
|
hooks: options.hooks ?? this.#hooks
|
|
5749
5851
|
});
|
|
5750
5852
|
child.#compact = this.#compact === void 0 ? void 0 : { ...this.#compact };
|
|
5853
|
+
child.#epoch = cloneEpoch(this.#epoch);
|
|
5751
5854
|
child.#messages = this.#messages.map((message2) => {
|
|
5752
5855
|
const { cacheBreakpoint: _cacheBreakpoint, ...copy } = cloneForkMessage(message2);
|
|
5753
5856
|
return copy;
|
|
@@ -5771,9 +5874,70 @@ var ContextManager = class _ContextManager {
|
|
|
5771
5874
|
updateTaskState(taskState) {
|
|
5772
5875
|
this.#taskState = taskState;
|
|
5773
5876
|
}
|
|
5877
|
+
beginTransientSystem(content) {
|
|
5878
|
+
const id = randomUUID6();
|
|
5879
|
+
const record = {
|
|
5880
|
+
id,
|
|
5881
|
+
role: "system",
|
|
5882
|
+
content,
|
|
5883
|
+
admittedAt: (/* @__PURE__ */ new Date()).toISOString(),
|
|
5884
|
+
scope: "run"
|
|
5885
|
+
};
|
|
5886
|
+
this.#visibilityLog.push(record);
|
|
5887
|
+
this.#activeTransientSystem.set(id, { role: "system", content });
|
|
5888
|
+
return id;
|
|
5889
|
+
}
|
|
5890
|
+
endTransientSystem(id) {
|
|
5891
|
+
this.#activeTransientSystem.delete(id);
|
|
5892
|
+
}
|
|
5893
|
+
/**
|
|
5894
|
+
* Admit dynamic source changes at a model-call savepoint. The stable prefix
|
|
5895
|
+
* remains byte-identical; changes become chronological, persisted system
|
|
5896
|
+
* messages after the cache breakpoint.
|
|
5897
|
+
*/
|
|
5898
|
+
refreshContextSources() {
|
|
5899
|
+
const updates = [];
|
|
5900
|
+
const next = this.#resolvedDynamicSources(this.#epoch.sources);
|
|
5901
|
+
let currentStable;
|
|
5902
|
+
try {
|
|
5903
|
+
currentStable = this.#buildStableMessages(this.#epoch.stableMessages);
|
|
5904
|
+
} catch {
|
|
5905
|
+
currentStable = void 0;
|
|
5906
|
+
}
|
|
5907
|
+
const currentStableHash = currentStable === void 0 ? this.#epoch.stableSourceHash : hashMessages(currentStable);
|
|
5908
|
+
if (currentStable !== void 0 && currentStableHash !== this.#epoch.stableSourceHash) {
|
|
5909
|
+
const value = currentStable.map((message2) => message2.content).join("\n\n");
|
|
5910
|
+
if (this.#epoch.sources["system-baseline"] !== value) {
|
|
5911
|
+
updates.push(contextUpdateMessage("system-baseline", value));
|
|
5912
|
+
this.#epoch.sources["system-baseline"] = value;
|
|
5913
|
+
}
|
|
5914
|
+
} else if (currentStable !== void 0 && this.#epoch.sources["system-baseline"] !== void 0) {
|
|
5915
|
+
const value = currentStable.map((message2) => message2.content).join("\n\n");
|
|
5916
|
+
updates.push(contextUpdateMessage("system-baseline", value));
|
|
5917
|
+
delete this.#epoch.sources["system-baseline"];
|
|
5918
|
+
}
|
|
5919
|
+
for (const key of DYNAMIC_SOURCE_ORDER) {
|
|
5920
|
+
const value = next[key];
|
|
5921
|
+
if (value === void 0 || value === this.#epoch.sources[key]) continue;
|
|
5922
|
+
updates.push(contextUpdateMessage(key, displaySourceValue(key, value)));
|
|
5923
|
+
this.#epoch.sources[key] = value;
|
|
5924
|
+
}
|
|
5925
|
+
for (const key of DYNAMIC_SOURCE_ORDER) {
|
|
5926
|
+
if (key in next || !(key in this.#epoch.sources)) continue;
|
|
5927
|
+
updates.push(contextUpdateMessage(key, "(removed)"));
|
|
5928
|
+
delete this.#epoch.sources[key];
|
|
5929
|
+
}
|
|
5930
|
+
if (updates.length === 0) return false;
|
|
5931
|
+
this.#messages.push(...updates);
|
|
5932
|
+
this.#lastRecordedInputTokens = void 0;
|
|
5933
|
+
this.#estimatedTokensAtLastRecordedUsage = void 0;
|
|
5934
|
+
return true;
|
|
5935
|
+
}
|
|
5774
5936
|
snapshot() {
|
|
5775
5937
|
return {
|
|
5776
5938
|
...this.#compact === void 0 ? {} : { compact: { ...this.#compact } },
|
|
5939
|
+
epoch: cloneEpoch(this.#epoch),
|
|
5940
|
+
...this.#visibilityLog.length === 0 ? {} : { visibilityLog: this.#visibilityLog.map((item) => ({ ...item })) },
|
|
5777
5941
|
messages: providerValidMessages(this.#messages)
|
|
5778
5942
|
};
|
|
5779
5943
|
}
|
|
@@ -5781,6 +5945,9 @@ var ContextManager = class _ContextManager {
|
|
|
5781
5945
|
const messages = providerValidMessages(snapshot4.messages);
|
|
5782
5946
|
const legacySummary = snapshot4.summary?.role === "system" && snapshot4.summary.content.startsWith("Conversation summary\n") ? snapshot4.summary.content.slice("Conversation summary\n".length) : void 0;
|
|
5783
5947
|
this.#compact = snapshot4.compact === void 0 ? legacySummary === void 0 ? void 0 : { summary: legacySummary, compactedAt: (/* @__PURE__ */ new Date(0)).toISOString() } : { ...snapshot4.compact };
|
|
5948
|
+
if (snapshot4.epoch !== void 0) this.#epoch = cloneEpoch(snapshot4.epoch);
|
|
5949
|
+
this.#visibilityLog = snapshot4.visibilityLog?.map((item) => ({ ...item })) ?? [];
|
|
5950
|
+
this.#activeTransientSystem.clear();
|
|
5784
5951
|
this.#messages = messages.map((message2) => message2.role === "tool" ? { ...message2, content: truncateToolOutput(message2.content, this.#toolOutputChars) } : cloneMessage2(message2));
|
|
5785
5952
|
this.#lastRecordedInputTokens = void 0;
|
|
5786
5953
|
this.#estimatedTokensAtLastRecordedUsage = void 0;
|
|
@@ -5798,7 +5965,8 @@ var ContextManager = class _ContextManager {
|
|
|
5798
5965
|
content: compactContinuationMessage(this.#compact.summary),
|
|
5799
5966
|
...this.#forkCompactBoundary ? { cacheBreakpoint: true } : {}
|
|
5800
5967
|
}],
|
|
5801
|
-
...this.#messages.map(cloneMessage2)
|
|
5968
|
+
...this.#messages.map(cloneMessage2),
|
|
5969
|
+
...[...this.#activeTransientSystem.values()].map(cloneMessage2)
|
|
5802
5970
|
];
|
|
5803
5971
|
}
|
|
5804
5972
|
estimatedTokens() {
|
|
@@ -5821,6 +5989,7 @@ var ContextManager = class _ContextManager {
|
|
|
5821
5989
|
}
|
|
5822
5990
|
async prepareForModelCall(signal = new AbortController().signal) {
|
|
5823
5991
|
signal.throwIfAborted();
|
|
5992
|
+
this.refreshContextSources();
|
|
5824
5993
|
if (!this.needsCompaction()) return false;
|
|
5825
5994
|
const originalMessages = this.#messages;
|
|
5826
5995
|
const originalRecordedUsage = this.#lastRecordedInputTokens;
|
|
@@ -5914,6 +6083,7 @@ var ContextManager = class _ContextManager {
|
|
|
5914
6083
|
this.#reportCompactProgress(90);
|
|
5915
6084
|
this.#compact = nextCompact;
|
|
5916
6085
|
this.#messages = nextMessages.map(cloneMessage2);
|
|
6086
|
+
this.#epoch = this.#createEpoch();
|
|
5917
6087
|
this.#lastRecordedInputTokens = void 0;
|
|
5918
6088
|
this.#estimatedTokensAtLastRecordedUsage = void 0;
|
|
5919
6089
|
this.#reportCompactProgress(100);
|
|
@@ -5935,11 +6105,33 @@ var ContextManager = class _ContextManager {
|
|
|
5935
6105
|
return Math.max(estimated, this.#lastRecordedInputTokens + appendedEstimate);
|
|
5936
6106
|
}
|
|
5937
6107
|
#pinnedMessages() {
|
|
5938
|
-
|
|
6108
|
+
return [
|
|
6109
|
+
...this.#epoch.stableMessages.map(cloneMessage2),
|
|
6110
|
+
...sourceMessages(this.#epoch.sources)
|
|
6111
|
+
];
|
|
6112
|
+
}
|
|
6113
|
+
#resolvedUserMemory() {
|
|
6114
|
+
const message2 = this.#epoch.stableMessages.find((item) => item.content.startsWith("User memory\n"));
|
|
6115
|
+
return message2?.content.slice("User memory\n".length);
|
|
6116
|
+
}
|
|
6117
|
+
#createEpoch() {
|
|
6118
|
+
const stableMessages = this.#buildStableMessages();
|
|
6119
|
+
const sources = this.#resolvedDynamicSources();
|
|
6120
|
+
return {
|
|
6121
|
+
version: 1,
|
|
6122
|
+
id: randomUUID6(),
|
|
6123
|
+
startedAt: (/* @__PURE__ */ new Date()).toISOString(),
|
|
6124
|
+
stableMessages,
|
|
6125
|
+
sources,
|
|
6126
|
+
stableSourceHash: hashMessages(stableMessages)
|
|
6127
|
+
};
|
|
6128
|
+
}
|
|
6129
|
+
#buildStableMessages(fallback = []) {
|
|
6130
|
+
const resolvedUserMemory = tryResolveOptionalSystemSections(this.#userMemory);
|
|
6131
|
+
const fallbackUserMemory = fallback.find((item) => item.content.startsWith("User memory\n"));
|
|
6132
|
+
const userMemory = resolvedUserMemory.available ? resolvedUserMemory.sections?.join("\n\n") : fallbackUserMemory?.content.slice("User memory\n".length);
|
|
5939
6133
|
const stable = [
|
|
5940
6134
|
...resolveSystemSections(this.#system).map((content) => ({ role: "system", content })),
|
|
5941
|
-
// Breakpoint after FLAVOR.md so the byte-stable system prompt plus project
|
|
5942
|
-
// guidance stays cached even when the trailing user memory section changes.
|
|
5943
6135
|
...this.#flavor === void 0 ? [] : [{
|
|
5944
6136
|
role: "system",
|
|
5945
6137
|
content: `FLAVOR.md
|
|
@@ -5949,29 +6141,87 @@ ${this.#flavor}`,
|
|
|
5949
6141
|
...this.#workspaceInstructions === void 0 ? [] : [{
|
|
5950
6142
|
role: "system",
|
|
5951
6143
|
content: `Workspace instructions
|
|
5952
|
-
${this.#workspaceInstructions}
|
|
5953
|
-
cacheBreakpoint: true
|
|
6144
|
+
${this.#workspaceInstructions}`
|
|
5954
6145
|
}],
|
|
5955
|
-
|
|
5956
|
-
|
|
5957
|
-
|
|
5958
|
-
|
|
5959
|
-
|
|
5960
|
-
|
|
5961
|
-
${this.#memory}` }],
|
|
5962
|
-
...this.#taskState === void 0 ? [] : [{ role: "system", content: `Task state
|
|
5963
|
-
${this.#taskState}` }],
|
|
5964
|
-
...this.#volatileSystem === void 0 ? [] : resolveSystemSections(this.#volatileSystem).map((content) => ({ role: "system", content }))
|
|
6146
|
+
...userMemory === void 0 || userMemory.length === 0 ? [] : [{
|
|
6147
|
+
role: "system",
|
|
6148
|
+
content: `User memory
|
|
6149
|
+
${userMemory}`,
|
|
6150
|
+
cacheBreakpoint: true
|
|
6151
|
+
}]
|
|
5965
6152
|
];
|
|
5966
|
-
if (
|
|
5967
|
-
|
|
5968
|
-
|
|
5969
|
-
|
|
5970
|
-
|
|
5971
|
-
|
|
5972
|
-
|
|
6153
|
+
if (stable.length > 0 && (userMemory === void 0 || userMemory.length === 0)) {
|
|
6154
|
+
stable[stable.length - 1].cacheBreakpoint = true;
|
|
6155
|
+
}
|
|
6156
|
+
return stable;
|
|
6157
|
+
}
|
|
6158
|
+
#resolvedDynamicSources(previous = {}) {
|
|
6159
|
+
const result = {};
|
|
6160
|
+
if (this.#memory !== void 0) result["long-term-memory"] = this.#memory;
|
|
6161
|
+
if (this.#taskState !== void 0) result["task-state"] = this.#taskState;
|
|
6162
|
+
const volatile = tryResolveOptionalSystemSections(this.#volatileSystem);
|
|
6163
|
+
if (!volatile.available && previous["runtime"] !== void 0) result["runtime"] = previous["runtime"];
|
|
6164
|
+
else if (volatile.sections !== void 0 && volatile.sections.length > 0) result["runtime"] = JSON.stringify(volatile.sections);
|
|
6165
|
+
return result;
|
|
5973
6166
|
}
|
|
5974
6167
|
};
|
|
6168
|
+
var DYNAMIC_SOURCE_ORDER = ["long-term-memory", "task-state", "runtime"];
|
|
6169
|
+
function sourceMessages(sources) {
|
|
6170
|
+
return DYNAMIC_SOURCE_ORDER.flatMap((key) => {
|
|
6171
|
+
const content = sources[key];
|
|
6172
|
+
if (content === void 0) return [];
|
|
6173
|
+
if (key === "runtime") {
|
|
6174
|
+
try {
|
|
6175
|
+
const sections = JSON.parse(content);
|
|
6176
|
+
if (Array.isArray(sections) && sections.every((item) => typeof item === "string")) {
|
|
6177
|
+
return sections.map((item) => ({ role: "system", content: item }));
|
|
6178
|
+
}
|
|
6179
|
+
} catch {
|
|
6180
|
+
}
|
|
6181
|
+
}
|
|
6182
|
+
const label = key === "long-term-memory" ? "Long-term memory" : key === "task-state" ? "Task state" : "Runtime";
|
|
6183
|
+
return [{
|
|
6184
|
+
role: "system",
|
|
6185
|
+
content: `${label}
|
|
6186
|
+
${content}`
|
|
6187
|
+
}];
|
|
6188
|
+
});
|
|
6189
|
+
}
|
|
6190
|
+
function displaySourceValue(source, value) {
|
|
6191
|
+
if (source !== "runtime") return value;
|
|
6192
|
+
try {
|
|
6193
|
+
const sections = JSON.parse(value);
|
|
6194
|
+
if (Array.isArray(sections) && sections.every((item) => typeof item === "string")) return sections.join("\n\n");
|
|
6195
|
+
} catch {
|
|
6196
|
+
}
|
|
6197
|
+
return value;
|
|
6198
|
+
}
|
|
6199
|
+
function contextUpdateMessage(source, content) {
|
|
6200
|
+
return { role: "system", content: `Context update [${source}]
|
|
6201
|
+
${content}` };
|
|
6202
|
+
}
|
|
6203
|
+
function cloneEpoch(epoch) {
|
|
6204
|
+
return {
|
|
6205
|
+
...epoch,
|
|
6206
|
+
stableMessages: epoch.stableMessages.map((message2) => ({
|
|
6207
|
+
role: "system",
|
|
6208
|
+
content: message2.content,
|
|
6209
|
+
...message2.cacheBreakpoint === void 0 ? {} : { cacheBreakpoint: message2.cacheBreakpoint }
|
|
6210
|
+
})),
|
|
6211
|
+
sources: { ...epoch.sources }
|
|
6212
|
+
};
|
|
6213
|
+
}
|
|
6214
|
+
function hashMessages(messages) {
|
|
6215
|
+
return createHash4("sha256").update(JSON.stringify(messages)).digest("hex");
|
|
6216
|
+
}
|
|
6217
|
+
function tryResolveOptionalSystemSections(source) {
|
|
6218
|
+
if (source === void 0) return { available: true };
|
|
6219
|
+
try {
|
|
6220
|
+
return { available: true, sections: resolveSystemSections(source) };
|
|
6221
|
+
} catch {
|
|
6222
|
+
return { available: false };
|
|
6223
|
+
}
|
|
6224
|
+
}
|
|
5975
6225
|
function resolveSystemSections(source) {
|
|
5976
6226
|
const resolved = typeof source === "function" ? source() : source;
|
|
5977
6227
|
const sections = typeof resolved === "string" ? [resolved] : resolved;
|
|
@@ -6036,7 +6286,10 @@ function providerValidMessages(input) {
|
|
|
6036
6286
|
const availableResults = new Set(input.filter((message2) => message2.role === "tool" && message2.toolCallId).map((message2) => message2.toolCallId));
|
|
6037
6287
|
const output = [];
|
|
6038
6288
|
for (const original of input) {
|
|
6039
|
-
if (original.role === "system")
|
|
6289
|
+
if (original.role === "system") {
|
|
6290
|
+
output.push(cloneMessage2(original));
|
|
6291
|
+
continue;
|
|
6292
|
+
}
|
|
6040
6293
|
if (original.role === "assistant" && original.toolCalls !== void 0) {
|
|
6041
6294
|
const calls = original.toolCalls.filter((call) => call.id && call.name && availableResults.has(call.id));
|
|
6042
6295
|
calls.forEach((call) => announced.add(call.id));
|
|
@@ -6055,7 +6308,7 @@ function providerValidMessages(input) {
|
|
|
6055
6308
|
}
|
|
6056
6309
|
|
|
6057
6310
|
// src/context/workspace-instructions.ts
|
|
6058
|
-
import { createHash as
|
|
6311
|
+
import { createHash as createHash5 } from "crypto";
|
|
6059
6312
|
import { lstat as lstat4, readFile as readFile6, realpath as realpath2 } from "fs/promises";
|
|
6060
6313
|
import { dirname as dirname9, isAbsolute as isAbsolute4, relative as relative6, resolve as resolve6, sep as sep6 } from "path";
|
|
6061
6314
|
var NAMES = ["AGENTS.md", "CLAUDE.md", "AGENTS.local.md", "CLAUDE.local.md"];
|
|
@@ -6130,7 +6383,7 @@ var WorkspaceInstructions = class {
|
|
|
6130
6383
|
path,
|
|
6131
6384
|
depth: directory.depth,
|
|
6132
6385
|
content,
|
|
6133
|
-
digest:
|
|
6386
|
+
digest: createHash5("sha256").update(bytes).digest("hex")
|
|
6134
6387
|
});
|
|
6135
6388
|
} catch {
|
|
6136
6389
|
}
|
|
@@ -6543,10 +6796,10 @@ function sanitize(message2, secrets) {
|
|
|
6543
6796
|
}
|
|
6544
6797
|
function waitForRetry(delayMs, signal) {
|
|
6545
6798
|
if (signal?.aborted) return Promise.reject(signal.reason);
|
|
6546
|
-
return new Promise((
|
|
6799
|
+
return new Promise((resolve28, reject) => {
|
|
6547
6800
|
const timer = setTimeout(() => {
|
|
6548
6801
|
signal?.removeEventListener("abort", onAbort);
|
|
6549
|
-
|
|
6802
|
+
resolve28();
|
|
6550
6803
|
}, delayMs);
|
|
6551
6804
|
const onAbort = () => {
|
|
6552
6805
|
clearTimeout(timer);
|
|
@@ -6618,6 +6871,15 @@ var AgentLoop = class {
|
|
|
6618
6871
|
this.#iterationLimitMode = mode;
|
|
6619
6872
|
}
|
|
6620
6873
|
async *run(request) {
|
|
6874
|
+
const transientId = request.additionalContext === void 0 ? void 0 : this.#options.context.beginTransientSystem(request.additionalContext);
|
|
6875
|
+
try {
|
|
6876
|
+
yield* this.#runInternal(request);
|
|
6877
|
+
} finally {
|
|
6878
|
+
if (transientId !== void 0) this.#options.context.endTransientSystem(transientId);
|
|
6879
|
+
}
|
|
6880
|
+
}
|
|
6881
|
+
async *#runInternal(request) {
|
|
6882
|
+
this.#options.context.refreshContextSources();
|
|
6621
6883
|
this.#options.context.append(request.initialUserMessage ?? { role: "user", content: request.prompt });
|
|
6622
6884
|
let totalInputTokens = 0;
|
|
6623
6885
|
let totalOutputTokens = 0;
|
|
@@ -6695,10 +6957,7 @@ var AgentLoop = class {
|
|
|
6695
6957
|
const { adapter, model } = resolved;
|
|
6696
6958
|
const modelRequest = {
|
|
6697
6959
|
model,
|
|
6698
|
-
messages:
|
|
6699
|
-
...this.#options.context.messagesForModel(),
|
|
6700
|
-
...request.additionalContext ? [{ role: "system", content: request.additionalContext }] : []
|
|
6701
|
-
],
|
|
6960
|
+
messages: this.#options.context.messagesForModel(),
|
|
6702
6961
|
tools: [...this.#options.tools],
|
|
6703
6962
|
...request.signal === void 0 ? {} : { signal: request.signal }
|
|
6704
6963
|
};
|
|
@@ -6724,6 +6983,13 @@ var AgentLoop = class {
|
|
|
6724
6983
|
let providerError = false;
|
|
6725
6984
|
let usage;
|
|
6726
6985
|
const modelCallId = String(++modelInvocation);
|
|
6986
|
+
const journalModelId = this.#options.modelJournal?.start({
|
|
6987
|
+
agent: this.#options.agent,
|
|
6988
|
+
model: attemptedModelId,
|
|
6989
|
+
iteration,
|
|
6990
|
+
attempt,
|
|
6991
|
+
messages: modelRequest.messages
|
|
6992
|
+
});
|
|
6727
6993
|
yield { type: "model-start", id: modelCallId };
|
|
6728
6994
|
try {
|
|
6729
6995
|
for await (const event of adapter.stream(modelRequest)) {
|
|
@@ -6794,6 +7060,9 @@ var AgentLoop = class {
|
|
|
6794
7060
|
providerError = false;
|
|
6795
7061
|
}
|
|
6796
7062
|
}
|
|
7063
|
+
if (journalModelId !== void 0) {
|
|
7064
|
+
this.#options.modelJournal?.complete(journalModelId, completed, terminalError?.message);
|
|
7065
|
+
}
|
|
6797
7066
|
}
|
|
6798
7067
|
yield { type: "model-end", id: modelCallId };
|
|
6799
7068
|
if (usage !== void 0) {
|
|
@@ -7132,10 +7401,10 @@ function serializeToolInput(input) {
|
|
|
7132
7401
|
}
|
|
7133
7402
|
function waitForRetry2(delayMs, signal) {
|
|
7134
7403
|
if (signal?.aborted) return Promise.reject(signal.reason);
|
|
7135
|
-
return new Promise((
|
|
7404
|
+
return new Promise((resolve28, reject) => {
|
|
7136
7405
|
const timer = setTimeout(() => {
|
|
7137
7406
|
signal?.removeEventListener("abort", onAbort);
|
|
7138
|
-
|
|
7407
|
+
resolve28();
|
|
7139
7408
|
}, delayMs);
|
|
7140
7409
|
const onAbort = () => {
|
|
7141
7410
|
clearTimeout(timer);
|
|
@@ -7211,7 +7480,7 @@ function redactClassifierText(value) {
|
|
|
7211
7480
|
}
|
|
7212
7481
|
|
|
7213
7482
|
// src/tools/runtime.ts
|
|
7214
|
-
import { randomUUID as
|
|
7483
|
+
import { randomUUID as randomUUID7 } from "crypto";
|
|
7215
7484
|
import { mkdir as mkdir7, writeFile as writeFile4 } from "fs/promises";
|
|
7216
7485
|
import { basename as basename2, join as join7, resolve as resolve7 } from "path";
|
|
7217
7486
|
import { z as z9 } from "zod";
|
|
@@ -7252,6 +7521,7 @@ var ToolRuntime = class {
|
|
|
7252
7521
|
#workspace;
|
|
7253
7522
|
#outputLimits;
|
|
7254
7523
|
#afterSuccess;
|
|
7524
|
+
#journal;
|
|
7255
7525
|
#turnOutputChars = 0;
|
|
7256
7526
|
#disposed = false;
|
|
7257
7527
|
constructor(options) {
|
|
@@ -7264,6 +7534,7 @@ var ToolRuntime = class {
|
|
|
7264
7534
|
this.#workspace = resolve7(options.workspace ?? process.cwd());
|
|
7265
7535
|
this.#outputLimits = { ...DEFAULT_TOOL_OUTPUT_LIMITS, ...options.outputLimits };
|
|
7266
7536
|
this.#afterSuccess = options.afterSuccess;
|
|
7537
|
+
this.#journal = options.journal;
|
|
7267
7538
|
validateOutputLimit("perToolChars", this.#outputLimits.perToolChars);
|
|
7268
7539
|
validateOutputLimit("perTurnChars", this.#outputLimits.perTurnChars);
|
|
7269
7540
|
this.#disposeSchemas = [
|
|
@@ -7336,6 +7607,18 @@ var ToolRuntime = class {
|
|
|
7336
7607
|
}
|
|
7337
7608
|
}
|
|
7338
7609
|
async execute(call, context) {
|
|
7610
|
+
const definition = this.#tools.get(call.name);
|
|
7611
|
+
const journalId = definition === void 0 || this.#journal === void 0 ? void 0 : this.#journal.start(call.name, call.input, definition.retrySafe === true || definition.readOnly === true);
|
|
7612
|
+
try {
|
|
7613
|
+
const result = await this.#execute(call, context);
|
|
7614
|
+
if (journalId !== void 0) this.#journal?.complete(journalId, result);
|
|
7615
|
+
return result;
|
|
7616
|
+
} catch (error) {
|
|
7617
|
+
if (journalId !== void 0) this.#journal?.interrupt(journalId, message(error));
|
|
7618
|
+
throw error;
|
|
7619
|
+
}
|
|
7620
|
+
}
|
|
7621
|
+
async #execute(call, context) {
|
|
7339
7622
|
const tool = this.#tools.get(call.name);
|
|
7340
7623
|
if (tool === void 0) return { ok: false, error: { code: "unknown_tool", message: `Unknown tool: ${call.name}` } };
|
|
7341
7624
|
const signal = context.signal ?? new AbortController().signal;
|
|
@@ -7480,7 +7763,7 @@ var ToolRuntime = class {
|
|
|
7480
7763
|
this.#turnOutputChars += preview.length;
|
|
7481
7764
|
const directory = join7(this.#workspace, ".flavor", "tool-results");
|
|
7482
7765
|
await mkdir7(directory, { recursive: true });
|
|
7483
|
-
const filename2 = `${(/* @__PURE__ */ new Date()).toISOString().replaceAll(":", "-")}-${
|
|
7766
|
+
const filename2 = `${(/* @__PURE__ */ new Date()).toISOString().replaceAll(":", "-")}-${randomUUID7()}-${safeFilename(tool)}.txt`;
|
|
7484
7767
|
const savedTo = join7(directory, filename2);
|
|
7485
7768
|
await writeFile4(savedTo, serialized, "utf8");
|
|
7486
7769
|
return {
|
|
@@ -7505,8 +7788,8 @@ var ToolRuntime = class {
|
|
|
7505
7788
|
const limited = await this.#limitOutput(tool, content);
|
|
7506
7789
|
return typeof limited === "string" ? limited : serializeToolOutput(limited);
|
|
7507
7790
|
}
|
|
7508
|
-
async #fail(tool, input, agent, code,
|
|
7509
|
-
const result = { ok: false, error: { code, message:
|
|
7791
|
+
async #fail(tool, input, agent, code, errorMessage2) {
|
|
7792
|
+
const result = { ok: false, error: { code, message: errorMessage2 } };
|
|
7510
7793
|
try {
|
|
7511
7794
|
await this.#hooks.emit({
|
|
7512
7795
|
version: 1,
|
|
@@ -7657,6 +7940,7 @@ var LocalHarness = class {
|
|
|
7657
7940
|
workspace: this.#options.workspace,
|
|
7658
7941
|
...this.#options.afterToolSuccess === void 0 ? {} : { afterSuccess: this.#options.afterToolSuccess },
|
|
7659
7942
|
profile: this.#permissionProfile,
|
|
7943
|
+
...this.#options.permissionPolicy === void 0 ? {} : { policy: this.#options.permissionPolicy },
|
|
7660
7944
|
mode: this.#options.loopMode ? "bypassPermissions" : agent === "subagent" ? this.#mainPermissions.mode === "plan" ? "plan" : "bubble" : this.#options.permissionMode ?? "default"
|
|
7661
7945
|
});
|
|
7662
7946
|
if (agent === "main") this.#mainPermissions = permissions;
|
|
@@ -7671,7 +7955,8 @@ var LocalHarness = class {
|
|
|
7671
7955
|
registry: this.#options.registry,
|
|
7672
7956
|
modelId: () => this.#subagentModelId
|
|
7673
7957
|
})
|
|
7674
|
-
} : {}
|
|
7958
|
+
} : {},
|
|
7959
|
+
...this.#options.toolJournal === void 0 ? {} : { journal: this.#options.toolJournal }
|
|
7675
7960
|
});
|
|
7676
7961
|
try {
|
|
7677
7962
|
const tools = definitions.map(toModelTool);
|
|
@@ -7689,7 +7974,8 @@ var LocalHarness = class {
|
|
|
7689
7974
|
ownerId,
|
|
7690
7975
|
...maxIterations === void 0 ? {} : { maxIterations },
|
|
7691
7976
|
...isMain && this.#options.hasActiveProgress !== void 0 ? { hasActiveProgress: this.#options.hasActiveProgress } : {},
|
|
7692
|
-
...isMain && this.#options.hallucinationGuard !== void 0 ? { hallucinationGuard: this.#options.hallucinationGuard } : {}
|
|
7977
|
+
...isMain && this.#options.hallucinationGuard !== void 0 ? { hallucinationGuard: this.#options.hallucinationGuard } : {},
|
|
7978
|
+
...this.#options.modelJournal === void 0 ? {} : { modelJournal: this.#options.modelJournal }
|
|
7693
7979
|
});
|
|
7694
7980
|
loop.setIterationLimitMode(this.#permissionProfile === "d2c" ? "d2c" : "standard");
|
|
7695
7981
|
return { get modelId() {
|
|
@@ -7714,11 +8000,306 @@ function toModelTool(tool) {
|
|
|
7714
8000
|
return tool.modelStrict === void 0 ? modelTool : { ...modelTool, strict: tool.modelStrict };
|
|
7715
8001
|
}
|
|
7716
8002
|
|
|
8003
|
+
// src/harness/journal.ts
|
|
8004
|
+
import { createHash as createHash6, randomUUID as randomUUID8 } from "crypto";
|
|
8005
|
+
import { closeSync, constants, existsSync as existsSync2, fsyncSync, mkdirSync as mkdirSync2, openSync, readFileSync as readFileSync3, realpathSync as realpathSync2, statSync, truncateSync, writeSync } from "fs";
|
|
8006
|
+
import { join as join8, relative as relative7, resolve as resolve8, sep as sep7 } from "path";
|
|
8007
|
+
import { z as z10 } from "zod";
|
|
8008
|
+
|
|
8009
|
+
// src/harness/invariants.ts
|
|
8010
|
+
function harnessInvariantViolations(records) {
|
|
8011
|
+
const violations = [];
|
|
8012
|
+
const queues = /* @__PURE__ */ new Set();
|
|
8013
|
+
const tools = /* @__PURE__ */ new Set();
|
|
8014
|
+
const models = /* @__PURE__ */ new Set();
|
|
8015
|
+
const turns = /* @__PURE__ */ new Set();
|
|
8016
|
+
records.forEach((record, index) => {
|
|
8017
|
+
if (record.sequence !== index + 1) violations.push(`sequence ${record.sequence} is not ${index + 1}`);
|
|
8018
|
+
const id = typeof record.payload.id === "string" ? record.payload.id : "";
|
|
8019
|
+
if (record.type === "queue.admitted") {
|
|
8020
|
+
if (queues.has(id)) violations.push(`queue ${id} was admitted twice`);
|
|
8021
|
+
queues.add(id);
|
|
8022
|
+
} else if (record.type === "queue.claimed" || record.type === "queue.released") {
|
|
8023
|
+
if (!queues.has(id)) violations.push(`${record.type} references unknown queue ${id}`);
|
|
8024
|
+
} else if (record.type === "queue.acked") {
|
|
8025
|
+
if (!queues.delete(id)) violations.push(`queue.acked references unknown queue ${id}`);
|
|
8026
|
+
} else if (record.type === "tool.started") {
|
|
8027
|
+
if (tools.has(id)) violations.push(`tool ${id} was started twice`);
|
|
8028
|
+
tools.add(id);
|
|
8029
|
+
} else if (record.type === "tool.completed" || record.type === "tool.interrupted") {
|
|
8030
|
+
if (!tools.delete(id)) violations.push(`${record.type} references unknown tool ${id}`);
|
|
8031
|
+
} else if (record.type === "model.requested") {
|
|
8032
|
+
if (models.has(id)) violations.push(`model ${id} was requested twice`);
|
|
8033
|
+
if (!Array.isArray(record.payload.messages)) violations.push(`model ${id} has no replayable messages`);
|
|
8034
|
+
models.add(id);
|
|
8035
|
+
} else if (record.type === "model.completed") {
|
|
8036
|
+
if (!models.delete(id)) violations.push(`model.completed references unknown model ${id}`);
|
|
8037
|
+
} else if (record.type === "turn.started") {
|
|
8038
|
+
if (turns.has(id)) violations.push(`turn ${id} was started twice`);
|
|
8039
|
+
if (!("prompt" in record.payload)) violations.push(`turn ${id} has no replayable prompt`);
|
|
8040
|
+
turns.add(id);
|
|
8041
|
+
} else if (record.type === "turn.completed" || record.type === "turn.interrupted") {
|
|
8042
|
+
if (!turns.delete(id)) violations.push(`${record.type} references unknown turn ${id}`);
|
|
8043
|
+
}
|
|
8044
|
+
});
|
|
8045
|
+
return violations;
|
|
8046
|
+
}
|
|
8047
|
+
function assertHarnessInvariants(records) {
|
|
8048
|
+
const violations = harnessInvariantViolations(records);
|
|
8049
|
+
if (violations.length > 0) throw new Error(`Harness invariant violation: ${violations.join("; ")}`);
|
|
8050
|
+
}
|
|
8051
|
+
|
|
8052
|
+
// src/harness/journal.ts
|
|
8053
|
+
var HARNESS_JOURNAL_VERSION = 1;
|
|
8054
|
+
var MAX_JOURNAL_BYTES = 32 * 1024 * 1024;
|
|
8055
|
+
var QueueKindSchema = z10.enum(["steer", "followUp"]);
|
|
8056
|
+
var EventTypeSchema = z10.enum([
|
|
8057
|
+
"queue.admitted",
|
|
8058
|
+
"queue.claimed",
|
|
8059
|
+
"queue.acked",
|
|
8060
|
+
"queue.released",
|
|
8061
|
+
"turn.started",
|
|
8062
|
+
"turn.completed",
|
|
8063
|
+
"turn.interrupted",
|
|
8064
|
+
"model.requested",
|
|
8065
|
+
"model.completed",
|
|
8066
|
+
"tool.started",
|
|
8067
|
+
"tool.completed",
|
|
8068
|
+
"tool.interrupted",
|
|
8069
|
+
"savepoint.created",
|
|
8070
|
+
"recovery.completed"
|
|
8071
|
+
]);
|
|
8072
|
+
var JournalRecordSchema = z10.object({
|
|
8073
|
+
version: z10.literal(HARNESS_JOURNAL_VERSION),
|
|
8074
|
+
sequence: z10.number().int().positive(),
|
|
8075
|
+
id: z10.string().uuid(),
|
|
8076
|
+
timestamp: z10.string().datetime({ offset: true }),
|
|
8077
|
+
type: EventTypeSchema,
|
|
8078
|
+
payload: z10.record(z10.string(), z10.unknown()),
|
|
8079
|
+
previousHash: z10.string().regex(/^[a-f0-9]{64}$/).nullable(),
|
|
8080
|
+
hash: z10.string().regex(/^[a-f0-9]{64}$/)
|
|
8081
|
+
}).strict();
|
|
8082
|
+
var HarnessJournal = class {
|
|
8083
|
+
#path;
|
|
8084
|
+
#now;
|
|
8085
|
+
#maxBytes;
|
|
8086
|
+
#sequence = 0;
|
|
8087
|
+
#lastHash = null;
|
|
8088
|
+
#records = [];
|
|
8089
|
+
constructor(options) {
|
|
8090
|
+
const workspace = resolve8(options.workspace);
|
|
8091
|
+
const directory = join8(workspace, ".flavor", "sessions");
|
|
8092
|
+
assertSessionId(options.sessionId);
|
|
8093
|
+
mkdirSync2(directory, { recursive: true, mode: 448 });
|
|
8094
|
+
assertWithin(workspace, directory);
|
|
8095
|
+
const canonicalWorkspace = realpathSync2.native(workspace);
|
|
8096
|
+
const canonicalDirectory = realpathSync2.native(directory);
|
|
8097
|
+
if (!isWithin2(canonicalWorkspace, canonicalDirectory)) throw new Error("Harness journal directory escapes the workspace");
|
|
8098
|
+
this.#path = join8(canonicalDirectory, `${options.sessionId}.events.jsonl`);
|
|
8099
|
+
this.#now = options.now ?? (() => (/* @__PURE__ */ new Date()).toISOString());
|
|
8100
|
+
this.#maxBytes = options.maxBytes ?? MAX_JOURNAL_BYTES;
|
|
8101
|
+
if (!Number.isSafeInteger(this.#maxBytes) || this.#maxBytes < 1024) throw new Error("maxBytes must be an integer of at least 1024");
|
|
8102
|
+
this.#load();
|
|
8103
|
+
}
|
|
8104
|
+
get path() {
|
|
8105
|
+
return this.#path;
|
|
8106
|
+
}
|
|
8107
|
+
get records() {
|
|
8108
|
+
return this.#records.map((record) => structuredClone(record));
|
|
8109
|
+
}
|
|
8110
|
+
admitQueue(kind, payload) {
|
|
8111
|
+
QueueKindSchema.parse(kind);
|
|
8112
|
+
const id = randomUUID8();
|
|
8113
|
+
this.#append("queue.admitted", { id, kind, payload });
|
|
8114
|
+
return id;
|
|
8115
|
+
}
|
|
8116
|
+
claimQueue(id) {
|
|
8117
|
+
this.#append("queue.claimed", { id });
|
|
8118
|
+
}
|
|
8119
|
+
ackQueue(id) {
|
|
8120
|
+
this.#append("queue.acked", { id });
|
|
8121
|
+
}
|
|
8122
|
+
releaseQueue(id, reason) {
|
|
8123
|
+
this.#append("queue.released", { id, reason });
|
|
8124
|
+
}
|
|
8125
|
+
startTurn(config, prompt) {
|
|
8126
|
+
const id = randomUUID8();
|
|
8127
|
+
this.#append("turn.started", { id, config: structuredClone(config), configHash: hashJson(config), prompt: structuredClone(prompt), promptHash: hashJson(prompt) });
|
|
8128
|
+
return id;
|
|
8129
|
+
}
|
|
8130
|
+
completeTurn(id) {
|
|
8131
|
+
this.#append("turn.completed", { id });
|
|
8132
|
+
}
|
|
8133
|
+
interruptTurn(id, reason) {
|
|
8134
|
+
this.#append("turn.interrupted", { id, reason });
|
|
8135
|
+
}
|
|
8136
|
+
startModel(input) {
|
|
8137
|
+
const id = randomUUID8();
|
|
8138
|
+
this.#append("model.requested", { id, ...structuredClone(input), messagesHash: hashJson(input.messages) });
|
|
8139
|
+
return id;
|
|
8140
|
+
}
|
|
8141
|
+
completeModel(id, completed, error) {
|
|
8142
|
+
this.#append("model.completed", { id, completed, ...error === void 0 ? {} : { error } });
|
|
8143
|
+
}
|
|
8144
|
+
startTool(tool, input, retrySafe) {
|
|
8145
|
+
const id = randomUUID8();
|
|
8146
|
+
this.#append("tool.started", { id, tool, input: structuredClone(input), inputHash: hashJson(input), retrySafe });
|
|
8147
|
+
return id;
|
|
8148
|
+
}
|
|
8149
|
+
completeTool(id, result) {
|
|
8150
|
+
this.#append("tool.completed", { id, result: structuredClone(result) });
|
|
8151
|
+
}
|
|
8152
|
+
interruptTool(id, reason) {
|
|
8153
|
+
this.#append("tool.interrupted", { id, reason });
|
|
8154
|
+
}
|
|
8155
|
+
savepoint(phase, config) {
|
|
8156
|
+
const id = randomUUID8();
|
|
8157
|
+
this.#append("savepoint.created", { id, phase, configHash: hashJson(config) });
|
|
8158
|
+
return id;
|
|
8159
|
+
}
|
|
8160
|
+
recover() {
|
|
8161
|
+
const queue = /* @__PURE__ */ new Map();
|
|
8162
|
+
const claimed = /* @__PURE__ */ new Set();
|
|
8163
|
+
const tools = /* @__PURE__ */ new Map();
|
|
8164
|
+
const models = /* @__PURE__ */ new Set();
|
|
8165
|
+
const turns = /* @__PURE__ */ new Set();
|
|
8166
|
+
let lastSavepoint;
|
|
8167
|
+
for (const record of this.#records) {
|
|
8168
|
+
const payload = record.payload;
|
|
8169
|
+
const id = typeof payload.id === "string" ? payload.id : "";
|
|
8170
|
+
if (record.type === "queue.admitted") {
|
|
8171
|
+
const kind = QueueKindSchema.parse(payload.kind);
|
|
8172
|
+
queue.set(id, { id, kind, payload: payload.payload, admittedAt: record.timestamp, recovered: false });
|
|
8173
|
+
} else if (record.type === "queue.claimed") claimed.add(id);
|
|
8174
|
+
else if (record.type === "queue.acked") {
|
|
8175
|
+
queue.delete(id);
|
|
8176
|
+
claimed.delete(id);
|
|
8177
|
+
} else if (record.type === "queue.released") claimed.delete(id);
|
|
8178
|
+
else if (record.type === "turn.started") turns.add(id);
|
|
8179
|
+
else if (record.type === "turn.completed" || record.type === "turn.interrupted") turns.delete(id);
|
|
8180
|
+
else if (record.type === "model.requested") models.add(id);
|
|
8181
|
+
else if (record.type === "model.completed") models.delete(id);
|
|
8182
|
+
else if (record.type === "tool.started") tools.set(id, {
|
|
8183
|
+
id,
|
|
8184
|
+
tool: String(payload.tool ?? "unknown"),
|
|
8185
|
+
retrySafe: payload.retrySafe === true,
|
|
8186
|
+
inputHash: String(payload.inputHash ?? "")
|
|
8187
|
+
});
|
|
8188
|
+
else if (record.type === "tool.completed" || record.type === "tool.interrupted") tools.delete(id);
|
|
8189
|
+
else if (record.type === "savepoint.created") lastSavepoint = {
|
|
8190
|
+
id,
|
|
8191
|
+
phase: String(payload.phase ?? "unknown"),
|
|
8192
|
+
configHash: String(payload.configHash ?? "")
|
|
8193
|
+
};
|
|
8194
|
+
}
|
|
8195
|
+
const recovered = [...queue.values()].map((item) => ({ ...item, recovered: claimed.has(item.id) }));
|
|
8196
|
+
return {
|
|
8197
|
+
queue: recovered,
|
|
8198
|
+
incompleteTools: [...tools.values()],
|
|
8199
|
+
incompleteModelIds: [...models],
|
|
8200
|
+
interruptedTurnIds: [...turns],
|
|
8201
|
+
...lastSavepoint === void 0 ? {} : { lastSavepoint }
|
|
8202
|
+
};
|
|
8203
|
+
}
|
|
8204
|
+
markRecoveryComplete(recovery) {
|
|
8205
|
+
for (const item of recovery.queue) if (item.recovered) this.releaseQueue(item.id, "process-recovery");
|
|
8206
|
+
for (const tool of recovery.incompleteTools) {
|
|
8207
|
+
this.interruptTool(tool.id, tool.retrySafe ? "interrupted by process exit; eligible only for explicit retry" : "interrupted by process exit; automatic replay forbidden");
|
|
8208
|
+
}
|
|
8209
|
+
for (const id of recovery.incompleteModelIds) this.completeModel(id, false, "interrupted by process exit");
|
|
8210
|
+
for (const id of recovery.interruptedTurnIds) this.interruptTurn(id, "interrupted by process exit");
|
|
8211
|
+
this.#append("recovery.completed", {
|
|
8212
|
+
queueItems: recovery.queue.length,
|
|
8213
|
+
interruptedTools: recovery.incompleteTools.length,
|
|
8214
|
+
interruptedModels: recovery.incompleteModelIds.length,
|
|
8215
|
+
interruptedTurns: recovery.interruptedTurnIds.length
|
|
8216
|
+
});
|
|
8217
|
+
}
|
|
8218
|
+
#load() {
|
|
8219
|
+
if (!existsSync2(this.#path)) return;
|
|
8220
|
+
const metadata = statSync(this.#path);
|
|
8221
|
+
if (!metadata.isFile()) throw new Error("Harness journal path is not a regular file");
|
|
8222
|
+
if (metadata.size > this.#maxBytes) throw new Error(`Harness journal exceeds ${this.#maxBytes} bytes`);
|
|
8223
|
+
const raw = readFileSync3(this.#path, "utf8");
|
|
8224
|
+
const committed = raw.endsWith("\n") ? raw : raw.slice(0, Math.max(0, raw.lastIndexOf("\n") + 1));
|
|
8225
|
+
if (committed.length !== raw.length) truncateSync(this.#path, Buffer.byteLength(committed));
|
|
8226
|
+
const lines = committed.split("\n").filter(Boolean);
|
|
8227
|
+
let previousHash = null;
|
|
8228
|
+
for (const line of lines) {
|
|
8229
|
+
const record = JournalRecordSchema.parse(JSON.parse(line));
|
|
8230
|
+
if (record.sequence !== this.#records.length + 1) throw new Error("Harness journal sequence is not contiguous");
|
|
8231
|
+
if (record.previousHash !== previousHash) throw new Error("Harness journal hash chain is broken");
|
|
8232
|
+
if (record.hash !== recordHash(record)) throw new Error("Harness journal record hash is invalid");
|
|
8233
|
+
this.#records.push(record);
|
|
8234
|
+
previousHash = record.hash;
|
|
8235
|
+
}
|
|
8236
|
+
this.#sequence = this.#records.length;
|
|
8237
|
+
this.#lastHash = previousHash;
|
|
8238
|
+
assertHarnessInvariants(this.#records);
|
|
8239
|
+
}
|
|
8240
|
+
#append(type, payload) {
|
|
8241
|
+
const normalizedPayload = jsonSafeRecord(payload);
|
|
8242
|
+
const unsigned = {
|
|
8243
|
+
version: HARNESS_JOURNAL_VERSION,
|
|
8244
|
+
sequence: this.#sequence + 1,
|
|
8245
|
+
id: randomUUID8(),
|
|
8246
|
+
timestamp: this.#now(),
|
|
8247
|
+
type,
|
|
8248
|
+
payload: normalizedPayload,
|
|
8249
|
+
previousHash: this.#lastHash
|
|
8250
|
+
};
|
|
8251
|
+
const record = JournalRecordSchema.parse({ ...unsigned, hash: hashJson(unsigned) });
|
|
8252
|
+
const line = `${JSON.stringify(record)}
|
|
8253
|
+
`;
|
|
8254
|
+
const priorBytes = existsSync2(this.#path) ? statSync(this.#path).size : 0;
|
|
8255
|
+
if (priorBytes + Buffer.byteLength(line) > this.#maxBytes) throw new Error(`Harness journal exceeds ${this.#maxBytes} bytes`);
|
|
8256
|
+
const fd = openSync(this.#path, constants.O_CREAT | constants.O_APPEND | constants.O_WRONLY, 384);
|
|
8257
|
+
try {
|
|
8258
|
+
writeSync(fd, line, void 0, "utf8");
|
|
8259
|
+
fsyncSync(fd);
|
|
8260
|
+
} finally {
|
|
8261
|
+
closeSync(fd);
|
|
8262
|
+
}
|
|
8263
|
+
this.#records.push(record);
|
|
8264
|
+
this.#sequence = record.sequence;
|
|
8265
|
+
this.#lastHash = record.hash;
|
|
8266
|
+
}
|
|
8267
|
+
};
|
|
8268
|
+
function jsonSafeRecord(value) {
|
|
8269
|
+
const json = JSON.stringify(value, (_key, item) => typeof item === "bigint" ? `${item}n` : item);
|
|
8270
|
+
return JSON.parse(json);
|
|
8271
|
+
}
|
|
8272
|
+
function hashJson(value) {
|
|
8273
|
+
return createHash6("sha256").update(stableJson(value)).digest("hex");
|
|
8274
|
+
}
|
|
8275
|
+
function recordHash(record) {
|
|
8276
|
+
const { hash: _hash, ...unsigned } = record;
|
|
8277
|
+
return hashJson(unsigned);
|
|
8278
|
+
}
|
|
8279
|
+
function stableJson(value) {
|
|
8280
|
+
if (Array.isArray(value)) return `[${value.map(stableJson).join(",")}]`;
|
|
8281
|
+
if (typeof value === "object" && value !== null) {
|
|
8282
|
+
const object = value;
|
|
8283
|
+
return `{${Object.keys(object).sort().map((key) => `${JSON.stringify(key)}:${stableJson(object[key])}`).join(",")}}`;
|
|
8284
|
+
}
|
|
8285
|
+
return JSON.stringify(value) ?? String(value);
|
|
8286
|
+
}
|
|
8287
|
+
function assertSessionId(value) {
|
|
8288
|
+
if (!/^[A-Za-z0-9][A-Za-z0-9._-]{0,127}$/.test(value)) throw new Error("Invalid session id");
|
|
8289
|
+
}
|
|
8290
|
+
function assertWithin(root, target) {
|
|
8291
|
+
if (!isWithin2(resolve8(root), resolve8(target))) throw new Error("Harness journal path escapes the workspace");
|
|
8292
|
+
}
|
|
8293
|
+
function isWithin2(root, target) {
|
|
8294
|
+
const rel = relative7(root, target);
|
|
8295
|
+
return rel === "" || !rel.startsWith("..") && rel !== ".." && !rel.startsWith(`..${sep7}`);
|
|
8296
|
+
}
|
|
8297
|
+
|
|
7717
8298
|
// src/hooks/bus.ts
|
|
7718
8299
|
import { spawn as spawn4 } from "child_process";
|
|
7719
8300
|
|
|
7720
8301
|
// src/hooks/types.ts
|
|
7721
|
-
import { z as
|
|
8302
|
+
import { z as z11 } from "zod";
|
|
7722
8303
|
var HOOK_EVENT_NAMES = [
|
|
7723
8304
|
"SessionStart",
|
|
7724
8305
|
"UserPromptSubmit",
|
|
@@ -7741,13 +8322,13 @@ var HOOK_EVENT_NAMES = [
|
|
|
7741
8322
|
"Notification",
|
|
7742
8323
|
"LoopEnd"
|
|
7743
8324
|
];
|
|
7744
|
-
var PayloadSchema =
|
|
7745
|
-
var eventSchema = (type) =>
|
|
7746
|
-
version:
|
|
7747
|
-
type:
|
|
8325
|
+
var PayloadSchema = z11.record(z11.string(), z11.unknown());
|
|
8326
|
+
var eventSchema = (type) => z11.object({
|
|
8327
|
+
version: z11.literal(1),
|
|
8328
|
+
type: z11.literal(type),
|
|
7748
8329
|
payload: PayloadSchema
|
|
7749
8330
|
});
|
|
7750
|
-
var HookEventSchema =
|
|
8331
|
+
var HookEventSchema = z11.discriminatedUnion("type", [
|
|
7751
8332
|
eventSchema("SessionStart"),
|
|
7752
8333
|
eventSchema("UserPromptSubmit"),
|
|
7753
8334
|
eventSchema("Stop"),
|
|
@@ -7769,11 +8350,11 @@ var HookEventSchema = z10.discriminatedUnion("type", [
|
|
|
7769
8350
|
eventSchema("Notification"),
|
|
7770
8351
|
eventSchema("LoopEnd")
|
|
7771
8352
|
]);
|
|
7772
|
-
var HookDecisionSchema =
|
|
7773
|
-
decision:
|
|
7774
|
-
reason:
|
|
7775
|
-
updatedInput:
|
|
7776
|
-
additionalContext:
|
|
8353
|
+
var HookDecisionSchema = z11.object({
|
|
8354
|
+
decision: z11.enum(["allow", "deny", "ask"]),
|
|
8355
|
+
reason: z11.string().optional(),
|
|
8356
|
+
updatedInput: z11.unknown().optional(),
|
|
8357
|
+
additionalContext: z11.string().optional()
|
|
7777
8358
|
});
|
|
7778
8359
|
|
|
7779
8360
|
// src/hooks/bus.ts
|
|
@@ -7863,7 +8444,7 @@ var HookBus = class {
|
|
|
7863
8444
|
}
|
|
7864
8445
|
};
|
|
7865
8446
|
function runShellHandler(descriptor, event, signal) {
|
|
7866
|
-
return new Promise((
|
|
8447
|
+
return new Promise((resolve28, reject) => {
|
|
7867
8448
|
const child = spawn4(descriptor.command, [...descriptor.args ?? []], {
|
|
7868
8449
|
env: { ...process.env, ...descriptor.env },
|
|
7869
8450
|
shell: false,
|
|
@@ -7906,7 +8487,7 @@ function runShellHandler(descriptor, event, signal) {
|
|
|
7906
8487
|
return;
|
|
7907
8488
|
}
|
|
7908
8489
|
try {
|
|
7909
|
-
|
|
8490
|
+
resolve28(HookDecisionSchema.parse(JSON.parse(stdout)));
|
|
7910
8491
|
} catch (error) {
|
|
7911
8492
|
reject(error);
|
|
7912
8493
|
}
|
|
@@ -8057,27 +8638,28 @@ function createIncidentReporter(options) {
|
|
|
8057
8638
|
// src/evolve/service.ts
|
|
8058
8639
|
import { spawn as spawn5 } from "child_process";
|
|
8059
8640
|
import { writeFile as writeFile8 } from "fs/promises";
|
|
8060
|
-
import { join as
|
|
8061
|
-
import { z as
|
|
8641
|
+
import { join as join11 } from "path";
|
|
8642
|
+
import { z as z14 } from "zod";
|
|
8062
8643
|
|
|
8063
8644
|
// src/evolve/loader.ts
|
|
8064
8645
|
import { cp, mkdir as mkdir8, mkdtemp, readdir as readdir4, rm as rm2, stat as stat4, writeFile as writeFile6 } from "fs/promises";
|
|
8065
8646
|
import { tmpdir as tmpdir2 } from "os";
|
|
8066
|
-
import { join as
|
|
8647
|
+
import { join as join9, resolve as resolve10 } from "path";
|
|
8067
8648
|
|
|
8068
8649
|
// src/plugins/host.ts
|
|
8069
|
-
import {
|
|
8070
|
-
import {
|
|
8071
|
-
import {
|
|
8650
|
+
import { createHash as createHash7 } from "crypto";
|
|
8651
|
+
import { lstatSync, realpathSync as realpathSync3 } from "fs";
|
|
8652
|
+
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";
|
|
8653
|
+
import { isAbsolute as isAbsolute5, relative as relative8, resolve as resolve9, sep as sep8 } from "path";
|
|
8072
8654
|
import { pathToFileURL } from "url";
|
|
8073
|
-
import { z as
|
|
8655
|
+
import { z as z13 } from "zod";
|
|
8074
8656
|
|
|
8075
8657
|
// src/plugins/types.ts
|
|
8076
|
-
import { z as
|
|
8658
|
+
import { z as z12 } from "zod";
|
|
8077
8659
|
var PLUGIN_NAME = /^(?:@[a-z0-9][a-z0-9._-]{0,63}\/)?[a-z0-9][a-z0-9._-]{0,63}$/;
|
|
8078
8660
|
var CONTRIBUTION_NAME = /^[a-zA-Z0-9][a-zA-Z0-9._:-]{0,127}$/;
|
|
8079
|
-
var entry =
|
|
8080
|
-
var uniqueArray = (schema, label, identify = (item) => String(item)) =>
|
|
8661
|
+
var entry = z12.object({ name: z12.string().regex(CONTRIBUTION_NAME) }).strict();
|
|
8662
|
+
var uniqueArray = (schema, label, identify = (item) => String(item)) => z12.array(schema).max(256).superRefine((items, context) => {
|
|
8081
8663
|
const seen = /* @__PURE__ */ new Set();
|
|
8082
8664
|
for (const [index, item] of items.entries()) {
|
|
8083
8665
|
const key = identify(item);
|
|
@@ -8085,17 +8667,23 @@ var uniqueArray = (schema, label, identify = (item) => String(item)) => z11.arra
|
|
|
8085
8667
|
seen.add(key);
|
|
8086
8668
|
}
|
|
8087
8669
|
});
|
|
8088
|
-
var PluginManifestSchema =
|
|
8089
|
-
name:
|
|
8090
|
-
version:
|
|
8091
|
-
apiVersion:
|
|
8092
|
-
main:
|
|
8093
|
-
permissions: uniqueArray(
|
|
8094
|
-
|
|
8670
|
+
var PluginManifestSchema = z12.object({
|
|
8671
|
+
name: z12.string().regex(PLUGIN_NAME),
|
|
8672
|
+
version: z12.string().min(1).max(64),
|
|
8673
|
+
apiVersion: z12.literal("1"),
|
|
8674
|
+
main: z12.string().min(1).max(512),
|
|
8675
|
+
permissions: uniqueArray(z12.enum([
|
|
8676
|
+
"filesystem:read",
|
|
8677
|
+
"filesystem:write",
|
|
8678
|
+
"network",
|
|
8679
|
+
"process",
|
|
8680
|
+
"runtime:in-process"
|
|
8681
|
+
]), "permission").max(16),
|
|
8682
|
+
contributes: z12.object({
|
|
8095
8683
|
commands: uniqueArray(entry, "command contribution", (item) => item.name),
|
|
8096
8684
|
tools: uniqueArray(entry, "tool contribution", (item) => item.name),
|
|
8097
|
-
hooks: uniqueArray(
|
|
8098
|
-
skillRoots: uniqueArray(entry.extend({ path:
|
|
8685
|
+
hooks: uniqueArray(z12.object({ name: z12.enum(HOOK_EVENT_NAMES) }).strict(), "hook contribution", (item) => item.name),
|
|
8686
|
+
skillRoots: uniqueArray(entry.extend({ path: z12.string().min(1).max(512) }).strict(), "skill-root contribution", (item) => item.name),
|
|
8099
8687
|
modelAdapters: uniqueArray(entry, "model-adapter contribution", (item) => item.name)
|
|
8100
8688
|
}).strict()
|
|
8101
8689
|
}).strict();
|
|
@@ -8274,8 +8862,8 @@ function isSandboxResult(value) {
|
|
|
8274
8862
|
if (!Array.isArray(contributions.hooks) || !contributions.hooks.every(named)) return false;
|
|
8275
8863
|
if (!Array.isArray(contributions.skillRoots) || !contributions.skillRoots.every((item) => named(item) && typeof item.root === "string")) return false;
|
|
8276
8864
|
if (!Array.isArray(contributions.modelAdapters) || !contributions.modelAdapters.every(named)) return false;
|
|
8277
|
-
const
|
|
8278
|
-
return
|
|
8865
|
+
const same2 = (left, right) => left.length === right.length && left.every((name, index) => name === right[index]?.name);
|
|
8866
|
+
return same2(result.registeredCommands, contributions.commands) && same2(result.registeredTools, contributions.tools) && same2(result.registeredHooks, contributions.hooks) && same2(result.registeredSkillRoots, contributions.skillRoots) && same2(result.registeredModelAdapters, contributions.modelAdapters);
|
|
8279
8867
|
}
|
|
8280
8868
|
|
|
8281
8869
|
// src/plugins/host.ts
|
|
@@ -8380,7 +8968,7 @@ var PluginHost = class {
|
|
|
8380
8968
|
const roots = [];
|
|
8381
8969
|
for (const directory of directories) {
|
|
8382
8970
|
try {
|
|
8383
|
-
for (const entry2 of await readdir3(directory, { withFileTypes: true })) if (entry2.isDirectory()) roots.push(
|
|
8971
|
+
for (const entry2 of await readdir3(directory, { withFileTypes: true })) if (entry2.isDirectory()) roots.push(resolve9(directory, entry2.name));
|
|
8384
8972
|
} catch (error) {
|
|
8385
8973
|
this.#diagnose(directory, error, directory);
|
|
8386
8974
|
}
|
|
@@ -8413,14 +9001,15 @@ var PluginHost = class {
|
|
|
8413
9001
|
const rootInfo = await lstat5(inputRoot);
|
|
8414
9002
|
if (!rootInfo.isDirectory() || rootInfo.isSymbolicLink()) throw new Error("Plugin root must be a static directory");
|
|
8415
9003
|
const root = await realpath4(inputRoot);
|
|
8416
|
-
const rawManifest = await readManifest(
|
|
9004
|
+
const rawManifest = await readManifest(resolve9(root, MANIFEST));
|
|
8417
9005
|
if (typeof rawManifest === "object" && rawManifest !== null && "name" in rawManifest && typeof rawManifest.name === "string") {
|
|
8418
9006
|
diagnosticLabel = rawManifest.name;
|
|
8419
9007
|
}
|
|
8420
9008
|
const manifest = PluginManifestSchema.parse(rawManifest);
|
|
8421
9009
|
const entry2 = resolveContained(root, manifest.main, "Plugin entry");
|
|
8422
9010
|
const entrySnapshot = await snapshotEntry(root, entry2);
|
|
8423
|
-
|
|
9011
|
+
const fingerprint2 = createHash7("sha256").update(JSON.stringify(manifest)).update(await readFile7(entrySnapshot.physicalPath)).digest("hex");
|
|
9012
|
+
return { manifest, root, source, entry: entry2, entrySnapshot, fingerprint: fingerprint2 };
|
|
8424
9013
|
} catch (error) {
|
|
8425
9014
|
this.#diagnose(diagnosticLabel, error, inputRoot);
|
|
8426
9015
|
return void 0;
|
|
@@ -8435,6 +9024,22 @@ var PluginHost = class {
|
|
|
8435
9024
|
const useSandbox = this.#options.sandbox === true;
|
|
8436
9025
|
try {
|
|
8437
9026
|
await verifyEntry(candidate.root, candidate.entry, candidate.entrySnapshot);
|
|
9027
|
+
if (!useSandbox && this.#options.requireInProcessTrust === true && !candidate.manifest.permissions.includes("runtime:in-process")) {
|
|
9028
|
+
throw new Error("Legacy in-process plugins must declare runtime:in-process");
|
|
9029
|
+
}
|
|
9030
|
+
const trustRequest = {
|
|
9031
|
+
name: plugin,
|
|
9032
|
+
version: candidate.manifest.version,
|
|
9033
|
+
source: candidate.source,
|
|
9034
|
+
root: candidate.root,
|
|
9035
|
+
fingerprint: candidate.fingerprint,
|
|
9036
|
+
sandboxed: useSandbox,
|
|
9037
|
+
permissions: [...candidate.manifest.permissions]
|
|
9038
|
+
};
|
|
9039
|
+
const trusted = await this.#options.authorizePlugin?.(trustRequest);
|
|
9040
|
+
if (trusted === false || !useSandbox && this.#options.requireInProcessTrust === true && trusted !== true) {
|
|
9041
|
+
throw new Error(`Plugin "${plugin}" is not trusted for ${useSandbox ? "sandboxed" : "in-process"} activation`);
|
|
9042
|
+
}
|
|
8438
9043
|
if (useSandbox) {
|
|
8439
9044
|
const session = await bounded(
|
|
8440
9045
|
startPluginSandbox({
|
|
@@ -8462,7 +9067,7 @@ var PluginHost = class {
|
|
|
8462
9067
|
const tool = {
|
|
8463
9068
|
name: contribution.toolName,
|
|
8464
9069
|
description: contribution.description,
|
|
8465
|
-
inputSchema:
|
|
9070
|
+
inputSchema: z13.unknown(),
|
|
8466
9071
|
...contribution.modelInputSchema === void 0 ? {} : { modelInputSchema: contribution.modelInputSchema },
|
|
8467
9072
|
...contribution.readOnly === void 0 ? {} : { readOnly: contribution.readOnly },
|
|
8468
9073
|
paths: () => [],
|
|
@@ -8507,7 +9112,15 @@ var PluginHost = class {
|
|
|
8507
9112
|
}
|
|
8508
9113
|
}
|
|
8509
9114
|
this.#assertAllDeclaredRegistered(candidate, claimed);
|
|
8510
|
-
const metadata = {
|
|
9115
|
+
const metadata = {
|
|
9116
|
+
name: plugin,
|
|
9117
|
+
version: candidate.manifest.version,
|
|
9118
|
+
source: candidate.source,
|
|
9119
|
+
root: candidate.root,
|
|
9120
|
+
fingerprint: candidate.fingerprint,
|
|
9121
|
+
sandboxed: useSandbox,
|
|
9122
|
+
permissions: [...candidate.manifest.permissions]
|
|
9123
|
+
};
|
|
8511
9124
|
this.#active.push({ metadata, disposers, state });
|
|
8512
9125
|
try {
|
|
8513
9126
|
await bounded(Promise.resolve(this.#options.emitLifecycle?.("PluginLoad", metadata)), this.#activationTimeoutMs, "PluginLoad lifecycle");
|
|
@@ -8573,7 +9186,7 @@ var PluginHost = class {
|
|
|
8573
9186
|
assertActive();
|
|
8574
9187
|
const permission = `filesystem:${operation}`;
|
|
8575
9188
|
if (!manifest.permissions.includes(permission)) throw new Error(`Plugin ${manifest.name} lacks ${permission} permission`);
|
|
8576
|
-
const path =
|
|
9189
|
+
const path = resolve9(rawPath);
|
|
8577
9190
|
const authorized = await this.#options.authorizeFilesystem?.({ plugin: manifest.name, operation, path });
|
|
8578
9191
|
assertActive();
|
|
8579
9192
|
if (authorized !== true) throw new Error(`Filesystem ${operation} denied for plugin ${manifest.name}`);
|
|
@@ -8612,7 +9225,7 @@ var PluginHost = class {
|
|
|
8612
9225
|
if (expected !== registered) throw new Error(`Registered skill root "${name}" does not match its declaration`);
|
|
8613
9226
|
const info = lstatSync(registered, { bigint: true });
|
|
8614
9227
|
if (!info.isDirectory() || info.isSymbolicLink()) throw new Error(`Skill root "${name}" must be a static directory`);
|
|
8615
|
-
const physical =
|
|
9228
|
+
const physical = realpathSync3.native(registered);
|
|
8616
9229
|
assertContained(candidate.root, physical, "Skill root");
|
|
8617
9230
|
const snapshot4 = fromStats(info);
|
|
8618
9231
|
const capability = Object.freeze({ path: physical, identity: Object.freeze(pickIdentity(snapshot4)) });
|
|
@@ -8705,13 +9318,13 @@ function assertSnapshot(expected, actual, label) {
|
|
|
8705
9318
|
}
|
|
8706
9319
|
function resolveContained(root, value, label) {
|
|
8707
9320
|
if (isAbsolute5(value)) throw new Error(`${label} must be relative to the plugin root`);
|
|
8708
|
-
const candidate =
|
|
9321
|
+
const candidate = resolve9(root, value);
|
|
8709
9322
|
assertContained(root, candidate, label);
|
|
8710
9323
|
return candidate;
|
|
8711
9324
|
}
|
|
8712
9325
|
function assertContained(root, candidate, label) {
|
|
8713
|
-
const delta =
|
|
8714
|
-
if (delta === ".." || delta.startsWith(`..${
|
|
9326
|
+
const delta = relative8(root, candidate);
|
|
9327
|
+
if (delta === ".." || delta.startsWith(`..${sep8}`) || isAbsolute5(delta)) throw new Error(`${label} escapes the plugin root`);
|
|
8715
9328
|
}
|
|
8716
9329
|
function scopedLogger(logger, plugin) {
|
|
8717
9330
|
const prefix = `[plugin:${plugin}] `;
|
|
@@ -8754,17 +9367,17 @@ function compare(left, right) {
|
|
|
8754
9367
|
}
|
|
8755
9368
|
|
|
8756
9369
|
// src/evolve/loader.ts
|
|
8757
|
-
var PLUGIN_DIR =
|
|
8758
|
-
var VERSIONS_DIR =
|
|
9370
|
+
var PLUGIN_DIR = join9(".flavor", "plugins");
|
|
9371
|
+
var VERSIONS_DIR = join9(".flavor", "plugins", ".versions");
|
|
8759
9372
|
function sanitizePluginName(tool) {
|
|
8760
9373
|
const base = String(tool ?? "fix").replace(/[^a-z0-9-]/gi, "-").toLowerCase();
|
|
8761
9374
|
return base.startsWith("fix-") ? base : `fix-${base}`;
|
|
8762
9375
|
}
|
|
8763
9376
|
function fixPluginDir(workspace, name) {
|
|
8764
|
-
return
|
|
9377
|
+
return resolve10(workspace, PLUGIN_DIR, name);
|
|
8765
9378
|
}
|
|
8766
9379
|
function versionsDirFor(workspace, name) {
|
|
8767
|
-
return
|
|
9380
|
+
return resolve10(workspace, VERSIONS_DIR, name);
|
|
8768
9381
|
}
|
|
8769
9382
|
function timestamp() {
|
|
8770
9383
|
return (/* @__PURE__ */ new Date()).toISOString().replaceAll(":", "-");
|
|
@@ -8799,8 +9412,8 @@ export function activate(ctx) {
|
|
|
8799
9412
|
async function scaffoldFixPlugin(workspace, name) {
|
|
8800
9413
|
const dir = fixPluginDir(workspace, name);
|
|
8801
9414
|
await mkdir8(dir, { recursive: true });
|
|
8802
|
-
const manifestPath =
|
|
8803
|
-
const entryPath =
|
|
9415
|
+
const manifestPath = join9(dir, "flavor-plugin.json");
|
|
9416
|
+
const entryPath = join9(dir, "index.js");
|
|
8804
9417
|
try {
|
|
8805
9418
|
await writeFile6(manifestPath, SCAFFOLD_MANIFEST(name), { encoding: "utf8", flag: "wx" });
|
|
8806
9419
|
await writeFile6(entryPath, SCAFFOLD_ENTRY(name), { encoding: "utf8", flag: "wx" });
|
|
@@ -8820,9 +9433,9 @@ async function verifyFixPlugin(workspace, name) {
|
|
|
8820
9433
|
return { ok: false, provided: [], tools: [], commands: [], error: `Plugin "${name}" not found.` };
|
|
8821
9434
|
}
|
|
8822
9435
|
const collected = { tools: [], commands: [] };
|
|
8823
|
-
const sandbox = await mkdtemp(
|
|
9436
|
+
const sandbox = await mkdtemp(join9(tmpdir2(), "flavor-evolve-verify-"));
|
|
8824
9437
|
try {
|
|
8825
|
-
await cp(pluginRoot,
|
|
9438
|
+
await cp(pluginRoot, join9(sandbox, name), { recursive: true });
|
|
8826
9439
|
const host = new PluginHost({
|
|
8827
9440
|
projectPluginDirs: [sandbox],
|
|
8828
9441
|
sandbox: true,
|
|
@@ -8873,7 +9486,7 @@ async function verifyFixPlugin(workspace, name) {
|
|
|
8873
9486
|
}
|
|
8874
9487
|
async function snapshotFixPlugin(workspace, name) {
|
|
8875
9488
|
const pluginRoot = fixPluginDir(workspace, name);
|
|
8876
|
-
const snapshotRoot =
|
|
9489
|
+
const snapshotRoot = join9(versionsDirFor(workspace, name), timestamp());
|
|
8877
9490
|
await mkdir8(snapshotRoot, { recursive: true });
|
|
8878
9491
|
await cp(pluginRoot, snapshotRoot, { recursive: true });
|
|
8879
9492
|
return snapshotRoot;
|
|
@@ -8894,7 +9507,7 @@ async function revertFixPlugin(workspace, name) {
|
|
|
8894
9507
|
}
|
|
8895
9508
|
const snapshots = entries.filter((entry2) => !entry2.startsWith(".")).sort();
|
|
8896
9509
|
if (snapshots.length === 0) throw new Error(`Plugin "${name}" has no snapshot to restore.`);
|
|
8897
|
-
const latest =
|
|
9510
|
+
const latest = join9(versionsRoot, snapshots[snapshots.length - 1]);
|
|
8898
9511
|
await rm2(pluginRoot, { recursive: true, force: true });
|
|
8899
9512
|
await mkdir8(pluginRoot, { recursive: true });
|
|
8900
9513
|
await cp(latest, pluginRoot, { recursive: true });
|
|
@@ -8902,14 +9515,14 @@ async function revertFixPlugin(workspace, name) {
|
|
|
8902
9515
|
}
|
|
8903
9516
|
|
|
8904
9517
|
// src/evolve/store.ts
|
|
8905
|
-
import { createHash as
|
|
8906
|
-
import { appendFile, mkdir as mkdir9, readFile as
|
|
8907
|
-
import { dirname as dirname11, join as
|
|
9518
|
+
import { createHash as createHash8 } from "crypto";
|
|
9519
|
+
import { appendFile, mkdir as mkdir9, readFile as readFile8, rm as rm3, writeFile as writeFile7 } from "fs/promises";
|
|
9520
|
+
import { dirname as dirname11, join as join10 } from "path";
|
|
8908
9521
|
function normalizeError(message2) {
|
|
8909
9522
|
return String(message2 ?? "").replace(/\s+/g, " ").replace(/"[^"]*"/g, '"\u2026"').replace(/`[^`]*`/g, '"\u2026"').trim().slice(0, 160);
|
|
8910
9523
|
}
|
|
8911
9524
|
function fingerprint(tool, errorCode, error) {
|
|
8912
|
-
return
|
|
9525
|
+
return createHash8("sha1").update(`${tool}::${errorCode ?? ""}::${normalizeError(error)}`).digest("hex").slice(0, 12);
|
|
8913
9526
|
}
|
|
8914
9527
|
function argKeys(args) {
|
|
8915
9528
|
if (args === null || typeof args !== "object") return [];
|
|
@@ -8917,7 +9530,7 @@ function argKeys(args) {
|
|
|
8917
9530
|
}
|
|
8918
9531
|
async function readJsonLines(file) {
|
|
8919
9532
|
try {
|
|
8920
|
-
const text = await
|
|
9533
|
+
const text = await readFile8(file, "utf8");
|
|
8921
9534
|
const records = [];
|
|
8922
9535
|
for (const line of text.split("\n")) {
|
|
8923
9536
|
const trimmed = line.trim();
|
|
@@ -8940,7 +9553,7 @@ async function writeJsonLines(file, records) {
|
|
|
8940
9553
|
}
|
|
8941
9554
|
async function readJsonArray(file) {
|
|
8942
9555
|
try {
|
|
8943
|
-
const parsed = JSON.parse(await
|
|
9556
|
+
const parsed = JSON.parse(await readFile8(file, "utf8"));
|
|
8944
9557
|
return Array.isArray(parsed) ? parsed.filter((item) => typeof item === "string") : [];
|
|
8945
9558
|
} catch {
|
|
8946
9559
|
return [];
|
|
@@ -8948,7 +9561,7 @@ async function readJsonArray(file) {
|
|
|
8948
9561
|
}
|
|
8949
9562
|
async function readRules(file) {
|
|
8950
9563
|
try {
|
|
8951
|
-
const parsed = JSON.parse(await
|
|
9564
|
+
const parsed = JSON.parse(await readFile8(file, "utf8"));
|
|
8952
9565
|
if (!Array.isArray(parsed)) return [];
|
|
8953
9566
|
return parsed.filter((item) => item !== null && typeof item === "object").filter((item) => typeof item.id === "string" && typeof item.text === "string").map((item) => ({
|
|
8954
9567
|
id: item.id,
|
|
@@ -8977,12 +9590,12 @@ var EvolveStore = class {
|
|
|
8977
9590
|
#queue = Promise.resolve();
|
|
8978
9591
|
constructor(options) {
|
|
8979
9592
|
const { workspace, maxSignals = 400, maxRules = 20 } = options;
|
|
8980
|
-
this.dir =
|
|
8981
|
-
this.signalsFile =
|
|
8982
|
-
this.reflectionsFile =
|
|
8983
|
-
this.doneFile =
|
|
8984
|
-
this.verifiedFile =
|
|
8985
|
-
this.rulesFile =
|
|
9593
|
+
this.dir = join10(workspace, ".flavor", "evolve");
|
|
9594
|
+
this.signalsFile = join10(this.dir, "signals.jsonl");
|
|
9595
|
+
this.reflectionsFile = join10(this.dir, "reflections.jsonl");
|
|
9596
|
+
this.doneFile = join10(this.dir, "done.json");
|
|
9597
|
+
this.verifiedFile = join10(this.dir, "verified.json");
|
|
9598
|
+
this.rulesFile = join10(this.dir, "rules.json");
|
|
8986
9599
|
this.maxSignals = maxSignals;
|
|
8987
9600
|
this.maxRules = maxRules;
|
|
8988
9601
|
}
|
|
@@ -9201,7 +9814,7 @@ var USAGE = [
|
|
|
9201
9814
|
" clear reset signals, done markers, and verified markers"
|
|
9202
9815
|
].join("\n");
|
|
9203
9816
|
function runCommand(command, cwd, timeoutMs) {
|
|
9204
|
-
return new Promise((
|
|
9817
|
+
return new Promise((resolve28) => {
|
|
9205
9818
|
const isWindows = process.platform === "win32";
|
|
9206
9819
|
const child = spawn5(isWindows ? "cmd.exe" : "/bin/sh", isWindows ? ["/d", "/s", "/c", command] : ["-c", command], {
|
|
9207
9820
|
cwd,
|
|
@@ -9211,7 +9824,7 @@ function runCommand(command, cwd, timeoutMs) {
|
|
|
9211
9824
|
let stderr = "";
|
|
9212
9825
|
const timer = setTimeout(() => {
|
|
9213
9826
|
child.kill();
|
|
9214
|
-
|
|
9827
|
+
resolve28({ ok: false, stdout, stderr: `${stderr}
|
|
9215
9828
|
[killed: exceeded ${timeoutMs}ms]`, code: "timeout" });
|
|
9216
9829
|
}, timeoutMs);
|
|
9217
9830
|
child.stdout.on("data", (chunk) => {
|
|
@@ -9222,12 +9835,12 @@ function runCommand(command, cwd, timeoutMs) {
|
|
|
9222
9835
|
});
|
|
9223
9836
|
child.on("error", (error) => {
|
|
9224
9837
|
clearTimeout(timer);
|
|
9225
|
-
|
|
9838
|
+
resolve28({ ok: false, stdout, stderr: `${stderr}
|
|
9226
9839
|
${error.message}`, code: "spawn" });
|
|
9227
9840
|
});
|
|
9228
9841
|
child.on("close", (code) => {
|
|
9229
9842
|
clearTimeout(timer);
|
|
9230
|
-
|
|
9843
|
+
resolve28({ ok: code === 0, stdout, stderr, code: code ?? "unknown" });
|
|
9231
9844
|
});
|
|
9232
9845
|
});
|
|
9233
9846
|
}
|
|
@@ -9521,7 +10134,7 @@ function createEvolveService(options) {
|
|
|
9521
10134
|
if (suggestion === void 0) return `No open suggestion with id "${suggestionId}". Use /evolve suggest to list them.`;
|
|
9522
10135
|
const name = sanitizePluginName(suggestion.tool);
|
|
9523
10136
|
const dir = await scaffoldFixPlugin(workspace, name);
|
|
9524
|
-
await writeFile8(
|
|
10137
|
+
await writeFile8(join11(dir, "PLAN.md"), buildPlan(suggestion, name, "See the repeated failure below; describe your fix in PLAN.md."), "utf8");
|
|
9525
10138
|
await snapshotFixPlugin(workspace, name);
|
|
9526
10139
|
return [
|
|
9527
10140
|
`suggestion ${suggestion.id}: ${suggestion.tool} x${suggestion.count} \u2014 ${suggestion.error}`,
|
|
@@ -9583,10 +10196,10 @@ ${result.stderr.slice(-4e3)}` : ""}`;
|
|
|
9583
10196
|
return USAGE;
|
|
9584
10197
|
},
|
|
9585
10198
|
toolDefinition() {
|
|
9586
|
-
const inputSchema =
|
|
9587
|
-
suggestionId:
|
|
9588
|
-
implementation:
|
|
9589
|
-
kind:
|
|
10199
|
+
const inputSchema = z14.object({
|
|
10200
|
+
suggestionId: z14.string().min(1).describe("Signal id from the evolve suggestions"),
|
|
10201
|
+
implementation: z14.string().min(1).describe("Concise description of the fix to implement (plugin plan, or the guardrail rule text when kind is prompt_rule)"),
|
|
10202
|
+
kind: z14.enum(["plugin", "prompt_rule"]).optional().describe(
|
|
9590
10203
|
"plugin (default): scaffold a fix plugin; prompt_rule: store a guardrail rule that is injected into future system prompts"
|
|
9591
10204
|
)
|
|
9592
10205
|
});
|
|
@@ -9612,7 +10225,7 @@ ${result.stderr.slice(-4e3)}` : ""}`;
|
|
|
9612
10225
|
}
|
|
9613
10226
|
const name = sanitizePluginName(suggestion.tool);
|
|
9614
10227
|
const dir = await scaffoldFixPlugin(workspace, name);
|
|
9615
|
-
await writeFile8(
|
|
10228
|
+
await writeFile8(join11(dir, "PLAN.md"), buildPlan(suggestion, name, implementation), "utf8");
|
|
9616
10229
|
await snapshotFixPlugin(workspace, name);
|
|
9617
10230
|
return [
|
|
9618
10231
|
`Scaffolded fix plugin at ${dir} for suggestion [${suggestion.id}] (${suggestion.tool} x${suggestion.count}).`,
|
|
@@ -9652,8 +10265,8 @@ function buildLoopCyclePrompt(input) {
|
|
|
9652
10265
|
}
|
|
9653
10266
|
|
|
9654
10267
|
// src/loop/types.ts
|
|
9655
|
-
import { z as
|
|
9656
|
-
var LoopStatusSchema =
|
|
10268
|
+
import { z as z15 } from "zod";
|
|
10269
|
+
var LoopStatusSchema = z15.enum([
|
|
9657
10270
|
"running",
|
|
9658
10271
|
"succeeded",
|
|
9659
10272
|
"failed",
|
|
@@ -9663,65 +10276,65 @@ var LoopStatusSchema = z14.enum([
|
|
|
9663
10276
|
"no_progress",
|
|
9664
10277
|
"needs_human"
|
|
9665
10278
|
]);
|
|
9666
|
-
var LoopApprovalSchema =
|
|
9667
|
-
dimension:
|
|
9668
|
-
previousCheckpoint:
|
|
9669
|
-
newCheckpoint:
|
|
9670
|
-
usage:
|
|
9671
|
-
approvedAt:
|
|
10279
|
+
var LoopApprovalSchema = z15.object({
|
|
10280
|
+
dimension: z15.enum(["cycles", "tokens"]),
|
|
10281
|
+
previousCheckpoint: z15.number().int().nonnegative(),
|
|
10282
|
+
newCheckpoint: z15.number().int().positive(),
|
|
10283
|
+
usage: z15.number().int().nonnegative(),
|
|
10284
|
+
approvedAt: z15.string().datetime({ offset: true })
|
|
9672
10285
|
}).strict();
|
|
9673
|
-
var LoopVerificationCommandSchema =
|
|
9674
|
-
command:
|
|
9675
|
-
args:
|
|
9676
|
-
exitCode:
|
|
9677
|
-
stdout:
|
|
9678
|
-
stderr:
|
|
9679
|
-
truncated:
|
|
10286
|
+
var LoopVerificationCommandSchema = z15.object({
|
|
10287
|
+
command: z15.string().min(1),
|
|
10288
|
+
args: z15.array(z15.string()),
|
|
10289
|
+
exitCode: z15.number().int().nullable(),
|
|
10290
|
+
stdout: z15.string(),
|
|
10291
|
+
stderr: z15.string(),
|
|
10292
|
+
truncated: z15.boolean()
|
|
9680
10293
|
}).strict();
|
|
9681
|
-
var LoopVerificationEvidenceSchema =
|
|
9682
|
-
passed:
|
|
9683
|
-
commands:
|
|
9684
|
-
summary:
|
|
10294
|
+
var LoopVerificationEvidenceSchema = z15.object({
|
|
10295
|
+
passed: z15.boolean(),
|
|
10296
|
+
commands: z15.array(LoopVerificationCommandSchema),
|
|
10297
|
+
summary: z15.string()
|
|
9685
10298
|
}).strict();
|
|
9686
|
-
var LoopCycleEvidenceSchema =
|
|
9687
|
-
cycle:
|
|
9688
|
-
startedAt:
|
|
9689
|
-
completedAt:
|
|
9690
|
-
inputTokens:
|
|
9691
|
-
outputTokens:
|
|
9692
|
-
workspaceFingerprint:
|
|
10299
|
+
var LoopCycleEvidenceSchema = z15.object({
|
|
10300
|
+
cycle: z15.number().int().positive(),
|
|
10301
|
+
startedAt: z15.string().datetime({ offset: true }),
|
|
10302
|
+
completedAt: z15.string().datetime({ offset: true }),
|
|
10303
|
+
inputTokens: z15.number().int().nonnegative(),
|
|
10304
|
+
outputTokens: z15.number().int().nonnegative(),
|
|
10305
|
+
workspaceFingerprint: z15.string(),
|
|
9693
10306
|
verification: LoopVerificationEvidenceSchema
|
|
9694
10307
|
}).strict();
|
|
9695
|
-
var LoopStateSchema =
|
|
9696
|
-
version:
|
|
9697
|
-
loopId:
|
|
9698
|
-
goal:
|
|
9699
|
-
workspace:
|
|
9700
|
-
createdAt:
|
|
9701
|
-
updatedAt:
|
|
10308
|
+
var LoopStateSchema = z15.object({
|
|
10309
|
+
version: z15.literal(1),
|
|
10310
|
+
loopId: z15.string().regex(/^[A-Za-z0-9][A-Za-z0-9._-]{0,127}$/),
|
|
10311
|
+
goal: z15.string().trim().min(1),
|
|
10312
|
+
workspace: z15.string().min(1),
|
|
10313
|
+
createdAt: z15.string().datetime({ offset: true }),
|
|
10314
|
+
updatedAt: z15.string().datetime({ offset: true }),
|
|
9702
10315
|
status: LoopStatusSchema,
|
|
9703
|
-
config:
|
|
9704
|
-
cycleStep:
|
|
9705
|
-
tokenStep:
|
|
9706
|
-
isolation:
|
|
10316
|
+
config: z15.object({
|
|
10317
|
+
cycleStep: z15.number().int().positive(),
|
|
10318
|
+
tokenStep: z15.number().int().positive(),
|
|
10319
|
+
isolation: z15.literal("auto")
|
|
9707
10320
|
}).strict(),
|
|
9708
|
-
budget:
|
|
9709
|
-
cyclesUsed:
|
|
9710
|
-
inputTokens:
|
|
9711
|
-
outputTokens:
|
|
9712
|
-
cycleCheckpoint:
|
|
9713
|
-
tokenCheckpoint:
|
|
9714
|
-
approvals:
|
|
10321
|
+
budget: z15.object({
|
|
10322
|
+
cyclesUsed: z15.number().int().nonnegative(),
|
|
10323
|
+
inputTokens: z15.number().int().nonnegative(),
|
|
10324
|
+
outputTokens: z15.number().int().nonnegative(),
|
|
10325
|
+
cycleCheckpoint: z15.number().int().positive(),
|
|
10326
|
+
tokenCheckpoint: z15.number().int().positive(),
|
|
10327
|
+
approvals: z15.array(LoopApprovalSchema)
|
|
9715
10328
|
}).strict(),
|
|
9716
|
-
cycles:
|
|
9717
|
-
terminalReason:
|
|
10329
|
+
cycles: z15.array(LoopCycleEvidenceSchema),
|
|
10330
|
+
terminalReason: z15.string().optional()
|
|
9718
10331
|
}).strict();
|
|
9719
|
-
var LoopEventSchema =
|
|
9720
|
-
version:
|
|
9721
|
-
type:
|
|
9722
|
-
timestamp:
|
|
9723
|
-
loopId:
|
|
9724
|
-
payload:
|
|
10332
|
+
var LoopEventSchema = z15.object({
|
|
10333
|
+
version: z15.literal(1),
|
|
10334
|
+
type: z15.enum(["created", "cycle_started", "cycle_completed", "verification", "budget_approved", "terminal"]),
|
|
10335
|
+
timestamp: z15.string().datetime({ offset: true }),
|
|
10336
|
+
loopId: z15.string().regex(/^[A-Za-z0-9][A-Za-z0-9._-]{0,127}$/),
|
|
10337
|
+
payload: z15.record(z15.string(), z15.unknown())
|
|
9725
10338
|
}).strict();
|
|
9726
10339
|
|
|
9727
10340
|
// src/loop/budget.ts
|
|
@@ -10033,19 +10646,20 @@ ${evidence.summary}`;
|
|
|
10033
10646
|
};
|
|
10034
10647
|
|
|
10035
10648
|
// src/goal/orchestrator.ts
|
|
10649
|
+
import { createHash as createHash10 } from "crypto";
|
|
10036
10650
|
import { writeFile as writeFile9, mkdir as mkdir10 } from "fs/promises";
|
|
10037
|
-
import { join as
|
|
10651
|
+
import { join as join13 } from "path";
|
|
10038
10652
|
|
|
10039
10653
|
// src/goal/types.ts
|
|
10040
|
-
import { z as
|
|
10041
|
-
var GoalPhaseSchema =
|
|
10654
|
+
import { z as z16 } from "zod";
|
|
10655
|
+
var GoalPhaseSchema = z16.enum([
|
|
10042
10656
|
"idle",
|
|
10043
10657
|
"planning",
|
|
10044
10658
|
"executing",
|
|
10045
10659
|
"verifying",
|
|
10046
10660
|
"complete"
|
|
10047
10661
|
]);
|
|
10048
|
-
var GoalStatusSchema =
|
|
10662
|
+
var GoalStatusSchema = z16.enum([
|
|
10049
10663
|
"active",
|
|
10050
10664
|
"paused",
|
|
10051
10665
|
"achieved",
|
|
@@ -10053,43 +10667,61 @@ var GoalStatusSchema = z15.enum([
|
|
|
10053
10667
|
"blocked",
|
|
10054
10668
|
"failed"
|
|
10055
10669
|
]);
|
|
10056
|
-
var AcceptanceCriterionSchema =
|
|
10057
|
-
id:
|
|
10058
|
-
description:
|
|
10059
|
-
type:
|
|
10670
|
+
var AcceptanceCriterionSchema = z16.object({
|
|
10671
|
+
id: z16.number().int().positive(),
|
|
10672
|
+
description: z16.string().min(1),
|
|
10673
|
+
type: z16.enum(["gating", "evidence"])
|
|
10674
|
+
}).strict();
|
|
10675
|
+
var PlanSchema = z16.object({
|
|
10676
|
+
kind: z16.enum(["code-change", "analysis", "research"]),
|
|
10677
|
+
criteria: z16.array(AcceptanceCriterionSchema).min(1).max(32),
|
|
10678
|
+
verificationPlan: z16.string().min(1),
|
|
10679
|
+
nonGoals: z16.array(z16.string()).max(16),
|
|
10680
|
+
assumedScope: z16.array(z16.string()).max(16),
|
|
10681
|
+
approach: z16.string().optional(),
|
|
10682
|
+
checklist: z16.array(z16.string()).max(32).optional()
|
|
10060
10683
|
}).strict();
|
|
10061
|
-
var
|
|
10062
|
-
|
|
10063
|
-
|
|
10064
|
-
|
|
10065
|
-
nonGoals: z15.array(z15.string()).max(16),
|
|
10066
|
-
assumedScope: z15.array(z15.string()).max(16),
|
|
10067
|
-
approach: z15.string().optional(),
|
|
10068
|
-
checklist: z15.array(z15.string()).max(32).optional()
|
|
10684
|
+
var GapSchema = z16.object({
|
|
10685
|
+
criterion: z16.string().min(1),
|
|
10686
|
+
description: z16.string().min(1),
|
|
10687
|
+
blocking: z16.enum(["model_fixable", "contradiction", "unverifiable"])
|
|
10069
10688
|
}).strict();
|
|
10070
|
-
var
|
|
10071
|
-
|
|
10072
|
-
|
|
10073
|
-
blocking: z15.enum(["model_fixable", "contradiction", "unverifiable"])
|
|
10689
|
+
var VerdictSchema = z16.object({
|
|
10690
|
+
refuted: z16.boolean(),
|
|
10691
|
+
gaps: z16.array(GapSchema)
|
|
10074
10692
|
}).strict();
|
|
10075
|
-
var
|
|
10076
|
-
|
|
10077
|
-
|
|
10693
|
+
var HostVerificationEvidenceSchema = z16.object({
|
|
10694
|
+
passed: z16.boolean(),
|
|
10695
|
+
summary: z16.string(),
|
|
10696
|
+
commands: z16.array(z16.object({
|
|
10697
|
+
command: z16.string(),
|
|
10698
|
+
args: z16.array(z16.string()),
|
|
10699
|
+
exitCode: z16.number().int().nullable(),
|
|
10700
|
+
stdout: z16.string().optional(),
|
|
10701
|
+
stderr: z16.string().optional(),
|
|
10702
|
+
truncated: z16.boolean().optional()
|
|
10703
|
+
}).strict()).max(100)
|
|
10078
10704
|
}).strict();
|
|
10079
|
-
var GoalStateSchema =
|
|
10080
|
-
id:
|
|
10081
|
-
objective:
|
|
10705
|
+
var GoalStateSchema = z16.object({
|
|
10706
|
+
id: z16.string().regex(/^[A-Za-z0-9][A-Za-z0-9._-]{0,127}$/),
|
|
10707
|
+
objective: z16.string().min(1),
|
|
10082
10708
|
phase: GoalPhaseSchema,
|
|
10083
10709
|
status: GoalStatusSchema,
|
|
10084
10710
|
plan: PlanSchema.nullable(),
|
|
10085
|
-
planPath:
|
|
10086
|
-
verifyRounds:
|
|
10087
|
-
workerRounds:
|
|
10088
|
-
lastGaps:
|
|
10089
|
-
gapFingerprint:
|
|
10090
|
-
stallStreak:
|
|
10091
|
-
|
|
10092
|
-
|
|
10711
|
+
planPath: z16.string().nullable(),
|
|
10712
|
+
verifyRounds: z16.number().int().min(0),
|
|
10713
|
+
workerRounds: z16.number().int().min(0),
|
|
10714
|
+
lastGaps: z16.array(GapSchema),
|
|
10715
|
+
gapFingerprint: z16.string(),
|
|
10716
|
+
stallStreak: z16.number().int().min(0),
|
|
10717
|
+
contractHash: z16.string().regex(/^[a-f0-9]{64}$/),
|
|
10718
|
+
evidenceRounds: z16.array(z16.object({
|
|
10719
|
+
round: z16.number().int().positive(),
|
|
10720
|
+
workspaceDiffHash: z16.string().regex(/^[a-f0-9]{64}$/),
|
|
10721
|
+
hostVerification: HostVerificationEvidenceSchema.optional()
|
|
10722
|
+
}).strict()).max(1e3),
|
|
10723
|
+
createdAt: z16.string().datetime({ offset: true }),
|
|
10724
|
+
updatedAt: z16.string().datetime({ offset: true })
|
|
10093
10725
|
}).strict();
|
|
10094
10726
|
|
|
10095
10727
|
// src/goal/planner.ts
|
|
@@ -10179,9 +10811,9 @@ function extractJson(text) {
|
|
|
10179
10811
|
}
|
|
10180
10812
|
|
|
10181
10813
|
// src/goal/classifier.ts
|
|
10182
|
-
import { createHash as
|
|
10183
|
-
import { readFile as
|
|
10184
|
-
import { join as
|
|
10814
|
+
import { createHash as createHash9 } from "crypto";
|
|
10815
|
+
import { readFile as readFile9 } from "fs/promises";
|
|
10816
|
+
import { join as join12 } from "path";
|
|
10185
10817
|
function buildSkepticPrompt(evidence, plan, index) {
|
|
10186
10818
|
const criteriaList = plan.criteria.map((c) => ` ${c.id}. [${c.type}] ${c.description}`).join("\n");
|
|
10187
10819
|
return [
|
|
@@ -10203,6 +10835,10 @@ function buildSkepticPrompt(evidence, plan, index) {
|
|
|
10203
10835
|
`Changed files: ${evidence.changedFiles.length > 0 ? evidence.changedFiles.join(", ") : "(none)"}`,
|
|
10204
10836
|
`Plan file: ${evidence.planFile ?? "(unavailable)"}`,
|
|
10205
10837
|
`Prior gaps: ${evidence.priorGaps || "(none)"}`,
|
|
10838
|
+
`Contract hash: ${evidence.contractHash}`,
|
|
10839
|
+
`Workspace diff hash: ${evidence.workspaceDiffHash}`,
|
|
10840
|
+
`Workspace status: ${evidence.workspaceStatus.length > 0 ? evidence.workspaceStatus.join(" | ") : "(clean)"}`,
|
|
10841
|
+
`Host verification: ${evidence.hostVerification === void 0 ? "(not configured)" : JSON.stringify(evidence.hostVerification)}`,
|
|
10206
10842
|
"",
|
|
10207
10843
|
"## Implementer's Closing Statement",
|
|
10208
10844
|
evidence.finalResponse || "(no closing statement)",
|
|
@@ -10214,7 +10850,8 @@ function buildSkepticPrompt(evidence, plan, index) {
|
|
|
10214
10850
|
"4. Missing tests alone are NOT grounds for refutation unless a criterion explicitly requires them.",
|
|
10215
10851
|
"5. NEVER invent requirements beyond the contract (objective + criteria + non-goals).",
|
|
10216
10852
|
"6. In repeat verification rounds, focus on whether PRIOR_GAPS have been fixed. New issues are only valid if they are clearly demonstrable defects.",
|
|
10217
|
-
"7.
|
|
10853
|
+
"7. Host verification is authoritative. A failed deterministic command must be refuted.",
|
|
10854
|
+
"8. Every PRIOR_GAP must have current evidence that it was resolved; uncertainty means refuted.",
|
|
10218
10855
|
"",
|
|
10219
10856
|
"## Output",
|
|
10220
10857
|
"Write your verdict as a single JSON object (no markdown, no preamble):",
|
|
@@ -10243,8 +10880,12 @@ async function runClassifier(evidence, plan, options) {
|
|
|
10243
10880
|
for (const result of skepticResults) {
|
|
10244
10881
|
try {
|
|
10245
10882
|
verdicts.push(parseVerdict(result));
|
|
10246
|
-
} catch {
|
|
10247
|
-
verdicts.push({ refuted:
|
|
10883
|
+
} catch (error) {
|
|
10884
|
+
verdicts.push({ refuted: true, gaps: [{
|
|
10885
|
+
criterion: "verification-panel",
|
|
10886
|
+
description: `Skeptic output was unavailable or invalid: ${error instanceof Error ? error.message : String(error)}`,
|
|
10887
|
+
blocking: "unverifiable"
|
|
10888
|
+
}] });
|
|
10248
10889
|
}
|
|
10249
10890
|
}
|
|
10250
10891
|
return aggregate(verdicts);
|
|
@@ -10345,50 +10986,56 @@ function deduplicateGaps(gaps) {
|
|
|
10345
10986
|
}
|
|
10346
10987
|
function computeFingerprint(gaps) {
|
|
10347
10988
|
const content = gaps.map((g) => `${g.criterion}:${g.description}`).sort().join("\n");
|
|
10348
|
-
return
|
|
10989
|
+
return createHash9("sha256").update(content).digest("hex").slice(0, 16);
|
|
10349
10990
|
}
|
|
10350
|
-
async function collectEvidence(workspace, objective, finalResponse, priorGaps) {
|
|
10351
|
-
const
|
|
10352
|
-
const planPath =
|
|
10991
|
+
async function collectEvidence(workspace, objective, finalResponse, priorGaps, contractHash2 = createHash9("sha256").update(objective).digest("hex"), hostVerification) {
|
|
10992
|
+
const workspaceState = await collectWorkspaceState(workspace);
|
|
10993
|
+
const planPath = join12(workspace, ".flavor", "goal-plan.md");
|
|
10353
10994
|
let planFile = null;
|
|
10354
10995
|
try {
|
|
10355
|
-
planFile = await
|
|
10996
|
+
planFile = await readFile9(planPath, "utf8");
|
|
10356
10997
|
} catch {
|
|
10357
10998
|
}
|
|
10358
10999
|
return {
|
|
10359
11000
|
objective,
|
|
10360
|
-
changedFiles: changedFiles
|
|
11001
|
+
changedFiles: workspaceState.changedFiles,
|
|
10361
11002
|
planFile,
|
|
10362
11003
|
finalResponse,
|
|
10363
|
-
priorGaps
|
|
11004
|
+
priorGaps,
|
|
11005
|
+
contractHash: contractHash2,
|
|
11006
|
+
workspaceDiffHash: workspaceState.diffHash,
|
|
11007
|
+
workspaceStatus: workspaceState.status,
|
|
11008
|
+
...hostVerification === void 0 ? {} : { hostVerification }
|
|
10364
11009
|
};
|
|
10365
11010
|
}
|
|
10366
|
-
async function
|
|
10367
|
-
const
|
|
10368
|
-
|
|
10369
|
-
|
|
10370
|
-
|
|
10371
|
-
|
|
10372
|
-
|
|
10373
|
-
|
|
10374
|
-
|
|
10375
|
-
|
|
10376
|
-
|
|
10377
|
-
const full = join11(dir, entry2.name);
|
|
10378
|
-
const rel = relative8(workspace, full);
|
|
10379
|
-
if (entry2.name === ".git" || entry2.name === "node_modules" || entry2.name === ".flavor" || entry2.name.startsWith(".")) continue;
|
|
10380
|
-
if (entry2.isDirectory()) {
|
|
10381
|
-
await walk(full);
|
|
10382
|
-
} else if (entry2.isFile()) {
|
|
10383
|
-
if (!seen.has(rel)) {
|
|
10384
|
-
seen.add(rel);
|
|
10385
|
-
results.push(full);
|
|
10386
|
-
}
|
|
10387
|
-
}
|
|
10388
|
-
}
|
|
11011
|
+
async function collectWorkspaceState(workspace) {
|
|
11012
|
+
const statusResult = await execFileNoThrow("git", ["-C", workspace, "status", "--porcelain=v1", "--untracked-files=all"], {
|
|
11013
|
+
timeout: 3e4,
|
|
11014
|
+
useCwd: false
|
|
11015
|
+
});
|
|
11016
|
+
if (statusResult.code !== 0) {
|
|
11017
|
+
return {
|
|
11018
|
+
changedFiles: [],
|
|
11019
|
+
status: ["not-a-git-worktree"],
|
|
11020
|
+
diffHash: createHash9("sha256").update("not-a-git-worktree").digest("hex")
|
|
11021
|
+
};
|
|
10389
11022
|
}
|
|
10390
|
-
|
|
10391
|
-
|
|
11023
|
+
const status = statusResult.stdout.split(/\r?\n/).map((line) => line.trimEnd()).filter(Boolean);
|
|
11024
|
+
const changedFiles = status.map((line) => {
|
|
11025
|
+
const value = line.slice(3);
|
|
11026
|
+
const arrow = value.lastIndexOf(" -> ");
|
|
11027
|
+
return arrow >= 0 ? value.slice(arrow + 4) : value;
|
|
11028
|
+
}).sort();
|
|
11029
|
+
const diff = await execFileNoThrow("git", ["-C", workspace, "diff", "--no-ext-diff", "--binary", "HEAD"], {
|
|
11030
|
+
timeout: 3e4,
|
|
11031
|
+
useCwd: false
|
|
11032
|
+
});
|
|
11033
|
+
const untracked = status.filter((line) => line.startsWith("?? ")).map((line) => line.slice(3)).sort().join("\n");
|
|
11034
|
+
return {
|
|
11035
|
+
changedFiles,
|
|
11036
|
+
status,
|
|
11037
|
+
diffHash: createHash9("sha256").update(diff.stdout).update("\nUNTRACKED\n").update(untracked).digest("hex")
|
|
11038
|
+
};
|
|
10392
11039
|
}
|
|
10393
11040
|
|
|
10394
11041
|
// src/goal/orchestrator.ts
|
|
@@ -10419,6 +11066,8 @@ var GoalOrchestrator = class {
|
|
|
10419
11066
|
lastGaps: [],
|
|
10420
11067
|
gapFingerprint: "",
|
|
10421
11068
|
stallStreak: 0,
|
|
11069
|
+
contractHash: contractHash(request.goal, null),
|
|
11070
|
+
evidenceRounds: [],
|
|
10422
11071
|
createdAt,
|
|
10423
11072
|
updatedAt: createdAt
|
|
10424
11073
|
};
|
|
@@ -10439,7 +11088,7 @@ var GoalOrchestrator = class {
|
|
|
10439
11088
|
signal: request.signal
|
|
10440
11089
|
});
|
|
10441
11090
|
planPath = await writePlanFile(workspace, plan);
|
|
10442
|
-
await persistState({ plan, planPath });
|
|
11091
|
+
await persistState({ plan, planPath, contractHash: contractHash(request.goal, plan) });
|
|
10443
11092
|
yield { type: "goal-plan-created", plan, planPath };
|
|
10444
11093
|
} catch (error) {
|
|
10445
11094
|
const reason = `Goal planning failed: ${message(error)}`;
|
|
@@ -10492,15 +11141,35 @@ var GoalOrchestrator = class {
|
|
|
10492
11141
|
}
|
|
10493
11142
|
await persistState({ phase: "verifying", status: "active", verifyRounds: round });
|
|
10494
11143
|
yield { type: "goal-verification-start", round };
|
|
11144
|
+
const hostVerification = await this.#options.verifyHost?.(request.signal);
|
|
10495
11145
|
const evidence = await collectEvidence(
|
|
10496
11146
|
workspace,
|
|
10497
11147
|
request.goal,
|
|
10498
11148
|
finalResponse.slice(-4e3),
|
|
10499
11149
|
// Last 4000 chars of response
|
|
10500
|
-
formatPriorGaps(priorGaps)
|
|
11150
|
+
formatPriorGaps(priorGaps),
|
|
11151
|
+
state.contractHash,
|
|
11152
|
+
hostVerification
|
|
10501
11153
|
);
|
|
11154
|
+
await persistState({ evidenceRounds: [...state.evidenceRounds, {
|
|
11155
|
+
round,
|
|
11156
|
+
workspaceDiffHash: evidence.workspaceDiffHash,
|
|
11157
|
+
...hostVerification === void 0 ? {} : { hostVerification }
|
|
11158
|
+
}] });
|
|
10502
11159
|
let outcome;
|
|
10503
|
-
|
|
11160
|
+
if (plan.kind === "code-change" && hostVerification !== void 0 && !hostVerification.passed) {
|
|
11161
|
+
if (hostVerification.commands.length === 0) {
|
|
11162
|
+
outcome = { type: "blocked", reason: `Host verification unavailable: ${hostVerification.summary}` };
|
|
11163
|
+
} else {
|
|
11164
|
+
const gaps = [{ criterion: "host-verification", description: hostVerification.summary, blocking: "model_fixable" }];
|
|
11165
|
+
outcome = {
|
|
11166
|
+
type: "not_achieved",
|
|
11167
|
+
gaps,
|
|
11168
|
+
summary: hostVerification.summary,
|
|
11169
|
+
fingerprint: createHash10("sha256").update(hostVerification.summary).digest("hex").slice(0, 16)
|
|
11170
|
+
};
|
|
11171
|
+
}
|
|
11172
|
+
} else try {
|
|
10504
11173
|
outcome = await runClassifier(evidence, plan, {
|
|
10505
11174
|
registry: this.#options.registry,
|
|
10506
11175
|
modelId: this.#options.classifierModelId,
|
|
@@ -10510,8 +11179,8 @@ var GoalOrchestrator = class {
|
|
|
10510
11179
|
});
|
|
10511
11180
|
} catch (error) {
|
|
10512
11181
|
outcome = {
|
|
10513
|
-
type: "
|
|
10514
|
-
|
|
11182
|
+
type: "blocked",
|
|
11183
|
+
reason: `Classifier infrastructure error: ${message(error)}`
|
|
10515
11184
|
};
|
|
10516
11185
|
}
|
|
10517
11186
|
yield {
|
|
@@ -10570,9 +11239,12 @@ var GoalOrchestrator = class {
|
|
|
10570
11239
|
};
|
|
10571
11240
|
}
|
|
10572
11241
|
};
|
|
11242
|
+
function contractHash(objective, plan) {
|
|
11243
|
+
return createHash10("sha256").update(JSON.stringify({ objective, plan })).digest("hex");
|
|
11244
|
+
}
|
|
10573
11245
|
async function writePlanFile(workspace, plan) {
|
|
10574
|
-
const dir =
|
|
10575
|
-
const planPath =
|
|
11246
|
+
const dir = join13(workspace, ".flavor");
|
|
11247
|
+
const planPath = join13(dir, "goal-plan.md");
|
|
10576
11248
|
await mkdir10(dir, { recursive: true });
|
|
10577
11249
|
const criteriaText = plan.criteria.map((c) => `- [${c.type}] AC-${c.id}: ${c.description}`).join("\n");
|
|
10578
11250
|
const content = [
|
|
@@ -10647,18 +11319,18 @@ function formatPriorGaps(gaps) {
|
|
|
10647
11319
|
}
|
|
10648
11320
|
|
|
10649
11321
|
// src/goal/store.ts
|
|
10650
|
-
import { constants } from "fs";
|
|
10651
|
-
import { randomUUID as
|
|
11322
|
+
import { constants as constants2 } from "fs";
|
|
11323
|
+
import { randomUUID as randomUUID9 } from "crypto";
|
|
10652
11324
|
import { lstat as lstat6, mkdir as mkdir11, open as open6, rename as rename4, rm as rm4 } from "fs/promises";
|
|
10653
|
-
import { dirname as dirname12, join as
|
|
11325
|
+
import { dirname as dirname12, join as join14, relative as relative9, resolve as resolve11, sep as sep9 } from "path";
|
|
10654
11326
|
var DEFAULT_MAX_BYTES = 1024 * 1024;
|
|
10655
11327
|
var GoalStore = class {
|
|
10656
11328
|
#workspace;
|
|
10657
11329
|
#root;
|
|
10658
11330
|
#maxBytes;
|
|
10659
11331
|
constructor(options) {
|
|
10660
|
-
this.#workspace =
|
|
10661
|
-
this.#root =
|
|
11332
|
+
this.#workspace = resolve11(options.workspace);
|
|
11333
|
+
this.#root = join14(this.#workspace, ".flavor", "goals");
|
|
10662
11334
|
this.#maxBytes = options.maxBytes ?? DEFAULT_MAX_BYTES;
|
|
10663
11335
|
}
|
|
10664
11336
|
async save(input) {
|
|
@@ -10666,16 +11338,16 @@ var GoalStore = class {
|
|
|
10666
11338
|
await assertNoSymlink(this.#workspace, dirname12(this.#root));
|
|
10667
11339
|
await mkdir11(this.#root, { recursive: true, mode: 448 });
|
|
10668
11340
|
await assertNoSymlink(this.#workspace, this.#root);
|
|
10669
|
-
const target =
|
|
11341
|
+
const target = join14(this.#root, `${state.id}.json`);
|
|
10670
11342
|
const body = `${JSON.stringify(state, null, 2)}
|
|
10671
11343
|
`;
|
|
10672
11344
|
if (Buffer.byteLength(body) > this.#maxBytes) {
|
|
10673
11345
|
throw new Error(`Goal state exceeds maximum size of ${this.#maxBytes} bytes`);
|
|
10674
11346
|
}
|
|
10675
|
-
const temporary =
|
|
11347
|
+
const temporary = join14(this.#root, `.${state.id}.${process.pid}.${randomUUID9()}.tmp`);
|
|
10676
11348
|
let handle;
|
|
10677
11349
|
try {
|
|
10678
|
-
handle = await open6(temporary,
|
|
11350
|
+
handle = await open6(temporary, constants2.O_CREAT | constants2.O_EXCL | constants2.O_WRONLY, 384);
|
|
10679
11351
|
await handle.writeFile(body, "utf8");
|
|
10680
11352
|
await handle.sync();
|
|
10681
11353
|
await handle.close();
|
|
@@ -10688,10 +11360,10 @@ var GoalStore = class {
|
|
|
10688
11360
|
}
|
|
10689
11361
|
};
|
|
10690
11362
|
async function assertNoSymlink(root, target) {
|
|
10691
|
-
let cursor =
|
|
10692
|
-
const stop =
|
|
11363
|
+
let cursor = resolve11(target);
|
|
11364
|
+
const stop = resolve11(root);
|
|
10693
11365
|
const value = relative9(stop, cursor);
|
|
10694
|
-
if (value === ".." || value.startsWith(`..${
|
|
11366
|
+
if (value === ".." || value.startsWith(`..${sep9}`) || value.includes(":")) {
|
|
10695
11367
|
throw new Error("Goal path escapes workspace");
|
|
10696
11368
|
}
|
|
10697
11369
|
const chain = [];
|
|
@@ -10712,8 +11384,8 @@ function isCode3(error, code) {
|
|
|
10712
11384
|
}
|
|
10713
11385
|
|
|
10714
11386
|
// src/loop/isolation.ts
|
|
10715
|
-
import { appendFile as appendFile2, mkdir as mkdir12, readFile as
|
|
10716
|
-
import { join as
|
|
11387
|
+
import { appendFile as appendFile2, mkdir as mkdir12, readFile as readFile10 } from "fs/promises";
|
|
11388
|
+
import { join as join15, resolve as resolve12 } from "path";
|
|
10717
11389
|
var CODE_CHANGE = /\b(?:fix|implement|add|update|change|modify|refactor|build|create|remove|delete|write|migrate)\b|修复|实现|添加|更新|修改|重构|创建|删除|迁移/i;
|
|
10718
11390
|
var READ_ONLY = /\b(?:review|analy[sz]e|inspect|explain|research|investigate|audit|summari[sz]e|report)\b|调研|分析|审查|检查|解释|研究|总结|报告/i;
|
|
10719
11391
|
function inferGoalIntent(goal) {
|
|
@@ -10723,7 +11395,7 @@ function inferGoalIntent(goal) {
|
|
|
10723
11395
|
}
|
|
10724
11396
|
async function prepareLoopWorkspace(options) {
|
|
10725
11397
|
options.signal?.throwIfAborted();
|
|
10726
|
-
const root =
|
|
11398
|
+
const root = resolve12(options.root);
|
|
10727
11399
|
const intent = inferGoalIntent(options.goal);
|
|
10728
11400
|
if (intent === "read_only") return { kind: "ready", workspace: { root, mode: "current" } };
|
|
10729
11401
|
const inside = await git(root, ["rev-parse", "--is-inside-work-tree"]);
|
|
@@ -10736,8 +11408,8 @@ async function prepareLoopWorkspace(options) {
|
|
|
10736
11408
|
return { kind: "needs_human", reason: "The workspace has uncommitted changes that a new worktree would omit." };
|
|
10737
11409
|
}
|
|
10738
11410
|
options.signal?.throwIfAborted();
|
|
10739
|
-
const worktrees =
|
|
10740
|
-
const path =
|
|
11411
|
+
const worktrees = join15(root, ".worktrees");
|
|
11412
|
+
const path = join15(worktrees, `loop-${options.loopId}`);
|
|
10741
11413
|
const branch = `loop/${options.loopId}`;
|
|
10742
11414
|
try {
|
|
10743
11415
|
await ensureWorktreeDirectoryIgnored(root);
|
|
@@ -10766,13 +11438,13 @@ async function ensureWorktreeDirectoryIgnored(root) {
|
|
|
10766
11438
|
if (ignored2.code === 0) return;
|
|
10767
11439
|
const commonDirectory = await git(root, ["rev-parse", "--git-common-dir"]);
|
|
10768
11440
|
if (commonDirectory.code !== 0) throw new Error(commonDirectory.stderr.trim() || "Git common directory is unavailable");
|
|
10769
|
-
const gitDirectory =
|
|
10770
|
-
const infoDirectory =
|
|
10771
|
-
const excludePath =
|
|
11441
|
+
const gitDirectory = resolve12(root, commonDirectory.stdout.trim());
|
|
11442
|
+
const infoDirectory = join15(gitDirectory, "info");
|
|
11443
|
+
const excludePath = join15(infoDirectory, "exclude");
|
|
10772
11444
|
await mkdir12(infoDirectory, { recursive: true });
|
|
10773
11445
|
let existing = "";
|
|
10774
11446
|
try {
|
|
10775
|
-
existing = await
|
|
11447
|
+
existing = await readFile10(excludePath, "utf8");
|
|
10776
11448
|
} catch (error) {
|
|
10777
11449
|
if (!(typeof error === "object" && error !== null && "code" in error && error.code === "ENOENT")) throw error;
|
|
10778
11450
|
}
|
|
@@ -10782,10 +11454,10 @@ async function ensureWorktreeDirectoryIgnored(root) {
|
|
|
10782
11454
|
}
|
|
10783
11455
|
|
|
10784
11456
|
// src/loop/store.ts
|
|
10785
|
-
import { constants as
|
|
10786
|
-
import { lstat as lstat7, mkdir as mkdir13, open as open7, readFile as
|
|
10787
|
-
import { dirname as dirname13, join as
|
|
10788
|
-
import { randomUUID as
|
|
11457
|
+
import { constants as constants3 } from "fs";
|
|
11458
|
+
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";
|
|
11459
|
+
import { dirname as dirname13, join as join16, relative as relative10, resolve as resolve13, sep as sep10 } from "path";
|
|
11460
|
+
import { randomUUID as randomUUID10 } from "crypto";
|
|
10789
11461
|
var LOOP_ID = /^[A-Za-z0-9][A-Za-z0-9._-]{0,127}$/;
|
|
10790
11462
|
var DEFAULT_MAX_BYTES2 = 5 * 1024 * 1024;
|
|
10791
11463
|
var LoopStore = class {
|
|
@@ -10794,8 +11466,8 @@ var LoopStore = class {
|
|
|
10794
11466
|
#maxBytes;
|
|
10795
11467
|
#appendTails = /* @__PURE__ */ new Map();
|
|
10796
11468
|
constructor(options) {
|
|
10797
|
-
this.#workspace =
|
|
10798
|
-
this.#root =
|
|
11469
|
+
this.#workspace = resolve13(options.workspace);
|
|
11470
|
+
this.#root = join16(this.#workspace, ".flavor", "loops");
|
|
10799
11471
|
this.#maxBytes = options.maxBytes ?? DEFAULT_MAX_BYTES2;
|
|
10800
11472
|
}
|
|
10801
11473
|
async create(state) {
|
|
@@ -10812,14 +11484,14 @@ var LoopStore = class {
|
|
|
10812
11484
|
const state = LoopStateSchema.parse(input);
|
|
10813
11485
|
await this.#assertWorkspace(state.workspace);
|
|
10814
11486
|
const directory = await this.#prepare(state.loopId);
|
|
10815
|
-
const target =
|
|
11487
|
+
const target = join16(directory, "state.json");
|
|
10816
11488
|
const body = `${JSON.stringify(state, null, 2)}
|
|
10817
11489
|
`;
|
|
10818
11490
|
if (Buffer.byteLength(body) > this.#maxBytes) throw new Error(`Loop state exceeds maximum size of ${this.#maxBytes} bytes`);
|
|
10819
|
-
const temporary =
|
|
11491
|
+
const temporary = join16(directory, `.state.${process.pid}.${randomUUID10()}.tmp`);
|
|
10820
11492
|
let handle;
|
|
10821
11493
|
try {
|
|
10822
|
-
handle = await open7(temporary,
|
|
11494
|
+
handle = await open7(temporary, constants3.O_CREAT | constants3.O_EXCL | constants3.O_WRONLY, 384);
|
|
10823
11495
|
await handle.writeFile(body, "utf8");
|
|
10824
11496
|
await handle.sync();
|
|
10825
11497
|
await handle.close();
|
|
@@ -10832,17 +11504,17 @@ var LoopStore = class {
|
|
|
10832
11504
|
}
|
|
10833
11505
|
async load(loopId) {
|
|
10834
11506
|
const directory = await this.#safeExisting(loopId);
|
|
10835
|
-
const target =
|
|
11507
|
+
const target = join16(directory, "state.json");
|
|
10836
11508
|
try {
|
|
10837
11509
|
const metadata = await stat5(target);
|
|
10838
11510
|
if (!metadata.isFile() || metadata.size > this.#maxBytes) throw new Error("Loop state is not a bounded regular file");
|
|
10839
|
-
const raw = await
|
|
11511
|
+
const raw = await readFile11(target, "utf8");
|
|
10840
11512
|
const state = LoopStateSchema.parse(JSON.parse(raw));
|
|
10841
11513
|
await this.#assertWorkspace(state.workspace);
|
|
10842
11514
|
return state;
|
|
10843
11515
|
} catch (error) {
|
|
10844
11516
|
if (isCode4(error, "ENOENT")) throw new Error(`Loop "${loopId}" was not found`);
|
|
10845
|
-
const quarantine = `${target}.corrupt-${Date.now()}-${
|
|
11517
|
+
const quarantine = `${target}.corrupt-${Date.now()}-${randomUUID10().slice(0, 8)}`;
|
|
10846
11518
|
await rename5(target, quarantine).catch(() => void 0);
|
|
10847
11519
|
throw new Error(`Loop "${loopId}" is corrupt and was quarantined: ${message(error)}`);
|
|
10848
11520
|
}
|
|
@@ -10852,8 +11524,8 @@ var LoopStore = class {
|
|
|
10852
11524
|
const previous = this.#appendTails.get(event.loopId) ?? Promise.resolve();
|
|
10853
11525
|
const next = previous.catch(() => void 0).then(async () => {
|
|
10854
11526
|
const directory = await this.#prepare(event.loopId);
|
|
10855
|
-
const path =
|
|
10856
|
-
const handle = await open7(path,
|
|
11527
|
+
const path = join16(directory, "events.jsonl");
|
|
11528
|
+
const handle = await open7(path, constants3.O_CREAT | constants3.O_APPEND | constants3.O_WRONLY, 384);
|
|
10857
11529
|
try {
|
|
10858
11530
|
await handle.writeFile(`${JSON.stringify(event)}
|
|
10859
11531
|
`, "utf8");
|
|
@@ -10869,7 +11541,7 @@ var LoopStore = class {
|
|
|
10869
11541
|
await assertNoSymlink2(this.#workspace, dirname13(this.#root));
|
|
10870
11542
|
await mkdir13(this.#root, { recursive: true, mode: 448 });
|
|
10871
11543
|
await assertNoSymlink2(this.#workspace, this.#root);
|
|
10872
|
-
const directory =
|
|
11544
|
+
const directory = join16(this.#root, loopId);
|
|
10873
11545
|
await mkdir13(directory, { recursive: true, mode: 448 });
|
|
10874
11546
|
await assertNoSymlink2(this.#workspace, directory);
|
|
10875
11547
|
return directory;
|
|
@@ -10877,7 +11549,7 @@ var LoopStore = class {
|
|
|
10877
11549
|
async #safeExisting(loopId) {
|
|
10878
11550
|
validateId(loopId);
|
|
10879
11551
|
await assertNoSymlink2(this.#workspace, this.#root);
|
|
10880
|
-
const directory =
|
|
11552
|
+
const directory = join16(this.#root, loopId);
|
|
10881
11553
|
await assertNoSymlink2(this.#workspace, directory);
|
|
10882
11554
|
return directory;
|
|
10883
11555
|
}
|
|
@@ -10893,9 +11565,9 @@ function validateId(loopId) {
|
|
|
10893
11565
|
if (!LOOP_ID.test(loopId)) throw new Error("Invalid loop id");
|
|
10894
11566
|
}
|
|
10895
11567
|
async function assertNoSymlink2(root, target) {
|
|
10896
|
-
let cursor =
|
|
10897
|
-
const stop =
|
|
10898
|
-
if (!
|
|
11568
|
+
let cursor = resolve13(target);
|
|
11569
|
+
const stop = resolve13(root);
|
|
11570
|
+
if (!isWithin3(stop, cursor)) throw new Error("Loop path escapes workspace");
|
|
10899
11571
|
const chain = [];
|
|
10900
11572
|
while (cursor !== stop) {
|
|
10901
11573
|
chain.push(cursor);
|
|
@@ -10909,15 +11581,15 @@ async function assertNoSymlink2(root, target) {
|
|
|
10909
11581
|
}
|
|
10910
11582
|
}
|
|
10911
11583
|
}
|
|
10912
|
-
function
|
|
10913
|
-
const value = relative10(
|
|
10914
|
-
return value === "" || !value.startsWith(`..${
|
|
11584
|
+
function isWithin3(root, path) {
|
|
11585
|
+
const value = relative10(resolve13(root), resolve13(path));
|
|
11586
|
+
return value === "" || !value.startsWith(`..${sep10}`) && value !== ".." && !value.includes(":");
|
|
10915
11587
|
}
|
|
10916
11588
|
async function canonical(path) {
|
|
10917
11589
|
try {
|
|
10918
|
-
return await realpath5(
|
|
11590
|
+
return await realpath5(resolve13(path));
|
|
10919
11591
|
} catch {
|
|
10920
|
-
return
|
|
11592
|
+
return resolve13(path);
|
|
10921
11593
|
}
|
|
10922
11594
|
}
|
|
10923
11595
|
function isCode4(error, code) {
|
|
@@ -10925,25 +11597,25 @@ function isCode4(error, code) {
|
|
|
10925
11597
|
}
|
|
10926
11598
|
|
|
10927
11599
|
// src/loop/verifier.ts
|
|
10928
|
-
import { readFile as
|
|
10929
|
-
import { join as
|
|
11600
|
+
import { readFile as readFile12 } from "fs/promises";
|
|
11601
|
+
import { join as join17 } from "path";
|
|
10930
11602
|
|
|
10931
11603
|
// src/tools/shell.ts
|
|
10932
11604
|
import { spawn as spawn6, spawnSync } from "child_process";
|
|
10933
|
-
import { isAbsolute as isAbsolute6, relative as relative11, resolve as
|
|
10934
|
-
import { z as
|
|
11605
|
+
import { isAbsolute as isAbsolute6, relative as relative11, resolve as resolve14, sep as sep11 } from "path";
|
|
11606
|
+
import { z as z18 } from "zod";
|
|
10935
11607
|
|
|
10936
11608
|
// src/tools/jobs.ts
|
|
10937
|
-
import { z as
|
|
10938
|
-
var JobId =
|
|
10939
|
-
var ReadInput =
|
|
10940
|
-
var IdInput =
|
|
11609
|
+
import { z as z17 } from "zod";
|
|
11610
|
+
var JobId = z17.string().regex(/^job-[a-f0-9-]+$/);
|
|
11611
|
+
var ReadInput = z17.object({ id: JobId, cursor: z17.coerce.number().int().min(0).optional() });
|
|
11612
|
+
var IdInput = z17.object({ id: JobId });
|
|
10941
11613
|
function createJobTools(registry) {
|
|
10942
11614
|
const tools = [
|
|
10943
11615
|
{
|
|
10944
11616
|
name: "JobList",
|
|
10945
11617
|
description: "List background jobs owned by this agent",
|
|
10946
|
-
inputSchema:
|
|
11618
|
+
inputSchema: z17.object({}),
|
|
10947
11619
|
paths: () => [],
|
|
10948
11620
|
execute: async (_input, _signal, context) => registry.list(owner(context)),
|
|
10949
11621
|
presentResult: (output) => jobListPresentation(output)
|
|
@@ -11018,14 +11690,14 @@ var ELLIPSIS = "\u2026";
|
|
|
11018
11690
|
var TERMINATION_GRACE_MS = 250;
|
|
11019
11691
|
var TERMINATION_FAILURE_MS = 5e3;
|
|
11020
11692
|
var STREAM_CLOSE_GRACE_MS = 500;
|
|
11021
|
-
var ShellInput =
|
|
11022
|
-
command:
|
|
11023
|
-
args:
|
|
11024
|
-
cwd:
|
|
11025
|
-
timeoutMs:
|
|
11693
|
+
var ShellInput = z18.object({
|
|
11694
|
+
command: z18.string().min(1),
|
|
11695
|
+
args: z18.array(z18.string()),
|
|
11696
|
+
cwd: z18.string().min(1).nullable().optional(),
|
|
11697
|
+
timeoutMs: z18.coerce.number().int().positive().max(864e5).nullable().optional(),
|
|
11026
11698
|
// Accepts booleans and their string forms (weak-typed models emit "true");
|
|
11027
11699
|
// kept transform-free so the schema converts to JSON Schema for providers.
|
|
11028
|
-
background:
|
|
11700
|
+
background: z18.union([z18.boolean(), z18.string().refine((value) => value === "true" || value === "false")]).optional()
|
|
11029
11701
|
});
|
|
11030
11702
|
function normalizeShellCommand(input) {
|
|
11031
11703
|
const { command, args } = input;
|
|
@@ -11083,7 +11755,7 @@ function buildWindowsCommandLine(kind, commandLine, command, args) {
|
|
|
11083
11755
|
return `chcp 65001>nul & ${executable} -NoLogo -NoProfile -NonInteractive -ExecutionPolicy Bypass -EncodedCommand ${encoded}`;
|
|
11084
11756
|
}
|
|
11085
11757
|
function createShellTool(workspace, options = {}) {
|
|
11086
|
-
const root =
|
|
11758
|
+
const root = resolve14(workspace);
|
|
11087
11759
|
const maxBytes = options.maxOutputBytes ?? DEFAULT_MAX_OUTPUT_BYTES2;
|
|
11088
11760
|
const defaultTimeoutMs = options.defaultTimeoutMs ?? DEFAULT_SHELL_TIMEOUT_MS;
|
|
11089
11761
|
if (!Number.isSafeInteger(maxBytes) || maxBytes <= 0) throw new Error("maxOutputBytes must be a positive integer");
|
|
@@ -11372,9 +12044,9 @@ function backgroundFlag(value) {
|
|
|
11372
12044
|
return value === true || value === "true";
|
|
11373
12045
|
}
|
|
11374
12046
|
function workingDirectory(root, cwd = ".") {
|
|
11375
|
-
const candidate =
|
|
12047
|
+
const candidate = resolve14(root, cwd);
|
|
11376
12048
|
const delta = relative11(root, candidate);
|
|
11377
|
-
if (delta === ".." || delta.startsWith(`..${
|
|
12049
|
+
if (delta === ".." || delta.startsWith(`..${sep11}`) || isAbsolute6(delta)) throw new Error("Working directory is outside the workspace");
|
|
11378
12050
|
return candidate;
|
|
11379
12051
|
}
|
|
11380
12052
|
async function terminateTree(pid) {
|
|
@@ -11484,13 +12156,13 @@ var SCRIPT_PRIORITY = ["test", "typecheck", "lint", "build", "smoke:install"];
|
|
|
11484
12156
|
async function inferVerificationPlan(workspace) {
|
|
11485
12157
|
const commands = [];
|
|
11486
12158
|
const seen = /* @__PURE__ */ new Set();
|
|
11487
|
-
const packageJson = await optionalJson(
|
|
12159
|
+
const packageJson = await optionalJson(join17(workspace, "package.json"));
|
|
11488
12160
|
const scripts = isRecord2(packageJson?.scripts) ? packageJson.scripts : {};
|
|
11489
12161
|
for (const name of SCRIPT_PRIORITY) {
|
|
11490
12162
|
if (typeof scripts[name] !== "string" || isUnconditionalPassThrough(scripts[name])) continue;
|
|
11491
12163
|
add(commands, seen, npmScript(name));
|
|
11492
12164
|
}
|
|
11493
|
-
const flavor = await optionalText(
|
|
12165
|
+
const flavor = await optionalText(join17(workspace, "FLAVOR.md"));
|
|
11494
12166
|
if (flavor !== void 0) {
|
|
11495
12167
|
for (const match of flavor.matchAll(/^- `([^`]+)`\s*$/gm)) {
|
|
11496
12168
|
const parsed = parseTrustedCommand(match[1] ?? "");
|
|
@@ -11557,7 +12229,7 @@ function isUnconditionalPassThrough(script) {
|
|
|
11557
12229
|
}
|
|
11558
12230
|
async function optionalText(path) {
|
|
11559
12231
|
try {
|
|
11560
|
-
return await
|
|
12232
|
+
return await readFile12(path, "utf8");
|
|
11561
12233
|
} catch (error) {
|
|
11562
12234
|
if (isCode5(error, "ENOENT")) return void 0;
|
|
11563
12235
|
throw error;
|
|
@@ -11578,7 +12250,7 @@ function isCode5(error, code) {
|
|
|
11578
12250
|
|
|
11579
12251
|
// src/models/anthropic.ts
|
|
11580
12252
|
import Anthropic from "@anthropic-ai/sdk";
|
|
11581
|
-
import { readFile as
|
|
12253
|
+
import { readFile as readFile13 } from "fs/promises";
|
|
11582
12254
|
|
|
11583
12255
|
// src/utils/json.ts
|
|
11584
12256
|
function safeParseJson(input) {
|
|
@@ -11780,7 +12452,7 @@ var AnthropicModelAdapter = class {
|
|
|
11780
12452
|
source: {
|
|
11781
12453
|
type: "base64",
|
|
11782
12454
|
media_type: block.mediaType,
|
|
11783
|
-
data: (await
|
|
12455
|
+
data: (await readFile13(block.source.path)).toString("base64")
|
|
11784
12456
|
}
|
|
11785
12457
|
}));
|
|
11786
12458
|
if (message2.cacheBreakpoint && Array.isArray(content) && content.length > 0) {
|
|
@@ -11962,7 +12634,7 @@ function resolveCacheProfile(input) {
|
|
|
11962
12634
|
|
|
11963
12635
|
// src/models/openai.ts
|
|
11964
12636
|
import OpenAI from "openai";
|
|
11965
|
-
import { readFile as
|
|
12637
|
+
import { readFile as readFile14 } from "fs/promises";
|
|
11966
12638
|
async function toInput(message2) {
|
|
11967
12639
|
if (message2.role === "tool") {
|
|
11968
12640
|
if (!message2.toolCallId) throw new Error("Tool messages require toolCallId");
|
|
@@ -11974,7 +12646,7 @@ async function toInput(message2) {
|
|
|
11974
12646
|
}
|
|
11975
12647
|
const content = typeof message2.content === "string" ? message2.content : await Promise.all(message2.content.map(async (block) => block.type === "text" ? { type: "input_text", text: block.text } : {
|
|
11976
12648
|
type: "input_image",
|
|
11977
|
-
image_url: `data:${block.mediaType};base64,${(await
|
|
12649
|
+
image_url: `data:${block.mediaType};base64,${(await readFile14(block.source.path)).toString("base64")}`,
|
|
11978
12650
|
detail: "auto"
|
|
11979
12651
|
}));
|
|
11980
12652
|
return [
|
|
@@ -12193,8 +12865,8 @@ var ModelRegistry = class {
|
|
|
12193
12865
|
};
|
|
12194
12866
|
|
|
12195
12867
|
// src/mcp/client.ts
|
|
12196
|
-
import { createHash as
|
|
12197
|
-
import { z as
|
|
12868
|
+
import { createHash as createHash11 } from "crypto";
|
|
12869
|
+
import { z as z19 } from "zod";
|
|
12198
12870
|
var McpManager = class {
|
|
12199
12871
|
#workspace;
|
|
12200
12872
|
#clientFactory;
|
|
@@ -12330,7 +13002,7 @@ async function listAllTools(client) {
|
|
|
12330
13002
|
function createMcpToolDefinition(serverName, timeoutMs, client, remote) {
|
|
12331
13003
|
if (remote.name.length === 0) throw new Error("MCP tool name must not be empty");
|
|
12332
13004
|
if (!isRecord3(remote.inputSchema)) throw new Error(`MCP tool "${remote.name}" has no JSON object input schema`);
|
|
12333
|
-
const inputSchema =
|
|
13005
|
+
const inputSchema = z19.fromJSONSchema(
|
|
12334
13006
|
remote.inputSchema
|
|
12335
13007
|
);
|
|
12336
13008
|
return {
|
|
@@ -12360,7 +13032,7 @@ function mcpToolName(serverName, remoteName) {
|
|
|
12360
13032
|
const safe = remoteName.replace(/[^A-Za-z0-9_-]/g, "_") || "tool";
|
|
12361
13033
|
const direct = `${prefix}${safe}`;
|
|
12362
13034
|
if (direct.length <= 64 && safe === remoteName) return direct;
|
|
12363
|
-
const suffix = `__${
|
|
13035
|
+
const suffix = `__${createHash11("sha256").update(remoteName).digest("hex").slice(0, 8)}`;
|
|
12364
13036
|
return `${prefix}${safe.slice(0, Math.max(1, 64 - prefix.length - suffix.length))}${suffix}`;
|
|
12365
13037
|
}
|
|
12366
13038
|
function mcpErrorText(content) {
|
|
@@ -12379,7 +13051,7 @@ function isRecord3(value) {
|
|
|
12379
13051
|
}
|
|
12380
13052
|
|
|
12381
13053
|
// src/mcp/sdk.ts
|
|
12382
|
-
import { resolve as
|
|
13054
|
+
import { resolve as resolve15 } from "path";
|
|
12383
13055
|
import { Client } from "@modelcontextprotocol/sdk/client/index.js";
|
|
12384
13056
|
import {
|
|
12385
13057
|
getDefaultEnvironment,
|
|
@@ -12401,7 +13073,7 @@ async function connectSdkMcpClient(input, dependencies = DEFAULT_DEPENDENCIES) {
|
|
|
12401
13073
|
command: config.command,
|
|
12402
13074
|
args: [...config.args],
|
|
12403
13075
|
env: { ...dependencies.defaultEnvironment(), ...config.env },
|
|
12404
|
-
cwd:
|
|
13076
|
+
cwd: resolve15(input.workspace, config.cwd ?? "."),
|
|
12405
13077
|
// MCP servers frequently log startup banners to stderr. Inheriting that
|
|
12406
13078
|
// stream corrupts Ink's input area, so connection errors are surfaced
|
|
12407
13079
|
// through McpManager diagnostics instead.
|
|
@@ -12426,14 +13098,14 @@ async function connectSdkMcpClient(input, dependencies = DEFAULT_DEPENDENCIES) {
|
|
|
12426
13098
|
}
|
|
12427
13099
|
|
|
12428
13100
|
// src/auth/oauth.ts
|
|
12429
|
-
import { randomBytes as randomBytes2, createHash as
|
|
13101
|
+
import { randomBytes as randomBytes2, createHash as createHash13 } from "crypto";
|
|
12430
13102
|
import { createServer as createServer2 } from "http";
|
|
12431
13103
|
import { URL as URL2, URLSearchParams } from "url";
|
|
12432
13104
|
|
|
12433
13105
|
// src/auth/oauth-config.ts
|
|
12434
|
-
import { createHash as
|
|
13106
|
+
import { createHash as createHash12 } from "crypto";
|
|
12435
13107
|
function oauthCredentialId(tokenUrl, clientId) {
|
|
12436
|
-
const digest =
|
|
13108
|
+
const digest = createHash12("sha256").update(`${tokenUrl}\0${clientId}`, "utf8").digest("base64url");
|
|
12437
13109
|
return `oauth:${digest}`;
|
|
12438
13110
|
}
|
|
12439
13111
|
function parseOAuthLlmConfig(value) {
|
|
@@ -12606,8 +13278,8 @@ var OAuthCallbackAuthProvider = class {
|
|
|
12606
13278
|
async #startCallbackServer(expectedState, signal) {
|
|
12607
13279
|
let serverResolve;
|
|
12608
13280
|
let serverReject;
|
|
12609
|
-
const codePromise = new Promise((
|
|
12610
|
-
serverResolve =
|
|
13281
|
+
const codePromise = new Promise((resolve28, reject) => {
|
|
13282
|
+
serverResolve = resolve28;
|
|
12611
13283
|
serverReject = reject;
|
|
12612
13284
|
});
|
|
12613
13285
|
const server = createServer2((req, res) => {
|
|
@@ -12641,7 +13313,7 @@ var OAuthCallbackAuthProvider = class {
|
|
|
12641
13313
|
);
|
|
12642
13314
|
serverResolve(code);
|
|
12643
13315
|
});
|
|
12644
|
-
return new Promise((
|
|
13316
|
+
return new Promise((resolve28, reject) => {
|
|
12645
13317
|
server.once("error", (error) => {
|
|
12646
13318
|
reject(new Error(`OAuth callback server failed: ${error.message}`));
|
|
12647
13319
|
});
|
|
@@ -12658,7 +13330,7 @@ var OAuthCallbackAuthProvider = class {
|
|
|
12658
13330
|
signal.addEventListener("abort", () => {
|
|
12659
13331
|
serverReject(new Error("OAuth flow aborted"));
|
|
12660
13332
|
}, { once: true });
|
|
12661
|
-
|
|
13333
|
+
resolve28({ redirectUri, promise: codePromise });
|
|
12662
13334
|
});
|
|
12663
13335
|
});
|
|
12664
13336
|
}
|
|
@@ -12690,7 +13362,7 @@ var OAuthCallbackAuthProvider = class {
|
|
|
12690
13362
|
return randomBytes2(32).toString("base64url");
|
|
12691
13363
|
}
|
|
12692
13364
|
#computeCodeChallenge(verifier) {
|
|
12693
|
-
return
|
|
13365
|
+
return createHash13("sha256").update(verifier).digest("base64url");
|
|
12694
13366
|
}
|
|
12695
13367
|
#generateState() {
|
|
12696
13368
|
return randomBytes2(32).toString("base64url");
|
|
@@ -12799,6 +13471,141 @@ function isStoredToken(value) {
|
|
|
12799
13471
|
return typeof value === "object" && value !== null && "accessToken" in value && typeof value.accessToken === "string" && value.accessToken.length > 0 && "expiresAt" in value && typeof value.expiresAt === "string" && (!("configVersion" in value) || value.configVersion === void 0 || Number.isInteger(value.configVersion) && value.configVersion >= 1) && (!("llmConfig" in value) || value.llmConfig === void 0 || isOAuthLlmConfig(value.llmConfig));
|
|
12800
13472
|
}
|
|
12801
13473
|
|
|
13474
|
+
// src/permissions/policy.ts
|
|
13475
|
+
import { readFile as readFile15 } from "fs/promises";
|
|
13476
|
+
import { join as join18, resolve as resolve16 } from "path";
|
|
13477
|
+
import { z as z20 } from "zod";
|
|
13478
|
+
var DecisionSchema2 = z20.enum(["allow", "ask", "deny"]);
|
|
13479
|
+
var PrefixSchema = z20.array(z20.string().min(1).max(1024)).min(1).max(64);
|
|
13480
|
+
var RuleSchema = z20.object({
|
|
13481
|
+
id: z20.string().regex(/^[A-Za-z0-9][A-Za-z0-9._-]{0,127}$/),
|
|
13482
|
+
decision: DecisionSchema2,
|
|
13483
|
+
prefix: PrefixSchema,
|
|
13484
|
+
tool: z20.string().min(1).max(256).optional(),
|
|
13485
|
+
agent: z20.enum(["main", "subagent"]).optional(),
|
|
13486
|
+
justification: z20.string().min(1).max(2048).optional(),
|
|
13487
|
+
match: z20.array(PrefixSchema).max(100).optional(),
|
|
13488
|
+
notMatch: z20.array(PrefixSchema).max(100).optional()
|
|
13489
|
+
}).strict();
|
|
13490
|
+
var PolicyFileSchema = z20.object({ version: z20.literal(1), rules: z20.array(RuleSchema).max(1e4) }).strict();
|
|
13491
|
+
var CompiledPermissionPolicy = class {
|
|
13492
|
+
#rules;
|
|
13493
|
+
diagnostics;
|
|
13494
|
+
constructor(rules, diagnostics = []) {
|
|
13495
|
+
this.#rules = [...rules];
|
|
13496
|
+
this.diagnostics = [...diagnostics];
|
|
13497
|
+
}
|
|
13498
|
+
get rules() {
|
|
13499
|
+
return this.#rules.map((rule) => ({ ...rule, prefix: [...rule.prefix] }));
|
|
13500
|
+
}
|
|
13501
|
+
decide(request) {
|
|
13502
|
+
const tokens = requestTokens(request);
|
|
13503
|
+
const matches = this.#rules.filter((rule) => (rule.tool === void 0 || same(rule.tool, request.tool)) && (rule.agent === void 0 || rule.agent === request.agent) && prefixMatches(rule.prefix, tokens));
|
|
13504
|
+
if (matches.length === 0) return void 0;
|
|
13505
|
+
const strictest = matches.reduce((selected, candidate) => rank(candidate.decision) > rank(selected.decision) ? candidate : selected);
|
|
13506
|
+
const reason = strictest.justification ?? `Permission rule ${strictest.id} from ${strictest.tier} policy`;
|
|
13507
|
+
return { decision: strictest.decision, reason };
|
|
13508
|
+
}
|
|
13509
|
+
};
|
|
13510
|
+
async function loadPermissionPolicy(options) {
|
|
13511
|
+
const sources = [
|
|
13512
|
+
...options.managedPath === void 0 ? [] : [{ tier: "managed", path: resolve16(options.managedPath) }],
|
|
13513
|
+
{ tier: "user", path: join18(resolve16(options.home), ".flavor-code", "permissions.json") },
|
|
13514
|
+
{ tier: "project", path: join18(resolve16(options.workspace), ".flavor", "permissions.json") },
|
|
13515
|
+
{ tier: "local", path: join18(resolve16(options.workspace), ".flavor", "permissions.local.json") }
|
|
13516
|
+
];
|
|
13517
|
+
const rules = [];
|
|
13518
|
+
for (const source of sources) {
|
|
13519
|
+
let raw;
|
|
13520
|
+
try {
|
|
13521
|
+
raw = await readFile15(source.path, "utf8");
|
|
13522
|
+
} catch (error) {
|
|
13523
|
+
if (isCode6(error, "ENOENT")) continue;
|
|
13524
|
+
throw error;
|
|
13525
|
+
}
|
|
13526
|
+
let parsed;
|
|
13527
|
+
try {
|
|
13528
|
+
parsed = PolicyFileSchema.parse(JSON.parse(raw));
|
|
13529
|
+
} catch (error) {
|
|
13530
|
+
throw new Error(`Invalid ${source.tier} permission policy ${source.path}: ${errorMessage(error)}`);
|
|
13531
|
+
}
|
|
13532
|
+
for (const input of parsed.rules) {
|
|
13533
|
+
validateExamples(input, source.path);
|
|
13534
|
+
rules.push({ ...input, source: source.path, tier: source.tier });
|
|
13535
|
+
}
|
|
13536
|
+
}
|
|
13537
|
+
for (const input of options.sessionRules ?? []) {
|
|
13538
|
+
const rule = RuleSchema.parse(input);
|
|
13539
|
+
validateExamples(rule, "session");
|
|
13540
|
+
rules.push({ ...rule, source: "session", tier: "session" });
|
|
13541
|
+
}
|
|
13542
|
+
const ids = /* @__PURE__ */ new Set();
|
|
13543
|
+
for (const rule of rules) {
|
|
13544
|
+
if (ids.has(rule.id)) throw new Error(`Duplicate permission rule id: ${rule.id}`);
|
|
13545
|
+
ids.add(rule.id);
|
|
13546
|
+
}
|
|
13547
|
+
return new CompiledPermissionPolicy(rules, shadowDiagnostics(rules));
|
|
13548
|
+
}
|
|
13549
|
+
function validateExamples(rule, source) {
|
|
13550
|
+
for (const example of rule.match ?? []) {
|
|
13551
|
+
if (!prefixMatches(rule.prefix, example)) throw new Error(`Permission rule ${rule.id} in ${source} failed a match example`);
|
|
13552
|
+
}
|
|
13553
|
+
for (const example of rule.notMatch ?? []) {
|
|
13554
|
+
if (prefixMatches(rule.prefix, example)) throw new Error(`Permission rule ${rule.id} in ${source} failed a notMatch example`);
|
|
13555
|
+
}
|
|
13556
|
+
}
|
|
13557
|
+
function shadowDiagnostics(rules) {
|
|
13558
|
+
const diagnostics = [];
|
|
13559
|
+
for (let index = 0; index < rules.length; index += 1) {
|
|
13560
|
+
const rule = rules[index];
|
|
13561
|
+
const shadow = rules.slice(0, index).find((candidate) => sameScope(candidate, rule) && prefixMatches(candidate.prefix, rule.prefix) && rank(candidate.decision) >= rank(rule.decision));
|
|
13562
|
+
if (shadow !== void 0) diagnostics.push(`Permission rule ${rule.id} is shadowed by ${shadow.id}`);
|
|
13563
|
+
}
|
|
13564
|
+
return diagnostics;
|
|
13565
|
+
}
|
|
13566
|
+
function sameScope(left, right) {
|
|
13567
|
+
return (left.tool === void 0 || right.tool === void 0 || same(left.tool, right.tool)) && (left.agent === void 0 || right.agent === void 0 || left.agent === right.agent);
|
|
13568
|
+
}
|
|
13569
|
+
function requestTokens(request) {
|
|
13570
|
+
if (request.command === void 0) return [request.tool];
|
|
13571
|
+
if (request.args !== void 0 && request.args.length > 0) return [request.command, ...request.args];
|
|
13572
|
+
return splitCommand(request.command);
|
|
13573
|
+
}
|
|
13574
|
+
function splitCommand(command) {
|
|
13575
|
+
const result = [];
|
|
13576
|
+
let current = "";
|
|
13577
|
+
let quote;
|
|
13578
|
+
for (const character of command.trim()) {
|
|
13579
|
+
if (character === "'" || character === '"') {
|
|
13580
|
+
if (quote === character) quote = void 0;
|
|
13581
|
+
else if (quote === void 0) quote = character;
|
|
13582
|
+
else current += character;
|
|
13583
|
+
} else if (/\s/.test(character) && quote === void 0) {
|
|
13584
|
+
if (current.length > 0) {
|
|
13585
|
+
result.push(current);
|
|
13586
|
+
current = "";
|
|
13587
|
+
}
|
|
13588
|
+
} else current += character;
|
|
13589
|
+
}
|
|
13590
|
+
if (current.length > 0) result.push(current);
|
|
13591
|
+
return result;
|
|
13592
|
+
}
|
|
13593
|
+
function prefixMatches(prefix, input) {
|
|
13594
|
+
return prefix.length <= input.length && prefix.every((token, index) => same(token, input[index] ?? ""));
|
|
13595
|
+
}
|
|
13596
|
+
function same(left, right) {
|
|
13597
|
+
return process.platform === "win32" ? left.toLowerCase() === right.toLowerCase() : left === right;
|
|
13598
|
+
}
|
|
13599
|
+
function rank(decision) {
|
|
13600
|
+
return decision === "deny" ? 2 : decision === "ask" ? 1 : 0;
|
|
13601
|
+
}
|
|
13602
|
+
function isCode6(error, code) {
|
|
13603
|
+
return typeof error === "object" && error !== null && "code" in error && error.code === code;
|
|
13604
|
+
}
|
|
13605
|
+
function errorMessage(error) {
|
|
13606
|
+
return error instanceof Error ? error.message : String(error);
|
|
13607
|
+
}
|
|
13608
|
+
|
|
12802
13609
|
// src/prompts/system.ts
|
|
12803
13610
|
function buildSystemPrompt(options) {
|
|
12804
13611
|
const sections = [
|
|
@@ -12925,9 +13732,9 @@ function data(value) {
|
|
|
12925
13732
|
}
|
|
12926
13733
|
|
|
12927
13734
|
// src/skills/registry.ts
|
|
12928
|
-
import { constants as
|
|
12929
|
-
import { lstat as lstat8, open as defaultOpen, readdir as
|
|
12930
|
-
import { isAbsolute as isAbsolute7, relative as relative12, resolve as
|
|
13735
|
+
import { constants as constants4 } from "fs";
|
|
13736
|
+
import { lstat as lstat8, open as defaultOpen, readdir as readdir5, realpath as realpath6 } from "fs/promises";
|
|
13737
|
+
import { isAbsolute as isAbsolute7, relative as relative12, resolve as resolve17, sep as sep12 } from "path";
|
|
12931
13738
|
import { marked } from "marked";
|
|
12932
13739
|
import { parseDocument } from "yaml";
|
|
12933
13740
|
var SKILL_NAME = /^[a-z0-9]+(?:-[a-z0-9]+)*$/;
|
|
@@ -13058,7 +13865,7 @@ var SkillRegistry = class {
|
|
|
13058
13865
|
const body = await this.loadBody(record.metadata);
|
|
13059
13866
|
const references = extractResourceReferences(body);
|
|
13060
13867
|
if (!references.has(normalized)) throw new Error(`Skill resource is not directly referenced: ${reference}`);
|
|
13061
|
-
const candidate =
|
|
13868
|
+
const candidate = resolve17(record.metadata.root, ...parts);
|
|
13062
13869
|
const file = await openVerifiedFile(candidate, record.metadata.root, "Resource", this.#options.openFile);
|
|
13063
13870
|
try {
|
|
13064
13871
|
if (file.snapshot.size > BigInt(this.#options.maxResourceBytes)) throw new Error(`Skill resource is too large: ${reference}`);
|
|
@@ -13087,14 +13894,14 @@ var SkillRegistry = class {
|
|
|
13087
13894
|
async #discoverSource(roots, source) {
|
|
13088
13895
|
const found = [];
|
|
13089
13896
|
for (const configuredRoot of roots) {
|
|
13090
|
-
const root =
|
|
13897
|
+
const root = resolve17(configuredRoot);
|
|
13091
13898
|
let entries;
|
|
13092
13899
|
let physicalRoot = root;
|
|
13093
13900
|
try {
|
|
13094
13901
|
const rootInfo = await lstat8(root);
|
|
13095
13902
|
if (rootInfo.isSymbolicLink() || !rootInfo.isDirectory()) throw new Error("Skill root must be a real directory");
|
|
13096
13903
|
physicalRoot = await realpath6(root);
|
|
13097
|
-
entries = await
|
|
13904
|
+
entries = await readdir5(root, { withFileTypes: true });
|
|
13098
13905
|
} catch (error) {
|
|
13099
13906
|
if (isMissing2(error)) continue;
|
|
13100
13907
|
this.#diagnose(root, error);
|
|
@@ -13102,13 +13909,13 @@ var SkillRegistry = class {
|
|
|
13102
13909
|
}
|
|
13103
13910
|
entries.sort((left, right) => compareCodePoints(left.name, right.name));
|
|
13104
13911
|
for (const entry2 of entries) {
|
|
13105
|
-
const skillRoot =
|
|
13912
|
+
const skillRoot = resolve17(root, entry2.name);
|
|
13106
13913
|
try {
|
|
13107
13914
|
if (entry2.isSymbolicLink()) throw new Error("Symlinked skill directories are not allowed");
|
|
13108
13915
|
if (!entry2.isDirectory()) continue;
|
|
13109
13916
|
const physicalSkillRoot = await realpath6(skillRoot);
|
|
13110
|
-
if (!
|
|
13111
|
-
const skillFile =
|
|
13917
|
+
if (!isWithin4(physicalRoot, physicalSkillRoot)) throw new Error("Skill directory escapes its registry root");
|
|
13918
|
+
const skillFile = resolve17(skillRoot, "SKILL.md");
|
|
13112
13919
|
const parsed = await readFrontmatterFile(
|
|
13113
13920
|
skillFile,
|
|
13114
13921
|
physicalSkillRoot,
|
|
@@ -13249,9 +14056,9 @@ async function openVerifiedFile(path, root, label, opener, expected) {
|
|
|
13249
14056
|
if (initial.isSymbolicLink()) throw new Error(`${label} symlinks are not allowed`);
|
|
13250
14057
|
assertRegular(initial, label);
|
|
13251
14058
|
const canonical3 = await realpath6(path);
|
|
13252
|
-
if (!
|
|
13253
|
-
const noFollow = typeof
|
|
13254
|
-
const handle = await opener(canonical3,
|
|
14059
|
+
if (!isWithin4(root, canonical3)) throw new Error(`${label} escapes the skill root`);
|
|
14060
|
+
const noFollow = typeof constants4.O_NOFOLLOW === "number" ? constants4.O_NOFOLLOW : 0;
|
|
14061
|
+
const handle = await opener(canonical3, constants4.O_RDONLY | noFollow);
|
|
13255
14062
|
try {
|
|
13256
14063
|
const opened = await handle.stat({ bigint: true });
|
|
13257
14064
|
assertRegular(opened, label);
|
|
@@ -13308,9 +14115,9 @@ function decodeUtf82(content, label) {
|
|
|
13308
14115
|
throw new Error(`${label} contains invalid UTF-8 encoding`, { cause: error });
|
|
13309
14116
|
}
|
|
13310
14117
|
}
|
|
13311
|
-
function
|
|
14118
|
+
function isWithin4(root, candidate) {
|
|
13312
14119
|
const delta = relative12(root, candidate);
|
|
13313
|
-
return delta === "" || delta !== ".." && !delta.startsWith(`..${
|
|
14120
|
+
return delta === "" || delta !== ".." && !delta.startsWith(`..${sep12}`) && !isAbsolute7(delta);
|
|
13314
14121
|
}
|
|
13315
14122
|
function isMissing2(error) {
|
|
13316
14123
|
return typeof error === "object" && error !== null && "code" in error && error.code === "ENOENT";
|
|
@@ -13357,7 +14164,7 @@ function score(metadata, query) {
|
|
|
13357
14164
|
}
|
|
13358
14165
|
|
|
13359
14166
|
// src/skills/tool.ts
|
|
13360
|
-
import { z as
|
|
14167
|
+
import { z as z21 } from "zod";
|
|
13361
14168
|
|
|
13362
14169
|
// src/skills/arguments.ts
|
|
13363
14170
|
function expandSkillArguments(body, argumentsText) {
|
|
@@ -13414,13 +14221,13 @@ function splitSkillArguments(input) {
|
|
|
13414
14221
|
}
|
|
13415
14222
|
|
|
13416
14223
|
// src/skills/tool.ts
|
|
13417
|
-
var SkillInput =
|
|
13418
|
-
skill:
|
|
13419
|
-
arguments:
|
|
14224
|
+
var SkillInput = z21.object({
|
|
14225
|
+
skill: z21.string().min(1),
|
|
14226
|
+
arguments: z21.string().optional()
|
|
13420
14227
|
}).strict();
|
|
13421
|
-
var SkillResourceInput =
|
|
13422
|
-
skill:
|
|
13423
|
-
reference:
|
|
14228
|
+
var SkillResourceInput = z21.object({
|
|
14229
|
+
skill: z21.string().min(1),
|
|
14230
|
+
reference: z21.string().min(1)
|
|
13424
14231
|
}).strict();
|
|
13425
14232
|
function createSkillTool(registry) {
|
|
13426
14233
|
return {
|
|
@@ -13472,98 +14279,125 @@ function createSkillResourceTool(registry) {
|
|
|
13472
14279
|
}
|
|
13473
14280
|
|
|
13474
14281
|
// src/session/store.ts
|
|
13475
|
-
import { constants as
|
|
13476
|
-
import { lstat as lstat9, mkdir as mkdir14, open as open8, readFile as
|
|
13477
|
-
import { basename as basename3, dirname as dirname15, join as
|
|
13478
|
-
import { randomUUID as
|
|
13479
|
-
import { z as
|
|
13480
|
-
var SESSION_VERSION =
|
|
14282
|
+
import { constants as constants5 } from "fs";
|
|
14283
|
+
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";
|
|
14284
|
+
import { basename as basename3, dirname as dirname15, join as join19, relative as relative13, resolve as resolve18, sep as sep13 } from "path";
|
|
14285
|
+
import { randomUUID as randomUUID11 } from "crypto";
|
|
14286
|
+
import { z as z22 } from "zod";
|
|
14287
|
+
var SESSION_VERSION = 4;
|
|
13481
14288
|
var DEFAULT_MAX_SESSION_BYTES = 5 * 1024 * 1024;
|
|
13482
|
-
var SessionIdSchema =
|
|
13483
|
-
var IsoDateSchema =
|
|
13484
|
-
var ToolCallSchema =
|
|
13485
|
-
id:
|
|
13486
|
-
name:
|
|
13487
|
-
input:
|
|
14289
|
+
var SessionIdSchema = z22.string().regex(/^[A-Za-z0-9][A-Za-z0-9._-]{0,127}$/, "Invalid session id");
|
|
14290
|
+
var IsoDateSchema = z22.string().datetime({ offset: true });
|
|
14291
|
+
var ToolCallSchema = z22.object({
|
|
14292
|
+
id: z22.string().min(1).max(256),
|
|
14293
|
+
name: z22.string().min(1).max(256),
|
|
14294
|
+
input: z22.unknown()
|
|
13488
14295
|
}).strict();
|
|
13489
|
-
var TextContentBlockSchema =
|
|
13490
|
-
type:
|
|
13491
|
-
text:
|
|
14296
|
+
var TextContentBlockSchema = z22.object({
|
|
14297
|
+
type: z22.literal("text"),
|
|
14298
|
+
text: z22.string().max(DEFAULT_MAX_SESSION_BYTES)
|
|
13492
14299
|
}).strict();
|
|
13493
|
-
var ImageContentBlockSchema =
|
|
13494
|
-
type:
|
|
13495
|
-
source:
|
|
13496
|
-
mediaType:
|
|
13497
|
-
sha256:
|
|
13498
|
-
bytes:
|
|
13499
|
-
name:
|
|
14300
|
+
var ImageContentBlockSchema = z22.object({
|
|
14301
|
+
type: z22.literal("image"),
|
|
14302
|
+
source: z22.object({ type: z22.literal("file"), path: z22.string().min(1).max(32768) }).strict(),
|
|
14303
|
+
mediaType: z22.enum(["image/png", "image/jpeg", "image/webp"]),
|
|
14304
|
+
sha256: z22.string().regex(/^[a-f0-9]{64}$/),
|
|
14305
|
+
bytes: z22.number().int().positive().max(20 * 1024 * 1024),
|
|
14306
|
+
name: z22.string().min(1).max(255).optional()
|
|
13500
14307
|
}).strict();
|
|
13501
|
-
var UserContentSchema =
|
|
13502
|
-
|
|
13503
|
-
|
|
14308
|
+
var UserContentSchema = z22.union([
|
|
14309
|
+
z22.string().max(DEFAULT_MAX_SESSION_BYTES),
|
|
14310
|
+
z22.array(z22.discriminatedUnion("type", [TextContentBlockSchema, ImageContentBlockSchema])).min(1).max(100)
|
|
13504
14311
|
]);
|
|
13505
14312
|
var MessageMetadataShape = {
|
|
13506
|
-
toolCallId:
|
|
13507
|
-
toolCalls:
|
|
14313
|
+
toolCallId: z22.string().min(1).max(256).optional(),
|
|
14314
|
+
toolCalls: z22.array(ToolCallSchema).max(1e3).optional()
|
|
13508
14315
|
};
|
|
13509
|
-
var MessageSchema =
|
|
13510
|
-
|
|
13511
|
-
|
|
13512
|
-
|
|
14316
|
+
var MessageSchema = z22.discriminatedUnion("role", [
|
|
14317
|
+
z22.object({ role: z22.literal("user"), content: UserContentSchema, ...MessageMetadataShape }).strict(),
|
|
14318
|
+
z22.object({ role: z22.literal("system"), content: z22.string().max(DEFAULT_MAX_SESSION_BYTES), ...MessageMetadataShape }).strict(),
|
|
14319
|
+
z22.object({ role: z22.literal("assistant"), content: z22.string().max(DEFAULT_MAX_SESSION_BYTES), ...MessageMetadataShape }).strict(),
|
|
14320
|
+
z22.object({ role: z22.literal("tool"), content: z22.string().max(DEFAULT_MAX_SESSION_BYTES), ...MessageMetadataShape }).strict()
|
|
13513
14321
|
]);
|
|
13514
|
-
var LegacySummarySchema =
|
|
13515
|
-
var CompactBoundarySchema =
|
|
13516
|
-
summary:
|
|
14322
|
+
var LegacySummarySchema = z22.object({ role: z22.literal("system"), content: z22.string().max(DEFAULT_MAX_SESSION_BYTES) }).strict();
|
|
14323
|
+
var CompactBoundarySchema = z22.object({
|
|
14324
|
+
summary: z22.string().min(1).max(DEFAULT_MAX_SESSION_BYTES),
|
|
13517
14325
|
compactedAt: IsoDateSchema
|
|
13518
14326
|
}).strict();
|
|
13519
|
-
var StateSchema =
|
|
13520
|
-
var MemoryLifecycleSchema =
|
|
13521
|
-
status:
|
|
13522
|
-
taskId:
|
|
13523
|
-
messageStart:
|
|
14327
|
+
var StateSchema = z22.enum(["pending", "running", "completed", "failed", "blocked", "cancelled"]);
|
|
14328
|
+
var MemoryLifecycleSchema = z22.object({
|
|
14329
|
+
status: z22.enum(["active", "completed", "abandoned"]),
|
|
14330
|
+
taskId: z22.string().regex(/^memory-[A-Za-z0-9._-]{1,120}$/).optional(),
|
|
14331
|
+
messageStart: z22.number().int().min(0).max(5e4).optional(),
|
|
13524
14332
|
finalizedAt: IsoDateSchema.optional(),
|
|
13525
|
-
transcriptHash:
|
|
14333
|
+
transcriptHash: z22.string().regex(/^[a-f0-9]{64}$/).optional()
|
|
13526
14334
|
}).strict();
|
|
13527
|
-
var SessionBaseSchema =
|
|
14335
|
+
var SessionBaseSchema = z22.object({
|
|
13528
14336
|
sessionId: SessionIdSchema,
|
|
13529
14337
|
createdAt: IsoDateSchema,
|
|
13530
14338
|
updatedAt: IsoDateSchema,
|
|
13531
|
-
workspace:
|
|
13532
|
-
tasks:
|
|
14339
|
+
workspace: z22.object({ path: z22.string().min(1).max(32768) }).strict(),
|
|
14340
|
+
tasks: z22.object({
|
|
13533
14341
|
plan: TaskPlanSchema.optional(),
|
|
13534
14342
|
graph: TaskGraphSchema.optional(),
|
|
13535
|
-
states:
|
|
13536
|
-
results:
|
|
14343
|
+
states: z22.record(z22.string(), StateSchema),
|
|
14344
|
+
results: z22.record(z22.string(), SubagentResultSchema)
|
|
13537
14345
|
}).strict(),
|
|
13538
|
-
models:
|
|
14346
|
+
models: z22.object({ main: z22.string().min(1).max(1024), subagent: z22.string().min(1).max(1024) }).strict(),
|
|
13539
14347
|
permissionMode: PermissionModeSchema,
|
|
13540
14348
|
memory: MemoryLifecycleSchema.optional()
|
|
13541
14349
|
}).strict();
|
|
13542
14350
|
var SessionDocumentV1Schema = SessionBaseSchema.extend({
|
|
13543
|
-
version:
|
|
13544
|
-
conversation:
|
|
14351
|
+
version: z22.literal(1),
|
|
14352
|
+
conversation: z22.object({
|
|
13545
14353
|
summary: LegacySummarySchema.optional(),
|
|
13546
|
-
messages:
|
|
14354
|
+
messages: z22.array(MessageSchema).max(5e4)
|
|
13547
14355
|
}).strict()
|
|
13548
14356
|
}).strict();
|
|
13549
|
-
var ConversationSchema =
|
|
14357
|
+
var ConversationSchema = z22.object({
|
|
13550
14358
|
compact: CompactBoundarySchema.optional(),
|
|
13551
|
-
messages:
|
|
14359
|
+
messages: z22.array(MessageSchema).max(5e4)
|
|
13552
14360
|
}).strict();
|
|
13553
14361
|
var SessionDocumentV2Schema = SessionBaseSchema.extend({
|
|
13554
|
-
version:
|
|
14362
|
+
version: z22.literal(2),
|
|
13555
14363
|
conversation: ConversationSchema
|
|
13556
14364
|
}).strict();
|
|
13557
|
-
var TranscriptStateSchema =
|
|
13558
|
-
var TimelineSchema =
|
|
13559
|
-
version:
|
|
14365
|
+
var TranscriptStateSchema = z22.custom(isTranscriptState, "Invalid transcript state");
|
|
14366
|
+
var TimelineSchema = z22.object({
|
|
14367
|
+
version: z22.literal(1),
|
|
13560
14368
|
state: TranscriptStateSchema
|
|
13561
14369
|
}).strict();
|
|
14370
|
+
var ContextEpochSchema = z22.object({
|
|
14371
|
+
version: z22.literal(1),
|
|
14372
|
+
id: z22.string().uuid(),
|
|
14373
|
+
startedAt: IsoDateSchema,
|
|
14374
|
+
stableMessages: z22.array(z22.object({
|
|
14375
|
+
role: z22.literal("system"),
|
|
14376
|
+
content: z22.string().max(DEFAULT_MAX_SESSION_BYTES),
|
|
14377
|
+
cacheBreakpoint: z22.boolean().optional()
|
|
14378
|
+
}).strict()).max(1e3),
|
|
14379
|
+
sources: z22.record(z22.string().min(1).max(128), z22.string().max(DEFAULT_MAX_SESSION_BYTES)),
|
|
14380
|
+
stableSourceHash: z22.string().regex(/^[a-f0-9]{64}$/)
|
|
14381
|
+
}).strict();
|
|
14382
|
+
var ContextVisibilityRecordSchema = z22.object({
|
|
14383
|
+
id: z22.string().uuid(),
|
|
14384
|
+
role: z22.literal("system"),
|
|
14385
|
+
content: z22.string().max(DEFAULT_MAX_SESSION_BYTES),
|
|
14386
|
+
admittedAt: IsoDateSchema,
|
|
14387
|
+
scope: z22.literal("run")
|
|
14388
|
+
}).strict();
|
|
14389
|
+
var SessionDocumentV3Schema = SessionBaseSchema.extend({
|
|
14390
|
+
version: z22.literal(3),
|
|
14391
|
+
conversation: ConversationSchema,
|
|
14392
|
+
timeline: TimelineSchema
|
|
14393
|
+
}).strict();
|
|
13562
14394
|
var SessionDocumentSchema = SessionBaseSchema.extend({
|
|
13563
|
-
version:
|
|
13564
|
-
conversation:
|
|
14395
|
+
version: z22.literal(SESSION_VERSION),
|
|
14396
|
+
conversation: z22.object({
|
|
13565
14397
|
compact: CompactBoundarySchema.optional(),
|
|
13566
|
-
|
|
14398
|
+
epoch: ContextEpochSchema.optional(),
|
|
14399
|
+
visibilityLog: z22.array(ContextVisibilityRecordSchema).max(1e4).optional(),
|
|
14400
|
+
messages: z22.array(MessageSchema).max(5e4)
|
|
13567
14401
|
}).strict(),
|
|
13568
14402
|
timeline: TimelineSchema
|
|
13569
14403
|
}).strict();
|
|
@@ -13573,8 +14407,8 @@ var SessionStore = class {
|
|
|
13573
14407
|
#maxBytes;
|
|
13574
14408
|
#maxSessions;
|
|
13575
14409
|
constructor(options) {
|
|
13576
|
-
this.#workspace =
|
|
13577
|
-
this.#sessions =
|
|
14410
|
+
this.#workspace = resolve18(options.workspace);
|
|
14411
|
+
this.#sessions = join19(this.#workspace, ".flavor", "sessions");
|
|
13578
14412
|
this.#maxBytes = options.maxBytes ?? DEFAULT_MAX_SESSION_BYTES;
|
|
13579
14413
|
if (!Number.isSafeInteger(this.#maxBytes) || this.#maxBytes < 256) throw new Error("maxBytes must be an integer of at least 256");
|
|
13580
14414
|
this.#maxSessions = options.maxSessions ?? 50;
|
|
@@ -13585,11 +14419,13 @@ var SessionStore = class {
|
|
|
13585
14419
|
await this.#assertWorkspace(document.workspace.path);
|
|
13586
14420
|
await this.#prepareDirectory();
|
|
13587
14421
|
const target = this.#path(document.sessionId);
|
|
13588
|
-
const { conversation: { messages, compact }, timeline, ...meta } = document;
|
|
14422
|
+
const { conversation: { messages, compact, epoch, visibilityLog }, timeline, ...meta } = document;
|
|
13589
14423
|
const metaLine = JSON.stringify({
|
|
13590
14424
|
__meta: true,
|
|
13591
14425
|
...meta,
|
|
13592
14426
|
...compact === void 0 ? {} : { compact },
|
|
14427
|
+
...epoch === void 0 ? {} : { epoch },
|
|
14428
|
+
...visibilityLog === void 0 ? {} : { visibilityLog },
|
|
13593
14429
|
timeline: {
|
|
13594
14430
|
version: timeline.version,
|
|
13595
14431
|
nextId: timeline.state.nextId,
|
|
@@ -13604,10 +14440,10 @@ var SessionStore = class {
|
|
|
13604
14440
|
const body = `${lines.join("\n")}
|
|
13605
14441
|
`;
|
|
13606
14442
|
if (Buffer.byteLength(body) > this.#maxBytes) throw new Error(`Session exceeds maximum size of ${this.#maxBytes} bytes`);
|
|
13607
|
-
const temporary =
|
|
14443
|
+
const temporary = join19(this.#sessions, `.${document.sessionId}.${process.pid}.${randomUUID11()}.tmp`);
|
|
13608
14444
|
let handle;
|
|
13609
14445
|
try {
|
|
13610
|
-
handle = await open8(temporary,
|
|
14446
|
+
handle = await open8(temporary, constants5.O_CREAT | constants5.O_EXCL | constants5.O_WRONLY, 384);
|
|
13611
14447
|
await handle.writeFile(body, "utf8");
|
|
13612
14448
|
await handle.sync();
|
|
13613
14449
|
await handle.close();
|
|
@@ -13632,7 +14468,7 @@ var SessionStore = class {
|
|
|
13632
14468
|
try {
|
|
13633
14469
|
raw = await boundedRead(path, this.#maxBytes);
|
|
13634
14470
|
} catch (error) {
|
|
13635
|
-
if (
|
|
14471
|
+
if (isCode7(error, "ENOENT")) throw new Error(`Session "${sessionId}" was not found in this workspace`);
|
|
13636
14472
|
throw error;
|
|
13637
14473
|
}
|
|
13638
14474
|
let parsed;
|
|
@@ -13643,6 +14479,8 @@ var SessionStore = class {
|
|
|
13643
14479
|
throw new Error(`Session "${sessionId}" is corrupt or incompatible and was quarantined as ${basename3(quarantine)}: ${message(error)}`);
|
|
13644
14480
|
}
|
|
13645
14481
|
await this.#assertWorkspace(parsed.workspace.path);
|
|
14482
|
+
const storedVersion = storedSessionVersion(raw);
|
|
14483
|
+
if (storedVersion < SESSION_VERSION) await this.#backupLegacy(path, raw, storedVersion);
|
|
13646
14484
|
return normalizeAbandonedTasks(parsed);
|
|
13647
14485
|
}
|
|
13648
14486
|
#parseSession(raw) {
|
|
@@ -13658,19 +14496,25 @@ var SessionStore = class {
|
|
|
13658
14496
|
const timelineRecords = records.filter(isTimelineRecord);
|
|
13659
14497
|
const summary2 = meta.summary;
|
|
13660
14498
|
const compact = meta.compact;
|
|
14499
|
+
const epoch = meta.epoch;
|
|
14500
|
+
const visibilityLog = meta.visibilityLog;
|
|
13661
14501
|
const timeline = meta.timeline;
|
|
13662
14502
|
delete meta.__meta;
|
|
13663
14503
|
delete meta.summary;
|
|
13664
14504
|
delete meta.compact;
|
|
14505
|
+
delete meta.epoch;
|
|
14506
|
+
delete meta.visibilityLog;
|
|
13665
14507
|
delete meta.timeline;
|
|
13666
14508
|
candidate = {
|
|
13667
14509
|
...meta,
|
|
13668
14510
|
conversation: {
|
|
13669
14511
|
...meta.version === 1 && summary2 !== void 0 ? { summary: summary2 } : {},
|
|
13670
|
-
...(meta.version === 2 || meta.version === SESSION_VERSION) && compact !== void 0 ? { compact } : {},
|
|
14512
|
+
...(meta.version === 2 || meta.version === 3 || meta.version === SESSION_VERSION) && compact !== void 0 ? { compact } : {},
|
|
14513
|
+
...meta.version === SESSION_VERSION && epoch !== void 0 ? { epoch } : {},
|
|
14514
|
+
...meta.version === SESSION_VERSION && visibilityLog !== void 0 ? { visibilityLog } : {},
|
|
13671
14515
|
messages
|
|
13672
14516
|
},
|
|
13673
|
-
...meta.version === SESSION_VERSION ? {
|
|
14517
|
+
...meta.version === 3 || meta.version === SESSION_VERSION ? {
|
|
13674
14518
|
timeline: timelineFromRecords(timeline, timelineRecords)
|
|
13675
14519
|
} : {}
|
|
13676
14520
|
};
|
|
@@ -13683,10 +14527,10 @@ var SessionStore = class {
|
|
|
13683
14527
|
try {
|
|
13684
14528
|
await this.#assertSafeExistingDirectory();
|
|
13685
14529
|
} catch (error) {
|
|
13686
|
-
if (
|
|
14530
|
+
if (isCode7(error, "ENOENT")) return [];
|
|
13687
14531
|
throw error;
|
|
13688
14532
|
}
|
|
13689
|
-
const names = (await
|
|
14533
|
+
const names = (await readdir6(this.#sessions)).filter((name) => name.endsWith(".jsonl") && !name.endsWith(".events.jsonl")).sort();
|
|
13690
14534
|
const entries = [];
|
|
13691
14535
|
for (const name of names) {
|
|
13692
14536
|
const id = name.slice(0, -6);
|
|
@@ -13705,7 +14549,7 @@ var SessionStore = class {
|
|
|
13705
14549
|
try {
|
|
13706
14550
|
await this.#assertSafeExistingDirectory();
|
|
13707
14551
|
} catch (error) {
|
|
13708
|
-
if (
|
|
14552
|
+
if (isCode7(error, "ENOENT")) return;
|
|
13709
14553
|
throw error;
|
|
13710
14554
|
}
|
|
13711
14555
|
const path = this.#path(sessionId);
|
|
@@ -13713,14 +14557,15 @@ var SessionStore = class {
|
|
|
13713
14557
|
const metadata = await lstat9(path);
|
|
13714
14558
|
if (!metadata.isFile()) throw new Error("Session path is not a regular file");
|
|
13715
14559
|
await rm6(path);
|
|
14560
|
+
await rm6(join19(this.#sessions, `${sessionId}.events.jsonl`), { force: true }).catch(() => void 0);
|
|
13716
14561
|
} catch (error) {
|
|
13717
|
-
if (!
|
|
14562
|
+
if (!isCode7(error, "ENOENT")) throw error;
|
|
13718
14563
|
}
|
|
13719
14564
|
}
|
|
13720
14565
|
#path(sessionId) {
|
|
13721
14566
|
SessionIdSchema.parse(sessionId);
|
|
13722
|
-
const path =
|
|
13723
|
-
if (!
|
|
14567
|
+
const path = resolve18(this.#sessions, `${sessionId}.jsonl`);
|
|
14568
|
+
if (!isWithin5(this.#sessions, path)) throw new Error("Invalid session id");
|
|
13724
14569
|
return path;
|
|
13725
14570
|
}
|
|
13726
14571
|
async #prune() {
|
|
@@ -13729,6 +14574,7 @@ var SessionStore = class {
|
|
|
13729
14574
|
const excess = entries.slice(this.#maxSessions);
|
|
13730
14575
|
for (const entry2 of excess) {
|
|
13731
14576
|
await rm6(this.#path(entry2.sessionId), { force: true }).catch(() => void 0);
|
|
14577
|
+
await rm6(join19(this.#sessions, `${entry2.sessionId}.events.jsonl`), { force: true }).catch(() => void 0);
|
|
13732
14578
|
}
|
|
13733
14579
|
}
|
|
13734
14580
|
async #assertWorkspace(stored) {
|
|
@@ -13747,19 +14593,38 @@ var SessionStore = class {
|
|
|
13747
14593
|
await assertNoSymlink3(this.#workspace, this.#sessions);
|
|
13748
14594
|
const canonicalRoot = await realpath7(this.#workspace);
|
|
13749
14595
|
const canonicalSessions = await realpath7(this.#sessions);
|
|
13750
|
-
if (!
|
|
14596
|
+
if (!isWithin5(canonicalRoot, canonicalSessions)) throw new Error("Session directory escapes the workspace");
|
|
13751
14597
|
}
|
|
13752
14598
|
async #quarantine(path) {
|
|
13753
|
-
const target = `${path}.corrupt-${Date.now()}-${
|
|
14599
|
+
const target = `${path}.corrupt-${Date.now()}-${randomUUID11().slice(0, 8)}`;
|
|
13754
14600
|
await rename6(path, target);
|
|
13755
14601
|
return target;
|
|
13756
14602
|
}
|
|
14603
|
+
async #backupLegacy(path, raw, version) {
|
|
14604
|
+
const target = `${path}.v${version}.bak`;
|
|
14605
|
+
let handle;
|
|
14606
|
+
try {
|
|
14607
|
+
handle = await open8(target, constants5.O_CREAT | constants5.O_EXCL | constants5.O_WRONLY, 384);
|
|
14608
|
+
await handle.writeFile(raw, "utf8");
|
|
14609
|
+
await handle.sync();
|
|
14610
|
+
} catch (error) {
|
|
14611
|
+
if (!isCode7(error, "EEXIST")) throw error;
|
|
14612
|
+
} finally {
|
|
14613
|
+
await handle?.close().catch(() => void 0);
|
|
14614
|
+
}
|
|
14615
|
+
}
|
|
13757
14616
|
};
|
|
14617
|
+
function storedSessionVersion(raw) {
|
|
14618
|
+
const trimmed = raw.trim();
|
|
14619
|
+
const first = JSON.parse(trimmed.split("\n", 1)[0] ?? "{}");
|
|
14620
|
+
const candidate = first.__meta === true ? first.version : JSON.parse(trimmed).version;
|
|
14621
|
+
return typeof candidate === "number" && Number.isInteger(candidate) ? candidate : 0;
|
|
14622
|
+
}
|
|
13758
14623
|
async function boundedRead(path, maxBytes) {
|
|
13759
14624
|
const metadata = await stat6(path);
|
|
13760
14625
|
if (!metadata.isFile()) throw new Error("Session path is not a regular file");
|
|
13761
14626
|
if (metadata.size > maxBytes) throw new Error(`Session exceeds maximum read size of ${maxBytes} bytes`);
|
|
13762
|
-
const value = await
|
|
14627
|
+
const value = await readFile16(path);
|
|
13763
14628
|
if (value.byteLength > maxBytes) throw new Error(`Session exceeds maximum read size of ${maxBytes} bytes`);
|
|
13764
14629
|
return value.toString("utf8");
|
|
13765
14630
|
}
|
|
@@ -13816,6 +14681,10 @@ function normalizeAbandonedTasks(document) {
|
|
|
13816
14681
|
function parseAndMigrateSession(input) {
|
|
13817
14682
|
if (typeof input !== "object" || input === null || !("version" in input)) throw new Error("Session version is missing");
|
|
13818
14683
|
if (input.version === SESSION_VERSION) return SessionDocumentSchema.parse(input);
|
|
14684
|
+
if (input.version === 3) {
|
|
14685
|
+
const legacy2 = SessionDocumentV3Schema.parse(input);
|
|
14686
|
+
return SessionDocumentSchema.parse({ ...legacy2, version: SESSION_VERSION });
|
|
14687
|
+
}
|
|
13819
14688
|
if (input.version === 2) {
|
|
13820
14689
|
const legacy2 = SessionDocumentV2Schema.parse(input);
|
|
13821
14690
|
return SessionDocumentSchema.parse({
|
|
@@ -13823,7 +14692,7 @@ function parseAndMigrateSession(input) {
|
|
|
13823
14692
|
version: SESSION_VERSION,
|
|
13824
14693
|
timeline: {
|
|
13825
14694
|
version: 1,
|
|
13826
|
-
state: transcriptFromLegacyConversation(legacy2.conversation.messages, legacy2.conversation.compact)
|
|
14695
|
+
state: transcriptFromLegacyConversation(legacy2.conversation.messages.filter((message2) => message2.role !== "system"), legacy2.conversation.compact)
|
|
13827
14696
|
}
|
|
13828
14697
|
});
|
|
13829
14698
|
}
|
|
@@ -13842,7 +14711,7 @@ function parseAndMigrateSession(input) {
|
|
|
13842
14711
|
},
|
|
13843
14712
|
timeline: {
|
|
13844
14713
|
version: 1,
|
|
13845
|
-
state: transcriptFromLegacyConversation(conversation.messages, compact)
|
|
14714
|
+
state: transcriptFromLegacyConversation(conversation.messages.filter((message2) => message2.role !== "system"), compact)
|
|
13846
14715
|
}
|
|
13847
14716
|
});
|
|
13848
14717
|
}
|
|
@@ -13897,9 +14766,9 @@ function isRecord5(value) {
|
|
|
13897
14766
|
return typeof value === "object" && value !== null && !Array.isArray(value);
|
|
13898
14767
|
}
|
|
13899
14768
|
async function assertNoSymlink3(root, target) {
|
|
13900
|
-
let cursor =
|
|
13901
|
-
const stop =
|
|
13902
|
-
if (!
|
|
14769
|
+
let cursor = resolve18(target);
|
|
14770
|
+
const stop = resolve18(root);
|
|
14771
|
+
if (!isWithin5(stop, cursor)) throw new Error("Session path escapes the workspace");
|
|
13903
14772
|
const chain = [];
|
|
13904
14773
|
while (cursor !== stop) {
|
|
13905
14774
|
chain.push(cursor);
|
|
@@ -13909,22 +14778,22 @@ async function assertNoSymlink3(root, target) {
|
|
|
13909
14778
|
try {
|
|
13910
14779
|
if ((await lstat9(path)).isSymbolicLink()) throw new Error(`Session path contains a symbolic link: ${path}`);
|
|
13911
14780
|
} catch (error) {
|
|
13912
|
-
if (!
|
|
14781
|
+
if (!isCode7(error, "ENOENT")) throw error;
|
|
13913
14782
|
}
|
|
13914
14783
|
}
|
|
13915
14784
|
}
|
|
13916
|
-
function
|
|
13917
|
-
const value = relative13(
|
|
13918
|
-
return value === "" || !value.startsWith(`..${
|
|
14785
|
+
function isWithin5(root, path) {
|
|
14786
|
+
const value = relative13(resolve18(root), resolve18(path));
|
|
14787
|
+
return value === "" || !value.startsWith(`..${sep13}`) && value !== ".." && !value.includes(":");
|
|
13919
14788
|
}
|
|
13920
14789
|
async function canonical2(path) {
|
|
13921
14790
|
try {
|
|
13922
|
-
return await realpath7(
|
|
14791
|
+
return await realpath7(resolve18(path));
|
|
13923
14792
|
} catch {
|
|
13924
|
-
return
|
|
14793
|
+
return resolve18(path);
|
|
13925
14794
|
}
|
|
13926
14795
|
}
|
|
13927
|
-
function
|
|
14796
|
+
function isCode7(error, code) {
|
|
13928
14797
|
return typeof error === "object" && error !== null && "code" in error && error.code === code;
|
|
13929
14798
|
}
|
|
13930
14799
|
function sanitize2(input, seen = /* @__PURE__ */ new WeakSet()) {
|
|
@@ -13946,47 +14815,47 @@ function sanitize2(input, seen = /* @__PURE__ */ new WeakSet()) {
|
|
|
13946
14815
|
}
|
|
13947
14816
|
|
|
13948
14817
|
// src/sleep/organizer.ts
|
|
13949
|
-
import { constants as
|
|
13950
|
-
import { randomUUID as
|
|
13951
|
-
import { lstat as lstat10, mkdir as mkdir15, open as open9, readdir as
|
|
13952
|
-
import { isAbsolute as isAbsolute8, join as
|
|
13953
|
-
import { z as
|
|
13954
|
-
var DateKeySchema =
|
|
13955
|
-
var ReviewSchema =
|
|
13956
|
-
title:
|
|
13957
|
-
taskSummary:
|
|
13958
|
-
executionReflection:
|
|
13959
|
-
decisionsAndLearnings:
|
|
13960
|
-
openQuestionsAndRisks:
|
|
13961
|
-
tomorrowPlan:
|
|
13962
|
-
toolUsage:
|
|
13963
|
-
totalCalls:
|
|
13964
|
-
shell:
|
|
13965
|
-
fileRead:
|
|
13966
|
-
fileWrite:
|
|
13967
|
-
search:
|
|
13968
|
-
lsp:
|
|
13969
|
-
subagent:
|
|
13970
|
-
other:
|
|
14818
|
+
import { constants as constants6 } from "fs";
|
|
14819
|
+
import { randomUUID as randomUUID12 } from "crypto";
|
|
14820
|
+
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";
|
|
14821
|
+
import { isAbsolute as isAbsolute8, join as join20, relative as relative14, resolve as resolve19, sep as sep14 } from "path";
|
|
14822
|
+
import { z as z23 } from "zod";
|
|
14823
|
+
var DateKeySchema = z23.string().regex(/^\d{4}-\d{2}-\d{2}$/);
|
|
14824
|
+
var ReviewSchema = z23.object({
|
|
14825
|
+
title: z23.string().min(1).max(120),
|
|
14826
|
+
taskSummary: z23.array(z23.string().min(1)).min(1).max(20),
|
|
14827
|
+
executionReflection: z23.array(z23.string().min(1)).min(1).max(20),
|
|
14828
|
+
decisionsAndLearnings: z23.array(z23.string().min(1)).max(20),
|
|
14829
|
+
openQuestionsAndRisks: z23.array(z23.string().min(1)).max(20),
|
|
14830
|
+
tomorrowPlan: z23.array(z23.string().min(1)).min(1).max(20),
|
|
14831
|
+
toolUsage: z23.object({
|
|
14832
|
+
totalCalls: z23.number().int().min(0),
|
|
14833
|
+
shell: z23.number().int().min(0),
|
|
14834
|
+
fileRead: z23.number().int().min(0),
|
|
14835
|
+
fileWrite: z23.number().int().min(0),
|
|
14836
|
+
search: z23.number().int().min(0),
|
|
14837
|
+
lsp: z23.number().int().min(0),
|
|
14838
|
+
subagent: z23.number().int().min(0),
|
|
14839
|
+
other: z23.number().int().min(0)
|
|
13971
14840
|
}).strict(),
|
|
13972
|
-
tokenEstimate:
|
|
13973
|
-
estimatedInput:
|
|
13974
|
-
estimatedOutput:
|
|
13975
|
-
notes:
|
|
14841
|
+
tokenEstimate: z23.object({
|
|
14842
|
+
estimatedInput: z23.number().int().min(0),
|
|
14843
|
+
estimatedOutput: z23.number().int().min(0),
|
|
14844
|
+
notes: z23.string().max(200)
|
|
13976
14845
|
}).strict(),
|
|
13977
|
-
humanIntervention:
|
|
13978
|
-
approvalRequests:
|
|
13979
|
-
questionsAsked:
|
|
13980
|
-
summary:
|
|
14846
|
+
humanIntervention: z23.object({
|
|
14847
|
+
approvalRequests: z23.number().int().min(0),
|
|
14848
|
+
questionsAsked: z23.number().int().min(0),
|
|
14849
|
+
summary: z23.string().max(300)
|
|
13981
14850
|
}).strict(),
|
|
13982
|
-
qualityIndicators:
|
|
13983
|
-
hallucinationAlerts:
|
|
13984
|
-
failuresAndRetries:
|
|
13985
|
-
codeChangeSummary:
|
|
13986
|
-
overallAssessment:
|
|
14851
|
+
qualityIndicators: z23.object({
|
|
14852
|
+
hallucinationAlerts: z23.array(z23.string().min(1)).max(10),
|
|
14853
|
+
failuresAndRetries: z23.array(z23.string().min(1)).max(10),
|
|
14854
|
+
codeChangeSummary: z23.string().max(500),
|
|
14855
|
+
overallAssessment: z23.string().max(200)
|
|
13987
14856
|
}).strict(),
|
|
13988
|
-
knowledgeDeposits:
|
|
13989
|
-
worthRemembering:
|
|
14857
|
+
knowledgeDeposits: z23.object({
|
|
14858
|
+
worthRemembering: z23.array(z23.string().min(1)).max(10)
|
|
13990
14859
|
}).strict()
|
|
13991
14860
|
}).strict();
|
|
13992
14861
|
function localDateKey(date) {
|
|
@@ -14003,8 +14872,8 @@ var ProjectSleepOrganizer = class {
|
|
|
14003
14872
|
#sessions;
|
|
14004
14873
|
#generate;
|
|
14005
14874
|
constructor(options) {
|
|
14006
|
-
this.#workspace =
|
|
14007
|
-
this.#sleepDirectory =
|
|
14875
|
+
this.#workspace = resolve19(options.workspace);
|
|
14876
|
+
this.#sleepDirectory = join20(this.#workspace, ".flavor", "sleep");
|
|
14008
14877
|
this.#sessions = options.sessions ?? new SessionStore({ workspace: this.#workspace });
|
|
14009
14878
|
this.#generate = options.generate;
|
|
14010
14879
|
}
|
|
@@ -14013,10 +14882,10 @@ var ProjectSleepOrganizer = class {
|
|
|
14013
14882
|
signal.throwIfAborted();
|
|
14014
14883
|
await this.#prepareDirectory();
|
|
14015
14884
|
if (await this.#hasReport(date)) return { status: "exists", date };
|
|
14016
|
-
const lockPath =
|
|
14885
|
+
const lockPath = join20(this.#sleepDirectory, `.${date}.lock`);
|
|
14017
14886
|
let lock;
|
|
14018
14887
|
try {
|
|
14019
|
-
lock = await open9(lockPath,
|
|
14888
|
+
lock = await open9(lockPath, constants6.O_CREAT | constants6.O_EXCL | constants6.O_WRONLY, 384);
|
|
14020
14889
|
} catch (error) {
|
|
14021
14890
|
if (hasCode(error, "EEXIST")) return { status: "locked", date };
|
|
14022
14891
|
throw error;
|
|
@@ -14048,7 +14917,7 @@ Previous output was invalid. Repair it and return strict JSON only.`;
|
|
|
14048
14917
|
}
|
|
14049
14918
|
if (review === void 0) throw lastError;
|
|
14050
14919
|
const filename2 = `${date}-${filenameSummary(review.title)}.md`;
|
|
14051
|
-
const path =
|
|
14920
|
+
const path = join20(this.#sleepDirectory, filename2);
|
|
14052
14921
|
const markdown = renderSleepReport(date, review, documents);
|
|
14053
14922
|
await this.#atomicWrite(path, markdown);
|
|
14054
14923
|
return { status: "written", date, path, sessionCount: documents.length };
|
|
@@ -14090,24 +14959,24 @@ Previous output was invalid. Repair it and return strict JSON only.`;
|
|
|
14090
14959
|
});
|
|
14091
14960
|
}
|
|
14092
14961
|
async #prepareDirectory() {
|
|
14093
|
-
const flavorDirectory =
|
|
14962
|
+
const flavorDirectory = join20(this.#workspace, ".flavor");
|
|
14094
14963
|
await assertNotSymlink(flavorDirectory);
|
|
14095
14964
|
await mkdir15(flavorDirectory, { recursive: true, mode: 448 });
|
|
14096
14965
|
await assertNotSymlink(this.#sleepDirectory);
|
|
14097
14966
|
await mkdir15(this.#sleepDirectory, { recursive: true, mode: 448 });
|
|
14098
14967
|
const root = await realpath8(this.#workspace);
|
|
14099
14968
|
const target = await realpath8(this.#sleepDirectory);
|
|
14100
|
-
if (!
|
|
14969
|
+
if (!isWithin6(root, target)) throw new Error("Sleep directory escapes the workspace");
|
|
14101
14970
|
}
|
|
14102
14971
|
async #hasReport(date) {
|
|
14103
14972
|
const prefix = `${date}-`;
|
|
14104
|
-
return (await
|
|
14973
|
+
return (await readdir7(this.#sleepDirectory, { withFileTypes: true })).some((entry2) => entry2.isFile() && entry2.name.startsWith(prefix) && entry2.name.endsWith(".md"));
|
|
14105
14974
|
}
|
|
14106
14975
|
async #atomicWrite(path, content) {
|
|
14107
|
-
const temporary =
|
|
14976
|
+
const temporary = join20(this.#sleepDirectory, `.${randomUUID12()}.tmp`);
|
|
14108
14977
|
let handle;
|
|
14109
14978
|
try {
|
|
14110
|
-
handle = await open9(temporary,
|
|
14979
|
+
handle = await open9(temporary, constants6.O_CREAT | constants6.O_EXCL | constants6.O_WRONLY, 384);
|
|
14111
14980
|
await handle.writeFile(content, "utf8");
|
|
14112
14981
|
await handle.sync();
|
|
14113
14982
|
await handle.close();
|
|
@@ -14425,11 +15294,11 @@ async function assertNotSymlink(path) {
|
|
|
14425
15294
|
if (!hasCode(error, "ENOENT")) throw error;
|
|
14426
15295
|
}
|
|
14427
15296
|
}
|
|
14428
|
-
function
|
|
15297
|
+
function isWithin6(root, candidate) {
|
|
14429
15298
|
const path = relative14(root, candidate);
|
|
14430
15299
|
if (path === "") return true;
|
|
14431
|
-
if (path === ".." || path.startsWith(`..${
|
|
14432
|
-
return process.platform !== "win32" ||
|
|
15300
|
+
if (path === ".." || path.startsWith(`..${sep14}`) || isAbsolute8(path)) return false;
|
|
15301
|
+
return process.platform !== "win32" || resolve19(root, path).toLowerCase().startsWith(`${resolve19(root).toLowerCase()}${sep14}`);
|
|
14433
15302
|
}
|
|
14434
15303
|
function hasCode(error, code) {
|
|
14435
15304
|
return typeof error === "object" && error !== null && "code" in error && error.code === code;
|
|
@@ -14477,11 +15346,11 @@ function computeSessionStats(documents) {
|
|
|
14477
15346
|
}
|
|
14478
15347
|
|
|
14479
15348
|
// src/tools/files.ts
|
|
14480
|
-
import { constants as
|
|
14481
|
-
import { mkdir as mkdir16, open as open10, readFile as
|
|
14482
|
-
import { dirname as dirname16, isAbsolute as isAbsolute9, relative as relative15, resolve as
|
|
14483
|
-
import { randomUUID as
|
|
14484
|
-
import { z as
|
|
15349
|
+
import { constants as constants7 } from "fs";
|
|
15350
|
+
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";
|
|
15351
|
+
import { dirname as dirname16, isAbsolute as isAbsolute9, relative as relative15, resolve as resolve20, sep as sep15 } from "path";
|
|
15352
|
+
import { randomUUID as randomUUID13 } from "crypto";
|
|
15353
|
+
import { z as z24 } from "zod";
|
|
14485
15354
|
|
|
14486
15355
|
// src/tools/file-diff.ts
|
|
14487
15356
|
var MAX_DIFF_PREVIEW_LINES = 120;
|
|
@@ -14587,18 +15456,18 @@ function truncateLines(lines) {
|
|
|
14587
15456
|
|
|
14588
15457
|
// src/tools/files.ts
|
|
14589
15458
|
var MAX_READ_BYTES = 1048576;
|
|
14590
|
-
var ReadInput2 =
|
|
14591
|
-
path:
|
|
14592
|
-
maxBytes:
|
|
14593
|
-
startLine:
|
|
14594
|
-
endLine:
|
|
15459
|
+
var ReadInput2 = z24.object({
|
|
15460
|
+
path: z24.string().min(1),
|
|
15461
|
+
maxBytes: z24.coerce.number().int().positive().max(MAX_READ_BYTES).nullable().optional(),
|
|
15462
|
+
startLine: z24.coerce.number().int().positive().nullable().optional(),
|
|
15463
|
+
endLine: z24.coerce.number().int().positive().nullable().optional(),
|
|
14595
15464
|
// Accepts booleans and their string forms (weak-typed models emit "true");
|
|
14596
15465
|
// kept transform-free so the schema converts to JSON Schema for providers.
|
|
14597
|
-
force:
|
|
15466
|
+
force: z24.union([z24.boolean(), z24.string().refine((value) => value === "true" || value === "false")]).nullable().optional()
|
|
14598
15467
|
});
|
|
14599
|
-
var WriteInput =
|
|
14600
|
-
var EditInput =
|
|
14601
|
-
var ApplyPatchInput =
|
|
15468
|
+
var WriteInput = z24.object({ path: z24.string().min(1), content: z24.string() });
|
|
15469
|
+
var EditInput = z24.object({ path: z24.string().min(1), oldText: z24.string().min(1), newText: z24.string() });
|
|
15470
|
+
var ApplyPatchInput = z24.object({ patch: z24.string().min(1) });
|
|
14602
15471
|
var FileObservationStore = class {
|
|
14603
15472
|
#versions = /* @__PURE__ */ new Map();
|
|
14604
15473
|
get(path) {
|
|
@@ -14617,7 +15486,7 @@ var FileObservationStore = class {
|
|
|
14617
15486
|
};
|
|
14618
15487
|
function createReadTool(workspace, options = {}) {
|
|
14619
15488
|
const guard = createPathGuard(workspace);
|
|
14620
|
-
const openFile2 = options.openFile ?? ((path) => open10(path,
|
|
15489
|
+
const openFile2 = options.openFile ?? ((path) => open10(path, constants7.O_RDONLY));
|
|
14621
15490
|
const readHistory = /* @__PURE__ */ new Map();
|
|
14622
15491
|
return {
|
|
14623
15492
|
name: "Read",
|
|
@@ -14796,9 +15665,9 @@ function createApplyPatchTool(workspace, options = {}) {
|
|
|
14796
15665
|
};
|
|
14797
15666
|
}
|
|
14798
15667
|
function createPathGuard(workspace) {
|
|
14799
|
-
const root =
|
|
15668
|
+
const root = resolve20(workspace);
|
|
14800
15669
|
const lexical = (input) => {
|
|
14801
|
-
const candidate =
|
|
15670
|
+
const candidate = resolve20(root, input);
|
|
14802
15671
|
if (!within2(root, candidate)) throw new Error("Path is outside the workspace");
|
|
14803
15672
|
return candidate;
|
|
14804
15673
|
};
|
|
@@ -14833,9 +15702,9 @@ function createPathGuard(workspace) {
|
|
|
14833
15702
|
async function atomicWrite(path, content, signal, expectedVersion) {
|
|
14834
15703
|
abortIfNeeded(signal);
|
|
14835
15704
|
await mkdir16(dirname16(path), { recursive: true });
|
|
14836
|
-
const temporary = `${path}.${
|
|
15705
|
+
const temporary = `${path}.${randomUUID13()}.tmp`;
|
|
14837
15706
|
try {
|
|
14838
|
-
const handle = await open10(temporary,
|
|
15707
|
+
const handle = await open10(temporary, constants7.O_CREAT | constants7.O_EXCL | constants7.O_WRONLY, 384);
|
|
14839
15708
|
try {
|
|
14840
15709
|
await handle.writeFile(content, "utf8");
|
|
14841
15710
|
await handle.sync();
|
|
@@ -14852,7 +15721,7 @@ async function atomicWrite(path, content, signal, expectedVersion) {
|
|
|
14852
15721
|
}
|
|
14853
15722
|
}
|
|
14854
15723
|
async function readText(path) {
|
|
14855
|
-
const contents = await
|
|
15724
|
+
const contents = await readFile17(path);
|
|
14856
15725
|
if (isBinary(contents)) throw new Error("Cannot edit binary file as text");
|
|
14857
15726
|
return contents.toString("utf8");
|
|
14858
15727
|
}
|
|
@@ -14882,7 +15751,7 @@ function versionOfStat(info) {
|
|
|
14882
15751
|
}
|
|
14883
15752
|
async function readOptionalPresentationText(path) {
|
|
14884
15753
|
try {
|
|
14885
|
-
const contents = await
|
|
15754
|
+
const contents = await readFile17(path);
|
|
14886
15755
|
return isBinary(contents) ? { exists: true } : { exists: true, text: contents.toString("utf8") };
|
|
14887
15756
|
} catch (error) {
|
|
14888
15757
|
return isMissing3(error) ? { exists: false } : { exists: true };
|
|
@@ -14968,7 +15837,7 @@ async function readBounded2(path, maxBytes, signal, openFile2) {
|
|
|
14968
15837
|
}
|
|
14969
15838
|
function within2(root, candidate) {
|
|
14970
15839
|
const delta = relative15(root, candidate);
|
|
14971
|
-
return delta === "" || !delta.startsWith(`..${
|
|
15840
|
+
return delta === "" || !delta.startsWith(`..${sep15}`) && delta !== ".." && !isAbsolute9(delta);
|
|
14972
15841
|
}
|
|
14973
15842
|
function forceFlag(value) {
|
|
14974
15843
|
return value === true || value === "true";
|
|
@@ -15194,22 +16063,22 @@ import { lookup } from "dns/promises";
|
|
|
15194
16063
|
import { isIP } from "net";
|
|
15195
16064
|
import { request as httpRequest } from "http";
|
|
15196
16065
|
import { request as httpsRequest } from "https";
|
|
15197
|
-
import { z as
|
|
15198
|
-
var WebFetchInput =
|
|
15199
|
-
url:
|
|
15200
|
-
timeoutMs:
|
|
15201
|
-
maxBytes:
|
|
16066
|
+
import { z as z25 } from "zod";
|
|
16067
|
+
var WebFetchInput = z25.object({
|
|
16068
|
+
url: z25.string().url().max(8192),
|
|
16069
|
+
timeoutMs: z25.coerce.number().int().positive().max(12e4).optional(),
|
|
16070
|
+
maxBytes: z25.coerce.number().int().positive().max(10 * 1024 * 1024).optional()
|
|
15202
16071
|
});
|
|
15203
|
-
var WebSearchInput =
|
|
15204
|
-
query:
|
|
15205
|
-
maxResults:
|
|
16072
|
+
var WebSearchInput = z25.object({
|
|
16073
|
+
query: z25.string().trim().min(1).max(1e3),
|
|
16074
|
+
maxResults: z25.coerce.number().int().min(1).max(20).optional()
|
|
15206
16075
|
});
|
|
15207
16076
|
function createWebFetchTool() {
|
|
15208
16077
|
return {
|
|
15209
16078
|
name: "WebFetch",
|
|
15210
16079
|
description: "Fetch a public HTTP(S) page with SSRF protection and return readable text",
|
|
15211
16080
|
inputSchema: WebFetchInput,
|
|
15212
|
-
outputSchema:
|
|
16081
|
+
outputSchema: z25.object({ url: z25.string(), status: z25.number(), contentType: z25.string(), content: z25.string(), truncated: z25.boolean() }),
|
|
15213
16082
|
paths: () => [],
|
|
15214
16083
|
summarize: (input) => input.url,
|
|
15215
16084
|
presentCall: (input) => ({ kind: "web", title: `Fetching ${new URL(input.url).hostname}`, url: input.url }),
|
|
@@ -15235,7 +16104,7 @@ function createWebSearchTool(provider = createDefaultWebSearchProvider()) {
|
|
|
15235
16104
|
name: "WebSearch",
|
|
15236
16105
|
description: "Search the public web and return titles, URLs, and snippets",
|
|
15237
16106
|
inputSchema: WebSearchInput,
|
|
15238
|
-
outputSchema:
|
|
16107
|
+
outputSchema: z25.object({ query: z25.string(), results: z25.array(z25.object({ title: z25.string(), url: z25.string(), snippet: z25.string() })) }),
|
|
15239
16108
|
paths: () => [],
|
|
15240
16109
|
summarize: (input) => input.query,
|
|
15241
16110
|
presentCall: (input) => ({ kind: "web", title: `Searching: ${input.query}` }),
|
|
@@ -15499,11 +16368,11 @@ function charset(contentType) {
|
|
|
15499
16368
|
|
|
15500
16369
|
// src/tools/lsp.ts
|
|
15501
16370
|
import { spawn as spawn7 } from "child_process";
|
|
15502
|
-
import { existsSync as
|
|
15503
|
-
import { access, constants as
|
|
15504
|
-
import { basename as basename4, dirname as dirname17, extname, isAbsolute as isAbsolute10, relative as relative16, resolve as
|
|
16371
|
+
import { existsSync as existsSync3 } from "fs";
|
|
16372
|
+
import { access, constants as constants8, readFile as readFile18 } from "fs/promises";
|
|
16373
|
+
import { basename as basename4, dirname as dirname17, extname, isAbsolute as isAbsolute10, relative as relative16, resolve as resolve21, sep as sep16 } from "path";
|
|
15505
16374
|
import { fileURLToPath as fileURLToPath3, pathToFileURL as pathToFileURL2 } from "url";
|
|
15506
|
-
import { z as
|
|
16375
|
+
import { z as z26 } from "zod";
|
|
15507
16376
|
var LSP_REQUEST_TIMEOUT_MS = 15e3;
|
|
15508
16377
|
var LspConnection = class {
|
|
15509
16378
|
#process;
|
|
@@ -15600,8 +16469,8 @@ var LspConnection = class {
|
|
|
15600
16469
|
const id = this.#nextId++;
|
|
15601
16470
|
const request = { jsonrpc: "2.0", id, method, params };
|
|
15602
16471
|
const timeout = AbortSignal.timeout(LSP_REQUEST_TIMEOUT_MS);
|
|
15603
|
-
const promise = new Promise((
|
|
15604
|
-
this.#pending.set(id, { resolve:
|
|
16472
|
+
const promise = new Promise((resolve28, reject) => {
|
|
16473
|
+
this.#pending.set(id, { resolve: resolve28, reject });
|
|
15605
16474
|
timeout.addEventListener("abort", () => {
|
|
15606
16475
|
if (this.#pending.has(id)) {
|
|
15607
16476
|
this.#pending.delete(id);
|
|
@@ -15666,7 +16535,7 @@ var LspConnection = class {
|
|
|
15666
16535
|
const url = new URL(uri);
|
|
15667
16536
|
if (url.protocol !== "file:") return;
|
|
15668
16537
|
const filePath = fileURLToPath3(url);
|
|
15669
|
-
const text = await
|
|
16538
|
+
const text = await readFile18(filePath, "utf8");
|
|
15670
16539
|
const current = this.#documents.get(uri);
|
|
15671
16540
|
if (current === void 0) {
|
|
15672
16541
|
this.#documents.set(uri, { text, version: 1 });
|
|
@@ -15717,7 +16586,7 @@ var LspBuffer = class {
|
|
|
15717
16586
|
}
|
|
15718
16587
|
}
|
|
15719
16588
|
};
|
|
15720
|
-
var TYPESCRIPT_CLI =
|
|
16589
|
+
var TYPESCRIPT_CLI = resolve21(
|
|
15721
16590
|
dirname17(fileURLToPath3(import.meta.resolve("typescript/package.json"))),
|
|
15722
16591
|
"lib",
|
|
15723
16592
|
"tsc.js"
|
|
@@ -15759,7 +16628,7 @@ var RealLspManager = class {
|
|
|
15759
16628
|
#serverConfigs;
|
|
15760
16629
|
#onStatus;
|
|
15761
16630
|
constructor(options) {
|
|
15762
|
-
this.#workspace =
|
|
16631
|
+
this.#workspace = resolve21(options.workspace);
|
|
15763
16632
|
this.#serverConfigs = options.serverConfigs ?? KNOWN_SERVERS;
|
|
15764
16633
|
this.#onStatus = options.onStatus;
|
|
15765
16634
|
}
|
|
@@ -15808,7 +16677,7 @@ var RealLspManager = class {
|
|
|
15808
16677
|
if (pending !== void 0) return pending;
|
|
15809
16678
|
const config = this.#serverConfigs.find((c) => c.language === language);
|
|
15810
16679
|
if (config === void 0) return void 0;
|
|
15811
|
-
const hasRoot = config.rootFiles.some((file) =>
|
|
16680
|
+
const hasRoot = config.rootFiles.some((file) => existsSync3(resolve21(this.#workspace, file)));
|
|
15812
16681
|
if (!hasRoot) return void 0;
|
|
15813
16682
|
const startPromise = this.#startServer(config);
|
|
15814
16683
|
this.#pendingStarts.set(language, startPromise);
|
|
@@ -15822,7 +16691,7 @@ var RealLspManager = class {
|
|
|
15822
16691
|
}
|
|
15823
16692
|
async #startServer(config) {
|
|
15824
16693
|
try {
|
|
15825
|
-
await access(config.command,
|
|
16694
|
+
await access(config.command, constants8.X_OK);
|
|
15826
16695
|
} catch {
|
|
15827
16696
|
}
|
|
15828
16697
|
const connection = new LspConnection(config.command, config.args, this.#workspace);
|
|
@@ -15837,25 +16706,25 @@ var RealLspManager = class {
|
|
|
15837
16706
|
return connection;
|
|
15838
16707
|
}
|
|
15839
16708
|
};
|
|
15840
|
-
var LspFindRefsInput =
|
|
15841
|
-
file:
|
|
15842
|
-
line:
|
|
15843
|
-
character:
|
|
16709
|
+
var LspFindRefsInput = z26.object({
|
|
16710
|
+
file: z26.string().min(1),
|
|
16711
|
+
line: z26.coerce.number().int().nonnegative(),
|
|
16712
|
+
character: z26.coerce.number().int().nonnegative()
|
|
15844
16713
|
});
|
|
15845
|
-
var LspHoverInput =
|
|
15846
|
-
file:
|
|
15847
|
-
line:
|
|
15848
|
-
character:
|
|
16714
|
+
var LspHoverInput = z26.object({
|
|
16715
|
+
file: z26.string().min(1),
|
|
16716
|
+
line: z26.coerce.number().int().nonnegative(),
|
|
16717
|
+
character: z26.coerce.number().int().nonnegative()
|
|
15849
16718
|
});
|
|
15850
|
-
var LspDiagnosticsInput =
|
|
15851
|
-
file:
|
|
16719
|
+
var LspDiagnosticsInput = z26.object({
|
|
16720
|
+
file: z26.string().min(1)
|
|
15852
16721
|
});
|
|
15853
16722
|
function createLspTools(workspace, options = {}) {
|
|
15854
|
-
const root =
|
|
16723
|
+
const root = resolve21(workspace);
|
|
15855
16724
|
const manager = options.manager ?? new RealLspManager({ workspace: root, onStatus: options.onStatus });
|
|
15856
16725
|
const guard = (input) => {
|
|
15857
|
-
const candidate =
|
|
15858
|
-
if (!
|
|
16726
|
+
const candidate = resolve21(root, input);
|
|
16727
|
+
if (!isWithin7(root, candidate)) throw new Error("Path is outside the workspace");
|
|
15859
16728
|
return candidate;
|
|
15860
16729
|
};
|
|
15861
16730
|
const toUri = (filePath) => pathToFileURL2(filePath).href;
|
|
@@ -15929,9 +16798,9 @@ function languageIdForPath(filePath) {
|
|
|
15929
16798
|
return "typescript";
|
|
15930
16799
|
}
|
|
15931
16800
|
}
|
|
15932
|
-
function
|
|
16801
|
+
function isWithin7(root, candidate) {
|
|
15933
16802
|
const delta = relative16(root, candidate);
|
|
15934
|
-
return delta === "" || !delta.startsWith(`..${
|
|
16803
|
+
return delta === "" || !delta.startsWith(`..${sep16}`) && delta !== ".." && !isAbsolute10(delta);
|
|
15935
16804
|
}
|
|
15936
16805
|
function uriToPath(uri) {
|
|
15937
16806
|
try {
|
|
@@ -16074,7 +16943,7 @@ async function gitMarker(workspace) {
|
|
|
16074
16943
|
}
|
|
16075
16944
|
|
|
16076
16945
|
// src/git/insights.ts
|
|
16077
|
-
import { z as
|
|
16946
|
+
import { z as z27 } from "zod";
|
|
16078
16947
|
async function suggestCommitMessage(options, input, signal) {
|
|
16079
16948
|
const prompt = [
|
|
16080
16949
|
"Write a git commit message for the staged changes below.",
|
|
@@ -16113,17 +16982,17 @@ function cleanCommitMessage(raw) {
|
|
|
16113
16982
|
}
|
|
16114
16983
|
return kept.join("\n").trim().slice(0, 2e3);
|
|
16115
16984
|
}
|
|
16116
|
-
var ReviewFindingSchema =
|
|
16117
|
-
severity:
|
|
16118
|
-
file:
|
|
16119
|
-
line:
|
|
16120
|
-
issue:
|
|
16121
|
-
suggestion:
|
|
16985
|
+
var ReviewFindingSchema = z27.object({
|
|
16986
|
+
severity: z27.enum(["critical", "warning", "nit"]),
|
|
16987
|
+
file: z27.string(),
|
|
16988
|
+
line: z27.string().describe('Line number or hunk header the finding refers to, e.g. "42" or "@@ -10,4 +10,6 @@"'),
|
|
16989
|
+
issue: z27.string(),
|
|
16990
|
+
suggestion: z27.string()
|
|
16122
16991
|
}).strict();
|
|
16123
|
-
var ReviewReportSchema =
|
|
16124
|
-
summary:
|
|
16125
|
-
verdict:
|
|
16126
|
-
findings:
|
|
16992
|
+
var ReviewReportSchema = z27.object({
|
|
16993
|
+
summary: z27.string().describe("One-paragraph overall assessment of the change"),
|
|
16994
|
+
verdict: z27.enum(["ship", "ship-with-fixes", "needs-work"]),
|
|
16995
|
+
findings: z27.array(ReviewFindingSchema).max(20)
|
|
16127
16996
|
}).strict();
|
|
16128
16997
|
async function reviewDiff(options, input, signal) {
|
|
16129
16998
|
const prompt = [
|
|
@@ -16167,10 +17036,10 @@ function formatReviewReport(report) {
|
|
|
16167
17036
|
}
|
|
16168
17037
|
|
|
16169
17038
|
// src/git/tools.ts
|
|
16170
|
-
import { z as
|
|
16171
|
-
var GitHistoryInput =
|
|
16172
|
-
path:
|
|
16173
|
-
limit:
|
|
17039
|
+
import { z as z28 } from "zod";
|
|
17040
|
+
var GitHistoryInput = z28.object({
|
|
17041
|
+
path: z28.string().describe("File to inspect; omit for the whole repository history").optional(),
|
|
17042
|
+
limit: z28.number().int().min(1).max(50).describe("Maximum number of commits to return (default 20)").optional()
|
|
16174
17043
|
});
|
|
16175
17044
|
function createGitHistoryTool(workspace) {
|
|
16176
17045
|
return {
|
|
@@ -16199,18 +17068,18 @@ function createGitHistoryTool(workspace) {
|
|
|
16199
17068
|
}
|
|
16200
17069
|
|
|
16201
17070
|
// src/tools/ask-user-question.ts
|
|
16202
|
-
import { z as
|
|
16203
|
-
var QuestionOptionSchema =
|
|
16204
|
-
label:
|
|
16205
|
-
description:
|
|
17071
|
+
import { z as z29 } from "zod";
|
|
17072
|
+
var QuestionOptionSchema = z29.object({
|
|
17073
|
+
label: z29.string().trim().min(1),
|
|
17074
|
+
description: z29.string().trim().min(1)
|
|
16206
17075
|
});
|
|
16207
|
-
var QuestionSchema =
|
|
16208
|
-
question:
|
|
16209
|
-
header:
|
|
16210
|
-
options:
|
|
17076
|
+
var QuestionSchema = z29.object({
|
|
17077
|
+
question: z29.string().trim().min(1),
|
|
17078
|
+
header: z29.string().trim().min(1),
|
|
17079
|
+
options: z29.array(QuestionOptionSchema).min(1).max(4)
|
|
16211
17080
|
});
|
|
16212
|
-
var AskUserQuestionInput =
|
|
16213
|
-
questions:
|
|
17081
|
+
var AskUserQuestionInput = z29.object({
|
|
17082
|
+
questions: z29.array(QuestionSchema).min(1).max(4)
|
|
16214
17083
|
});
|
|
16215
17084
|
function hookAnswersFromUpdatedInput(updatedInput, questions) {
|
|
16216
17085
|
if (typeof updatedInput !== "object" || updatedInput === null) return void 0;
|
|
@@ -16239,7 +17108,7 @@ var QuestionBridge = class {
|
|
|
16239
17108
|
ask(questions, signal) {
|
|
16240
17109
|
if (this.#pending !== void 0) return Promise.reject(new Error("A question is already pending"));
|
|
16241
17110
|
if (signal.aborted) return Promise.reject(signal.reason);
|
|
16242
|
-
return new Promise((
|
|
17111
|
+
return new Promise((resolve28, reject) => {
|
|
16243
17112
|
const onAbort = () => {
|
|
16244
17113
|
this.#pending = void 0;
|
|
16245
17114
|
reject(signal.reason instanceof Error ? signal.reason : new Error(String(signal.reason)));
|
|
@@ -16251,7 +17120,7 @@ var QuestionBridge = class {
|
|
|
16251
17120
|
signal.removeEventListener("abort", onAbort);
|
|
16252
17121
|
this.#pending = void 0;
|
|
16253
17122
|
this.#onChange?.();
|
|
16254
|
-
|
|
17123
|
+
resolve28(answers);
|
|
16255
17124
|
},
|
|
16256
17125
|
reject: (error) => {
|
|
16257
17126
|
signal.removeEventListener("abort", onAbort);
|
|
@@ -16294,20 +17163,20 @@ function createAskUserQuestionTool(handler) {
|
|
|
16294
17163
|
}
|
|
16295
17164
|
|
|
16296
17165
|
// src/tools/todo-write.ts
|
|
16297
|
-
import { z as
|
|
16298
|
-
var TodoStatusSchema =
|
|
17166
|
+
import { z as z30 } from "zod";
|
|
17167
|
+
var TodoStatusSchema = z30.enum([
|
|
16299
17168
|
"pending",
|
|
16300
17169
|
"in_progress",
|
|
16301
17170
|
"completed",
|
|
16302
17171
|
"cancelled"
|
|
16303
17172
|
]);
|
|
16304
|
-
var TodoItemSchema =
|
|
16305
|
-
content:
|
|
17173
|
+
var TodoItemSchema = z30.object({
|
|
17174
|
+
content: z30.string().trim().min(1),
|
|
16306
17175
|
status: TodoStatusSchema,
|
|
16307
|
-
activeForm:
|
|
17176
|
+
activeForm: z30.string().trim().min(1)
|
|
16308
17177
|
});
|
|
16309
|
-
var TodoWriteInput =
|
|
16310
|
-
todos:
|
|
17178
|
+
var TodoWriteInput = z30.object({
|
|
17179
|
+
todos: z30.array(TodoItemSchema).min(1).max(50)
|
|
16311
17180
|
});
|
|
16312
17181
|
function createTodoWriteTool() {
|
|
16313
17182
|
return {
|
|
@@ -16341,39 +17210,39 @@ function createTodoWriteTool() {
|
|
|
16341
17210
|
}
|
|
16342
17211
|
|
|
16343
17212
|
// src/tools/managed.ts
|
|
16344
|
-
import { randomUUID as
|
|
16345
|
-
import { link as link2, lstat as lstat11, mkdir as mkdir17, readFile as
|
|
16346
|
-
import { dirname as dirname18, join as
|
|
16347
|
-
import { z as
|
|
17213
|
+
import { randomUUID as randomUUID14 } from "crypto";
|
|
17214
|
+
import { link as link2, lstat as lstat11, mkdir as mkdir17, readFile as readFile19, readdir as readdir8, unlink as unlink4, writeFile as writeFile10 } from "fs/promises";
|
|
17215
|
+
import { dirname as dirname18, join as join21, resolve as resolve22 } from "path";
|
|
17216
|
+
import { z as z31 } from "zod";
|
|
16348
17217
|
var MANAGED_TOOL_VERSION = 1;
|
|
16349
17218
|
var MAX_RECORD_BYTES = 256 * 1024;
|
|
16350
17219
|
var TOOL_NAME = /^[A-Za-z][A-Za-z0-9_]{0,63}$/;
|
|
16351
|
-
var JsonSchemaObject =
|
|
17220
|
+
var JsonSchemaObject = z31.record(z31.string(), z31.unknown()).superRefine((schema, context) => {
|
|
16352
17221
|
if (schema.type !== "object") {
|
|
16353
17222
|
context.addIssue({ code: "custom", message: "Tool input JSON Schema must have type object" });
|
|
16354
17223
|
}
|
|
16355
17224
|
});
|
|
16356
|
-
var RegisterManagedToolInputSchema =
|
|
16357
|
-
name:
|
|
16358
|
-
description:
|
|
17225
|
+
var RegisterManagedToolInputSchema = z31.object({
|
|
17226
|
+
name: z31.string().regex(TOOL_NAME).describe("Unique callable tool name using letters, digits, and underscores"),
|
|
17227
|
+
description: z31.string().min(1).max(4096).describe("Purpose and appropriate use of the generated tool"),
|
|
16359
17228
|
inputSchema: JsonSchemaObject.describe("Provider-facing JSON Schema with an object at the root"),
|
|
16360
|
-
implementation:
|
|
17229
|
+
implementation: z31.string().min(1).max(64 * 1024).describe(
|
|
16361
17230
|
"Async JavaScript function body, or complete function/arrow expression, receiving input, signal, and context; return the tool result"
|
|
16362
17231
|
),
|
|
16363
|
-
scope:
|
|
17232
|
+
scope: z31.enum(["project", "global"]).default("project").describe(
|
|
16364
17233
|
"Project persists in the current workspace; global persists for every workspace"
|
|
16365
17234
|
),
|
|
16366
|
-
agents:
|
|
17235
|
+
agents: z31.array(z31.enum(["main", "subagent"])).min(1).max(2).optional().describe(
|
|
16367
17236
|
"Agent roles allowed to call the generated tool; omit to allow both"
|
|
16368
17237
|
)
|
|
16369
17238
|
}).strict();
|
|
16370
|
-
var RemoveManagedToolInputSchema =
|
|
16371
|
-
name:
|
|
16372
|
-
scope:
|
|
17239
|
+
var RemoveManagedToolInputSchema = z31.object({
|
|
17240
|
+
name: z31.string().regex(TOOL_NAME).describe("Managed tool name to remove"),
|
|
17241
|
+
scope: z31.enum(["project", "global"]).optional().describe("Required only when the same name exists in both scopes")
|
|
16373
17242
|
}).strict();
|
|
16374
17243
|
var ManagedToolRecordSchema = RegisterManagedToolInputSchema.extend({
|
|
16375
|
-
version:
|
|
16376
|
-
createdAt:
|
|
17244
|
+
version: z31.literal(MANAGED_TOOL_VERSION),
|
|
17245
|
+
createdAt: z31.string().datetime()
|
|
16377
17246
|
}).strict();
|
|
16378
17247
|
var AsyncFunction = Object.getPrototypeOf(async function() {
|
|
16379
17248
|
}).constructor;
|
|
@@ -16385,9 +17254,9 @@ var ManagedToolStore = class {
|
|
|
16385
17254
|
#entries = /* @__PURE__ */ new Map();
|
|
16386
17255
|
#diagnostics = [];
|
|
16387
17256
|
constructor(options) {
|
|
16388
|
-
this.#workspace =
|
|
16389
|
-
this.#projectRoot =
|
|
16390
|
-
this.#globalRoot =
|
|
17257
|
+
this.#workspace = resolve22(options.workspace);
|
|
17258
|
+
this.#projectRoot = join21(this.#workspace, ".flavor", "tools");
|
|
17259
|
+
this.#globalRoot = join21(resolve22(options.home), ".flavor-code", "tools");
|
|
16391
17260
|
this.#now = options.now ?? (() => (/* @__PURE__ */ new Date()).toISOString());
|
|
16392
17261
|
}
|
|
16393
17262
|
get diagnostics() {
|
|
@@ -16434,7 +17303,7 @@ var ManagedToolStore = class {
|
|
|
16434
17303
|
version: MANAGED_TOOL_VERSION,
|
|
16435
17304
|
createdAt: this.#now()
|
|
16436
17305
|
});
|
|
16437
|
-
const path =
|
|
17306
|
+
const path = join21(this.directory(input.scope), filename(input.name));
|
|
16438
17307
|
const definition = compileDefinition(record, input.scope, this.#workspace);
|
|
16439
17308
|
await writeExclusive(path, `${JSON.stringify(record, void 0, 2)}
|
|
16440
17309
|
`);
|
|
@@ -16459,25 +17328,25 @@ var ManagedToolStore = class {
|
|
|
16459
17328
|
if (!parsed.success) return [];
|
|
16460
17329
|
const matches = this.#matching(parsed.data.name).filter((entry2) => parsed.data.scope === void 0 || entry2.scope === parsed.data.scope);
|
|
16461
17330
|
if (matches.length > 0) return matches.map((entry2) => entry2.path);
|
|
16462
|
-
return [
|
|
17331
|
+
return [join21(this.directory(parsed.data.scope ?? "project"), filename(parsed.data.name))];
|
|
16463
17332
|
}
|
|
16464
17333
|
async #loadDirectory(scope2) {
|
|
16465
17334
|
const root = this.directory(scope2);
|
|
16466
17335
|
let names;
|
|
16467
17336
|
try {
|
|
16468
|
-
names = (await
|
|
17337
|
+
names = (await readdir8(root, { withFileTypes: true })).filter((entry2) => entry2.name.endsWith(".json")).sort((left, right) => compare2(left.name, right.name)).map((entry2) => entry2.name);
|
|
16469
17338
|
} catch (error) {
|
|
16470
17339
|
if (isMissing4(error)) return;
|
|
16471
17340
|
this.#diagnostics.push(`${root}: ${message(error)}`);
|
|
16472
17341
|
return;
|
|
16473
17342
|
}
|
|
16474
17343
|
for (const name of names) {
|
|
16475
|
-
const path =
|
|
17344
|
+
const path = join21(root, name);
|
|
16476
17345
|
try {
|
|
16477
17346
|
const info = await lstat11(path);
|
|
16478
17347
|
if (!info.isFile() || info.isSymbolicLink()) throw new Error("Managed tool record must be a regular file");
|
|
16479
17348
|
if (info.size > MAX_RECORD_BYTES) throw new Error(`Managed tool record exceeds ${MAX_RECORD_BYTES} bytes`);
|
|
16480
|
-
const record = ManagedToolRecordSchema.parse(JSON.parse(await
|
|
17349
|
+
const record = ManagedToolRecordSchema.parse(JSON.parse(await readFile19(path, "utf8")));
|
|
16481
17350
|
if (record.scope !== scope2) throw new Error(`Managed tool record scope must match its ${scope2} directory`);
|
|
16482
17351
|
const key = scopedKey(scope2, record.name);
|
|
16483
17352
|
if (this.#entries.has(key)) throw new Error(`Duplicate managed tool name in ${scope2} scope: ${record.name}`);
|
|
@@ -16560,7 +17429,7 @@ function createManagedToolManagementTools(options) {
|
|
|
16560
17429
|
const listTools = {
|
|
16561
17430
|
name: "ListRegisteredTools",
|
|
16562
17431
|
description: "List durable tools created through RegisterTool, including scope, path, and active precedence",
|
|
16563
|
-
inputSchema:
|
|
17432
|
+
inputSchema: z31.object({}).strict(),
|
|
16564
17433
|
agents: mainOnly,
|
|
16565
17434
|
paths: () => [],
|
|
16566
17435
|
execute: async (_input, signal) => {
|
|
@@ -16573,7 +17442,7 @@ function createManagedToolManagementTools(options) {
|
|
|
16573
17442
|
function compileDefinition(record, scope2, workspace) {
|
|
16574
17443
|
let inputSchema;
|
|
16575
17444
|
try {
|
|
16576
|
-
inputSchema =
|
|
17445
|
+
inputSchema = z31.fromJSONSchema(record.inputSchema);
|
|
16577
17446
|
} catch (error) {
|
|
16578
17447
|
throw new Error(`Invalid input schema for managed tool "${record.name}": ${message(error)}`);
|
|
16579
17448
|
}
|
|
@@ -16628,7 +17497,7 @@ ${sourceUrl}`
|
|
|
16628
17497
|
}
|
|
16629
17498
|
async function writeExclusive(path, content) {
|
|
16630
17499
|
await mkdir17(dirname18(path), { recursive: true });
|
|
16631
|
-
const temporary = `${path}.${
|
|
17500
|
+
const temporary = `${path}.${randomUUID14()}.tmp`;
|
|
16632
17501
|
try {
|
|
16633
17502
|
await writeFile10(temporary, content, { encoding: "utf8", flag: "wx", mode: 384 });
|
|
16634
17503
|
await link2(temporary, path);
|
|
@@ -16673,7 +17542,7 @@ function isMissing4(error) {
|
|
|
16673
17542
|
}
|
|
16674
17543
|
|
|
16675
17544
|
// src/hallucination/confidence.ts
|
|
16676
|
-
import { z as
|
|
17545
|
+
import { z as z32 } from "zod";
|
|
16677
17546
|
|
|
16678
17547
|
// src/hallucination/types.ts
|
|
16679
17548
|
var DEFAULT_SLIDING_WINDOW_SIZE = 20;
|
|
@@ -16686,12 +17555,12 @@ var DEFAULT_EVALUATION_TIMEOUT_MS = 2e3;
|
|
|
16686
17555
|
var MAX_QUERY_CHARS = 5e3;
|
|
16687
17556
|
var MAX_OUTPUT_CHARS = 1e4;
|
|
16688
17557
|
var MAX_EVIDENCE_CHARS = 6e3;
|
|
16689
|
-
var ConfidenceSchema =
|
|
16690
|
-
taskAlignment:
|
|
16691
|
-
evidenceGrounding:
|
|
16692
|
-
processReliability:
|
|
16693
|
-
reason:
|
|
16694
|
-
unsupportedClaims:
|
|
17558
|
+
var ConfidenceSchema = z32.object({
|
|
17559
|
+
taskAlignment: z32.number(),
|
|
17560
|
+
evidenceGrounding: z32.number(),
|
|
17561
|
+
processReliability: z32.number(),
|
|
17562
|
+
reason: z32.string(),
|
|
17563
|
+
unsupportedClaims: z32.array(z32.string()).max(3)
|
|
16695
17564
|
});
|
|
16696
17565
|
var PROMPT = `You are a fast, evidence-aware task-completion verifier.
|
|
16697
17566
|
|
|
@@ -17018,7 +17887,7 @@ function buildWarningMessages(report, languageTag) {
|
|
|
17018
17887
|
}
|
|
17019
17888
|
|
|
17020
17889
|
// src/hallucination/sliding-window.ts
|
|
17021
|
-
import { createHash as
|
|
17890
|
+
import { createHash as createHash14 } from "crypto";
|
|
17022
17891
|
var SlidingWindow = class {
|
|
17023
17892
|
#windowSize;
|
|
17024
17893
|
#threshold;
|
|
@@ -17034,7 +17903,7 @@ var SlidingWindow = class {
|
|
|
17034
17903
|
}
|
|
17035
17904
|
hash(toolName, params) {
|
|
17036
17905
|
const sorted = JSON.stringify(params, sortedKeys(params));
|
|
17037
|
-
return
|
|
17906
|
+
return createHash14("sha256").update(`${toolName}:${sorted}`).digest("hex");
|
|
17038
17907
|
}
|
|
17039
17908
|
push(toolName, params) {
|
|
17040
17909
|
const hash = this.hash(toolName, params);
|
|
@@ -17389,9 +18258,9 @@ ${total}`;
|
|
|
17389
18258
|
}
|
|
17390
18259
|
|
|
17391
18260
|
// src/memory/store.ts
|
|
17392
|
-
import { createHash as
|
|
17393
|
-
import { readFile as
|
|
17394
|
-
import { resolve as
|
|
18261
|
+
import { createHash as createHash15 } from "crypto";
|
|
18262
|
+
import { readFile as readFile20, rm as rm9 } from "fs/promises";
|
|
18263
|
+
import { resolve as resolve23, join as join22, relative as relative17, sep as sep17 } from "path";
|
|
17395
18264
|
|
|
17396
18265
|
// src/memory/similarity.ts
|
|
17397
18266
|
function normalizeForSimilarity(value) {
|
|
@@ -17493,10 +18362,10 @@ var MemoryStore = class {
|
|
|
17493
18362
|
constructor(options) {
|
|
17494
18363
|
if (!Number.isSafeInteger(options.maxEntries) || options.maxEntries < 1) throw new Error("maxEntries must be an integer of at least 1");
|
|
17495
18364
|
if (!Number.isSafeInteger(options.maxEntryChars) || options.maxEntryChars < 1) throw new Error("maxEntryChars must be an integer of at least 1");
|
|
17496
|
-
this.workspace =
|
|
17497
|
-
this.#memoryRoot =
|
|
17498
|
-
this.path =
|
|
17499
|
-
this.#behaviorPath =
|
|
18365
|
+
this.workspace = resolve23(options.workspace);
|
|
18366
|
+
this.#memoryRoot = join22(this.workspace, ".flavor", "memory");
|
|
18367
|
+
this.path = join22(this.#memoryRoot, "MEMORY.md");
|
|
18368
|
+
this.#behaviorPath = join22(this.#memoryRoot, "behavior.json");
|
|
17500
18369
|
this.#maxEntries = options.maxEntries;
|
|
17501
18370
|
this.#maxEntryChars = options.maxEntryChars;
|
|
17502
18371
|
}
|
|
@@ -17714,14 +18583,14 @@ var MemoryStore = class {
|
|
|
17714
18583
|
});
|
|
17715
18584
|
}
|
|
17716
18585
|
async #readTaskItem(reference) {
|
|
17717
|
-
const document = decodeTask(await
|
|
18586
|
+
const document = decodeTask(await readFile20(this.#resolveContentPath(reference.contentPath), "utf8"));
|
|
17718
18587
|
return document.items.find((item) => item.id === reference.id)?.content ?? reference.summary;
|
|
17719
18588
|
}
|
|
17720
18589
|
#resolveContentPath(contentPath) {
|
|
17721
18590
|
if (!/^tasks\/[A-Za-z0-9][A-Za-z0-9._-]{0,127}\.md$/.test(contentPath)) throw new Error("Invalid memory content path");
|
|
17722
|
-
const path =
|
|
18591
|
+
const path = resolve23(this.#memoryRoot, ...contentPath.split("/"));
|
|
17723
18592
|
const rel = relative17(this.#memoryRoot, path);
|
|
17724
|
-
if (rel === ".." || rel.startsWith(`..${
|
|
18593
|
+
if (rel === ".." || rel.startsWith(`..${sep17}`)) throw new Error("Memory content path escapes the memory directory");
|
|
17725
18594
|
return path;
|
|
17726
18595
|
}
|
|
17727
18596
|
async #updateIndex(update) {
|
|
@@ -17875,7 +18744,7 @@ function sanitizeTopicKey(value) {
|
|
|
17875
18744
|
return value.normalize("NFKC").toLocaleLowerCase().replace(/[^a-z0-9._-]+/g, "-").replace(/^-+|-+$/g, "").slice(0, 128);
|
|
17876
18745
|
}
|
|
17877
18746
|
function memoryId(type, content) {
|
|
17878
|
-
return
|
|
18747
|
+
return createHash15("sha256").update(`${type}\0${normalizeMemoryContent(content).toLocaleLowerCase()}`, "utf8").digest("hex").slice(0, 12);
|
|
17879
18748
|
}
|
|
17880
18749
|
function assertTaskId(taskId) {
|
|
17881
18750
|
if (!TASK_ID.test(taskId)) throw new Error(`Invalid memory task id: ${taskId}`);
|
|
@@ -18190,8 +19059,8 @@ function createExecutionEnvironment(workspace, config) {
|
|
|
18190
19059
|
}
|
|
18191
19060
|
|
|
18192
19061
|
// src/ide/client.ts
|
|
18193
|
-
import { readFile as
|
|
18194
|
-
import { basename as basename5, join as
|
|
19062
|
+
import { readFile as readFile21, readdir as readdir9, stat as stat8 } from "fs/promises";
|
|
19063
|
+
import { basename as basename5, join as join23, relative as relative18, resolve as resolve24, sep as sep18 } from "path";
|
|
18195
19064
|
var FlavorIdeClient = class {
|
|
18196
19065
|
#workspace;
|
|
18197
19066
|
#home;
|
|
@@ -18202,8 +19071,8 @@ var FlavorIdeClient = class {
|
|
|
18202
19071
|
#sessionId;
|
|
18203
19072
|
#eventTail = Promise.resolve();
|
|
18204
19073
|
constructor(options) {
|
|
18205
|
-
this.#workspace =
|
|
18206
|
-
this.#home =
|
|
19074
|
+
this.#workspace = resolve24(options.workspace);
|
|
19075
|
+
this.#home = resolve24(options.home);
|
|
18207
19076
|
this.#environment = options.environment ?? process.env;
|
|
18208
19077
|
this.#fetch = options.fetch ?? fetch;
|
|
18209
19078
|
}
|
|
@@ -18230,7 +19099,7 @@ var FlavorIdeClient = class {
|
|
|
18230
19099
|
}
|
|
18231
19100
|
async promptContext() {
|
|
18232
19101
|
const context = await this.editorContext();
|
|
18233
|
-
if (context?.filePath === void 0 || !
|
|
19102
|
+
if (context?.filePath === void 0 || !isWithin8(this.#workspace, context.filePath)) return void 0;
|
|
18234
19103
|
const path = relativeDisplay(this.#workspace, context.filePath);
|
|
18235
19104
|
const cursor = context.selection.active;
|
|
18236
19105
|
if (context.selection.isEmpty || !context.selectedText) {
|
|
@@ -18287,23 +19156,23 @@ var FlavorIdeClient = class {
|
|
|
18287
19156
|
};
|
|
18288
19157
|
if (await this.#healthy(configured)) return configured;
|
|
18289
19158
|
}
|
|
18290
|
-
const directory =
|
|
19159
|
+
const directory = join23(this.#home, ".flavor-code", "ide");
|
|
18291
19160
|
let entries;
|
|
18292
19161
|
try {
|
|
18293
|
-
entries = (await
|
|
19162
|
+
entries = (await readdir9(directory)).filter((name) => name.endsWith(".lock"));
|
|
18294
19163
|
} catch {
|
|
18295
19164
|
return void 0;
|
|
18296
19165
|
}
|
|
18297
19166
|
const candidates = (await Promise.all(entries.map(async (name) => {
|
|
18298
|
-
const path =
|
|
19167
|
+
const path = join23(directory, name);
|
|
18299
19168
|
try {
|
|
18300
|
-
const [contents, info] = await Promise.all([
|
|
19169
|
+
const [contents, info] = await Promise.all([readFile21(path, "utf8"), stat8(path)]);
|
|
18301
19170
|
const lock = parseLockfile(contents);
|
|
18302
19171
|
return lock === void 0 ? void 0 : { lock, modified: info.mtimeMs };
|
|
18303
19172
|
} catch {
|
|
18304
19173
|
return void 0;
|
|
18305
19174
|
}
|
|
18306
|
-
}))).filter((item) => item !== void 0).filter(({ lock }) => lock.workspaceFolders.some((folder) =>
|
|
19175
|
+
}))).filter((item) => item !== void 0).filter(({ lock }) => lock.workspaceFolders.some((folder) => isWithin8(folder, this.#workspace))).sort((left, right) => right.modified - left.modified);
|
|
18307
19176
|
for (const { lock } of candidates) {
|
|
18308
19177
|
if (await this.#healthy(lock)) return lock;
|
|
18309
19178
|
}
|
|
@@ -18400,10 +19269,10 @@ function parsePort(value) {
|
|
|
18400
19269
|
const port = Number(value);
|
|
18401
19270
|
return Number.isInteger(port) && port > 0 && port <= 65535 ? port : void 0;
|
|
18402
19271
|
}
|
|
18403
|
-
function
|
|
18404
|
-
const base = normalize(
|
|
18405
|
-
const target = normalize(
|
|
18406
|
-
return target === base || target.startsWith(`${base}${
|
|
19272
|
+
function isWithin8(parent, child) {
|
|
19273
|
+
const base = normalize(resolve24(parent));
|
|
19274
|
+
const target = normalize(resolve24(child));
|
|
19275
|
+
return target === base || target.startsWith(`${base}${sep18}`);
|
|
18407
19276
|
}
|
|
18408
19277
|
function normalize(path) {
|
|
18409
19278
|
const normalized = path.normalize("NFC");
|
|
@@ -18415,7 +19284,7 @@ function relativeDisplay(workspace, filePath) {
|
|
|
18415
19284
|
}
|
|
18416
19285
|
|
|
18417
19286
|
// src/jobs/registry.ts
|
|
18418
|
-
import { randomUUID as
|
|
19287
|
+
import { randomUUID as randomUUID15 } from "crypto";
|
|
18419
19288
|
var JobRegistry = class {
|
|
18420
19289
|
#jobs = /* @__PURE__ */ new Map();
|
|
18421
19290
|
#maxOutputChars;
|
|
@@ -18429,11 +19298,11 @@ var JobRegistry = class {
|
|
|
18429
19298
|
}
|
|
18430
19299
|
create(input) {
|
|
18431
19300
|
this.#pruneCompleted();
|
|
18432
|
-
const id = `job-${
|
|
19301
|
+
const id = `job-${randomUUID15().slice(0, 12)}`;
|
|
18433
19302
|
const now = (/* @__PURE__ */ new Date()).toISOString();
|
|
18434
19303
|
let settle;
|
|
18435
|
-
const done = new Promise((
|
|
18436
|
-
settle =
|
|
19304
|
+
const done = new Promise((resolve28) => {
|
|
19305
|
+
settle = resolve28;
|
|
18437
19306
|
});
|
|
18438
19307
|
const record = {
|
|
18439
19308
|
id,
|
|
@@ -18555,9 +19424,9 @@ function snapshot2(job) {
|
|
|
18555
19424
|
}
|
|
18556
19425
|
|
|
18557
19426
|
// src/terminal/service.ts
|
|
18558
|
-
import { randomUUID as
|
|
19427
|
+
import { randomUUID as randomUUID16 } from "crypto";
|
|
18559
19428
|
import { createRequire as nodeCreateRequire } from "module";
|
|
18560
|
-
import { isAbsolute as isAbsolute11, relative as relative19, resolve as
|
|
19429
|
+
import { isAbsolute as isAbsolute11, relative as relative19, resolve as resolve25, sep as sep19 } from "path";
|
|
18561
19430
|
var TerminalService = class {
|
|
18562
19431
|
#root;
|
|
18563
19432
|
#factory;
|
|
@@ -18565,7 +19434,7 @@ var TerminalService = class {
|
|
|
18565
19434
|
#maxOutputChars;
|
|
18566
19435
|
#sessions = /* @__PURE__ */ new Map();
|
|
18567
19436
|
constructor(workspace, options = {}) {
|
|
18568
|
-
this.#root =
|
|
19437
|
+
this.#root = resolve25(workspace);
|
|
18569
19438
|
this.#factory = options.factory ?? defaultFactory;
|
|
18570
19439
|
this.#jobs = options.jobs;
|
|
18571
19440
|
this.#maxOutputChars = options.maxOutputChars ?? 2e5;
|
|
@@ -18573,7 +19442,7 @@ var TerminalService = class {
|
|
|
18573
19442
|
open(input) {
|
|
18574
19443
|
const cwd = workspacePath(this.#root, input.cwd ?? ".");
|
|
18575
19444
|
const shell = input.shell ?? defaultShell();
|
|
18576
|
-
const id = `term-${
|
|
19445
|
+
const id = `term-${randomUUID16().slice(0, 12)}`;
|
|
18577
19446
|
const pty = this.#factory(shell, input.args ?? [], {
|
|
18578
19447
|
cwd,
|
|
18579
19448
|
cols: input.columns ?? 100,
|
|
@@ -18683,30 +19552,30 @@ function cleanEnvironment(environment) {
|
|
|
18683
19552
|
return Object.fromEntries(Object.entries(environment).filter((entry2) => entry2[1] !== void 0));
|
|
18684
19553
|
}
|
|
18685
19554
|
function workspacePath(root, input) {
|
|
18686
|
-
const path =
|
|
19555
|
+
const path = resolve25(root, input);
|
|
18687
19556
|
const delta = relative19(root, path);
|
|
18688
|
-
if (delta === ".." || delta.startsWith(`..${
|
|
19557
|
+
if (delta === ".." || delta.startsWith(`..${sep19}`) || isAbsolute11(delta)) throw new Error("Terminal cwd is outside the workspace");
|
|
18689
19558
|
return path;
|
|
18690
19559
|
}
|
|
18691
19560
|
|
|
18692
19561
|
// src/tools/terminal.ts
|
|
18693
|
-
import { z as
|
|
18694
|
-
import { resolve as
|
|
18695
|
-
var Id =
|
|
18696
|
-
var OpenInput =
|
|
18697
|
-
var OptionalBoolean =
|
|
18698
|
-
var WriteInput2 =
|
|
18699
|
-
var ReadInput3 =
|
|
18700
|
-
var ResizeInput =
|
|
18701
|
-
var CloseInput =
|
|
19562
|
+
import { z as z33 } from "zod";
|
|
19563
|
+
import { resolve as resolve26 } from "path";
|
|
19564
|
+
var Id = z33.string().regex(/^term-[a-f0-9-]+$/);
|
|
19565
|
+
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() });
|
|
19566
|
+
var OptionalBoolean = z33.union([z33.boolean(), z33.string().refine((value) => value === "true" || value === "false")]);
|
|
19567
|
+
var WriteInput2 = z33.object({ id: Id, data: z33.string().max(1e5), enter: OptionalBoolean.optional() });
|
|
19568
|
+
var ReadInput3 = z33.object({ id: Id, cursor: z33.coerce.number().int().min(0).optional() });
|
|
19569
|
+
var ResizeInput = z33.object({ id: Id, columns: z33.coerce.number().int().min(10).max(500), rows: z33.coerce.number().int().min(2).max(300) });
|
|
19570
|
+
var CloseInput = z33.object({ id: Id });
|
|
18702
19571
|
function createTerminalTools(terminals, workspace = process.cwd()) {
|
|
18703
19572
|
const tools = [
|
|
18704
19573
|
{
|
|
18705
19574
|
name: "TerminalOpen",
|
|
18706
19575
|
description: "Open a persistent interactive pseudo-terminal in the workspace",
|
|
18707
19576
|
inputSchema: OpenInput,
|
|
18708
|
-
paths: (input) => [
|
|
18709
|
-
permissions: (input) => ({ paths: [
|
|
19577
|
+
paths: (input) => [resolve26(workspace, input.cwd ?? ".")],
|
|
19578
|
+
permissions: (input) => ({ paths: [resolve26(workspace, input.cwd ?? ".")], command: input.shell ?? "system-shell", args: input.args ?? [], cwd: resolve26(workspace, input.cwd ?? ".") }),
|
|
18710
19579
|
execute: async (input, _signal, context) => terminals.open({
|
|
18711
19580
|
owner: owner(context),
|
|
18712
19581
|
...input.cwd === void 0 ? {} : { cwd: input.cwd },
|
|
@@ -18774,7 +19643,7 @@ function createTerminalTools(terminals, workspace = process.cwd()) {
|
|
|
18774
19643
|
{
|
|
18775
19644
|
name: "TerminalList",
|
|
18776
19645
|
description: "List persistent terminals owned by this agent",
|
|
18777
|
-
inputSchema:
|
|
19646
|
+
inputSchema: z33.object({}),
|
|
18778
19647
|
paths: () => [],
|
|
18779
19648
|
execute: async (_input, _signal, context) => terminals.list(owner(context))
|
|
18780
19649
|
}
|
|
@@ -18783,10 +19652,10 @@ function createTerminalTools(terminals, workspace = process.cwd()) {
|
|
|
18783
19652
|
}
|
|
18784
19653
|
|
|
18785
19654
|
// src/pals/address.ts
|
|
18786
|
-
import { createHash as
|
|
19655
|
+
import { createHash as createHash16 } from "crypto";
|
|
18787
19656
|
function opaqueUserScope(userScope) {
|
|
18788
19657
|
if (userScope.length === 0) throw new Error("User scope must not be empty");
|
|
18789
|
-
const digest =
|
|
19658
|
+
const digest = createHash16("sha256").update(userScope, "utf8").digest("hex").slice(0, 16);
|
|
18790
19659
|
return `u-${digest}`;
|
|
18791
19660
|
}
|
|
18792
19661
|
function palSocketAddress(options) {
|
|
@@ -18799,7 +19668,7 @@ function palSocketAddress(options) {
|
|
|
18799
19668
|
}
|
|
18800
19669
|
|
|
18801
19670
|
// src/pals/client.ts
|
|
18802
|
-
import { randomUUID as
|
|
19671
|
+
import { randomUUID as randomUUID17 } from "crypto";
|
|
18803
19672
|
import { Socket as Socket3 } from "net";
|
|
18804
19673
|
var DEFAULT_REQUEST_TIMEOUT_MS = 5e3;
|
|
18805
19674
|
var DEFAULT_HEARTBEAT_INTERVAL_MS = 1e4;
|
|
@@ -18875,7 +19744,7 @@ var PalClient = class {
|
|
|
18875
19744
|
return this.starting;
|
|
18876
19745
|
}
|
|
18877
19746
|
async list() {
|
|
18878
|
-
const data2 = await this.request({ version: 1, type: "list", requestId:
|
|
19747
|
+
const data2 = await this.request({ version: 1, type: "list", requestId: randomUUID17() });
|
|
18879
19748
|
return PalPresenceListSchema.parse(data2);
|
|
18880
19749
|
}
|
|
18881
19750
|
async rename(alias) {
|
|
@@ -18888,38 +19757,38 @@ var PalClient = class {
|
|
|
18888
19757
|
throw error;
|
|
18889
19758
|
}
|
|
18890
19759
|
}
|
|
18891
|
-
async sendTask(target, message2, messageId =
|
|
18892
|
-
const data2 = await this.request({ version: 1, type: "task", requestId:
|
|
19760
|
+
async sendTask(target, message2, messageId = randomUUID17()) {
|
|
19761
|
+
const data2 = await this.request({ version: 1, type: "task", requestId: randomUUID17(), messageId, target, goal: message2 });
|
|
18893
19762
|
return DeliveryReceiptSchema.parse(data2);
|
|
18894
19763
|
}
|
|
18895
|
-
async sendChat(target, message2, messageId =
|
|
18896
|
-
const data2 = await this.request({ version: 1, type: "chat", requestId:
|
|
19764
|
+
async sendChat(target, message2, messageId = randomUUID17()) {
|
|
19765
|
+
const data2 = await this.request({ version: 1, type: "chat", requestId: randomUUID17(), messageId, target, message: message2 });
|
|
18897
19766
|
return DeliveryReceiptSchema.parse(data2);
|
|
18898
19767
|
}
|
|
18899
19768
|
async startCoWork(input) {
|
|
18900
19769
|
const data2 = await this.request({
|
|
18901
19770
|
version: 1,
|
|
18902
19771
|
type: "cowork-propose",
|
|
18903
|
-
requestId:
|
|
18904
|
-
coWorkId: input.coWorkId ??
|
|
19772
|
+
requestId: randomUUID17(),
|
|
19773
|
+
coWorkId: input.coWorkId ?? randomUUID17(),
|
|
18905
19774
|
goal: input.goal,
|
|
18906
19775
|
participants: input.participants
|
|
18907
19776
|
});
|
|
18908
19777
|
return CoWorkSnapshotSchema.parse(data2);
|
|
18909
19778
|
}
|
|
18910
19779
|
async coWorkAction(action) {
|
|
18911
|
-
const request = { version: 1, requestId:
|
|
19780
|
+
const request = { version: 1, requestId: randomUUID17(), ...action };
|
|
18912
19781
|
return CoWorkSnapshotSchema.parse(await this.request(request));
|
|
18913
19782
|
}
|
|
18914
19783
|
async coWorkStatus(coWorkId) {
|
|
18915
|
-
const data2 = await this.request({ version: 1, type: "cowork-get", requestId:
|
|
19784
|
+
const data2 = await this.request({ version: 1, type: "cowork-get", requestId: randomUUID17(), coWorkId });
|
|
18916
19785
|
return CoWorkSnapshotSchema.parse(data2);
|
|
18917
19786
|
}
|
|
18918
19787
|
async integrateCoWork(input) {
|
|
18919
19788
|
return this.coWorkAction({ type: "cowork-integration", ...input });
|
|
18920
19789
|
}
|
|
18921
19790
|
async cancelCoWork(coWorkId, reason) {
|
|
18922
|
-
const data2 = await this.request({ version: 1, type: "cowork-cancel", requestId:
|
|
19791
|
+
const data2 = await this.request({ version: 1, type: "cowork-cancel", requestId: randomUUID17(), coWorkId, reason });
|
|
18923
19792
|
return CoWorkSnapshotSchema.parse(data2);
|
|
18924
19793
|
}
|
|
18925
19794
|
subscribe(listener) {
|
|
@@ -18931,9 +19800,9 @@ var PalClient = class {
|
|
|
18931
19800
|
if (this.registered && this.socket !== void 0) {
|
|
18932
19801
|
let disconnectTimer;
|
|
18933
19802
|
await Promise.race([
|
|
18934
|
-
this.request({ version: 1, type: "disconnect", requestId:
|
|
18935
|
-
new Promise((
|
|
18936
|
-
disconnectTimer = setTimeout(
|
|
19803
|
+
this.request({ version: 1, type: "disconnect", requestId: randomUUID17() }).catch(() => void 0),
|
|
19804
|
+
new Promise((resolve28) => {
|
|
19805
|
+
disconnectTimer = setTimeout(resolve28, Math.min(this.requestTimeoutMs, 250));
|
|
18937
19806
|
})
|
|
18938
19807
|
]);
|
|
18939
19808
|
if (disconnectTimer !== void 0) clearTimeout(disconnectTimer);
|
|
@@ -18979,7 +19848,7 @@ var PalClient = class {
|
|
|
18979
19848
|
return presence;
|
|
18980
19849
|
}
|
|
18981
19850
|
openSocket() {
|
|
18982
|
-
return new Promise((
|
|
19851
|
+
return new Promise((resolve28, reject) => {
|
|
18983
19852
|
let settled = false;
|
|
18984
19853
|
const rejectConnection = (error) => {
|
|
18985
19854
|
if (settled) return;
|
|
@@ -19002,7 +19871,7 @@ var PalClient = class {
|
|
|
19002
19871
|
settled = true;
|
|
19003
19872
|
if (this.connectingReject === rejectConnection) this.connectingReject = void 0;
|
|
19004
19873
|
socket.off("error", onInitialError);
|
|
19005
|
-
|
|
19874
|
+
resolve28();
|
|
19006
19875
|
};
|
|
19007
19876
|
const onInitialError = (error) => {
|
|
19008
19877
|
socket.off("connect", onConnect);
|
|
@@ -19022,7 +19891,7 @@ var PalClient = class {
|
|
|
19022
19891
|
const data2 = await this.request({
|
|
19023
19892
|
version: 1,
|
|
19024
19893
|
type: "register",
|
|
19025
|
-
requestId:
|
|
19894
|
+
requestId: randomUUID17(),
|
|
19026
19895
|
authToken: this.authToken,
|
|
19027
19896
|
presence: { version: 1, ...this.registration, connectedAt: now, lastSeenAt: now }
|
|
19028
19897
|
});
|
|
@@ -19033,12 +19902,12 @@ var PalClient = class {
|
|
|
19033
19902
|
if (this.closed || socket === void 0 || socket.destroyed || !socket.writable) {
|
|
19034
19903
|
return Promise.reject(new Error(this.closed ? "Pal client is closed" : "Pal client is not connected"));
|
|
19035
19904
|
}
|
|
19036
|
-
return new Promise((
|
|
19905
|
+
return new Promise((resolve28, reject) => {
|
|
19037
19906
|
const timer = setTimeout(() => {
|
|
19038
19907
|
this.pending.delete(request.requestId);
|
|
19039
19908
|
reject(new Error(`Pal request '${request.type}' timed out`));
|
|
19040
19909
|
}, this.requestTimeoutMs);
|
|
19041
|
-
this.pending.set(request.requestId, { resolve:
|
|
19910
|
+
this.pending.set(request.requestId, { resolve: resolve28, reject, timer });
|
|
19042
19911
|
let frame;
|
|
19043
19912
|
try {
|
|
19044
19913
|
frame = encodeControlFrame(BrokerRequestSchema.parse(request));
|
|
@@ -19134,7 +20003,7 @@ var PalClient = class {
|
|
|
19134
20003
|
if (!this.registered) return;
|
|
19135
20004
|
if (this.heartbeatPending) return;
|
|
19136
20005
|
this.heartbeatPending = true;
|
|
19137
|
-
void this.request({ version: 1, type: "heartbeat", requestId:
|
|
20006
|
+
void this.request({ version: 1, type: "heartbeat", requestId: randomUUID17() }).catch(() => void 0).finally(() => {
|
|
19138
20007
|
this.heartbeatPending = false;
|
|
19139
20008
|
});
|
|
19140
20009
|
}, this.heartbeatIntervalMs);
|
|
@@ -19153,7 +20022,7 @@ var PalClient = class {
|
|
|
19153
20022
|
};
|
|
19154
20023
|
|
|
19155
20024
|
// src/pals/tools.ts
|
|
19156
|
-
import { z as
|
|
20025
|
+
import { z as z34 } from "zod";
|
|
19157
20026
|
var DEFAULT_SHARE_BUDGET_BYTES = 128 * 1024;
|
|
19158
20027
|
var CollaborationShareGuard = class {
|
|
19159
20028
|
redact;
|
|
@@ -19174,25 +20043,25 @@ var CollaborationShareGuard = class {
|
|
|
19174
20043
|
return redacted;
|
|
19175
20044
|
}
|
|
19176
20045
|
};
|
|
19177
|
-
var Uuid =
|
|
19178
|
-
var Hash =
|
|
20046
|
+
var Uuid = z34.uuid();
|
|
20047
|
+
var Hash = z34.string().regex(/^[a-f0-9]{64}$/);
|
|
19179
20048
|
var Target = PalTargetSchema;
|
|
19180
|
-
var BoundedText =
|
|
20049
|
+
var BoundedText = z34.string().trim().min(1).refine(
|
|
19181
20050
|
(value) => Buffer.byteLength(value, "utf8") <= MAX_MESSAGE_BYTES,
|
|
19182
20051
|
`Text must not exceed ${MAX_MESSAGE_BYTES} UTF-8 bytes`
|
|
19183
20052
|
);
|
|
19184
|
-
var CompletionEvidence =
|
|
20053
|
+
var CompletionEvidence = z34.string().trim().min(1).refine(
|
|
19185
20054
|
(value) => Buffer.byteLength(value, "utf8") <= MAX_COMPLETION_DETAIL_BYTES,
|
|
19186
20055
|
`Completion evidence must not exceed ${MAX_COMPLETION_DETAIL_BYTES} UTF-8 bytes`
|
|
19187
20056
|
);
|
|
19188
|
-
var EmptyInput =
|
|
19189
|
-
var StateInput =
|
|
19190
|
-
var TokenInput =
|
|
19191
|
-
var SendInput =
|
|
19192
|
-
var PlanInput =
|
|
19193
|
-
var ProgressInput =
|
|
19194
|
-
var CompleteInput = TokenInput.extend({ passed:
|
|
19195
|
-
var IntegrateInput = TokenInput.extend({ passed:
|
|
20057
|
+
var EmptyInput = z34.object({}).strict();
|
|
20058
|
+
var StateInput = z34.object({ coWorkId: Uuid }).strict();
|
|
20059
|
+
var TokenInput = z34.object({ coWorkId: Uuid, epoch: z34.number().int().positive(), planHash: Hash }).strict();
|
|
20060
|
+
var SendInput = z34.object({ target: Target, message: BoundedText, coWorkId: Uuid.optional() }).strict();
|
|
20061
|
+
var PlanInput = z34.object({ plan: CoWorkPlanSchema }).strict();
|
|
20062
|
+
var ProgressInput = z34.object({ coWorkId: Uuid, target: Target, detail: BoundedText }).strict();
|
|
20063
|
+
var CompleteInput = TokenInput.extend({ passed: z34.boolean(), detail: CompletionEvidence }).strict();
|
|
20064
|
+
var IntegrateInput = TokenInput.extend({ passed: z34.boolean(), evidence: CompletionEvidence }).strict();
|
|
19196
20065
|
var sharedBytes = (...values) => values.reduce((total, value) => total + Buffer.byteLength(value, "utf8"), 0);
|
|
19197
20066
|
function resolvePresence(presences, target) {
|
|
19198
20067
|
const normalized = normalizePalIdentity(target);
|
|
@@ -19351,6 +20220,51 @@ var INTERRUPTED_TASK_PLAN_CONTEXT = [
|
|
|
19351
20220
|
"Reassess the current query independently.",
|
|
19352
20221
|
"If the current work needs a plan, create a fresh one with TaskPlan before using TaskUpdate."
|
|
19353
20222
|
].join(" ");
|
|
20223
|
+
function recoverablePalClient(client, onRecovered) {
|
|
20224
|
+
let presence;
|
|
20225
|
+
let starting;
|
|
20226
|
+
const ensure = () => {
|
|
20227
|
+
if (presence !== void 0) return Promise.resolve(presence);
|
|
20228
|
+
if (starting !== void 0) return starting;
|
|
20229
|
+
starting = client.start().then((value) => {
|
|
20230
|
+
presence = value;
|
|
20231
|
+
onRecovered();
|
|
20232
|
+
return value;
|
|
20233
|
+
}).finally(() => {
|
|
20234
|
+
starting = void 0;
|
|
20235
|
+
});
|
|
20236
|
+
return starting;
|
|
20237
|
+
};
|
|
20238
|
+
const invoke = async (run) => {
|
|
20239
|
+
await ensure();
|
|
20240
|
+
try {
|
|
20241
|
+
return await run();
|
|
20242
|
+
} catch (error) {
|
|
20243
|
+
const detail = message(error);
|
|
20244
|
+
if (!/not connected|connection closed|ECONNRESET|ECONNREFUSED|EPIPE/i.test(detail)) throw error;
|
|
20245
|
+
presence = void 0;
|
|
20246
|
+
await ensure();
|
|
20247
|
+
return run();
|
|
20248
|
+
}
|
|
20249
|
+
};
|
|
20250
|
+
return {
|
|
20251
|
+
start: ensure,
|
|
20252
|
+
list: () => invoke(() => client.list()),
|
|
20253
|
+
rename: (alias) => invoke(() => client.rename(alias)),
|
|
20254
|
+
sendTask: (target, value) => invoke(() => client.sendTask(target, value)),
|
|
20255
|
+
sendChat: (target, value) => invoke(() => client.sendChat(target, value)),
|
|
20256
|
+
startCoWork: (input) => invoke(() => client.startCoWork(input)),
|
|
20257
|
+
coWorkAction: (action) => invoke(() => client.coWorkAction(action)),
|
|
20258
|
+
coWorkStatus: (coWorkId) => invoke(() => client.coWorkStatus(coWorkId)),
|
|
20259
|
+
integrateCoWork: (input) => invoke(() => client.integrateCoWork(input)),
|
|
20260
|
+
cancelCoWork: (coWorkId, reason) => invoke(() => client.cancelCoWork(coWorkId, reason)),
|
|
20261
|
+
subscribe: (listener) => client.subscribe(listener),
|
|
20262
|
+
close: async () => {
|
|
20263
|
+
presence = void 0;
|
|
20264
|
+
await client.close();
|
|
20265
|
+
}
|
|
20266
|
+
};
|
|
20267
|
+
}
|
|
19354
20268
|
var DEFAULT_SHUTDOWN_STEP_TIMEOUT_MS = 3e3;
|
|
19355
20269
|
function createPromptEnvironment(input = {}) {
|
|
19356
20270
|
const now = input.now ?? /* @__PURE__ */ new Date();
|
|
@@ -19376,7 +20290,7 @@ var ApprovalBridge = class {
|
|
|
19376
20290
|
request(request, signal = new AbortController().signal) {
|
|
19377
20291
|
if (this.#settle !== void 0) return Promise.resolve("deny");
|
|
19378
20292
|
if (signal.aborted) return Promise.resolve("deny");
|
|
19379
|
-
this.#pending = { id:
|
|
20293
|
+
this.#pending = { id: randomUUID18(), ...request };
|
|
19380
20294
|
this.#onChange?.();
|
|
19381
20295
|
return new Promise((resolvePromise) => {
|
|
19382
20296
|
this.#settle = resolvePromise;
|
|
@@ -19397,8 +20311,8 @@ var ApprovalBridge = class {
|
|
|
19397
20311
|
}
|
|
19398
20312
|
};
|
|
19399
20313
|
async function createProductionRuntime(options) {
|
|
19400
|
-
const workspace =
|
|
19401
|
-
const home =
|
|
20314
|
+
const workspace = resolve27(options.workspace ?? process.cwd());
|
|
20315
|
+
const home = resolve27(options.home ?? homedir2());
|
|
19402
20316
|
const environment = options.environment ?? process.env;
|
|
19403
20317
|
const ide = new FlavorIdeClient({ workspace, home, environment });
|
|
19404
20318
|
const ideReady = ide.initialize();
|
|
@@ -19507,6 +20421,12 @@ async function createProductionRuntime(options) {
|
|
|
19507
20421
|
);
|
|
19508
20422
|
const registry = new ModelRegistry();
|
|
19509
20423
|
const diagnostics = [];
|
|
20424
|
+
const permissionPolicy = await loadPermissionPolicy({
|
|
20425
|
+
workspace,
|
|
20426
|
+
home,
|
|
20427
|
+
...environment.FLAVOR_MANAGED_PERMISSIONS === void 0 ? {} : { managedPath: environment.FLAVOR_MANAGED_PERMISSIONS }
|
|
20428
|
+
});
|
|
20429
|
+
diagnostics.push(...permissionPolicy.diagnostics);
|
|
19510
20430
|
const approvals = new ApprovalBridge(options.onApprovalChange);
|
|
19511
20431
|
const resolveToolApproval = options.approvalPolicy === "deny" && options.rpcToolApprovals !== true ? () => "deny" : (request, signal) => approvals.request(request, signal);
|
|
19512
20432
|
const questions = new QuestionBridge(options.onApprovalChange);
|
|
@@ -19601,9 +20521,10 @@ async function createProductionRuntime(options) {
|
|
|
19601
20521
|
const registeredProviders = registration.registered;
|
|
19602
20522
|
let effectiveLlm = registration.effectiveLlm;
|
|
19603
20523
|
const pluginHost = new PluginHost({
|
|
19604
|
-
globalPluginDirs: [
|
|
19605
|
-
projectPluginDirs: [
|
|
20524
|
+
globalPluginDirs: [join24(home, ".flavor-code", "plugins")],
|
|
20525
|
+
projectPluginDirs: [join24(workspace, ".flavor", "plugins")],
|
|
19606
20526
|
config,
|
|
20527
|
+
sandbox: options.pluginSandbox ?? true,
|
|
19607
20528
|
registrations: {
|
|
19608
20529
|
command(name, handler, description) {
|
|
19609
20530
|
if (typeof handler !== "function") throw new Error(`Plugin command "${name}" must be a function.`);
|
|
@@ -19664,6 +20585,7 @@ async function createProductionRuntime(options) {
|
|
|
19664
20585
|
tools.push(evolveService.toolDefinition());
|
|
19665
20586
|
let sleepScheduler;
|
|
19666
20587
|
let collaborationClient;
|
|
20588
|
+
let collaborationUnavailableDiagnostic;
|
|
19667
20589
|
let unsubscribeCollaboration;
|
|
19668
20590
|
let collaborationSession;
|
|
19669
20591
|
let deliverCollaborationEvent;
|
|
@@ -19704,9 +20626,9 @@ async function createProductionRuntime(options) {
|
|
|
19704
20626
|
const address = palSocketAddress({
|
|
19705
20627
|
platform: process.platform,
|
|
19706
20628
|
userScope: home,
|
|
19707
|
-
runtimeDir:
|
|
20629
|
+
runtimeDir: join24(tmpdir3(), `flavor-code-pals-${createHash17("sha256").update(home).digest("hex").slice(0, 16)}`)
|
|
19708
20630
|
});
|
|
19709
|
-
|
|
20631
|
+
const baseCollaborationClient = collaboration.client ?? collaboration.createClient?.({
|
|
19710
20632
|
instanceId: collaboration.instanceId,
|
|
19711
20633
|
alias,
|
|
19712
20634
|
projectPath: workspace
|
|
@@ -19714,8 +20636,13 @@ async function createProductionRuntime(options) {
|
|
|
19714
20636
|
address,
|
|
19715
20637
|
authHome: home,
|
|
19716
20638
|
registration: { id: collaboration.instanceId, alias, projectPath: workspace },
|
|
19717
|
-
startBroker: () => ensurePalBrokerRunning({ address })
|
|
20639
|
+
startBroker: () => ensurePalBrokerRunning({ address, ...collaboration.startBroker === void 0 ? {} : { startBroker: () => collaboration.startBroker(address) } })
|
|
19718
20640
|
});
|
|
20641
|
+
collaborationClient = collaboration.optional ? recoverablePalClient(baseCollaborationClient, () => {
|
|
20642
|
+
if (collaborationUnavailableDiagnostic === void 0) return;
|
|
20643
|
+
remove(diagnostics, collaborationUnavailableDiagnostic);
|
|
20644
|
+
collaborationUnavailableDiagnostic = void 0;
|
|
20645
|
+
}) : baseCollaborationClient;
|
|
19719
20646
|
deliverCollaborationEvent = async (event) => {
|
|
19720
20647
|
const session2 = collaborationSession;
|
|
19721
20648
|
const client = collaborationClient;
|
|
@@ -19770,11 +20697,19 @@ async function createProductionRuntime(options) {
|
|
|
19770
20697
|
}
|
|
19771
20698
|
};
|
|
19772
20699
|
unsubscribeCollaboration = collaborationClient.subscribe(enqueueCollaborationEvent);
|
|
19773
|
-
|
|
19774
|
-
|
|
19775
|
-
|
|
19776
|
-
|
|
19777
|
-
|
|
20700
|
+
try {
|
|
20701
|
+
await collaborationClient.start();
|
|
20702
|
+
} catch (error) {
|
|
20703
|
+
if (!collaboration.optional) throw error;
|
|
20704
|
+
collaborationUnavailableDiagnostic = `Pals unavailable: ${message(error)}`;
|
|
20705
|
+
diagnostics.push(collaborationUnavailableDiagnostic);
|
|
20706
|
+
}
|
|
20707
|
+
if (collaborationClient !== void 0) {
|
|
20708
|
+
tools.push(...createPalsTools(collaborationClient, {
|
|
20709
|
+
selfId: collaboration.instanceId,
|
|
20710
|
+
shareGuard: new CollaborationShareGuard({ redact: (value) => redactSecrets(value, secrets) })
|
|
20711
|
+
}));
|
|
20712
|
+
}
|
|
19778
20713
|
}
|
|
19779
20714
|
const syncMcpTools = () => {
|
|
19780
20715
|
for (const tool of mcpTools) remove(tools, tool);
|
|
@@ -19802,8 +20737,8 @@ async function createProductionRuntime(options) {
|
|
|
19802
20737
|
diagnostics.push(`MCP servers could not start: ${message(error)}`);
|
|
19803
20738
|
});
|
|
19804
20739
|
const skills = new SkillRegistry({
|
|
19805
|
-
globalRoots: [
|
|
19806
|
-
projectRoots: [
|
|
20740
|
+
globalRoots: [join24(home, ".flavor-code", "skills")],
|
|
20741
|
+
projectRoots: [join24(workspace, ".flavor", "skills"), ...pluginSkillRoots],
|
|
19807
20742
|
authorizeResource: async () => true,
|
|
19808
20743
|
disabledNames: config.skills.disabled
|
|
19809
20744
|
});
|
|
@@ -19811,7 +20746,7 @@ async function createProductionRuntime(options) {
|
|
|
19811
20746
|
skillsReady.catch(() => void 0);
|
|
19812
20747
|
tools.push(createSkillTool(skills), createSkillResourceTool(skills));
|
|
19813
20748
|
if (options.extraTools !== void 0) tools.push(...options.extraTools);
|
|
19814
|
-
const flavor = await optionalText2(
|
|
20749
|
+
const flavor = await optionalText2(join24(workspace, "FLAVOR.md"));
|
|
19815
20750
|
const instructionBaseline = await workspaceInstructions.baseline();
|
|
19816
20751
|
let memoryContext;
|
|
19817
20752
|
if (memoryStore !== void 0) {
|
|
@@ -19835,7 +20770,18 @@ async function createProductionRuntime(options) {
|
|
|
19835
20770
|
let taskResults = { ...recovered?.tasks.results ?? {} };
|
|
19836
20771
|
const subagentStartedAt = {};
|
|
19837
20772
|
const subagentElapsedMs = {};
|
|
19838
|
-
let sessionId = recovered?.sessionId ?? `session-${(/* @__PURE__ */ new Date()).toISOString().replace(/[^0-9]/g, "").slice(0, 17)}-${
|
|
20773
|
+
let sessionId = recovered?.sessionId ?? `session-${(/* @__PURE__ */ new Date()).toISOString().replace(/[^0-9]/g, "").slice(0, 17)}-${randomUUID18().slice(0, 8)}`;
|
|
20774
|
+
let harnessJournal = new HarnessJournal({ workspace, sessionId });
|
|
20775
|
+
const harnessRecovery = harnessJournal.recover();
|
|
20776
|
+
if (harnessRecovery.queue.length > 0 || harnessRecovery.incompleteTools.length > 0 || harnessRecovery.incompleteModelIds.length > 0 || harnessRecovery.interruptedTurnIds.length > 0) {
|
|
20777
|
+
diagnostics.push(
|
|
20778
|
+
`Recovered durable harness state: ${harnessRecovery.queue.length} queued prompt(s), ${harnessRecovery.incompleteTools.length} interrupted tool call(s), ${harnessRecovery.incompleteModelIds.length} interrupted model call(s), ${harnessRecovery.interruptedTurnIds.length} interrupted turn(s).`
|
|
20779
|
+
);
|
|
20780
|
+
for (const tool of harnessRecovery.incompleteTools.filter((item) => !item.retrySafe)) {
|
|
20781
|
+
diagnostics.push(`Interrupted non-retry-safe tool "${tool.tool}" was not replayed (${tool.inputHash.slice(0, 12)}).`);
|
|
20782
|
+
}
|
|
20783
|
+
harnessJournal.markRecoveryComplete(harnessRecovery);
|
|
20784
|
+
}
|
|
19839
20785
|
setUsageSession(sessionId);
|
|
19840
20786
|
ideSessionId = sessionId;
|
|
19841
20787
|
await ideReady;
|
|
@@ -20008,8 +20954,8 @@ async function createProductionRuntime(options) {
|
|
|
20008
20954
|
volatileSystem: () => [
|
|
20009
20955
|
buildCurrentDateSection(promptEnvironment.date),
|
|
20010
20956
|
buildRuntimeEnvironmentSection({
|
|
20011
|
-
model: agent === "main" ? harness.mainModelId : contextModelId,
|
|
20012
|
-
permissionMode: agent === "subagent" ? harness.permissionMode === "plan" ? "plan" : "bubble" : harness.permissionMode
|
|
20957
|
+
model: agent === "main" && harnessCreated ? harness.mainModelId : contextModelId,
|
|
20958
|
+
permissionMode: agent === "subagent" ? (harnessCreated ? harness.permissionMode : recovered?.permissionMode ?? config.permissionMode) === "plan" ? "plan" : "bubble" : harnessCreated ? harness.permissionMode : recovered?.permissionMode ?? config.permissionMode
|
|
20013
20959
|
})
|
|
20014
20960
|
],
|
|
20015
20961
|
...flavor === void 0 ? {} : { flavor },
|
|
@@ -20048,15 +20994,36 @@ async function createProductionRuntime(options) {
|
|
|
20048
20994
|
tools,
|
|
20049
20995
|
createContext,
|
|
20050
20996
|
permissionMode: recovered?.permissionMode ?? config.permissionMode,
|
|
20997
|
+
permissionPolicy,
|
|
20051
20998
|
maxIterationsMain: config.maxIterations.main,
|
|
20052
20999
|
maxIterationsSubagent: config.maxIterations.subagent,
|
|
20053
21000
|
hasActiveProgress,
|
|
20054
21001
|
afterToolSuccess: async (_tool, paths, _input, _output, context) => workspaceInstructions.discover(paths, context.ownerId ?? context.agent),
|
|
21002
|
+
toolJournal: {
|
|
21003
|
+
start: (tool, input, retrySafe) => harnessJournal.startTool(tool, input, retrySafe),
|
|
21004
|
+
complete: (id, result) => harnessJournal.completeTool(id, result),
|
|
21005
|
+
interrupt: (id, reason) => harnessJournal.interruptTool(id, reason)
|
|
21006
|
+
},
|
|
21007
|
+
modelJournal: {
|
|
21008
|
+
start: (input) => harnessJournal.startModel(input),
|
|
21009
|
+
complete: (id, completed, error) => harnessJournal.completeModel(id, completed, error)
|
|
21010
|
+
},
|
|
20055
21011
|
approve: resolveToolApproval
|
|
20056
21012
|
});
|
|
20057
21013
|
harnessCreated = true;
|
|
20058
21014
|
if (recovered !== void 0) harness.main.context.restore({
|
|
20059
21015
|
...recovered.conversation.compact === void 0 ? {} : { compact: recovered.conversation.compact },
|
|
21016
|
+
...recovered.conversation.epoch === void 0 ? {} : { epoch: {
|
|
21017
|
+
...recovered.conversation.epoch,
|
|
21018
|
+
stableMessages: recovered.conversation.epoch.stableMessages.map((message2) => ({
|
|
21019
|
+
role: "system",
|
|
21020
|
+
content: message2.content,
|
|
21021
|
+
...message2.cacheBreakpoint === void 0 ? {} : { cacheBreakpoint: message2.cacheBreakpoint }
|
|
21022
|
+
}))
|
|
21023
|
+
} },
|
|
21024
|
+
...recovered.conversation.visibilityLog === void 0 ? {} : {
|
|
21025
|
+
visibilityLog: recovered.conversation.visibilityLog
|
|
21026
|
+
},
|
|
20060
21027
|
messages: recovered.conversation.messages.map((message2) => {
|
|
20061
21028
|
const metadata = {
|
|
20062
21029
|
...message2.toolCallId === void 0 ? {} : { toolCallId: message2.toolCallId },
|
|
@@ -20235,7 +21202,7 @@ async function createProductionRuntime(options) {
|
|
|
20235
21202
|
agent,
|
|
20236
21203
|
providerError,
|
|
20237
21204
|
errorCode,
|
|
20238
|
-
errorMessage,
|
|
21205
|
+
errorMessage: errorMessage2,
|
|
20239
21206
|
attempt,
|
|
20240
21207
|
maxAttempts,
|
|
20241
21208
|
purpose,
|
|
@@ -20251,7 +21218,7 @@ async function createProductionRuntime(options) {
|
|
|
20251
21218
|
model: typeof modelId === "string" ? modelId : void 0,
|
|
20252
21219
|
agent: typeof agent === "string" ? agent : void 0,
|
|
20253
21220
|
errorCode: typeof errorCode === "string" ? errorCode : void 0,
|
|
20254
|
-
errorMessage: typeof
|
|
21221
|
+
errorMessage: typeof errorMessage2 === "string" ? errorMessage2 : void 0,
|
|
20255
21222
|
attempt: typeof attempt === "number" ? attempt : void 0,
|
|
20256
21223
|
maxAttempts: typeof maxAttempts === "number" ? maxAttempts : void 0,
|
|
20257
21224
|
purpose: typeof purpose === "string" ? purpose : void 0,
|
|
@@ -20306,7 +21273,7 @@ async function createProductionRuntime(options) {
|
|
|
20306
21273
|
evolveService.toolDefinition(),
|
|
20307
21274
|
...mcpTools
|
|
20308
21275
|
];
|
|
20309
|
-
const loopFlavor = await optionalText2(
|
|
21276
|
+
const loopFlavor = await optionalText2(join24(input.workspace, "FLAVOR.md"));
|
|
20310
21277
|
const loopInstructionBaseline = await loopInstructions.baseline();
|
|
20311
21278
|
const loopEnvironment = createPromptEnvironment({
|
|
20312
21279
|
now: /* @__PURE__ */ new Date(),
|
|
@@ -20318,6 +21285,7 @@ async function createProductionRuntime(options) {
|
|
|
20318
21285
|
let compactionInputTokens = 0;
|
|
20319
21286
|
let compactionOutputTokens = 0;
|
|
20320
21287
|
let loopHarness;
|
|
21288
|
+
let loopHarnessCreated = false;
|
|
20321
21289
|
const createLoopContext = (agent, agentTools, contextModelId) => {
|
|
20322
21290
|
const language2 = resolveLanguage(config.language);
|
|
20323
21291
|
const { compactAtChars, toolOutputChars, ...compaction } = config.context;
|
|
@@ -20337,8 +21305,8 @@ async function createProductionRuntime(options) {
|
|
|
20337
21305
|
volatileSystem: () => [
|
|
20338
21306
|
buildCurrentDateSection(loopEnvironment.date),
|
|
20339
21307
|
buildRuntimeEnvironmentSection({
|
|
20340
|
-
model: agent === "main" ? loopHarness.mainModelId : contextModelId,
|
|
20341
|
-
permissionMode: agent === "subagent" ? loopHarness.permissionMode === "plan" ? "plan" : "bubble" : loopHarness.permissionMode
|
|
21308
|
+
model: agent === "main" && loopHarnessCreated ? loopHarness.mainModelId : contextModelId,
|
|
21309
|
+
permissionMode: agent === "subagent" ? (loopHarnessCreated ? loopHarness.permissionMode : harness.permissionMode) === "plan" ? "plan" : "bubble" : loopHarnessCreated ? loopHarness.permissionMode : harness.permissionMode
|
|
20342
21310
|
})
|
|
20343
21311
|
],
|
|
20344
21312
|
...loopFlavor === void 0 ? {} : { flavor: loopFlavor },
|
|
@@ -20372,12 +21340,23 @@ async function createProductionRuntime(options) {
|
|
|
20372
21340
|
tools: loopTools,
|
|
20373
21341
|
createContext: createLoopContext,
|
|
20374
21342
|
permissionMode: harness.permissionMode,
|
|
21343
|
+
permissionPolicy,
|
|
20375
21344
|
maxIterationsMain: config.maxIterations.main,
|
|
20376
21345
|
maxIterationsSubagent: config.maxIterations.subagent,
|
|
20377
21346
|
loopMode: true,
|
|
20378
21347
|
afterToolSuccess: async (_tool, paths, _input, _output, context) => loopInstructions.discover(paths, context.ownerId ?? context.agent),
|
|
21348
|
+
toolJournal: {
|
|
21349
|
+
start: (tool, input2, retrySafe) => harnessJournal.startTool(tool, input2, retrySafe),
|
|
21350
|
+
complete: (id, result) => harnessJournal.completeTool(id, result),
|
|
21351
|
+
interrupt: (id, reason) => harnessJournal.interruptTool(id, reason)
|
|
21352
|
+
},
|
|
21353
|
+
modelJournal: {
|
|
21354
|
+
start: (input2) => harnessJournal.startModel(input2),
|
|
21355
|
+
complete: (id, completed, error) => harnessJournal.completeModel(id, completed, error)
|
|
21356
|
+
},
|
|
20379
21357
|
approve: resolveToolApproval
|
|
20380
21358
|
});
|
|
21359
|
+
loopHarnessCreated = true;
|
|
20381
21360
|
let runReason = "finished";
|
|
20382
21361
|
try {
|
|
20383
21362
|
evolveService.beginRun();
|
|
@@ -20433,7 +21412,7 @@ async function createProductionRuntime(options) {
|
|
|
20433
21412
|
return answers[0] === "Continue" ? "approved" : "rejected";
|
|
20434
21413
|
},
|
|
20435
21414
|
fingerprint: workspaceFingerprint,
|
|
20436
|
-
idFactory: () => `loop-${(/* @__PURE__ */ new Date()).toISOString().replace(/[^0-9]/g, "").slice(0, 17)}-${
|
|
21415
|
+
idFactory: () => `loop-${(/* @__PURE__ */ new Date()).toISOString().replace(/[^0-9]/g, "").slice(0, 17)}-${randomUUID18().slice(0, 8)}`
|
|
20437
21416
|
});
|
|
20438
21417
|
const goalStore = new GoalStore({ workspace });
|
|
20439
21418
|
const goalOrchestrator = new GoalOrchestrator({
|
|
@@ -20445,8 +21424,14 @@ async function createProductionRuntime(options) {
|
|
|
20445
21424
|
maxRounds: 5,
|
|
20446
21425
|
maxStallStreak: 2,
|
|
20447
21426
|
persistence: goalStore,
|
|
21427
|
+
verifyHost: async (signal) => runVerificationPlan(
|
|
21428
|
+
await inferVerificationPlan(workspace),
|
|
21429
|
+
workspace,
|
|
21430
|
+
signal,
|
|
21431
|
+
executionEnvironment
|
|
21432
|
+
),
|
|
20448
21433
|
now: () => (/* @__PURE__ */ new Date()).toISOString(),
|
|
20449
|
-
idFactory: () => `goal-${(/* @__PURE__ */ new Date()).toISOString().replace(/[^0-9]/g, "").slice(0, 17)}-${
|
|
21434
|
+
idFactory: () => `goal-${(/* @__PURE__ */ new Date()).toISOString().replace(/[^0-9]/g, "").slice(0, 17)}-${randomUUID18().slice(0, 8)}`,
|
|
20450
21435
|
runWorker: ({ workspace: goalWorkspace, prompt, signal }) => runLoopWorker({ workspace: goalWorkspace, prompt, signal })
|
|
20451
21436
|
});
|
|
20452
21437
|
let sessionHistory = await SessionHistory.open({
|
|
@@ -20481,6 +21466,7 @@ async function createProductionRuntime(options) {
|
|
|
20481
21466
|
}),
|
|
20482
21467
|
rename: (alias) => collaborationClient.rename(alias),
|
|
20483
21468
|
info: (target) => resolveCollaborationTarget(target),
|
|
21469
|
+
sendChat: (target, message2) => collaborationClient.sendChat(target, message2),
|
|
20484
21470
|
sendTask: (target, goal) => collaborationClient.sendTask(target, goal),
|
|
20485
21471
|
startCoWork: async (target, goal) => {
|
|
20486
21472
|
const recipient = await resolveCollaborationTarget(target);
|
|
@@ -20507,6 +21493,13 @@ async function createProductionRuntime(options) {
|
|
|
20507
21493
|
const services = {
|
|
20508
21494
|
hooks,
|
|
20509
21495
|
workspace,
|
|
21496
|
+
durableQueue: {
|
|
21497
|
+
recover: () => harnessRecovery.queue,
|
|
21498
|
+
admit: (kind, payload) => harnessJournal.admitQueue(kind, payload),
|
|
21499
|
+
claim: (id) => harnessJournal.claimQueue(id),
|
|
21500
|
+
ack: (id) => harnessJournal.ackQueue(id),
|
|
21501
|
+
release: (id, reason) => harnessJournal.releaseQueue(id, reason)
|
|
21502
|
+
},
|
|
20510
21503
|
mainModel: () => mainModel,
|
|
20511
21504
|
subagentModel: () => childModel,
|
|
20512
21505
|
llmServiceName: () => effectiveLlm?.serviceName,
|
|
@@ -20518,7 +21511,14 @@ async function createProductionRuntime(options) {
|
|
|
20518
21511
|
...palServices === void 0 ? {} : { pals: palServices },
|
|
20519
21512
|
run: (prompt, signal, runOptions) => {
|
|
20520
21513
|
interruptedTaskPlanNeedsReassessment = false;
|
|
20521
|
-
|
|
21514
|
+
const turnConfig = Object.freeze({
|
|
21515
|
+
mainModel,
|
|
21516
|
+
subagentModel: childModel,
|
|
21517
|
+
permissionMode: harness.permissionMode,
|
|
21518
|
+
contextEpoch: harness.main.context.snapshot().epoch?.id ?? "legacy"
|
|
21519
|
+
});
|
|
21520
|
+
const turnId = harnessJournal.startTurn(turnConfig, { prompt, initialUserMessage: runOptions?.initialUserMessage });
|
|
21521
|
+
return durableTurn(persistAndCheckpointAfter(runMain(
|
|
20522
21522
|
harness,
|
|
20523
21523
|
skills,
|
|
20524
21524
|
prompt,
|
|
@@ -20538,7 +21538,7 @@ async function createProductionRuntime(options) {
|
|
|
20538
21538
|
prompt,
|
|
20539
21539
|
context: harness.main.context.snapshot(),
|
|
20540
21540
|
label: `turn: ${prompt.slice(0, 80)}`
|
|
20541
|
-
}));
|
|
21541
|
+
})), harnessJournal, turnId, turnConfig);
|
|
20542
21542
|
},
|
|
20543
21543
|
runSkill: (skill, prompt, signal) => persistAfter(
|
|
20544
21544
|
runExplicitSkill(harness, skills, skill, prompt, signal, selectedModels.mainError),
|
|
@@ -20635,7 +21635,7 @@ async function createProductionRuntime(options) {
|
|
|
20635
21635
|
}, focus, signal),
|
|
20636
21636
|
audit: async (toolFilter) => {
|
|
20637
21637
|
try {
|
|
20638
|
-
const raw = await
|
|
21638
|
+
const raw = await readFile22(auditLogger.path, "utf8");
|
|
20639
21639
|
const lines = raw.trim().split("\n").filter((line) => line.length > 0);
|
|
20640
21640
|
const entries = lines.map((line) => {
|
|
20641
21641
|
try {
|
|
@@ -20674,7 +21674,7 @@ ${summary2}`;
|
|
|
20674
21674
|
},
|
|
20675
21675
|
usage: async () => {
|
|
20676
21676
|
try {
|
|
20677
|
-
const raw = await
|
|
21677
|
+
const raw = await readFile22(usageLogPath(), "utf8");
|
|
20678
21678
|
return formatUsageSummary(summarizeUsage(parseUsageEntries(raw)));
|
|
20679
21679
|
} catch (error) {
|
|
20680
21680
|
if (typeof error === "object" && error !== null && "code" in error && error.code === "ENOENT") {
|
|
@@ -20702,7 +21702,8 @@ ${summary2}`;
|
|
|
20702
21702
|
for (const key of Object.keys(subagentStartedAt)) delete subagentStartedAt[key];
|
|
20703
21703
|
for (const key of Object.keys(subagentElapsedMs)) delete subagentElapsedMs[key];
|
|
20704
21704
|
const previousIdeSessionId = ideSessionId;
|
|
20705
|
-
sessionId = `session-${(/* @__PURE__ */ new Date()).toISOString().replace(/[^0-9]/g, "").slice(0, 17)}-${
|
|
21705
|
+
sessionId = `session-${(/* @__PURE__ */ new Date()).toISOString().replace(/[^0-9]/g, "").slice(0, 17)}-${randomUUID18().slice(0, 8)}`;
|
|
21706
|
+
harnessJournal = new HarnessJournal({ workspace, sessionId });
|
|
20706
21707
|
setUsageSession(sessionId);
|
|
20707
21708
|
ideSessionId = sessionId;
|
|
20708
21709
|
if (previousIdeSessionId !== void 0) await ide.endSession(previousIdeSessionId);
|
|
@@ -20726,6 +21727,7 @@ ${summary2}`;
|
|
|
20726
21727
|
return sessionHistory.checkpoint(tagged, harness.main.context.snapshot());
|
|
20727
21728
|
},
|
|
20728
21729
|
tree: () => sessionHistory.tree(),
|
|
21730
|
+
historyLeaf: () => sessionHistory.leafId,
|
|
20729
21731
|
rewind: async (nodeId) => {
|
|
20730
21732
|
await sessionHistory.rewind(nodeId, harness.main.context.snapshot());
|
|
20731
21733
|
await persist();
|
|
@@ -20800,7 +21802,7 @@ ${renderMemoryDocument(entries)}`;
|
|
|
20800
21802
|
return `Provider "${providerName}" is missing authorizationUrl, tokenUrl, or clientId.`;
|
|
20801
21803
|
}
|
|
20802
21804
|
try {
|
|
20803
|
-
const tokenStore = createFileTokenStore(
|
|
21805
|
+
const tokenStore = createFileTokenStore(join24(home, ".flavor-code", "auth.json"));
|
|
20804
21806
|
const credentialId = oauthCredentialId(oauthConfig.tokenUrl, oauthConfig.clientId);
|
|
20805
21807
|
await migrateLegacyOAuthToken(tokenStore, providerName, credentialId);
|
|
20806
21808
|
const oauth = new OAuthCallbackAuthProvider({
|
|
@@ -20843,7 +21845,7 @@ ${renderMemoryDocument(entries)}`;
|
|
|
20843
21845
|
}
|
|
20844
21846
|
},
|
|
20845
21847
|
async logout() {
|
|
20846
|
-
const tokenStore = createFileTokenStore(
|
|
21848
|
+
const tokenStore = createFileTokenStore(join24(home, ".flavor-code", "auth.json"));
|
|
20847
21849
|
const tokens = await tokenStore.load();
|
|
20848
21850
|
const hadCredentials = Object.keys(tokens).length > 0 || effectiveLlm !== void 0;
|
|
20849
21851
|
if (!hadCredentials) {
|
|
@@ -20914,7 +21916,7 @@ ${renderMemoryDocument(entries)}`;
|
|
|
20914
21916
|
approvals,
|
|
20915
21917
|
memoryReviews,
|
|
20916
21918
|
restoredTranscript,
|
|
20917
|
-
jobs: { list: () => jobs.list(), subscribe: (listener) => jobs.subscribe(listener) },
|
|
21919
|
+
jobs: { list: () => jobs.list(), read: (id, owner2, cursor) => jobs.read(id, owner2, cursor), subscribe: (listener) => jobs.subscribe(listener) },
|
|
20918
21920
|
get sessionId() {
|
|
20919
21921
|
return sessionId;
|
|
20920
21922
|
},
|
|
@@ -21073,7 +22075,7 @@ function terminalProgressState(status) {
|
|
|
21073
22075
|
return "failed";
|
|
21074
22076
|
}
|
|
21075
22077
|
async function workspaceFingerprint(workspace) {
|
|
21076
|
-
const hash =
|
|
22078
|
+
const hash = createHash17("sha256");
|
|
21077
22079
|
const diff = await execFileNoThrow(
|
|
21078
22080
|
"git",
|
|
21079
22081
|
["-C", workspace, "diff", "--no-ext-diff", "--binary", "HEAD"],
|
|
@@ -21089,13 +22091,13 @@ async function workspaceFingerprint(workspace) {
|
|
|
21089
22091
|
let remaining = 5 * 1024 * 1024;
|
|
21090
22092
|
for (const name of untracked.stdout.split("\0").filter(Boolean).sort()) {
|
|
21091
22093
|
if (name === ".flavor/loops" || name.startsWith(".flavor/loops/")) continue;
|
|
21092
|
-
const path =
|
|
21093
|
-
const relativePath = relative20(
|
|
21094
|
-
if (relativePath === ".." || relativePath.startsWith(`..${
|
|
22094
|
+
const path = resolve27(workspace, name);
|
|
22095
|
+
const relativePath = relative20(resolve27(workspace), path);
|
|
22096
|
+
if (relativePath === ".." || relativePath.startsWith(`..${sep20}`)) continue;
|
|
21095
22097
|
hash.update(name);
|
|
21096
22098
|
if (remaining <= 0) continue;
|
|
21097
22099
|
try {
|
|
21098
|
-
const content = await
|
|
22100
|
+
const content = await readFile22(path);
|
|
21099
22101
|
const slice = content.subarray(0, remaining);
|
|
21100
22102
|
hash.update(slice);
|
|
21101
22103
|
remaining -= slice.length;
|
|
@@ -21194,6 +22196,27 @@ async function* persistEach(source, persist) {
|
|
|
21194
22196
|
await persist();
|
|
21195
22197
|
}
|
|
21196
22198
|
}
|
|
22199
|
+
async function* durableTurn(source, journal, turnId, config) {
|
|
22200
|
+
let failure;
|
|
22201
|
+
let completed = false;
|
|
22202
|
+
try {
|
|
22203
|
+
for await (const event of source) {
|
|
22204
|
+
if (event.type === "error") failure = event.error.message;
|
|
22205
|
+
yield event;
|
|
22206
|
+
}
|
|
22207
|
+
completed = true;
|
|
22208
|
+
} catch (error) {
|
|
22209
|
+
failure = message(error);
|
|
22210
|
+
throw error;
|
|
22211
|
+
} finally {
|
|
22212
|
+
if (completed && failure === void 0) {
|
|
22213
|
+
journal.completeTurn(turnId);
|
|
22214
|
+
journal.savepoint("turn-complete", config);
|
|
22215
|
+
} else {
|
|
22216
|
+
journal.interruptTurn(turnId, failure ?? "turn consumer interrupted before completion");
|
|
22217
|
+
}
|
|
22218
|
+
}
|
|
22219
|
+
}
|
|
21197
22220
|
async function runGitCommit(deps, hint, signal) {
|
|
21198
22221
|
if (!await isGitRepository(deps.workspace)) return "Not a git repository \u2014 /commit needs git.";
|
|
21199
22222
|
let summary2 = await changeSummary(deps.workspace);
|
|
@@ -21287,7 +22310,7 @@ async function registerConfiguredAdapters(providers, registry, environment, diag
|
|
|
21287
22310
|
const configured = { ...providers };
|
|
21288
22311
|
if (configured.openai === void 0 && environment.OPENAI_API_KEY) configured.openai = { type: "openai", apiKey: environment.OPENAI_API_KEY };
|
|
21289
22312
|
if (configured.anthropic === void 0 && environment.ANTHROPIC_API_KEY) configured.anthropic = { type: "anthropic", apiKey: environment.ANTHROPIC_API_KEY };
|
|
21290
|
-
const oauthTokenStore = createFileTokenStore(
|
|
22313
|
+
const oauthTokenStore = createFileTokenStore(join24(home, ".flavor-code", "auth.json"));
|
|
21291
22314
|
const registered = [];
|
|
21292
22315
|
let effectiveLlm;
|
|
21293
22316
|
for (const [name, provider] of Object.entries(configured)) {
|
|
@@ -21552,7 +22575,7 @@ async function generateSleepReview(registry, modelId, prompt, signal) {
|
|
|
21552
22575
|
}
|
|
21553
22576
|
async function optionalText2(path) {
|
|
21554
22577
|
try {
|
|
21555
|
-
return await
|
|
22578
|
+
return await readFile22(path, "utf8");
|
|
21556
22579
|
} catch (error) {
|
|
21557
22580
|
if (typeof error === "object" && error !== null && "code" in error && error.code === "ENOENT") return void 0;
|
|
21558
22581
|
throw error;
|
|
@@ -21568,13 +22591,15 @@ function sameToolName(left, right) {
|
|
|
21568
22591
|
function storedConversation(snapshot4) {
|
|
21569
22592
|
return {
|
|
21570
22593
|
...snapshot4.compact === void 0 ? {} : { compact: snapshot4.compact },
|
|
22594
|
+
...snapshot4.epoch === void 0 ? {} : { epoch: snapshot4.epoch },
|
|
22595
|
+
...snapshot4.visibilityLog === void 0 ? {} : { visibilityLog: snapshot4.visibilityLog },
|
|
21571
22596
|
messages: snapshot4.messages.flatMap((message2) => {
|
|
21572
|
-
if (message2.role === "system") return [];
|
|
21573
22597
|
const metadata = {
|
|
21574
22598
|
...message2.toolCallId === void 0 ? {} : { toolCallId: message2.toolCallId },
|
|
21575
22599
|
...message2.toolCalls === void 0 ? {} : { toolCalls: message2.toolCalls }
|
|
21576
22600
|
};
|
|
21577
22601
|
if (message2.role === "user") return { role: "user", content: message2.content, ...metadata };
|
|
22602
|
+
if (message2.role === "system") return { role: "system", content: message2.content, ...metadata };
|
|
21578
22603
|
if (message2.role === "assistant") return { role: "assistant", content: message2.content, ...metadata };
|
|
21579
22604
|
return { role: "tool", content: message2.content, ...metadata };
|
|
21580
22605
|
})
|
|
@@ -21704,10 +22729,10 @@ async function* runGoalSession(orchestrator, goal, signal) {
|
|
|
21704
22729
|
}
|
|
21705
22730
|
function memoryTranscriptHash(messages) {
|
|
21706
22731
|
const visible = messages.filter((item) => item.role === "user" || item.role === "assistant").map((item) => `${item.role}\0${modelContentText(item.content).trim()}`).join("\n");
|
|
21707
|
-
return
|
|
22732
|
+
return createHash17("sha256").update(visible, "utf8").digest("hex");
|
|
21708
22733
|
}
|
|
21709
22734
|
function createMemoryTaskId() {
|
|
21710
|
-
return `memory-${
|
|
22735
|
+
return `memory-${randomUUID18()}`;
|
|
21711
22736
|
}
|
|
21712
22737
|
|
|
21713
22738
|
export {
|
|
@@ -21734,6 +22759,7 @@ export {
|
|
|
21734
22759
|
COMMAND_DESCRIPTIONS,
|
|
21735
22760
|
AgentMessageQueue,
|
|
21736
22761
|
FlavorSession,
|
|
22762
|
+
classifyMemoryHeat,
|
|
21737
22763
|
MemoryStore,
|
|
21738
22764
|
buildDockerInvocation,
|
|
21739
22765
|
DockerExecutionEnvironment,
|