flavor-code 1.2.20 → 1.3.2

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