flavor-code 1.2.20 → 1.3.0
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 +15 -10
- package/README.zh-CN.md +15 -10
- package/dist/agent/loop.d.ts +11 -1
- package/dist/{app-5D5RSGQX.js → app-FOPTLLK5.js} +1 -1
- package/dist/{chunk-A7PMQ22O.js → chunk-IX65GYUF.js} +1 -1
- package/dist/{chunk-AXLUVJKC.js → chunk-KL4UHJ2Q.js} +1718 -761
- package/dist/cli.js +3 -3
- package/dist/context/manager.d.ts +32 -0
- package/dist/desktop/main.js +2253 -1296
- 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/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 +3 -0
- 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 +11 -0
- package/package.json +3 -2
|
@@ -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));
|
|
@@ -4889,8 +4974,8 @@ async function runPalBroker(options) {
|
|
|
4889
4974
|
const sweepIntervalMs = options.sweepIntervalMs ?? 5e3;
|
|
4890
4975
|
const idleShutdownMs = options.idleShutdownMs ?? 3e4;
|
|
4891
4976
|
let resolveClosed;
|
|
4892
|
-
const closed = new Promise((
|
|
4893
|
-
resolveClosed =
|
|
4977
|
+
const closed = new Promise((resolve28) => {
|
|
4978
|
+
resolveClosed = resolve28;
|
|
4894
4979
|
});
|
|
4895
4980
|
let closing;
|
|
4896
4981
|
const timer = setInterval(() => {
|
|
@@ -4930,10 +5015,10 @@ async function ensureOnce(options) {
|
|
|
4930
5015
|
}
|
|
4931
5016
|
|
|
4932
5017
|
// src/production.ts
|
|
4933
|
-
import { readFile as
|
|
4934
|
-
import { createHash as
|
|
5018
|
+
import { readFile as readFile22 } from "fs/promises";
|
|
5019
|
+
import { createHash as createHash17, randomUUID as randomUUID18 } from "crypto";
|
|
4935
5020
|
import { homedir as homedir2, release as osRelease, tmpdir as tmpdir3, version as osVersion } from "os";
|
|
4936
|
-
import { basename as basename6, join as
|
|
5021
|
+
import { basename as basename6, join as join24, relative as relative20, resolve as resolve27, sep as sep20 } from "path";
|
|
4937
5022
|
|
|
4938
5023
|
// src/agent/subagents.ts
|
|
4939
5024
|
import { z as z5 } from "zod";
|
|
@@ -5475,6 +5560,9 @@ function createTaskPlanTools(options) {
|
|
|
5475
5560
|
return [planTool, updateTool];
|
|
5476
5561
|
}
|
|
5477
5562
|
|
|
5563
|
+
// src/context/manager.ts
|
|
5564
|
+
import { createHash as createHash4, randomUUID as randomUUID6 } from "crypto";
|
|
5565
|
+
|
|
5478
5566
|
// src/context/compaction.ts
|
|
5479
5567
|
var DEFAULT_COMPACTION_POLICY = {
|
|
5480
5568
|
windowTokens: 2e5,
|
|
@@ -5686,6 +5774,9 @@ var ContextManager = class _ContextManager {
|
|
|
5686
5774
|
#lastCompactProgress;
|
|
5687
5775
|
#forkPinnedBoundary = false;
|
|
5688
5776
|
#forkCompactBoundary = false;
|
|
5777
|
+
#epoch;
|
|
5778
|
+
#visibilityLog = [];
|
|
5779
|
+
#activeTransientSystem = /* @__PURE__ */ new Map();
|
|
5689
5780
|
constructor(options) {
|
|
5690
5781
|
if (options.compactAtChars !== void 0 && options.compactAtChars <= 0) throw new Error("compactAtChars must be positive");
|
|
5691
5782
|
if (options.toolOutputChars <= 0) throw new Error("toolOutputChars must be positive");
|
|
@@ -5712,6 +5803,7 @@ var ContextManager = class _ContextManager {
|
|
|
5712
5803
|
this.#summarize = options.summarize;
|
|
5713
5804
|
this.#onCompactProgress = options.onCompactProgress;
|
|
5714
5805
|
this.#hooks = options.hooks;
|
|
5806
|
+
this.#epoch = this.#createEpoch();
|
|
5715
5807
|
}
|
|
5716
5808
|
clear() {
|
|
5717
5809
|
this.#compact = void 0;
|
|
@@ -5722,6 +5814,9 @@ var ContextManager = class _ContextManager {
|
|
|
5722
5814
|
this.#consecutiveAutoCompactFailures = 0;
|
|
5723
5815
|
this.#forkPinnedBoundary = false;
|
|
5724
5816
|
this.#forkCompactBoundary = false;
|
|
5817
|
+
this.#epoch = this.#createEpoch();
|
|
5818
|
+
this.#visibilityLog = [];
|
|
5819
|
+
this.#activeTransientSystem.clear();
|
|
5725
5820
|
}
|
|
5726
5821
|
/**
|
|
5727
5822
|
* Create an isolated child context whose model-visible prefix is identical to
|
|
@@ -5748,6 +5843,7 @@ var ContextManager = class _ContextManager {
|
|
|
5748
5843
|
hooks: options.hooks ?? this.#hooks
|
|
5749
5844
|
});
|
|
5750
5845
|
child.#compact = this.#compact === void 0 ? void 0 : { ...this.#compact };
|
|
5846
|
+
child.#epoch = cloneEpoch(this.#epoch);
|
|
5751
5847
|
child.#messages = this.#messages.map((message2) => {
|
|
5752
5848
|
const { cacheBreakpoint: _cacheBreakpoint, ...copy } = cloneForkMessage(message2);
|
|
5753
5849
|
return copy;
|
|
@@ -5771,9 +5867,70 @@ var ContextManager = class _ContextManager {
|
|
|
5771
5867
|
updateTaskState(taskState) {
|
|
5772
5868
|
this.#taskState = taskState;
|
|
5773
5869
|
}
|
|
5870
|
+
beginTransientSystem(content) {
|
|
5871
|
+
const id = randomUUID6();
|
|
5872
|
+
const record = {
|
|
5873
|
+
id,
|
|
5874
|
+
role: "system",
|
|
5875
|
+
content,
|
|
5876
|
+
admittedAt: (/* @__PURE__ */ new Date()).toISOString(),
|
|
5877
|
+
scope: "run"
|
|
5878
|
+
};
|
|
5879
|
+
this.#visibilityLog.push(record);
|
|
5880
|
+
this.#activeTransientSystem.set(id, { role: "system", content });
|
|
5881
|
+
return id;
|
|
5882
|
+
}
|
|
5883
|
+
endTransientSystem(id) {
|
|
5884
|
+
this.#activeTransientSystem.delete(id);
|
|
5885
|
+
}
|
|
5886
|
+
/**
|
|
5887
|
+
* Admit dynamic source changes at a model-call savepoint. The stable prefix
|
|
5888
|
+
* remains byte-identical; changes become chronological, persisted system
|
|
5889
|
+
* messages after the cache breakpoint.
|
|
5890
|
+
*/
|
|
5891
|
+
refreshContextSources() {
|
|
5892
|
+
const updates = [];
|
|
5893
|
+
const next = this.#resolvedDynamicSources(this.#epoch.sources);
|
|
5894
|
+
let currentStable;
|
|
5895
|
+
try {
|
|
5896
|
+
currentStable = this.#buildStableMessages(this.#epoch.stableMessages);
|
|
5897
|
+
} catch {
|
|
5898
|
+
currentStable = void 0;
|
|
5899
|
+
}
|
|
5900
|
+
const currentStableHash = currentStable === void 0 ? this.#epoch.stableSourceHash : hashMessages(currentStable);
|
|
5901
|
+
if (currentStable !== void 0 && currentStableHash !== this.#epoch.stableSourceHash) {
|
|
5902
|
+
const value = currentStable.map((message2) => message2.content).join("\n\n");
|
|
5903
|
+
if (this.#epoch.sources["system-baseline"] !== value) {
|
|
5904
|
+
updates.push(contextUpdateMessage("system-baseline", value));
|
|
5905
|
+
this.#epoch.sources["system-baseline"] = value;
|
|
5906
|
+
}
|
|
5907
|
+
} else if (currentStable !== void 0 && this.#epoch.sources["system-baseline"] !== void 0) {
|
|
5908
|
+
const value = currentStable.map((message2) => message2.content).join("\n\n");
|
|
5909
|
+
updates.push(contextUpdateMessage("system-baseline", value));
|
|
5910
|
+
delete this.#epoch.sources["system-baseline"];
|
|
5911
|
+
}
|
|
5912
|
+
for (const key of DYNAMIC_SOURCE_ORDER) {
|
|
5913
|
+
const value = next[key];
|
|
5914
|
+
if (value === void 0 || value === this.#epoch.sources[key]) continue;
|
|
5915
|
+
updates.push(contextUpdateMessage(key, displaySourceValue(key, value)));
|
|
5916
|
+
this.#epoch.sources[key] = value;
|
|
5917
|
+
}
|
|
5918
|
+
for (const key of DYNAMIC_SOURCE_ORDER) {
|
|
5919
|
+
if (key in next || !(key in this.#epoch.sources)) continue;
|
|
5920
|
+
updates.push(contextUpdateMessage(key, "(removed)"));
|
|
5921
|
+
delete this.#epoch.sources[key];
|
|
5922
|
+
}
|
|
5923
|
+
if (updates.length === 0) return false;
|
|
5924
|
+
this.#messages.push(...updates);
|
|
5925
|
+
this.#lastRecordedInputTokens = void 0;
|
|
5926
|
+
this.#estimatedTokensAtLastRecordedUsage = void 0;
|
|
5927
|
+
return true;
|
|
5928
|
+
}
|
|
5774
5929
|
snapshot() {
|
|
5775
5930
|
return {
|
|
5776
5931
|
...this.#compact === void 0 ? {} : { compact: { ...this.#compact } },
|
|
5932
|
+
epoch: cloneEpoch(this.#epoch),
|
|
5933
|
+
...this.#visibilityLog.length === 0 ? {} : { visibilityLog: this.#visibilityLog.map((item) => ({ ...item })) },
|
|
5777
5934
|
messages: providerValidMessages(this.#messages)
|
|
5778
5935
|
};
|
|
5779
5936
|
}
|
|
@@ -5781,6 +5938,9 @@ var ContextManager = class _ContextManager {
|
|
|
5781
5938
|
const messages = providerValidMessages(snapshot4.messages);
|
|
5782
5939
|
const legacySummary = snapshot4.summary?.role === "system" && snapshot4.summary.content.startsWith("Conversation summary\n") ? snapshot4.summary.content.slice("Conversation summary\n".length) : void 0;
|
|
5783
5940
|
this.#compact = snapshot4.compact === void 0 ? legacySummary === void 0 ? void 0 : { summary: legacySummary, compactedAt: (/* @__PURE__ */ new Date(0)).toISOString() } : { ...snapshot4.compact };
|
|
5941
|
+
if (snapshot4.epoch !== void 0) this.#epoch = cloneEpoch(snapshot4.epoch);
|
|
5942
|
+
this.#visibilityLog = snapshot4.visibilityLog?.map((item) => ({ ...item })) ?? [];
|
|
5943
|
+
this.#activeTransientSystem.clear();
|
|
5784
5944
|
this.#messages = messages.map((message2) => message2.role === "tool" ? { ...message2, content: truncateToolOutput(message2.content, this.#toolOutputChars) } : cloneMessage2(message2));
|
|
5785
5945
|
this.#lastRecordedInputTokens = void 0;
|
|
5786
5946
|
this.#estimatedTokensAtLastRecordedUsage = void 0;
|
|
@@ -5798,7 +5958,8 @@ var ContextManager = class _ContextManager {
|
|
|
5798
5958
|
content: compactContinuationMessage(this.#compact.summary),
|
|
5799
5959
|
...this.#forkCompactBoundary ? { cacheBreakpoint: true } : {}
|
|
5800
5960
|
}],
|
|
5801
|
-
...this.#messages.map(cloneMessage2)
|
|
5961
|
+
...this.#messages.map(cloneMessage2),
|
|
5962
|
+
...[...this.#activeTransientSystem.values()].map(cloneMessage2)
|
|
5802
5963
|
];
|
|
5803
5964
|
}
|
|
5804
5965
|
estimatedTokens() {
|
|
@@ -5821,6 +5982,7 @@ var ContextManager = class _ContextManager {
|
|
|
5821
5982
|
}
|
|
5822
5983
|
async prepareForModelCall(signal = new AbortController().signal) {
|
|
5823
5984
|
signal.throwIfAborted();
|
|
5985
|
+
this.refreshContextSources();
|
|
5824
5986
|
if (!this.needsCompaction()) return false;
|
|
5825
5987
|
const originalMessages = this.#messages;
|
|
5826
5988
|
const originalRecordedUsage = this.#lastRecordedInputTokens;
|
|
@@ -5914,6 +6076,7 @@ var ContextManager = class _ContextManager {
|
|
|
5914
6076
|
this.#reportCompactProgress(90);
|
|
5915
6077
|
this.#compact = nextCompact;
|
|
5916
6078
|
this.#messages = nextMessages.map(cloneMessage2);
|
|
6079
|
+
this.#epoch = this.#createEpoch();
|
|
5917
6080
|
this.#lastRecordedInputTokens = void 0;
|
|
5918
6081
|
this.#estimatedTokensAtLastRecordedUsage = void 0;
|
|
5919
6082
|
this.#reportCompactProgress(100);
|
|
@@ -5935,11 +6098,33 @@ var ContextManager = class _ContextManager {
|
|
|
5935
6098
|
return Math.max(estimated, this.#lastRecordedInputTokens + appendedEstimate);
|
|
5936
6099
|
}
|
|
5937
6100
|
#pinnedMessages() {
|
|
5938
|
-
|
|
6101
|
+
return [
|
|
6102
|
+
...this.#epoch.stableMessages.map(cloneMessage2),
|
|
6103
|
+
...sourceMessages(this.#epoch.sources)
|
|
6104
|
+
];
|
|
6105
|
+
}
|
|
6106
|
+
#resolvedUserMemory() {
|
|
6107
|
+
const message2 = this.#epoch.stableMessages.find((item) => item.content.startsWith("User memory\n"));
|
|
6108
|
+
return message2?.content.slice("User memory\n".length);
|
|
6109
|
+
}
|
|
6110
|
+
#createEpoch() {
|
|
6111
|
+
const stableMessages = this.#buildStableMessages();
|
|
6112
|
+
const sources = this.#resolvedDynamicSources();
|
|
6113
|
+
return {
|
|
6114
|
+
version: 1,
|
|
6115
|
+
id: randomUUID6(),
|
|
6116
|
+
startedAt: (/* @__PURE__ */ new Date()).toISOString(),
|
|
6117
|
+
stableMessages,
|
|
6118
|
+
sources,
|
|
6119
|
+
stableSourceHash: hashMessages(stableMessages)
|
|
6120
|
+
};
|
|
6121
|
+
}
|
|
6122
|
+
#buildStableMessages(fallback = []) {
|
|
6123
|
+
const resolvedUserMemory = tryResolveOptionalSystemSections(this.#userMemory);
|
|
6124
|
+
const fallbackUserMemory = fallback.find((item) => item.content.startsWith("User memory\n"));
|
|
6125
|
+
const userMemory = resolvedUserMemory.available ? resolvedUserMemory.sections?.join("\n\n") : fallbackUserMemory?.content.slice("User memory\n".length);
|
|
5939
6126
|
const stable = [
|
|
5940
6127
|
...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
6128
|
...this.#flavor === void 0 ? [] : [{
|
|
5944
6129
|
role: "system",
|
|
5945
6130
|
content: `FLAVOR.md
|
|
@@ -5949,29 +6134,87 @@ ${this.#flavor}`,
|
|
|
5949
6134
|
...this.#workspaceInstructions === void 0 ? [] : [{
|
|
5950
6135
|
role: "system",
|
|
5951
6136
|
content: `Workspace instructions
|
|
5952
|
-
${this.#workspaceInstructions}
|
|
5953
|
-
cacheBreakpoint: true
|
|
6137
|
+
${this.#workspaceInstructions}`
|
|
5954
6138
|
}],
|
|
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 }))
|
|
6139
|
+
...userMemory === void 0 || userMemory.length === 0 ? [] : [{
|
|
6140
|
+
role: "system",
|
|
6141
|
+
content: `User memory
|
|
6142
|
+
${userMemory}`,
|
|
6143
|
+
cacheBreakpoint: true
|
|
6144
|
+
}]
|
|
5965
6145
|
];
|
|
5966
|
-
if (
|
|
5967
|
-
|
|
5968
|
-
|
|
5969
|
-
|
|
5970
|
-
|
|
5971
|
-
|
|
5972
|
-
|
|
6146
|
+
if (stable.length > 0 && (userMemory === void 0 || userMemory.length === 0)) {
|
|
6147
|
+
stable[stable.length - 1].cacheBreakpoint = true;
|
|
6148
|
+
}
|
|
6149
|
+
return stable;
|
|
6150
|
+
}
|
|
6151
|
+
#resolvedDynamicSources(previous = {}) {
|
|
6152
|
+
const result = {};
|
|
6153
|
+
if (this.#memory !== void 0) result["long-term-memory"] = this.#memory;
|
|
6154
|
+
if (this.#taskState !== void 0) result["task-state"] = this.#taskState;
|
|
6155
|
+
const volatile = tryResolveOptionalSystemSections(this.#volatileSystem);
|
|
6156
|
+
if (!volatile.available && previous["runtime"] !== void 0) result["runtime"] = previous["runtime"];
|
|
6157
|
+
else if (volatile.sections !== void 0 && volatile.sections.length > 0) result["runtime"] = JSON.stringify(volatile.sections);
|
|
6158
|
+
return result;
|
|
5973
6159
|
}
|
|
5974
6160
|
};
|
|
6161
|
+
var DYNAMIC_SOURCE_ORDER = ["long-term-memory", "task-state", "runtime"];
|
|
6162
|
+
function sourceMessages(sources) {
|
|
6163
|
+
return DYNAMIC_SOURCE_ORDER.flatMap((key) => {
|
|
6164
|
+
const content = sources[key];
|
|
6165
|
+
if (content === void 0) return [];
|
|
6166
|
+
if (key === "runtime") {
|
|
6167
|
+
try {
|
|
6168
|
+
const sections = JSON.parse(content);
|
|
6169
|
+
if (Array.isArray(sections) && sections.every((item) => typeof item === "string")) {
|
|
6170
|
+
return sections.map((item) => ({ role: "system", content: item }));
|
|
6171
|
+
}
|
|
6172
|
+
} catch {
|
|
6173
|
+
}
|
|
6174
|
+
}
|
|
6175
|
+
const label = key === "long-term-memory" ? "Long-term memory" : key === "task-state" ? "Task state" : "Runtime";
|
|
6176
|
+
return [{
|
|
6177
|
+
role: "system",
|
|
6178
|
+
content: `${label}
|
|
6179
|
+
${content}`
|
|
6180
|
+
}];
|
|
6181
|
+
});
|
|
6182
|
+
}
|
|
6183
|
+
function displaySourceValue(source, value) {
|
|
6184
|
+
if (source !== "runtime") return value;
|
|
6185
|
+
try {
|
|
6186
|
+
const sections = JSON.parse(value);
|
|
6187
|
+
if (Array.isArray(sections) && sections.every((item) => typeof item === "string")) return sections.join("\n\n");
|
|
6188
|
+
} catch {
|
|
6189
|
+
}
|
|
6190
|
+
return value;
|
|
6191
|
+
}
|
|
6192
|
+
function contextUpdateMessage(source, content) {
|
|
6193
|
+
return { role: "system", content: `Context update [${source}]
|
|
6194
|
+
${content}` };
|
|
6195
|
+
}
|
|
6196
|
+
function cloneEpoch(epoch) {
|
|
6197
|
+
return {
|
|
6198
|
+
...epoch,
|
|
6199
|
+
stableMessages: epoch.stableMessages.map((message2) => ({
|
|
6200
|
+
role: "system",
|
|
6201
|
+
content: message2.content,
|
|
6202
|
+
...message2.cacheBreakpoint === void 0 ? {} : { cacheBreakpoint: message2.cacheBreakpoint }
|
|
6203
|
+
})),
|
|
6204
|
+
sources: { ...epoch.sources }
|
|
6205
|
+
};
|
|
6206
|
+
}
|
|
6207
|
+
function hashMessages(messages) {
|
|
6208
|
+
return createHash4("sha256").update(JSON.stringify(messages)).digest("hex");
|
|
6209
|
+
}
|
|
6210
|
+
function tryResolveOptionalSystemSections(source) {
|
|
6211
|
+
if (source === void 0) return { available: true };
|
|
6212
|
+
try {
|
|
6213
|
+
return { available: true, sections: resolveSystemSections(source) };
|
|
6214
|
+
} catch {
|
|
6215
|
+
return { available: false };
|
|
6216
|
+
}
|
|
6217
|
+
}
|
|
5975
6218
|
function resolveSystemSections(source) {
|
|
5976
6219
|
const resolved = typeof source === "function" ? source() : source;
|
|
5977
6220
|
const sections = typeof resolved === "string" ? [resolved] : resolved;
|
|
@@ -6036,7 +6279,10 @@ function providerValidMessages(input) {
|
|
|
6036
6279
|
const availableResults = new Set(input.filter((message2) => message2.role === "tool" && message2.toolCallId).map((message2) => message2.toolCallId));
|
|
6037
6280
|
const output = [];
|
|
6038
6281
|
for (const original of input) {
|
|
6039
|
-
if (original.role === "system")
|
|
6282
|
+
if (original.role === "system") {
|
|
6283
|
+
output.push(cloneMessage2(original));
|
|
6284
|
+
continue;
|
|
6285
|
+
}
|
|
6040
6286
|
if (original.role === "assistant" && original.toolCalls !== void 0) {
|
|
6041
6287
|
const calls = original.toolCalls.filter((call) => call.id && call.name && availableResults.has(call.id));
|
|
6042
6288
|
calls.forEach((call) => announced.add(call.id));
|
|
@@ -6055,7 +6301,7 @@ function providerValidMessages(input) {
|
|
|
6055
6301
|
}
|
|
6056
6302
|
|
|
6057
6303
|
// src/context/workspace-instructions.ts
|
|
6058
|
-
import { createHash as
|
|
6304
|
+
import { createHash as createHash5 } from "crypto";
|
|
6059
6305
|
import { lstat as lstat4, readFile as readFile6, realpath as realpath2 } from "fs/promises";
|
|
6060
6306
|
import { dirname as dirname9, isAbsolute as isAbsolute4, relative as relative6, resolve as resolve6, sep as sep6 } from "path";
|
|
6061
6307
|
var NAMES = ["AGENTS.md", "CLAUDE.md", "AGENTS.local.md", "CLAUDE.local.md"];
|
|
@@ -6130,7 +6376,7 @@ var WorkspaceInstructions = class {
|
|
|
6130
6376
|
path,
|
|
6131
6377
|
depth: directory.depth,
|
|
6132
6378
|
content,
|
|
6133
|
-
digest:
|
|
6379
|
+
digest: createHash5("sha256").update(bytes).digest("hex")
|
|
6134
6380
|
});
|
|
6135
6381
|
} catch {
|
|
6136
6382
|
}
|
|
@@ -6543,10 +6789,10 @@ function sanitize(message2, secrets) {
|
|
|
6543
6789
|
}
|
|
6544
6790
|
function waitForRetry(delayMs, signal) {
|
|
6545
6791
|
if (signal?.aborted) return Promise.reject(signal.reason);
|
|
6546
|
-
return new Promise((
|
|
6792
|
+
return new Promise((resolve28, reject) => {
|
|
6547
6793
|
const timer = setTimeout(() => {
|
|
6548
6794
|
signal?.removeEventListener("abort", onAbort);
|
|
6549
|
-
|
|
6795
|
+
resolve28();
|
|
6550
6796
|
}, delayMs);
|
|
6551
6797
|
const onAbort = () => {
|
|
6552
6798
|
clearTimeout(timer);
|
|
@@ -6618,6 +6864,15 @@ var AgentLoop = class {
|
|
|
6618
6864
|
this.#iterationLimitMode = mode;
|
|
6619
6865
|
}
|
|
6620
6866
|
async *run(request) {
|
|
6867
|
+
const transientId = request.additionalContext === void 0 ? void 0 : this.#options.context.beginTransientSystem(request.additionalContext);
|
|
6868
|
+
try {
|
|
6869
|
+
yield* this.#runInternal(request);
|
|
6870
|
+
} finally {
|
|
6871
|
+
if (transientId !== void 0) this.#options.context.endTransientSystem(transientId);
|
|
6872
|
+
}
|
|
6873
|
+
}
|
|
6874
|
+
async *#runInternal(request) {
|
|
6875
|
+
this.#options.context.refreshContextSources();
|
|
6621
6876
|
this.#options.context.append(request.initialUserMessage ?? { role: "user", content: request.prompt });
|
|
6622
6877
|
let totalInputTokens = 0;
|
|
6623
6878
|
let totalOutputTokens = 0;
|
|
@@ -6695,10 +6950,7 @@ var AgentLoop = class {
|
|
|
6695
6950
|
const { adapter, model } = resolved;
|
|
6696
6951
|
const modelRequest = {
|
|
6697
6952
|
model,
|
|
6698
|
-
messages:
|
|
6699
|
-
...this.#options.context.messagesForModel(),
|
|
6700
|
-
...request.additionalContext ? [{ role: "system", content: request.additionalContext }] : []
|
|
6701
|
-
],
|
|
6953
|
+
messages: this.#options.context.messagesForModel(),
|
|
6702
6954
|
tools: [...this.#options.tools],
|
|
6703
6955
|
...request.signal === void 0 ? {} : { signal: request.signal }
|
|
6704
6956
|
};
|
|
@@ -6724,6 +6976,13 @@ var AgentLoop = class {
|
|
|
6724
6976
|
let providerError = false;
|
|
6725
6977
|
let usage;
|
|
6726
6978
|
const modelCallId = String(++modelInvocation);
|
|
6979
|
+
const journalModelId = this.#options.modelJournal?.start({
|
|
6980
|
+
agent: this.#options.agent,
|
|
6981
|
+
model: attemptedModelId,
|
|
6982
|
+
iteration,
|
|
6983
|
+
attempt,
|
|
6984
|
+
messages: modelRequest.messages
|
|
6985
|
+
});
|
|
6727
6986
|
yield { type: "model-start", id: modelCallId };
|
|
6728
6987
|
try {
|
|
6729
6988
|
for await (const event of adapter.stream(modelRequest)) {
|
|
@@ -6794,6 +7053,9 @@ var AgentLoop = class {
|
|
|
6794
7053
|
providerError = false;
|
|
6795
7054
|
}
|
|
6796
7055
|
}
|
|
7056
|
+
if (journalModelId !== void 0) {
|
|
7057
|
+
this.#options.modelJournal?.complete(journalModelId, completed, terminalError?.message);
|
|
7058
|
+
}
|
|
6797
7059
|
}
|
|
6798
7060
|
yield { type: "model-end", id: modelCallId };
|
|
6799
7061
|
if (usage !== void 0) {
|
|
@@ -7132,10 +7394,10 @@ function serializeToolInput(input) {
|
|
|
7132
7394
|
}
|
|
7133
7395
|
function waitForRetry2(delayMs, signal) {
|
|
7134
7396
|
if (signal?.aborted) return Promise.reject(signal.reason);
|
|
7135
|
-
return new Promise((
|
|
7397
|
+
return new Promise((resolve28, reject) => {
|
|
7136
7398
|
const timer = setTimeout(() => {
|
|
7137
7399
|
signal?.removeEventListener("abort", onAbort);
|
|
7138
|
-
|
|
7400
|
+
resolve28();
|
|
7139
7401
|
}, delayMs);
|
|
7140
7402
|
const onAbort = () => {
|
|
7141
7403
|
clearTimeout(timer);
|
|
@@ -7211,7 +7473,7 @@ function redactClassifierText(value) {
|
|
|
7211
7473
|
}
|
|
7212
7474
|
|
|
7213
7475
|
// src/tools/runtime.ts
|
|
7214
|
-
import { randomUUID as
|
|
7476
|
+
import { randomUUID as randomUUID7 } from "crypto";
|
|
7215
7477
|
import { mkdir as mkdir7, writeFile as writeFile4 } from "fs/promises";
|
|
7216
7478
|
import { basename as basename2, join as join7, resolve as resolve7 } from "path";
|
|
7217
7479
|
import { z as z9 } from "zod";
|
|
@@ -7252,6 +7514,7 @@ var ToolRuntime = class {
|
|
|
7252
7514
|
#workspace;
|
|
7253
7515
|
#outputLimits;
|
|
7254
7516
|
#afterSuccess;
|
|
7517
|
+
#journal;
|
|
7255
7518
|
#turnOutputChars = 0;
|
|
7256
7519
|
#disposed = false;
|
|
7257
7520
|
constructor(options) {
|
|
@@ -7264,6 +7527,7 @@ var ToolRuntime = class {
|
|
|
7264
7527
|
this.#workspace = resolve7(options.workspace ?? process.cwd());
|
|
7265
7528
|
this.#outputLimits = { ...DEFAULT_TOOL_OUTPUT_LIMITS, ...options.outputLimits };
|
|
7266
7529
|
this.#afterSuccess = options.afterSuccess;
|
|
7530
|
+
this.#journal = options.journal;
|
|
7267
7531
|
validateOutputLimit("perToolChars", this.#outputLimits.perToolChars);
|
|
7268
7532
|
validateOutputLimit("perTurnChars", this.#outputLimits.perTurnChars);
|
|
7269
7533
|
this.#disposeSchemas = [
|
|
@@ -7336,6 +7600,18 @@ var ToolRuntime = class {
|
|
|
7336
7600
|
}
|
|
7337
7601
|
}
|
|
7338
7602
|
async execute(call, context) {
|
|
7603
|
+
const definition = this.#tools.get(call.name);
|
|
7604
|
+
const journalId = definition === void 0 || this.#journal === void 0 ? void 0 : this.#journal.start(call.name, call.input, definition.retrySafe === true || definition.readOnly === true);
|
|
7605
|
+
try {
|
|
7606
|
+
const result = await this.#execute(call, context);
|
|
7607
|
+
if (journalId !== void 0) this.#journal?.complete(journalId, result);
|
|
7608
|
+
return result;
|
|
7609
|
+
} catch (error) {
|
|
7610
|
+
if (journalId !== void 0) this.#journal?.interrupt(journalId, message(error));
|
|
7611
|
+
throw error;
|
|
7612
|
+
}
|
|
7613
|
+
}
|
|
7614
|
+
async #execute(call, context) {
|
|
7339
7615
|
const tool = this.#tools.get(call.name);
|
|
7340
7616
|
if (tool === void 0) return { ok: false, error: { code: "unknown_tool", message: `Unknown tool: ${call.name}` } };
|
|
7341
7617
|
const signal = context.signal ?? new AbortController().signal;
|
|
@@ -7480,7 +7756,7 @@ var ToolRuntime = class {
|
|
|
7480
7756
|
this.#turnOutputChars += preview.length;
|
|
7481
7757
|
const directory = join7(this.#workspace, ".flavor", "tool-results");
|
|
7482
7758
|
await mkdir7(directory, { recursive: true });
|
|
7483
|
-
const filename2 = `${(/* @__PURE__ */ new Date()).toISOString().replaceAll(":", "-")}-${
|
|
7759
|
+
const filename2 = `${(/* @__PURE__ */ new Date()).toISOString().replaceAll(":", "-")}-${randomUUID7()}-${safeFilename(tool)}.txt`;
|
|
7484
7760
|
const savedTo = join7(directory, filename2);
|
|
7485
7761
|
await writeFile4(savedTo, serialized, "utf8");
|
|
7486
7762
|
return {
|
|
@@ -7505,8 +7781,8 @@ var ToolRuntime = class {
|
|
|
7505
7781
|
const limited = await this.#limitOutput(tool, content);
|
|
7506
7782
|
return typeof limited === "string" ? limited : serializeToolOutput(limited);
|
|
7507
7783
|
}
|
|
7508
|
-
async #fail(tool, input, agent, code,
|
|
7509
|
-
const result = { ok: false, error: { code, message:
|
|
7784
|
+
async #fail(tool, input, agent, code, errorMessage2) {
|
|
7785
|
+
const result = { ok: false, error: { code, message: errorMessage2 } };
|
|
7510
7786
|
try {
|
|
7511
7787
|
await this.#hooks.emit({
|
|
7512
7788
|
version: 1,
|
|
@@ -7657,6 +7933,7 @@ var LocalHarness = class {
|
|
|
7657
7933
|
workspace: this.#options.workspace,
|
|
7658
7934
|
...this.#options.afterToolSuccess === void 0 ? {} : { afterSuccess: this.#options.afterToolSuccess },
|
|
7659
7935
|
profile: this.#permissionProfile,
|
|
7936
|
+
...this.#options.permissionPolicy === void 0 ? {} : { policy: this.#options.permissionPolicy },
|
|
7660
7937
|
mode: this.#options.loopMode ? "bypassPermissions" : agent === "subagent" ? this.#mainPermissions.mode === "plan" ? "plan" : "bubble" : this.#options.permissionMode ?? "default"
|
|
7661
7938
|
});
|
|
7662
7939
|
if (agent === "main") this.#mainPermissions = permissions;
|
|
@@ -7671,7 +7948,8 @@ var LocalHarness = class {
|
|
|
7671
7948
|
registry: this.#options.registry,
|
|
7672
7949
|
modelId: () => this.#subagentModelId
|
|
7673
7950
|
})
|
|
7674
|
-
} : {}
|
|
7951
|
+
} : {},
|
|
7952
|
+
...this.#options.toolJournal === void 0 ? {} : { journal: this.#options.toolJournal }
|
|
7675
7953
|
});
|
|
7676
7954
|
try {
|
|
7677
7955
|
const tools = definitions.map(toModelTool);
|
|
@@ -7689,7 +7967,8 @@ var LocalHarness = class {
|
|
|
7689
7967
|
ownerId,
|
|
7690
7968
|
...maxIterations === void 0 ? {} : { maxIterations },
|
|
7691
7969
|
...isMain && this.#options.hasActiveProgress !== void 0 ? { hasActiveProgress: this.#options.hasActiveProgress } : {},
|
|
7692
|
-
...isMain && this.#options.hallucinationGuard !== void 0 ? { hallucinationGuard: this.#options.hallucinationGuard } : {}
|
|
7970
|
+
...isMain && this.#options.hallucinationGuard !== void 0 ? { hallucinationGuard: this.#options.hallucinationGuard } : {},
|
|
7971
|
+
...this.#options.modelJournal === void 0 ? {} : { modelJournal: this.#options.modelJournal }
|
|
7693
7972
|
});
|
|
7694
7973
|
loop.setIterationLimitMode(this.#permissionProfile === "d2c" ? "d2c" : "standard");
|
|
7695
7974
|
return { get modelId() {
|
|
@@ -7714,11 +7993,306 @@ function toModelTool(tool) {
|
|
|
7714
7993
|
return tool.modelStrict === void 0 ? modelTool : { ...modelTool, strict: tool.modelStrict };
|
|
7715
7994
|
}
|
|
7716
7995
|
|
|
7996
|
+
// src/harness/journal.ts
|
|
7997
|
+
import { createHash as createHash6, randomUUID as randomUUID8 } from "crypto";
|
|
7998
|
+
import { closeSync, constants, existsSync as existsSync2, fsyncSync, mkdirSync as mkdirSync2, openSync, readFileSync as readFileSync3, realpathSync as realpathSync2, statSync, truncateSync, writeSync } from "fs";
|
|
7999
|
+
import { join as join8, relative as relative7, resolve as resolve8, sep as sep7 } from "path";
|
|
8000
|
+
import { z as z10 } from "zod";
|
|
8001
|
+
|
|
8002
|
+
// src/harness/invariants.ts
|
|
8003
|
+
function harnessInvariantViolations(records) {
|
|
8004
|
+
const violations = [];
|
|
8005
|
+
const queues = /* @__PURE__ */ new Set();
|
|
8006
|
+
const tools = /* @__PURE__ */ new Set();
|
|
8007
|
+
const models = /* @__PURE__ */ new Set();
|
|
8008
|
+
const turns = /* @__PURE__ */ new Set();
|
|
8009
|
+
records.forEach((record, index) => {
|
|
8010
|
+
if (record.sequence !== index + 1) violations.push(`sequence ${record.sequence} is not ${index + 1}`);
|
|
8011
|
+
const id = typeof record.payload.id === "string" ? record.payload.id : "";
|
|
8012
|
+
if (record.type === "queue.admitted") {
|
|
8013
|
+
if (queues.has(id)) violations.push(`queue ${id} was admitted twice`);
|
|
8014
|
+
queues.add(id);
|
|
8015
|
+
} else if (record.type === "queue.claimed" || record.type === "queue.released") {
|
|
8016
|
+
if (!queues.has(id)) violations.push(`${record.type} references unknown queue ${id}`);
|
|
8017
|
+
} else if (record.type === "queue.acked") {
|
|
8018
|
+
if (!queues.delete(id)) violations.push(`queue.acked references unknown queue ${id}`);
|
|
8019
|
+
} else if (record.type === "tool.started") {
|
|
8020
|
+
if (tools.has(id)) violations.push(`tool ${id} was started twice`);
|
|
8021
|
+
tools.add(id);
|
|
8022
|
+
} else if (record.type === "tool.completed" || record.type === "tool.interrupted") {
|
|
8023
|
+
if (!tools.delete(id)) violations.push(`${record.type} references unknown tool ${id}`);
|
|
8024
|
+
} else if (record.type === "model.requested") {
|
|
8025
|
+
if (models.has(id)) violations.push(`model ${id} was requested twice`);
|
|
8026
|
+
if (!Array.isArray(record.payload.messages)) violations.push(`model ${id} has no replayable messages`);
|
|
8027
|
+
models.add(id);
|
|
8028
|
+
} else if (record.type === "model.completed") {
|
|
8029
|
+
if (!models.delete(id)) violations.push(`model.completed references unknown model ${id}`);
|
|
8030
|
+
} else if (record.type === "turn.started") {
|
|
8031
|
+
if (turns.has(id)) violations.push(`turn ${id} was started twice`);
|
|
8032
|
+
if (!("prompt" in record.payload)) violations.push(`turn ${id} has no replayable prompt`);
|
|
8033
|
+
turns.add(id);
|
|
8034
|
+
} else if (record.type === "turn.completed" || record.type === "turn.interrupted") {
|
|
8035
|
+
if (!turns.delete(id)) violations.push(`${record.type} references unknown turn ${id}`);
|
|
8036
|
+
}
|
|
8037
|
+
});
|
|
8038
|
+
return violations;
|
|
8039
|
+
}
|
|
8040
|
+
function assertHarnessInvariants(records) {
|
|
8041
|
+
const violations = harnessInvariantViolations(records);
|
|
8042
|
+
if (violations.length > 0) throw new Error(`Harness invariant violation: ${violations.join("; ")}`);
|
|
8043
|
+
}
|
|
8044
|
+
|
|
8045
|
+
// src/harness/journal.ts
|
|
8046
|
+
var HARNESS_JOURNAL_VERSION = 1;
|
|
8047
|
+
var MAX_JOURNAL_BYTES = 32 * 1024 * 1024;
|
|
8048
|
+
var QueueKindSchema = z10.enum(["steer", "followUp"]);
|
|
8049
|
+
var EventTypeSchema = z10.enum([
|
|
8050
|
+
"queue.admitted",
|
|
8051
|
+
"queue.claimed",
|
|
8052
|
+
"queue.acked",
|
|
8053
|
+
"queue.released",
|
|
8054
|
+
"turn.started",
|
|
8055
|
+
"turn.completed",
|
|
8056
|
+
"turn.interrupted",
|
|
8057
|
+
"model.requested",
|
|
8058
|
+
"model.completed",
|
|
8059
|
+
"tool.started",
|
|
8060
|
+
"tool.completed",
|
|
8061
|
+
"tool.interrupted",
|
|
8062
|
+
"savepoint.created",
|
|
8063
|
+
"recovery.completed"
|
|
8064
|
+
]);
|
|
8065
|
+
var JournalRecordSchema = z10.object({
|
|
8066
|
+
version: z10.literal(HARNESS_JOURNAL_VERSION),
|
|
8067
|
+
sequence: z10.number().int().positive(),
|
|
8068
|
+
id: z10.string().uuid(),
|
|
8069
|
+
timestamp: z10.string().datetime({ offset: true }),
|
|
8070
|
+
type: EventTypeSchema,
|
|
8071
|
+
payload: z10.record(z10.string(), z10.unknown()),
|
|
8072
|
+
previousHash: z10.string().regex(/^[a-f0-9]{64}$/).nullable(),
|
|
8073
|
+
hash: z10.string().regex(/^[a-f0-9]{64}$/)
|
|
8074
|
+
}).strict();
|
|
8075
|
+
var HarnessJournal = class {
|
|
8076
|
+
#path;
|
|
8077
|
+
#now;
|
|
8078
|
+
#maxBytes;
|
|
8079
|
+
#sequence = 0;
|
|
8080
|
+
#lastHash = null;
|
|
8081
|
+
#records = [];
|
|
8082
|
+
constructor(options) {
|
|
8083
|
+
const workspace = resolve8(options.workspace);
|
|
8084
|
+
const directory = join8(workspace, ".flavor", "sessions");
|
|
8085
|
+
assertSessionId(options.sessionId);
|
|
8086
|
+
mkdirSync2(directory, { recursive: true, mode: 448 });
|
|
8087
|
+
assertWithin(workspace, directory);
|
|
8088
|
+
const canonicalWorkspace = realpathSync2.native(workspace);
|
|
8089
|
+
const canonicalDirectory = realpathSync2.native(directory);
|
|
8090
|
+
if (!isWithin2(canonicalWorkspace, canonicalDirectory)) throw new Error("Harness journal directory escapes the workspace");
|
|
8091
|
+
this.#path = join8(canonicalDirectory, `${options.sessionId}.events.jsonl`);
|
|
8092
|
+
this.#now = options.now ?? (() => (/* @__PURE__ */ new Date()).toISOString());
|
|
8093
|
+
this.#maxBytes = options.maxBytes ?? MAX_JOURNAL_BYTES;
|
|
8094
|
+
if (!Number.isSafeInteger(this.#maxBytes) || this.#maxBytes < 1024) throw new Error("maxBytes must be an integer of at least 1024");
|
|
8095
|
+
this.#load();
|
|
8096
|
+
}
|
|
8097
|
+
get path() {
|
|
8098
|
+
return this.#path;
|
|
8099
|
+
}
|
|
8100
|
+
get records() {
|
|
8101
|
+
return this.#records.map((record) => structuredClone(record));
|
|
8102
|
+
}
|
|
8103
|
+
admitQueue(kind, payload) {
|
|
8104
|
+
QueueKindSchema.parse(kind);
|
|
8105
|
+
const id = randomUUID8();
|
|
8106
|
+
this.#append("queue.admitted", { id, kind, payload });
|
|
8107
|
+
return id;
|
|
8108
|
+
}
|
|
8109
|
+
claimQueue(id) {
|
|
8110
|
+
this.#append("queue.claimed", { id });
|
|
8111
|
+
}
|
|
8112
|
+
ackQueue(id) {
|
|
8113
|
+
this.#append("queue.acked", { id });
|
|
8114
|
+
}
|
|
8115
|
+
releaseQueue(id, reason) {
|
|
8116
|
+
this.#append("queue.released", { id, reason });
|
|
8117
|
+
}
|
|
8118
|
+
startTurn(config, prompt) {
|
|
8119
|
+
const id = randomUUID8();
|
|
8120
|
+
this.#append("turn.started", { id, config: structuredClone(config), configHash: hashJson(config), prompt: structuredClone(prompt), promptHash: hashJson(prompt) });
|
|
8121
|
+
return id;
|
|
8122
|
+
}
|
|
8123
|
+
completeTurn(id) {
|
|
8124
|
+
this.#append("turn.completed", { id });
|
|
8125
|
+
}
|
|
8126
|
+
interruptTurn(id, reason) {
|
|
8127
|
+
this.#append("turn.interrupted", { id, reason });
|
|
8128
|
+
}
|
|
8129
|
+
startModel(input) {
|
|
8130
|
+
const id = randomUUID8();
|
|
8131
|
+
this.#append("model.requested", { id, ...structuredClone(input), messagesHash: hashJson(input.messages) });
|
|
8132
|
+
return id;
|
|
8133
|
+
}
|
|
8134
|
+
completeModel(id, completed, error) {
|
|
8135
|
+
this.#append("model.completed", { id, completed, ...error === void 0 ? {} : { error } });
|
|
8136
|
+
}
|
|
8137
|
+
startTool(tool, input, retrySafe) {
|
|
8138
|
+
const id = randomUUID8();
|
|
8139
|
+
this.#append("tool.started", { id, tool, input: structuredClone(input), inputHash: hashJson(input), retrySafe });
|
|
8140
|
+
return id;
|
|
8141
|
+
}
|
|
8142
|
+
completeTool(id, result) {
|
|
8143
|
+
this.#append("tool.completed", { id, result: structuredClone(result) });
|
|
8144
|
+
}
|
|
8145
|
+
interruptTool(id, reason) {
|
|
8146
|
+
this.#append("tool.interrupted", { id, reason });
|
|
8147
|
+
}
|
|
8148
|
+
savepoint(phase, config) {
|
|
8149
|
+
const id = randomUUID8();
|
|
8150
|
+
this.#append("savepoint.created", { id, phase, configHash: hashJson(config) });
|
|
8151
|
+
return id;
|
|
8152
|
+
}
|
|
8153
|
+
recover() {
|
|
8154
|
+
const queue = /* @__PURE__ */ new Map();
|
|
8155
|
+
const claimed = /* @__PURE__ */ new Set();
|
|
8156
|
+
const tools = /* @__PURE__ */ new Map();
|
|
8157
|
+
const models = /* @__PURE__ */ new Set();
|
|
8158
|
+
const turns = /* @__PURE__ */ new Set();
|
|
8159
|
+
let lastSavepoint;
|
|
8160
|
+
for (const record of this.#records) {
|
|
8161
|
+
const payload = record.payload;
|
|
8162
|
+
const id = typeof payload.id === "string" ? payload.id : "";
|
|
8163
|
+
if (record.type === "queue.admitted") {
|
|
8164
|
+
const kind = QueueKindSchema.parse(payload.kind);
|
|
8165
|
+
queue.set(id, { id, kind, payload: payload.payload, admittedAt: record.timestamp, recovered: false });
|
|
8166
|
+
} else if (record.type === "queue.claimed") claimed.add(id);
|
|
8167
|
+
else if (record.type === "queue.acked") {
|
|
8168
|
+
queue.delete(id);
|
|
8169
|
+
claimed.delete(id);
|
|
8170
|
+
} else if (record.type === "queue.released") claimed.delete(id);
|
|
8171
|
+
else if (record.type === "turn.started") turns.add(id);
|
|
8172
|
+
else if (record.type === "turn.completed" || record.type === "turn.interrupted") turns.delete(id);
|
|
8173
|
+
else if (record.type === "model.requested") models.add(id);
|
|
8174
|
+
else if (record.type === "model.completed") models.delete(id);
|
|
8175
|
+
else if (record.type === "tool.started") tools.set(id, {
|
|
8176
|
+
id,
|
|
8177
|
+
tool: String(payload.tool ?? "unknown"),
|
|
8178
|
+
retrySafe: payload.retrySafe === true,
|
|
8179
|
+
inputHash: String(payload.inputHash ?? "")
|
|
8180
|
+
});
|
|
8181
|
+
else if (record.type === "tool.completed" || record.type === "tool.interrupted") tools.delete(id);
|
|
8182
|
+
else if (record.type === "savepoint.created") lastSavepoint = {
|
|
8183
|
+
id,
|
|
8184
|
+
phase: String(payload.phase ?? "unknown"),
|
|
8185
|
+
configHash: String(payload.configHash ?? "")
|
|
8186
|
+
};
|
|
8187
|
+
}
|
|
8188
|
+
const recovered = [...queue.values()].map((item) => ({ ...item, recovered: claimed.has(item.id) }));
|
|
8189
|
+
return {
|
|
8190
|
+
queue: recovered,
|
|
8191
|
+
incompleteTools: [...tools.values()],
|
|
8192
|
+
incompleteModelIds: [...models],
|
|
8193
|
+
interruptedTurnIds: [...turns],
|
|
8194
|
+
...lastSavepoint === void 0 ? {} : { lastSavepoint }
|
|
8195
|
+
};
|
|
8196
|
+
}
|
|
8197
|
+
markRecoveryComplete(recovery) {
|
|
8198
|
+
for (const item of recovery.queue) if (item.recovered) this.releaseQueue(item.id, "process-recovery");
|
|
8199
|
+
for (const tool of recovery.incompleteTools) {
|
|
8200
|
+
this.interruptTool(tool.id, tool.retrySafe ? "interrupted by process exit; eligible only for explicit retry" : "interrupted by process exit; automatic replay forbidden");
|
|
8201
|
+
}
|
|
8202
|
+
for (const id of recovery.incompleteModelIds) this.completeModel(id, false, "interrupted by process exit");
|
|
8203
|
+
for (const id of recovery.interruptedTurnIds) this.interruptTurn(id, "interrupted by process exit");
|
|
8204
|
+
this.#append("recovery.completed", {
|
|
8205
|
+
queueItems: recovery.queue.length,
|
|
8206
|
+
interruptedTools: recovery.incompleteTools.length,
|
|
8207
|
+
interruptedModels: recovery.incompleteModelIds.length,
|
|
8208
|
+
interruptedTurns: recovery.interruptedTurnIds.length
|
|
8209
|
+
});
|
|
8210
|
+
}
|
|
8211
|
+
#load() {
|
|
8212
|
+
if (!existsSync2(this.#path)) return;
|
|
8213
|
+
const metadata = statSync(this.#path);
|
|
8214
|
+
if (!metadata.isFile()) throw new Error("Harness journal path is not a regular file");
|
|
8215
|
+
if (metadata.size > this.#maxBytes) throw new Error(`Harness journal exceeds ${this.#maxBytes} bytes`);
|
|
8216
|
+
const raw = readFileSync3(this.#path, "utf8");
|
|
8217
|
+
const committed = raw.endsWith("\n") ? raw : raw.slice(0, Math.max(0, raw.lastIndexOf("\n") + 1));
|
|
8218
|
+
if (committed.length !== raw.length) truncateSync(this.#path, Buffer.byteLength(committed));
|
|
8219
|
+
const lines = committed.split("\n").filter(Boolean);
|
|
8220
|
+
let previousHash = null;
|
|
8221
|
+
for (const line of lines) {
|
|
8222
|
+
const record = JournalRecordSchema.parse(JSON.parse(line));
|
|
8223
|
+
if (record.sequence !== this.#records.length + 1) throw new Error("Harness journal sequence is not contiguous");
|
|
8224
|
+
if (record.previousHash !== previousHash) throw new Error("Harness journal hash chain is broken");
|
|
8225
|
+
if (record.hash !== recordHash(record)) throw new Error("Harness journal record hash is invalid");
|
|
8226
|
+
this.#records.push(record);
|
|
8227
|
+
previousHash = record.hash;
|
|
8228
|
+
}
|
|
8229
|
+
this.#sequence = this.#records.length;
|
|
8230
|
+
this.#lastHash = previousHash;
|
|
8231
|
+
assertHarnessInvariants(this.#records);
|
|
8232
|
+
}
|
|
8233
|
+
#append(type, payload) {
|
|
8234
|
+
const normalizedPayload = jsonSafeRecord(payload);
|
|
8235
|
+
const unsigned = {
|
|
8236
|
+
version: HARNESS_JOURNAL_VERSION,
|
|
8237
|
+
sequence: this.#sequence + 1,
|
|
8238
|
+
id: randomUUID8(),
|
|
8239
|
+
timestamp: this.#now(),
|
|
8240
|
+
type,
|
|
8241
|
+
payload: normalizedPayload,
|
|
8242
|
+
previousHash: this.#lastHash
|
|
8243
|
+
};
|
|
8244
|
+
const record = JournalRecordSchema.parse({ ...unsigned, hash: hashJson(unsigned) });
|
|
8245
|
+
const line = `${JSON.stringify(record)}
|
|
8246
|
+
`;
|
|
8247
|
+
const priorBytes = existsSync2(this.#path) ? statSync(this.#path).size : 0;
|
|
8248
|
+
if (priorBytes + Buffer.byteLength(line) > this.#maxBytes) throw new Error(`Harness journal exceeds ${this.#maxBytes} bytes`);
|
|
8249
|
+
const fd = openSync(this.#path, constants.O_CREAT | constants.O_APPEND | constants.O_WRONLY, 384);
|
|
8250
|
+
try {
|
|
8251
|
+
writeSync(fd, line, void 0, "utf8");
|
|
8252
|
+
fsyncSync(fd);
|
|
8253
|
+
} finally {
|
|
8254
|
+
closeSync(fd);
|
|
8255
|
+
}
|
|
8256
|
+
this.#records.push(record);
|
|
8257
|
+
this.#sequence = record.sequence;
|
|
8258
|
+
this.#lastHash = record.hash;
|
|
8259
|
+
}
|
|
8260
|
+
};
|
|
8261
|
+
function jsonSafeRecord(value) {
|
|
8262
|
+
const json = JSON.stringify(value, (_key, item) => typeof item === "bigint" ? `${item}n` : item);
|
|
8263
|
+
return JSON.parse(json);
|
|
8264
|
+
}
|
|
8265
|
+
function hashJson(value) {
|
|
8266
|
+
return createHash6("sha256").update(stableJson(value)).digest("hex");
|
|
8267
|
+
}
|
|
8268
|
+
function recordHash(record) {
|
|
8269
|
+
const { hash: _hash, ...unsigned } = record;
|
|
8270
|
+
return hashJson(unsigned);
|
|
8271
|
+
}
|
|
8272
|
+
function stableJson(value) {
|
|
8273
|
+
if (Array.isArray(value)) return `[${value.map(stableJson).join(",")}]`;
|
|
8274
|
+
if (typeof value === "object" && value !== null) {
|
|
8275
|
+
const object = value;
|
|
8276
|
+
return `{${Object.keys(object).sort().map((key) => `${JSON.stringify(key)}:${stableJson(object[key])}`).join(",")}}`;
|
|
8277
|
+
}
|
|
8278
|
+
return JSON.stringify(value) ?? String(value);
|
|
8279
|
+
}
|
|
8280
|
+
function assertSessionId(value) {
|
|
8281
|
+
if (!/^[A-Za-z0-9][A-Za-z0-9._-]{0,127}$/.test(value)) throw new Error("Invalid session id");
|
|
8282
|
+
}
|
|
8283
|
+
function assertWithin(root, target) {
|
|
8284
|
+
if (!isWithin2(resolve8(root), resolve8(target))) throw new Error("Harness journal path escapes the workspace");
|
|
8285
|
+
}
|
|
8286
|
+
function isWithin2(root, target) {
|
|
8287
|
+
const rel = relative7(root, target);
|
|
8288
|
+
return rel === "" || !rel.startsWith("..") && rel !== ".." && !rel.startsWith(`..${sep7}`);
|
|
8289
|
+
}
|
|
8290
|
+
|
|
7717
8291
|
// src/hooks/bus.ts
|
|
7718
8292
|
import { spawn as spawn4 } from "child_process";
|
|
7719
8293
|
|
|
7720
8294
|
// src/hooks/types.ts
|
|
7721
|
-
import { z as
|
|
8295
|
+
import { z as z11 } from "zod";
|
|
7722
8296
|
var HOOK_EVENT_NAMES = [
|
|
7723
8297
|
"SessionStart",
|
|
7724
8298
|
"UserPromptSubmit",
|
|
@@ -7741,13 +8315,13 @@ var HOOK_EVENT_NAMES = [
|
|
|
7741
8315
|
"Notification",
|
|
7742
8316
|
"LoopEnd"
|
|
7743
8317
|
];
|
|
7744
|
-
var PayloadSchema =
|
|
7745
|
-
var eventSchema = (type) =>
|
|
7746
|
-
version:
|
|
7747
|
-
type:
|
|
8318
|
+
var PayloadSchema = z11.record(z11.string(), z11.unknown());
|
|
8319
|
+
var eventSchema = (type) => z11.object({
|
|
8320
|
+
version: z11.literal(1),
|
|
8321
|
+
type: z11.literal(type),
|
|
7748
8322
|
payload: PayloadSchema
|
|
7749
8323
|
});
|
|
7750
|
-
var HookEventSchema =
|
|
8324
|
+
var HookEventSchema = z11.discriminatedUnion("type", [
|
|
7751
8325
|
eventSchema("SessionStart"),
|
|
7752
8326
|
eventSchema("UserPromptSubmit"),
|
|
7753
8327
|
eventSchema("Stop"),
|
|
@@ -7769,11 +8343,11 @@ var HookEventSchema = z10.discriminatedUnion("type", [
|
|
|
7769
8343
|
eventSchema("Notification"),
|
|
7770
8344
|
eventSchema("LoopEnd")
|
|
7771
8345
|
]);
|
|
7772
|
-
var HookDecisionSchema =
|
|
7773
|
-
decision:
|
|
7774
|
-
reason:
|
|
7775
|
-
updatedInput:
|
|
7776
|
-
additionalContext:
|
|
8346
|
+
var HookDecisionSchema = z11.object({
|
|
8347
|
+
decision: z11.enum(["allow", "deny", "ask"]),
|
|
8348
|
+
reason: z11.string().optional(),
|
|
8349
|
+
updatedInput: z11.unknown().optional(),
|
|
8350
|
+
additionalContext: z11.string().optional()
|
|
7777
8351
|
});
|
|
7778
8352
|
|
|
7779
8353
|
// src/hooks/bus.ts
|
|
@@ -7863,7 +8437,7 @@ var HookBus = class {
|
|
|
7863
8437
|
}
|
|
7864
8438
|
};
|
|
7865
8439
|
function runShellHandler(descriptor, event, signal) {
|
|
7866
|
-
return new Promise((
|
|
8440
|
+
return new Promise((resolve28, reject) => {
|
|
7867
8441
|
const child = spawn4(descriptor.command, [...descriptor.args ?? []], {
|
|
7868
8442
|
env: { ...process.env, ...descriptor.env },
|
|
7869
8443
|
shell: false,
|
|
@@ -7906,7 +8480,7 @@ function runShellHandler(descriptor, event, signal) {
|
|
|
7906
8480
|
return;
|
|
7907
8481
|
}
|
|
7908
8482
|
try {
|
|
7909
|
-
|
|
8483
|
+
resolve28(HookDecisionSchema.parse(JSON.parse(stdout)));
|
|
7910
8484
|
} catch (error) {
|
|
7911
8485
|
reject(error);
|
|
7912
8486
|
}
|
|
@@ -8057,27 +8631,28 @@ function createIncidentReporter(options) {
|
|
|
8057
8631
|
// src/evolve/service.ts
|
|
8058
8632
|
import { spawn as spawn5 } from "child_process";
|
|
8059
8633
|
import { writeFile as writeFile8 } from "fs/promises";
|
|
8060
|
-
import { join as
|
|
8061
|
-
import { z as
|
|
8634
|
+
import { join as join11 } from "path";
|
|
8635
|
+
import { z as z14 } from "zod";
|
|
8062
8636
|
|
|
8063
8637
|
// src/evolve/loader.ts
|
|
8064
8638
|
import { cp, mkdir as mkdir8, mkdtemp, readdir as readdir4, rm as rm2, stat as stat4, writeFile as writeFile6 } from "fs/promises";
|
|
8065
8639
|
import { tmpdir as tmpdir2 } from "os";
|
|
8066
|
-
import { join as
|
|
8640
|
+
import { join as join9, resolve as resolve10 } from "path";
|
|
8067
8641
|
|
|
8068
8642
|
// src/plugins/host.ts
|
|
8069
|
-
import {
|
|
8070
|
-
import {
|
|
8071
|
-
import {
|
|
8643
|
+
import { createHash as createHash7 } from "crypto";
|
|
8644
|
+
import { lstatSync, realpathSync as realpathSync3 } from "fs";
|
|
8645
|
+
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";
|
|
8646
|
+
import { isAbsolute as isAbsolute5, relative as relative8, resolve as resolve9, sep as sep8 } from "path";
|
|
8072
8647
|
import { pathToFileURL } from "url";
|
|
8073
|
-
import { z as
|
|
8648
|
+
import { z as z13 } from "zod";
|
|
8074
8649
|
|
|
8075
8650
|
// src/plugins/types.ts
|
|
8076
|
-
import { z as
|
|
8651
|
+
import { z as z12 } from "zod";
|
|
8077
8652
|
var PLUGIN_NAME = /^(?:@[a-z0-9][a-z0-9._-]{0,63}\/)?[a-z0-9][a-z0-9._-]{0,63}$/;
|
|
8078
8653
|
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)) =>
|
|
8654
|
+
var entry = z12.object({ name: z12.string().regex(CONTRIBUTION_NAME) }).strict();
|
|
8655
|
+
var uniqueArray = (schema, label, identify = (item) => String(item)) => z12.array(schema).max(256).superRefine((items, context) => {
|
|
8081
8656
|
const seen = /* @__PURE__ */ new Set();
|
|
8082
8657
|
for (const [index, item] of items.entries()) {
|
|
8083
8658
|
const key = identify(item);
|
|
@@ -8085,17 +8660,23 @@ var uniqueArray = (schema, label, identify = (item) => String(item)) => z11.arra
|
|
|
8085
8660
|
seen.add(key);
|
|
8086
8661
|
}
|
|
8087
8662
|
});
|
|
8088
|
-
var PluginManifestSchema =
|
|
8089
|
-
name:
|
|
8090
|
-
version:
|
|
8091
|
-
apiVersion:
|
|
8092
|
-
main:
|
|
8093
|
-
permissions: uniqueArray(
|
|
8094
|
-
|
|
8663
|
+
var PluginManifestSchema = z12.object({
|
|
8664
|
+
name: z12.string().regex(PLUGIN_NAME),
|
|
8665
|
+
version: z12.string().min(1).max(64),
|
|
8666
|
+
apiVersion: z12.literal("1"),
|
|
8667
|
+
main: z12.string().min(1).max(512),
|
|
8668
|
+
permissions: uniqueArray(z12.enum([
|
|
8669
|
+
"filesystem:read",
|
|
8670
|
+
"filesystem:write",
|
|
8671
|
+
"network",
|
|
8672
|
+
"process",
|
|
8673
|
+
"runtime:in-process"
|
|
8674
|
+
]), "permission").max(16),
|
|
8675
|
+
contributes: z12.object({
|
|
8095
8676
|
commands: uniqueArray(entry, "command contribution", (item) => item.name),
|
|
8096
8677
|
tools: uniqueArray(entry, "tool contribution", (item) => item.name),
|
|
8097
|
-
hooks: uniqueArray(
|
|
8098
|
-
skillRoots: uniqueArray(entry.extend({ path:
|
|
8678
|
+
hooks: uniqueArray(z12.object({ name: z12.enum(HOOK_EVENT_NAMES) }).strict(), "hook contribution", (item) => item.name),
|
|
8679
|
+
skillRoots: uniqueArray(entry.extend({ path: z12.string().min(1).max(512) }).strict(), "skill-root contribution", (item) => item.name),
|
|
8099
8680
|
modelAdapters: uniqueArray(entry, "model-adapter contribution", (item) => item.name)
|
|
8100
8681
|
}).strict()
|
|
8101
8682
|
}).strict();
|
|
@@ -8274,8 +8855,8 @@ function isSandboxResult(value) {
|
|
|
8274
8855
|
if (!Array.isArray(contributions.hooks) || !contributions.hooks.every(named)) return false;
|
|
8275
8856
|
if (!Array.isArray(contributions.skillRoots) || !contributions.skillRoots.every((item) => named(item) && typeof item.root === "string")) return false;
|
|
8276
8857
|
if (!Array.isArray(contributions.modelAdapters) || !contributions.modelAdapters.every(named)) return false;
|
|
8277
|
-
const
|
|
8278
|
-
return
|
|
8858
|
+
const same2 = (left, right) => left.length === right.length && left.every((name, index) => name === right[index]?.name);
|
|
8859
|
+
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
8860
|
}
|
|
8280
8861
|
|
|
8281
8862
|
// src/plugins/host.ts
|
|
@@ -8380,7 +8961,7 @@ var PluginHost = class {
|
|
|
8380
8961
|
const roots = [];
|
|
8381
8962
|
for (const directory of directories) {
|
|
8382
8963
|
try {
|
|
8383
|
-
for (const entry2 of await readdir3(directory, { withFileTypes: true })) if (entry2.isDirectory()) roots.push(
|
|
8964
|
+
for (const entry2 of await readdir3(directory, { withFileTypes: true })) if (entry2.isDirectory()) roots.push(resolve9(directory, entry2.name));
|
|
8384
8965
|
} catch (error) {
|
|
8385
8966
|
this.#diagnose(directory, error, directory);
|
|
8386
8967
|
}
|
|
@@ -8413,14 +8994,15 @@ var PluginHost = class {
|
|
|
8413
8994
|
const rootInfo = await lstat5(inputRoot);
|
|
8414
8995
|
if (!rootInfo.isDirectory() || rootInfo.isSymbolicLink()) throw new Error("Plugin root must be a static directory");
|
|
8415
8996
|
const root = await realpath4(inputRoot);
|
|
8416
|
-
const rawManifest = await readManifest(
|
|
8997
|
+
const rawManifest = await readManifest(resolve9(root, MANIFEST));
|
|
8417
8998
|
if (typeof rawManifest === "object" && rawManifest !== null && "name" in rawManifest && typeof rawManifest.name === "string") {
|
|
8418
8999
|
diagnosticLabel = rawManifest.name;
|
|
8419
9000
|
}
|
|
8420
9001
|
const manifest = PluginManifestSchema.parse(rawManifest);
|
|
8421
9002
|
const entry2 = resolveContained(root, manifest.main, "Plugin entry");
|
|
8422
9003
|
const entrySnapshot = await snapshotEntry(root, entry2);
|
|
8423
|
-
|
|
9004
|
+
const fingerprint2 = createHash7("sha256").update(JSON.stringify(manifest)).update(await readFile7(entrySnapshot.physicalPath)).digest("hex");
|
|
9005
|
+
return { manifest, root, source, entry: entry2, entrySnapshot, fingerprint: fingerprint2 };
|
|
8424
9006
|
} catch (error) {
|
|
8425
9007
|
this.#diagnose(diagnosticLabel, error, inputRoot);
|
|
8426
9008
|
return void 0;
|
|
@@ -8435,6 +9017,22 @@ var PluginHost = class {
|
|
|
8435
9017
|
const useSandbox = this.#options.sandbox === true;
|
|
8436
9018
|
try {
|
|
8437
9019
|
await verifyEntry(candidate.root, candidate.entry, candidate.entrySnapshot);
|
|
9020
|
+
if (!useSandbox && this.#options.requireInProcessTrust === true && !candidate.manifest.permissions.includes("runtime:in-process")) {
|
|
9021
|
+
throw new Error("Legacy in-process plugins must declare runtime:in-process");
|
|
9022
|
+
}
|
|
9023
|
+
const trustRequest = {
|
|
9024
|
+
name: plugin,
|
|
9025
|
+
version: candidate.manifest.version,
|
|
9026
|
+
source: candidate.source,
|
|
9027
|
+
root: candidate.root,
|
|
9028
|
+
fingerprint: candidate.fingerprint,
|
|
9029
|
+
sandboxed: useSandbox,
|
|
9030
|
+
permissions: [...candidate.manifest.permissions]
|
|
9031
|
+
};
|
|
9032
|
+
const trusted = await this.#options.authorizePlugin?.(trustRequest);
|
|
9033
|
+
if (trusted === false || !useSandbox && this.#options.requireInProcessTrust === true && trusted !== true) {
|
|
9034
|
+
throw new Error(`Plugin "${plugin}" is not trusted for ${useSandbox ? "sandboxed" : "in-process"} activation`);
|
|
9035
|
+
}
|
|
8438
9036
|
if (useSandbox) {
|
|
8439
9037
|
const session = await bounded(
|
|
8440
9038
|
startPluginSandbox({
|
|
@@ -8462,7 +9060,7 @@ var PluginHost = class {
|
|
|
8462
9060
|
const tool = {
|
|
8463
9061
|
name: contribution.toolName,
|
|
8464
9062
|
description: contribution.description,
|
|
8465
|
-
inputSchema:
|
|
9063
|
+
inputSchema: z13.unknown(),
|
|
8466
9064
|
...contribution.modelInputSchema === void 0 ? {} : { modelInputSchema: contribution.modelInputSchema },
|
|
8467
9065
|
...contribution.readOnly === void 0 ? {} : { readOnly: contribution.readOnly },
|
|
8468
9066
|
paths: () => [],
|
|
@@ -8507,7 +9105,15 @@ var PluginHost = class {
|
|
|
8507
9105
|
}
|
|
8508
9106
|
}
|
|
8509
9107
|
this.#assertAllDeclaredRegistered(candidate, claimed);
|
|
8510
|
-
const metadata = {
|
|
9108
|
+
const metadata = {
|
|
9109
|
+
name: plugin,
|
|
9110
|
+
version: candidate.manifest.version,
|
|
9111
|
+
source: candidate.source,
|
|
9112
|
+
root: candidate.root,
|
|
9113
|
+
fingerprint: candidate.fingerprint,
|
|
9114
|
+
sandboxed: useSandbox,
|
|
9115
|
+
permissions: [...candidate.manifest.permissions]
|
|
9116
|
+
};
|
|
8511
9117
|
this.#active.push({ metadata, disposers, state });
|
|
8512
9118
|
try {
|
|
8513
9119
|
await bounded(Promise.resolve(this.#options.emitLifecycle?.("PluginLoad", metadata)), this.#activationTimeoutMs, "PluginLoad lifecycle");
|
|
@@ -8573,7 +9179,7 @@ var PluginHost = class {
|
|
|
8573
9179
|
assertActive();
|
|
8574
9180
|
const permission = `filesystem:${operation}`;
|
|
8575
9181
|
if (!manifest.permissions.includes(permission)) throw new Error(`Plugin ${manifest.name} lacks ${permission} permission`);
|
|
8576
|
-
const path =
|
|
9182
|
+
const path = resolve9(rawPath);
|
|
8577
9183
|
const authorized = await this.#options.authorizeFilesystem?.({ plugin: manifest.name, operation, path });
|
|
8578
9184
|
assertActive();
|
|
8579
9185
|
if (authorized !== true) throw new Error(`Filesystem ${operation} denied for plugin ${manifest.name}`);
|
|
@@ -8612,7 +9218,7 @@ var PluginHost = class {
|
|
|
8612
9218
|
if (expected !== registered) throw new Error(`Registered skill root "${name}" does not match its declaration`);
|
|
8613
9219
|
const info = lstatSync(registered, { bigint: true });
|
|
8614
9220
|
if (!info.isDirectory() || info.isSymbolicLink()) throw new Error(`Skill root "${name}" must be a static directory`);
|
|
8615
|
-
const physical =
|
|
9221
|
+
const physical = realpathSync3.native(registered);
|
|
8616
9222
|
assertContained(candidate.root, physical, "Skill root");
|
|
8617
9223
|
const snapshot4 = fromStats(info);
|
|
8618
9224
|
const capability = Object.freeze({ path: physical, identity: Object.freeze(pickIdentity(snapshot4)) });
|
|
@@ -8705,13 +9311,13 @@ function assertSnapshot(expected, actual, label) {
|
|
|
8705
9311
|
}
|
|
8706
9312
|
function resolveContained(root, value, label) {
|
|
8707
9313
|
if (isAbsolute5(value)) throw new Error(`${label} must be relative to the plugin root`);
|
|
8708
|
-
const candidate =
|
|
9314
|
+
const candidate = resolve9(root, value);
|
|
8709
9315
|
assertContained(root, candidate, label);
|
|
8710
9316
|
return candidate;
|
|
8711
9317
|
}
|
|
8712
9318
|
function assertContained(root, candidate, label) {
|
|
8713
|
-
const delta =
|
|
8714
|
-
if (delta === ".." || delta.startsWith(`..${
|
|
9319
|
+
const delta = relative8(root, candidate);
|
|
9320
|
+
if (delta === ".." || delta.startsWith(`..${sep8}`) || isAbsolute5(delta)) throw new Error(`${label} escapes the plugin root`);
|
|
8715
9321
|
}
|
|
8716
9322
|
function scopedLogger(logger, plugin) {
|
|
8717
9323
|
const prefix = `[plugin:${plugin}] `;
|
|
@@ -8754,17 +9360,17 @@ function compare(left, right) {
|
|
|
8754
9360
|
}
|
|
8755
9361
|
|
|
8756
9362
|
// src/evolve/loader.ts
|
|
8757
|
-
var PLUGIN_DIR =
|
|
8758
|
-
var VERSIONS_DIR =
|
|
9363
|
+
var PLUGIN_DIR = join9(".flavor", "plugins");
|
|
9364
|
+
var VERSIONS_DIR = join9(".flavor", "plugins", ".versions");
|
|
8759
9365
|
function sanitizePluginName(tool) {
|
|
8760
9366
|
const base = String(tool ?? "fix").replace(/[^a-z0-9-]/gi, "-").toLowerCase();
|
|
8761
9367
|
return base.startsWith("fix-") ? base : `fix-${base}`;
|
|
8762
9368
|
}
|
|
8763
9369
|
function fixPluginDir(workspace, name) {
|
|
8764
|
-
return
|
|
9370
|
+
return resolve10(workspace, PLUGIN_DIR, name);
|
|
8765
9371
|
}
|
|
8766
9372
|
function versionsDirFor(workspace, name) {
|
|
8767
|
-
return
|
|
9373
|
+
return resolve10(workspace, VERSIONS_DIR, name);
|
|
8768
9374
|
}
|
|
8769
9375
|
function timestamp() {
|
|
8770
9376
|
return (/* @__PURE__ */ new Date()).toISOString().replaceAll(":", "-");
|
|
@@ -8799,8 +9405,8 @@ export function activate(ctx) {
|
|
|
8799
9405
|
async function scaffoldFixPlugin(workspace, name) {
|
|
8800
9406
|
const dir = fixPluginDir(workspace, name);
|
|
8801
9407
|
await mkdir8(dir, { recursive: true });
|
|
8802
|
-
const manifestPath =
|
|
8803
|
-
const entryPath =
|
|
9408
|
+
const manifestPath = join9(dir, "flavor-plugin.json");
|
|
9409
|
+
const entryPath = join9(dir, "index.js");
|
|
8804
9410
|
try {
|
|
8805
9411
|
await writeFile6(manifestPath, SCAFFOLD_MANIFEST(name), { encoding: "utf8", flag: "wx" });
|
|
8806
9412
|
await writeFile6(entryPath, SCAFFOLD_ENTRY(name), { encoding: "utf8", flag: "wx" });
|
|
@@ -8820,9 +9426,9 @@ async function verifyFixPlugin(workspace, name) {
|
|
|
8820
9426
|
return { ok: false, provided: [], tools: [], commands: [], error: `Plugin "${name}" not found.` };
|
|
8821
9427
|
}
|
|
8822
9428
|
const collected = { tools: [], commands: [] };
|
|
8823
|
-
const sandbox = await mkdtemp(
|
|
9429
|
+
const sandbox = await mkdtemp(join9(tmpdir2(), "flavor-evolve-verify-"));
|
|
8824
9430
|
try {
|
|
8825
|
-
await cp(pluginRoot,
|
|
9431
|
+
await cp(pluginRoot, join9(sandbox, name), { recursive: true });
|
|
8826
9432
|
const host = new PluginHost({
|
|
8827
9433
|
projectPluginDirs: [sandbox],
|
|
8828
9434
|
sandbox: true,
|
|
@@ -8873,7 +9479,7 @@ async function verifyFixPlugin(workspace, name) {
|
|
|
8873
9479
|
}
|
|
8874
9480
|
async function snapshotFixPlugin(workspace, name) {
|
|
8875
9481
|
const pluginRoot = fixPluginDir(workspace, name);
|
|
8876
|
-
const snapshotRoot =
|
|
9482
|
+
const snapshotRoot = join9(versionsDirFor(workspace, name), timestamp());
|
|
8877
9483
|
await mkdir8(snapshotRoot, { recursive: true });
|
|
8878
9484
|
await cp(pluginRoot, snapshotRoot, { recursive: true });
|
|
8879
9485
|
return snapshotRoot;
|
|
@@ -8894,7 +9500,7 @@ async function revertFixPlugin(workspace, name) {
|
|
|
8894
9500
|
}
|
|
8895
9501
|
const snapshots = entries.filter((entry2) => !entry2.startsWith(".")).sort();
|
|
8896
9502
|
if (snapshots.length === 0) throw new Error(`Plugin "${name}" has no snapshot to restore.`);
|
|
8897
|
-
const latest =
|
|
9503
|
+
const latest = join9(versionsRoot, snapshots[snapshots.length - 1]);
|
|
8898
9504
|
await rm2(pluginRoot, { recursive: true, force: true });
|
|
8899
9505
|
await mkdir8(pluginRoot, { recursive: true });
|
|
8900
9506
|
await cp(latest, pluginRoot, { recursive: true });
|
|
@@ -8902,14 +9508,14 @@ async function revertFixPlugin(workspace, name) {
|
|
|
8902
9508
|
}
|
|
8903
9509
|
|
|
8904
9510
|
// src/evolve/store.ts
|
|
8905
|
-
import { createHash as
|
|
8906
|
-
import { appendFile, mkdir as mkdir9, readFile as
|
|
8907
|
-
import { dirname as dirname11, join as
|
|
9511
|
+
import { createHash as createHash8 } from "crypto";
|
|
9512
|
+
import { appendFile, mkdir as mkdir9, readFile as readFile8, rm as rm3, writeFile as writeFile7 } from "fs/promises";
|
|
9513
|
+
import { dirname as dirname11, join as join10 } from "path";
|
|
8908
9514
|
function normalizeError(message2) {
|
|
8909
9515
|
return String(message2 ?? "").replace(/\s+/g, " ").replace(/"[^"]*"/g, '"\u2026"').replace(/`[^`]*`/g, '"\u2026"').trim().slice(0, 160);
|
|
8910
9516
|
}
|
|
8911
9517
|
function fingerprint(tool, errorCode, error) {
|
|
8912
|
-
return
|
|
9518
|
+
return createHash8("sha1").update(`${tool}::${errorCode ?? ""}::${normalizeError(error)}`).digest("hex").slice(0, 12);
|
|
8913
9519
|
}
|
|
8914
9520
|
function argKeys(args) {
|
|
8915
9521
|
if (args === null || typeof args !== "object") return [];
|
|
@@ -8917,7 +9523,7 @@ function argKeys(args) {
|
|
|
8917
9523
|
}
|
|
8918
9524
|
async function readJsonLines(file) {
|
|
8919
9525
|
try {
|
|
8920
|
-
const text = await
|
|
9526
|
+
const text = await readFile8(file, "utf8");
|
|
8921
9527
|
const records = [];
|
|
8922
9528
|
for (const line of text.split("\n")) {
|
|
8923
9529
|
const trimmed = line.trim();
|
|
@@ -8940,7 +9546,7 @@ async function writeJsonLines(file, records) {
|
|
|
8940
9546
|
}
|
|
8941
9547
|
async function readJsonArray(file) {
|
|
8942
9548
|
try {
|
|
8943
|
-
const parsed = JSON.parse(await
|
|
9549
|
+
const parsed = JSON.parse(await readFile8(file, "utf8"));
|
|
8944
9550
|
return Array.isArray(parsed) ? parsed.filter((item) => typeof item === "string") : [];
|
|
8945
9551
|
} catch {
|
|
8946
9552
|
return [];
|
|
@@ -8948,7 +9554,7 @@ async function readJsonArray(file) {
|
|
|
8948
9554
|
}
|
|
8949
9555
|
async function readRules(file) {
|
|
8950
9556
|
try {
|
|
8951
|
-
const parsed = JSON.parse(await
|
|
9557
|
+
const parsed = JSON.parse(await readFile8(file, "utf8"));
|
|
8952
9558
|
if (!Array.isArray(parsed)) return [];
|
|
8953
9559
|
return parsed.filter((item) => item !== null && typeof item === "object").filter((item) => typeof item.id === "string" && typeof item.text === "string").map((item) => ({
|
|
8954
9560
|
id: item.id,
|
|
@@ -8977,12 +9583,12 @@ var EvolveStore = class {
|
|
|
8977
9583
|
#queue = Promise.resolve();
|
|
8978
9584
|
constructor(options) {
|
|
8979
9585
|
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 =
|
|
9586
|
+
this.dir = join10(workspace, ".flavor", "evolve");
|
|
9587
|
+
this.signalsFile = join10(this.dir, "signals.jsonl");
|
|
9588
|
+
this.reflectionsFile = join10(this.dir, "reflections.jsonl");
|
|
9589
|
+
this.doneFile = join10(this.dir, "done.json");
|
|
9590
|
+
this.verifiedFile = join10(this.dir, "verified.json");
|
|
9591
|
+
this.rulesFile = join10(this.dir, "rules.json");
|
|
8986
9592
|
this.maxSignals = maxSignals;
|
|
8987
9593
|
this.maxRules = maxRules;
|
|
8988
9594
|
}
|
|
@@ -9201,7 +9807,7 @@ var USAGE = [
|
|
|
9201
9807
|
" clear reset signals, done markers, and verified markers"
|
|
9202
9808
|
].join("\n");
|
|
9203
9809
|
function runCommand(command, cwd, timeoutMs) {
|
|
9204
|
-
return new Promise((
|
|
9810
|
+
return new Promise((resolve28) => {
|
|
9205
9811
|
const isWindows = process.platform === "win32";
|
|
9206
9812
|
const child = spawn5(isWindows ? "cmd.exe" : "/bin/sh", isWindows ? ["/d", "/s", "/c", command] : ["-c", command], {
|
|
9207
9813
|
cwd,
|
|
@@ -9211,7 +9817,7 @@ function runCommand(command, cwd, timeoutMs) {
|
|
|
9211
9817
|
let stderr = "";
|
|
9212
9818
|
const timer = setTimeout(() => {
|
|
9213
9819
|
child.kill();
|
|
9214
|
-
|
|
9820
|
+
resolve28({ ok: false, stdout, stderr: `${stderr}
|
|
9215
9821
|
[killed: exceeded ${timeoutMs}ms]`, code: "timeout" });
|
|
9216
9822
|
}, timeoutMs);
|
|
9217
9823
|
child.stdout.on("data", (chunk) => {
|
|
@@ -9222,12 +9828,12 @@ function runCommand(command, cwd, timeoutMs) {
|
|
|
9222
9828
|
});
|
|
9223
9829
|
child.on("error", (error) => {
|
|
9224
9830
|
clearTimeout(timer);
|
|
9225
|
-
|
|
9831
|
+
resolve28({ ok: false, stdout, stderr: `${stderr}
|
|
9226
9832
|
${error.message}`, code: "spawn" });
|
|
9227
9833
|
});
|
|
9228
9834
|
child.on("close", (code) => {
|
|
9229
9835
|
clearTimeout(timer);
|
|
9230
|
-
|
|
9836
|
+
resolve28({ ok: code === 0, stdout, stderr, code: code ?? "unknown" });
|
|
9231
9837
|
});
|
|
9232
9838
|
});
|
|
9233
9839
|
}
|
|
@@ -9521,7 +10127,7 @@ function createEvolveService(options) {
|
|
|
9521
10127
|
if (suggestion === void 0) return `No open suggestion with id "${suggestionId}". Use /evolve suggest to list them.`;
|
|
9522
10128
|
const name = sanitizePluginName(suggestion.tool);
|
|
9523
10129
|
const dir = await scaffoldFixPlugin(workspace, name);
|
|
9524
|
-
await writeFile8(
|
|
10130
|
+
await writeFile8(join11(dir, "PLAN.md"), buildPlan(suggestion, name, "See the repeated failure below; describe your fix in PLAN.md."), "utf8");
|
|
9525
10131
|
await snapshotFixPlugin(workspace, name);
|
|
9526
10132
|
return [
|
|
9527
10133
|
`suggestion ${suggestion.id}: ${suggestion.tool} x${suggestion.count} \u2014 ${suggestion.error}`,
|
|
@@ -9583,10 +10189,10 @@ ${result.stderr.slice(-4e3)}` : ""}`;
|
|
|
9583
10189
|
return USAGE;
|
|
9584
10190
|
},
|
|
9585
10191
|
toolDefinition() {
|
|
9586
|
-
const inputSchema =
|
|
9587
|
-
suggestionId:
|
|
9588
|
-
implementation:
|
|
9589
|
-
kind:
|
|
10192
|
+
const inputSchema = z14.object({
|
|
10193
|
+
suggestionId: z14.string().min(1).describe("Signal id from the evolve suggestions"),
|
|
10194
|
+
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)"),
|
|
10195
|
+
kind: z14.enum(["plugin", "prompt_rule"]).optional().describe(
|
|
9590
10196
|
"plugin (default): scaffold a fix plugin; prompt_rule: store a guardrail rule that is injected into future system prompts"
|
|
9591
10197
|
)
|
|
9592
10198
|
});
|
|
@@ -9612,7 +10218,7 @@ ${result.stderr.slice(-4e3)}` : ""}`;
|
|
|
9612
10218
|
}
|
|
9613
10219
|
const name = sanitizePluginName(suggestion.tool);
|
|
9614
10220
|
const dir = await scaffoldFixPlugin(workspace, name);
|
|
9615
|
-
await writeFile8(
|
|
10221
|
+
await writeFile8(join11(dir, "PLAN.md"), buildPlan(suggestion, name, implementation), "utf8");
|
|
9616
10222
|
await snapshotFixPlugin(workspace, name);
|
|
9617
10223
|
return [
|
|
9618
10224
|
`Scaffolded fix plugin at ${dir} for suggestion [${suggestion.id}] (${suggestion.tool} x${suggestion.count}).`,
|
|
@@ -9652,8 +10258,8 @@ function buildLoopCyclePrompt(input) {
|
|
|
9652
10258
|
}
|
|
9653
10259
|
|
|
9654
10260
|
// src/loop/types.ts
|
|
9655
|
-
import { z as
|
|
9656
|
-
var LoopStatusSchema =
|
|
10261
|
+
import { z as z15 } from "zod";
|
|
10262
|
+
var LoopStatusSchema = z15.enum([
|
|
9657
10263
|
"running",
|
|
9658
10264
|
"succeeded",
|
|
9659
10265
|
"failed",
|
|
@@ -9663,65 +10269,65 @@ var LoopStatusSchema = z14.enum([
|
|
|
9663
10269
|
"no_progress",
|
|
9664
10270
|
"needs_human"
|
|
9665
10271
|
]);
|
|
9666
|
-
var LoopApprovalSchema =
|
|
9667
|
-
dimension:
|
|
9668
|
-
previousCheckpoint:
|
|
9669
|
-
newCheckpoint:
|
|
9670
|
-
usage:
|
|
9671
|
-
approvedAt:
|
|
10272
|
+
var LoopApprovalSchema = z15.object({
|
|
10273
|
+
dimension: z15.enum(["cycles", "tokens"]),
|
|
10274
|
+
previousCheckpoint: z15.number().int().nonnegative(),
|
|
10275
|
+
newCheckpoint: z15.number().int().positive(),
|
|
10276
|
+
usage: z15.number().int().nonnegative(),
|
|
10277
|
+
approvedAt: z15.string().datetime({ offset: true })
|
|
9672
10278
|
}).strict();
|
|
9673
|
-
var LoopVerificationCommandSchema =
|
|
9674
|
-
command:
|
|
9675
|
-
args:
|
|
9676
|
-
exitCode:
|
|
9677
|
-
stdout:
|
|
9678
|
-
stderr:
|
|
9679
|
-
truncated:
|
|
10279
|
+
var LoopVerificationCommandSchema = z15.object({
|
|
10280
|
+
command: z15.string().min(1),
|
|
10281
|
+
args: z15.array(z15.string()),
|
|
10282
|
+
exitCode: z15.number().int().nullable(),
|
|
10283
|
+
stdout: z15.string(),
|
|
10284
|
+
stderr: z15.string(),
|
|
10285
|
+
truncated: z15.boolean()
|
|
9680
10286
|
}).strict();
|
|
9681
|
-
var LoopVerificationEvidenceSchema =
|
|
9682
|
-
passed:
|
|
9683
|
-
commands:
|
|
9684
|
-
summary:
|
|
10287
|
+
var LoopVerificationEvidenceSchema = z15.object({
|
|
10288
|
+
passed: z15.boolean(),
|
|
10289
|
+
commands: z15.array(LoopVerificationCommandSchema),
|
|
10290
|
+
summary: z15.string()
|
|
9685
10291
|
}).strict();
|
|
9686
|
-
var LoopCycleEvidenceSchema =
|
|
9687
|
-
cycle:
|
|
9688
|
-
startedAt:
|
|
9689
|
-
completedAt:
|
|
9690
|
-
inputTokens:
|
|
9691
|
-
outputTokens:
|
|
9692
|
-
workspaceFingerprint:
|
|
10292
|
+
var LoopCycleEvidenceSchema = z15.object({
|
|
10293
|
+
cycle: z15.number().int().positive(),
|
|
10294
|
+
startedAt: z15.string().datetime({ offset: true }),
|
|
10295
|
+
completedAt: z15.string().datetime({ offset: true }),
|
|
10296
|
+
inputTokens: z15.number().int().nonnegative(),
|
|
10297
|
+
outputTokens: z15.number().int().nonnegative(),
|
|
10298
|
+
workspaceFingerprint: z15.string(),
|
|
9693
10299
|
verification: LoopVerificationEvidenceSchema
|
|
9694
10300
|
}).strict();
|
|
9695
|
-
var LoopStateSchema =
|
|
9696
|
-
version:
|
|
9697
|
-
loopId:
|
|
9698
|
-
goal:
|
|
9699
|
-
workspace:
|
|
9700
|
-
createdAt:
|
|
9701
|
-
updatedAt:
|
|
10301
|
+
var LoopStateSchema = z15.object({
|
|
10302
|
+
version: z15.literal(1),
|
|
10303
|
+
loopId: z15.string().regex(/^[A-Za-z0-9][A-Za-z0-9._-]{0,127}$/),
|
|
10304
|
+
goal: z15.string().trim().min(1),
|
|
10305
|
+
workspace: z15.string().min(1),
|
|
10306
|
+
createdAt: z15.string().datetime({ offset: true }),
|
|
10307
|
+
updatedAt: z15.string().datetime({ offset: true }),
|
|
9702
10308
|
status: LoopStatusSchema,
|
|
9703
|
-
config:
|
|
9704
|
-
cycleStep:
|
|
9705
|
-
tokenStep:
|
|
9706
|
-
isolation:
|
|
10309
|
+
config: z15.object({
|
|
10310
|
+
cycleStep: z15.number().int().positive(),
|
|
10311
|
+
tokenStep: z15.number().int().positive(),
|
|
10312
|
+
isolation: z15.literal("auto")
|
|
9707
10313
|
}).strict(),
|
|
9708
|
-
budget:
|
|
9709
|
-
cyclesUsed:
|
|
9710
|
-
inputTokens:
|
|
9711
|
-
outputTokens:
|
|
9712
|
-
cycleCheckpoint:
|
|
9713
|
-
tokenCheckpoint:
|
|
9714
|
-
approvals:
|
|
10314
|
+
budget: z15.object({
|
|
10315
|
+
cyclesUsed: z15.number().int().nonnegative(),
|
|
10316
|
+
inputTokens: z15.number().int().nonnegative(),
|
|
10317
|
+
outputTokens: z15.number().int().nonnegative(),
|
|
10318
|
+
cycleCheckpoint: z15.number().int().positive(),
|
|
10319
|
+
tokenCheckpoint: z15.number().int().positive(),
|
|
10320
|
+
approvals: z15.array(LoopApprovalSchema)
|
|
9715
10321
|
}).strict(),
|
|
9716
|
-
cycles:
|
|
9717
|
-
terminalReason:
|
|
10322
|
+
cycles: z15.array(LoopCycleEvidenceSchema),
|
|
10323
|
+
terminalReason: z15.string().optional()
|
|
9718
10324
|
}).strict();
|
|
9719
|
-
var LoopEventSchema =
|
|
9720
|
-
version:
|
|
9721
|
-
type:
|
|
9722
|
-
timestamp:
|
|
9723
|
-
loopId:
|
|
9724
|
-
payload:
|
|
10325
|
+
var LoopEventSchema = z15.object({
|
|
10326
|
+
version: z15.literal(1),
|
|
10327
|
+
type: z15.enum(["created", "cycle_started", "cycle_completed", "verification", "budget_approved", "terminal"]),
|
|
10328
|
+
timestamp: z15.string().datetime({ offset: true }),
|
|
10329
|
+
loopId: z15.string().regex(/^[A-Za-z0-9][A-Za-z0-9._-]{0,127}$/),
|
|
10330
|
+
payload: z15.record(z15.string(), z15.unknown())
|
|
9725
10331
|
}).strict();
|
|
9726
10332
|
|
|
9727
10333
|
// src/loop/budget.ts
|
|
@@ -10033,19 +10639,20 @@ ${evidence.summary}`;
|
|
|
10033
10639
|
};
|
|
10034
10640
|
|
|
10035
10641
|
// src/goal/orchestrator.ts
|
|
10642
|
+
import { createHash as createHash10 } from "crypto";
|
|
10036
10643
|
import { writeFile as writeFile9, mkdir as mkdir10 } from "fs/promises";
|
|
10037
|
-
import { join as
|
|
10644
|
+
import { join as join13 } from "path";
|
|
10038
10645
|
|
|
10039
10646
|
// src/goal/types.ts
|
|
10040
|
-
import { z as
|
|
10041
|
-
var GoalPhaseSchema =
|
|
10647
|
+
import { z as z16 } from "zod";
|
|
10648
|
+
var GoalPhaseSchema = z16.enum([
|
|
10042
10649
|
"idle",
|
|
10043
10650
|
"planning",
|
|
10044
10651
|
"executing",
|
|
10045
10652
|
"verifying",
|
|
10046
10653
|
"complete"
|
|
10047
10654
|
]);
|
|
10048
|
-
var GoalStatusSchema =
|
|
10655
|
+
var GoalStatusSchema = z16.enum([
|
|
10049
10656
|
"active",
|
|
10050
10657
|
"paused",
|
|
10051
10658
|
"achieved",
|
|
@@ -10053,43 +10660,61 @@ var GoalStatusSchema = z15.enum([
|
|
|
10053
10660
|
"blocked",
|
|
10054
10661
|
"failed"
|
|
10055
10662
|
]);
|
|
10056
|
-
var AcceptanceCriterionSchema =
|
|
10057
|
-
id:
|
|
10058
|
-
description:
|
|
10059
|
-
type:
|
|
10663
|
+
var AcceptanceCriterionSchema = z16.object({
|
|
10664
|
+
id: z16.number().int().positive(),
|
|
10665
|
+
description: z16.string().min(1),
|
|
10666
|
+
type: z16.enum(["gating", "evidence"])
|
|
10667
|
+
}).strict();
|
|
10668
|
+
var PlanSchema = z16.object({
|
|
10669
|
+
kind: z16.enum(["code-change", "analysis", "research"]),
|
|
10670
|
+
criteria: z16.array(AcceptanceCriterionSchema).min(1).max(32),
|
|
10671
|
+
verificationPlan: z16.string().min(1),
|
|
10672
|
+
nonGoals: z16.array(z16.string()).max(16),
|
|
10673
|
+
assumedScope: z16.array(z16.string()).max(16),
|
|
10674
|
+
approach: z16.string().optional(),
|
|
10675
|
+
checklist: z16.array(z16.string()).max(32).optional()
|
|
10060
10676
|
}).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()
|
|
10677
|
+
var GapSchema = z16.object({
|
|
10678
|
+
criterion: z16.string().min(1),
|
|
10679
|
+
description: z16.string().min(1),
|
|
10680
|
+
blocking: z16.enum(["model_fixable", "contradiction", "unverifiable"])
|
|
10069
10681
|
}).strict();
|
|
10070
|
-
var
|
|
10071
|
-
|
|
10072
|
-
|
|
10073
|
-
blocking: z15.enum(["model_fixable", "contradiction", "unverifiable"])
|
|
10682
|
+
var VerdictSchema = z16.object({
|
|
10683
|
+
refuted: z16.boolean(),
|
|
10684
|
+
gaps: z16.array(GapSchema)
|
|
10074
10685
|
}).strict();
|
|
10075
|
-
var
|
|
10076
|
-
|
|
10077
|
-
|
|
10686
|
+
var HostVerificationEvidenceSchema = z16.object({
|
|
10687
|
+
passed: z16.boolean(),
|
|
10688
|
+
summary: z16.string(),
|
|
10689
|
+
commands: z16.array(z16.object({
|
|
10690
|
+
command: z16.string(),
|
|
10691
|
+
args: z16.array(z16.string()),
|
|
10692
|
+
exitCode: z16.number().int().nullable(),
|
|
10693
|
+
stdout: z16.string().optional(),
|
|
10694
|
+
stderr: z16.string().optional(),
|
|
10695
|
+
truncated: z16.boolean().optional()
|
|
10696
|
+
}).strict()).max(100)
|
|
10078
10697
|
}).strict();
|
|
10079
|
-
var GoalStateSchema =
|
|
10080
|
-
id:
|
|
10081
|
-
objective:
|
|
10698
|
+
var GoalStateSchema = z16.object({
|
|
10699
|
+
id: z16.string().regex(/^[A-Za-z0-9][A-Za-z0-9._-]{0,127}$/),
|
|
10700
|
+
objective: z16.string().min(1),
|
|
10082
10701
|
phase: GoalPhaseSchema,
|
|
10083
10702
|
status: GoalStatusSchema,
|
|
10084
10703
|
plan: PlanSchema.nullable(),
|
|
10085
|
-
planPath:
|
|
10086
|
-
verifyRounds:
|
|
10087
|
-
workerRounds:
|
|
10088
|
-
lastGaps:
|
|
10089
|
-
gapFingerprint:
|
|
10090
|
-
stallStreak:
|
|
10091
|
-
|
|
10092
|
-
|
|
10704
|
+
planPath: z16.string().nullable(),
|
|
10705
|
+
verifyRounds: z16.number().int().min(0),
|
|
10706
|
+
workerRounds: z16.number().int().min(0),
|
|
10707
|
+
lastGaps: z16.array(GapSchema),
|
|
10708
|
+
gapFingerprint: z16.string(),
|
|
10709
|
+
stallStreak: z16.number().int().min(0),
|
|
10710
|
+
contractHash: z16.string().regex(/^[a-f0-9]{64}$/),
|
|
10711
|
+
evidenceRounds: z16.array(z16.object({
|
|
10712
|
+
round: z16.number().int().positive(),
|
|
10713
|
+
workspaceDiffHash: z16.string().regex(/^[a-f0-9]{64}$/),
|
|
10714
|
+
hostVerification: HostVerificationEvidenceSchema.optional()
|
|
10715
|
+
}).strict()).max(1e3),
|
|
10716
|
+
createdAt: z16.string().datetime({ offset: true }),
|
|
10717
|
+
updatedAt: z16.string().datetime({ offset: true })
|
|
10093
10718
|
}).strict();
|
|
10094
10719
|
|
|
10095
10720
|
// src/goal/planner.ts
|
|
@@ -10179,9 +10804,9 @@ function extractJson(text) {
|
|
|
10179
10804
|
}
|
|
10180
10805
|
|
|
10181
10806
|
// src/goal/classifier.ts
|
|
10182
|
-
import { createHash as
|
|
10183
|
-
import { readFile as
|
|
10184
|
-
import { join as
|
|
10807
|
+
import { createHash as createHash9 } from "crypto";
|
|
10808
|
+
import { readFile as readFile9 } from "fs/promises";
|
|
10809
|
+
import { join as join12 } from "path";
|
|
10185
10810
|
function buildSkepticPrompt(evidence, plan, index) {
|
|
10186
10811
|
const criteriaList = plan.criteria.map((c) => ` ${c.id}. [${c.type}] ${c.description}`).join("\n");
|
|
10187
10812
|
return [
|
|
@@ -10203,6 +10828,10 @@ function buildSkepticPrompt(evidence, plan, index) {
|
|
|
10203
10828
|
`Changed files: ${evidence.changedFiles.length > 0 ? evidence.changedFiles.join(", ") : "(none)"}`,
|
|
10204
10829
|
`Plan file: ${evidence.planFile ?? "(unavailable)"}`,
|
|
10205
10830
|
`Prior gaps: ${evidence.priorGaps || "(none)"}`,
|
|
10831
|
+
`Contract hash: ${evidence.contractHash}`,
|
|
10832
|
+
`Workspace diff hash: ${evidence.workspaceDiffHash}`,
|
|
10833
|
+
`Workspace status: ${evidence.workspaceStatus.length > 0 ? evidence.workspaceStatus.join(" | ") : "(clean)"}`,
|
|
10834
|
+
`Host verification: ${evidence.hostVerification === void 0 ? "(not configured)" : JSON.stringify(evidence.hostVerification)}`,
|
|
10206
10835
|
"",
|
|
10207
10836
|
"## Implementer's Closing Statement",
|
|
10208
10837
|
evidence.finalResponse || "(no closing statement)",
|
|
@@ -10214,7 +10843,8 @@ function buildSkepticPrompt(evidence, plan, index) {
|
|
|
10214
10843
|
"4. Missing tests alone are NOT grounds for refutation unless a criterion explicitly requires them.",
|
|
10215
10844
|
"5. NEVER invent requirements beyond the contract (objective + criteria + non-goals).",
|
|
10216
10845
|
"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.
|
|
10846
|
+
"7. Host verification is authoritative. A failed deterministic command must be refuted.",
|
|
10847
|
+
"8. Every PRIOR_GAP must have current evidence that it was resolved; uncertainty means refuted.",
|
|
10218
10848
|
"",
|
|
10219
10849
|
"## Output",
|
|
10220
10850
|
"Write your verdict as a single JSON object (no markdown, no preamble):",
|
|
@@ -10243,8 +10873,12 @@ async function runClassifier(evidence, plan, options) {
|
|
|
10243
10873
|
for (const result of skepticResults) {
|
|
10244
10874
|
try {
|
|
10245
10875
|
verdicts.push(parseVerdict(result));
|
|
10246
|
-
} catch {
|
|
10247
|
-
verdicts.push({ refuted:
|
|
10876
|
+
} catch (error) {
|
|
10877
|
+
verdicts.push({ refuted: true, gaps: [{
|
|
10878
|
+
criterion: "verification-panel",
|
|
10879
|
+
description: `Skeptic output was unavailable or invalid: ${error instanceof Error ? error.message : String(error)}`,
|
|
10880
|
+
blocking: "unverifiable"
|
|
10881
|
+
}] });
|
|
10248
10882
|
}
|
|
10249
10883
|
}
|
|
10250
10884
|
return aggregate(verdicts);
|
|
@@ -10345,50 +10979,56 @@ function deduplicateGaps(gaps) {
|
|
|
10345
10979
|
}
|
|
10346
10980
|
function computeFingerprint(gaps) {
|
|
10347
10981
|
const content = gaps.map((g) => `${g.criterion}:${g.description}`).sort().join("\n");
|
|
10348
|
-
return
|
|
10982
|
+
return createHash9("sha256").update(content).digest("hex").slice(0, 16);
|
|
10349
10983
|
}
|
|
10350
|
-
async function collectEvidence(workspace, objective, finalResponse, priorGaps) {
|
|
10351
|
-
const
|
|
10352
|
-
const planPath =
|
|
10984
|
+
async function collectEvidence(workspace, objective, finalResponse, priorGaps, contractHash2 = createHash9("sha256").update(objective).digest("hex"), hostVerification) {
|
|
10985
|
+
const workspaceState = await collectWorkspaceState(workspace);
|
|
10986
|
+
const planPath = join12(workspace, ".flavor", "goal-plan.md");
|
|
10353
10987
|
let planFile = null;
|
|
10354
10988
|
try {
|
|
10355
|
-
planFile = await
|
|
10989
|
+
planFile = await readFile9(planPath, "utf8");
|
|
10356
10990
|
} catch {
|
|
10357
10991
|
}
|
|
10358
10992
|
return {
|
|
10359
10993
|
objective,
|
|
10360
|
-
changedFiles: changedFiles
|
|
10994
|
+
changedFiles: workspaceState.changedFiles,
|
|
10361
10995
|
planFile,
|
|
10362
10996
|
finalResponse,
|
|
10363
|
-
priorGaps
|
|
10997
|
+
priorGaps,
|
|
10998
|
+
contractHash: contractHash2,
|
|
10999
|
+
workspaceDiffHash: workspaceState.diffHash,
|
|
11000
|
+
workspaceStatus: workspaceState.status,
|
|
11001
|
+
...hostVerification === void 0 ? {} : { hostVerification }
|
|
10364
11002
|
};
|
|
10365
11003
|
}
|
|
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
|
-
}
|
|
11004
|
+
async function collectWorkspaceState(workspace) {
|
|
11005
|
+
const statusResult = await execFileNoThrow("git", ["-C", workspace, "status", "--porcelain=v1", "--untracked-files=all"], {
|
|
11006
|
+
timeout: 3e4,
|
|
11007
|
+
useCwd: false
|
|
11008
|
+
});
|
|
11009
|
+
if (statusResult.code !== 0) {
|
|
11010
|
+
return {
|
|
11011
|
+
changedFiles: [],
|
|
11012
|
+
status: ["not-a-git-worktree"],
|
|
11013
|
+
diffHash: createHash9("sha256").update("not-a-git-worktree").digest("hex")
|
|
11014
|
+
};
|
|
10389
11015
|
}
|
|
10390
|
-
|
|
10391
|
-
|
|
11016
|
+
const status = statusResult.stdout.split(/\r?\n/).map((line) => line.trimEnd()).filter(Boolean);
|
|
11017
|
+
const changedFiles = status.map((line) => {
|
|
11018
|
+
const value = line.slice(3);
|
|
11019
|
+
const arrow = value.lastIndexOf(" -> ");
|
|
11020
|
+
return arrow >= 0 ? value.slice(arrow + 4) : value;
|
|
11021
|
+
}).sort();
|
|
11022
|
+
const diff = await execFileNoThrow("git", ["-C", workspace, "diff", "--no-ext-diff", "--binary", "HEAD"], {
|
|
11023
|
+
timeout: 3e4,
|
|
11024
|
+
useCwd: false
|
|
11025
|
+
});
|
|
11026
|
+
const untracked = status.filter((line) => line.startsWith("?? ")).map((line) => line.slice(3)).sort().join("\n");
|
|
11027
|
+
return {
|
|
11028
|
+
changedFiles,
|
|
11029
|
+
status,
|
|
11030
|
+
diffHash: createHash9("sha256").update(diff.stdout).update("\nUNTRACKED\n").update(untracked).digest("hex")
|
|
11031
|
+
};
|
|
10392
11032
|
}
|
|
10393
11033
|
|
|
10394
11034
|
// src/goal/orchestrator.ts
|
|
@@ -10419,6 +11059,8 @@ var GoalOrchestrator = class {
|
|
|
10419
11059
|
lastGaps: [],
|
|
10420
11060
|
gapFingerprint: "",
|
|
10421
11061
|
stallStreak: 0,
|
|
11062
|
+
contractHash: contractHash(request.goal, null),
|
|
11063
|
+
evidenceRounds: [],
|
|
10422
11064
|
createdAt,
|
|
10423
11065
|
updatedAt: createdAt
|
|
10424
11066
|
};
|
|
@@ -10439,7 +11081,7 @@ var GoalOrchestrator = class {
|
|
|
10439
11081
|
signal: request.signal
|
|
10440
11082
|
});
|
|
10441
11083
|
planPath = await writePlanFile(workspace, plan);
|
|
10442
|
-
await persistState({ plan, planPath });
|
|
11084
|
+
await persistState({ plan, planPath, contractHash: contractHash(request.goal, plan) });
|
|
10443
11085
|
yield { type: "goal-plan-created", plan, planPath };
|
|
10444
11086
|
} catch (error) {
|
|
10445
11087
|
const reason = `Goal planning failed: ${message(error)}`;
|
|
@@ -10492,15 +11134,35 @@ var GoalOrchestrator = class {
|
|
|
10492
11134
|
}
|
|
10493
11135
|
await persistState({ phase: "verifying", status: "active", verifyRounds: round });
|
|
10494
11136
|
yield { type: "goal-verification-start", round };
|
|
11137
|
+
const hostVerification = await this.#options.verifyHost?.(request.signal);
|
|
10495
11138
|
const evidence = await collectEvidence(
|
|
10496
11139
|
workspace,
|
|
10497
11140
|
request.goal,
|
|
10498
11141
|
finalResponse.slice(-4e3),
|
|
10499
11142
|
// Last 4000 chars of response
|
|
10500
|
-
formatPriorGaps(priorGaps)
|
|
11143
|
+
formatPriorGaps(priorGaps),
|
|
11144
|
+
state.contractHash,
|
|
11145
|
+
hostVerification
|
|
10501
11146
|
);
|
|
11147
|
+
await persistState({ evidenceRounds: [...state.evidenceRounds, {
|
|
11148
|
+
round,
|
|
11149
|
+
workspaceDiffHash: evidence.workspaceDiffHash,
|
|
11150
|
+
...hostVerification === void 0 ? {} : { hostVerification }
|
|
11151
|
+
}] });
|
|
10502
11152
|
let outcome;
|
|
10503
|
-
|
|
11153
|
+
if (plan.kind === "code-change" && hostVerification !== void 0 && !hostVerification.passed) {
|
|
11154
|
+
if (hostVerification.commands.length === 0) {
|
|
11155
|
+
outcome = { type: "blocked", reason: `Host verification unavailable: ${hostVerification.summary}` };
|
|
11156
|
+
} else {
|
|
11157
|
+
const gaps = [{ criterion: "host-verification", description: hostVerification.summary, blocking: "model_fixable" }];
|
|
11158
|
+
outcome = {
|
|
11159
|
+
type: "not_achieved",
|
|
11160
|
+
gaps,
|
|
11161
|
+
summary: hostVerification.summary,
|
|
11162
|
+
fingerprint: createHash10("sha256").update(hostVerification.summary).digest("hex").slice(0, 16)
|
|
11163
|
+
};
|
|
11164
|
+
}
|
|
11165
|
+
} else try {
|
|
10504
11166
|
outcome = await runClassifier(evidence, plan, {
|
|
10505
11167
|
registry: this.#options.registry,
|
|
10506
11168
|
modelId: this.#options.classifierModelId,
|
|
@@ -10510,8 +11172,8 @@ var GoalOrchestrator = class {
|
|
|
10510
11172
|
});
|
|
10511
11173
|
} catch (error) {
|
|
10512
11174
|
outcome = {
|
|
10513
|
-
type: "
|
|
10514
|
-
|
|
11175
|
+
type: "blocked",
|
|
11176
|
+
reason: `Classifier infrastructure error: ${message(error)}`
|
|
10515
11177
|
};
|
|
10516
11178
|
}
|
|
10517
11179
|
yield {
|
|
@@ -10570,9 +11232,12 @@ var GoalOrchestrator = class {
|
|
|
10570
11232
|
};
|
|
10571
11233
|
}
|
|
10572
11234
|
};
|
|
11235
|
+
function contractHash(objective, plan) {
|
|
11236
|
+
return createHash10("sha256").update(JSON.stringify({ objective, plan })).digest("hex");
|
|
11237
|
+
}
|
|
10573
11238
|
async function writePlanFile(workspace, plan) {
|
|
10574
|
-
const dir =
|
|
10575
|
-
const planPath =
|
|
11239
|
+
const dir = join13(workspace, ".flavor");
|
|
11240
|
+
const planPath = join13(dir, "goal-plan.md");
|
|
10576
11241
|
await mkdir10(dir, { recursive: true });
|
|
10577
11242
|
const criteriaText = plan.criteria.map((c) => `- [${c.type}] AC-${c.id}: ${c.description}`).join("\n");
|
|
10578
11243
|
const content = [
|
|
@@ -10647,18 +11312,18 @@ function formatPriorGaps(gaps) {
|
|
|
10647
11312
|
}
|
|
10648
11313
|
|
|
10649
11314
|
// src/goal/store.ts
|
|
10650
|
-
import { constants } from "fs";
|
|
10651
|
-
import { randomUUID as
|
|
11315
|
+
import { constants as constants2 } from "fs";
|
|
11316
|
+
import { randomUUID as randomUUID9 } from "crypto";
|
|
10652
11317
|
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
|
|
11318
|
+
import { dirname as dirname12, join as join14, relative as relative9, resolve as resolve11, sep as sep9 } from "path";
|
|
10654
11319
|
var DEFAULT_MAX_BYTES = 1024 * 1024;
|
|
10655
11320
|
var GoalStore = class {
|
|
10656
11321
|
#workspace;
|
|
10657
11322
|
#root;
|
|
10658
11323
|
#maxBytes;
|
|
10659
11324
|
constructor(options) {
|
|
10660
|
-
this.#workspace =
|
|
10661
|
-
this.#root =
|
|
11325
|
+
this.#workspace = resolve11(options.workspace);
|
|
11326
|
+
this.#root = join14(this.#workspace, ".flavor", "goals");
|
|
10662
11327
|
this.#maxBytes = options.maxBytes ?? DEFAULT_MAX_BYTES;
|
|
10663
11328
|
}
|
|
10664
11329
|
async save(input) {
|
|
@@ -10666,16 +11331,16 @@ var GoalStore = class {
|
|
|
10666
11331
|
await assertNoSymlink(this.#workspace, dirname12(this.#root));
|
|
10667
11332
|
await mkdir11(this.#root, { recursive: true, mode: 448 });
|
|
10668
11333
|
await assertNoSymlink(this.#workspace, this.#root);
|
|
10669
|
-
const target =
|
|
11334
|
+
const target = join14(this.#root, `${state.id}.json`);
|
|
10670
11335
|
const body = `${JSON.stringify(state, null, 2)}
|
|
10671
11336
|
`;
|
|
10672
11337
|
if (Buffer.byteLength(body) > this.#maxBytes) {
|
|
10673
11338
|
throw new Error(`Goal state exceeds maximum size of ${this.#maxBytes} bytes`);
|
|
10674
11339
|
}
|
|
10675
|
-
const temporary =
|
|
11340
|
+
const temporary = join14(this.#root, `.${state.id}.${process.pid}.${randomUUID9()}.tmp`);
|
|
10676
11341
|
let handle;
|
|
10677
11342
|
try {
|
|
10678
|
-
handle = await open6(temporary,
|
|
11343
|
+
handle = await open6(temporary, constants2.O_CREAT | constants2.O_EXCL | constants2.O_WRONLY, 384);
|
|
10679
11344
|
await handle.writeFile(body, "utf8");
|
|
10680
11345
|
await handle.sync();
|
|
10681
11346
|
await handle.close();
|
|
@@ -10688,10 +11353,10 @@ var GoalStore = class {
|
|
|
10688
11353
|
}
|
|
10689
11354
|
};
|
|
10690
11355
|
async function assertNoSymlink(root, target) {
|
|
10691
|
-
let cursor =
|
|
10692
|
-
const stop =
|
|
11356
|
+
let cursor = resolve11(target);
|
|
11357
|
+
const stop = resolve11(root);
|
|
10693
11358
|
const value = relative9(stop, cursor);
|
|
10694
|
-
if (value === ".." || value.startsWith(`..${
|
|
11359
|
+
if (value === ".." || value.startsWith(`..${sep9}`) || value.includes(":")) {
|
|
10695
11360
|
throw new Error("Goal path escapes workspace");
|
|
10696
11361
|
}
|
|
10697
11362
|
const chain = [];
|
|
@@ -10712,8 +11377,8 @@ function isCode3(error, code) {
|
|
|
10712
11377
|
}
|
|
10713
11378
|
|
|
10714
11379
|
// src/loop/isolation.ts
|
|
10715
|
-
import { appendFile as appendFile2, mkdir as mkdir12, readFile as
|
|
10716
|
-
import { join as
|
|
11380
|
+
import { appendFile as appendFile2, mkdir as mkdir12, readFile as readFile10 } from "fs/promises";
|
|
11381
|
+
import { join as join15, resolve as resolve12 } from "path";
|
|
10717
11382
|
var CODE_CHANGE = /\b(?:fix|implement|add|update|change|modify|refactor|build|create|remove|delete|write|migrate)\b|修复|实现|添加|更新|修改|重构|创建|删除|迁移/i;
|
|
10718
11383
|
var READ_ONLY = /\b(?:review|analy[sz]e|inspect|explain|research|investigate|audit|summari[sz]e|report)\b|调研|分析|审查|检查|解释|研究|总结|报告/i;
|
|
10719
11384
|
function inferGoalIntent(goal) {
|
|
@@ -10723,7 +11388,7 @@ function inferGoalIntent(goal) {
|
|
|
10723
11388
|
}
|
|
10724
11389
|
async function prepareLoopWorkspace(options) {
|
|
10725
11390
|
options.signal?.throwIfAborted();
|
|
10726
|
-
const root =
|
|
11391
|
+
const root = resolve12(options.root);
|
|
10727
11392
|
const intent = inferGoalIntent(options.goal);
|
|
10728
11393
|
if (intent === "read_only") return { kind: "ready", workspace: { root, mode: "current" } };
|
|
10729
11394
|
const inside = await git(root, ["rev-parse", "--is-inside-work-tree"]);
|
|
@@ -10736,8 +11401,8 @@ async function prepareLoopWorkspace(options) {
|
|
|
10736
11401
|
return { kind: "needs_human", reason: "The workspace has uncommitted changes that a new worktree would omit." };
|
|
10737
11402
|
}
|
|
10738
11403
|
options.signal?.throwIfAborted();
|
|
10739
|
-
const worktrees =
|
|
10740
|
-
const path =
|
|
11404
|
+
const worktrees = join15(root, ".worktrees");
|
|
11405
|
+
const path = join15(worktrees, `loop-${options.loopId}`);
|
|
10741
11406
|
const branch = `loop/${options.loopId}`;
|
|
10742
11407
|
try {
|
|
10743
11408
|
await ensureWorktreeDirectoryIgnored(root);
|
|
@@ -10766,13 +11431,13 @@ async function ensureWorktreeDirectoryIgnored(root) {
|
|
|
10766
11431
|
if (ignored2.code === 0) return;
|
|
10767
11432
|
const commonDirectory = await git(root, ["rev-parse", "--git-common-dir"]);
|
|
10768
11433
|
if (commonDirectory.code !== 0) throw new Error(commonDirectory.stderr.trim() || "Git common directory is unavailable");
|
|
10769
|
-
const gitDirectory =
|
|
10770
|
-
const infoDirectory =
|
|
10771
|
-
const excludePath =
|
|
11434
|
+
const gitDirectory = resolve12(root, commonDirectory.stdout.trim());
|
|
11435
|
+
const infoDirectory = join15(gitDirectory, "info");
|
|
11436
|
+
const excludePath = join15(infoDirectory, "exclude");
|
|
10772
11437
|
await mkdir12(infoDirectory, { recursive: true });
|
|
10773
11438
|
let existing = "";
|
|
10774
11439
|
try {
|
|
10775
|
-
existing = await
|
|
11440
|
+
existing = await readFile10(excludePath, "utf8");
|
|
10776
11441
|
} catch (error) {
|
|
10777
11442
|
if (!(typeof error === "object" && error !== null && "code" in error && error.code === "ENOENT")) throw error;
|
|
10778
11443
|
}
|
|
@@ -10782,10 +11447,10 @@ async function ensureWorktreeDirectoryIgnored(root) {
|
|
|
10782
11447
|
}
|
|
10783
11448
|
|
|
10784
11449
|
// 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
|
|
11450
|
+
import { constants as constants3 } from "fs";
|
|
11451
|
+
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";
|
|
11452
|
+
import { dirname as dirname13, join as join16, relative as relative10, resolve as resolve13, sep as sep10 } from "path";
|
|
11453
|
+
import { randomUUID as randomUUID10 } from "crypto";
|
|
10789
11454
|
var LOOP_ID = /^[A-Za-z0-9][A-Za-z0-9._-]{0,127}$/;
|
|
10790
11455
|
var DEFAULT_MAX_BYTES2 = 5 * 1024 * 1024;
|
|
10791
11456
|
var LoopStore = class {
|
|
@@ -10794,8 +11459,8 @@ var LoopStore = class {
|
|
|
10794
11459
|
#maxBytes;
|
|
10795
11460
|
#appendTails = /* @__PURE__ */ new Map();
|
|
10796
11461
|
constructor(options) {
|
|
10797
|
-
this.#workspace =
|
|
10798
|
-
this.#root =
|
|
11462
|
+
this.#workspace = resolve13(options.workspace);
|
|
11463
|
+
this.#root = join16(this.#workspace, ".flavor", "loops");
|
|
10799
11464
|
this.#maxBytes = options.maxBytes ?? DEFAULT_MAX_BYTES2;
|
|
10800
11465
|
}
|
|
10801
11466
|
async create(state) {
|
|
@@ -10812,14 +11477,14 @@ var LoopStore = class {
|
|
|
10812
11477
|
const state = LoopStateSchema.parse(input);
|
|
10813
11478
|
await this.#assertWorkspace(state.workspace);
|
|
10814
11479
|
const directory = await this.#prepare(state.loopId);
|
|
10815
|
-
const target =
|
|
11480
|
+
const target = join16(directory, "state.json");
|
|
10816
11481
|
const body = `${JSON.stringify(state, null, 2)}
|
|
10817
11482
|
`;
|
|
10818
11483
|
if (Buffer.byteLength(body) > this.#maxBytes) throw new Error(`Loop state exceeds maximum size of ${this.#maxBytes} bytes`);
|
|
10819
|
-
const temporary =
|
|
11484
|
+
const temporary = join16(directory, `.state.${process.pid}.${randomUUID10()}.tmp`);
|
|
10820
11485
|
let handle;
|
|
10821
11486
|
try {
|
|
10822
|
-
handle = await open7(temporary,
|
|
11487
|
+
handle = await open7(temporary, constants3.O_CREAT | constants3.O_EXCL | constants3.O_WRONLY, 384);
|
|
10823
11488
|
await handle.writeFile(body, "utf8");
|
|
10824
11489
|
await handle.sync();
|
|
10825
11490
|
await handle.close();
|
|
@@ -10832,17 +11497,17 @@ var LoopStore = class {
|
|
|
10832
11497
|
}
|
|
10833
11498
|
async load(loopId) {
|
|
10834
11499
|
const directory = await this.#safeExisting(loopId);
|
|
10835
|
-
const target =
|
|
11500
|
+
const target = join16(directory, "state.json");
|
|
10836
11501
|
try {
|
|
10837
11502
|
const metadata = await stat5(target);
|
|
10838
11503
|
if (!metadata.isFile() || metadata.size > this.#maxBytes) throw new Error("Loop state is not a bounded regular file");
|
|
10839
|
-
const raw = await
|
|
11504
|
+
const raw = await readFile11(target, "utf8");
|
|
10840
11505
|
const state = LoopStateSchema.parse(JSON.parse(raw));
|
|
10841
11506
|
await this.#assertWorkspace(state.workspace);
|
|
10842
11507
|
return state;
|
|
10843
11508
|
} catch (error) {
|
|
10844
11509
|
if (isCode4(error, "ENOENT")) throw new Error(`Loop "${loopId}" was not found`);
|
|
10845
|
-
const quarantine = `${target}.corrupt-${Date.now()}-${
|
|
11510
|
+
const quarantine = `${target}.corrupt-${Date.now()}-${randomUUID10().slice(0, 8)}`;
|
|
10846
11511
|
await rename5(target, quarantine).catch(() => void 0);
|
|
10847
11512
|
throw new Error(`Loop "${loopId}" is corrupt and was quarantined: ${message(error)}`);
|
|
10848
11513
|
}
|
|
@@ -10852,8 +11517,8 @@ var LoopStore = class {
|
|
|
10852
11517
|
const previous = this.#appendTails.get(event.loopId) ?? Promise.resolve();
|
|
10853
11518
|
const next = previous.catch(() => void 0).then(async () => {
|
|
10854
11519
|
const directory = await this.#prepare(event.loopId);
|
|
10855
|
-
const path =
|
|
10856
|
-
const handle = await open7(path,
|
|
11520
|
+
const path = join16(directory, "events.jsonl");
|
|
11521
|
+
const handle = await open7(path, constants3.O_CREAT | constants3.O_APPEND | constants3.O_WRONLY, 384);
|
|
10857
11522
|
try {
|
|
10858
11523
|
await handle.writeFile(`${JSON.stringify(event)}
|
|
10859
11524
|
`, "utf8");
|
|
@@ -10869,7 +11534,7 @@ var LoopStore = class {
|
|
|
10869
11534
|
await assertNoSymlink2(this.#workspace, dirname13(this.#root));
|
|
10870
11535
|
await mkdir13(this.#root, { recursive: true, mode: 448 });
|
|
10871
11536
|
await assertNoSymlink2(this.#workspace, this.#root);
|
|
10872
|
-
const directory =
|
|
11537
|
+
const directory = join16(this.#root, loopId);
|
|
10873
11538
|
await mkdir13(directory, { recursive: true, mode: 448 });
|
|
10874
11539
|
await assertNoSymlink2(this.#workspace, directory);
|
|
10875
11540
|
return directory;
|
|
@@ -10877,7 +11542,7 @@ var LoopStore = class {
|
|
|
10877
11542
|
async #safeExisting(loopId) {
|
|
10878
11543
|
validateId(loopId);
|
|
10879
11544
|
await assertNoSymlink2(this.#workspace, this.#root);
|
|
10880
|
-
const directory =
|
|
11545
|
+
const directory = join16(this.#root, loopId);
|
|
10881
11546
|
await assertNoSymlink2(this.#workspace, directory);
|
|
10882
11547
|
return directory;
|
|
10883
11548
|
}
|
|
@@ -10893,9 +11558,9 @@ function validateId(loopId) {
|
|
|
10893
11558
|
if (!LOOP_ID.test(loopId)) throw new Error("Invalid loop id");
|
|
10894
11559
|
}
|
|
10895
11560
|
async function assertNoSymlink2(root, target) {
|
|
10896
|
-
let cursor =
|
|
10897
|
-
const stop =
|
|
10898
|
-
if (!
|
|
11561
|
+
let cursor = resolve13(target);
|
|
11562
|
+
const stop = resolve13(root);
|
|
11563
|
+
if (!isWithin3(stop, cursor)) throw new Error("Loop path escapes workspace");
|
|
10899
11564
|
const chain = [];
|
|
10900
11565
|
while (cursor !== stop) {
|
|
10901
11566
|
chain.push(cursor);
|
|
@@ -10909,15 +11574,15 @@ async function assertNoSymlink2(root, target) {
|
|
|
10909
11574
|
}
|
|
10910
11575
|
}
|
|
10911
11576
|
}
|
|
10912
|
-
function
|
|
10913
|
-
const value = relative10(
|
|
10914
|
-
return value === "" || !value.startsWith(`..${
|
|
11577
|
+
function isWithin3(root, path) {
|
|
11578
|
+
const value = relative10(resolve13(root), resolve13(path));
|
|
11579
|
+
return value === "" || !value.startsWith(`..${sep10}`) && value !== ".." && !value.includes(":");
|
|
10915
11580
|
}
|
|
10916
11581
|
async function canonical(path) {
|
|
10917
11582
|
try {
|
|
10918
|
-
return await realpath5(
|
|
11583
|
+
return await realpath5(resolve13(path));
|
|
10919
11584
|
} catch {
|
|
10920
|
-
return
|
|
11585
|
+
return resolve13(path);
|
|
10921
11586
|
}
|
|
10922
11587
|
}
|
|
10923
11588
|
function isCode4(error, code) {
|
|
@@ -10925,25 +11590,25 @@ function isCode4(error, code) {
|
|
|
10925
11590
|
}
|
|
10926
11591
|
|
|
10927
11592
|
// src/loop/verifier.ts
|
|
10928
|
-
import { readFile as
|
|
10929
|
-
import { join as
|
|
11593
|
+
import { readFile as readFile12 } from "fs/promises";
|
|
11594
|
+
import { join as join17 } from "path";
|
|
10930
11595
|
|
|
10931
11596
|
// src/tools/shell.ts
|
|
10932
11597
|
import { spawn as spawn6, spawnSync } from "child_process";
|
|
10933
|
-
import { isAbsolute as isAbsolute6, relative as relative11, resolve as
|
|
10934
|
-
import { z as
|
|
11598
|
+
import { isAbsolute as isAbsolute6, relative as relative11, resolve as resolve14, sep as sep11 } from "path";
|
|
11599
|
+
import { z as z18 } from "zod";
|
|
10935
11600
|
|
|
10936
11601
|
// src/tools/jobs.ts
|
|
10937
|
-
import { z as
|
|
10938
|
-
var JobId =
|
|
10939
|
-
var ReadInput =
|
|
10940
|
-
var IdInput =
|
|
11602
|
+
import { z as z17 } from "zod";
|
|
11603
|
+
var JobId = z17.string().regex(/^job-[a-f0-9-]+$/);
|
|
11604
|
+
var ReadInput = z17.object({ id: JobId, cursor: z17.coerce.number().int().min(0).optional() });
|
|
11605
|
+
var IdInput = z17.object({ id: JobId });
|
|
10941
11606
|
function createJobTools(registry) {
|
|
10942
11607
|
const tools = [
|
|
10943
11608
|
{
|
|
10944
11609
|
name: "JobList",
|
|
10945
11610
|
description: "List background jobs owned by this agent",
|
|
10946
|
-
inputSchema:
|
|
11611
|
+
inputSchema: z17.object({}),
|
|
10947
11612
|
paths: () => [],
|
|
10948
11613
|
execute: async (_input, _signal, context) => registry.list(owner(context)),
|
|
10949
11614
|
presentResult: (output) => jobListPresentation(output)
|
|
@@ -11018,14 +11683,14 @@ var ELLIPSIS = "\u2026";
|
|
|
11018
11683
|
var TERMINATION_GRACE_MS = 250;
|
|
11019
11684
|
var TERMINATION_FAILURE_MS = 5e3;
|
|
11020
11685
|
var STREAM_CLOSE_GRACE_MS = 500;
|
|
11021
|
-
var ShellInput =
|
|
11022
|
-
command:
|
|
11023
|
-
args:
|
|
11024
|
-
cwd:
|
|
11025
|
-
timeoutMs:
|
|
11686
|
+
var ShellInput = z18.object({
|
|
11687
|
+
command: z18.string().min(1),
|
|
11688
|
+
args: z18.array(z18.string()),
|
|
11689
|
+
cwd: z18.string().min(1).nullable().optional(),
|
|
11690
|
+
timeoutMs: z18.coerce.number().int().positive().max(864e5).nullable().optional(),
|
|
11026
11691
|
// Accepts booleans and their string forms (weak-typed models emit "true");
|
|
11027
11692
|
// kept transform-free so the schema converts to JSON Schema for providers.
|
|
11028
|
-
background:
|
|
11693
|
+
background: z18.union([z18.boolean(), z18.string().refine((value) => value === "true" || value === "false")]).optional()
|
|
11029
11694
|
});
|
|
11030
11695
|
function normalizeShellCommand(input) {
|
|
11031
11696
|
const { command, args } = input;
|
|
@@ -11083,7 +11748,7 @@ function buildWindowsCommandLine(kind, commandLine, command, args) {
|
|
|
11083
11748
|
return `chcp 65001>nul & ${executable} -NoLogo -NoProfile -NonInteractive -ExecutionPolicy Bypass -EncodedCommand ${encoded}`;
|
|
11084
11749
|
}
|
|
11085
11750
|
function createShellTool(workspace, options = {}) {
|
|
11086
|
-
const root =
|
|
11751
|
+
const root = resolve14(workspace);
|
|
11087
11752
|
const maxBytes = options.maxOutputBytes ?? DEFAULT_MAX_OUTPUT_BYTES2;
|
|
11088
11753
|
const defaultTimeoutMs = options.defaultTimeoutMs ?? DEFAULT_SHELL_TIMEOUT_MS;
|
|
11089
11754
|
if (!Number.isSafeInteger(maxBytes) || maxBytes <= 0) throw new Error("maxOutputBytes must be a positive integer");
|
|
@@ -11372,9 +12037,9 @@ function backgroundFlag(value) {
|
|
|
11372
12037
|
return value === true || value === "true";
|
|
11373
12038
|
}
|
|
11374
12039
|
function workingDirectory(root, cwd = ".") {
|
|
11375
|
-
const candidate =
|
|
12040
|
+
const candidate = resolve14(root, cwd);
|
|
11376
12041
|
const delta = relative11(root, candidate);
|
|
11377
|
-
if (delta === ".." || delta.startsWith(`..${
|
|
12042
|
+
if (delta === ".." || delta.startsWith(`..${sep11}`) || isAbsolute6(delta)) throw new Error("Working directory is outside the workspace");
|
|
11378
12043
|
return candidate;
|
|
11379
12044
|
}
|
|
11380
12045
|
async function terminateTree(pid) {
|
|
@@ -11484,13 +12149,13 @@ var SCRIPT_PRIORITY = ["test", "typecheck", "lint", "build", "smoke:install"];
|
|
|
11484
12149
|
async function inferVerificationPlan(workspace) {
|
|
11485
12150
|
const commands = [];
|
|
11486
12151
|
const seen = /* @__PURE__ */ new Set();
|
|
11487
|
-
const packageJson = await optionalJson(
|
|
12152
|
+
const packageJson = await optionalJson(join17(workspace, "package.json"));
|
|
11488
12153
|
const scripts = isRecord2(packageJson?.scripts) ? packageJson.scripts : {};
|
|
11489
12154
|
for (const name of SCRIPT_PRIORITY) {
|
|
11490
12155
|
if (typeof scripts[name] !== "string" || isUnconditionalPassThrough(scripts[name])) continue;
|
|
11491
12156
|
add(commands, seen, npmScript(name));
|
|
11492
12157
|
}
|
|
11493
|
-
const flavor = await optionalText(
|
|
12158
|
+
const flavor = await optionalText(join17(workspace, "FLAVOR.md"));
|
|
11494
12159
|
if (flavor !== void 0) {
|
|
11495
12160
|
for (const match of flavor.matchAll(/^- `([^`]+)`\s*$/gm)) {
|
|
11496
12161
|
const parsed = parseTrustedCommand(match[1] ?? "");
|
|
@@ -11557,7 +12222,7 @@ function isUnconditionalPassThrough(script) {
|
|
|
11557
12222
|
}
|
|
11558
12223
|
async function optionalText(path) {
|
|
11559
12224
|
try {
|
|
11560
|
-
return await
|
|
12225
|
+
return await readFile12(path, "utf8");
|
|
11561
12226
|
} catch (error) {
|
|
11562
12227
|
if (isCode5(error, "ENOENT")) return void 0;
|
|
11563
12228
|
throw error;
|
|
@@ -11578,7 +12243,7 @@ function isCode5(error, code) {
|
|
|
11578
12243
|
|
|
11579
12244
|
// src/models/anthropic.ts
|
|
11580
12245
|
import Anthropic from "@anthropic-ai/sdk";
|
|
11581
|
-
import { readFile as
|
|
12246
|
+
import { readFile as readFile13 } from "fs/promises";
|
|
11582
12247
|
|
|
11583
12248
|
// src/utils/json.ts
|
|
11584
12249
|
function safeParseJson(input) {
|
|
@@ -11780,7 +12445,7 @@ var AnthropicModelAdapter = class {
|
|
|
11780
12445
|
source: {
|
|
11781
12446
|
type: "base64",
|
|
11782
12447
|
media_type: block.mediaType,
|
|
11783
|
-
data: (await
|
|
12448
|
+
data: (await readFile13(block.source.path)).toString("base64")
|
|
11784
12449
|
}
|
|
11785
12450
|
}));
|
|
11786
12451
|
if (message2.cacheBreakpoint && Array.isArray(content) && content.length > 0) {
|
|
@@ -11962,7 +12627,7 @@ function resolveCacheProfile(input) {
|
|
|
11962
12627
|
|
|
11963
12628
|
// src/models/openai.ts
|
|
11964
12629
|
import OpenAI from "openai";
|
|
11965
|
-
import { readFile as
|
|
12630
|
+
import { readFile as readFile14 } from "fs/promises";
|
|
11966
12631
|
async function toInput(message2) {
|
|
11967
12632
|
if (message2.role === "tool") {
|
|
11968
12633
|
if (!message2.toolCallId) throw new Error("Tool messages require toolCallId");
|
|
@@ -11974,7 +12639,7 @@ async function toInput(message2) {
|
|
|
11974
12639
|
}
|
|
11975
12640
|
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
12641
|
type: "input_image",
|
|
11977
|
-
image_url: `data:${block.mediaType};base64,${(await
|
|
12642
|
+
image_url: `data:${block.mediaType};base64,${(await readFile14(block.source.path)).toString("base64")}`,
|
|
11978
12643
|
detail: "auto"
|
|
11979
12644
|
}));
|
|
11980
12645
|
return [
|
|
@@ -12193,8 +12858,8 @@ var ModelRegistry = class {
|
|
|
12193
12858
|
};
|
|
12194
12859
|
|
|
12195
12860
|
// src/mcp/client.ts
|
|
12196
|
-
import { createHash as
|
|
12197
|
-
import { z as
|
|
12861
|
+
import { createHash as createHash11 } from "crypto";
|
|
12862
|
+
import { z as z19 } from "zod";
|
|
12198
12863
|
var McpManager = class {
|
|
12199
12864
|
#workspace;
|
|
12200
12865
|
#clientFactory;
|
|
@@ -12330,7 +12995,7 @@ async function listAllTools(client) {
|
|
|
12330
12995
|
function createMcpToolDefinition(serverName, timeoutMs, client, remote) {
|
|
12331
12996
|
if (remote.name.length === 0) throw new Error("MCP tool name must not be empty");
|
|
12332
12997
|
if (!isRecord3(remote.inputSchema)) throw new Error(`MCP tool "${remote.name}" has no JSON object input schema`);
|
|
12333
|
-
const inputSchema =
|
|
12998
|
+
const inputSchema = z19.fromJSONSchema(
|
|
12334
12999
|
remote.inputSchema
|
|
12335
13000
|
);
|
|
12336
13001
|
return {
|
|
@@ -12360,7 +13025,7 @@ function mcpToolName(serverName, remoteName) {
|
|
|
12360
13025
|
const safe = remoteName.replace(/[^A-Za-z0-9_-]/g, "_") || "tool";
|
|
12361
13026
|
const direct = `${prefix}${safe}`;
|
|
12362
13027
|
if (direct.length <= 64 && safe === remoteName) return direct;
|
|
12363
|
-
const suffix = `__${
|
|
13028
|
+
const suffix = `__${createHash11("sha256").update(remoteName).digest("hex").slice(0, 8)}`;
|
|
12364
13029
|
return `${prefix}${safe.slice(0, Math.max(1, 64 - prefix.length - suffix.length))}${suffix}`;
|
|
12365
13030
|
}
|
|
12366
13031
|
function mcpErrorText(content) {
|
|
@@ -12379,7 +13044,7 @@ function isRecord3(value) {
|
|
|
12379
13044
|
}
|
|
12380
13045
|
|
|
12381
13046
|
// src/mcp/sdk.ts
|
|
12382
|
-
import { resolve as
|
|
13047
|
+
import { resolve as resolve15 } from "path";
|
|
12383
13048
|
import { Client } from "@modelcontextprotocol/sdk/client/index.js";
|
|
12384
13049
|
import {
|
|
12385
13050
|
getDefaultEnvironment,
|
|
@@ -12401,7 +13066,7 @@ async function connectSdkMcpClient(input, dependencies = DEFAULT_DEPENDENCIES) {
|
|
|
12401
13066
|
command: config.command,
|
|
12402
13067
|
args: [...config.args],
|
|
12403
13068
|
env: { ...dependencies.defaultEnvironment(), ...config.env },
|
|
12404
|
-
cwd:
|
|
13069
|
+
cwd: resolve15(input.workspace, config.cwd ?? "."),
|
|
12405
13070
|
// MCP servers frequently log startup banners to stderr. Inheriting that
|
|
12406
13071
|
// stream corrupts Ink's input area, so connection errors are surfaced
|
|
12407
13072
|
// through McpManager diagnostics instead.
|
|
@@ -12426,14 +13091,14 @@ async function connectSdkMcpClient(input, dependencies = DEFAULT_DEPENDENCIES) {
|
|
|
12426
13091
|
}
|
|
12427
13092
|
|
|
12428
13093
|
// src/auth/oauth.ts
|
|
12429
|
-
import { randomBytes as randomBytes2, createHash as
|
|
13094
|
+
import { randomBytes as randomBytes2, createHash as createHash13 } from "crypto";
|
|
12430
13095
|
import { createServer as createServer2 } from "http";
|
|
12431
13096
|
import { URL as URL2, URLSearchParams } from "url";
|
|
12432
13097
|
|
|
12433
13098
|
// src/auth/oauth-config.ts
|
|
12434
|
-
import { createHash as
|
|
13099
|
+
import { createHash as createHash12 } from "crypto";
|
|
12435
13100
|
function oauthCredentialId(tokenUrl, clientId) {
|
|
12436
|
-
const digest =
|
|
13101
|
+
const digest = createHash12("sha256").update(`${tokenUrl}\0${clientId}`, "utf8").digest("base64url");
|
|
12437
13102
|
return `oauth:${digest}`;
|
|
12438
13103
|
}
|
|
12439
13104
|
function parseOAuthLlmConfig(value) {
|
|
@@ -12606,8 +13271,8 @@ var OAuthCallbackAuthProvider = class {
|
|
|
12606
13271
|
async #startCallbackServer(expectedState, signal) {
|
|
12607
13272
|
let serverResolve;
|
|
12608
13273
|
let serverReject;
|
|
12609
|
-
const codePromise = new Promise((
|
|
12610
|
-
serverResolve =
|
|
13274
|
+
const codePromise = new Promise((resolve28, reject) => {
|
|
13275
|
+
serverResolve = resolve28;
|
|
12611
13276
|
serverReject = reject;
|
|
12612
13277
|
});
|
|
12613
13278
|
const server = createServer2((req, res) => {
|
|
@@ -12641,7 +13306,7 @@ var OAuthCallbackAuthProvider = class {
|
|
|
12641
13306
|
);
|
|
12642
13307
|
serverResolve(code);
|
|
12643
13308
|
});
|
|
12644
|
-
return new Promise((
|
|
13309
|
+
return new Promise((resolve28, reject) => {
|
|
12645
13310
|
server.once("error", (error) => {
|
|
12646
13311
|
reject(new Error(`OAuth callback server failed: ${error.message}`));
|
|
12647
13312
|
});
|
|
@@ -12658,7 +13323,7 @@ var OAuthCallbackAuthProvider = class {
|
|
|
12658
13323
|
signal.addEventListener("abort", () => {
|
|
12659
13324
|
serverReject(new Error("OAuth flow aborted"));
|
|
12660
13325
|
}, { once: true });
|
|
12661
|
-
|
|
13326
|
+
resolve28({ redirectUri, promise: codePromise });
|
|
12662
13327
|
});
|
|
12663
13328
|
});
|
|
12664
13329
|
}
|
|
@@ -12690,7 +13355,7 @@ var OAuthCallbackAuthProvider = class {
|
|
|
12690
13355
|
return randomBytes2(32).toString("base64url");
|
|
12691
13356
|
}
|
|
12692
13357
|
#computeCodeChallenge(verifier) {
|
|
12693
|
-
return
|
|
13358
|
+
return createHash13("sha256").update(verifier).digest("base64url");
|
|
12694
13359
|
}
|
|
12695
13360
|
#generateState() {
|
|
12696
13361
|
return randomBytes2(32).toString("base64url");
|
|
@@ -12799,6 +13464,141 @@ function isStoredToken(value) {
|
|
|
12799
13464
|
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
13465
|
}
|
|
12801
13466
|
|
|
13467
|
+
// src/permissions/policy.ts
|
|
13468
|
+
import { readFile as readFile15 } from "fs/promises";
|
|
13469
|
+
import { join as join18, resolve as resolve16 } from "path";
|
|
13470
|
+
import { z as z20 } from "zod";
|
|
13471
|
+
var DecisionSchema2 = z20.enum(["allow", "ask", "deny"]);
|
|
13472
|
+
var PrefixSchema = z20.array(z20.string().min(1).max(1024)).min(1).max(64);
|
|
13473
|
+
var RuleSchema = z20.object({
|
|
13474
|
+
id: z20.string().regex(/^[A-Za-z0-9][A-Za-z0-9._-]{0,127}$/),
|
|
13475
|
+
decision: DecisionSchema2,
|
|
13476
|
+
prefix: PrefixSchema,
|
|
13477
|
+
tool: z20.string().min(1).max(256).optional(),
|
|
13478
|
+
agent: z20.enum(["main", "subagent"]).optional(),
|
|
13479
|
+
justification: z20.string().min(1).max(2048).optional(),
|
|
13480
|
+
match: z20.array(PrefixSchema).max(100).optional(),
|
|
13481
|
+
notMatch: z20.array(PrefixSchema).max(100).optional()
|
|
13482
|
+
}).strict();
|
|
13483
|
+
var PolicyFileSchema = z20.object({ version: z20.literal(1), rules: z20.array(RuleSchema).max(1e4) }).strict();
|
|
13484
|
+
var CompiledPermissionPolicy = class {
|
|
13485
|
+
#rules;
|
|
13486
|
+
diagnostics;
|
|
13487
|
+
constructor(rules, diagnostics = []) {
|
|
13488
|
+
this.#rules = [...rules];
|
|
13489
|
+
this.diagnostics = [...diagnostics];
|
|
13490
|
+
}
|
|
13491
|
+
get rules() {
|
|
13492
|
+
return this.#rules.map((rule) => ({ ...rule, prefix: [...rule.prefix] }));
|
|
13493
|
+
}
|
|
13494
|
+
decide(request) {
|
|
13495
|
+
const tokens = requestTokens(request);
|
|
13496
|
+
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));
|
|
13497
|
+
if (matches.length === 0) return void 0;
|
|
13498
|
+
const strictest = matches.reduce((selected, candidate) => rank(candidate.decision) > rank(selected.decision) ? candidate : selected);
|
|
13499
|
+
const reason = strictest.justification ?? `Permission rule ${strictest.id} from ${strictest.tier} policy`;
|
|
13500
|
+
return { decision: strictest.decision, reason };
|
|
13501
|
+
}
|
|
13502
|
+
};
|
|
13503
|
+
async function loadPermissionPolicy(options) {
|
|
13504
|
+
const sources = [
|
|
13505
|
+
...options.managedPath === void 0 ? [] : [{ tier: "managed", path: resolve16(options.managedPath) }],
|
|
13506
|
+
{ tier: "user", path: join18(resolve16(options.home), ".flavor-code", "permissions.json") },
|
|
13507
|
+
{ tier: "project", path: join18(resolve16(options.workspace), ".flavor", "permissions.json") },
|
|
13508
|
+
{ tier: "local", path: join18(resolve16(options.workspace), ".flavor", "permissions.local.json") }
|
|
13509
|
+
];
|
|
13510
|
+
const rules = [];
|
|
13511
|
+
for (const source of sources) {
|
|
13512
|
+
let raw;
|
|
13513
|
+
try {
|
|
13514
|
+
raw = await readFile15(source.path, "utf8");
|
|
13515
|
+
} catch (error) {
|
|
13516
|
+
if (isCode6(error, "ENOENT")) continue;
|
|
13517
|
+
throw error;
|
|
13518
|
+
}
|
|
13519
|
+
let parsed;
|
|
13520
|
+
try {
|
|
13521
|
+
parsed = PolicyFileSchema.parse(JSON.parse(raw));
|
|
13522
|
+
} catch (error) {
|
|
13523
|
+
throw new Error(`Invalid ${source.tier} permission policy ${source.path}: ${errorMessage(error)}`);
|
|
13524
|
+
}
|
|
13525
|
+
for (const input of parsed.rules) {
|
|
13526
|
+
validateExamples(input, source.path);
|
|
13527
|
+
rules.push({ ...input, source: source.path, tier: source.tier });
|
|
13528
|
+
}
|
|
13529
|
+
}
|
|
13530
|
+
for (const input of options.sessionRules ?? []) {
|
|
13531
|
+
const rule = RuleSchema.parse(input);
|
|
13532
|
+
validateExamples(rule, "session");
|
|
13533
|
+
rules.push({ ...rule, source: "session", tier: "session" });
|
|
13534
|
+
}
|
|
13535
|
+
const ids = /* @__PURE__ */ new Set();
|
|
13536
|
+
for (const rule of rules) {
|
|
13537
|
+
if (ids.has(rule.id)) throw new Error(`Duplicate permission rule id: ${rule.id}`);
|
|
13538
|
+
ids.add(rule.id);
|
|
13539
|
+
}
|
|
13540
|
+
return new CompiledPermissionPolicy(rules, shadowDiagnostics(rules));
|
|
13541
|
+
}
|
|
13542
|
+
function validateExamples(rule, source) {
|
|
13543
|
+
for (const example of rule.match ?? []) {
|
|
13544
|
+
if (!prefixMatches(rule.prefix, example)) throw new Error(`Permission rule ${rule.id} in ${source} failed a match example`);
|
|
13545
|
+
}
|
|
13546
|
+
for (const example of rule.notMatch ?? []) {
|
|
13547
|
+
if (prefixMatches(rule.prefix, example)) throw new Error(`Permission rule ${rule.id} in ${source} failed a notMatch example`);
|
|
13548
|
+
}
|
|
13549
|
+
}
|
|
13550
|
+
function shadowDiagnostics(rules) {
|
|
13551
|
+
const diagnostics = [];
|
|
13552
|
+
for (let index = 0; index < rules.length; index += 1) {
|
|
13553
|
+
const rule = rules[index];
|
|
13554
|
+
const shadow = rules.slice(0, index).find((candidate) => sameScope(candidate, rule) && prefixMatches(candidate.prefix, rule.prefix) && rank(candidate.decision) >= rank(rule.decision));
|
|
13555
|
+
if (shadow !== void 0) diagnostics.push(`Permission rule ${rule.id} is shadowed by ${shadow.id}`);
|
|
13556
|
+
}
|
|
13557
|
+
return diagnostics;
|
|
13558
|
+
}
|
|
13559
|
+
function sameScope(left, right) {
|
|
13560
|
+
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);
|
|
13561
|
+
}
|
|
13562
|
+
function requestTokens(request) {
|
|
13563
|
+
if (request.command === void 0) return [request.tool];
|
|
13564
|
+
if (request.args !== void 0 && request.args.length > 0) return [request.command, ...request.args];
|
|
13565
|
+
return splitCommand(request.command);
|
|
13566
|
+
}
|
|
13567
|
+
function splitCommand(command) {
|
|
13568
|
+
const result = [];
|
|
13569
|
+
let current = "";
|
|
13570
|
+
let quote;
|
|
13571
|
+
for (const character of command.trim()) {
|
|
13572
|
+
if (character === "'" || character === '"') {
|
|
13573
|
+
if (quote === character) quote = void 0;
|
|
13574
|
+
else if (quote === void 0) quote = character;
|
|
13575
|
+
else current += character;
|
|
13576
|
+
} else if (/\s/.test(character) && quote === void 0) {
|
|
13577
|
+
if (current.length > 0) {
|
|
13578
|
+
result.push(current);
|
|
13579
|
+
current = "";
|
|
13580
|
+
}
|
|
13581
|
+
} else current += character;
|
|
13582
|
+
}
|
|
13583
|
+
if (current.length > 0) result.push(current);
|
|
13584
|
+
return result;
|
|
13585
|
+
}
|
|
13586
|
+
function prefixMatches(prefix, input) {
|
|
13587
|
+
return prefix.length <= input.length && prefix.every((token, index) => same(token, input[index] ?? ""));
|
|
13588
|
+
}
|
|
13589
|
+
function same(left, right) {
|
|
13590
|
+
return process.platform === "win32" ? left.toLowerCase() === right.toLowerCase() : left === right;
|
|
13591
|
+
}
|
|
13592
|
+
function rank(decision) {
|
|
13593
|
+
return decision === "deny" ? 2 : decision === "ask" ? 1 : 0;
|
|
13594
|
+
}
|
|
13595
|
+
function isCode6(error, code) {
|
|
13596
|
+
return typeof error === "object" && error !== null && "code" in error && error.code === code;
|
|
13597
|
+
}
|
|
13598
|
+
function errorMessage(error) {
|
|
13599
|
+
return error instanceof Error ? error.message : String(error);
|
|
13600
|
+
}
|
|
13601
|
+
|
|
12802
13602
|
// src/prompts/system.ts
|
|
12803
13603
|
function buildSystemPrompt(options) {
|
|
12804
13604
|
const sections = [
|
|
@@ -12925,9 +13725,9 @@ function data(value) {
|
|
|
12925
13725
|
}
|
|
12926
13726
|
|
|
12927
13727
|
// 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
|
|
13728
|
+
import { constants as constants4 } from "fs";
|
|
13729
|
+
import { lstat as lstat8, open as defaultOpen, readdir as readdir5, realpath as realpath6 } from "fs/promises";
|
|
13730
|
+
import { isAbsolute as isAbsolute7, relative as relative12, resolve as resolve17, sep as sep12 } from "path";
|
|
12931
13731
|
import { marked } from "marked";
|
|
12932
13732
|
import { parseDocument } from "yaml";
|
|
12933
13733
|
var SKILL_NAME = /^[a-z0-9]+(?:-[a-z0-9]+)*$/;
|
|
@@ -13058,7 +13858,7 @@ var SkillRegistry = class {
|
|
|
13058
13858
|
const body = await this.loadBody(record.metadata);
|
|
13059
13859
|
const references = extractResourceReferences(body);
|
|
13060
13860
|
if (!references.has(normalized)) throw new Error(`Skill resource is not directly referenced: ${reference}`);
|
|
13061
|
-
const candidate =
|
|
13861
|
+
const candidate = resolve17(record.metadata.root, ...parts);
|
|
13062
13862
|
const file = await openVerifiedFile(candidate, record.metadata.root, "Resource", this.#options.openFile);
|
|
13063
13863
|
try {
|
|
13064
13864
|
if (file.snapshot.size > BigInt(this.#options.maxResourceBytes)) throw new Error(`Skill resource is too large: ${reference}`);
|
|
@@ -13087,14 +13887,14 @@ var SkillRegistry = class {
|
|
|
13087
13887
|
async #discoverSource(roots, source) {
|
|
13088
13888
|
const found = [];
|
|
13089
13889
|
for (const configuredRoot of roots) {
|
|
13090
|
-
const root =
|
|
13890
|
+
const root = resolve17(configuredRoot);
|
|
13091
13891
|
let entries;
|
|
13092
13892
|
let physicalRoot = root;
|
|
13093
13893
|
try {
|
|
13094
13894
|
const rootInfo = await lstat8(root);
|
|
13095
13895
|
if (rootInfo.isSymbolicLink() || !rootInfo.isDirectory()) throw new Error("Skill root must be a real directory");
|
|
13096
13896
|
physicalRoot = await realpath6(root);
|
|
13097
|
-
entries = await
|
|
13897
|
+
entries = await readdir5(root, { withFileTypes: true });
|
|
13098
13898
|
} catch (error) {
|
|
13099
13899
|
if (isMissing2(error)) continue;
|
|
13100
13900
|
this.#diagnose(root, error);
|
|
@@ -13102,13 +13902,13 @@ var SkillRegistry = class {
|
|
|
13102
13902
|
}
|
|
13103
13903
|
entries.sort((left, right) => compareCodePoints(left.name, right.name));
|
|
13104
13904
|
for (const entry2 of entries) {
|
|
13105
|
-
const skillRoot =
|
|
13905
|
+
const skillRoot = resolve17(root, entry2.name);
|
|
13106
13906
|
try {
|
|
13107
13907
|
if (entry2.isSymbolicLink()) throw new Error("Symlinked skill directories are not allowed");
|
|
13108
13908
|
if (!entry2.isDirectory()) continue;
|
|
13109
13909
|
const physicalSkillRoot = await realpath6(skillRoot);
|
|
13110
|
-
if (!
|
|
13111
|
-
const skillFile =
|
|
13910
|
+
if (!isWithin4(physicalRoot, physicalSkillRoot)) throw new Error("Skill directory escapes its registry root");
|
|
13911
|
+
const skillFile = resolve17(skillRoot, "SKILL.md");
|
|
13112
13912
|
const parsed = await readFrontmatterFile(
|
|
13113
13913
|
skillFile,
|
|
13114
13914
|
physicalSkillRoot,
|
|
@@ -13249,9 +14049,9 @@ async function openVerifiedFile(path, root, label, opener, expected) {
|
|
|
13249
14049
|
if (initial.isSymbolicLink()) throw new Error(`${label} symlinks are not allowed`);
|
|
13250
14050
|
assertRegular(initial, label);
|
|
13251
14051
|
const canonical3 = await realpath6(path);
|
|
13252
|
-
if (!
|
|
13253
|
-
const noFollow = typeof
|
|
13254
|
-
const handle = await opener(canonical3,
|
|
14052
|
+
if (!isWithin4(root, canonical3)) throw new Error(`${label} escapes the skill root`);
|
|
14053
|
+
const noFollow = typeof constants4.O_NOFOLLOW === "number" ? constants4.O_NOFOLLOW : 0;
|
|
14054
|
+
const handle = await opener(canonical3, constants4.O_RDONLY | noFollow);
|
|
13255
14055
|
try {
|
|
13256
14056
|
const opened = await handle.stat({ bigint: true });
|
|
13257
14057
|
assertRegular(opened, label);
|
|
@@ -13308,9 +14108,9 @@ function decodeUtf82(content, label) {
|
|
|
13308
14108
|
throw new Error(`${label} contains invalid UTF-8 encoding`, { cause: error });
|
|
13309
14109
|
}
|
|
13310
14110
|
}
|
|
13311
|
-
function
|
|
14111
|
+
function isWithin4(root, candidate) {
|
|
13312
14112
|
const delta = relative12(root, candidate);
|
|
13313
|
-
return delta === "" || delta !== ".." && !delta.startsWith(`..${
|
|
14113
|
+
return delta === "" || delta !== ".." && !delta.startsWith(`..${sep12}`) && !isAbsolute7(delta);
|
|
13314
14114
|
}
|
|
13315
14115
|
function isMissing2(error) {
|
|
13316
14116
|
return typeof error === "object" && error !== null && "code" in error && error.code === "ENOENT";
|
|
@@ -13357,7 +14157,7 @@ function score(metadata, query) {
|
|
|
13357
14157
|
}
|
|
13358
14158
|
|
|
13359
14159
|
// src/skills/tool.ts
|
|
13360
|
-
import { z as
|
|
14160
|
+
import { z as z21 } from "zod";
|
|
13361
14161
|
|
|
13362
14162
|
// src/skills/arguments.ts
|
|
13363
14163
|
function expandSkillArguments(body, argumentsText) {
|
|
@@ -13414,13 +14214,13 @@ function splitSkillArguments(input) {
|
|
|
13414
14214
|
}
|
|
13415
14215
|
|
|
13416
14216
|
// src/skills/tool.ts
|
|
13417
|
-
var SkillInput =
|
|
13418
|
-
skill:
|
|
13419
|
-
arguments:
|
|
14217
|
+
var SkillInput = z21.object({
|
|
14218
|
+
skill: z21.string().min(1),
|
|
14219
|
+
arguments: z21.string().optional()
|
|
13420
14220
|
}).strict();
|
|
13421
|
-
var SkillResourceInput =
|
|
13422
|
-
skill:
|
|
13423
|
-
reference:
|
|
14221
|
+
var SkillResourceInput = z21.object({
|
|
14222
|
+
skill: z21.string().min(1),
|
|
14223
|
+
reference: z21.string().min(1)
|
|
13424
14224
|
}).strict();
|
|
13425
14225
|
function createSkillTool(registry) {
|
|
13426
14226
|
return {
|
|
@@ -13472,98 +14272,125 @@ function createSkillResourceTool(registry) {
|
|
|
13472
14272
|
}
|
|
13473
14273
|
|
|
13474
14274
|
// 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 =
|
|
14275
|
+
import { constants as constants5 } from "fs";
|
|
14276
|
+
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";
|
|
14277
|
+
import { basename as basename3, dirname as dirname15, join as join19, relative as relative13, resolve as resolve18, sep as sep13 } from "path";
|
|
14278
|
+
import { randomUUID as randomUUID11 } from "crypto";
|
|
14279
|
+
import { z as z22 } from "zod";
|
|
14280
|
+
var SESSION_VERSION = 4;
|
|
13481
14281
|
var DEFAULT_MAX_SESSION_BYTES = 5 * 1024 * 1024;
|
|
13482
|
-
var SessionIdSchema =
|
|
13483
|
-
var IsoDateSchema =
|
|
13484
|
-
var ToolCallSchema =
|
|
13485
|
-
id:
|
|
13486
|
-
name:
|
|
13487
|
-
input:
|
|
14282
|
+
var SessionIdSchema = z22.string().regex(/^[A-Za-z0-9][A-Za-z0-9._-]{0,127}$/, "Invalid session id");
|
|
14283
|
+
var IsoDateSchema = z22.string().datetime({ offset: true });
|
|
14284
|
+
var ToolCallSchema = z22.object({
|
|
14285
|
+
id: z22.string().min(1).max(256),
|
|
14286
|
+
name: z22.string().min(1).max(256),
|
|
14287
|
+
input: z22.unknown()
|
|
13488
14288
|
}).strict();
|
|
13489
|
-
var TextContentBlockSchema =
|
|
13490
|
-
type:
|
|
13491
|
-
text:
|
|
14289
|
+
var TextContentBlockSchema = z22.object({
|
|
14290
|
+
type: z22.literal("text"),
|
|
14291
|
+
text: z22.string().max(DEFAULT_MAX_SESSION_BYTES)
|
|
13492
14292
|
}).strict();
|
|
13493
|
-
var ImageContentBlockSchema =
|
|
13494
|
-
type:
|
|
13495
|
-
source:
|
|
13496
|
-
mediaType:
|
|
13497
|
-
sha256:
|
|
13498
|
-
bytes:
|
|
13499
|
-
name:
|
|
14293
|
+
var ImageContentBlockSchema = z22.object({
|
|
14294
|
+
type: z22.literal("image"),
|
|
14295
|
+
source: z22.object({ type: z22.literal("file"), path: z22.string().min(1).max(32768) }).strict(),
|
|
14296
|
+
mediaType: z22.enum(["image/png", "image/jpeg", "image/webp"]),
|
|
14297
|
+
sha256: z22.string().regex(/^[a-f0-9]{64}$/),
|
|
14298
|
+
bytes: z22.number().int().positive().max(20 * 1024 * 1024),
|
|
14299
|
+
name: z22.string().min(1).max(255).optional()
|
|
13500
14300
|
}).strict();
|
|
13501
|
-
var UserContentSchema =
|
|
13502
|
-
|
|
13503
|
-
|
|
14301
|
+
var UserContentSchema = z22.union([
|
|
14302
|
+
z22.string().max(DEFAULT_MAX_SESSION_BYTES),
|
|
14303
|
+
z22.array(z22.discriminatedUnion("type", [TextContentBlockSchema, ImageContentBlockSchema])).min(1).max(100)
|
|
13504
14304
|
]);
|
|
13505
14305
|
var MessageMetadataShape = {
|
|
13506
|
-
toolCallId:
|
|
13507
|
-
toolCalls:
|
|
14306
|
+
toolCallId: z22.string().min(1).max(256).optional(),
|
|
14307
|
+
toolCalls: z22.array(ToolCallSchema).max(1e3).optional()
|
|
13508
14308
|
};
|
|
13509
|
-
var MessageSchema =
|
|
13510
|
-
|
|
13511
|
-
|
|
13512
|
-
|
|
14309
|
+
var MessageSchema = z22.discriminatedUnion("role", [
|
|
14310
|
+
z22.object({ role: z22.literal("user"), content: UserContentSchema, ...MessageMetadataShape }).strict(),
|
|
14311
|
+
z22.object({ role: z22.literal("system"), content: z22.string().max(DEFAULT_MAX_SESSION_BYTES), ...MessageMetadataShape }).strict(),
|
|
14312
|
+
z22.object({ role: z22.literal("assistant"), content: z22.string().max(DEFAULT_MAX_SESSION_BYTES), ...MessageMetadataShape }).strict(),
|
|
14313
|
+
z22.object({ role: z22.literal("tool"), content: z22.string().max(DEFAULT_MAX_SESSION_BYTES), ...MessageMetadataShape }).strict()
|
|
13513
14314
|
]);
|
|
13514
|
-
var LegacySummarySchema =
|
|
13515
|
-
var CompactBoundarySchema =
|
|
13516
|
-
summary:
|
|
14315
|
+
var LegacySummarySchema = z22.object({ role: z22.literal("system"), content: z22.string().max(DEFAULT_MAX_SESSION_BYTES) }).strict();
|
|
14316
|
+
var CompactBoundarySchema = z22.object({
|
|
14317
|
+
summary: z22.string().min(1).max(DEFAULT_MAX_SESSION_BYTES),
|
|
13517
14318
|
compactedAt: IsoDateSchema
|
|
13518
14319
|
}).strict();
|
|
13519
|
-
var StateSchema =
|
|
13520
|
-
var MemoryLifecycleSchema =
|
|
13521
|
-
status:
|
|
13522
|
-
taskId:
|
|
13523
|
-
messageStart:
|
|
14320
|
+
var StateSchema = z22.enum(["pending", "running", "completed", "failed", "blocked", "cancelled"]);
|
|
14321
|
+
var MemoryLifecycleSchema = z22.object({
|
|
14322
|
+
status: z22.enum(["active", "completed", "abandoned"]),
|
|
14323
|
+
taskId: z22.string().regex(/^memory-[A-Za-z0-9._-]{1,120}$/).optional(),
|
|
14324
|
+
messageStart: z22.number().int().min(0).max(5e4).optional(),
|
|
13524
14325
|
finalizedAt: IsoDateSchema.optional(),
|
|
13525
|
-
transcriptHash:
|
|
14326
|
+
transcriptHash: z22.string().regex(/^[a-f0-9]{64}$/).optional()
|
|
13526
14327
|
}).strict();
|
|
13527
|
-
var SessionBaseSchema =
|
|
14328
|
+
var SessionBaseSchema = z22.object({
|
|
13528
14329
|
sessionId: SessionIdSchema,
|
|
13529
14330
|
createdAt: IsoDateSchema,
|
|
13530
14331
|
updatedAt: IsoDateSchema,
|
|
13531
|
-
workspace:
|
|
13532
|
-
tasks:
|
|
14332
|
+
workspace: z22.object({ path: z22.string().min(1).max(32768) }).strict(),
|
|
14333
|
+
tasks: z22.object({
|
|
13533
14334
|
plan: TaskPlanSchema.optional(),
|
|
13534
14335
|
graph: TaskGraphSchema.optional(),
|
|
13535
|
-
states:
|
|
13536
|
-
results:
|
|
14336
|
+
states: z22.record(z22.string(), StateSchema),
|
|
14337
|
+
results: z22.record(z22.string(), SubagentResultSchema)
|
|
13537
14338
|
}).strict(),
|
|
13538
|
-
models:
|
|
14339
|
+
models: z22.object({ main: z22.string().min(1).max(1024), subagent: z22.string().min(1).max(1024) }).strict(),
|
|
13539
14340
|
permissionMode: PermissionModeSchema,
|
|
13540
14341
|
memory: MemoryLifecycleSchema.optional()
|
|
13541
14342
|
}).strict();
|
|
13542
14343
|
var SessionDocumentV1Schema = SessionBaseSchema.extend({
|
|
13543
|
-
version:
|
|
13544
|
-
conversation:
|
|
14344
|
+
version: z22.literal(1),
|
|
14345
|
+
conversation: z22.object({
|
|
13545
14346
|
summary: LegacySummarySchema.optional(),
|
|
13546
|
-
messages:
|
|
14347
|
+
messages: z22.array(MessageSchema).max(5e4)
|
|
13547
14348
|
}).strict()
|
|
13548
14349
|
}).strict();
|
|
13549
|
-
var ConversationSchema =
|
|
14350
|
+
var ConversationSchema = z22.object({
|
|
13550
14351
|
compact: CompactBoundarySchema.optional(),
|
|
13551
|
-
messages:
|
|
14352
|
+
messages: z22.array(MessageSchema).max(5e4)
|
|
13552
14353
|
}).strict();
|
|
13553
14354
|
var SessionDocumentV2Schema = SessionBaseSchema.extend({
|
|
13554
|
-
version:
|
|
14355
|
+
version: z22.literal(2),
|
|
13555
14356
|
conversation: ConversationSchema
|
|
13556
14357
|
}).strict();
|
|
13557
|
-
var TranscriptStateSchema =
|
|
13558
|
-
var TimelineSchema =
|
|
13559
|
-
version:
|
|
14358
|
+
var TranscriptStateSchema = z22.custom(isTranscriptState, "Invalid transcript state");
|
|
14359
|
+
var TimelineSchema = z22.object({
|
|
14360
|
+
version: z22.literal(1),
|
|
13560
14361
|
state: TranscriptStateSchema
|
|
13561
14362
|
}).strict();
|
|
14363
|
+
var ContextEpochSchema = z22.object({
|
|
14364
|
+
version: z22.literal(1),
|
|
14365
|
+
id: z22.string().uuid(),
|
|
14366
|
+
startedAt: IsoDateSchema,
|
|
14367
|
+
stableMessages: z22.array(z22.object({
|
|
14368
|
+
role: z22.literal("system"),
|
|
14369
|
+
content: z22.string().max(DEFAULT_MAX_SESSION_BYTES),
|
|
14370
|
+
cacheBreakpoint: z22.boolean().optional()
|
|
14371
|
+
}).strict()).max(1e3),
|
|
14372
|
+
sources: z22.record(z22.string().min(1).max(128), z22.string().max(DEFAULT_MAX_SESSION_BYTES)),
|
|
14373
|
+
stableSourceHash: z22.string().regex(/^[a-f0-9]{64}$/)
|
|
14374
|
+
}).strict();
|
|
14375
|
+
var ContextVisibilityRecordSchema = z22.object({
|
|
14376
|
+
id: z22.string().uuid(),
|
|
14377
|
+
role: z22.literal("system"),
|
|
14378
|
+
content: z22.string().max(DEFAULT_MAX_SESSION_BYTES),
|
|
14379
|
+
admittedAt: IsoDateSchema,
|
|
14380
|
+
scope: z22.literal("run")
|
|
14381
|
+
}).strict();
|
|
14382
|
+
var SessionDocumentV3Schema = SessionBaseSchema.extend({
|
|
14383
|
+
version: z22.literal(3),
|
|
14384
|
+
conversation: ConversationSchema,
|
|
14385
|
+
timeline: TimelineSchema
|
|
14386
|
+
}).strict();
|
|
13562
14387
|
var SessionDocumentSchema = SessionBaseSchema.extend({
|
|
13563
|
-
version:
|
|
13564
|
-
conversation:
|
|
14388
|
+
version: z22.literal(SESSION_VERSION),
|
|
14389
|
+
conversation: z22.object({
|
|
13565
14390
|
compact: CompactBoundarySchema.optional(),
|
|
13566
|
-
|
|
14391
|
+
epoch: ContextEpochSchema.optional(),
|
|
14392
|
+
visibilityLog: z22.array(ContextVisibilityRecordSchema).max(1e4).optional(),
|
|
14393
|
+
messages: z22.array(MessageSchema).max(5e4)
|
|
13567
14394
|
}).strict(),
|
|
13568
14395
|
timeline: TimelineSchema
|
|
13569
14396
|
}).strict();
|
|
@@ -13573,8 +14400,8 @@ var SessionStore = class {
|
|
|
13573
14400
|
#maxBytes;
|
|
13574
14401
|
#maxSessions;
|
|
13575
14402
|
constructor(options) {
|
|
13576
|
-
this.#workspace =
|
|
13577
|
-
this.#sessions =
|
|
14403
|
+
this.#workspace = resolve18(options.workspace);
|
|
14404
|
+
this.#sessions = join19(this.#workspace, ".flavor", "sessions");
|
|
13578
14405
|
this.#maxBytes = options.maxBytes ?? DEFAULT_MAX_SESSION_BYTES;
|
|
13579
14406
|
if (!Number.isSafeInteger(this.#maxBytes) || this.#maxBytes < 256) throw new Error("maxBytes must be an integer of at least 256");
|
|
13580
14407
|
this.#maxSessions = options.maxSessions ?? 50;
|
|
@@ -13585,11 +14412,13 @@ var SessionStore = class {
|
|
|
13585
14412
|
await this.#assertWorkspace(document.workspace.path);
|
|
13586
14413
|
await this.#prepareDirectory();
|
|
13587
14414
|
const target = this.#path(document.sessionId);
|
|
13588
|
-
const { conversation: { messages, compact }, timeline, ...meta } = document;
|
|
14415
|
+
const { conversation: { messages, compact, epoch, visibilityLog }, timeline, ...meta } = document;
|
|
13589
14416
|
const metaLine = JSON.stringify({
|
|
13590
14417
|
__meta: true,
|
|
13591
14418
|
...meta,
|
|
13592
14419
|
...compact === void 0 ? {} : { compact },
|
|
14420
|
+
...epoch === void 0 ? {} : { epoch },
|
|
14421
|
+
...visibilityLog === void 0 ? {} : { visibilityLog },
|
|
13593
14422
|
timeline: {
|
|
13594
14423
|
version: timeline.version,
|
|
13595
14424
|
nextId: timeline.state.nextId,
|
|
@@ -13604,10 +14433,10 @@ var SessionStore = class {
|
|
|
13604
14433
|
const body = `${lines.join("\n")}
|
|
13605
14434
|
`;
|
|
13606
14435
|
if (Buffer.byteLength(body) > this.#maxBytes) throw new Error(`Session exceeds maximum size of ${this.#maxBytes} bytes`);
|
|
13607
|
-
const temporary =
|
|
14436
|
+
const temporary = join19(this.#sessions, `.${document.sessionId}.${process.pid}.${randomUUID11()}.tmp`);
|
|
13608
14437
|
let handle;
|
|
13609
14438
|
try {
|
|
13610
|
-
handle = await open8(temporary,
|
|
14439
|
+
handle = await open8(temporary, constants5.O_CREAT | constants5.O_EXCL | constants5.O_WRONLY, 384);
|
|
13611
14440
|
await handle.writeFile(body, "utf8");
|
|
13612
14441
|
await handle.sync();
|
|
13613
14442
|
await handle.close();
|
|
@@ -13632,7 +14461,7 @@ var SessionStore = class {
|
|
|
13632
14461
|
try {
|
|
13633
14462
|
raw = await boundedRead(path, this.#maxBytes);
|
|
13634
14463
|
} catch (error) {
|
|
13635
|
-
if (
|
|
14464
|
+
if (isCode7(error, "ENOENT")) throw new Error(`Session "${sessionId}" was not found in this workspace`);
|
|
13636
14465
|
throw error;
|
|
13637
14466
|
}
|
|
13638
14467
|
let parsed;
|
|
@@ -13643,6 +14472,8 @@ var SessionStore = class {
|
|
|
13643
14472
|
throw new Error(`Session "${sessionId}" is corrupt or incompatible and was quarantined as ${basename3(quarantine)}: ${message(error)}`);
|
|
13644
14473
|
}
|
|
13645
14474
|
await this.#assertWorkspace(parsed.workspace.path);
|
|
14475
|
+
const storedVersion = storedSessionVersion(raw);
|
|
14476
|
+
if (storedVersion < SESSION_VERSION) await this.#backupLegacy(path, raw, storedVersion);
|
|
13646
14477
|
return normalizeAbandonedTasks(parsed);
|
|
13647
14478
|
}
|
|
13648
14479
|
#parseSession(raw) {
|
|
@@ -13658,19 +14489,25 @@ var SessionStore = class {
|
|
|
13658
14489
|
const timelineRecords = records.filter(isTimelineRecord);
|
|
13659
14490
|
const summary2 = meta.summary;
|
|
13660
14491
|
const compact = meta.compact;
|
|
14492
|
+
const epoch = meta.epoch;
|
|
14493
|
+
const visibilityLog = meta.visibilityLog;
|
|
13661
14494
|
const timeline = meta.timeline;
|
|
13662
14495
|
delete meta.__meta;
|
|
13663
14496
|
delete meta.summary;
|
|
13664
14497
|
delete meta.compact;
|
|
14498
|
+
delete meta.epoch;
|
|
14499
|
+
delete meta.visibilityLog;
|
|
13665
14500
|
delete meta.timeline;
|
|
13666
14501
|
candidate = {
|
|
13667
14502
|
...meta,
|
|
13668
14503
|
conversation: {
|
|
13669
14504
|
...meta.version === 1 && summary2 !== void 0 ? { summary: summary2 } : {},
|
|
13670
|
-
...(meta.version === 2 || meta.version === SESSION_VERSION) && compact !== void 0 ? { compact } : {},
|
|
14505
|
+
...(meta.version === 2 || meta.version === 3 || meta.version === SESSION_VERSION) && compact !== void 0 ? { compact } : {},
|
|
14506
|
+
...meta.version === SESSION_VERSION && epoch !== void 0 ? { epoch } : {},
|
|
14507
|
+
...meta.version === SESSION_VERSION && visibilityLog !== void 0 ? { visibilityLog } : {},
|
|
13671
14508
|
messages
|
|
13672
14509
|
},
|
|
13673
|
-
...meta.version === SESSION_VERSION ? {
|
|
14510
|
+
...meta.version === 3 || meta.version === SESSION_VERSION ? {
|
|
13674
14511
|
timeline: timelineFromRecords(timeline, timelineRecords)
|
|
13675
14512
|
} : {}
|
|
13676
14513
|
};
|
|
@@ -13683,10 +14520,10 @@ var SessionStore = class {
|
|
|
13683
14520
|
try {
|
|
13684
14521
|
await this.#assertSafeExistingDirectory();
|
|
13685
14522
|
} catch (error) {
|
|
13686
|
-
if (
|
|
14523
|
+
if (isCode7(error, "ENOENT")) return [];
|
|
13687
14524
|
throw error;
|
|
13688
14525
|
}
|
|
13689
|
-
const names = (await
|
|
14526
|
+
const names = (await readdir6(this.#sessions)).filter((name) => name.endsWith(".jsonl") && !name.endsWith(".events.jsonl")).sort();
|
|
13690
14527
|
const entries = [];
|
|
13691
14528
|
for (const name of names) {
|
|
13692
14529
|
const id = name.slice(0, -6);
|
|
@@ -13705,7 +14542,7 @@ var SessionStore = class {
|
|
|
13705
14542
|
try {
|
|
13706
14543
|
await this.#assertSafeExistingDirectory();
|
|
13707
14544
|
} catch (error) {
|
|
13708
|
-
if (
|
|
14545
|
+
if (isCode7(error, "ENOENT")) return;
|
|
13709
14546
|
throw error;
|
|
13710
14547
|
}
|
|
13711
14548
|
const path = this.#path(sessionId);
|
|
@@ -13713,14 +14550,15 @@ var SessionStore = class {
|
|
|
13713
14550
|
const metadata = await lstat9(path);
|
|
13714
14551
|
if (!metadata.isFile()) throw new Error("Session path is not a regular file");
|
|
13715
14552
|
await rm6(path);
|
|
14553
|
+
await rm6(join19(this.#sessions, `${sessionId}.events.jsonl`), { force: true }).catch(() => void 0);
|
|
13716
14554
|
} catch (error) {
|
|
13717
|
-
if (!
|
|
14555
|
+
if (!isCode7(error, "ENOENT")) throw error;
|
|
13718
14556
|
}
|
|
13719
14557
|
}
|
|
13720
14558
|
#path(sessionId) {
|
|
13721
14559
|
SessionIdSchema.parse(sessionId);
|
|
13722
|
-
const path =
|
|
13723
|
-
if (!
|
|
14560
|
+
const path = resolve18(this.#sessions, `${sessionId}.jsonl`);
|
|
14561
|
+
if (!isWithin5(this.#sessions, path)) throw new Error("Invalid session id");
|
|
13724
14562
|
return path;
|
|
13725
14563
|
}
|
|
13726
14564
|
async #prune() {
|
|
@@ -13729,6 +14567,7 @@ var SessionStore = class {
|
|
|
13729
14567
|
const excess = entries.slice(this.#maxSessions);
|
|
13730
14568
|
for (const entry2 of excess) {
|
|
13731
14569
|
await rm6(this.#path(entry2.sessionId), { force: true }).catch(() => void 0);
|
|
14570
|
+
await rm6(join19(this.#sessions, `${entry2.sessionId}.events.jsonl`), { force: true }).catch(() => void 0);
|
|
13732
14571
|
}
|
|
13733
14572
|
}
|
|
13734
14573
|
async #assertWorkspace(stored) {
|
|
@@ -13747,19 +14586,38 @@ var SessionStore = class {
|
|
|
13747
14586
|
await assertNoSymlink3(this.#workspace, this.#sessions);
|
|
13748
14587
|
const canonicalRoot = await realpath7(this.#workspace);
|
|
13749
14588
|
const canonicalSessions = await realpath7(this.#sessions);
|
|
13750
|
-
if (!
|
|
14589
|
+
if (!isWithin5(canonicalRoot, canonicalSessions)) throw new Error("Session directory escapes the workspace");
|
|
13751
14590
|
}
|
|
13752
14591
|
async #quarantine(path) {
|
|
13753
|
-
const target = `${path}.corrupt-${Date.now()}-${
|
|
14592
|
+
const target = `${path}.corrupt-${Date.now()}-${randomUUID11().slice(0, 8)}`;
|
|
13754
14593
|
await rename6(path, target);
|
|
13755
14594
|
return target;
|
|
13756
14595
|
}
|
|
14596
|
+
async #backupLegacy(path, raw, version) {
|
|
14597
|
+
const target = `${path}.v${version}.bak`;
|
|
14598
|
+
let handle;
|
|
14599
|
+
try {
|
|
14600
|
+
handle = await open8(target, constants5.O_CREAT | constants5.O_EXCL | constants5.O_WRONLY, 384);
|
|
14601
|
+
await handle.writeFile(raw, "utf8");
|
|
14602
|
+
await handle.sync();
|
|
14603
|
+
} catch (error) {
|
|
14604
|
+
if (!isCode7(error, "EEXIST")) throw error;
|
|
14605
|
+
} finally {
|
|
14606
|
+
await handle?.close().catch(() => void 0);
|
|
14607
|
+
}
|
|
14608
|
+
}
|
|
13757
14609
|
};
|
|
14610
|
+
function storedSessionVersion(raw) {
|
|
14611
|
+
const trimmed = raw.trim();
|
|
14612
|
+
const first = JSON.parse(trimmed.split("\n", 1)[0] ?? "{}");
|
|
14613
|
+
const candidate = first.__meta === true ? first.version : JSON.parse(trimmed).version;
|
|
14614
|
+
return typeof candidate === "number" && Number.isInteger(candidate) ? candidate : 0;
|
|
14615
|
+
}
|
|
13758
14616
|
async function boundedRead(path, maxBytes) {
|
|
13759
14617
|
const metadata = await stat6(path);
|
|
13760
14618
|
if (!metadata.isFile()) throw new Error("Session path is not a regular file");
|
|
13761
14619
|
if (metadata.size > maxBytes) throw new Error(`Session exceeds maximum read size of ${maxBytes} bytes`);
|
|
13762
|
-
const value = await
|
|
14620
|
+
const value = await readFile16(path);
|
|
13763
14621
|
if (value.byteLength > maxBytes) throw new Error(`Session exceeds maximum read size of ${maxBytes} bytes`);
|
|
13764
14622
|
return value.toString("utf8");
|
|
13765
14623
|
}
|
|
@@ -13816,6 +14674,10 @@ function normalizeAbandonedTasks(document) {
|
|
|
13816
14674
|
function parseAndMigrateSession(input) {
|
|
13817
14675
|
if (typeof input !== "object" || input === null || !("version" in input)) throw new Error("Session version is missing");
|
|
13818
14676
|
if (input.version === SESSION_VERSION) return SessionDocumentSchema.parse(input);
|
|
14677
|
+
if (input.version === 3) {
|
|
14678
|
+
const legacy2 = SessionDocumentV3Schema.parse(input);
|
|
14679
|
+
return SessionDocumentSchema.parse({ ...legacy2, version: SESSION_VERSION });
|
|
14680
|
+
}
|
|
13819
14681
|
if (input.version === 2) {
|
|
13820
14682
|
const legacy2 = SessionDocumentV2Schema.parse(input);
|
|
13821
14683
|
return SessionDocumentSchema.parse({
|
|
@@ -13823,7 +14685,7 @@ function parseAndMigrateSession(input) {
|
|
|
13823
14685
|
version: SESSION_VERSION,
|
|
13824
14686
|
timeline: {
|
|
13825
14687
|
version: 1,
|
|
13826
|
-
state: transcriptFromLegacyConversation(legacy2.conversation.messages, legacy2.conversation.compact)
|
|
14688
|
+
state: transcriptFromLegacyConversation(legacy2.conversation.messages.filter((message2) => message2.role !== "system"), legacy2.conversation.compact)
|
|
13827
14689
|
}
|
|
13828
14690
|
});
|
|
13829
14691
|
}
|
|
@@ -13842,7 +14704,7 @@ function parseAndMigrateSession(input) {
|
|
|
13842
14704
|
},
|
|
13843
14705
|
timeline: {
|
|
13844
14706
|
version: 1,
|
|
13845
|
-
state: transcriptFromLegacyConversation(conversation.messages, compact)
|
|
14707
|
+
state: transcriptFromLegacyConversation(conversation.messages.filter((message2) => message2.role !== "system"), compact)
|
|
13846
14708
|
}
|
|
13847
14709
|
});
|
|
13848
14710
|
}
|
|
@@ -13897,9 +14759,9 @@ function isRecord5(value) {
|
|
|
13897
14759
|
return typeof value === "object" && value !== null && !Array.isArray(value);
|
|
13898
14760
|
}
|
|
13899
14761
|
async function assertNoSymlink3(root, target) {
|
|
13900
|
-
let cursor =
|
|
13901
|
-
const stop =
|
|
13902
|
-
if (!
|
|
14762
|
+
let cursor = resolve18(target);
|
|
14763
|
+
const stop = resolve18(root);
|
|
14764
|
+
if (!isWithin5(stop, cursor)) throw new Error("Session path escapes the workspace");
|
|
13903
14765
|
const chain = [];
|
|
13904
14766
|
while (cursor !== stop) {
|
|
13905
14767
|
chain.push(cursor);
|
|
@@ -13909,22 +14771,22 @@ async function assertNoSymlink3(root, target) {
|
|
|
13909
14771
|
try {
|
|
13910
14772
|
if ((await lstat9(path)).isSymbolicLink()) throw new Error(`Session path contains a symbolic link: ${path}`);
|
|
13911
14773
|
} catch (error) {
|
|
13912
|
-
if (!
|
|
14774
|
+
if (!isCode7(error, "ENOENT")) throw error;
|
|
13913
14775
|
}
|
|
13914
14776
|
}
|
|
13915
14777
|
}
|
|
13916
|
-
function
|
|
13917
|
-
const value = relative13(
|
|
13918
|
-
return value === "" || !value.startsWith(`..${
|
|
14778
|
+
function isWithin5(root, path) {
|
|
14779
|
+
const value = relative13(resolve18(root), resolve18(path));
|
|
14780
|
+
return value === "" || !value.startsWith(`..${sep13}`) && value !== ".." && !value.includes(":");
|
|
13919
14781
|
}
|
|
13920
14782
|
async function canonical2(path) {
|
|
13921
14783
|
try {
|
|
13922
|
-
return await realpath7(
|
|
14784
|
+
return await realpath7(resolve18(path));
|
|
13923
14785
|
} catch {
|
|
13924
|
-
return
|
|
14786
|
+
return resolve18(path);
|
|
13925
14787
|
}
|
|
13926
14788
|
}
|
|
13927
|
-
function
|
|
14789
|
+
function isCode7(error, code) {
|
|
13928
14790
|
return typeof error === "object" && error !== null && "code" in error && error.code === code;
|
|
13929
14791
|
}
|
|
13930
14792
|
function sanitize2(input, seen = /* @__PURE__ */ new WeakSet()) {
|
|
@@ -13946,47 +14808,47 @@ function sanitize2(input, seen = /* @__PURE__ */ new WeakSet()) {
|
|
|
13946
14808
|
}
|
|
13947
14809
|
|
|
13948
14810
|
// 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:
|
|
14811
|
+
import { constants as constants6 } from "fs";
|
|
14812
|
+
import { randomUUID as randomUUID12 } from "crypto";
|
|
14813
|
+
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";
|
|
14814
|
+
import { isAbsolute as isAbsolute8, join as join20, relative as relative14, resolve as resolve19, sep as sep14 } from "path";
|
|
14815
|
+
import { z as z23 } from "zod";
|
|
14816
|
+
var DateKeySchema = z23.string().regex(/^\d{4}-\d{2}-\d{2}$/);
|
|
14817
|
+
var ReviewSchema = z23.object({
|
|
14818
|
+
title: z23.string().min(1).max(120),
|
|
14819
|
+
taskSummary: z23.array(z23.string().min(1)).min(1).max(20),
|
|
14820
|
+
executionReflection: z23.array(z23.string().min(1)).min(1).max(20),
|
|
14821
|
+
decisionsAndLearnings: z23.array(z23.string().min(1)).max(20),
|
|
14822
|
+
openQuestionsAndRisks: z23.array(z23.string().min(1)).max(20),
|
|
14823
|
+
tomorrowPlan: z23.array(z23.string().min(1)).min(1).max(20),
|
|
14824
|
+
toolUsage: z23.object({
|
|
14825
|
+
totalCalls: z23.number().int().min(0),
|
|
14826
|
+
shell: z23.number().int().min(0),
|
|
14827
|
+
fileRead: z23.number().int().min(0),
|
|
14828
|
+
fileWrite: z23.number().int().min(0),
|
|
14829
|
+
search: z23.number().int().min(0),
|
|
14830
|
+
lsp: z23.number().int().min(0),
|
|
14831
|
+
subagent: z23.number().int().min(0),
|
|
14832
|
+
other: z23.number().int().min(0)
|
|
13971
14833
|
}).strict(),
|
|
13972
|
-
tokenEstimate:
|
|
13973
|
-
estimatedInput:
|
|
13974
|
-
estimatedOutput:
|
|
13975
|
-
notes:
|
|
14834
|
+
tokenEstimate: z23.object({
|
|
14835
|
+
estimatedInput: z23.number().int().min(0),
|
|
14836
|
+
estimatedOutput: z23.number().int().min(0),
|
|
14837
|
+
notes: z23.string().max(200)
|
|
13976
14838
|
}).strict(),
|
|
13977
|
-
humanIntervention:
|
|
13978
|
-
approvalRequests:
|
|
13979
|
-
questionsAsked:
|
|
13980
|
-
summary:
|
|
14839
|
+
humanIntervention: z23.object({
|
|
14840
|
+
approvalRequests: z23.number().int().min(0),
|
|
14841
|
+
questionsAsked: z23.number().int().min(0),
|
|
14842
|
+
summary: z23.string().max(300)
|
|
13981
14843
|
}).strict(),
|
|
13982
|
-
qualityIndicators:
|
|
13983
|
-
hallucinationAlerts:
|
|
13984
|
-
failuresAndRetries:
|
|
13985
|
-
codeChangeSummary:
|
|
13986
|
-
overallAssessment:
|
|
14844
|
+
qualityIndicators: z23.object({
|
|
14845
|
+
hallucinationAlerts: z23.array(z23.string().min(1)).max(10),
|
|
14846
|
+
failuresAndRetries: z23.array(z23.string().min(1)).max(10),
|
|
14847
|
+
codeChangeSummary: z23.string().max(500),
|
|
14848
|
+
overallAssessment: z23.string().max(200)
|
|
13987
14849
|
}).strict(),
|
|
13988
|
-
knowledgeDeposits:
|
|
13989
|
-
worthRemembering:
|
|
14850
|
+
knowledgeDeposits: z23.object({
|
|
14851
|
+
worthRemembering: z23.array(z23.string().min(1)).max(10)
|
|
13990
14852
|
}).strict()
|
|
13991
14853
|
}).strict();
|
|
13992
14854
|
function localDateKey(date) {
|
|
@@ -14003,8 +14865,8 @@ var ProjectSleepOrganizer = class {
|
|
|
14003
14865
|
#sessions;
|
|
14004
14866
|
#generate;
|
|
14005
14867
|
constructor(options) {
|
|
14006
|
-
this.#workspace =
|
|
14007
|
-
this.#sleepDirectory =
|
|
14868
|
+
this.#workspace = resolve19(options.workspace);
|
|
14869
|
+
this.#sleepDirectory = join20(this.#workspace, ".flavor", "sleep");
|
|
14008
14870
|
this.#sessions = options.sessions ?? new SessionStore({ workspace: this.#workspace });
|
|
14009
14871
|
this.#generate = options.generate;
|
|
14010
14872
|
}
|
|
@@ -14013,10 +14875,10 @@ var ProjectSleepOrganizer = class {
|
|
|
14013
14875
|
signal.throwIfAborted();
|
|
14014
14876
|
await this.#prepareDirectory();
|
|
14015
14877
|
if (await this.#hasReport(date)) return { status: "exists", date };
|
|
14016
|
-
const lockPath =
|
|
14878
|
+
const lockPath = join20(this.#sleepDirectory, `.${date}.lock`);
|
|
14017
14879
|
let lock;
|
|
14018
14880
|
try {
|
|
14019
|
-
lock = await open9(lockPath,
|
|
14881
|
+
lock = await open9(lockPath, constants6.O_CREAT | constants6.O_EXCL | constants6.O_WRONLY, 384);
|
|
14020
14882
|
} catch (error) {
|
|
14021
14883
|
if (hasCode(error, "EEXIST")) return { status: "locked", date };
|
|
14022
14884
|
throw error;
|
|
@@ -14048,7 +14910,7 @@ Previous output was invalid. Repair it and return strict JSON only.`;
|
|
|
14048
14910
|
}
|
|
14049
14911
|
if (review === void 0) throw lastError;
|
|
14050
14912
|
const filename2 = `${date}-${filenameSummary(review.title)}.md`;
|
|
14051
|
-
const path =
|
|
14913
|
+
const path = join20(this.#sleepDirectory, filename2);
|
|
14052
14914
|
const markdown = renderSleepReport(date, review, documents);
|
|
14053
14915
|
await this.#atomicWrite(path, markdown);
|
|
14054
14916
|
return { status: "written", date, path, sessionCount: documents.length };
|
|
@@ -14090,24 +14952,24 @@ Previous output was invalid. Repair it and return strict JSON only.`;
|
|
|
14090
14952
|
});
|
|
14091
14953
|
}
|
|
14092
14954
|
async #prepareDirectory() {
|
|
14093
|
-
const flavorDirectory =
|
|
14955
|
+
const flavorDirectory = join20(this.#workspace, ".flavor");
|
|
14094
14956
|
await assertNotSymlink(flavorDirectory);
|
|
14095
14957
|
await mkdir15(flavorDirectory, { recursive: true, mode: 448 });
|
|
14096
14958
|
await assertNotSymlink(this.#sleepDirectory);
|
|
14097
14959
|
await mkdir15(this.#sleepDirectory, { recursive: true, mode: 448 });
|
|
14098
14960
|
const root = await realpath8(this.#workspace);
|
|
14099
14961
|
const target = await realpath8(this.#sleepDirectory);
|
|
14100
|
-
if (!
|
|
14962
|
+
if (!isWithin6(root, target)) throw new Error("Sleep directory escapes the workspace");
|
|
14101
14963
|
}
|
|
14102
14964
|
async #hasReport(date) {
|
|
14103
14965
|
const prefix = `${date}-`;
|
|
14104
|
-
return (await
|
|
14966
|
+
return (await readdir7(this.#sleepDirectory, { withFileTypes: true })).some((entry2) => entry2.isFile() && entry2.name.startsWith(prefix) && entry2.name.endsWith(".md"));
|
|
14105
14967
|
}
|
|
14106
14968
|
async #atomicWrite(path, content) {
|
|
14107
|
-
const temporary =
|
|
14969
|
+
const temporary = join20(this.#sleepDirectory, `.${randomUUID12()}.tmp`);
|
|
14108
14970
|
let handle;
|
|
14109
14971
|
try {
|
|
14110
|
-
handle = await open9(temporary,
|
|
14972
|
+
handle = await open9(temporary, constants6.O_CREAT | constants6.O_EXCL | constants6.O_WRONLY, 384);
|
|
14111
14973
|
await handle.writeFile(content, "utf8");
|
|
14112
14974
|
await handle.sync();
|
|
14113
14975
|
await handle.close();
|
|
@@ -14425,11 +15287,11 @@ async function assertNotSymlink(path) {
|
|
|
14425
15287
|
if (!hasCode(error, "ENOENT")) throw error;
|
|
14426
15288
|
}
|
|
14427
15289
|
}
|
|
14428
|
-
function
|
|
15290
|
+
function isWithin6(root, candidate) {
|
|
14429
15291
|
const path = relative14(root, candidate);
|
|
14430
15292
|
if (path === "") return true;
|
|
14431
|
-
if (path === ".." || path.startsWith(`..${
|
|
14432
|
-
return process.platform !== "win32" ||
|
|
15293
|
+
if (path === ".." || path.startsWith(`..${sep14}`) || isAbsolute8(path)) return false;
|
|
15294
|
+
return process.platform !== "win32" || resolve19(root, path).toLowerCase().startsWith(`${resolve19(root).toLowerCase()}${sep14}`);
|
|
14433
15295
|
}
|
|
14434
15296
|
function hasCode(error, code) {
|
|
14435
15297
|
return typeof error === "object" && error !== null && "code" in error && error.code === code;
|
|
@@ -14477,11 +15339,11 @@ function computeSessionStats(documents) {
|
|
|
14477
15339
|
}
|
|
14478
15340
|
|
|
14479
15341
|
// 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
|
|
15342
|
+
import { constants as constants7 } from "fs";
|
|
15343
|
+
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";
|
|
15344
|
+
import { dirname as dirname16, isAbsolute as isAbsolute9, relative as relative15, resolve as resolve20, sep as sep15 } from "path";
|
|
15345
|
+
import { randomUUID as randomUUID13 } from "crypto";
|
|
15346
|
+
import { z as z24 } from "zod";
|
|
14485
15347
|
|
|
14486
15348
|
// src/tools/file-diff.ts
|
|
14487
15349
|
var MAX_DIFF_PREVIEW_LINES = 120;
|
|
@@ -14587,18 +15449,18 @@ function truncateLines(lines) {
|
|
|
14587
15449
|
|
|
14588
15450
|
// src/tools/files.ts
|
|
14589
15451
|
var MAX_READ_BYTES = 1048576;
|
|
14590
|
-
var ReadInput2 =
|
|
14591
|
-
path:
|
|
14592
|
-
maxBytes:
|
|
14593
|
-
startLine:
|
|
14594
|
-
endLine:
|
|
15452
|
+
var ReadInput2 = z24.object({
|
|
15453
|
+
path: z24.string().min(1),
|
|
15454
|
+
maxBytes: z24.coerce.number().int().positive().max(MAX_READ_BYTES).nullable().optional(),
|
|
15455
|
+
startLine: z24.coerce.number().int().positive().nullable().optional(),
|
|
15456
|
+
endLine: z24.coerce.number().int().positive().nullable().optional(),
|
|
14595
15457
|
// Accepts booleans and their string forms (weak-typed models emit "true");
|
|
14596
15458
|
// kept transform-free so the schema converts to JSON Schema for providers.
|
|
14597
|
-
force:
|
|
15459
|
+
force: z24.union([z24.boolean(), z24.string().refine((value) => value === "true" || value === "false")]).nullable().optional()
|
|
14598
15460
|
});
|
|
14599
|
-
var WriteInput =
|
|
14600
|
-
var EditInput =
|
|
14601
|
-
var ApplyPatchInput =
|
|
15461
|
+
var WriteInput = z24.object({ path: z24.string().min(1), content: z24.string() });
|
|
15462
|
+
var EditInput = z24.object({ path: z24.string().min(1), oldText: z24.string().min(1), newText: z24.string() });
|
|
15463
|
+
var ApplyPatchInput = z24.object({ patch: z24.string().min(1) });
|
|
14602
15464
|
var FileObservationStore = class {
|
|
14603
15465
|
#versions = /* @__PURE__ */ new Map();
|
|
14604
15466
|
get(path) {
|
|
@@ -14617,7 +15479,7 @@ var FileObservationStore = class {
|
|
|
14617
15479
|
};
|
|
14618
15480
|
function createReadTool(workspace, options = {}) {
|
|
14619
15481
|
const guard = createPathGuard(workspace);
|
|
14620
|
-
const openFile2 = options.openFile ?? ((path) => open10(path,
|
|
15482
|
+
const openFile2 = options.openFile ?? ((path) => open10(path, constants7.O_RDONLY));
|
|
14621
15483
|
const readHistory = /* @__PURE__ */ new Map();
|
|
14622
15484
|
return {
|
|
14623
15485
|
name: "Read",
|
|
@@ -14796,9 +15658,9 @@ function createApplyPatchTool(workspace, options = {}) {
|
|
|
14796
15658
|
};
|
|
14797
15659
|
}
|
|
14798
15660
|
function createPathGuard(workspace) {
|
|
14799
|
-
const root =
|
|
15661
|
+
const root = resolve20(workspace);
|
|
14800
15662
|
const lexical = (input) => {
|
|
14801
|
-
const candidate =
|
|
15663
|
+
const candidate = resolve20(root, input);
|
|
14802
15664
|
if (!within2(root, candidate)) throw new Error("Path is outside the workspace");
|
|
14803
15665
|
return candidate;
|
|
14804
15666
|
};
|
|
@@ -14833,9 +15695,9 @@ function createPathGuard(workspace) {
|
|
|
14833
15695
|
async function atomicWrite(path, content, signal, expectedVersion) {
|
|
14834
15696
|
abortIfNeeded(signal);
|
|
14835
15697
|
await mkdir16(dirname16(path), { recursive: true });
|
|
14836
|
-
const temporary = `${path}.${
|
|
15698
|
+
const temporary = `${path}.${randomUUID13()}.tmp`;
|
|
14837
15699
|
try {
|
|
14838
|
-
const handle = await open10(temporary,
|
|
15700
|
+
const handle = await open10(temporary, constants7.O_CREAT | constants7.O_EXCL | constants7.O_WRONLY, 384);
|
|
14839
15701
|
try {
|
|
14840
15702
|
await handle.writeFile(content, "utf8");
|
|
14841
15703
|
await handle.sync();
|
|
@@ -14852,7 +15714,7 @@ async function atomicWrite(path, content, signal, expectedVersion) {
|
|
|
14852
15714
|
}
|
|
14853
15715
|
}
|
|
14854
15716
|
async function readText(path) {
|
|
14855
|
-
const contents = await
|
|
15717
|
+
const contents = await readFile17(path);
|
|
14856
15718
|
if (isBinary(contents)) throw new Error("Cannot edit binary file as text");
|
|
14857
15719
|
return contents.toString("utf8");
|
|
14858
15720
|
}
|
|
@@ -14882,7 +15744,7 @@ function versionOfStat(info) {
|
|
|
14882
15744
|
}
|
|
14883
15745
|
async function readOptionalPresentationText(path) {
|
|
14884
15746
|
try {
|
|
14885
|
-
const contents = await
|
|
15747
|
+
const contents = await readFile17(path);
|
|
14886
15748
|
return isBinary(contents) ? { exists: true } : { exists: true, text: contents.toString("utf8") };
|
|
14887
15749
|
} catch (error) {
|
|
14888
15750
|
return isMissing3(error) ? { exists: false } : { exists: true };
|
|
@@ -14968,7 +15830,7 @@ async function readBounded2(path, maxBytes, signal, openFile2) {
|
|
|
14968
15830
|
}
|
|
14969
15831
|
function within2(root, candidate) {
|
|
14970
15832
|
const delta = relative15(root, candidate);
|
|
14971
|
-
return delta === "" || !delta.startsWith(`..${
|
|
15833
|
+
return delta === "" || !delta.startsWith(`..${sep15}`) && delta !== ".." && !isAbsolute9(delta);
|
|
14972
15834
|
}
|
|
14973
15835
|
function forceFlag(value) {
|
|
14974
15836
|
return value === true || value === "true";
|
|
@@ -15194,22 +16056,22 @@ import { lookup } from "dns/promises";
|
|
|
15194
16056
|
import { isIP } from "net";
|
|
15195
16057
|
import { request as httpRequest } from "http";
|
|
15196
16058
|
import { request as httpsRequest } from "https";
|
|
15197
|
-
import { z as
|
|
15198
|
-
var WebFetchInput =
|
|
15199
|
-
url:
|
|
15200
|
-
timeoutMs:
|
|
15201
|
-
maxBytes:
|
|
16059
|
+
import { z as z25 } from "zod";
|
|
16060
|
+
var WebFetchInput = z25.object({
|
|
16061
|
+
url: z25.string().url().max(8192),
|
|
16062
|
+
timeoutMs: z25.coerce.number().int().positive().max(12e4).optional(),
|
|
16063
|
+
maxBytes: z25.coerce.number().int().positive().max(10 * 1024 * 1024).optional()
|
|
15202
16064
|
});
|
|
15203
|
-
var WebSearchInput =
|
|
15204
|
-
query:
|
|
15205
|
-
maxResults:
|
|
16065
|
+
var WebSearchInput = z25.object({
|
|
16066
|
+
query: z25.string().trim().min(1).max(1e3),
|
|
16067
|
+
maxResults: z25.coerce.number().int().min(1).max(20).optional()
|
|
15206
16068
|
});
|
|
15207
16069
|
function createWebFetchTool() {
|
|
15208
16070
|
return {
|
|
15209
16071
|
name: "WebFetch",
|
|
15210
16072
|
description: "Fetch a public HTTP(S) page with SSRF protection and return readable text",
|
|
15211
16073
|
inputSchema: WebFetchInput,
|
|
15212
|
-
outputSchema:
|
|
16074
|
+
outputSchema: z25.object({ url: z25.string(), status: z25.number(), contentType: z25.string(), content: z25.string(), truncated: z25.boolean() }),
|
|
15213
16075
|
paths: () => [],
|
|
15214
16076
|
summarize: (input) => input.url,
|
|
15215
16077
|
presentCall: (input) => ({ kind: "web", title: `Fetching ${new URL(input.url).hostname}`, url: input.url }),
|
|
@@ -15235,7 +16097,7 @@ function createWebSearchTool(provider = createDefaultWebSearchProvider()) {
|
|
|
15235
16097
|
name: "WebSearch",
|
|
15236
16098
|
description: "Search the public web and return titles, URLs, and snippets",
|
|
15237
16099
|
inputSchema: WebSearchInput,
|
|
15238
|
-
outputSchema:
|
|
16100
|
+
outputSchema: z25.object({ query: z25.string(), results: z25.array(z25.object({ title: z25.string(), url: z25.string(), snippet: z25.string() })) }),
|
|
15239
16101
|
paths: () => [],
|
|
15240
16102
|
summarize: (input) => input.query,
|
|
15241
16103
|
presentCall: (input) => ({ kind: "web", title: `Searching: ${input.query}` }),
|
|
@@ -15499,11 +16361,11 @@ function charset(contentType) {
|
|
|
15499
16361
|
|
|
15500
16362
|
// src/tools/lsp.ts
|
|
15501
16363
|
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
|
|
16364
|
+
import { existsSync as existsSync3 } from "fs";
|
|
16365
|
+
import { access, constants as constants8, readFile as readFile18 } from "fs/promises";
|
|
16366
|
+
import { basename as basename4, dirname as dirname17, extname, isAbsolute as isAbsolute10, relative as relative16, resolve as resolve21, sep as sep16 } from "path";
|
|
15505
16367
|
import { fileURLToPath as fileURLToPath3, pathToFileURL as pathToFileURL2 } from "url";
|
|
15506
|
-
import { z as
|
|
16368
|
+
import { z as z26 } from "zod";
|
|
15507
16369
|
var LSP_REQUEST_TIMEOUT_MS = 15e3;
|
|
15508
16370
|
var LspConnection = class {
|
|
15509
16371
|
#process;
|
|
@@ -15600,8 +16462,8 @@ var LspConnection = class {
|
|
|
15600
16462
|
const id = this.#nextId++;
|
|
15601
16463
|
const request = { jsonrpc: "2.0", id, method, params };
|
|
15602
16464
|
const timeout = AbortSignal.timeout(LSP_REQUEST_TIMEOUT_MS);
|
|
15603
|
-
const promise = new Promise((
|
|
15604
|
-
this.#pending.set(id, { resolve:
|
|
16465
|
+
const promise = new Promise((resolve28, reject) => {
|
|
16466
|
+
this.#pending.set(id, { resolve: resolve28, reject });
|
|
15605
16467
|
timeout.addEventListener("abort", () => {
|
|
15606
16468
|
if (this.#pending.has(id)) {
|
|
15607
16469
|
this.#pending.delete(id);
|
|
@@ -15666,7 +16528,7 @@ var LspConnection = class {
|
|
|
15666
16528
|
const url = new URL(uri);
|
|
15667
16529
|
if (url.protocol !== "file:") return;
|
|
15668
16530
|
const filePath = fileURLToPath3(url);
|
|
15669
|
-
const text = await
|
|
16531
|
+
const text = await readFile18(filePath, "utf8");
|
|
15670
16532
|
const current = this.#documents.get(uri);
|
|
15671
16533
|
if (current === void 0) {
|
|
15672
16534
|
this.#documents.set(uri, { text, version: 1 });
|
|
@@ -15717,7 +16579,7 @@ var LspBuffer = class {
|
|
|
15717
16579
|
}
|
|
15718
16580
|
}
|
|
15719
16581
|
};
|
|
15720
|
-
var TYPESCRIPT_CLI =
|
|
16582
|
+
var TYPESCRIPT_CLI = resolve21(
|
|
15721
16583
|
dirname17(fileURLToPath3(import.meta.resolve("typescript/package.json"))),
|
|
15722
16584
|
"lib",
|
|
15723
16585
|
"tsc.js"
|
|
@@ -15759,7 +16621,7 @@ var RealLspManager = class {
|
|
|
15759
16621
|
#serverConfigs;
|
|
15760
16622
|
#onStatus;
|
|
15761
16623
|
constructor(options) {
|
|
15762
|
-
this.#workspace =
|
|
16624
|
+
this.#workspace = resolve21(options.workspace);
|
|
15763
16625
|
this.#serverConfigs = options.serverConfigs ?? KNOWN_SERVERS;
|
|
15764
16626
|
this.#onStatus = options.onStatus;
|
|
15765
16627
|
}
|
|
@@ -15808,7 +16670,7 @@ var RealLspManager = class {
|
|
|
15808
16670
|
if (pending !== void 0) return pending;
|
|
15809
16671
|
const config = this.#serverConfigs.find((c) => c.language === language);
|
|
15810
16672
|
if (config === void 0) return void 0;
|
|
15811
|
-
const hasRoot = config.rootFiles.some((file) =>
|
|
16673
|
+
const hasRoot = config.rootFiles.some((file) => existsSync3(resolve21(this.#workspace, file)));
|
|
15812
16674
|
if (!hasRoot) return void 0;
|
|
15813
16675
|
const startPromise = this.#startServer(config);
|
|
15814
16676
|
this.#pendingStarts.set(language, startPromise);
|
|
@@ -15822,7 +16684,7 @@ var RealLspManager = class {
|
|
|
15822
16684
|
}
|
|
15823
16685
|
async #startServer(config) {
|
|
15824
16686
|
try {
|
|
15825
|
-
await access(config.command,
|
|
16687
|
+
await access(config.command, constants8.X_OK);
|
|
15826
16688
|
} catch {
|
|
15827
16689
|
}
|
|
15828
16690
|
const connection = new LspConnection(config.command, config.args, this.#workspace);
|
|
@@ -15837,25 +16699,25 @@ var RealLspManager = class {
|
|
|
15837
16699
|
return connection;
|
|
15838
16700
|
}
|
|
15839
16701
|
};
|
|
15840
|
-
var LspFindRefsInput =
|
|
15841
|
-
file:
|
|
15842
|
-
line:
|
|
15843
|
-
character:
|
|
16702
|
+
var LspFindRefsInput = z26.object({
|
|
16703
|
+
file: z26.string().min(1),
|
|
16704
|
+
line: z26.coerce.number().int().nonnegative(),
|
|
16705
|
+
character: z26.coerce.number().int().nonnegative()
|
|
15844
16706
|
});
|
|
15845
|
-
var LspHoverInput =
|
|
15846
|
-
file:
|
|
15847
|
-
line:
|
|
15848
|
-
character:
|
|
16707
|
+
var LspHoverInput = z26.object({
|
|
16708
|
+
file: z26.string().min(1),
|
|
16709
|
+
line: z26.coerce.number().int().nonnegative(),
|
|
16710
|
+
character: z26.coerce.number().int().nonnegative()
|
|
15849
16711
|
});
|
|
15850
|
-
var LspDiagnosticsInput =
|
|
15851
|
-
file:
|
|
16712
|
+
var LspDiagnosticsInput = z26.object({
|
|
16713
|
+
file: z26.string().min(1)
|
|
15852
16714
|
});
|
|
15853
16715
|
function createLspTools(workspace, options = {}) {
|
|
15854
|
-
const root =
|
|
16716
|
+
const root = resolve21(workspace);
|
|
15855
16717
|
const manager = options.manager ?? new RealLspManager({ workspace: root, onStatus: options.onStatus });
|
|
15856
16718
|
const guard = (input) => {
|
|
15857
|
-
const candidate =
|
|
15858
|
-
if (!
|
|
16719
|
+
const candidate = resolve21(root, input);
|
|
16720
|
+
if (!isWithin7(root, candidate)) throw new Error("Path is outside the workspace");
|
|
15859
16721
|
return candidate;
|
|
15860
16722
|
};
|
|
15861
16723
|
const toUri = (filePath) => pathToFileURL2(filePath).href;
|
|
@@ -15929,9 +16791,9 @@ function languageIdForPath(filePath) {
|
|
|
15929
16791
|
return "typescript";
|
|
15930
16792
|
}
|
|
15931
16793
|
}
|
|
15932
|
-
function
|
|
16794
|
+
function isWithin7(root, candidate) {
|
|
15933
16795
|
const delta = relative16(root, candidate);
|
|
15934
|
-
return delta === "" || !delta.startsWith(`..${
|
|
16796
|
+
return delta === "" || !delta.startsWith(`..${sep16}`) && delta !== ".." && !isAbsolute10(delta);
|
|
15935
16797
|
}
|
|
15936
16798
|
function uriToPath(uri) {
|
|
15937
16799
|
try {
|
|
@@ -16074,7 +16936,7 @@ async function gitMarker(workspace) {
|
|
|
16074
16936
|
}
|
|
16075
16937
|
|
|
16076
16938
|
// src/git/insights.ts
|
|
16077
|
-
import { z as
|
|
16939
|
+
import { z as z27 } from "zod";
|
|
16078
16940
|
async function suggestCommitMessage(options, input, signal) {
|
|
16079
16941
|
const prompt = [
|
|
16080
16942
|
"Write a git commit message for the staged changes below.",
|
|
@@ -16113,17 +16975,17 @@ function cleanCommitMessage(raw) {
|
|
|
16113
16975
|
}
|
|
16114
16976
|
return kept.join("\n").trim().slice(0, 2e3);
|
|
16115
16977
|
}
|
|
16116
|
-
var ReviewFindingSchema =
|
|
16117
|
-
severity:
|
|
16118
|
-
file:
|
|
16119
|
-
line:
|
|
16120
|
-
issue:
|
|
16121
|
-
suggestion:
|
|
16978
|
+
var ReviewFindingSchema = z27.object({
|
|
16979
|
+
severity: z27.enum(["critical", "warning", "nit"]),
|
|
16980
|
+
file: z27.string(),
|
|
16981
|
+
line: z27.string().describe('Line number or hunk header the finding refers to, e.g. "42" or "@@ -10,4 +10,6 @@"'),
|
|
16982
|
+
issue: z27.string(),
|
|
16983
|
+
suggestion: z27.string()
|
|
16122
16984
|
}).strict();
|
|
16123
|
-
var ReviewReportSchema =
|
|
16124
|
-
summary:
|
|
16125
|
-
verdict:
|
|
16126
|
-
findings:
|
|
16985
|
+
var ReviewReportSchema = z27.object({
|
|
16986
|
+
summary: z27.string().describe("One-paragraph overall assessment of the change"),
|
|
16987
|
+
verdict: z27.enum(["ship", "ship-with-fixes", "needs-work"]),
|
|
16988
|
+
findings: z27.array(ReviewFindingSchema).max(20)
|
|
16127
16989
|
}).strict();
|
|
16128
16990
|
async function reviewDiff(options, input, signal) {
|
|
16129
16991
|
const prompt = [
|
|
@@ -16167,10 +17029,10 @@ function formatReviewReport(report) {
|
|
|
16167
17029
|
}
|
|
16168
17030
|
|
|
16169
17031
|
// src/git/tools.ts
|
|
16170
|
-
import { z as
|
|
16171
|
-
var GitHistoryInput =
|
|
16172
|
-
path:
|
|
16173
|
-
limit:
|
|
17032
|
+
import { z as z28 } from "zod";
|
|
17033
|
+
var GitHistoryInput = z28.object({
|
|
17034
|
+
path: z28.string().describe("File to inspect; omit for the whole repository history").optional(),
|
|
17035
|
+
limit: z28.number().int().min(1).max(50).describe("Maximum number of commits to return (default 20)").optional()
|
|
16174
17036
|
});
|
|
16175
17037
|
function createGitHistoryTool(workspace) {
|
|
16176
17038
|
return {
|
|
@@ -16199,18 +17061,18 @@ function createGitHistoryTool(workspace) {
|
|
|
16199
17061
|
}
|
|
16200
17062
|
|
|
16201
17063
|
// src/tools/ask-user-question.ts
|
|
16202
|
-
import { z as
|
|
16203
|
-
var QuestionOptionSchema =
|
|
16204
|
-
label:
|
|
16205
|
-
description:
|
|
17064
|
+
import { z as z29 } from "zod";
|
|
17065
|
+
var QuestionOptionSchema = z29.object({
|
|
17066
|
+
label: z29.string().trim().min(1),
|
|
17067
|
+
description: z29.string().trim().min(1)
|
|
16206
17068
|
});
|
|
16207
|
-
var QuestionSchema =
|
|
16208
|
-
question:
|
|
16209
|
-
header:
|
|
16210
|
-
options:
|
|
17069
|
+
var QuestionSchema = z29.object({
|
|
17070
|
+
question: z29.string().trim().min(1),
|
|
17071
|
+
header: z29.string().trim().min(1),
|
|
17072
|
+
options: z29.array(QuestionOptionSchema).min(1).max(4)
|
|
16211
17073
|
});
|
|
16212
|
-
var AskUserQuestionInput =
|
|
16213
|
-
questions:
|
|
17074
|
+
var AskUserQuestionInput = z29.object({
|
|
17075
|
+
questions: z29.array(QuestionSchema).min(1).max(4)
|
|
16214
17076
|
});
|
|
16215
17077
|
function hookAnswersFromUpdatedInput(updatedInput, questions) {
|
|
16216
17078
|
if (typeof updatedInput !== "object" || updatedInput === null) return void 0;
|
|
@@ -16239,7 +17101,7 @@ var QuestionBridge = class {
|
|
|
16239
17101
|
ask(questions, signal) {
|
|
16240
17102
|
if (this.#pending !== void 0) return Promise.reject(new Error("A question is already pending"));
|
|
16241
17103
|
if (signal.aborted) return Promise.reject(signal.reason);
|
|
16242
|
-
return new Promise((
|
|
17104
|
+
return new Promise((resolve28, reject) => {
|
|
16243
17105
|
const onAbort = () => {
|
|
16244
17106
|
this.#pending = void 0;
|
|
16245
17107
|
reject(signal.reason instanceof Error ? signal.reason : new Error(String(signal.reason)));
|
|
@@ -16251,7 +17113,7 @@ var QuestionBridge = class {
|
|
|
16251
17113
|
signal.removeEventListener("abort", onAbort);
|
|
16252
17114
|
this.#pending = void 0;
|
|
16253
17115
|
this.#onChange?.();
|
|
16254
|
-
|
|
17116
|
+
resolve28(answers);
|
|
16255
17117
|
},
|
|
16256
17118
|
reject: (error) => {
|
|
16257
17119
|
signal.removeEventListener("abort", onAbort);
|
|
@@ -16294,20 +17156,20 @@ function createAskUserQuestionTool(handler) {
|
|
|
16294
17156
|
}
|
|
16295
17157
|
|
|
16296
17158
|
// src/tools/todo-write.ts
|
|
16297
|
-
import { z as
|
|
16298
|
-
var TodoStatusSchema =
|
|
17159
|
+
import { z as z30 } from "zod";
|
|
17160
|
+
var TodoStatusSchema = z30.enum([
|
|
16299
17161
|
"pending",
|
|
16300
17162
|
"in_progress",
|
|
16301
17163
|
"completed",
|
|
16302
17164
|
"cancelled"
|
|
16303
17165
|
]);
|
|
16304
|
-
var TodoItemSchema =
|
|
16305
|
-
content:
|
|
17166
|
+
var TodoItemSchema = z30.object({
|
|
17167
|
+
content: z30.string().trim().min(1),
|
|
16306
17168
|
status: TodoStatusSchema,
|
|
16307
|
-
activeForm:
|
|
17169
|
+
activeForm: z30.string().trim().min(1)
|
|
16308
17170
|
});
|
|
16309
|
-
var TodoWriteInput =
|
|
16310
|
-
todos:
|
|
17171
|
+
var TodoWriteInput = z30.object({
|
|
17172
|
+
todos: z30.array(TodoItemSchema).min(1).max(50)
|
|
16311
17173
|
});
|
|
16312
17174
|
function createTodoWriteTool() {
|
|
16313
17175
|
return {
|
|
@@ -16341,39 +17203,39 @@ function createTodoWriteTool() {
|
|
|
16341
17203
|
}
|
|
16342
17204
|
|
|
16343
17205
|
// 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
|
|
17206
|
+
import { randomUUID as randomUUID14 } from "crypto";
|
|
17207
|
+
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";
|
|
17208
|
+
import { dirname as dirname18, join as join21, resolve as resolve22 } from "path";
|
|
17209
|
+
import { z as z31 } from "zod";
|
|
16348
17210
|
var MANAGED_TOOL_VERSION = 1;
|
|
16349
17211
|
var MAX_RECORD_BYTES = 256 * 1024;
|
|
16350
17212
|
var TOOL_NAME = /^[A-Za-z][A-Za-z0-9_]{0,63}$/;
|
|
16351
|
-
var JsonSchemaObject =
|
|
17213
|
+
var JsonSchemaObject = z31.record(z31.string(), z31.unknown()).superRefine((schema, context) => {
|
|
16352
17214
|
if (schema.type !== "object") {
|
|
16353
17215
|
context.addIssue({ code: "custom", message: "Tool input JSON Schema must have type object" });
|
|
16354
17216
|
}
|
|
16355
17217
|
});
|
|
16356
|
-
var RegisterManagedToolInputSchema =
|
|
16357
|
-
name:
|
|
16358
|
-
description:
|
|
17218
|
+
var RegisterManagedToolInputSchema = z31.object({
|
|
17219
|
+
name: z31.string().regex(TOOL_NAME).describe("Unique callable tool name using letters, digits, and underscores"),
|
|
17220
|
+
description: z31.string().min(1).max(4096).describe("Purpose and appropriate use of the generated tool"),
|
|
16359
17221
|
inputSchema: JsonSchemaObject.describe("Provider-facing JSON Schema with an object at the root"),
|
|
16360
|
-
implementation:
|
|
17222
|
+
implementation: z31.string().min(1).max(64 * 1024).describe(
|
|
16361
17223
|
"Async JavaScript function body, or complete function/arrow expression, receiving input, signal, and context; return the tool result"
|
|
16362
17224
|
),
|
|
16363
|
-
scope:
|
|
17225
|
+
scope: z31.enum(["project", "global"]).default("project").describe(
|
|
16364
17226
|
"Project persists in the current workspace; global persists for every workspace"
|
|
16365
17227
|
),
|
|
16366
|
-
agents:
|
|
17228
|
+
agents: z31.array(z31.enum(["main", "subagent"])).min(1).max(2).optional().describe(
|
|
16367
17229
|
"Agent roles allowed to call the generated tool; omit to allow both"
|
|
16368
17230
|
)
|
|
16369
17231
|
}).strict();
|
|
16370
|
-
var RemoveManagedToolInputSchema =
|
|
16371
|
-
name:
|
|
16372
|
-
scope:
|
|
17232
|
+
var RemoveManagedToolInputSchema = z31.object({
|
|
17233
|
+
name: z31.string().regex(TOOL_NAME).describe("Managed tool name to remove"),
|
|
17234
|
+
scope: z31.enum(["project", "global"]).optional().describe("Required only when the same name exists in both scopes")
|
|
16373
17235
|
}).strict();
|
|
16374
17236
|
var ManagedToolRecordSchema = RegisterManagedToolInputSchema.extend({
|
|
16375
|
-
version:
|
|
16376
|
-
createdAt:
|
|
17237
|
+
version: z31.literal(MANAGED_TOOL_VERSION),
|
|
17238
|
+
createdAt: z31.string().datetime()
|
|
16377
17239
|
}).strict();
|
|
16378
17240
|
var AsyncFunction = Object.getPrototypeOf(async function() {
|
|
16379
17241
|
}).constructor;
|
|
@@ -16385,9 +17247,9 @@ var ManagedToolStore = class {
|
|
|
16385
17247
|
#entries = /* @__PURE__ */ new Map();
|
|
16386
17248
|
#diagnostics = [];
|
|
16387
17249
|
constructor(options) {
|
|
16388
|
-
this.#workspace =
|
|
16389
|
-
this.#projectRoot =
|
|
16390
|
-
this.#globalRoot =
|
|
17250
|
+
this.#workspace = resolve22(options.workspace);
|
|
17251
|
+
this.#projectRoot = join21(this.#workspace, ".flavor", "tools");
|
|
17252
|
+
this.#globalRoot = join21(resolve22(options.home), ".flavor-code", "tools");
|
|
16391
17253
|
this.#now = options.now ?? (() => (/* @__PURE__ */ new Date()).toISOString());
|
|
16392
17254
|
}
|
|
16393
17255
|
get diagnostics() {
|
|
@@ -16434,7 +17296,7 @@ var ManagedToolStore = class {
|
|
|
16434
17296
|
version: MANAGED_TOOL_VERSION,
|
|
16435
17297
|
createdAt: this.#now()
|
|
16436
17298
|
});
|
|
16437
|
-
const path =
|
|
17299
|
+
const path = join21(this.directory(input.scope), filename(input.name));
|
|
16438
17300
|
const definition = compileDefinition(record, input.scope, this.#workspace);
|
|
16439
17301
|
await writeExclusive(path, `${JSON.stringify(record, void 0, 2)}
|
|
16440
17302
|
`);
|
|
@@ -16459,25 +17321,25 @@ var ManagedToolStore = class {
|
|
|
16459
17321
|
if (!parsed.success) return [];
|
|
16460
17322
|
const matches = this.#matching(parsed.data.name).filter((entry2) => parsed.data.scope === void 0 || entry2.scope === parsed.data.scope);
|
|
16461
17323
|
if (matches.length > 0) return matches.map((entry2) => entry2.path);
|
|
16462
|
-
return [
|
|
17324
|
+
return [join21(this.directory(parsed.data.scope ?? "project"), filename(parsed.data.name))];
|
|
16463
17325
|
}
|
|
16464
17326
|
async #loadDirectory(scope2) {
|
|
16465
17327
|
const root = this.directory(scope2);
|
|
16466
17328
|
let names;
|
|
16467
17329
|
try {
|
|
16468
|
-
names = (await
|
|
17330
|
+
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
17331
|
} catch (error) {
|
|
16470
17332
|
if (isMissing4(error)) return;
|
|
16471
17333
|
this.#diagnostics.push(`${root}: ${message(error)}`);
|
|
16472
17334
|
return;
|
|
16473
17335
|
}
|
|
16474
17336
|
for (const name of names) {
|
|
16475
|
-
const path =
|
|
17337
|
+
const path = join21(root, name);
|
|
16476
17338
|
try {
|
|
16477
17339
|
const info = await lstat11(path);
|
|
16478
17340
|
if (!info.isFile() || info.isSymbolicLink()) throw new Error("Managed tool record must be a regular file");
|
|
16479
17341
|
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
|
|
17342
|
+
const record = ManagedToolRecordSchema.parse(JSON.parse(await readFile19(path, "utf8")));
|
|
16481
17343
|
if (record.scope !== scope2) throw new Error(`Managed tool record scope must match its ${scope2} directory`);
|
|
16482
17344
|
const key = scopedKey(scope2, record.name);
|
|
16483
17345
|
if (this.#entries.has(key)) throw new Error(`Duplicate managed tool name in ${scope2} scope: ${record.name}`);
|
|
@@ -16560,7 +17422,7 @@ function createManagedToolManagementTools(options) {
|
|
|
16560
17422
|
const listTools = {
|
|
16561
17423
|
name: "ListRegisteredTools",
|
|
16562
17424
|
description: "List durable tools created through RegisterTool, including scope, path, and active precedence",
|
|
16563
|
-
inputSchema:
|
|
17425
|
+
inputSchema: z31.object({}).strict(),
|
|
16564
17426
|
agents: mainOnly,
|
|
16565
17427
|
paths: () => [],
|
|
16566
17428
|
execute: async (_input, signal) => {
|
|
@@ -16573,7 +17435,7 @@ function createManagedToolManagementTools(options) {
|
|
|
16573
17435
|
function compileDefinition(record, scope2, workspace) {
|
|
16574
17436
|
let inputSchema;
|
|
16575
17437
|
try {
|
|
16576
|
-
inputSchema =
|
|
17438
|
+
inputSchema = z31.fromJSONSchema(record.inputSchema);
|
|
16577
17439
|
} catch (error) {
|
|
16578
17440
|
throw new Error(`Invalid input schema for managed tool "${record.name}": ${message(error)}`);
|
|
16579
17441
|
}
|
|
@@ -16628,7 +17490,7 @@ ${sourceUrl}`
|
|
|
16628
17490
|
}
|
|
16629
17491
|
async function writeExclusive(path, content) {
|
|
16630
17492
|
await mkdir17(dirname18(path), { recursive: true });
|
|
16631
|
-
const temporary = `${path}.${
|
|
17493
|
+
const temporary = `${path}.${randomUUID14()}.tmp`;
|
|
16632
17494
|
try {
|
|
16633
17495
|
await writeFile10(temporary, content, { encoding: "utf8", flag: "wx", mode: 384 });
|
|
16634
17496
|
await link2(temporary, path);
|
|
@@ -16673,7 +17535,7 @@ function isMissing4(error) {
|
|
|
16673
17535
|
}
|
|
16674
17536
|
|
|
16675
17537
|
// src/hallucination/confidence.ts
|
|
16676
|
-
import { z as
|
|
17538
|
+
import { z as z32 } from "zod";
|
|
16677
17539
|
|
|
16678
17540
|
// src/hallucination/types.ts
|
|
16679
17541
|
var DEFAULT_SLIDING_WINDOW_SIZE = 20;
|
|
@@ -16686,12 +17548,12 @@ var DEFAULT_EVALUATION_TIMEOUT_MS = 2e3;
|
|
|
16686
17548
|
var MAX_QUERY_CHARS = 5e3;
|
|
16687
17549
|
var MAX_OUTPUT_CHARS = 1e4;
|
|
16688
17550
|
var MAX_EVIDENCE_CHARS = 6e3;
|
|
16689
|
-
var ConfidenceSchema =
|
|
16690
|
-
taskAlignment:
|
|
16691
|
-
evidenceGrounding:
|
|
16692
|
-
processReliability:
|
|
16693
|
-
reason:
|
|
16694
|
-
unsupportedClaims:
|
|
17551
|
+
var ConfidenceSchema = z32.object({
|
|
17552
|
+
taskAlignment: z32.number(),
|
|
17553
|
+
evidenceGrounding: z32.number(),
|
|
17554
|
+
processReliability: z32.number(),
|
|
17555
|
+
reason: z32.string(),
|
|
17556
|
+
unsupportedClaims: z32.array(z32.string()).max(3)
|
|
16695
17557
|
});
|
|
16696
17558
|
var PROMPT = `You are a fast, evidence-aware task-completion verifier.
|
|
16697
17559
|
|
|
@@ -17018,7 +17880,7 @@ function buildWarningMessages(report, languageTag) {
|
|
|
17018
17880
|
}
|
|
17019
17881
|
|
|
17020
17882
|
// src/hallucination/sliding-window.ts
|
|
17021
|
-
import { createHash as
|
|
17883
|
+
import { createHash as createHash14 } from "crypto";
|
|
17022
17884
|
var SlidingWindow = class {
|
|
17023
17885
|
#windowSize;
|
|
17024
17886
|
#threshold;
|
|
@@ -17034,7 +17896,7 @@ var SlidingWindow = class {
|
|
|
17034
17896
|
}
|
|
17035
17897
|
hash(toolName, params) {
|
|
17036
17898
|
const sorted = JSON.stringify(params, sortedKeys(params));
|
|
17037
|
-
return
|
|
17899
|
+
return createHash14("sha256").update(`${toolName}:${sorted}`).digest("hex");
|
|
17038
17900
|
}
|
|
17039
17901
|
push(toolName, params) {
|
|
17040
17902
|
const hash = this.hash(toolName, params);
|
|
@@ -17389,9 +18251,9 @@ ${total}`;
|
|
|
17389
18251
|
}
|
|
17390
18252
|
|
|
17391
18253
|
// src/memory/store.ts
|
|
17392
|
-
import { createHash as
|
|
17393
|
-
import { readFile as
|
|
17394
|
-
import { resolve as
|
|
18254
|
+
import { createHash as createHash15 } from "crypto";
|
|
18255
|
+
import { readFile as readFile20, rm as rm9 } from "fs/promises";
|
|
18256
|
+
import { resolve as resolve23, join as join22, relative as relative17, sep as sep17 } from "path";
|
|
17395
18257
|
|
|
17396
18258
|
// src/memory/similarity.ts
|
|
17397
18259
|
function normalizeForSimilarity(value) {
|
|
@@ -17493,10 +18355,10 @@ var MemoryStore = class {
|
|
|
17493
18355
|
constructor(options) {
|
|
17494
18356
|
if (!Number.isSafeInteger(options.maxEntries) || options.maxEntries < 1) throw new Error("maxEntries must be an integer of at least 1");
|
|
17495
18357
|
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 =
|
|
18358
|
+
this.workspace = resolve23(options.workspace);
|
|
18359
|
+
this.#memoryRoot = join22(this.workspace, ".flavor", "memory");
|
|
18360
|
+
this.path = join22(this.#memoryRoot, "MEMORY.md");
|
|
18361
|
+
this.#behaviorPath = join22(this.#memoryRoot, "behavior.json");
|
|
17500
18362
|
this.#maxEntries = options.maxEntries;
|
|
17501
18363
|
this.#maxEntryChars = options.maxEntryChars;
|
|
17502
18364
|
}
|
|
@@ -17714,14 +18576,14 @@ var MemoryStore = class {
|
|
|
17714
18576
|
});
|
|
17715
18577
|
}
|
|
17716
18578
|
async #readTaskItem(reference) {
|
|
17717
|
-
const document = decodeTask(await
|
|
18579
|
+
const document = decodeTask(await readFile20(this.#resolveContentPath(reference.contentPath), "utf8"));
|
|
17718
18580
|
return document.items.find((item) => item.id === reference.id)?.content ?? reference.summary;
|
|
17719
18581
|
}
|
|
17720
18582
|
#resolveContentPath(contentPath) {
|
|
17721
18583
|
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 =
|
|
18584
|
+
const path = resolve23(this.#memoryRoot, ...contentPath.split("/"));
|
|
17723
18585
|
const rel = relative17(this.#memoryRoot, path);
|
|
17724
|
-
if (rel === ".." || rel.startsWith(`..${
|
|
18586
|
+
if (rel === ".." || rel.startsWith(`..${sep17}`)) throw new Error("Memory content path escapes the memory directory");
|
|
17725
18587
|
return path;
|
|
17726
18588
|
}
|
|
17727
18589
|
async #updateIndex(update) {
|
|
@@ -17875,7 +18737,7 @@ function sanitizeTopicKey(value) {
|
|
|
17875
18737
|
return value.normalize("NFKC").toLocaleLowerCase().replace(/[^a-z0-9._-]+/g, "-").replace(/^-+|-+$/g, "").slice(0, 128);
|
|
17876
18738
|
}
|
|
17877
18739
|
function memoryId(type, content) {
|
|
17878
|
-
return
|
|
18740
|
+
return createHash15("sha256").update(`${type}\0${normalizeMemoryContent(content).toLocaleLowerCase()}`, "utf8").digest("hex").slice(0, 12);
|
|
17879
18741
|
}
|
|
17880
18742
|
function assertTaskId(taskId) {
|
|
17881
18743
|
if (!TASK_ID.test(taskId)) throw new Error(`Invalid memory task id: ${taskId}`);
|
|
@@ -18190,8 +19052,8 @@ function createExecutionEnvironment(workspace, config) {
|
|
|
18190
19052
|
}
|
|
18191
19053
|
|
|
18192
19054
|
// src/ide/client.ts
|
|
18193
|
-
import { readFile as
|
|
18194
|
-
import { basename as basename5, join as
|
|
19055
|
+
import { readFile as readFile21, readdir as readdir9, stat as stat8 } from "fs/promises";
|
|
19056
|
+
import { basename as basename5, join as join23, relative as relative18, resolve as resolve24, sep as sep18 } from "path";
|
|
18195
19057
|
var FlavorIdeClient = class {
|
|
18196
19058
|
#workspace;
|
|
18197
19059
|
#home;
|
|
@@ -18202,8 +19064,8 @@ var FlavorIdeClient = class {
|
|
|
18202
19064
|
#sessionId;
|
|
18203
19065
|
#eventTail = Promise.resolve();
|
|
18204
19066
|
constructor(options) {
|
|
18205
|
-
this.#workspace =
|
|
18206
|
-
this.#home =
|
|
19067
|
+
this.#workspace = resolve24(options.workspace);
|
|
19068
|
+
this.#home = resolve24(options.home);
|
|
18207
19069
|
this.#environment = options.environment ?? process.env;
|
|
18208
19070
|
this.#fetch = options.fetch ?? fetch;
|
|
18209
19071
|
}
|
|
@@ -18230,7 +19092,7 @@ var FlavorIdeClient = class {
|
|
|
18230
19092
|
}
|
|
18231
19093
|
async promptContext() {
|
|
18232
19094
|
const context = await this.editorContext();
|
|
18233
|
-
if (context?.filePath === void 0 || !
|
|
19095
|
+
if (context?.filePath === void 0 || !isWithin8(this.#workspace, context.filePath)) return void 0;
|
|
18234
19096
|
const path = relativeDisplay(this.#workspace, context.filePath);
|
|
18235
19097
|
const cursor = context.selection.active;
|
|
18236
19098
|
if (context.selection.isEmpty || !context.selectedText) {
|
|
@@ -18287,23 +19149,23 @@ var FlavorIdeClient = class {
|
|
|
18287
19149
|
};
|
|
18288
19150
|
if (await this.#healthy(configured)) return configured;
|
|
18289
19151
|
}
|
|
18290
|
-
const directory =
|
|
19152
|
+
const directory = join23(this.#home, ".flavor-code", "ide");
|
|
18291
19153
|
let entries;
|
|
18292
19154
|
try {
|
|
18293
|
-
entries = (await
|
|
19155
|
+
entries = (await readdir9(directory)).filter((name) => name.endsWith(".lock"));
|
|
18294
19156
|
} catch {
|
|
18295
19157
|
return void 0;
|
|
18296
19158
|
}
|
|
18297
19159
|
const candidates = (await Promise.all(entries.map(async (name) => {
|
|
18298
|
-
const path =
|
|
19160
|
+
const path = join23(directory, name);
|
|
18299
19161
|
try {
|
|
18300
|
-
const [contents, info] = await Promise.all([
|
|
19162
|
+
const [contents, info] = await Promise.all([readFile21(path, "utf8"), stat8(path)]);
|
|
18301
19163
|
const lock = parseLockfile(contents);
|
|
18302
19164
|
return lock === void 0 ? void 0 : { lock, modified: info.mtimeMs };
|
|
18303
19165
|
} catch {
|
|
18304
19166
|
return void 0;
|
|
18305
19167
|
}
|
|
18306
|
-
}))).filter((item) => item !== void 0).filter(({ lock }) => lock.workspaceFolders.some((folder) =>
|
|
19168
|
+
}))).filter((item) => item !== void 0).filter(({ lock }) => lock.workspaceFolders.some((folder) => isWithin8(folder, this.#workspace))).sort((left, right) => right.modified - left.modified);
|
|
18307
19169
|
for (const { lock } of candidates) {
|
|
18308
19170
|
if (await this.#healthy(lock)) return lock;
|
|
18309
19171
|
}
|
|
@@ -18400,10 +19262,10 @@ function parsePort(value) {
|
|
|
18400
19262
|
const port = Number(value);
|
|
18401
19263
|
return Number.isInteger(port) && port > 0 && port <= 65535 ? port : void 0;
|
|
18402
19264
|
}
|
|
18403
|
-
function
|
|
18404
|
-
const base = normalize(
|
|
18405
|
-
const target = normalize(
|
|
18406
|
-
return target === base || target.startsWith(`${base}${
|
|
19265
|
+
function isWithin8(parent, child) {
|
|
19266
|
+
const base = normalize(resolve24(parent));
|
|
19267
|
+
const target = normalize(resolve24(child));
|
|
19268
|
+
return target === base || target.startsWith(`${base}${sep18}`);
|
|
18407
19269
|
}
|
|
18408
19270
|
function normalize(path) {
|
|
18409
19271
|
const normalized = path.normalize("NFC");
|
|
@@ -18415,7 +19277,7 @@ function relativeDisplay(workspace, filePath) {
|
|
|
18415
19277
|
}
|
|
18416
19278
|
|
|
18417
19279
|
// src/jobs/registry.ts
|
|
18418
|
-
import { randomUUID as
|
|
19280
|
+
import { randomUUID as randomUUID15 } from "crypto";
|
|
18419
19281
|
var JobRegistry = class {
|
|
18420
19282
|
#jobs = /* @__PURE__ */ new Map();
|
|
18421
19283
|
#maxOutputChars;
|
|
@@ -18429,11 +19291,11 @@ var JobRegistry = class {
|
|
|
18429
19291
|
}
|
|
18430
19292
|
create(input) {
|
|
18431
19293
|
this.#pruneCompleted();
|
|
18432
|
-
const id = `job-${
|
|
19294
|
+
const id = `job-${randomUUID15().slice(0, 12)}`;
|
|
18433
19295
|
const now = (/* @__PURE__ */ new Date()).toISOString();
|
|
18434
19296
|
let settle;
|
|
18435
|
-
const done = new Promise((
|
|
18436
|
-
settle =
|
|
19297
|
+
const done = new Promise((resolve28) => {
|
|
19298
|
+
settle = resolve28;
|
|
18437
19299
|
});
|
|
18438
19300
|
const record = {
|
|
18439
19301
|
id,
|
|
@@ -18555,9 +19417,9 @@ function snapshot2(job) {
|
|
|
18555
19417
|
}
|
|
18556
19418
|
|
|
18557
19419
|
// src/terminal/service.ts
|
|
18558
|
-
import { randomUUID as
|
|
19420
|
+
import { randomUUID as randomUUID16 } from "crypto";
|
|
18559
19421
|
import { createRequire as nodeCreateRequire } from "module";
|
|
18560
|
-
import { isAbsolute as isAbsolute11, relative as relative19, resolve as
|
|
19422
|
+
import { isAbsolute as isAbsolute11, relative as relative19, resolve as resolve25, sep as sep19 } from "path";
|
|
18561
19423
|
var TerminalService = class {
|
|
18562
19424
|
#root;
|
|
18563
19425
|
#factory;
|
|
@@ -18565,7 +19427,7 @@ var TerminalService = class {
|
|
|
18565
19427
|
#maxOutputChars;
|
|
18566
19428
|
#sessions = /* @__PURE__ */ new Map();
|
|
18567
19429
|
constructor(workspace, options = {}) {
|
|
18568
|
-
this.#root =
|
|
19430
|
+
this.#root = resolve25(workspace);
|
|
18569
19431
|
this.#factory = options.factory ?? defaultFactory;
|
|
18570
19432
|
this.#jobs = options.jobs;
|
|
18571
19433
|
this.#maxOutputChars = options.maxOutputChars ?? 2e5;
|
|
@@ -18573,7 +19435,7 @@ var TerminalService = class {
|
|
|
18573
19435
|
open(input) {
|
|
18574
19436
|
const cwd = workspacePath(this.#root, input.cwd ?? ".");
|
|
18575
19437
|
const shell = input.shell ?? defaultShell();
|
|
18576
|
-
const id = `term-${
|
|
19438
|
+
const id = `term-${randomUUID16().slice(0, 12)}`;
|
|
18577
19439
|
const pty = this.#factory(shell, input.args ?? [], {
|
|
18578
19440
|
cwd,
|
|
18579
19441
|
cols: input.columns ?? 100,
|
|
@@ -18683,30 +19545,30 @@ function cleanEnvironment(environment) {
|
|
|
18683
19545
|
return Object.fromEntries(Object.entries(environment).filter((entry2) => entry2[1] !== void 0));
|
|
18684
19546
|
}
|
|
18685
19547
|
function workspacePath(root, input) {
|
|
18686
|
-
const path =
|
|
19548
|
+
const path = resolve25(root, input);
|
|
18687
19549
|
const delta = relative19(root, path);
|
|
18688
|
-
if (delta === ".." || delta.startsWith(`..${
|
|
19550
|
+
if (delta === ".." || delta.startsWith(`..${sep19}`) || isAbsolute11(delta)) throw new Error("Terminal cwd is outside the workspace");
|
|
18689
19551
|
return path;
|
|
18690
19552
|
}
|
|
18691
19553
|
|
|
18692
19554
|
// 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 =
|
|
19555
|
+
import { z as z33 } from "zod";
|
|
19556
|
+
import { resolve as resolve26 } from "path";
|
|
19557
|
+
var Id = z33.string().regex(/^term-[a-f0-9-]+$/);
|
|
19558
|
+
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() });
|
|
19559
|
+
var OptionalBoolean = z33.union([z33.boolean(), z33.string().refine((value) => value === "true" || value === "false")]);
|
|
19560
|
+
var WriteInput2 = z33.object({ id: Id, data: z33.string().max(1e5), enter: OptionalBoolean.optional() });
|
|
19561
|
+
var ReadInput3 = z33.object({ id: Id, cursor: z33.coerce.number().int().min(0).optional() });
|
|
19562
|
+
var ResizeInput = z33.object({ id: Id, columns: z33.coerce.number().int().min(10).max(500), rows: z33.coerce.number().int().min(2).max(300) });
|
|
19563
|
+
var CloseInput = z33.object({ id: Id });
|
|
18702
19564
|
function createTerminalTools(terminals, workspace = process.cwd()) {
|
|
18703
19565
|
const tools = [
|
|
18704
19566
|
{
|
|
18705
19567
|
name: "TerminalOpen",
|
|
18706
19568
|
description: "Open a persistent interactive pseudo-terminal in the workspace",
|
|
18707
19569
|
inputSchema: OpenInput,
|
|
18708
|
-
paths: (input) => [
|
|
18709
|
-
permissions: (input) => ({ paths: [
|
|
19570
|
+
paths: (input) => [resolve26(workspace, input.cwd ?? ".")],
|
|
19571
|
+
permissions: (input) => ({ paths: [resolve26(workspace, input.cwd ?? ".")], command: input.shell ?? "system-shell", args: input.args ?? [], cwd: resolve26(workspace, input.cwd ?? ".") }),
|
|
18710
19572
|
execute: async (input, _signal, context) => terminals.open({
|
|
18711
19573
|
owner: owner(context),
|
|
18712
19574
|
...input.cwd === void 0 ? {} : { cwd: input.cwd },
|
|
@@ -18774,7 +19636,7 @@ function createTerminalTools(terminals, workspace = process.cwd()) {
|
|
|
18774
19636
|
{
|
|
18775
19637
|
name: "TerminalList",
|
|
18776
19638
|
description: "List persistent terminals owned by this agent",
|
|
18777
|
-
inputSchema:
|
|
19639
|
+
inputSchema: z33.object({}),
|
|
18778
19640
|
paths: () => [],
|
|
18779
19641
|
execute: async (_input, _signal, context) => terminals.list(owner(context))
|
|
18780
19642
|
}
|
|
@@ -18783,10 +19645,10 @@ function createTerminalTools(terminals, workspace = process.cwd()) {
|
|
|
18783
19645
|
}
|
|
18784
19646
|
|
|
18785
19647
|
// src/pals/address.ts
|
|
18786
|
-
import { createHash as
|
|
19648
|
+
import { createHash as createHash16 } from "crypto";
|
|
18787
19649
|
function opaqueUserScope(userScope) {
|
|
18788
19650
|
if (userScope.length === 0) throw new Error("User scope must not be empty");
|
|
18789
|
-
const digest =
|
|
19651
|
+
const digest = createHash16("sha256").update(userScope, "utf8").digest("hex").slice(0, 16);
|
|
18790
19652
|
return `u-${digest}`;
|
|
18791
19653
|
}
|
|
18792
19654
|
function palSocketAddress(options) {
|
|
@@ -18799,7 +19661,7 @@ function palSocketAddress(options) {
|
|
|
18799
19661
|
}
|
|
18800
19662
|
|
|
18801
19663
|
// src/pals/client.ts
|
|
18802
|
-
import { randomUUID as
|
|
19664
|
+
import { randomUUID as randomUUID17 } from "crypto";
|
|
18803
19665
|
import { Socket as Socket3 } from "net";
|
|
18804
19666
|
var DEFAULT_REQUEST_TIMEOUT_MS = 5e3;
|
|
18805
19667
|
var DEFAULT_HEARTBEAT_INTERVAL_MS = 1e4;
|
|
@@ -18875,7 +19737,7 @@ var PalClient = class {
|
|
|
18875
19737
|
return this.starting;
|
|
18876
19738
|
}
|
|
18877
19739
|
async list() {
|
|
18878
|
-
const data2 = await this.request({ version: 1, type: "list", requestId:
|
|
19740
|
+
const data2 = await this.request({ version: 1, type: "list", requestId: randomUUID17() });
|
|
18879
19741
|
return PalPresenceListSchema.parse(data2);
|
|
18880
19742
|
}
|
|
18881
19743
|
async rename(alias) {
|
|
@@ -18888,38 +19750,38 @@ var PalClient = class {
|
|
|
18888
19750
|
throw error;
|
|
18889
19751
|
}
|
|
18890
19752
|
}
|
|
18891
|
-
async sendTask(target, message2, messageId =
|
|
18892
|
-
const data2 = await this.request({ version: 1, type: "task", requestId:
|
|
19753
|
+
async sendTask(target, message2, messageId = randomUUID17()) {
|
|
19754
|
+
const data2 = await this.request({ version: 1, type: "task", requestId: randomUUID17(), messageId, target, goal: message2 });
|
|
18893
19755
|
return DeliveryReceiptSchema.parse(data2);
|
|
18894
19756
|
}
|
|
18895
|
-
async sendChat(target, message2, messageId =
|
|
18896
|
-
const data2 = await this.request({ version: 1, type: "chat", requestId:
|
|
19757
|
+
async sendChat(target, message2, messageId = randomUUID17()) {
|
|
19758
|
+
const data2 = await this.request({ version: 1, type: "chat", requestId: randomUUID17(), messageId, target, message: message2 });
|
|
18897
19759
|
return DeliveryReceiptSchema.parse(data2);
|
|
18898
19760
|
}
|
|
18899
19761
|
async startCoWork(input) {
|
|
18900
19762
|
const data2 = await this.request({
|
|
18901
19763
|
version: 1,
|
|
18902
19764
|
type: "cowork-propose",
|
|
18903
|
-
requestId:
|
|
18904
|
-
coWorkId: input.coWorkId ??
|
|
19765
|
+
requestId: randomUUID17(),
|
|
19766
|
+
coWorkId: input.coWorkId ?? randomUUID17(),
|
|
18905
19767
|
goal: input.goal,
|
|
18906
19768
|
participants: input.participants
|
|
18907
19769
|
});
|
|
18908
19770
|
return CoWorkSnapshotSchema.parse(data2);
|
|
18909
19771
|
}
|
|
18910
19772
|
async coWorkAction(action) {
|
|
18911
|
-
const request = { version: 1, requestId:
|
|
19773
|
+
const request = { version: 1, requestId: randomUUID17(), ...action };
|
|
18912
19774
|
return CoWorkSnapshotSchema.parse(await this.request(request));
|
|
18913
19775
|
}
|
|
18914
19776
|
async coWorkStatus(coWorkId) {
|
|
18915
|
-
const data2 = await this.request({ version: 1, type: "cowork-get", requestId:
|
|
19777
|
+
const data2 = await this.request({ version: 1, type: "cowork-get", requestId: randomUUID17(), coWorkId });
|
|
18916
19778
|
return CoWorkSnapshotSchema.parse(data2);
|
|
18917
19779
|
}
|
|
18918
19780
|
async integrateCoWork(input) {
|
|
18919
19781
|
return this.coWorkAction({ type: "cowork-integration", ...input });
|
|
18920
19782
|
}
|
|
18921
19783
|
async cancelCoWork(coWorkId, reason) {
|
|
18922
|
-
const data2 = await this.request({ version: 1, type: "cowork-cancel", requestId:
|
|
19784
|
+
const data2 = await this.request({ version: 1, type: "cowork-cancel", requestId: randomUUID17(), coWorkId, reason });
|
|
18923
19785
|
return CoWorkSnapshotSchema.parse(data2);
|
|
18924
19786
|
}
|
|
18925
19787
|
subscribe(listener) {
|
|
@@ -18931,9 +19793,9 @@ var PalClient = class {
|
|
|
18931
19793
|
if (this.registered && this.socket !== void 0) {
|
|
18932
19794
|
let disconnectTimer;
|
|
18933
19795
|
await Promise.race([
|
|
18934
|
-
this.request({ version: 1, type: "disconnect", requestId:
|
|
18935
|
-
new Promise((
|
|
18936
|
-
disconnectTimer = setTimeout(
|
|
19796
|
+
this.request({ version: 1, type: "disconnect", requestId: randomUUID17() }).catch(() => void 0),
|
|
19797
|
+
new Promise((resolve28) => {
|
|
19798
|
+
disconnectTimer = setTimeout(resolve28, Math.min(this.requestTimeoutMs, 250));
|
|
18937
19799
|
})
|
|
18938
19800
|
]);
|
|
18939
19801
|
if (disconnectTimer !== void 0) clearTimeout(disconnectTimer);
|
|
@@ -18979,7 +19841,7 @@ var PalClient = class {
|
|
|
18979
19841
|
return presence;
|
|
18980
19842
|
}
|
|
18981
19843
|
openSocket() {
|
|
18982
|
-
return new Promise((
|
|
19844
|
+
return new Promise((resolve28, reject) => {
|
|
18983
19845
|
let settled = false;
|
|
18984
19846
|
const rejectConnection = (error) => {
|
|
18985
19847
|
if (settled) return;
|
|
@@ -19002,7 +19864,7 @@ var PalClient = class {
|
|
|
19002
19864
|
settled = true;
|
|
19003
19865
|
if (this.connectingReject === rejectConnection) this.connectingReject = void 0;
|
|
19004
19866
|
socket.off("error", onInitialError);
|
|
19005
|
-
|
|
19867
|
+
resolve28();
|
|
19006
19868
|
};
|
|
19007
19869
|
const onInitialError = (error) => {
|
|
19008
19870
|
socket.off("connect", onConnect);
|
|
@@ -19022,7 +19884,7 @@ var PalClient = class {
|
|
|
19022
19884
|
const data2 = await this.request({
|
|
19023
19885
|
version: 1,
|
|
19024
19886
|
type: "register",
|
|
19025
|
-
requestId:
|
|
19887
|
+
requestId: randomUUID17(),
|
|
19026
19888
|
authToken: this.authToken,
|
|
19027
19889
|
presence: { version: 1, ...this.registration, connectedAt: now, lastSeenAt: now }
|
|
19028
19890
|
});
|
|
@@ -19033,12 +19895,12 @@ var PalClient = class {
|
|
|
19033
19895
|
if (this.closed || socket === void 0 || socket.destroyed || !socket.writable) {
|
|
19034
19896
|
return Promise.reject(new Error(this.closed ? "Pal client is closed" : "Pal client is not connected"));
|
|
19035
19897
|
}
|
|
19036
|
-
return new Promise((
|
|
19898
|
+
return new Promise((resolve28, reject) => {
|
|
19037
19899
|
const timer = setTimeout(() => {
|
|
19038
19900
|
this.pending.delete(request.requestId);
|
|
19039
19901
|
reject(new Error(`Pal request '${request.type}' timed out`));
|
|
19040
19902
|
}, this.requestTimeoutMs);
|
|
19041
|
-
this.pending.set(request.requestId, { resolve:
|
|
19903
|
+
this.pending.set(request.requestId, { resolve: resolve28, reject, timer });
|
|
19042
19904
|
let frame;
|
|
19043
19905
|
try {
|
|
19044
19906
|
frame = encodeControlFrame(BrokerRequestSchema.parse(request));
|
|
@@ -19134,7 +19996,7 @@ var PalClient = class {
|
|
|
19134
19996
|
if (!this.registered) return;
|
|
19135
19997
|
if (this.heartbeatPending) return;
|
|
19136
19998
|
this.heartbeatPending = true;
|
|
19137
|
-
void this.request({ version: 1, type: "heartbeat", requestId:
|
|
19999
|
+
void this.request({ version: 1, type: "heartbeat", requestId: randomUUID17() }).catch(() => void 0).finally(() => {
|
|
19138
20000
|
this.heartbeatPending = false;
|
|
19139
20001
|
});
|
|
19140
20002
|
}, this.heartbeatIntervalMs);
|
|
@@ -19153,7 +20015,7 @@ var PalClient = class {
|
|
|
19153
20015
|
};
|
|
19154
20016
|
|
|
19155
20017
|
// src/pals/tools.ts
|
|
19156
|
-
import { z as
|
|
20018
|
+
import { z as z34 } from "zod";
|
|
19157
20019
|
var DEFAULT_SHARE_BUDGET_BYTES = 128 * 1024;
|
|
19158
20020
|
var CollaborationShareGuard = class {
|
|
19159
20021
|
redact;
|
|
@@ -19174,25 +20036,25 @@ var CollaborationShareGuard = class {
|
|
|
19174
20036
|
return redacted;
|
|
19175
20037
|
}
|
|
19176
20038
|
};
|
|
19177
|
-
var Uuid =
|
|
19178
|
-
var Hash =
|
|
20039
|
+
var Uuid = z34.uuid();
|
|
20040
|
+
var Hash = z34.string().regex(/^[a-f0-9]{64}$/);
|
|
19179
20041
|
var Target = PalTargetSchema;
|
|
19180
|
-
var BoundedText =
|
|
20042
|
+
var BoundedText = z34.string().trim().min(1).refine(
|
|
19181
20043
|
(value) => Buffer.byteLength(value, "utf8") <= MAX_MESSAGE_BYTES,
|
|
19182
20044
|
`Text must not exceed ${MAX_MESSAGE_BYTES} UTF-8 bytes`
|
|
19183
20045
|
);
|
|
19184
|
-
var CompletionEvidence =
|
|
20046
|
+
var CompletionEvidence = z34.string().trim().min(1).refine(
|
|
19185
20047
|
(value) => Buffer.byteLength(value, "utf8") <= MAX_COMPLETION_DETAIL_BYTES,
|
|
19186
20048
|
`Completion evidence must not exceed ${MAX_COMPLETION_DETAIL_BYTES} UTF-8 bytes`
|
|
19187
20049
|
);
|
|
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:
|
|
20050
|
+
var EmptyInput = z34.object({}).strict();
|
|
20051
|
+
var StateInput = z34.object({ coWorkId: Uuid }).strict();
|
|
20052
|
+
var TokenInput = z34.object({ coWorkId: Uuid, epoch: z34.number().int().positive(), planHash: Hash }).strict();
|
|
20053
|
+
var SendInput = z34.object({ target: Target, message: BoundedText, coWorkId: Uuid.optional() }).strict();
|
|
20054
|
+
var PlanInput = z34.object({ plan: CoWorkPlanSchema }).strict();
|
|
20055
|
+
var ProgressInput = z34.object({ coWorkId: Uuid, target: Target, detail: BoundedText }).strict();
|
|
20056
|
+
var CompleteInput = TokenInput.extend({ passed: z34.boolean(), detail: CompletionEvidence }).strict();
|
|
20057
|
+
var IntegrateInput = TokenInput.extend({ passed: z34.boolean(), evidence: CompletionEvidence }).strict();
|
|
19196
20058
|
var sharedBytes = (...values) => values.reduce((total, value) => total + Buffer.byteLength(value, "utf8"), 0);
|
|
19197
20059
|
function resolvePresence(presences, target) {
|
|
19198
20060
|
const normalized = normalizePalIdentity(target);
|
|
@@ -19376,7 +20238,7 @@ var ApprovalBridge = class {
|
|
|
19376
20238
|
request(request, signal = new AbortController().signal) {
|
|
19377
20239
|
if (this.#settle !== void 0) return Promise.resolve("deny");
|
|
19378
20240
|
if (signal.aborted) return Promise.resolve("deny");
|
|
19379
|
-
this.#pending = { id:
|
|
20241
|
+
this.#pending = { id: randomUUID18(), ...request };
|
|
19380
20242
|
this.#onChange?.();
|
|
19381
20243
|
return new Promise((resolvePromise) => {
|
|
19382
20244
|
this.#settle = resolvePromise;
|
|
@@ -19397,8 +20259,8 @@ var ApprovalBridge = class {
|
|
|
19397
20259
|
}
|
|
19398
20260
|
};
|
|
19399
20261
|
async function createProductionRuntime(options) {
|
|
19400
|
-
const workspace =
|
|
19401
|
-
const home =
|
|
20262
|
+
const workspace = resolve27(options.workspace ?? process.cwd());
|
|
20263
|
+
const home = resolve27(options.home ?? homedir2());
|
|
19402
20264
|
const environment = options.environment ?? process.env;
|
|
19403
20265
|
const ide = new FlavorIdeClient({ workspace, home, environment });
|
|
19404
20266
|
const ideReady = ide.initialize();
|
|
@@ -19507,6 +20369,12 @@ async function createProductionRuntime(options) {
|
|
|
19507
20369
|
);
|
|
19508
20370
|
const registry = new ModelRegistry();
|
|
19509
20371
|
const diagnostics = [];
|
|
20372
|
+
const permissionPolicy = await loadPermissionPolicy({
|
|
20373
|
+
workspace,
|
|
20374
|
+
home,
|
|
20375
|
+
...environment.FLAVOR_MANAGED_PERMISSIONS === void 0 ? {} : { managedPath: environment.FLAVOR_MANAGED_PERMISSIONS }
|
|
20376
|
+
});
|
|
20377
|
+
diagnostics.push(...permissionPolicy.diagnostics);
|
|
19510
20378
|
const approvals = new ApprovalBridge(options.onApprovalChange);
|
|
19511
20379
|
const resolveToolApproval = options.approvalPolicy === "deny" && options.rpcToolApprovals !== true ? () => "deny" : (request, signal) => approvals.request(request, signal);
|
|
19512
20380
|
const questions = new QuestionBridge(options.onApprovalChange);
|
|
@@ -19601,9 +20469,10 @@ async function createProductionRuntime(options) {
|
|
|
19601
20469
|
const registeredProviders = registration.registered;
|
|
19602
20470
|
let effectiveLlm = registration.effectiveLlm;
|
|
19603
20471
|
const pluginHost = new PluginHost({
|
|
19604
|
-
globalPluginDirs: [
|
|
19605
|
-
projectPluginDirs: [
|
|
20472
|
+
globalPluginDirs: [join24(home, ".flavor-code", "plugins")],
|
|
20473
|
+
projectPluginDirs: [join24(workspace, ".flavor", "plugins")],
|
|
19606
20474
|
config,
|
|
20475
|
+
sandbox: options.pluginSandbox ?? true,
|
|
19607
20476
|
registrations: {
|
|
19608
20477
|
command(name, handler, description) {
|
|
19609
20478
|
if (typeof handler !== "function") throw new Error(`Plugin command "${name}" must be a function.`);
|
|
@@ -19704,7 +20573,7 @@ async function createProductionRuntime(options) {
|
|
|
19704
20573
|
const address = palSocketAddress({
|
|
19705
20574
|
platform: process.platform,
|
|
19706
20575
|
userScope: home,
|
|
19707
|
-
runtimeDir:
|
|
20576
|
+
runtimeDir: join24(tmpdir3(), `flavor-code-pals-${createHash17("sha256").update(home).digest("hex").slice(0, 16)}`)
|
|
19708
20577
|
});
|
|
19709
20578
|
collaborationClient = collaboration.client ?? collaboration.createClient?.({
|
|
19710
20579
|
instanceId: collaboration.instanceId,
|
|
@@ -19802,8 +20671,8 @@ async function createProductionRuntime(options) {
|
|
|
19802
20671
|
diagnostics.push(`MCP servers could not start: ${message(error)}`);
|
|
19803
20672
|
});
|
|
19804
20673
|
const skills = new SkillRegistry({
|
|
19805
|
-
globalRoots: [
|
|
19806
|
-
projectRoots: [
|
|
20674
|
+
globalRoots: [join24(home, ".flavor-code", "skills")],
|
|
20675
|
+
projectRoots: [join24(workspace, ".flavor", "skills"), ...pluginSkillRoots],
|
|
19807
20676
|
authorizeResource: async () => true,
|
|
19808
20677
|
disabledNames: config.skills.disabled
|
|
19809
20678
|
});
|
|
@@ -19811,7 +20680,7 @@ async function createProductionRuntime(options) {
|
|
|
19811
20680
|
skillsReady.catch(() => void 0);
|
|
19812
20681
|
tools.push(createSkillTool(skills), createSkillResourceTool(skills));
|
|
19813
20682
|
if (options.extraTools !== void 0) tools.push(...options.extraTools);
|
|
19814
|
-
const flavor = await optionalText2(
|
|
20683
|
+
const flavor = await optionalText2(join24(workspace, "FLAVOR.md"));
|
|
19815
20684
|
const instructionBaseline = await workspaceInstructions.baseline();
|
|
19816
20685
|
let memoryContext;
|
|
19817
20686
|
if (memoryStore !== void 0) {
|
|
@@ -19835,7 +20704,18 @@ async function createProductionRuntime(options) {
|
|
|
19835
20704
|
let taskResults = { ...recovered?.tasks.results ?? {} };
|
|
19836
20705
|
const subagentStartedAt = {};
|
|
19837
20706
|
const subagentElapsedMs = {};
|
|
19838
|
-
let sessionId = recovered?.sessionId ?? `session-${(/* @__PURE__ */ new Date()).toISOString().replace(/[^0-9]/g, "").slice(0, 17)}-${
|
|
20707
|
+
let sessionId = recovered?.sessionId ?? `session-${(/* @__PURE__ */ new Date()).toISOString().replace(/[^0-9]/g, "").slice(0, 17)}-${randomUUID18().slice(0, 8)}`;
|
|
20708
|
+
let harnessJournal = new HarnessJournal({ workspace, sessionId });
|
|
20709
|
+
const harnessRecovery = harnessJournal.recover();
|
|
20710
|
+
if (harnessRecovery.queue.length > 0 || harnessRecovery.incompleteTools.length > 0 || harnessRecovery.incompleteModelIds.length > 0 || harnessRecovery.interruptedTurnIds.length > 0) {
|
|
20711
|
+
diagnostics.push(
|
|
20712
|
+
`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).`
|
|
20713
|
+
);
|
|
20714
|
+
for (const tool of harnessRecovery.incompleteTools.filter((item) => !item.retrySafe)) {
|
|
20715
|
+
diagnostics.push(`Interrupted non-retry-safe tool "${tool.tool}" was not replayed (${tool.inputHash.slice(0, 12)}).`);
|
|
20716
|
+
}
|
|
20717
|
+
harnessJournal.markRecoveryComplete(harnessRecovery);
|
|
20718
|
+
}
|
|
19839
20719
|
setUsageSession(sessionId);
|
|
19840
20720
|
ideSessionId = sessionId;
|
|
19841
20721
|
await ideReady;
|
|
@@ -20008,8 +20888,8 @@ async function createProductionRuntime(options) {
|
|
|
20008
20888
|
volatileSystem: () => [
|
|
20009
20889
|
buildCurrentDateSection(promptEnvironment.date),
|
|
20010
20890
|
buildRuntimeEnvironmentSection({
|
|
20011
|
-
model: agent === "main" ? harness.mainModelId : contextModelId,
|
|
20012
|
-
permissionMode: agent === "subagent" ? harness.permissionMode === "plan" ? "plan" : "bubble" : harness.permissionMode
|
|
20891
|
+
model: agent === "main" && harnessCreated ? harness.mainModelId : contextModelId,
|
|
20892
|
+
permissionMode: agent === "subagent" ? (harnessCreated ? harness.permissionMode : recovered?.permissionMode ?? config.permissionMode) === "plan" ? "plan" : "bubble" : harnessCreated ? harness.permissionMode : recovered?.permissionMode ?? config.permissionMode
|
|
20013
20893
|
})
|
|
20014
20894
|
],
|
|
20015
20895
|
...flavor === void 0 ? {} : { flavor },
|
|
@@ -20048,15 +20928,36 @@ async function createProductionRuntime(options) {
|
|
|
20048
20928
|
tools,
|
|
20049
20929
|
createContext,
|
|
20050
20930
|
permissionMode: recovered?.permissionMode ?? config.permissionMode,
|
|
20931
|
+
permissionPolicy,
|
|
20051
20932
|
maxIterationsMain: config.maxIterations.main,
|
|
20052
20933
|
maxIterationsSubagent: config.maxIterations.subagent,
|
|
20053
20934
|
hasActiveProgress,
|
|
20054
20935
|
afterToolSuccess: async (_tool, paths, _input, _output, context) => workspaceInstructions.discover(paths, context.ownerId ?? context.agent),
|
|
20936
|
+
toolJournal: {
|
|
20937
|
+
start: (tool, input, retrySafe) => harnessJournal.startTool(tool, input, retrySafe),
|
|
20938
|
+
complete: (id, result) => harnessJournal.completeTool(id, result),
|
|
20939
|
+
interrupt: (id, reason) => harnessJournal.interruptTool(id, reason)
|
|
20940
|
+
},
|
|
20941
|
+
modelJournal: {
|
|
20942
|
+
start: (input) => harnessJournal.startModel(input),
|
|
20943
|
+
complete: (id, completed, error) => harnessJournal.completeModel(id, completed, error)
|
|
20944
|
+
},
|
|
20055
20945
|
approve: resolveToolApproval
|
|
20056
20946
|
});
|
|
20057
20947
|
harnessCreated = true;
|
|
20058
20948
|
if (recovered !== void 0) harness.main.context.restore({
|
|
20059
20949
|
...recovered.conversation.compact === void 0 ? {} : { compact: recovered.conversation.compact },
|
|
20950
|
+
...recovered.conversation.epoch === void 0 ? {} : { epoch: {
|
|
20951
|
+
...recovered.conversation.epoch,
|
|
20952
|
+
stableMessages: recovered.conversation.epoch.stableMessages.map((message2) => ({
|
|
20953
|
+
role: "system",
|
|
20954
|
+
content: message2.content,
|
|
20955
|
+
...message2.cacheBreakpoint === void 0 ? {} : { cacheBreakpoint: message2.cacheBreakpoint }
|
|
20956
|
+
}))
|
|
20957
|
+
} },
|
|
20958
|
+
...recovered.conversation.visibilityLog === void 0 ? {} : {
|
|
20959
|
+
visibilityLog: recovered.conversation.visibilityLog
|
|
20960
|
+
},
|
|
20060
20961
|
messages: recovered.conversation.messages.map((message2) => {
|
|
20061
20962
|
const metadata = {
|
|
20062
20963
|
...message2.toolCallId === void 0 ? {} : { toolCallId: message2.toolCallId },
|
|
@@ -20235,7 +21136,7 @@ async function createProductionRuntime(options) {
|
|
|
20235
21136
|
agent,
|
|
20236
21137
|
providerError,
|
|
20237
21138
|
errorCode,
|
|
20238
|
-
errorMessage,
|
|
21139
|
+
errorMessage: errorMessage2,
|
|
20239
21140
|
attempt,
|
|
20240
21141
|
maxAttempts,
|
|
20241
21142
|
purpose,
|
|
@@ -20251,7 +21152,7 @@ async function createProductionRuntime(options) {
|
|
|
20251
21152
|
model: typeof modelId === "string" ? modelId : void 0,
|
|
20252
21153
|
agent: typeof agent === "string" ? agent : void 0,
|
|
20253
21154
|
errorCode: typeof errorCode === "string" ? errorCode : void 0,
|
|
20254
|
-
errorMessage: typeof
|
|
21155
|
+
errorMessage: typeof errorMessage2 === "string" ? errorMessage2 : void 0,
|
|
20255
21156
|
attempt: typeof attempt === "number" ? attempt : void 0,
|
|
20256
21157
|
maxAttempts: typeof maxAttempts === "number" ? maxAttempts : void 0,
|
|
20257
21158
|
purpose: typeof purpose === "string" ? purpose : void 0,
|
|
@@ -20306,7 +21207,7 @@ async function createProductionRuntime(options) {
|
|
|
20306
21207
|
evolveService.toolDefinition(),
|
|
20307
21208
|
...mcpTools
|
|
20308
21209
|
];
|
|
20309
|
-
const loopFlavor = await optionalText2(
|
|
21210
|
+
const loopFlavor = await optionalText2(join24(input.workspace, "FLAVOR.md"));
|
|
20310
21211
|
const loopInstructionBaseline = await loopInstructions.baseline();
|
|
20311
21212
|
const loopEnvironment = createPromptEnvironment({
|
|
20312
21213
|
now: /* @__PURE__ */ new Date(),
|
|
@@ -20318,6 +21219,7 @@ async function createProductionRuntime(options) {
|
|
|
20318
21219
|
let compactionInputTokens = 0;
|
|
20319
21220
|
let compactionOutputTokens = 0;
|
|
20320
21221
|
let loopHarness;
|
|
21222
|
+
let loopHarnessCreated = false;
|
|
20321
21223
|
const createLoopContext = (agent, agentTools, contextModelId) => {
|
|
20322
21224
|
const language2 = resolveLanguage(config.language);
|
|
20323
21225
|
const { compactAtChars, toolOutputChars, ...compaction } = config.context;
|
|
@@ -20337,8 +21239,8 @@ async function createProductionRuntime(options) {
|
|
|
20337
21239
|
volatileSystem: () => [
|
|
20338
21240
|
buildCurrentDateSection(loopEnvironment.date),
|
|
20339
21241
|
buildRuntimeEnvironmentSection({
|
|
20340
|
-
model: agent === "main" ? loopHarness.mainModelId : contextModelId,
|
|
20341
|
-
permissionMode: agent === "subagent" ? loopHarness.permissionMode === "plan" ? "plan" : "bubble" : loopHarness.permissionMode
|
|
21242
|
+
model: agent === "main" && loopHarnessCreated ? loopHarness.mainModelId : contextModelId,
|
|
21243
|
+
permissionMode: agent === "subagent" ? (loopHarnessCreated ? loopHarness.permissionMode : harness.permissionMode) === "plan" ? "plan" : "bubble" : loopHarnessCreated ? loopHarness.permissionMode : harness.permissionMode
|
|
20342
21244
|
})
|
|
20343
21245
|
],
|
|
20344
21246
|
...loopFlavor === void 0 ? {} : { flavor: loopFlavor },
|
|
@@ -20372,12 +21274,23 @@ async function createProductionRuntime(options) {
|
|
|
20372
21274
|
tools: loopTools,
|
|
20373
21275
|
createContext: createLoopContext,
|
|
20374
21276
|
permissionMode: harness.permissionMode,
|
|
21277
|
+
permissionPolicy,
|
|
20375
21278
|
maxIterationsMain: config.maxIterations.main,
|
|
20376
21279
|
maxIterationsSubagent: config.maxIterations.subagent,
|
|
20377
21280
|
loopMode: true,
|
|
20378
21281
|
afterToolSuccess: async (_tool, paths, _input, _output, context) => loopInstructions.discover(paths, context.ownerId ?? context.agent),
|
|
21282
|
+
toolJournal: {
|
|
21283
|
+
start: (tool, input2, retrySafe) => harnessJournal.startTool(tool, input2, retrySafe),
|
|
21284
|
+
complete: (id, result) => harnessJournal.completeTool(id, result),
|
|
21285
|
+
interrupt: (id, reason) => harnessJournal.interruptTool(id, reason)
|
|
21286
|
+
},
|
|
21287
|
+
modelJournal: {
|
|
21288
|
+
start: (input2) => harnessJournal.startModel(input2),
|
|
21289
|
+
complete: (id, completed, error) => harnessJournal.completeModel(id, completed, error)
|
|
21290
|
+
},
|
|
20379
21291
|
approve: resolveToolApproval
|
|
20380
21292
|
});
|
|
21293
|
+
loopHarnessCreated = true;
|
|
20381
21294
|
let runReason = "finished";
|
|
20382
21295
|
try {
|
|
20383
21296
|
evolveService.beginRun();
|
|
@@ -20433,7 +21346,7 @@ async function createProductionRuntime(options) {
|
|
|
20433
21346
|
return answers[0] === "Continue" ? "approved" : "rejected";
|
|
20434
21347
|
},
|
|
20435
21348
|
fingerprint: workspaceFingerprint,
|
|
20436
|
-
idFactory: () => `loop-${(/* @__PURE__ */ new Date()).toISOString().replace(/[^0-9]/g, "").slice(0, 17)}-${
|
|
21349
|
+
idFactory: () => `loop-${(/* @__PURE__ */ new Date()).toISOString().replace(/[^0-9]/g, "").slice(0, 17)}-${randomUUID18().slice(0, 8)}`
|
|
20437
21350
|
});
|
|
20438
21351
|
const goalStore = new GoalStore({ workspace });
|
|
20439
21352
|
const goalOrchestrator = new GoalOrchestrator({
|
|
@@ -20445,8 +21358,14 @@ async function createProductionRuntime(options) {
|
|
|
20445
21358
|
maxRounds: 5,
|
|
20446
21359
|
maxStallStreak: 2,
|
|
20447
21360
|
persistence: goalStore,
|
|
21361
|
+
verifyHost: async (signal) => runVerificationPlan(
|
|
21362
|
+
await inferVerificationPlan(workspace),
|
|
21363
|
+
workspace,
|
|
21364
|
+
signal,
|
|
21365
|
+
executionEnvironment
|
|
21366
|
+
),
|
|
20448
21367
|
now: () => (/* @__PURE__ */ new Date()).toISOString(),
|
|
20449
|
-
idFactory: () => `goal-${(/* @__PURE__ */ new Date()).toISOString().replace(/[^0-9]/g, "").slice(0, 17)}-${
|
|
21368
|
+
idFactory: () => `goal-${(/* @__PURE__ */ new Date()).toISOString().replace(/[^0-9]/g, "").slice(0, 17)}-${randomUUID18().slice(0, 8)}`,
|
|
20450
21369
|
runWorker: ({ workspace: goalWorkspace, prompt, signal }) => runLoopWorker({ workspace: goalWorkspace, prompt, signal })
|
|
20451
21370
|
});
|
|
20452
21371
|
let sessionHistory = await SessionHistory.open({
|
|
@@ -20507,6 +21426,13 @@ async function createProductionRuntime(options) {
|
|
|
20507
21426
|
const services = {
|
|
20508
21427
|
hooks,
|
|
20509
21428
|
workspace,
|
|
21429
|
+
durableQueue: {
|
|
21430
|
+
recover: () => harnessRecovery.queue,
|
|
21431
|
+
admit: (kind, payload) => harnessJournal.admitQueue(kind, payload),
|
|
21432
|
+
claim: (id) => harnessJournal.claimQueue(id),
|
|
21433
|
+
ack: (id) => harnessJournal.ackQueue(id),
|
|
21434
|
+
release: (id, reason) => harnessJournal.releaseQueue(id, reason)
|
|
21435
|
+
},
|
|
20510
21436
|
mainModel: () => mainModel,
|
|
20511
21437
|
subagentModel: () => childModel,
|
|
20512
21438
|
llmServiceName: () => effectiveLlm?.serviceName,
|
|
@@ -20518,7 +21444,14 @@ async function createProductionRuntime(options) {
|
|
|
20518
21444
|
...palServices === void 0 ? {} : { pals: palServices },
|
|
20519
21445
|
run: (prompt, signal, runOptions) => {
|
|
20520
21446
|
interruptedTaskPlanNeedsReassessment = false;
|
|
20521
|
-
|
|
21447
|
+
const turnConfig = Object.freeze({
|
|
21448
|
+
mainModel,
|
|
21449
|
+
subagentModel: childModel,
|
|
21450
|
+
permissionMode: harness.permissionMode,
|
|
21451
|
+
contextEpoch: harness.main.context.snapshot().epoch?.id ?? "legacy"
|
|
21452
|
+
});
|
|
21453
|
+
const turnId = harnessJournal.startTurn(turnConfig, { prompt, initialUserMessage: runOptions?.initialUserMessage });
|
|
21454
|
+
return durableTurn(persistAndCheckpointAfter(runMain(
|
|
20522
21455
|
harness,
|
|
20523
21456
|
skills,
|
|
20524
21457
|
prompt,
|
|
@@ -20538,7 +21471,7 @@ async function createProductionRuntime(options) {
|
|
|
20538
21471
|
prompt,
|
|
20539
21472
|
context: harness.main.context.snapshot(),
|
|
20540
21473
|
label: `turn: ${prompt.slice(0, 80)}`
|
|
20541
|
-
}));
|
|
21474
|
+
})), harnessJournal, turnId, turnConfig);
|
|
20542
21475
|
},
|
|
20543
21476
|
runSkill: (skill, prompt, signal) => persistAfter(
|
|
20544
21477
|
runExplicitSkill(harness, skills, skill, prompt, signal, selectedModels.mainError),
|
|
@@ -20635,7 +21568,7 @@ async function createProductionRuntime(options) {
|
|
|
20635
21568
|
}, focus, signal),
|
|
20636
21569
|
audit: async (toolFilter) => {
|
|
20637
21570
|
try {
|
|
20638
|
-
const raw = await
|
|
21571
|
+
const raw = await readFile22(auditLogger.path, "utf8");
|
|
20639
21572
|
const lines = raw.trim().split("\n").filter((line) => line.length > 0);
|
|
20640
21573
|
const entries = lines.map((line) => {
|
|
20641
21574
|
try {
|
|
@@ -20674,7 +21607,7 @@ ${summary2}`;
|
|
|
20674
21607
|
},
|
|
20675
21608
|
usage: async () => {
|
|
20676
21609
|
try {
|
|
20677
|
-
const raw = await
|
|
21610
|
+
const raw = await readFile22(usageLogPath(), "utf8");
|
|
20678
21611
|
return formatUsageSummary(summarizeUsage(parseUsageEntries(raw)));
|
|
20679
21612
|
} catch (error) {
|
|
20680
21613
|
if (typeof error === "object" && error !== null && "code" in error && error.code === "ENOENT") {
|
|
@@ -20702,7 +21635,8 @@ ${summary2}`;
|
|
|
20702
21635
|
for (const key of Object.keys(subagentStartedAt)) delete subagentStartedAt[key];
|
|
20703
21636
|
for (const key of Object.keys(subagentElapsedMs)) delete subagentElapsedMs[key];
|
|
20704
21637
|
const previousIdeSessionId = ideSessionId;
|
|
20705
|
-
sessionId = `session-${(/* @__PURE__ */ new Date()).toISOString().replace(/[^0-9]/g, "").slice(0, 17)}-${
|
|
21638
|
+
sessionId = `session-${(/* @__PURE__ */ new Date()).toISOString().replace(/[^0-9]/g, "").slice(0, 17)}-${randomUUID18().slice(0, 8)}`;
|
|
21639
|
+
harnessJournal = new HarnessJournal({ workspace, sessionId });
|
|
20706
21640
|
setUsageSession(sessionId);
|
|
20707
21641
|
ideSessionId = sessionId;
|
|
20708
21642
|
if (previousIdeSessionId !== void 0) await ide.endSession(previousIdeSessionId);
|
|
@@ -20800,7 +21734,7 @@ ${renderMemoryDocument(entries)}`;
|
|
|
20800
21734
|
return `Provider "${providerName}" is missing authorizationUrl, tokenUrl, or clientId.`;
|
|
20801
21735
|
}
|
|
20802
21736
|
try {
|
|
20803
|
-
const tokenStore = createFileTokenStore(
|
|
21737
|
+
const tokenStore = createFileTokenStore(join24(home, ".flavor-code", "auth.json"));
|
|
20804
21738
|
const credentialId = oauthCredentialId(oauthConfig.tokenUrl, oauthConfig.clientId);
|
|
20805
21739
|
await migrateLegacyOAuthToken(tokenStore, providerName, credentialId);
|
|
20806
21740
|
const oauth = new OAuthCallbackAuthProvider({
|
|
@@ -20843,7 +21777,7 @@ ${renderMemoryDocument(entries)}`;
|
|
|
20843
21777
|
}
|
|
20844
21778
|
},
|
|
20845
21779
|
async logout() {
|
|
20846
|
-
const tokenStore = createFileTokenStore(
|
|
21780
|
+
const tokenStore = createFileTokenStore(join24(home, ".flavor-code", "auth.json"));
|
|
20847
21781
|
const tokens = await tokenStore.load();
|
|
20848
21782
|
const hadCredentials = Object.keys(tokens).length > 0 || effectiveLlm !== void 0;
|
|
20849
21783
|
if (!hadCredentials) {
|
|
@@ -21073,7 +22007,7 @@ function terminalProgressState(status) {
|
|
|
21073
22007
|
return "failed";
|
|
21074
22008
|
}
|
|
21075
22009
|
async function workspaceFingerprint(workspace) {
|
|
21076
|
-
const hash =
|
|
22010
|
+
const hash = createHash17("sha256");
|
|
21077
22011
|
const diff = await execFileNoThrow(
|
|
21078
22012
|
"git",
|
|
21079
22013
|
["-C", workspace, "diff", "--no-ext-diff", "--binary", "HEAD"],
|
|
@@ -21089,13 +22023,13 @@ async function workspaceFingerprint(workspace) {
|
|
|
21089
22023
|
let remaining = 5 * 1024 * 1024;
|
|
21090
22024
|
for (const name of untracked.stdout.split("\0").filter(Boolean).sort()) {
|
|
21091
22025
|
if (name === ".flavor/loops" || name.startsWith(".flavor/loops/")) continue;
|
|
21092
|
-
const path =
|
|
21093
|
-
const relativePath = relative20(
|
|
21094
|
-
if (relativePath === ".." || relativePath.startsWith(`..${
|
|
22026
|
+
const path = resolve27(workspace, name);
|
|
22027
|
+
const relativePath = relative20(resolve27(workspace), path);
|
|
22028
|
+
if (relativePath === ".." || relativePath.startsWith(`..${sep20}`)) continue;
|
|
21095
22029
|
hash.update(name);
|
|
21096
22030
|
if (remaining <= 0) continue;
|
|
21097
22031
|
try {
|
|
21098
|
-
const content = await
|
|
22032
|
+
const content = await readFile22(path);
|
|
21099
22033
|
const slice = content.subarray(0, remaining);
|
|
21100
22034
|
hash.update(slice);
|
|
21101
22035
|
remaining -= slice.length;
|
|
@@ -21194,6 +22128,27 @@ async function* persistEach(source, persist) {
|
|
|
21194
22128
|
await persist();
|
|
21195
22129
|
}
|
|
21196
22130
|
}
|
|
22131
|
+
async function* durableTurn(source, journal, turnId, config) {
|
|
22132
|
+
let failure;
|
|
22133
|
+
let completed = false;
|
|
22134
|
+
try {
|
|
22135
|
+
for await (const event of source) {
|
|
22136
|
+
if (event.type === "error") failure = event.error.message;
|
|
22137
|
+
yield event;
|
|
22138
|
+
}
|
|
22139
|
+
completed = true;
|
|
22140
|
+
} catch (error) {
|
|
22141
|
+
failure = message(error);
|
|
22142
|
+
throw error;
|
|
22143
|
+
} finally {
|
|
22144
|
+
if (completed && failure === void 0) {
|
|
22145
|
+
journal.completeTurn(turnId);
|
|
22146
|
+
journal.savepoint("turn-complete", config);
|
|
22147
|
+
} else {
|
|
22148
|
+
journal.interruptTurn(turnId, failure ?? "turn consumer interrupted before completion");
|
|
22149
|
+
}
|
|
22150
|
+
}
|
|
22151
|
+
}
|
|
21197
22152
|
async function runGitCommit(deps, hint, signal) {
|
|
21198
22153
|
if (!await isGitRepository(deps.workspace)) return "Not a git repository \u2014 /commit needs git.";
|
|
21199
22154
|
let summary2 = await changeSummary(deps.workspace);
|
|
@@ -21287,7 +22242,7 @@ async function registerConfiguredAdapters(providers, registry, environment, diag
|
|
|
21287
22242
|
const configured = { ...providers };
|
|
21288
22243
|
if (configured.openai === void 0 && environment.OPENAI_API_KEY) configured.openai = { type: "openai", apiKey: environment.OPENAI_API_KEY };
|
|
21289
22244
|
if (configured.anthropic === void 0 && environment.ANTHROPIC_API_KEY) configured.anthropic = { type: "anthropic", apiKey: environment.ANTHROPIC_API_KEY };
|
|
21290
|
-
const oauthTokenStore = createFileTokenStore(
|
|
22245
|
+
const oauthTokenStore = createFileTokenStore(join24(home, ".flavor-code", "auth.json"));
|
|
21291
22246
|
const registered = [];
|
|
21292
22247
|
let effectiveLlm;
|
|
21293
22248
|
for (const [name, provider] of Object.entries(configured)) {
|
|
@@ -21552,7 +22507,7 @@ async function generateSleepReview(registry, modelId, prompt, signal) {
|
|
|
21552
22507
|
}
|
|
21553
22508
|
async function optionalText2(path) {
|
|
21554
22509
|
try {
|
|
21555
|
-
return await
|
|
22510
|
+
return await readFile22(path, "utf8");
|
|
21556
22511
|
} catch (error) {
|
|
21557
22512
|
if (typeof error === "object" && error !== null && "code" in error && error.code === "ENOENT") return void 0;
|
|
21558
22513
|
throw error;
|
|
@@ -21568,13 +22523,15 @@ function sameToolName(left, right) {
|
|
|
21568
22523
|
function storedConversation(snapshot4) {
|
|
21569
22524
|
return {
|
|
21570
22525
|
...snapshot4.compact === void 0 ? {} : { compact: snapshot4.compact },
|
|
22526
|
+
...snapshot4.epoch === void 0 ? {} : { epoch: snapshot4.epoch },
|
|
22527
|
+
...snapshot4.visibilityLog === void 0 ? {} : { visibilityLog: snapshot4.visibilityLog },
|
|
21571
22528
|
messages: snapshot4.messages.flatMap((message2) => {
|
|
21572
|
-
if (message2.role === "system") return [];
|
|
21573
22529
|
const metadata = {
|
|
21574
22530
|
...message2.toolCallId === void 0 ? {} : { toolCallId: message2.toolCallId },
|
|
21575
22531
|
...message2.toolCalls === void 0 ? {} : { toolCalls: message2.toolCalls }
|
|
21576
22532
|
};
|
|
21577
22533
|
if (message2.role === "user") return { role: "user", content: message2.content, ...metadata };
|
|
22534
|
+
if (message2.role === "system") return { role: "system", content: message2.content, ...metadata };
|
|
21578
22535
|
if (message2.role === "assistant") return { role: "assistant", content: message2.content, ...metadata };
|
|
21579
22536
|
return { role: "tool", content: message2.content, ...metadata };
|
|
21580
22537
|
})
|
|
@@ -21704,10 +22661,10 @@ async function* runGoalSession(orchestrator, goal, signal) {
|
|
|
21704
22661
|
}
|
|
21705
22662
|
function memoryTranscriptHash(messages) {
|
|
21706
22663
|
const visible = messages.filter((item) => item.role === "user" || item.role === "assistant").map((item) => `${item.role}\0${modelContentText(item.content).trim()}`).join("\n");
|
|
21707
|
-
return
|
|
22664
|
+
return createHash17("sha256").update(visible, "utf8").digest("hex");
|
|
21708
22665
|
}
|
|
21709
22666
|
function createMemoryTaskId() {
|
|
21710
|
-
return `memory-${
|
|
22667
|
+
return `memory-${randomUUID18()}`;
|
|
21711
22668
|
}
|
|
21712
22669
|
|
|
21713
22670
|
export {
|