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.
@@ -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((resolve26, reject) => {
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
- resolve26({ timedOut: true, value: void 0 });
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
- resolve26({ timedOut: false, value });
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((resolve26, reject) => {
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
- resolve26(value);
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)) return;
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)) return;
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) => this.#queuedSubmissions[kind].shift() ?? normalizeRequiredSubmission(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((resolve26) => {
3823
+ return new Promise((resolve28) => {
3756
3824
  const check = () => {
3757
- if (!this.#activePlanning.has(key)) resolve26();
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((resolve26) => {
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
- resolve26(connected);
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((resolve26, reject) => {
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
- resolve26();
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((resolve26) => this.server.close(() => resolve26()));
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((resolve26) => setTimeout(resolve26, milliseconds));
4933
+ return new Promise((resolve28) => setTimeout(resolve28, milliseconds));
4849
4934
  }
4850
4935
  function canConnect2(address) {
4851
- return new Promise((resolve26) => {
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
- resolve26(connected);
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((resolve26) => {
4893
- resolveClosed = resolve26;
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 readFile20 } from "fs/promises";
4934
- import { createHash as createHash13, randomUUID as randomUUID16 } from "crypto";
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 join22, relative as relative20, resolve as resolve25, sep as sep19 } from "path";
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
- const userMemory = this.#resolvedUserMemory();
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
- // Stable user preferences close the cacheable prefix.
5956
- ...userMemory === void 0 ? [] : [{ role: "system", content: `User memory
5957
- ${userMemory}` }]
5958
- ];
5959
- const dynamic = [
5960
- ...this.#memory === void 0 ? [] : [{ role: "system", content: `Long-term memory
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 (dynamic.length > 0 && stable.length > 0) stable[stable.length - 1].cacheBreakpoint = true;
5967
- return [...stable, ...dynamic];
5968
- }
5969
- #resolvedUserMemory() {
5970
- if (this.#userMemory === void 0) return void 0;
5971
- const content = resolveSystemSections(this.#userMemory).join("\n\n");
5972
- return content.length === 0 ? void 0 : content;
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") continue;
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 createHash4 } from "crypto";
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: createHash4("sha256").update(bytes).digest("hex")
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((resolve26, reject) => {
6792
+ return new Promise((resolve28, reject) => {
6547
6793
  const timer = setTimeout(() => {
6548
6794
  signal?.removeEventListener("abort", onAbort);
6549
- resolve26();
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((resolve26, reject) => {
7397
+ return new Promise((resolve28, reject) => {
7136
7398
  const timer = setTimeout(() => {
7137
7399
  signal?.removeEventListener("abort", onAbort);
7138
- resolve26();
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 randomUUID6 } from "crypto";
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(":", "-")}-${randomUUID6()}-${safeFilename(tool)}.txt`;
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, errorMessage) {
7509
- const result = { ok: false, error: { code, message: errorMessage } };
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 z10 } from "zod";
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 = z10.record(z10.string(), z10.unknown());
7745
- var eventSchema = (type) => z10.object({
7746
- version: z10.literal(1),
7747
- type: z10.literal(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 = z10.discriminatedUnion("type", [
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 = z10.object({
7773
- decision: z10.enum(["allow", "deny", "ask"]),
7774
- reason: z10.string().optional(),
7775
- updatedInput: z10.unknown().optional(),
7776
- additionalContext: z10.string().optional()
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((resolve26, reject) => {
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
- resolve26(HookDecisionSchema.parse(JSON.parse(stdout)));
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 join10 } from "path";
8061
- import { z as z13 } from "zod";
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 join8, resolve as resolve9 } from "path";
8640
+ import { join as join9, resolve as resolve10 } from "path";
8067
8641
 
8068
8642
  // src/plugins/host.ts
8069
- import { lstatSync, realpathSync as realpathSync2 } from "fs";
8070
- import { lstat as lstat5, open as open5, readdir as readdir3, realpath as realpath4, stat as stat3, writeFile as writeFile5 } from "fs/promises";
8071
- import { isAbsolute as isAbsolute5, relative as relative7, resolve as resolve8, sep as sep7 } from "path";
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 z12 } from "zod";
8648
+ import { z as z13 } from "zod";
8074
8649
 
8075
8650
  // src/plugins/types.ts
8076
- import { z as z11 } from "zod";
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 = z11.object({ name: z11.string().regex(CONTRIBUTION_NAME) }).strict();
8080
- var uniqueArray = (schema, label, identify = (item) => String(item)) => z11.array(schema).max(256).superRefine((items, context) => {
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 = z11.object({
8089
- name: z11.string().regex(PLUGIN_NAME),
8090
- version: z11.string().min(1).max(64),
8091
- apiVersion: z11.literal("1"),
8092
- main: z11.string().min(1).max(512),
8093
- permissions: uniqueArray(z11.enum(["filesystem:read", "filesystem:write"]), "permission").max(16),
8094
- contributes: z11.object({
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(z11.object({ name: z11.enum(HOOK_EVENT_NAMES) }).strict(), "hook contribution", (item) => item.name),
8098
- skillRoots: uniqueArray(entry.extend({ path: z11.string().min(1).max(512) }).strict(), "skill-root contribution", (item) => item.name),
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 same = (left, right) => left.length === right.length && left.every((name, index) => name === right[index]?.name);
8278
- return same(result.registeredCommands, contributions.commands) && same(result.registeredTools, contributions.tools) && same(result.registeredHooks, contributions.hooks) && same(result.registeredSkillRoots, contributions.skillRoots) && same(result.registeredModelAdapters, contributions.modelAdapters);
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(resolve8(directory, entry2.name));
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(resolve8(root, MANIFEST));
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
- return { manifest, root, source, entry: entry2, entrySnapshot };
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: z12.unknown(),
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 = { name: plugin, version: candidate.manifest.version, source: candidate.source, root: candidate.root };
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 = resolve8(rawPath);
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 = realpathSync2.native(registered);
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 = resolve8(root, value);
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 = relative7(root, candidate);
8714
- if (delta === ".." || delta.startsWith(`..${sep7}`) || isAbsolute5(delta)) throw new Error(`${label} escapes the plugin root`);
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 = join8(".flavor", "plugins");
8758
- var VERSIONS_DIR = join8(".flavor", "plugins", ".versions");
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 resolve9(workspace, PLUGIN_DIR, name);
9370
+ return resolve10(workspace, PLUGIN_DIR, name);
8765
9371
  }
8766
9372
  function versionsDirFor(workspace, name) {
8767
- return resolve9(workspace, VERSIONS_DIR, name);
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 = join8(dir, "flavor-plugin.json");
8803
- const entryPath = join8(dir, "index.js");
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(join8(tmpdir2(), "flavor-evolve-verify-"));
9429
+ const sandbox = await mkdtemp(join9(tmpdir2(), "flavor-evolve-verify-"));
8824
9430
  try {
8825
- await cp(pluginRoot, join8(sandbox, name), { recursive: true });
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 = join8(versionsDirFor(workspace, name), timestamp());
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 = join8(versionsRoot, snapshots[snapshots.length - 1]);
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 createHash5 } from "crypto";
8906
- import { appendFile, mkdir as mkdir9, readFile as readFile7, rm as rm3, writeFile as writeFile7 } from "fs/promises";
8907
- import { dirname as dirname11, join as join9 } from "path";
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 createHash5("sha1").update(`${tool}::${errorCode ?? ""}::${normalizeError(error)}`).digest("hex").slice(0, 12);
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 readFile7(file, "utf8");
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 readFile7(file, "utf8"));
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 readFile7(file, "utf8"));
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 = join9(workspace, ".flavor", "evolve");
8981
- this.signalsFile = join9(this.dir, "signals.jsonl");
8982
- this.reflectionsFile = join9(this.dir, "reflections.jsonl");
8983
- this.doneFile = join9(this.dir, "done.json");
8984
- this.verifiedFile = join9(this.dir, "verified.json");
8985
- this.rulesFile = join9(this.dir, "rules.json");
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((resolve26) => {
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
- resolve26({ ok: false, stdout, stderr: `${stderr}
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
- resolve26({ ok: false, stdout, stderr: `${stderr}
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
- resolve26({ ok: code === 0, stdout, stderr, code: code ?? "unknown" });
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(join10(dir, "PLAN.md"), buildPlan(suggestion, name, "See the repeated failure below; describe your fix in PLAN.md."), "utf8");
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 = z13.object({
9587
- suggestionId: z13.string().min(1).describe("Signal id from the evolve suggestions"),
9588
- implementation: z13.string().min(1).describe("Concise description of the fix to implement (plugin plan, or the guardrail rule text when kind is prompt_rule)"),
9589
- kind: z13.enum(["plugin", "prompt_rule"]).optional().describe(
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(join10(dir, "PLAN.md"), buildPlan(suggestion, name, implementation), "utf8");
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 z14 } from "zod";
9656
- var LoopStatusSchema = z14.enum([
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 = z14.object({
9667
- dimension: z14.enum(["cycles", "tokens"]),
9668
- previousCheckpoint: z14.number().int().nonnegative(),
9669
- newCheckpoint: z14.number().int().positive(),
9670
- usage: z14.number().int().nonnegative(),
9671
- approvedAt: z14.string().datetime({ offset: true })
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 = z14.object({
9674
- command: z14.string().min(1),
9675
- args: z14.array(z14.string()),
9676
- exitCode: z14.number().int().nullable(),
9677
- stdout: z14.string(),
9678
- stderr: z14.string(),
9679
- truncated: z14.boolean()
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 = z14.object({
9682
- passed: z14.boolean(),
9683
- commands: z14.array(LoopVerificationCommandSchema),
9684
- summary: z14.string()
10287
+ var LoopVerificationEvidenceSchema = z15.object({
10288
+ passed: z15.boolean(),
10289
+ commands: z15.array(LoopVerificationCommandSchema),
10290
+ summary: z15.string()
9685
10291
  }).strict();
9686
- var LoopCycleEvidenceSchema = z14.object({
9687
- cycle: z14.number().int().positive(),
9688
- startedAt: z14.string().datetime({ offset: true }),
9689
- completedAt: z14.string().datetime({ offset: true }),
9690
- inputTokens: z14.number().int().nonnegative(),
9691
- outputTokens: z14.number().int().nonnegative(),
9692
- workspaceFingerprint: z14.string(),
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 = z14.object({
9696
- version: z14.literal(1),
9697
- loopId: z14.string().regex(/^[A-Za-z0-9][A-Za-z0-9._-]{0,127}$/),
9698
- goal: z14.string().trim().min(1),
9699
- workspace: z14.string().min(1),
9700
- createdAt: z14.string().datetime({ offset: true }),
9701
- updatedAt: z14.string().datetime({ offset: true }),
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: z14.object({
9704
- cycleStep: z14.number().int().positive(),
9705
- tokenStep: z14.number().int().positive(),
9706
- isolation: z14.literal("auto")
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: z14.object({
9709
- cyclesUsed: z14.number().int().nonnegative(),
9710
- inputTokens: z14.number().int().nonnegative(),
9711
- outputTokens: z14.number().int().nonnegative(),
9712
- cycleCheckpoint: z14.number().int().positive(),
9713
- tokenCheckpoint: z14.number().int().positive(),
9714
- approvals: z14.array(LoopApprovalSchema)
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: z14.array(LoopCycleEvidenceSchema),
9717
- terminalReason: z14.string().optional()
10322
+ cycles: z15.array(LoopCycleEvidenceSchema),
10323
+ terminalReason: z15.string().optional()
9718
10324
  }).strict();
9719
- var LoopEventSchema = z14.object({
9720
- version: z14.literal(1),
9721
- type: z14.enum(["created", "cycle_started", "cycle_completed", "verification", "budget_approved", "terminal"]),
9722
- timestamp: z14.string().datetime({ offset: true }),
9723
- loopId: z14.string().regex(/^[A-Za-z0-9][A-Za-z0-9._-]{0,127}$/),
9724
- payload: z14.record(z14.string(), z14.unknown())
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 join12 } from "path";
10644
+ import { join as join13 } from "path";
10038
10645
 
10039
10646
  // src/goal/types.ts
10040
- import { z as z15 } from "zod";
10041
- var GoalPhaseSchema = z15.enum([
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 = z15.enum([
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 = z15.object({
10057
- id: z15.number().int().positive(),
10058
- description: z15.string().min(1),
10059
- type: z15.enum(["gating", "evidence"])
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 PlanSchema = z15.object({
10062
- kind: z15.enum(["code-change", "analysis", "research"]),
10063
- criteria: z15.array(AcceptanceCriterionSchema).min(1).max(32),
10064
- verificationPlan: z15.string().min(1),
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 GapSchema = z15.object({
10071
- criterion: z15.string().min(1),
10072
- description: z15.string().min(1),
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 VerdictSchema = z15.object({
10076
- refuted: z15.boolean(),
10077
- gaps: z15.array(GapSchema)
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 = z15.object({
10080
- id: z15.string().regex(/^[A-Za-z0-9][A-Za-z0-9._-]{0,127}$/),
10081
- objective: z15.string().min(1),
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: z15.string().nullable(),
10086
- verifyRounds: z15.number().int().min(0),
10087
- workerRounds: z15.number().int().min(0),
10088
- lastGaps: z15.array(GapSchema),
10089
- gapFingerprint: z15.string(),
10090
- stallStreak: z15.number().int().min(0),
10091
- createdAt: z15.string().datetime({ offset: true }),
10092
- updatedAt: z15.string().datetime({ offset: true })
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 createHash6 } from "crypto";
10183
- import { readFile as readFile8, readdir as readdir5 } from "fs/promises";
10184
- import { join as join11, relative as relative8 } from "path";
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. Audit the CURRENT workspace state \u2014 do not re-execute code unless it is a cheap spot-check.",
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: false, gaps: [] });
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 createHash6("sha256").update(content).digest("hex").slice(0, 16);
10982
+ return createHash9("sha256").update(content).digest("hex").slice(0, 16);
10349
10983
  }
10350
- async function collectEvidence(workspace, objective, finalResponse, priorGaps) {
10351
- const changedFiles = await collectChangedFiles(workspace);
10352
- const planPath = join11(workspace, ".flavor", "goal-plan.md");
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 readFile8(planPath, "utf8");
10989
+ planFile = await readFile9(planPath, "utf8");
10356
10990
  } catch {
10357
10991
  }
10358
10992
  return {
10359
10993
  objective,
10360
- changedFiles: changedFiles.map((f) => relative8(workspace, f)),
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 collectChangedFiles(workspace) {
10367
- const results = [];
10368
- const seen = /* @__PURE__ */ new Set();
10369
- async function walk(dir) {
10370
- let entries;
10371
- try {
10372
- entries = await readdir5(dir, { withFileTypes: true });
10373
- } catch {
10374
- return;
10375
- }
10376
- for (const entry2 of entries) {
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
- await walk(workspace);
10391
- return results.sort();
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
- try {
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: "achieved",
10514
- summary: `Classifier infrastructure error (fail-open): ${message(error)}`
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 = join12(workspace, ".flavor");
10575
- const planPath = join12(dir, "goal-plan.md");
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 randomUUID7 } from "crypto";
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 join13, relative as relative9, resolve as resolve10, sep as sep8 } from "path";
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 = resolve10(options.workspace);
10661
- this.#root = join13(this.#workspace, ".flavor", "goals");
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 = join13(this.#root, `${state.id}.json`);
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 = join13(this.#root, `.${state.id}.${process.pid}.${randomUUID7()}.tmp`);
11340
+ const temporary = join14(this.#root, `.${state.id}.${process.pid}.${randomUUID9()}.tmp`);
10676
11341
  let handle;
10677
11342
  try {
10678
- handle = await open6(temporary, constants.O_CREAT | constants.O_EXCL | constants.O_WRONLY, 384);
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 = resolve10(target);
10692
- const stop = resolve10(root);
11356
+ let cursor = resolve11(target);
11357
+ const stop = resolve11(root);
10693
11358
  const value = relative9(stop, cursor);
10694
- if (value === ".." || value.startsWith(`..${sep8}`) || value.includes(":")) {
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 readFile9 } from "fs/promises";
10716
- import { join as join14, resolve as resolve11 } from "path";
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 = resolve11(options.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 = join14(root, ".worktrees");
10740
- const path = join14(worktrees, `loop-${options.loopId}`);
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 = resolve11(root, commonDirectory.stdout.trim());
10770
- const infoDirectory = join14(gitDirectory, "info");
10771
- const excludePath = join14(infoDirectory, "exclude");
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 readFile9(excludePath, "utf8");
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 constants2 } from "fs";
10786
- import { lstat as lstat7, mkdir as mkdir13, open as open7, readFile as readFile10, realpath as realpath5, rename as rename5, rm as rm5, stat as stat5 } from "fs/promises";
10787
- import { dirname as dirname13, join as join15, relative as relative10, resolve as resolve12, sep as sep9 } from "path";
10788
- import { randomUUID as randomUUID8 } from "crypto";
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 = resolve12(options.workspace);
10798
- this.#root = join15(this.#workspace, ".flavor", "loops");
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 = join15(directory, "state.json");
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 = join15(directory, `.state.${process.pid}.${randomUUID8()}.tmp`);
11484
+ const temporary = join16(directory, `.state.${process.pid}.${randomUUID10()}.tmp`);
10820
11485
  let handle;
10821
11486
  try {
10822
- handle = await open7(temporary, constants2.O_CREAT | constants2.O_EXCL | constants2.O_WRONLY, 384);
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 = join15(directory, "state.json");
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 readFile10(target, "utf8");
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()}-${randomUUID8().slice(0, 8)}`;
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 = join15(directory, "events.jsonl");
10856
- const handle = await open7(path, constants2.O_CREAT | constants2.O_APPEND | constants2.O_WRONLY, 384);
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 = join15(this.#root, loopId);
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 = join15(this.#root, loopId);
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 = resolve12(target);
10897
- const stop = resolve12(root);
10898
- if (!isWithin2(stop, cursor)) throw new Error("Loop path escapes workspace");
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 isWithin2(root, path) {
10913
- const value = relative10(resolve12(root), resolve12(path));
10914
- return value === "" || !value.startsWith(`..${sep9}`) && value !== ".." && !value.includes(":");
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(resolve12(path));
11583
+ return await realpath5(resolve13(path));
10919
11584
  } catch {
10920
- return resolve12(path);
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 readFile11 } from "fs/promises";
10929
- import { join as join16 } from "path";
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 resolve13, sep as sep10 } from "path";
10934
- import { z as z17 } from "zod";
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 z16 } from "zod";
10938
- var JobId = z16.string().regex(/^job-[a-f0-9-]+$/);
10939
- var ReadInput = z16.object({ id: JobId, cursor: z16.coerce.number().int().min(0).optional() });
10940
- var IdInput = z16.object({ id: JobId });
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: z16.object({}),
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 = z17.object({
11022
- command: z17.string().min(1),
11023
- args: z17.array(z17.string()),
11024
- cwd: z17.string().min(1).nullable().optional(),
11025
- timeoutMs: z17.coerce.number().int().positive().max(864e5).nullable().optional(),
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: z17.union([z17.boolean(), z17.string().refine((value) => value === "true" || value === "false")]).optional()
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 = resolve13(workspace);
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 = resolve13(root, cwd);
12040
+ const candidate = resolve14(root, cwd);
11376
12041
  const delta = relative11(root, candidate);
11377
- if (delta === ".." || delta.startsWith(`..${sep10}`) || isAbsolute6(delta)) throw new Error("Working directory is outside the workspace");
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(join16(workspace, "package.json"));
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(join16(workspace, "FLAVOR.md"));
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 readFile11(path, "utf8");
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 readFile12 } from "fs/promises";
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 readFile12(block.source.path)).toString("base64")
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 readFile13 } from "fs/promises";
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 readFile13(block.source.path)).toString("base64")}`,
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 createHash7 } from "crypto";
12197
- import { z as z18 } from "zod";
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 = z18.fromJSONSchema(
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 = `__${createHash7("sha256").update(remoteName).digest("hex").slice(0, 8)}`;
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 resolve14 } from "path";
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: resolve14(input.workspace, config.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 createHash9 } from "crypto";
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 createHash8 } from "crypto";
13099
+ import { createHash as createHash12 } from "crypto";
12435
13100
  function oauthCredentialId(tokenUrl, clientId) {
12436
- const digest = createHash8("sha256").update(`${tokenUrl}\0${clientId}`, "utf8").digest("base64url");
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((resolve26, reject) => {
12610
- serverResolve = resolve26;
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((resolve26, reject) => {
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
- resolve26({ redirectUri, promise: codePromise });
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 createHash9("sha256").update(verifier).digest("base64url");
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 constants3 } from "fs";
12929
- import { lstat as lstat8, open as defaultOpen, readdir as readdir6, realpath as realpath6 } from "fs/promises";
12930
- import { isAbsolute as isAbsolute7, relative as relative12, resolve as resolve15, sep as sep11 } from "path";
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 = resolve15(record.metadata.root, ...parts);
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 = resolve15(configuredRoot);
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 readdir6(root, { withFileTypes: true });
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 = resolve15(root, entry2.name);
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 (!isWithin3(physicalRoot, physicalSkillRoot)) throw new Error("Skill directory escapes its registry root");
13111
- const skillFile = resolve15(skillRoot, "SKILL.md");
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 (!isWithin3(root, canonical3)) throw new Error(`${label} escapes the skill root`);
13253
- const noFollow = typeof constants3.O_NOFOLLOW === "number" ? constants3.O_NOFOLLOW : 0;
13254
- const handle = await opener(canonical3, constants3.O_RDONLY | noFollow);
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 isWithin3(root, candidate) {
14111
+ function isWithin4(root, candidate) {
13312
14112
  const delta = relative12(root, candidate);
13313
- return delta === "" || delta !== ".." && !delta.startsWith(`..${sep11}`) && !isAbsolute7(delta);
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 z19 } from "zod";
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 = z19.object({
13418
- skill: z19.string().min(1),
13419
- arguments: z19.string().optional()
14217
+ var SkillInput = z21.object({
14218
+ skill: z21.string().min(1),
14219
+ arguments: z21.string().optional()
13420
14220
  }).strict();
13421
- var SkillResourceInput = z19.object({
13422
- skill: z19.string().min(1),
13423
- reference: z19.string().min(1)
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 constants4 } from "fs";
13476
- import { lstat as lstat9, mkdir as mkdir14, open as open8, readFile as readFile14, readdir as readdir7, realpath as realpath7, rename as rename6, rm as rm6, stat as stat6 } from "fs/promises";
13477
- import { basename as basename3, dirname as dirname15, join as join17, relative as relative13, resolve as resolve16, sep as sep12 } from "path";
13478
- import { randomUUID as randomUUID9 } from "crypto";
13479
- import { z as z20 } from "zod";
13480
- var SESSION_VERSION = 3;
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 = z20.string().regex(/^[A-Za-z0-9][A-Za-z0-9._-]{0,127}$/, "Invalid session id");
13483
- var IsoDateSchema = z20.string().datetime({ offset: true });
13484
- var ToolCallSchema = z20.object({
13485
- id: z20.string().min(1).max(256),
13486
- name: z20.string().min(1).max(256),
13487
- input: z20.unknown()
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 = z20.object({
13490
- type: z20.literal("text"),
13491
- text: z20.string().max(DEFAULT_MAX_SESSION_BYTES)
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 = z20.object({
13494
- type: z20.literal("image"),
13495
- source: z20.object({ type: z20.literal("file"), path: z20.string().min(1).max(32768) }).strict(),
13496
- mediaType: z20.enum(["image/png", "image/jpeg", "image/webp"]),
13497
- sha256: z20.string().regex(/^[a-f0-9]{64}$/),
13498
- bytes: z20.number().int().positive().max(20 * 1024 * 1024),
13499
- name: z20.string().min(1).max(255).optional()
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 = z20.union([
13502
- z20.string().max(DEFAULT_MAX_SESSION_BYTES),
13503
- z20.array(z20.discriminatedUnion("type", [TextContentBlockSchema, ImageContentBlockSchema])).min(1).max(100)
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: z20.string().min(1).max(256).optional(),
13507
- toolCalls: z20.array(ToolCallSchema).max(1e3).optional()
14306
+ toolCallId: z22.string().min(1).max(256).optional(),
14307
+ toolCalls: z22.array(ToolCallSchema).max(1e3).optional()
13508
14308
  };
13509
- var MessageSchema = z20.discriminatedUnion("role", [
13510
- z20.object({ role: z20.literal("user"), content: UserContentSchema, ...MessageMetadataShape }).strict(),
13511
- z20.object({ role: z20.literal("assistant"), content: z20.string().max(DEFAULT_MAX_SESSION_BYTES), ...MessageMetadataShape }).strict(),
13512
- z20.object({ role: z20.literal("tool"), content: z20.string().max(DEFAULT_MAX_SESSION_BYTES), ...MessageMetadataShape }).strict()
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 = z20.object({ role: z20.literal("system"), content: z20.string().max(DEFAULT_MAX_SESSION_BYTES) }).strict();
13515
- var CompactBoundarySchema = z20.object({
13516
- summary: z20.string().min(1).max(DEFAULT_MAX_SESSION_BYTES),
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 = z20.enum(["pending", "running", "completed", "failed", "blocked", "cancelled"]);
13520
- var MemoryLifecycleSchema = z20.object({
13521
- status: z20.enum(["active", "completed", "abandoned"]),
13522
- taskId: z20.string().regex(/^memory-[A-Za-z0-9._-]{1,120}$/).optional(),
13523
- messageStart: z20.number().int().min(0).max(5e4).optional(),
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: z20.string().regex(/^[a-f0-9]{64}$/).optional()
14326
+ transcriptHash: z22.string().regex(/^[a-f0-9]{64}$/).optional()
13526
14327
  }).strict();
13527
- var SessionBaseSchema = z20.object({
14328
+ var SessionBaseSchema = z22.object({
13528
14329
  sessionId: SessionIdSchema,
13529
14330
  createdAt: IsoDateSchema,
13530
14331
  updatedAt: IsoDateSchema,
13531
- workspace: z20.object({ path: z20.string().min(1).max(32768) }).strict(),
13532
- tasks: z20.object({
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: z20.record(z20.string(), StateSchema),
13536
- results: z20.record(z20.string(), SubagentResultSchema)
14336
+ states: z22.record(z22.string(), StateSchema),
14337
+ results: z22.record(z22.string(), SubagentResultSchema)
13537
14338
  }).strict(),
13538
- models: z20.object({ main: z20.string().min(1).max(1024), subagent: z20.string().min(1).max(1024) }).strict(),
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: z20.literal(1),
13544
- conversation: z20.object({
14344
+ version: z22.literal(1),
14345
+ conversation: z22.object({
13545
14346
  summary: LegacySummarySchema.optional(),
13546
- messages: z20.array(MessageSchema).max(5e4)
14347
+ messages: z22.array(MessageSchema).max(5e4)
13547
14348
  }).strict()
13548
14349
  }).strict();
13549
- var ConversationSchema = z20.object({
14350
+ var ConversationSchema = z22.object({
13550
14351
  compact: CompactBoundarySchema.optional(),
13551
- messages: z20.array(MessageSchema).max(5e4)
14352
+ messages: z22.array(MessageSchema).max(5e4)
13552
14353
  }).strict();
13553
14354
  var SessionDocumentV2Schema = SessionBaseSchema.extend({
13554
- version: z20.literal(2),
14355
+ version: z22.literal(2),
13555
14356
  conversation: ConversationSchema
13556
14357
  }).strict();
13557
- var TranscriptStateSchema = z20.custom(isTranscriptState, "Invalid transcript state");
13558
- var TimelineSchema = z20.object({
13559
- version: z20.literal(1),
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: z20.literal(SESSION_VERSION),
13564
- conversation: z20.object({
14388
+ version: z22.literal(SESSION_VERSION),
14389
+ conversation: z22.object({
13565
14390
  compact: CompactBoundarySchema.optional(),
13566
- messages: z20.array(MessageSchema).max(5e4)
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 = resolve16(options.workspace);
13577
- this.#sessions = join17(this.#workspace, ".flavor", "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 = join17(this.#sessions, `.${document.sessionId}.${process.pid}.${randomUUID9()}.tmp`);
14436
+ const temporary = join19(this.#sessions, `.${document.sessionId}.${process.pid}.${randomUUID11()}.tmp`);
13608
14437
  let handle;
13609
14438
  try {
13610
- handle = await open8(temporary, constants4.O_CREAT | constants4.O_EXCL | constants4.O_WRONLY, 384);
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 (isCode6(error, "ENOENT")) throw new Error(`Session "${sessionId}" was not found in this workspace`);
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 (isCode6(error, "ENOENT")) return [];
14523
+ if (isCode7(error, "ENOENT")) return [];
13687
14524
  throw error;
13688
14525
  }
13689
- const names = (await readdir7(this.#sessions)).filter((name) => name.endsWith(".jsonl")).sort();
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 (isCode6(error, "ENOENT")) return;
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 (!isCode6(error, "ENOENT")) throw error;
14555
+ if (!isCode7(error, "ENOENT")) throw error;
13718
14556
  }
13719
14557
  }
13720
14558
  #path(sessionId) {
13721
14559
  SessionIdSchema.parse(sessionId);
13722
- const path = resolve16(this.#sessions, `${sessionId}.jsonl`);
13723
- if (!isWithin4(this.#sessions, path)) throw new Error("Invalid session id");
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 (!isWithin4(canonicalRoot, canonicalSessions)) throw new Error("Session directory escapes the workspace");
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()}-${randomUUID9().slice(0, 8)}`;
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 readFile14(path);
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 = resolve16(target);
13901
- const stop = resolve16(root);
13902
- if (!isWithin4(stop, cursor)) throw new Error("Session path escapes the workspace");
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 (!isCode6(error, "ENOENT")) throw error;
14774
+ if (!isCode7(error, "ENOENT")) throw error;
13913
14775
  }
13914
14776
  }
13915
14777
  }
13916
- function isWithin4(root, path) {
13917
- const value = relative13(resolve16(root), resolve16(path));
13918
- return value === "" || !value.startsWith(`..${sep12}`) && value !== ".." && !value.includes(":");
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(resolve16(path));
14784
+ return await realpath7(resolve18(path));
13923
14785
  } catch {
13924
- return resolve16(path);
14786
+ return resolve18(path);
13925
14787
  }
13926
14788
  }
13927
- function isCode6(error, code) {
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 constants5 } from "fs";
13950
- import { randomUUID as randomUUID10 } from "crypto";
13951
- import { lstat as lstat10, mkdir as mkdir15, open as open9, readdir as readdir8, realpath as realpath8, rename as rename7, rm as rm7 } from "fs/promises";
13952
- import { isAbsolute as isAbsolute8, join as join18, relative as relative14, resolve as resolve17, sep as sep13 } from "path";
13953
- import { z as z21 } from "zod";
13954
- var DateKeySchema = z21.string().regex(/^\d{4}-\d{2}-\d{2}$/);
13955
- var ReviewSchema = z21.object({
13956
- title: z21.string().min(1).max(120),
13957
- taskSummary: z21.array(z21.string().min(1)).min(1).max(20),
13958
- executionReflection: z21.array(z21.string().min(1)).min(1).max(20),
13959
- decisionsAndLearnings: z21.array(z21.string().min(1)).max(20),
13960
- openQuestionsAndRisks: z21.array(z21.string().min(1)).max(20),
13961
- tomorrowPlan: z21.array(z21.string().min(1)).min(1).max(20),
13962
- toolUsage: z21.object({
13963
- totalCalls: z21.number().int().min(0),
13964
- shell: z21.number().int().min(0),
13965
- fileRead: z21.number().int().min(0),
13966
- fileWrite: z21.number().int().min(0),
13967
- search: z21.number().int().min(0),
13968
- lsp: z21.number().int().min(0),
13969
- subagent: z21.number().int().min(0),
13970
- other: z21.number().int().min(0)
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: z21.object({
13973
- estimatedInput: z21.number().int().min(0),
13974
- estimatedOutput: z21.number().int().min(0),
13975
- notes: z21.string().max(200)
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: z21.object({
13978
- approvalRequests: z21.number().int().min(0),
13979
- questionsAsked: z21.number().int().min(0),
13980
- summary: z21.string().max(300)
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: z21.object({
13983
- hallucinationAlerts: z21.array(z21.string().min(1)).max(10),
13984
- failuresAndRetries: z21.array(z21.string().min(1)).max(10),
13985
- codeChangeSummary: z21.string().max(500),
13986
- overallAssessment: z21.string().max(200)
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: z21.object({
13989
- worthRemembering: z21.array(z21.string().min(1)).max(10)
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 = resolve17(options.workspace);
14007
- this.#sleepDirectory = join18(this.#workspace, ".flavor", "sleep");
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 = join18(this.#sleepDirectory, `.${date}.lock`);
14878
+ const lockPath = join20(this.#sleepDirectory, `.${date}.lock`);
14017
14879
  let lock;
14018
14880
  try {
14019
- lock = await open9(lockPath, constants5.O_CREAT | constants5.O_EXCL | constants5.O_WRONLY, 384);
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 = join18(this.#sleepDirectory, filename2);
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 = join18(this.#workspace, ".flavor");
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 (!isWithin5(root, target)) throw new Error("Sleep directory escapes the workspace");
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 readdir8(this.#sleepDirectory, { withFileTypes: true })).some((entry2) => entry2.isFile() && entry2.name.startsWith(prefix) && entry2.name.endsWith(".md"));
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 = join18(this.#sleepDirectory, `.${randomUUID10()}.tmp`);
14969
+ const temporary = join20(this.#sleepDirectory, `.${randomUUID12()}.tmp`);
14108
14970
  let handle;
14109
14971
  try {
14110
- handle = await open9(temporary, constants5.O_CREAT | constants5.O_EXCL | constants5.O_WRONLY, 384);
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 isWithin5(root, candidate) {
15290
+ function isWithin6(root, candidate) {
14429
15291
  const path = relative14(root, candidate);
14430
15292
  if (path === "") return true;
14431
- if (path === ".." || path.startsWith(`..${sep13}`) || isAbsolute8(path)) return false;
14432
- return process.platform !== "win32" || resolve17(root, path).toLowerCase().startsWith(`${resolve17(root).toLowerCase()}${sep13}`);
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 constants6 } from "fs";
14481
- import { mkdir as mkdir16, open as open10, readFile as readFile15, realpath as realpath9, rename as rename8, rm as rm8, stat as stat7 } from "fs/promises";
14482
- import { dirname as dirname16, isAbsolute as isAbsolute9, relative as relative15, resolve as resolve18, sep as sep14 } from "path";
14483
- import { randomUUID as randomUUID11 } from "crypto";
14484
- import { z as z22 } from "zod";
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 = z22.object({
14591
- path: z22.string().min(1),
14592
- maxBytes: z22.coerce.number().int().positive().max(MAX_READ_BYTES).nullable().optional(),
14593
- startLine: z22.coerce.number().int().positive().nullable().optional(),
14594
- endLine: z22.coerce.number().int().positive().nullable().optional(),
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: z22.union([z22.boolean(), z22.string().refine((value) => value === "true" || value === "false")]).nullable().optional()
15459
+ force: z24.union([z24.boolean(), z24.string().refine((value) => value === "true" || value === "false")]).nullable().optional()
14598
15460
  });
14599
- var WriteInput = z22.object({ path: z22.string().min(1), content: z22.string() });
14600
- var EditInput = z22.object({ path: z22.string().min(1), oldText: z22.string().min(1), newText: z22.string() });
14601
- var ApplyPatchInput = z22.object({ patch: z22.string().min(1) });
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, constants6.O_RDONLY));
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 = resolve18(workspace);
15661
+ const root = resolve20(workspace);
14800
15662
  const lexical = (input) => {
14801
- const candidate = resolve18(root, input);
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}.${randomUUID11()}.tmp`;
15698
+ const temporary = `${path}.${randomUUID13()}.tmp`;
14837
15699
  try {
14838
- const handle = await open10(temporary, constants6.O_CREAT | constants6.O_EXCL | constants6.O_WRONLY, 384);
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 readFile15(path);
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 readFile15(path);
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(`..${sep14}`) && delta !== ".." && !isAbsolute9(delta);
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 z23 } from "zod";
15198
- var WebFetchInput = z23.object({
15199
- url: z23.string().url().max(8192),
15200
- timeoutMs: z23.coerce.number().int().positive().max(12e4).optional(),
15201
- maxBytes: z23.coerce.number().int().positive().max(10 * 1024 * 1024).optional()
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 = z23.object({
15204
- query: z23.string().trim().min(1).max(1e3),
15205
- maxResults: z23.coerce.number().int().min(1).max(20).optional()
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: z23.object({ url: z23.string(), status: z23.number(), contentType: z23.string(), content: z23.string(), truncated: z23.boolean() }),
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: z23.object({ query: z23.string(), results: z23.array(z23.object({ title: z23.string(), url: z23.string(), snippet: z23.string() })) }),
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 existsSync2 } from "fs";
15503
- import { access, constants as constants7, readFile as readFile16 } from "fs/promises";
15504
- import { basename as basename4, dirname as dirname17, extname, isAbsolute as isAbsolute10, relative as relative16, resolve as resolve19, sep as sep15 } from "path";
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 z24 } from "zod";
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((resolve26, reject) => {
15604
- this.#pending.set(id, { resolve: resolve26, reject });
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 readFile16(filePath, "utf8");
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 = resolve19(
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 = resolve19(options.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) => existsSync2(resolve19(this.#workspace, 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, constants7.X_OK);
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 = z24.object({
15841
- file: z24.string().min(1),
15842
- line: z24.coerce.number().int().nonnegative(),
15843
- character: z24.coerce.number().int().nonnegative()
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 = z24.object({
15846
- file: z24.string().min(1),
15847
- line: z24.coerce.number().int().nonnegative(),
15848
- character: z24.coerce.number().int().nonnegative()
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 = z24.object({
15851
- file: z24.string().min(1)
16712
+ var LspDiagnosticsInput = z26.object({
16713
+ file: z26.string().min(1)
15852
16714
  });
15853
16715
  function createLspTools(workspace, options = {}) {
15854
- const root = resolve19(workspace);
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 = resolve19(root, input);
15858
- if (!isWithin6(root, candidate)) throw new Error("Path is outside the workspace");
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 isWithin6(root, candidate) {
16794
+ function isWithin7(root, candidate) {
15933
16795
  const delta = relative16(root, candidate);
15934
- return delta === "" || !delta.startsWith(`..${sep15}`) && delta !== ".." && !isAbsolute10(delta);
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 z25 } from "zod";
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 = z25.object({
16117
- severity: z25.enum(["critical", "warning", "nit"]),
16118
- file: z25.string(),
16119
- line: z25.string().describe('Line number or hunk header the finding refers to, e.g. "42" or "@@ -10,4 +10,6 @@"'),
16120
- issue: z25.string(),
16121
- suggestion: z25.string()
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 = z25.object({
16124
- summary: z25.string().describe("One-paragraph overall assessment of the change"),
16125
- verdict: z25.enum(["ship", "ship-with-fixes", "needs-work"]),
16126
- findings: z25.array(ReviewFindingSchema).max(20)
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 z26 } from "zod";
16171
- var GitHistoryInput = z26.object({
16172
- path: z26.string().describe("File to inspect; omit for the whole repository history").optional(),
16173
- limit: z26.number().int().min(1).max(50).describe("Maximum number of commits to return (default 20)").optional()
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 z27 } from "zod";
16203
- var QuestionOptionSchema = z27.object({
16204
- label: z27.string().trim().min(1),
16205
- description: z27.string().trim().min(1)
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 = z27.object({
16208
- question: z27.string().trim().min(1),
16209
- header: z27.string().trim().min(1),
16210
- options: z27.array(QuestionOptionSchema).min(1).max(4)
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 = z27.object({
16213
- questions: z27.array(QuestionSchema).min(1).max(4)
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((resolve26, reject) => {
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
- resolve26(answers);
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 z28 } from "zod";
16298
- var TodoStatusSchema = z28.enum([
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 = z28.object({
16305
- content: z28.string().trim().min(1),
17166
+ var TodoItemSchema = z30.object({
17167
+ content: z30.string().trim().min(1),
16306
17168
  status: TodoStatusSchema,
16307
- activeForm: z28.string().trim().min(1)
17169
+ activeForm: z30.string().trim().min(1)
16308
17170
  });
16309
- var TodoWriteInput = z28.object({
16310
- todos: z28.array(TodoItemSchema).min(1).max(50)
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 randomUUID12 } from "crypto";
16345
- import { link as link2, lstat as lstat11, mkdir as mkdir17, readFile as readFile17, readdir as readdir9, unlink as unlink4, writeFile as writeFile10 } from "fs/promises";
16346
- import { dirname as dirname18, join as join19, resolve as resolve20 } from "path";
16347
- import { z as z29 } from "zod";
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 = z29.record(z29.string(), z29.unknown()).superRefine((schema, context) => {
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 = z29.object({
16357
- name: z29.string().regex(TOOL_NAME).describe("Unique callable tool name using letters, digits, and underscores"),
16358
- description: z29.string().min(1).max(4096).describe("Purpose and appropriate use of the generated tool"),
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: z29.string().min(1).max(64 * 1024).describe(
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: z29.enum(["project", "global"]).default("project").describe(
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: z29.array(z29.enum(["main", "subagent"])).min(1).max(2).optional().describe(
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 = z29.object({
16371
- name: z29.string().regex(TOOL_NAME).describe("Managed tool name to remove"),
16372
- scope: z29.enum(["project", "global"]).optional().describe("Required only when the same name exists in both scopes")
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: z29.literal(MANAGED_TOOL_VERSION),
16376
- createdAt: z29.string().datetime()
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 = resolve20(options.workspace);
16389
- this.#projectRoot = join19(this.#workspace, ".flavor", "tools");
16390
- this.#globalRoot = join19(resolve20(options.home), ".flavor-code", "tools");
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 = join19(this.directory(input.scope), filename(input.name));
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 [join19(this.directory(parsed.data.scope ?? "project"), filename(parsed.data.name))];
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 readdir9(root, { withFileTypes: true })).filter((entry2) => entry2.name.endsWith(".json")).sort((left, right) => compare2(left.name, right.name)).map((entry2) => entry2.name);
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 = join19(root, name);
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 readFile17(path, "utf8")));
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: z29.object({}).strict(),
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 = z29.fromJSONSchema(record.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}.${randomUUID12()}.tmp`;
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 z30 } from "zod";
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 = z30.object({
16690
- taskAlignment: z30.number(),
16691
- evidenceGrounding: z30.number(),
16692
- processReliability: z30.number(),
16693
- reason: z30.string(),
16694
- unsupportedClaims: z30.array(z30.string()).max(3)
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 createHash10 } from "crypto";
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 createHash10("sha256").update(`${toolName}:${sorted}`).digest("hex");
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 createHash11 } from "crypto";
17393
- import { readFile as readFile18, rm as rm9 } from "fs/promises";
17394
- import { resolve as resolve21, join as join20, relative as relative17, sep as sep16 } from "path";
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 = resolve21(options.workspace);
17497
- this.#memoryRoot = join20(this.workspace, ".flavor", "memory");
17498
- this.path = join20(this.#memoryRoot, "MEMORY.md");
17499
- this.#behaviorPath = join20(this.#memoryRoot, "behavior.json");
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 readFile18(this.#resolveContentPath(reference.contentPath), "utf8"));
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 = resolve21(this.#memoryRoot, ...contentPath.split("/"));
18584
+ const path = resolve23(this.#memoryRoot, ...contentPath.split("/"));
17723
18585
  const rel = relative17(this.#memoryRoot, path);
17724
- if (rel === ".." || rel.startsWith(`..${sep16}`)) throw new Error("Memory content path escapes the memory directory");
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 createHash11("sha256").update(`${type}\0${normalizeMemoryContent(content).toLocaleLowerCase()}`, "utf8").digest("hex").slice(0, 12);
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 readFile19, readdir as readdir10, stat as stat8 } from "fs/promises";
18194
- import { basename as basename5, join as join21, relative as relative18, resolve as resolve22, sep as sep17 } from "path";
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 = resolve22(options.workspace);
18206
- this.#home = resolve22(options.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 || !isWithin7(this.#workspace, context.filePath)) return 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 = join21(this.#home, ".flavor-code", "ide");
19152
+ const directory = join23(this.#home, ".flavor-code", "ide");
18291
19153
  let entries;
18292
19154
  try {
18293
- entries = (await readdir10(directory)).filter((name) => name.endsWith(".lock"));
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 = join21(directory, name);
19160
+ const path = join23(directory, name);
18299
19161
  try {
18300
- const [contents, info] = await Promise.all([readFile19(path, "utf8"), stat8(path)]);
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) => isWithin7(folder, this.#workspace))).sort((left, right) => right.modified - left.modified);
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 isWithin7(parent, child) {
18404
- const base = normalize(resolve22(parent));
18405
- const target = normalize(resolve22(child));
18406
- return target === base || target.startsWith(`${base}${sep17}`);
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 randomUUID13 } from "crypto";
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-${randomUUID13().slice(0, 12)}`;
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((resolve26) => {
18436
- settle = resolve26;
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 randomUUID14 } from "crypto";
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 resolve23, sep as sep18 } from "path";
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 = resolve23(workspace);
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-${randomUUID14().slice(0, 12)}`;
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 = resolve23(root, input);
19548
+ const path = resolve25(root, input);
18687
19549
  const delta = relative19(root, path);
18688
- if (delta === ".." || delta.startsWith(`..${sep18}`) || isAbsolute11(delta)) throw new Error("Terminal cwd is outside the workspace");
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 z31 } from "zod";
18694
- import { resolve as resolve24 } from "path";
18695
- var Id = z31.string().regex(/^term-[a-f0-9-]+$/);
18696
- var OpenInput = z31.object({ cwd: z31.string().optional(), shell: z31.string().optional(), args: z31.array(z31.string()).optional(), columns: z31.coerce.number().int().min(10).max(500).optional(), rows: z31.coerce.number().int().min(2).max(300).optional() });
18697
- var OptionalBoolean = z31.union([z31.boolean(), z31.string().refine((value) => value === "true" || value === "false")]);
18698
- var WriteInput2 = z31.object({ id: Id, data: z31.string().max(1e5), enter: OptionalBoolean.optional() });
18699
- var ReadInput3 = z31.object({ id: Id, cursor: z31.coerce.number().int().min(0).optional() });
18700
- var ResizeInput = z31.object({ id: Id, columns: z31.coerce.number().int().min(10).max(500), rows: z31.coerce.number().int().min(2).max(300) });
18701
- var CloseInput = z31.object({ id: Id });
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) => [resolve24(workspace, input.cwd ?? ".")],
18709
- permissions: (input) => ({ paths: [resolve24(workspace, input.cwd ?? ".")], command: input.shell ?? "system-shell", args: input.args ?? [], cwd: resolve24(workspace, input.cwd ?? ".") }),
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: z31.object({}),
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 createHash12 } from "crypto";
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 = createHash12("sha256").update(userScope, "utf8").digest("hex").slice(0, 16);
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 randomUUID15 } from "crypto";
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: randomUUID15() });
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 = randomUUID15()) {
18892
- const data2 = await this.request({ version: 1, type: "task", requestId: randomUUID15(), messageId, target, goal: message2 });
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 = randomUUID15()) {
18896
- const data2 = await this.request({ version: 1, type: "chat", requestId: randomUUID15(), messageId, target, message: message2 });
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: randomUUID15(),
18904
- coWorkId: input.coWorkId ?? randomUUID15(),
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: randomUUID15(), ...action };
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: randomUUID15(), coWorkId });
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: randomUUID15(), coWorkId, reason });
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: randomUUID15() }).catch(() => void 0),
18935
- new Promise((resolve26) => {
18936
- disconnectTimer = setTimeout(resolve26, Math.min(this.requestTimeoutMs, 250));
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((resolve26, reject) => {
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
- resolve26();
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: randomUUID15(),
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((resolve26, reject) => {
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: resolve26, reject, timer });
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: randomUUID15() }).catch(() => void 0).finally(() => {
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 z32 } from "zod";
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 = z32.uuid();
19178
- var Hash = z32.string().regex(/^[a-f0-9]{64}$/);
20039
+ var Uuid = z34.uuid();
20040
+ var Hash = z34.string().regex(/^[a-f0-9]{64}$/);
19179
20041
  var Target = PalTargetSchema;
19180
- var BoundedText = z32.string().trim().min(1).refine(
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 = z32.string().trim().min(1).refine(
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 = z32.object({}).strict();
19189
- var StateInput = z32.object({ coWorkId: Uuid }).strict();
19190
- var TokenInput = z32.object({ coWorkId: Uuid, epoch: z32.number().int().positive(), planHash: Hash }).strict();
19191
- var SendInput = z32.object({ target: Target, message: BoundedText, coWorkId: Uuid.optional() }).strict();
19192
- var PlanInput = z32.object({ plan: CoWorkPlanSchema }).strict();
19193
- var ProgressInput = z32.object({ coWorkId: Uuid, target: Target, detail: BoundedText }).strict();
19194
- var CompleteInput = TokenInput.extend({ passed: z32.boolean(), detail: CompletionEvidence }).strict();
19195
- var IntegrateInput = TokenInput.extend({ passed: z32.boolean(), evidence: CompletionEvidence }).strict();
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: randomUUID16(), ...request };
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 = resolve25(options.workspace ?? process.cwd());
19401
- const home = resolve25(options.home ?? homedir2());
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: [join22(home, ".flavor-code", "plugins")],
19605
- projectPluginDirs: [join22(workspace, ".flavor", "plugins")],
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: join22(tmpdir3(), `flavor-code-pals-${createHash13("sha256").update(home).digest("hex").slice(0, 16)}`)
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: [join22(home, ".flavor-code", "skills")],
19806
- projectRoots: [join22(workspace, ".flavor", "skills"), ...pluginSkillRoots],
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(join22(workspace, "FLAVOR.md"));
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)}-${randomUUID16().slice(0, 8)}`;
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 errorMessage === "string" ? errorMessage : void 0,
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(join22(input.workspace, "FLAVOR.md"));
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)}-${randomUUID16().slice(0, 8)}`
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)}-${randomUUID16().slice(0, 8)}`,
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
- return persistAndCheckpointAfter(runMain(
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 readFile20(auditLogger.path, "utf8");
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 readFile20(usageLogPath(), "utf8");
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)}-${randomUUID16().slice(0, 8)}`;
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(join22(home, ".flavor-code", "auth.json"));
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(join22(home, ".flavor-code", "auth.json"));
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 = createHash13("sha256");
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 = resolve25(workspace, name);
21093
- const relativePath = relative20(resolve25(workspace), path);
21094
- if (relativePath === ".." || relativePath.startsWith(`..${sep19}`)) continue;
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 readFile20(path);
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(join22(home, ".flavor-code", "auth.json"));
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 readFile20(path, "utf8");
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 createHash13("sha256").update(visible, "utf8").digest("hex");
22664
+ return createHash17("sha256").update(visible, "utf8").digest("hex");
21708
22665
  }
21709
22666
  function createMemoryTaskId() {
21710
- return `memory-${randomUUID16()}`;
22667
+ return `memory-${randomUUID18()}`;
21711
22668
  }
21712
22669
 
21713
22670
  export {