opencode-reviewer 1.0.1 → 1.0.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 (3) hide show
  1. package/README.md +33 -16
  2. package/dist/tui.js +1152 -332
  3. package/package.json +1 -1
package/dist/tui.js CHANGED
@@ -548,7 +548,7 @@ import { memo as _$memo3 } from "@opentui/solid";
548
548
  import { createComponent as _$createComponent4 } from "@opentui/solid";
549
549
  import { setProp as _$setProp5 } from "@opentui/solid";
550
550
  import { createElement as _$createElement5 } from "@opentui/solid";
551
- import { createEffect as createEffect3, createMemo as createMemo4, createSignal as createSignal4, onCleanup as onCleanup5, Show as Show3 } from "solid-js";
551
+ import { createEffect as createEffect4, createMemo as createMemo5, createSignal as createSignal4, onCleanup as onCleanup5, Show as Show3, untrack as untrack2 } from "solid-js";
552
552
  import { CliRenderEvents as CliRenderEvents3 } from "@opentui/core";
553
553
 
554
554
  // src/config.ts
@@ -564,6 +564,14 @@ init_define_REVIEW_PROMPTS();
564
564
 
565
565
  // src/ui-text.ts
566
566
  init_define_REVIEW_PROMPTS();
567
+ function compactTokens(count3) {
568
+ const units = [[1e15, "Q"], [1e12, "T"], [1e9, "B"], [1e6, "M"], [1e3, "k"]];
569
+ for (const [scale, suffix] of units) if (count3 >= scale) {
570
+ const precision = count3 < scale * 100 ? 10 : 1;
571
+ return `${Math.floor(count3 / (scale / precision)) / precision}${suffix}`;
572
+ }
573
+ return String(count3);
574
+ }
567
575
  var uiText = {
568
576
  review: {
569
577
  heading: "Permission analysis",
@@ -620,7 +628,7 @@ var uiText = {
620
628
  lifetime: {
621
629
  title: "Reviewer statistics",
622
630
  close: "Close",
623
- scopeTab: (scope, active) => `${active ? "[" : ""}${scope === "conversation" ? "Conversation" : "Lifetime"}${active ? "]" : ""}`,
631
+ scopeTab: (scope) => scope === "conversation" ? "Conversation" : "Lifetime",
624
632
  switchScope: "Tab: switch view",
625
633
  noConversation: "Select a conversation to see its statistics.",
626
634
  conversationUnavailable: "Conversation statistics unavailable. Recorded totals have not been reset.",
@@ -639,7 +647,7 @@ var uiText = {
639
647
  ratings: (safe, unsafe) => `ratings: ${safe} \u2713 ${unsafe} \u2717`,
640
648
  dialogReviews: (count3) => `Reviews: ${count3}`,
641
649
  dialogRetries: (count3) => `Retries: ${count3}`,
642
- dialogTokens: (input, output, partial) => `Tokens: ${input} in ${output} out${partial ? " (partial coverage)" : ""}`,
650
+ dialogTokens: (input, output, partial) => `Tokens: ${compactTokens(input)} in ${compactTokens(output)} out${partial ? " (partial coverage)" : ""}`,
643
651
  dialogTokensUnavailable: "Tokens: unavailable",
644
652
  dialogCost: (amount2, partial) => amount2 === void 0 ? "Cost: unavailable" : `Cost: $${amount2}${partial ? " (partial pricing)" : ""}`,
645
653
  dialogSafe: (count3, percentage) => `Safe: ${count3} (${percentage === void 0 ? "n/a" : `${percentage}%`})`,
@@ -800,6 +808,37 @@ import path3 from "node:path";
800
808
 
801
809
  // src/deadline.ts
802
810
  init_define_REVIEW_PROMPTS();
811
+
812
+ // src/transport-ownership.ts
813
+ init_define_REVIEW_PROMPTS();
814
+ var TransportOwnership = class {
815
+ constructor(parent) {
816
+ this.parent = parent;
817
+ }
818
+ parent;
819
+ pending = /* @__PURE__ */ new Set();
820
+ track(operation) {
821
+ this.pending.add(operation);
822
+ this.parent?.track(operation);
823
+ const release = () => {
824
+ this.pending.delete(operation);
825
+ };
826
+ void operation.then(release, release);
827
+ return operation;
828
+ }
829
+ async settled() {
830
+ while (this.pending.size) await Promise.allSettled([...this.pending]);
831
+ }
832
+ };
833
+ var scopes = /* @__PURE__ */ new WeakMap();
834
+ function bindTransportOwnership(signal, scope) {
835
+ scopes.set(signal, scope);
836
+ }
837
+ function transportOwnership(signal) {
838
+ return scopes.get(signal);
839
+ }
840
+
841
+ // src/deadline.ts
803
842
  var states = /* @__PURE__ */ new WeakMap();
804
843
  var DeadlineError = class extends Error {
805
844
  constructor(stage) {
@@ -824,23 +863,29 @@ async function withDeadline(parent, ms, run, stage = "Review") {
824
863
  parent.throwIfAborted();
825
864
  const controller = new AbortController();
826
865
  const signal = AbortSignal.any([parent, controller.signal]);
827
- const duration = Math.max(0, Math.min(ms, remainingTime(parent)));
866
+ const now = performance.now();
867
+ const end = Math.min(now + ms, states.get(parent)?.end ?? Infinity);
868
+ const duration = Math.max(0, end - now);
828
869
  if (duration <= 0) throw new DeadlineError(stage);
829
- const state = { end: performance.now() + duration, stage };
870
+ const state = { end, stage };
830
871
  states.set(signal, state);
872
+ const ownership = transportOwnership(parent);
873
+ if (ownership) bindTransportOwnership(signal, ownership);
831
874
  const timer = setTimeout(() => controller.abort(new DeadlineError(state.stage)), Math.ceil(duration));
832
875
  let abort = () => {
833
876
  };
834
877
  try {
878
+ const worker = Promise.resolve().then(async () => {
879
+ signal.throwIfAborted();
880
+ if (state.end <= performance.now()) throw new DeadlineError(state.stage);
881
+ const value = await run(signal);
882
+ signal.throwIfAborted();
883
+ if (state.end <= performance.now()) throw new DeadlineError(state.stage);
884
+ return value;
885
+ });
886
+ ownership?.track(worker);
835
887
  return await Promise.race([
836
- Promise.resolve().then(async () => {
837
- signal.throwIfAborted();
838
- if (state.end <= performance.now()) throw new DeadlineError(state.stage);
839
- const value = await run(signal);
840
- signal.throwIfAborted();
841
- if (state.end <= performance.now()) throw new DeadlineError(state.stage);
842
- return value;
843
- }),
888
+ worker,
844
889
  new Promise((_, reject) => {
845
890
  abort = () => reject(signal.reason);
846
891
  signal.addEventListener("abort", abort, { once: true });
@@ -1068,6 +1113,7 @@ async function loadContext(request, reader2, signal, scope = fileAccess.scope(si
1068
1113
  const canonical = await scope.canonical(cwd);
1069
1114
  execution.canonicalCwd = canonical.path;
1070
1115
  if (!canonical.path) limitations.push(`Execution directory could not be canonicalized (${canonical.reason}); cwd is the declared launch path, not a verified physical path.`);
1116
+ else if (canonical.path !== cwd) limitations.push("Declared launch directory differs from its canonical path. Inherited shell PWD is unavailable; neither path alone establishes the logical working directory after cd.");
1071
1117
  }
1072
1118
  return {
1073
1119
  ...conversation,
@@ -1156,7 +1202,9 @@ async function loadConversationContext(request, reader2, signal, invocation) {
1156
1202
  contextSignal.throwIfAborted();
1157
1203
  prompt2 = latestUserPrompt(messages, root.id);
1158
1204
  };
1159
- await Promise.all([rootPrompt(), delegated()]);
1205
+ const branches = [rootPrompt(), delegated()];
1206
+ for (const branch of branches) transportOwnership(contextSignal)?.track(branch);
1207
+ await Promise.all(branches);
1160
1208
  };
1161
1209
  try {
1162
1210
  await withDeadline(signal, contextMs(), ancestry, "Conversation context lookup");
@@ -1378,6 +1426,7 @@ var HISTORY_RECORD_BYTES = {
1378
1426
  var HistoryInvalid = class extends Error {
1379
1427
  };
1380
1428
  var opaque = (...parts) => createHash("sha256").update(JSON.stringify(parts)).digest("hex");
1429
+ var rootID = (c) => opaque(c.scope, c.root);
1381
1430
  var count = (v) => typeof v === "number" && Number.isSafeInteger(v) && v >= 0;
1382
1431
  var amount = (v) => typeof v === "number" && Number.isFinite(v) && v >= 0;
1383
1432
  function requireValue(ok) {
@@ -1971,7 +2020,7 @@ function measured(trace, phase, run) {
1971
2020
 
1972
2021
  // src/transport-retry.ts
1973
2022
  init_define_REVIEW_PROMPTS();
1974
- import { setTimeout as sleep } from "node:timers/promises";
2023
+ import timers from "node:timers/promises";
1975
2024
  var statuses = /* @__PURE__ */ new Set([408, 429, 500, 502, 503, 504]);
1976
2025
  var networkCodes = /* @__PURE__ */ new Set([
1977
2026
  "EAI_AGAIN",
@@ -2033,7 +2082,7 @@ var TransportRetries = class {
2033
2082
  } catch {
2034
2083
  }
2035
2084
  try {
2036
- await sleep(Math.ceil(delay), void 0, { signal });
2085
+ await timers.setTimeout(Math.ceil(delay), void 0, { signal });
2037
2086
  } catch {
2038
2087
  signal.throwIfAborted();
2039
2088
  throw error;
@@ -2044,7 +2093,7 @@ var TransportRetries = class {
2044
2093
  };
2045
2094
 
2046
2095
  // src/reviewer.ts
2047
- function abortable(promise, signal, late) {
2096
+ function abortable(promise, signal, ownership, late) {
2048
2097
  return new Promise((resolve, reject) => {
2049
2098
  let settled = false;
2050
2099
  const abort = () => {
@@ -2054,11 +2103,8 @@ function abortable(promise, signal, late) {
2054
2103
  reject(signal.reason);
2055
2104
  };
2056
2105
  signal.addEventListener("abort", abort, { once: true });
2057
- promise.then((value) => {
2058
- if (settled) {
2059
- late?.(value);
2060
- return;
2061
- }
2106
+ const physical = promise.then((value) => {
2107
+ if (settled) return late?.(value);
2062
2108
  settled = true;
2063
2109
  signal.removeEventListener("abort", abort);
2064
2110
  resolve(value);
@@ -2068,17 +2114,17 @@ function abortable(promise, signal, late) {
2068
2114
  signal.removeEventListener("abort", abort);
2069
2115
  reject(error);
2070
2116
  });
2117
+ ownership?.track(physical);
2071
2118
  if (signal.aborted) abort();
2072
2119
  });
2073
2120
  }
2074
- function cancelBody(response) {
2121
+ async function cancelBody(response) {
2075
2122
  try {
2076
- void response.body?.cancel().catch(() => {
2077
- });
2123
+ await response.body?.cancel();
2078
2124
  } catch {
2079
2125
  }
2080
2126
  }
2081
- async function readBody(response, signal, consume) {
2127
+ async function readBody(response, signal, ownership, consume) {
2082
2128
  if (!response.body) throw new Error("Reviewer returned an empty HTTP response");
2083
2129
  const reader2 = response.body.getReader();
2084
2130
  try {
@@ -2086,7 +2132,7 @@ async function readBody(response, signal, consume) {
2086
2132
  reviewStage(signal, "Reviewer response body");
2087
2133
  let part;
2088
2134
  try {
2089
- part = await abortable(reader2.read(), signal);
2135
+ part = await abortable(reader2.read(), signal, ownership);
2090
2136
  } catch (error) {
2091
2137
  signal.throwIfAborted();
2092
2138
  throw networkFailure(error, "Reviewer response read failed");
@@ -2097,18 +2143,18 @@ async function readBody(response, signal, consume) {
2097
2143
  }
2098
2144
  } finally {
2099
2145
  try {
2100
- void reader2.cancel().catch(() => {
2101
- });
2146
+ ownership.track(reader2.cancel());
2147
+ } catch {
2102
2148
  } finally {
2103
2149
  reader2.releaseLock();
2104
2150
  }
2105
2151
  }
2106
2152
  }
2107
2153
  var MAX_RESPONSE_BYTES = 65536;
2108
- async function responseText(response, signal) {
2154
+ async function responseText(response, signal, ownership) {
2109
2155
  let bytes = 0;
2110
2156
  const chunks = [];
2111
- await readBody(response, signal, (value) => {
2157
+ await readBody(response, signal, ownership, (value) => {
2112
2158
  bytes += value.length;
2113
2159
  if (bytes > MAX_RESPONSE_BYTES) throw new Error("Reviewer HTTP response exceeds 64 KiB");
2114
2160
  chunks.push(value);
@@ -2122,25 +2168,35 @@ async function responseText(response, signal) {
2122
2168
  var object2 = (value) => !!value && typeof value === "object" && !Array.isArray(value);
2123
2169
  var textMetadata = (message) => (message.role === void 0 || message.role === "assistant") && message.function_call == null && (message.tool_calls == null || Array.isArray(message.tool_calls) && message.tool_calls.length === 0) && (message.refusal == null || message.refusal === "");
2124
2170
  var nonTextFinish = (value) => value === "content_filter" || value === "tool_calls" || value === "function_call";
2125
- async function streamedAssessment(response, signal, accounting, progress, firstContent) {
2171
+ var validMetadata = (delta) => (delta.role === void 0 || delta.role === "assistant") && (delta.function_call == null || object2(delta.function_call)) && (delta.tool_calls == null || Array.isArray(delta.tool_calls) && delta.tool_calls.every(object2)) && (delta.refusal == null || typeof delta.refusal === "string");
2172
+ var TerminalAssessmentRejection = class extends Error {
2173
+ };
2174
+ async function streamedAssessment(response, signal, ownership, accounting, progress, firstContent) {
2126
2175
  if (response.headers.get("content-type")?.split(";", 1)[0]?.trim().toLowerCase() !== "text/event-stream") {
2127
- cancelBody(response);
2176
+ ownership.track(cancelBody(response));
2128
2177
  throw new Error("Reviewer expected an SSE response");
2129
2178
  }
2130
2179
  const assessment = new StreamingAssessment();
2131
2180
  let stopped = false, done = false;
2181
+ let finished;
2132
2182
  let id, model;
2133
2183
  let last;
2134
2184
  let failure;
2135
2185
  function invalid(retryable = true) {
2136
2186
  const message = "Reviewer API did not return a text assessment stream";
2137
- throw retryable ? new AssessmentStreamError(message) : new Error(message);
2187
+ throw retryable && !(failure instanceof TerminalAssessmentRejection) ? new AssessmentStreamError(message) : new Error(message);
2188
+ }
2189
+ function rejected() {
2190
+ if (failure instanceof TerminalAssessmentRejection) return;
2191
+ failure = new TerminalAssessmentRejection("Reviewer API did not return a text assessment stream");
2192
+ progress();
2138
2193
  }
2139
2194
  const sse = new SSEParser(({ data, event }) => {
2140
2195
  try {
2196
+ reviewStage(signal, "Reviewer response body");
2141
2197
  if (data.trim() === "[DONE]") {
2142
- if (failure) return;
2143
- if (done || !stopped || event !== "message") invalid();
2198
+ if (failure instanceof AssessmentStreamError) return;
2199
+ if (done || !stopped && !failure || event !== "message") invalid();
2144
2200
  done = true;
2145
2201
  return;
2146
2202
  }
@@ -2153,9 +2209,10 @@ async function streamedAssessment(response, signal, accounting, progress, firstC
2153
2209
  accounting.observe(object2(body) && body.model === void 0 && model !== void 0 ? { ...body, model } : body);
2154
2210
  reviewStage(signal, "Reviewer response body");
2155
2211
  if (event === "error" || object2(body) && body.error != null) invalid(false);
2156
- if (failure) {
2157
- if (object2(body) && Array.isArray(body.choices) && body.choices.some((choice2) => object2(choice2) && (choice2.error != null || nonTextFinish(choice2.finish_reason) || object2(choice2.delta) && !textMetadata(choice2.delta) || object2(choice2.message) && !textMetadata(choice2.message)))) invalid(false);
2158
- return;
2212
+ if (failure instanceof AssessmentStreamError) {
2213
+ const terminal = object2(body) && Array.isArray(body.choices) && body.choices.some((choice2) => object2(choice2) && (choice2.error != null || nonTextFinish(choice2.finish_reason) || object2(choice2.delta) && !textMetadata(choice2.delta) || object2(choice2.message) && !textMetadata(choice2.message)));
2214
+ if (!terminal) return;
2215
+ rejected();
2159
2216
  }
2160
2217
  if (done || event !== "message" || !object2(body)) invalid();
2161
2218
  if (body.object !== void 0 && body.object !== "chat.completion.chunk") invalid();
@@ -2177,14 +2234,23 @@ async function streamedAssessment(response, signal, accounting, progress, firstC
2177
2234
  if (!object2(choice) || choice.index !== 0 || choice.message != null || choice.text != null) invalid();
2178
2235
  if (choice.error != null) invalid(false);
2179
2236
  const finish = choice.finish_reason;
2180
- if (nonTextFinish(finish)) invalid(false);
2181
- const delta = choice.delta === void 0 && (finish === "stop" || finish === "length") ? {} : choice.delta;
2182
- if (!object2(delta)) invalid();
2183
- if (!textMetadata(delta)) invalid(false);
2237
+ const delta = choice.delta === void 0 && (finish === "stop" || finish === "length" || nonTextFinish(finish)) ? {} : choice.delta;
2238
+ if (!object2(delta)) invalid(!nonTextFinish(finish));
2239
+ if (!validMetadata(delta)) invalid(false);
2240
+ const nonText = nonTextFinish(finish) || !textMetadata(delta);
2241
+ if (delta.content != null && typeof delta.content !== "string") invalid(!nonText);
2242
+ if (finish != null && finish !== "stop" && finish !== "length" && !nonTextFinish(finish)) invalid(!nonText);
2243
+ if (finished !== void 0 && (finish !== finished || !textMetadata(delta) || [delta.content, delta.reasoning, delta.reasoning_content, delta.reasoning_details].some((value) => value != null && value !== "" && !(Array.isArray(value) && value.length === 0)))) invalid(!nonText);
2244
+ if (typeof finish === "string") finished = finish;
2245
+ if (nonText) {
2246
+ rejected();
2247
+ return;
2248
+ }
2249
+ if (failure instanceof TerminalAssessmentRejection) {
2250
+ return;
2251
+ }
2184
2252
  if (finish === "length") throw new AssessmentStreamError("Reviewer output token limit reached; increase maxOutputTokens");
2185
2253
  if (finish != null && finish !== "stop" || stopped && finish !== "stop") invalid();
2186
- if (delta.content != null && typeof delta.content !== "string") invalid();
2187
- if (stopped && [delta.content, delta.reasoning, delta.reasoning_content, delta.reasoning_details].some((value) => value != null && value !== "" && !(Array.isArray(value) && value.length === 0))) invalid();
2188
2254
  if (typeof delta.content === "string" && delta.content) {
2189
2255
  firstContent?.();
2190
2256
  assessment.push(delta.content);
@@ -2203,7 +2269,13 @@ async function streamedAssessment(response, signal, accounting, progress, firstC
2203
2269
  }
2204
2270
  }
2205
2271
  });
2206
- await readBody(response, signal, (bytes) => sse.push(bytes));
2272
+ try {
2273
+ await readBody(response, signal, ownership, (bytes) => sse.push(bytes));
2274
+ } catch (error) {
2275
+ signal.throwIfAborted();
2276
+ if (failure instanceof TerminalAssessmentRejection) throw new Error(error instanceof Error ? error.message : failure.message);
2277
+ throw error;
2278
+ }
2207
2279
  sse.finish();
2208
2280
  if (failure) throw failure;
2209
2281
  if (!done || !stopped) throw new AssessmentStreamError("Reviewer stream ended without stop and DONE");
@@ -2266,6 +2338,7 @@ async function review(evidence, config, signal, {
2266
2338
  const attemptID = randomUUID();
2267
2339
  let dispatched = false;
2268
2340
  const requestAbort = new AbortController();
2341
+ const ownership = new TransportOwnership(transportOwnership(signal));
2269
2342
  let contentStarted = false;
2270
2343
  let failed = false;
2271
2344
  let failure;
@@ -2304,18 +2377,18 @@ async function review(evidence, config, signal, {
2304
2377
  }
2305
2378
  }
2306
2379
  }
2307
- response = await abortable(pending, signal, cancelBody);
2380
+ response = await abortable(pending, signal, ownership, cancelBody);
2308
2381
  diagnose?.("headers");
2309
2382
  } catch (error) {
2310
2383
  signal.throwIfAborted();
2311
2384
  throw networkFailure(error, "Reviewer network request failed");
2312
2385
  }
2313
2386
  if (!response.ok) {
2314
- cancelBody(response);
2387
+ ownership.track(cancelBody(response));
2315
2388
  throw httpFailure(response);
2316
2389
  }
2317
2390
  let assessment;
2318
- if (config.stream) assessment = await streamedAssessment(response, signal, accounting, (preview) => {
2391
+ if (config.stream) assessment = await streamedAssessment(response, signal, ownership, accounting, (preview) => {
2319
2392
  if (typeof preview?.safe === "boolean") diagnose?.("first-rating");
2320
2393
  progress("streaming", preview);
2321
2394
  }, () => {
@@ -2325,7 +2398,7 @@ async function review(evidence, config, signal, {
2325
2398
  else {
2326
2399
  let envelope;
2327
2400
  try {
2328
- envelope = JSON.parse(await responseText(response, signal));
2401
+ envelope = JSON.parse(await responseText(response, signal, ownership));
2329
2402
  } catch (error) {
2330
2403
  signal.throwIfAborted();
2331
2404
  if (error instanceof SyntaxError) throw new Error("Reviewer returned invalid API JSON");
@@ -2394,6 +2467,7 @@ async function review(evidence, config, signal, {
2394
2467
  startedAttempt = attempt + 1;
2395
2468
  progress("retrying", void 0, startedAttempt);
2396
2469
  })) throw failure;
2470
+ await abortable(ownership.settled(), signal);
2397
2471
  retry = failed ? "transport" : "format";
2398
2472
  }
2399
2473
  }
@@ -2452,8 +2526,8 @@ function classify(request, invocation, ids, servers) {
2452
2526
  init_define_REVIEW_PROMPTS();
2453
2527
  function reviewSyntaxStyles(theme) {
2454
2528
  const styles = { default: { fg: theme.text } };
2455
- const add = (scopes, style) => {
2456
- for (const scope of scopes) styles[scope] = style;
2529
+ const add = (scopes2, style) => {
2530
+ for (const scope of scopes2) styles[scope] = style;
2457
2531
  };
2458
2532
  add(["markup.heading", ...Array.from({ length: 6 }, (_, i) => `markup.heading.${i + 1}`)], { fg: theme.markdownHeading, bold: true });
2459
2533
  styles["markup.heading.1"] = { fg: theme.markdownHeading, bold: true, underline: true };
@@ -2518,6 +2592,176 @@ function scannerFrame(frame) {
2518
2592
  });
2519
2593
  }
2520
2594
 
2595
+ // src/route-approval.ts
2596
+ init_define_REVIEW_PROMPTS();
2597
+ var RouteSessions = class {
2598
+ constructor(current) {
2599
+ this.current = current;
2600
+ }
2601
+ current;
2602
+ events = /* @__PURE__ */ new Map();
2603
+ get(id) {
2604
+ const event = this.events.get(id), current = this.current(id);
2605
+ if (event === null || current && current.id !== id) return;
2606
+ if (event && current && event.parentID !== current.parentID) return;
2607
+ return current ?? event;
2608
+ }
2609
+ remember(value) {
2610
+ const old = this.events.get(value.id);
2611
+ if (old === null || old && old.parentID === value.parentID) return false;
2612
+ this.set(value.id, { id: value.id, ...value.parentID === void 0 ? {} : { parentID: value.parentID } });
2613
+ return true;
2614
+ }
2615
+ deleted(id) {
2616
+ this.set(id, null);
2617
+ }
2618
+ set(id, value) {
2619
+ this.events.delete(id);
2620
+ this.events.set(id, value);
2621
+ if (this.events.size > 4096) this.events.delete(this.events.keys().next().value);
2622
+ }
2623
+ };
2624
+ function conversationRoot(id, session) {
2625
+ const visited = /* @__PURE__ */ new Set();
2626
+ for (let depth = 0; id && depth <= 16; depth++) {
2627
+ if (visited.has(id)) return;
2628
+ visited.add(id);
2629
+ const value = session(id);
2630
+ if (value?.id !== id) return;
2631
+ if (value.parentID === void 0) return id;
2632
+ if (typeof value.parentID !== "string" || !value.parentID) return;
2633
+ id = value.parentID;
2634
+ }
2635
+ }
2636
+ var RouteHandoff = class {
2637
+ session;
2638
+ root;
2639
+ returning;
2640
+ observe(session, root) {
2641
+ if (this.session === session && this.root === root) return;
2642
+ this.returning = root && session === root && this.root === root && this.session !== root ? root : void 0;
2643
+ this.session = session;
2644
+ this.root = root;
2645
+ }
2646
+ pending(root) {
2647
+ return this.returning === root;
2648
+ }
2649
+ frame() {
2650
+ this.returning = void 0;
2651
+ }
2652
+ };
2653
+ function routeCovered(root, width, height) {
2654
+ let remaining = 8192;
2655
+ const visit2 = (node, parent) => {
2656
+ if (--remaining < 0) return true;
2657
+ if (!node.visible || node.isDestroyed) return false;
2658
+ if (parent && node.x <= 2 && node.y <= 1 && node.x + node.width >= width - 2 && node.y + node.height >= height - 2 && (node.y < parent.y || node.y + node.height > parent.y + parent.height)) return true;
2659
+ return node.getChildren().some((child) => visit2(child, node));
2660
+ };
2661
+ return width < 5 || height < 4 || visit2(root);
2662
+ }
2663
+
2664
+ // src/review-admission.ts
2665
+ init_define_REVIEW_PROMPTS();
2666
+ var MAX_REVIEW_EVALUATORS = 4;
2667
+ function ownedReview(parent, ms, run) {
2668
+ let worker, finish;
2669
+ const settled = new Promise((resolve) => {
2670
+ finish = resolve;
2671
+ });
2672
+ const result = withDeadline(parent, ms, (signal) => {
2673
+ const ownership = new TransportOwnership(transportOwnership(parent));
2674
+ bindTransportOwnership(signal, ownership);
2675
+ worker = Promise.resolve().then(() => {
2676
+ signal.throwIfAborted();
2677
+ return run(signal);
2678
+ });
2679
+ const finalized = () => {
2680
+ void ownership.settled().then(finish, finish);
2681
+ };
2682
+ void worker.then(finalized, finalized);
2683
+ return worker;
2684
+ });
2685
+ const undispatched = () => {
2686
+ if (!worker) finish();
2687
+ };
2688
+ void result.then(undispatched, undispatched);
2689
+ return Object.assign(result, { settled });
2690
+ }
2691
+ var ReviewAdmission = class {
2692
+ constructor(front = () => void 0) {
2693
+ this.front = front;
2694
+ }
2695
+ front;
2696
+ roots = /* @__PURE__ */ new Map();
2697
+ running = 0;
2698
+ scheduled = false;
2699
+ workers = /* @__PURE__ */ new Set();
2700
+ run(request, root, signal, evaluate) {
2701
+ return new Promise((resolve, reject) => {
2702
+ if (signal.aborted) {
2703
+ reject(signal.reason);
2704
+ return;
2705
+ }
2706
+ const cancel = () => {
2707
+ const queue2 = this.roots.get(root);
2708
+ const index = queue2?.indexOf(waiting) ?? -1;
2709
+ if (index < 0) return;
2710
+ queue2.splice(index, 1);
2711
+ if (!queue2.length) this.roots.delete(root);
2712
+ signal.removeEventListener("abort", cancel);
2713
+ reject(signal.reason);
2714
+ };
2715
+ const waiting = { request, start: () => {
2716
+ signal.removeEventListener("abort", cancel);
2717
+ this.running++;
2718
+ const worker = Promise.resolve().then(() => {
2719
+ signal.throwIfAborted();
2720
+ const evaluation = evaluate();
2721
+ void evaluation.then(resolve, reject);
2722
+ return Promise.allSettled([evaluation, evaluation.settled]);
2723
+ });
2724
+ this.workers.add(worker);
2725
+ const release = () => {
2726
+ this.workers.delete(worker);
2727
+ this.running--;
2728
+ this.schedule();
2729
+ };
2730
+ void worker.then(release, (error) => {
2731
+ release();
2732
+ reject(error);
2733
+ });
2734
+ } };
2735
+ const queue = this.roots.get(root);
2736
+ if (queue) queue.push(waiting);
2737
+ else this.roots.set(root, [waiting]);
2738
+ signal.addEventListener("abort", cancel, { once: true });
2739
+ this.schedule();
2740
+ });
2741
+ }
2742
+ /** Caller must first abort waiting work. Physical finalizers still own slots. */
2743
+ async settled() {
2744
+ await Promise.allSettled([...this.workers]);
2745
+ }
2746
+ schedule() {
2747
+ if (this.scheduled || this.running >= MAX_REVIEW_EVALUATORS || !this.roots.size) return;
2748
+ this.scheduled = true;
2749
+ queueMicrotask(() => {
2750
+ this.scheduled = false;
2751
+ while (this.running < MAX_REVIEW_EVALUATORS && this.roots.size) {
2752
+ const [root, queue] = this.roots.entries().next().value;
2753
+ const front = this.front(root);
2754
+ let first = queue.findIndex((waiting2) => waiting2.request.id === front);
2755
+ if (first < 0) first = 0;
2756
+ const [waiting] = queue.splice(first, 1);
2757
+ this.roots.delete(root);
2758
+ if (queue.length) this.roots.set(root, queue);
2759
+ waiting.start();
2760
+ }
2761
+ });
2762
+ }
2763
+ };
2764
+
2521
2765
  // src/controller.ts
2522
2766
  var clock = {
2523
2767
  now: () => performance.now(),
@@ -2527,6 +2771,7 @@ var clock = {
2527
2771
  }
2528
2772
  };
2529
2773
  var permissionResolved = Object.freeze({ reason: "permission resolved" });
2774
+ var requestChanged = Object.freeze({ reason: "pending request changed" });
2530
2775
  var Controller = class {
2531
2776
  constructor(evaluate, changed, {
2532
2777
  reviewOptions = { reviewBash: true, reviewEdits: true },
@@ -2535,7 +2780,8 @@ var Controller = class {
2535
2780
  modes,
2536
2781
  onApproval,
2537
2782
  onRating,
2538
- onLifecycle
2783
+ onLifecycle,
2784
+ session
2539
2785
  } = {}) {
2540
2786
  this.evaluate = evaluate;
2541
2787
  this.changed = changed;
@@ -2543,6 +2789,7 @@ var Controller = class {
2543
2789
  this.approval = approval;
2544
2790
  this.time = time;
2545
2791
  this.modes = modes;
2792
+ this.session = session;
2546
2793
  this.approvalObserver = onApproval;
2547
2794
  this.ratingObserver = onRating;
2548
2795
  this.lifecycleObserver = onLifecycle;
@@ -2552,10 +2799,11 @@ var Controller = class {
2552
2799
  entries = /* @__PURE__ */ new Map();
2553
2800
  stopped = false;
2554
2801
  version = 0;
2555
- visibleID;
2802
+ selectedID;
2556
2803
  modeChanges = /* @__PURE__ */ new Map();
2557
2804
  manual = /* @__PURE__ */ new Map();
2558
2805
  workers = /* @__PURE__ */ new Set();
2806
+ admission = new ReviewAdmission((root) => this.nativeFront(root));
2559
2807
  acknowledgements = /* @__PURE__ */ new Map();
2560
2808
  approvalWorkers = /* @__PURE__ */ new Set();
2561
2809
  approvalRead;
@@ -2565,6 +2813,7 @@ var Controller = class {
2565
2813
  approval;
2566
2814
  time;
2567
2815
  modes;
2816
+ session;
2568
2817
  approvalObserver;
2569
2818
  ratingObserver;
2570
2819
  lifecycleObserver;
@@ -2605,6 +2854,15 @@ var Controller = class {
2605
2854
  } catch {
2606
2855
  }
2607
2856
  }
2857
+ nativeFront(root) {
2858
+ let first;
2859
+ for (const entry of this.entries.values()) {
2860
+ const request = entry.view.request;
2861
+ if (entry.view.resolved || request.sessionID !== root && (this.session ? this.session(request.sessionID)?.parentID !== root : entry.root !== root)) continue;
2862
+ if (!first || request.sessionID < first.sessionID || request.sessionID === first.sessionID && request.id < first.id) first = request;
2863
+ }
2864
+ return first?.id;
2865
+ }
2608
2866
  /** Local state is already switched; invalidate snapshots before any publication. */
2609
2867
  modeChanged(root) {
2610
2868
  if (this.stopped) return;
@@ -2616,10 +2874,10 @@ var Controller = class {
2616
2874
  if (entry.fastApproval) return;
2617
2875
  const id = entry.view.request.id;
2618
2876
  const state = entry.view.autoApproval?.status;
2619
- if (state === "countdown" || state === "checking" || state === "allowing" && !entry.approvalDispatched)
2877
+ if (entry.approvalArmed && !this.manual.has(id) && !entry.approvalDispatched)
2620
2878
  this.lifecycleFact({ type: "cancelled", request: entry.view.request, reason: "mode" });
2621
2879
  const uncertain = state === "failed" || state === "allowing" && entry.approvalDispatched;
2622
- if (state) this.manual.set(id, uncertain ? "failed" : "cancelled");
2880
+ if (state || entry.approvalArmed) this.manual.set(id, uncertain ? "failed" : "cancelled");
2623
2881
  entry.cancelTimer?.();
2624
2882
  this.clearProgress(entry);
2625
2883
  entry.approvalAbort?.abort();
@@ -2637,16 +2895,28 @@ var Controller = class {
2637
2895
  } : {}
2638
2896
  };
2639
2897
  }
2640
- /** Normal mode requires final rendering; fast mode requires physical panel visibility. */
2641
- presented(id) {
2898
+ /** Physical presentation and intentional same-root suppression are distinct proofs. */
2899
+ presented(value) {
2642
2900
  if (this.stopped) return;
2643
- if (id !== this.visibleID) {
2644
- const old = this.visibleID;
2645
- this.visibleID = id;
2901
+ const proof = typeof value === "string" ? { state: "visible", id: value } : value ?? { state: "lost" };
2902
+ const id = proof.state === "lost" ? void 0 : proof.id;
2903
+ if (id !== this.selectedID) {
2904
+ const old = this.selectedID;
2905
+ this.selectedID = id;
2646
2906
  if (old) this.cancelAutoApproval(old, "visibility");
2647
2907
  }
2648
2908
  const entry = id ? this.entries.get(id) : void 0;
2649
- if (!entry || entry.view.autoApproval || !this.eligible(entry)) return;
2909
+ if (entry?.approvalArmed && this.presentationState(entry) === "lost") this.cancelAutoApproval(entry.view.request.id, "visibility");
2910
+ if (entry && (proof.state === "visible" || proof.state === "route-hidden")) {
2911
+ this.startApproval(entry);
2912
+ if (entry.view.autoApproval?.status === "countdown" && !entry.cancelTimer && !entry.approvalAbort) this.schedule(entry);
2913
+ }
2914
+ }
2915
+ /** Rating/settlement callbacks are not painted-frame observations. They may
2916
+ * start eligible work, but cannot prove a hide from an unpainted publication. */
2917
+ startApproval(entry) {
2918
+ if (entry.view.autoApproval || entry.approvalAbort || entry.approvalRefresh || !this.eligible(entry)) return;
2919
+ entry.approvalArmed = true;
2650
2920
  if (this.options.fastMode) {
2651
2921
  entry.view = { ...entry.view, autoApproval: { status: "checking" } };
2652
2922
  this.publish();
@@ -2661,7 +2931,7 @@ var Controller = class {
2661
2931
  }
2662
2932
  cancelAutoApproval(id, reason = "explicit") {
2663
2933
  const entry = this.entries.get(id);
2664
- if (!entry || entry.approvalDispatched || !["countdown", "checking", "allowing"].includes(entry.view.autoApproval?.status ?? "")) return;
2934
+ if (!entry || !entry.approvalArmed || entry.approvalDispatched || this.manual.has(id)) return;
2665
2935
  this.lifecycleFact({ type: "cancelled", request: entry.view.request, reason });
2666
2936
  entry.cancelTimer?.();
2667
2937
  entry.cancelTimer = void 0;
@@ -2690,6 +2960,7 @@ var Controller = class {
2690
2960
  entry.attempt = value.attempt;
2691
2961
  entry.ratingAt = void 0;
2692
2962
  entry.view = { ...entry.view, progress: { attempt: value.attempt, phase: value.phase } };
2963
+ this.pauseRating(entry);
2693
2964
  this.publish();
2694
2965
  return;
2695
2966
  }
@@ -2697,6 +2968,7 @@ var Controller = class {
2697
2968
  if (!value.preview) {
2698
2969
  this.clearProgress(entry);
2699
2970
  entry.view = { ...entry.view, progress: { attempt: value.attempt, phase: value.phase } };
2971
+ this.pauseRating(entry);
2700
2972
  this.publish();
2701
2973
  return;
2702
2974
  }
@@ -2713,8 +2985,9 @@ var Controller = class {
2713
2985
  phase: "streaming",
2714
2986
  preview: { ...preview.safe === void 0 ? {} : { safe: preview.safe }, ...desc === void 0 ? {} : { desc } }
2715
2987
  } };
2988
+ if (preview.safe !== true) this.pauseRating(entry);
2716
2989
  this.publish();
2717
- if (this.options.fastMode && preview.safe === true) this.presented(this.visibleID);
2990
+ if (this.options.fastMode && preview.safe === true) this.startApproval(entry);
2718
2991
  }
2719
2992
  if (!this.reviewing(entry) || entry.cancelProgress || !entry.pendingProgress) return;
2720
2993
  const generation = entry.progressGeneration;
@@ -2730,33 +3003,106 @@ var Controller = class {
2730
3003
  }
2731
3004
  });
2732
3005
  }
3006
+ safe(entry) {
3007
+ return entry.view.status === "complete" ? entry.view.assessment?.safe === true : this.options.fastMode === true && entry.view.status === "analyzing" && entry.view.progress?.preview?.safe === true;
3008
+ }
3009
+ presentationState(entry, check = "eligibility") {
3010
+ if (!this.active(entry) || entry.view.resolved || entry.fastApproval || this.options.autoApprove !== true || this.modes && (entry.root === void 0 || !this.modes.enabled(entry.root)) || this.selectedID !== entry.view.request.id) return "lost";
3011
+ const proof = this.approval?.presentation?.(check);
3012
+ if (proof?.state === "route-hidden") return entry.root === proof.root && proof.id === entry.view.request.id && this.nativeFront(proof.root) === proof.id ? "route-hidden" : "lost";
3013
+ if (proof) return proof.state !== "lost" && proof.id === entry.view.request.id ? proof.state : "lost";
3014
+ return this.approval?.visibleID() === entry.view.request.id ? "visible" : "lost";
3015
+ }
2733
3016
  eligible(entry) {
2734
- const safe = entry.view.status === "complete" ? entry.view.assessment?.safe === true : this.options.fastMode === true && entry.view.status === "analyzing" && entry.view.progress?.preview?.safe === true;
2735
- return this.active(entry) && !entry.view.resolved && !entry.fastApproval && this.options.autoApprove === true && safe && (!this.modes || entry.root !== void 0 && this.modes.enabled(entry.root)) && this.visibleID === entry.view.request.id && this.approval?.visibleID() === entry.view.request.id;
3017
+ if (this.manual.has(entry.view.request.id) || entry.approvalPaused || !this.safe(entry)) return false;
3018
+ const state = this.presentationState(entry);
3019
+ return state === "visible" || state === "route-hidden";
3020
+ }
3021
+ /** Rating rejection revokes the reservation, even if Safe returns before its GET.
3022
+ * Keep the armed lifetime so real presentation loss/Cancel remains durable. */
3023
+ pauseRating(entry) {
3024
+ if (!entry.approvalArmed || entry.approvalDispatched || this.manual.has(entry.view.request.id)) return;
3025
+ if (entry.view.status !== "analyzing") {
3026
+ entry.approvalRefresh = false;
3027
+ entry.approvalPaused = !!entry.approvalAbort;
3028
+ entry.approvalAbort?.abort();
3029
+ entry.view = { ...entry.view, autoApproval: void 0 };
3030
+ return;
3031
+ }
3032
+ if (entry.approvalPaused || !entry.view.autoApproval) return;
3033
+ entry.approvalPaused = !!entry.approvalAbort;
3034
+ entry.approvalAbort?.abort();
3035
+ entry.view = { ...entry.view, autoApproval: entry.approvalRefresh ? { status: "checking" } : void 0 };
3036
+ }
3037
+ approvalEligible(entry, check = "verification") {
3038
+ const state = this.presentationState(entry, check);
3039
+ if (state === "lost") this.cancelAutoApproval(entry.view.request.id, "visibility");
3040
+ else if (state === "pending") entry.approvalPaintPending = true;
3041
+ else if (!this.safe(entry)) this.pauseRating(entry);
3042
+ return this.eligible(entry);
3043
+ }
3044
+ /** Do not await noncooperative I/O in the caller, or release its reservation on
3045
+ * timeout. The callback runs only after the physical slot has actually settled. */
3046
+ afterApprovalRead(entry, abort, run) {
3047
+ const settled = async () => {
3048
+ if (entry.approvalAbort !== abort || !this.active(entry) || this.manual.has(entry.view.request.id)) return;
3049
+ await run();
3050
+ };
3051
+ if (this.approvalRead) {
3052
+ void this.approvalRead.then(settled, settled).catch(() => {
3053
+ });
3054
+ return Promise.resolve();
3055
+ }
3056
+ return settled();
3057
+ }
3058
+ async recoverApproval(entry, abort) {
3059
+ await this.afterApprovalRead(entry, abort, async () => {
3060
+ const revision = ++this.version;
3061
+ try {
3062
+ abort.signal.throwIfAborted();
3063
+ if (this.presentationState(entry) === "lost") {
3064
+ this.cancelAutoApproval(entry.view.request.id, "visibility");
3065
+ return;
3066
+ }
3067
+ const pending = await withDeadline(
3068
+ AbortSignal.any([entry.abort.signal, abort.signal]),
3069
+ 5e3,
3070
+ (signal) => this.approvalList(signal)
3071
+ );
3072
+ if (!this.active(entry) || this.manual.has(entry.view.request.id)) return;
3073
+ entry.approvalAbort = void 0;
3074
+ entry.approvalPaused = false;
3075
+ this.reconcile(pending, revision);
3076
+ } catch {
3077
+ await this.afterApprovalRead(entry, abort, () => {
3078
+ ++this.version;
3079
+ entry.approvalAbort = void 0;
3080
+ entry.approvalPaused = false;
3081
+ });
3082
+ }
3083
+ });
2736
3084
  }
2737
3085
  schedule(entry) {
2738
- if (!this.active(entry) || entry.view.autoApproval?.status !== "countdown") return;
3086
+ if (!this.active(entry) || entry.view.autoApproval?.status !== "countdown" || entry.cancelTimer) return;
2739
3087
  const remaining = Math.max(0, entry.deadline - this.time.now());
2740
3088
  const next = remaining > 0 ? remaining - (Math.ceil(remaining / 1e3) - 1) * 1e3 : 0;
2741
3089
  entry.cancelTimer = this.time.after(next, () => {
2742
3090
  entry.cancelTimer = void 0;
2743
3091
  if (!this.active(entry) || entry.view.autoApproval?.status !== "countdown") return;
2744
- if (!this.eligible(entry)) {
2745
- this.cancelAutoApproval(entry.view.request.id, "visibility");
2746
- return;
2747
- }
3092
+ const visible = this.approvalEligible(entry, "timer");
3093
+ if (this.manual.has(entry.view.request.id)) return;
2748
3094
  const seconds = Math.min(
2749
3095
  this.options.autoApproveDelaySeconds ?? 15,
2750
3096
  Math.max(0, Math.ceil((entry.deadline - this.time.now()) / 1e3))
2751
3097
  );
2752
- if (seconds === 0) {
2753
- void this.approveNow(entry.view.request.id, true);
2754
- return;
2755
- }
2756
3098
  if (entry.view.autoApproval.seconds !== seconds) {
2757
3099
  entry.view = { ...entry.view, autoApproval: { status: "countdown", seconds } };
2758
3100
  this.publish();
2759
3101
  }
3102
+ if (seconds === 0) {
3103
+ if (visible) void this.approveNow(entry.view.request.id, true);
3104
+ return;
3105
+ }
2760
3106
  this.schedule(entry);
2761
3107
  });
2762
3108
  }
@@ -2779,13 +3125,11 @@ var Controller = class {
2779
3125
  async approveNow(id, automatic = false) {
2780
3126
  const entry = this.entries.get(id);
2781
3127
  if (!entry || !this.approval || entry.approvalRefresh || !(entry.view.autoApproval?.status === "countdown" || this.options.fastMode && automatic && entry.view.autoApproval?.status === "checking" && !entry.approvalAbort)) return;
2782
- if (!this.eligible(entry)) {
2783
- this.cancelAutoApproval(id, "visibility");
2784
- return;
2785
- }
3128
+ if (!this.approvalEligible(entry)) return;
2786
3129
  entry.cancelTimer?.();
2787
3130
  entry.cancelTimer = void 0;
2788
- entry.approvalAbort = new AbortController();
3131
+ const abort = entry.approvalAbort = new AbortController();
3132
+ entry.approvalPaintPending = false;
2789
3133
  entry.view = { ...entry.view, autoApproval: { status: "checking" } };
2790
3134
  this.publish();
2791
3135
  let dispatched = false;
@@ -2800,52 +3144,40 @@ var Controller = class {
2800
3144
  ...type === "settled" && !confirmed ? { result: dispatched ? "uncertain" : "not-sent" } : {}
2801
3145
  });
2802
3146
  try {
2803
- await withDeadline(AbortSignal.any([entry.abort.signal, entry.approvalAbort.signal]), 5e3, async (signal) => {
3147
+ await withDeadline(AbortSignal.any([entry.abort.signal, abort.signal]), 5e3, async (signal) => {
2804
3148
  for (let reads = 0; reads < 3; reads++) {
2805
3149
  reviewStage(signal, "Approval verification");
2806
- if (!this.eligible(entry)) {
2807
- this.cancelAutoApproval(id, "visibility");
2808
- return;
2809
- }
3150
+ if (!this.approvalEligible(entry)) return;
2810
3151
  const revision = this.version;
2811
3152
  const pending = await this.approvalList(signal);
2812
3153
  reviewStage(signal, "Approval verification");
2813
- if (!this.eligible(entry)) {
2814
- this.cancelAutoApproval(id, "visibility");
2815
- return;
2816
- }
3154
+ if (!this.approvalEligible(entry)) return;
2817
3155
  if (revision !== this.version) continue;
2818
3156
  const current = pending.find((request) => request.id === id);
2819
3157
  if (!current) {
2820
3158
  this.replied(id, true, "reconciled");
2821
3159
  return;
2822
3160
  }
2823
- if (!isDeepStrictEqual(current, entry.view.request)) throw new Error("Pending request changed");
3161
+ if (!isDeepStrictEqual(current, entry.view.request)) throw requestChanged;
2824
3162
  const verified = structuredClone(current);
2825
3163
  this.reconcile(pending, revision);
2826
3164
  const verifiedRevision = this.version;
2827
- if (!this.eligible(entry)) {
2828
- this.cancelAutoApproval(id, "visibility");
2829
- return;
2830
- }
3165
+ if (!this.approvalEligible(entry)) return;
2831
3166
  entry.view = { ...entry.view, autoApproval: { status: "allowing" } };
2832
3167
  this.publish();
2833
3168
  const remaining = remainingTime(signal);
2834
3169
  const acknowledgement = new AbortController();
2835
- const abort = () => {
3170
+ const abort2 = () => {
2836
3171
  if (!dispatched || signal.reason !== permissionResolved) acknowledgement.abort(signal.reason);
2837
3172
  };
2838
- signal.addEventListener("abort", abort, { once: true });
2839
- if (signal.aborted) abort();
3173
+ signal.addEventListener("abort", abort2, { once: true });
3174
+ if (signal.aborted) abort2();
2840
3175
  const write = withDeadline(acknowledgement.signal, remaining, async (bounded) => {
2841
3176
  reviewStage(signal, "Approval verification");
2842
3177
  reviewStage(bounded, "Approval acknowledgement");
2843
- if (!this.eligible(entry)) {
2844
- this.cancelAutoApproval(id, "visibility");
2845
- return;
2846
- }
3178
+ if (!this.approvalEligible(entry)) return;
2847
3179
  if (verifiedRevision !== this.version) return;
2848
- if (!isDeepStrictEqual(verified, entry.view.request)) throw new Error("Pending request changed");
3180
+ if (!isDeepStrictEqual(verified, entry.view.request)) throw requestChanged;
2849
3181
  reviewStage(signal, "Approval verification");
2850
3182
  reviewStage(bounded, "Approval acknowledgement");
2851
3183
  this.manual.set(id, "failed");
@@ -2881,7 +3213,7 @@ var Controller = class {
2881
3213
  }
2882
3214
  }).finally(() => {
2883
3215
  this.acknowledgements.delete(acknowledgement);
2884
- signal.removeEventListener("abort", abort);
3216
+ signal.removeEventListener("abort", abort2);
2885
3217
  });
2886
3218
  this.approvalWorkers.add(write);
2887
3219
  void write.then(() => this.approvalWorkers.delete(write), () => this.approvalWorkers.delete(write));
@@ -2891,10 +3223,7 @@ var Controller = class {
2891
3223
  this.replied(id);
2892
3224
  return;
2893
3225
  }
2894
- if (!this.eligible(entry)) {
2895
- this.cancelAutoApproval(id, "visibility");
2896
- return;
2897
- }
3226
+ if (!this.approvalEligible(entry)) return;
2898
3227
  entry.view = { ...entry.view, autoApproval: { status: "checking" } };
2899
3228
  this.publish();
2900
3229
  }
@@ -2904,8 +3233,16 @@ var Controller = class {
2904
3233
  this.publish();
2905
3234
  }
2906
3235
  });
2907
- } catch {
3236
+ } catch (error) {
2908
3237
  if (!this.active(entry) || entry.view.autoApproval?.status === "cancelled") return;
3238
+ if (!dispatched && error !== requestChanged && !this.manual.has(id)) {
3239
+ if (entry.approvalPaused) return;
3240
+ entry.approvalRefresh = true;
3241
+ entry.view = { ...entry.view, autoApproval: { status: "checking" } };
3242
+ this.publish();
3243
+ await this.recoverApproval(entry, abort);
3244
+ return;
3245
+ }
2909
3246
  if (entry.fastApproval) {
2910
3247
  entry.fastApproval = void 0;
2911
3248
  if (entry.view.resolved) {
@@ -2926,7 +3263,28 @@ var Controller = class {
2926
3263
  } catch {
2927
3264
  }
2928
3265
  } finally {
2929
- if (!dispatched) fact("settled");
3266
+ if (!dispatched) {
3267
+ fact("settled");
3268
+ if (entry.approvalPaused) await this.afterApprovalRead(entry, abort, () => {
3269
+ entry.approvalAbort = void 0;
3270
+ entry.approvalPaused = false;
3271
+ if (this.options.fastMode && !entry.approvalRefresh) this.startApproval(entry);
3272
+ });
3273
+ else if (!entry.approvalRefresh && entry.approvalAbort === abort) entry.approvalAbort = void 0;
3274
+ else if (entry.approvalRefresh && entry.approvalAbort === abort && !this.approvalRead) entry.approvalAbort = void 0;
3275
+ if (entry.approvalPaintPending && !entry.approvalAbort && !entry.approvalRefresh && this.active(entry) && !this.manual.has(id) && this.safe(entry)) {
3276
+ entry.approvalPaintPending = false;
3277
+ entry.view = { ...entry.view, autoApproval: this.options.fastMode ? void 0 : {
3278
+ status: "countdown",
3279
+ seconds: Math.min(this.options.autoApproveDelaySeconds ?? 15, Math.max(0, Math.ceil((entry.deadline - this.time.now()) / 1e3)))
3280
+ } };
3281
+ this.publish();
3282
+ if (this.eligible(entry)) {
3283
+ if (this.options.fastMode) this.startApproval(entry);
3284
+ else this.schedule(entry);
3285
+ }
3286
+ }
3287
+ }
2930
3288
  }
2931
3289
  }
2932
3290
  asked(request) {
@@ -2971,17 +3329,26 @@ var Controller = class {
2971
3329
  return null;
2972
3330
  }
2973
3331
  }
2974
- entry.startedAt = this.time.now();
2975
- return this.evaluate(request, entry.abort.signal, () => {
2976
- if (active() && (entry.view.status === "identifying" || entry.view.status === "analyzing")) {
2977
- entry.view = { ...entry.view, status: "analyzing" };
3332
+ return this.admission.run(request, entry.root ?? request.sessionID, entry.abort.signal, () => {
3333
+ if (!active()) return Promise.resolve(null);
3334
+ if (this.modes && (!this.modes.enabled(entry.root) || (this.modeChanges.get(entry.root) ?? 0) > entry.gateRevision)) {
3335
+ this.suspend(entry);
2978
3336
  this.publish();
3337
+ return Promise.resolve(null);
2979
3338
  }
2980
- }, (progress) => this.progress(entry, progress), { review: entry.review, root: entry.root });
3339
+ entry.startedAt = this.time.now();
3340
+ return this.evaluate(request, entry.abort.signal, () => {
3341
+ if (active() && (entry.view.status === "identifying" || entry.view.status === "analyzing")) {
3342
+ entry.view = { ...entry.view, status: "analyzing" };
3343
+ this.publish();
3344
+ }
3345
+ }, (progress) => this.progress(entry, progress), { review: entry.review, root: entry.root });
3346
+ });
2981
3347
  }).then((assessment) => {
2982
3348
  this.clearProgress(entry);
2983
3349
  if (active()) {
2984
3350
  entry.view = assessment ? { ...entry.view, status: "complete", assessment, progress: void 0 } : { ...entry.view, status: "unrelated", progress: void 0 };
3351
+ if (!this.safe(entry)) this.pauseRating(entry);
2985
3352
  if (assessment) {
2986
3353
  try {
2987
3354
  const fullReportMs = Math.max(0, this.time.now() - entry.startedAt);
@@ -3007,6 +3374,7 @@ var Controller = class {
3007
3374
  this.clearProgress(entry);
3008
3375
  if (active()) {
3009
3376
  entry.view = { ...entry.view, status: entry.view.status === "identifying" ? "unidentified" : "unavailable", progress: void 0, error: error instanceof Error ? error.message : uiText.review.failed };
3377
+ this.pauseRating(entry);
3010
3378
  this.publish();
3011
3379
  this.finishFast(entry);
3012
3380
  }
@@ -3052,7 +3420,14 @@ var Controller = class {
3052
3420
  for (const [id, entry] of this.entries) if (!ids.has(id) && !entry.view.resolved) this.replied(id, true, "reconciled");
3053
3421
  for (const request of requests) {
3054
3422
  const entry = this.entries.get(request.id);
3055
- if (entry?.approvalRefresh && entry.view.autoApproval?.status === "checking") {
3423
+ if (entry && this.options.autoApprove && !entry.approvalDispatched && !this.manual.has(request.id) && !isDeepStrictEqual(request, entry.view.request)) {
3424
+ this.manual.set(request.id, "failed");
3425
+ entry.cancelTimer?.();
3426
+ entry.approvalAbort?.abort(requestChanged);
3427
+ entry.view = { ...entry.view, autoApproval: { status: "failed" } };
3428
+ this.publish();
3429
+ }
3430
+ if (entry?.approvalRefresh && !entry.approvalAbort && !this.approvalRead && !this.manual.has(request.id) && entry.view.autoApproval?.status === "checking") {
3056
3431
  entry.approvalRefresh = false;
3057
3432
  entry.approvalAbort = void 0;
3058
3433
  entry.view = { ...entry.view, autoApproval: void 0 };
@@ -3087,19 +3462,14 @@ var Controller = class {
3087
3462
  }
3088
3463
  this.entries.clear();
3089
3464
  await Promise.allSettled([...this.workers]);
3465
+ await this.admission.settled();
3090
3466
  await Promise.allSettled([...this.approvalWorkers]);
3091
3467
  this.manual.clear();
3092
3468
  }
3093
3469
  };
3094
3470
  function visibleReview(views, sessionID, getSession) {
3471
+ sessionID = conversationRoot(sessionID, getSession);
3095
3472
  if (!sessionID) return;
3096
- const session = getSession(sessionID);
3097
- if (!session) return;
3098
- if (session.parentID) {
3099
- const root = getSession(session.parentID);
3100
- if (!root || root.parentID) return;
3101
- sessionID = root.id;
3102
- }
3103
3473
  let first;
3104
3474
  let retained;
3105
3475
  for (const view of views) {
@@ -3384,6 +3754,8 @@ function unsupportedShellSyntax(command, budget) {
3384
3754
  inWord = true;
3385
3755
  } else if (quote) {
3386
3756
  if (char === quote) quote = "";
3757
+ } else if (char === "$" && command[i + 1] === "'") {
3758
+ return "ANSI-C shell quoting is outside byte-faithful literal discovery; script targets were not resolved.";
3387
3759
  } else if (char === "'" || char === '"') {
3388
3760
  quote = char;
3389
3761
  inWord = true;
@@ -3404,14 +3776,16 @@ function programIdentity(executable) {
3404
3776
  if (name === "cat" || name === "head" || name === "env") return name;
3405
3777
  }
3406
3778
  var DiscoveryState = class {
3407
- constructor(cwd, depth, budget) {
3779
+ constructor(cwd, depth, budget, logicalCwdKnown) {
3408
3780
  this.cwd = cwd;
3409
3781
  this.depth = depth;
3410
3782
  this.budget = budget;
3783
+ this.logicalCwdKnown = logicalCwdKnown;
3411
3784
  }
3412
3785
  cwd;
3413
3786
  depth;
3414
3787
  budget;
3788
+ logicalCwdKnown;
3415
3789
  references = [];
3416
3790
  limitations = [];
3417
3791
  uncertainWord = false;
@@ -3446,7 +3820,7 @@ var DiscoveryState = class {
3446
3820
  }
3447
3821
  nested(command, physical) {
3448
3822
  if (physical) this.note("Physical shell directory mode is outside literal cwd inference; nested relative script targets are unresolved.");
3449
- const nested = scan(command, this.explicitCdpath || physical ? null : this.cwd, this.depth + 1, this.budget);
3823
+ const nested = scan(command, this.explicitCdpath || physical ? null : this.cwd, this.depth + 1, this.budget, this.logicalCwdKnown);
3450
3824
  if (nested.references.length) this.qualifySnapshot();
3451
3825
  this.references.push(...nested.references);
3452
3826
  nested.limitations.forEach((text2) => this.note(text2));
@@ -3550,6 +3924,8 @@ function builtin(executable, args, state, next) {
3550
3924
  const searchesCdpath = target && !path8.isAbsolute(target) && !/^\.\.?(?:\/|$)/.test(target);
3551
3925
  if (state.previousOperator === "|" || next === "|") {
3552
3926
  state.invalidateCwd("Pipeline directory changes do not establish a reliable cwd for later script targets.");
3927
+ } else if (!state.logicalCwdKnown) {
3928
+ state.invalidateCwd("Declared and canonical launch directories do not establish the shell's logical PWD; working directory after cd is unresolved and relative script targets will not be guessed.");
3553
3929
  } else if (args.length === 1 && target && !target.startsWith("-") && literal(target) && !state.uncertainWord && !(state.explicitCdpath && searchesCdpath) && next === "&&" && state.cwd) {
3554
3930
  state.cwd = path8.resolve(state.cwd, target);
3555
3931
  state.conditionalCwd = true;
@@ -3656,6 +4032,10 @@ function interpreter(isPython, args, state) {
3656
4032
  }
3657
4033
  }
3658
4034
  function visit(args, state, next) {
4035
+ if (args[0] === "!") {
4036
+ state.invalidateCwd("Shell negation is outside literal state inference; negated source targets and the subsequent working directory are unresolved.");
4037
+ return;
4038
+ }
3659
4039
  if (args[0] === "time") {
3660
4040
  state.invalidateCwd("Shell timed commands are outside literal discovery; the working directory is unresolved.");
3661
4041
  return;
@@ -3686,10 +4066,17 @@ function visit(args, state, next) {
3686
4066
  }
3687
4067
  }
3688
4068
  function discover(command, cwd, depth = 0, signal) {
3689
- return scan(command, cwd, depth, new DiscoveryBudget(signal));
4069
+ const launch = typeof cwd === "string" || cwd === null ? { declared: cwd, canonical: cwd } : cwd;
4070
+ return scan(
4071
+ command,
4072
+ launch.declared,
4073
+ depth,
4074
+ new DiscoveryBudget(signal),
4075
+ launch.canonical !== null && launch.declared === launch.canonical
4076
+ );
3690
4077
  }
3691
- function scan(command, cwd, depth, budget) {
3692
- const state = new DiscoveryState(cwd, depth, budget);
4078
+ function scan(command, cwd, depth, budget, logicalCwdKnown) {
4079
+ const state = new DiscoveryState(cwd, depth, budget, logicalCwdKnown);
3693
4080
  const result = { references: state.references, limitations: state.limitations };
3694
4081
  try {
3695
4082
  budget.admit(command);
@@ -3818,7 +4205,8 @@ async function collectEvidence(input, limits, signal, scope = fileAccess.scope(s
3818
4205
  if (input.command.length > limits.maxEvidenceBytes) throw new Error("Command exceeds configured evidence budget");
3819
4206
  const commandBytes = Buffer.byteLength(input.command);
3820
4207
  if (commandBytes > limits.maxEvidenceBytes) throw new Error("Command exceeds configured evidence budget");
3821
- const discovery = discover(input.command, input.cwd, 0, signal);
4208
+ const canonical = input.execution ? input.execution.canonicalCwd : input.cwd ? (await scope.canonical(input.cwd)).path : null;
4209
+ const discovery = discover(input.command, { declared: input.cwd, canonical }, 0, signal);
3822
4210
  const evidence = { ...input, kind: "shell", files: [], limitations: [
3823
4211
  ...input.limitations ?? [],
3824
4212
  "Only literal Python/shell source and supported cat/head file operands are collected. Files are full review-time snapshots, not command output. Imports, dependencies, other runtimes and calls inside source files are not recursively inspected.",
@@ -4170,6 +4558,50 @@ function collectDirectoryEvidence(context, limits, signal) {
4170
4558
  // src/skill-evidence.ts
4171
4559
  init_define_REVIEW_PROMPTS();
4172
4560
  import path10 from "node:path";
4561
+ function commandTokens(text2, check, maxTokens) {
4562
+ const parts = [];
4563
+ let value = "", quote = "", active = false;
4564
+ const unsupported = { limitation: "Unsupported shell syntax in a skill command was not resolved; the whole command was omitted." };
4565
+ for (let cursor = 0; cursor < text2.length; cursor++) {
4566
+ if (!check()) return void 0;
4567
+ const char = text2[cursor];
4568
+ if (/[\u0000-\u0008\u000a-\u001f\u007f]/.test(char)) return unsupported;
4569
+ if (quote === "'") {
4570
+ if (char === "'") quote = "";
4571
+ else value += char;
4572
+ } else if (char === "\\") {
4573
+ const next = text2[++cursor];
4574
+ if (next === void 0 || /[\u0000-\u001f\u007f]/.test(next)) return unsupported;
4575
+ value += quote === '"' && !/["\\$`]/.test(next) ? `\\${next}` : next;
4576
+ active = true;
4577
+ } else if (quote === '"') {
4578
+ if (char === '"') quote = "";
4579
+ else if (char === "$" || char === "`") return unsupported;
4580
+ else value += char;
4581
+ } else if (char === " " || char === " ") {
4582
+ if (active) {
4583
+ if (parts.length >= maxTokens) return void 0;
4584
+ parts.push(value);
4585
+ value = "";
4586
+ active = false;
4587
+ }
4588
+ } else if (char === "'" || char === '"') {
4589
+ quote = char;
4590
+ active = true;
4591
+ } else {
4592
+ if (/[|&;<>()[\]{}$`*?~#]/.test(char) || char === "!" && !active) return unsupported;
4593
+ value += char;
4594
+ active = true;
4595
+ }
4596
+ if (value.length > 4096) return { limitation: "Skill command exceeded the literal argument length limit; the whole command was omitted." };
4597
+ }
4598
+ if (quote) return unsupported;
4599
+ if (active) {
4600
+ if (parts.length >= maxTokens) return void 0;
4601
+ parts.push(value);
4602
+ }
4603
+ return { parts };
4604
+ }
4173
4605
  function skillReferences(text2, signal) {
4174
4606
  const references = /* @__PURE__ */ new Set(), limitations = /* @__PURE__ */ new Set();
4175
4607
  const end = performance.now() + 250;
@@ -4252,24 +4684,51 @@ function skillReferences(text2, signal) {
4252
4684
  cursor++;
4253
4685
  continue;
4254
4686
  }
4255
- const start = ++cursor;
4256
- while (cursor < text2.length && text2[cursor] !== char) {
4687
+ const opening = cursor++;
4688
+ while (char === "`" && text2[cursor] === char) {
4257
4689
  if (!check()) {
4258
4690
  exhausted = true;
4259
4691
  break;
4260
4692
  }
4261
4693
  cursor++;
4262
4694
  }
4695
+ const width = cursor - opening, start = cursor;
4696
+ let close = -1;
4697
+ while (cursor < text2.length && !exhausted) {
4698
+ if (!check()) {
4699
+ exhausted = true;
4700
+ break;
4701
+ }
4702
+ if (text2[cursor] !== char) {
4703
+ cursor++;
4704
+ continue;
4705
+ }
4706
+ const run = cursor++;
4707
+ while (char === "`" && text2[cursor] === char) {
4708
+ if (!check()) {
4709
+ exhausted = true;
4710
+ break;
4711
+ }
4712
+ cursor++;
4713
+ }
4714
+ if (cursor - run === width) {
4715
+ close = run;
4716
+ break;
4717
+ }
4718
+ }
4263
4719
  if (exhausted) break;
4264
- if (cursor === text2.length) {
4720
+ if (close === -1) {
4265
4721
  limitations.add("Unclosed quoted skill reference was not resolved.");
4266
4722
  break;
4267
4723
  }
4268
- const quoted = text2.slice(start, cursor++);
4724
+ const quoted = text2.slice(start, close);
4269
4725
  const command = char === "`" && /^(?:(?:python(?:\d+(?:\.\d+)*)?|node|bun|deno|bash|sh|zsh|cat|head|source|uv|npx|git)\s|[^\s]+\.(?:py|sh|bash|js|mjs|ts)\s|\.{1,2}\/[^\s]+\s+--)/.test(quoted);
4270
4726
  if (command) {
4271
- for (const part of quoted.matchAll(/"([^"]+)"|'([^']+)'|[^\s]+/g)) {
4272
- if (!add(part[1] ?? part[2] ?? part[0], "literal")) {
4727
+ const parsed = commandTokens(quoted, check, 16384 - tokens);
4728
+ if (!parsed) exhausted = true;
4729
+ else if ("limitation" in parsed) limitations.add(parsed.limitation);
4730
+ else for (const part of parsed.parts) {
4731
+ if (!add(part, "literal")) {
4273
4732
  exhausted = true;
4274
4733
  break;
4275
4734
  }
@@ -4515,7 +4974,8 @@ import { insert as _$insert } from "@opentui/solid";
4515
4974
  import { setProp as _$setProp } from "@opentui/solid";
4516
4975
  import { createElement as _$createElement } from "@opentui/solid";
4517
4976
  import { createComponent as _$createComponent } from "@opentui/solid";
4518
- import { createSignal, onCleanup } from "solid-js";
4977
+ import { createSignal, For, onCleanup } from "solid-js";
4978
+ import { RGBA } from "@opentui/core";
4519
4979
 
4520
4980
  // src/lifetime.ts
4521
4981
  init_define_REVIEW_PROMPTS();
@@ -4553,18 +5013,15 @@ var HistoryRefresh = class {
4553
5013
  this.query = query;
4554
5014
  this.publish = publish;
4555
5015
  this.query = snapshotQuery(query);
4556
- this.offCommit = store.onCommit(() => this.refresh());
5016
+ this.offCommit = store.onCommit(() => this.request());
4557
5017
  this.offFailure = store.onWriteFailure(() => {
4558
5018
  if (this.query.type !== "totals" && this.query.type !== "conversationTotals" || this.stopped) return;
4559
5019
  this.revision++;
4560
5020
  this.again = false;
4561
5021
  this.publish(void 0);
4562
5022
  });
4563
- this.timer = setInterval(() => {
4564
- if (this.active) this.again = true;
4565
- else this.refresh();
4566
- }, 2e3);
4567
- this.refresh();
5023
+ this.timer = setInterval(() => this.request(), 2e3);
5024
+ this.request();
4568
5025
  }
4569
5026
  store;
4570
5027
  query;
@@ -4585,9 +5042,14 @@ var HistoryRefresh = class {
4585
5042
  retain(query) {
4586
5043
  this.query = snapshotQuery(query);
4587
5044
  }
5045
+ /** Navigation, deletion and maintenance invalidate even a successful old read. */
4588
5046
  refresh() {
4589
5047
  if (this.stopped) return;
4590
5048
  this.revision++;
5049
+ this.request();
5050
+ }
5051
+ request() {
5052
+ if (this.stopped) return;
4591
5053
  if (this.active) {
4592
5054
  this.again = true;
4593
5055
  return;
@@ -4604,7 +5066,8 @@ var HistoryRefresh = class {
4604
5066
  }, () => {
4605
5067
  if (!this.stopped && revision === this.revision) this.publish(void 0);
4606
5068
  }).finally(async () => {
4607
- await read.settled;
5069
+ await read.settled?.catch(() => {
5070
+ });
4608
5071
  this.active = false;
4609
5072
  if (this.again && !this.stopped) this.start();
4610
5073
  }).catch(() => {
@@ -4769,6 +5232,17 @@ function lifetimeTracker(api, store, root) {
4769
5232
  };
4770
5233
  }
4771
5234
  function StatisticsDialog(props) {
5235
+ const [hover, setHover] = createSignal();
5236
+ const selectedForeground = () => {
5237
+ const theme = props.api.theme.current;
5238
+ if (theme.selectedListItemText.a !== 0) return theme.selectedListItemText;
5239
+ const {
5240
+ r,
5241
+ g,
5242
+ b
5243
+ } = theme.accent;
5244
+ return 0.299 * r + 0.587 * g + 0.114 * b > 0.5 ? RGBA.fromInts(0, 0, 0) : RGBA.fromInts(255, 255, 255);
5245
+ };
4772
5246
  const depth = props.api.ui.dialog.depth;
4773
5247
  const toggle = () => props.controller.select(props.state.view === "lifetime" ? "conversation" : "lifetime");
4774
5248
  const unregister = props.api.keymap.registerLayer({
@@ -4796,11 +5270,11 @@ function StatisticsDialog(props) {
4796
5270
  return lifetimeReport(state.conversation.totals);
4797
5271
  };
4798
5272
  return (() => {
4799
- var _el$ = _$createElement("box"), _el$2 = _$createElement("box"), _el$3 = _$createElement("text"), _el$4 = _$createElement("b"), _el$5 = _$createElement("text"), _el$6 = _$createElement("box"), _el$7 = _$createElement("text"), _el$8 = _$createElement("text"), _el$9 = _$createElement("text"), _el$0 = _$createElement("text");
5273
+ var _el$ = _$createElement("box"), _el$2 = _$createElement("box"), _el$3 = _$createElement("text"), _el$4 = _$createElement("b"), _el$5 = _$createElement("text"), _el$6 = _$createElement("box"), _el$7 = _$createElement("text"), _el$8 = _$createElement("text");
4800
5274
  _$insertNode(_el$, _el$2);
4801
5275
  _$insertNode(_el$, _el$6);
4802
- _$insertNode(_el$, _el$9);
4803
- _$insertNode(_el$, _el$0);
5276
+ _$insertNode(_el$, _el$7);
5277
+ _$insertNode(_el$, _el$8);
4804
5278
  _$setProp(_el$, "paddingLeft", 2);
4805
5279
  _$setProp(_el$, "paddingRight", 2);
4806
5280
  _$setProp(_el$, "paddingBottom", 1);
@@ -4813,32 +5287,51 @@ function StatisticsDialog(props) {
4813
5287
  _$insert(_el$4, () => uiText.lifetime.title);
4814
5288
  _$setProp(_el$5, "onMouseUp", () => props.api.ui.dialog.clear());
4815
5289
  _$insert(_el$5, () => uiText.lifetime.close);
4816
- _$insertNode(_el$6, _el$7);
4817
- _$insertNode(_el$6, _el$8);
4818
5290
  _$setProp(_el$6, "flexDirection", "row");
4819
- _$setProp(_el$6, "gap", 3);
4820
- _$setProp(_el$7, "onMouseUp", () => props.controller.select("conversation"));
4821
- _$insert(_el$7, () => uiText.lifetime.scopeTab("conversation", props.state.view === "conversation"));
4822
- _$setProp(_el$8, "onMouseUp", () => props.controller.select("lifetime"));
4823
- _$insert(_el$8, () => uiText.lifetime.scopeTab("lifetime", props.state.view === "lifetime"));
4824
- _$insert(_el$9, message);
4825
- _$insert(_el$0, () => uiText.lifetime.switchScope);
5291
+ _$setProp(_el$6, "gap", 1);
5292
+ _$insert(_el$6, _$createComponent(For, {
5293
+ each: ["conversation", "lifetime"],
5294
+ children: (scope) => {
5295
+ const active = () => props.state.view === scope;
5296
+ return (() => {
5297
+ var _el$9 = _$createElement("box"), _el$0 = _$createElement("text");
5298
+ _$insertNode(_el$9, _el$0);
5299
+ _$setProp(_el$9, "paddingLeft", 1);
5300
+ _$setProp(_el$9, "paddingRight", 1);
5301
+ _$setProp(_el$9, "onMouseOver", () => setHover(scope));
5302
+ _$setProp(_el$9, "onMouseOut", () => setHover(void 0));
5303
+ _$setProp(_el$9, "onMouseUp", (event) => {
5304
+ if (event.button !== 0 || props.api.renderer.getSelection()?.getSelectedText()) return;
5305
+ props.controller.select(scope);
5306
+ });
5307
+ _$insert(_el$0, () => uiText.lifetime.scopeTab(scope));
5308
+ _$effect((_p$) => {
5309
+ var _v$5 = active() ? props.api.theme.current.accent : hover() === scope ? props.api.theme.current.backgroundElement : props.api.theme.current.backgroundPanel, _v$6 = active() ? selectedForeground() : props.api.theme.current.textMuted;
5310
+ _v$5 !== _p$.e && (_p$.e = _$setProp(_el$9, "backgroundColor", _v$5, _p$.e));
5311
+ _v$6 !== _p$.t && (_p$.t = _$setProp(_el$0, "fg", _v$6, _p$.t));
5312
+ return _p$;
5313
+ }, {
5314
+ e: void 0,
5315
+ t: void 0
5316
+ });
5317
+ return _el$9;
5318
+ })();
5319
+ }
5320
+ }));
5321
+ _$insert(_el$7, message);
5322
+ _$insert(_el$8, () => uiText.lifetime.switchScope);
4826
5323
  _$effect((_p$) => {
4827
- var _v$ = props.api.theme.current.text, _v$2 = props.api.theme.current.textMuted, _v$3 = props.state.view === "conversation" ? props.api.theme.current.accent : props.api.theme.current.textMuted, _v$4 = props.state.view === "lifetime" ? props.api.theme.current.accent : props.api.theme.current.textMuted, _v$5 = props.api.theme.current.text, _v$6 = props.api.theme.current.textMuted;
5324
+ var _v$ = props.api.theme.current.text, _v$2 = props.api.theme.current.textMuted, _v$3 = props.api.theme.current.text, _v$4 = props.api.theme.current.textMuted;
4828
5325
  _v$ !== _p$.e && (_p$.e = _$setProp(_el$3, "fg", _v$, _p$.e));
4829
5326
  _v$2 !== _p$.t && (_p$.t = _$setProp(_el$5, "fg", _v$2, _p$.t));
4830
5327
  _v$3 !== _p$.a && (_p$.a = _$setProp(_el$7, "fg", _v$3, _p$.a));
4831
5328
  _v$4 !== _p$.o && (_p$.o = _$setProp(_el$8, "fg", _v$4, _p$.o));
4832
- _v$5 !== _p$.i && (_p$.i = _$setProp(_el$9, "fg", _v$5, _p$.i));
4833
- _v$6 !== _p$.n && (_p$.n = _$setProp(_el$0, "fg", _v$6, _p$.n));
4834
5329
  return _p$;
4835
5330
  }, {
4836
5331
  e: void 0,
4837
5332
  t: void 0,
4838
5333
  a: void 0,
4839
- o: void 0,
4840
- i: void 0,
4841
- n: void 0
5334
+ o: void 0
4842
5335
  });
4843
5336
  return _el$;
4844
5337
  })();
@@ -5063,7 +5556,7 @@ var SessionModes = class {
5063
5556
  this.values.set(root, enabled);
5064
5557
  return this.store.write(root, enabled);
5065
5558
  }
5066
- deleted(id) {
5559
+ deleted(_id) {
5067
5560
  this.ancestryRevision++;
5068
5561
  this.roots.clear();
5069
5562
  }
@@ -5101,9 +5594,10 @@ function sessionModeCommands(api, modes, changed) {
5101
5594
  if (enabled) await modes.load(root, api.lifecycle.signal);
5102
5595
  api.lifecycle.signal.throwIfAborted();
5103
5596
  if (latest.get(root) !== order) return;
5597
+ const invalidate = !enabled || !modes.enabled(root);
5104
5598
  const persisted = modes.set(root, enabled);
5105
5599
  applied = true;
5106
- changed(root);
5600
+ if (invalidate) changed(root);
5107
5601
  await persisted;
5108
5602
  if (!api.lifecycle.signal.aborted && latest.get(root) === order) api.ui.toast({
5109
5603
  variant: "success",
@@ -5372,8 +5866,15 @@ var NotificationPolicy = class {
5372
5866
  entry.phase = "attention";
5373
5867
  entry.message = this.message(kind, uiText.notifications[kind], entry.target);
5374
5868
  const message = entry.message;
5869
+ if (entry.identity.kind === "permission" && kind !== "question" && entry.announced?.has(kind)) {
5870
+ entry.banner = void 0;
5871
+ this.remind(entry);
5872
+ return;
5873
+ }
5375
5874
  entry.banner = message ? this.dispatch(message, entry.target, () => {
5376
- if (entry.message === message) this.remind(entry);
5875
+ if (entry.message !== message) return;
5876
+ if (entry.identity.kind === "permission" && kind !== "question") (entry.announced ??= /* @__PURE__ */ new Set()).add(kind);
5877
+ this.remind(entry);
5377
5878
  }) : void 0;
5378
5879
  }
5379
5880
  wait(entry) {
@@ -5383,13 +5884,14 @@ var NotificationPolicy = class {
5383
5884
  entry.message = void 0;
5384
5885
  entry.phase = "waiting";
5385
5886
  }
5386
- snapshot(views, blockers = this.blockers) {
5887
+ snapshot(views, blockers = this.blockers, healthy = true) {
5387
5888
  if (this.stopped) return;
5388
5889
  this.views = new Map(views.map((view) => [view.request.id, view]));
5389
5890
  this.blockers = new Map(blockers);
5390
5891
  for (const [id, entry] of this.permissions) {
5391
5892
  if (this.views.has(id)) this.select(entry);
5392
- else this.resolved("permission", id);
5893
+ else if (healthy) this.resolved("permission", id);
5894
+ else this.wait(entry);
5393
5895
  }
5394
5896
  for (const entry of this.questions.values()) this.select(entry);
5395
5897
  }
@@ -5572,6 +6074,7 @@ var NotificationHost = class {
5572
6074
  for (const id of this.questions.keys()) if (!ids.has(id)) this.resolved("question", id);
5573
6075
  this.admissions();
5574
6076
  this.selectBlockers();
6077
+ this.recheckIdle();
5575
6078
  } catch {
5576
6079
  if (!this.stopped) {
5577
6080
  this.questionsReady = false;
@@ -5585,19 +6088,25 @@ var NotificationHost = class {
5585
6088
  if (this.stopped) return;
5586
6089
  this.policy.pending(this.blockers());
5587
6090
  }
5588
- blockers() {
6091
+ interactions() {
5589
6092
  const pending = /* @__PURE__ */ new Map();
5590
6093
  const add = (kind, id, sessionID) => {
5591
6094
  if (!this.closed.has(`${kind}:${id}`) && !this.deletedSessions.has(sessionID)) pending.set(`${kind}:${id}`, { kind, id, sessionID });
5592
6095
  };
5593
6096
  for (const view of this.views) add("permission", view.request.id, view.request.sessionID);
5594
6097
  for (const [id, request] of this.permissions) add("permission", id, request.sessionID);
5595
- const unknownPermission = [...pending.values()].some((value) => !this.api.state.session.get(value.sessionID));
5596
- if (this.questionsReady && this.permissionsReady && !unknownPermission) {
5597
- for (const request of this.questionSnapshot) add("question", request.id, request.sessionID);
5598
- for (const [id, request] of this.questions) add("question", id, request.sessionID);
5599
- }
5600
- return notificationBlockers(new Set(this.roots.keys()), pending.values(), (id) => this.api.state.session.get(id));
6098
+ for (const request of this.questionSnapshot) add("question", request.id, request.sessionID);
6099
+ for (const [id, request] of this.questions) add("question", id, request.sessionID);
6100
+ return [...pending.values()];
6101
+ }
6102
+ blockers() {
6103
+ const pending = this.interactions();
6104
+ if (!this.permissionsReady || pending.some((value) => value.kind === "permission" && !this.rootOf(value.sessionID))) return /* @__PURE__ */ new Map();
6105
+ return notificationBlockers(
6106
+ new Set(this.roots.keys()),
6107
+ pending.filter((value) => value.kind === "permission" || this.questionsReady),
6108
+ (id) => this.api.state.session.get(id)
6109
+ );
5601
6110
  }
5602
6111
  rootOf(id) {
5603
6112
  const seen = /* @__PURE__ */ new Set();
@@ -5672,8 +6181,8 @@ var NotificationHost = class {
5672
6181
  }
5673
6182
  admit(kind, id, request) {
5674
6183
  if (request.target || this.stopped || this.deletedSessions.has(request.sessionID)) return;
5675
- const rootID = request.root ?? this.rootOf(request.sessionID);
5676
- if (!rootID) {
6184
+ const rootID2 = request.root ?? this.rootOf(request.sessionID);
6185
+ if (!rootID2) {
5677
6186
  if (request.resolving || (request.retryAt ?? 0) > this.clock.now()) return;
5678
6187
  const lookup = this.lookup(request.sessionID);
5679
6188
  if (!lookup) return;
@@ -5691,9 +6200,9 @@ var NotificationHost = class {
5691
6200
  });
5692
6201
  return;
5693
6202
  }
5694
- const root = this.roots.get(rootID);
6203
+ const root = this.roots.get(rootID2);
5695
6204
  if (!root || request.received < root.since) return;
5696
- const target = { root: rootID, sessionID: rootID, title: this.api.state.session.get(rootID)?.title ?? uiText.notifications.fallbackSession };
6205
+ const target = { root: rootID2, sessionID: rootID2, title: this.api.state.session.get(rootID2)?.title ?? uiText.notifications.fallbackSession };
5697
6206
  request.target = target;
5698
6207
  if (kind === "question") this.policy.question(id, target, true);
5699
6208
  else {
@@ -5721,11 +6230,14 @@ var NotificationHost = class {
5721
6230
  for (const request of this.permissions.values()) if (request.target) {
5722
6231
  request.target.title = this.api.state.session.get(request.target.root)?.title ?? uiText.notifications.fallbackSession;
5723
6232
  }
5724
- this.policy.snapshot(views, this.blockers());
6233
+ this.policy.snapshot(views, this.blockers(), this.permissionsReady);
5725
6234
  for (const [id, request] of this.permissions) this.admit("permission", id, request);
5726
6235
  const pending = new Set(views.map((v) => v.request.id));
5727
- for (const [id] of this.permissions) if (!pending.has(id)) this.resolved("permission", id);
6236
+ if (this.permissionsReady) {
6237
+ for (const [id] of this.permissions) if (!pending.has(id)) this.resolved("permission", id);
6238
+ }
5728
6239
  this.selectBlockers();
6240
+ if (permissionHealthy === true) this.recheckIdle();
5729
6241
  }
5730
6242
  fact(fact) {
5731
6243
  if (this.stopped) return;
@@ -5753,6 +6265,7 @@ var NotificationHost = class {
5753
6265
  this.closed.add(key);
5754
6266
  this.policy.resolved(kind, id);
5755
6267
  this.selectBlockers();
6268
+ this.recheckIdle();
5756
6269
  }
5757
6270
  message(message) {
5758
6271
  if (this.stopped) return;
@@ -5777,19 +6290,26 @@ var NotificationHost = class {
5777
6290
  if (message.error?.name === "MessageAbortedError") root.cancelled = true;
5778
6291
  }
5779
6292
  }
6293
+ recheckIdle() {
6294
+ for (const id of this.roots.keys()) this.idle(id);
6295
+ }
5780
6296
  idle(id) {
5781
6297
  const root = this.roots.get(id);
5782
6298
  if (!root || this.stopped) return;
5783
6299
  root.idle?.();
5784
6300
  root.idle = this.clock.after(100, () => {
5785
6301
  root.idle = void 0;
5786
- if (this.stopped || this.roots.get(id) !== root || this.api.state.session.status(id)?.type !== "idle") return;
6302
+ if (this.stopped || this.api.lifecycle.signal.aborted || this.roots.get(id) !== root || this.api.state.session.status(id)?.type !== "idle") return;
5787
6303
  if (root.cancelled) return;
6304
+ if (!this.permissionsReady || !this.questionsReady) return;
5788
6305
  const messages = this.api.state.session.messages(id);
5789
6306
  const assistant = messages.findLast((m) => m.role === "assistant" && !m.summary && m.parentID === root.user);
5790
6307
  if (assistant?.role === "assistant") root.assistant = assistant;
5791
6308
  if (root.assistant?.error?.name === "MessageAbortedError" || root.error?.name === "MessageAbortedError") return;
5792
- if (this.views.some((v) => this.rootOf(v.request.sessionID) === id) || [...this.questions.values()].some((q) => this.rootOf(q.sessionID) === id) || this.api.state.session.question(id).length || this.api.state.session.permission(id).length) return;
6309
+ if (this.interactions().some((value) => {
6310
+ const root2 = this.rootOf(value.sessionID);
6311
+ return !root2 || root2 === id;
6312
+ }) || this.api.state.session.question(id).length || this.api.state.session.permission(id).length) return;
5793
6313
  const target = { root: id, sessionID: id, title: this.api.state.session.get(id)?.title ?? uiText.notifications.fallbackSession };
5794
6314
  const message = root.assistant;
5795
6315
  if (message?.error || root.error && (!message?.time.completed || !message.finish || message.finish === "error")) {
@@ -6185,20 +6705,20 @@ function EmscriptenWASM(WASMAudioDecoderCommon2) {
6185
6705
  var __abort_js = () => abort("");
6186
6706
  var __emscripten_runtime_keepalive_clear = () => {
6187
6707
  };
6188
- var timers = {};
6708
+ var timers2 = {};
6189
6709
  var callUserCallback = (func) => func();
6190
6710
  var _emscripten_get_now = () => performance.now();
6191
6711
  var __setitimer_js = (which, timeout_ms) => {
6192
- if (timers[which]) {
6193
- clearTimeout(timers[which].id);
6194
- delete timers[which];
6712
+ if (timers2[which]) {
6713
+ clearTimeout(timers2[which].id);
6714
+ delete timers2[which];
6195
6715
  }
6196
6716
  if (!timeout_ms) return 0;
6197
6717
  var id = setTimeout(() => {
6198
- delete timers[which];
6718
+ delete timers2[which];
6199
6719
  callUserCallback(() => __emscripten_timeout(which, _emscripten_get_now()));
6200
6720
  }, timeout_ms);
6201
- timers[which] = {
6721
+ timers2[which] = {
6202
6722
  id,
6203
6723
  timeout_ms
6204
6724
  };
@@ -7489,7 +8009,7 @@ init_define_REVIEW_PROMPTS();
7489
8009
  import { randomUUID as randomUUID4 } from "node:crypto";
7490
8010
  import path14 from "node:path";
7491
8011
  import { Worker } from "node:worker_threads";
7492
- var historyWorkerSource = true ? '"use strict";\nvar __create = Object.create;\nvar __defProp = Object.defineProperty;\nvar __getOwnPropDesc = Object.getOwnPropertyDescriptor;\nvar __getOwnPropNames = Object.getOwnPropertyNames;\nvar __getProtoOf = Object.getPrototypeOf;\nvar __hasOwnProp = Object.prototype.hasOwnProperty;\nvar __export = (target, all) => {\n for (var name in all)\n __defProp(target, name, { get: all[name], enumerable: true });\n};\nvar __copyProps = (to, from, except, desc) => {\n if (from && typeof from === "object" || typeof from === "function") {\n for (let key of __getOwnPropNames(from))\n if (!__hasOwnProp.call(to, key) && key !== except)\n __defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });\n }\n return to;\n};\nvar __toESM = (mod, isNodeMode, target) => (target = mod != null ? __create(__getProtoOf(mod)) : {}, __copyProps(\n // If the importer is in node compatibility mode or this is not an ESM\n // file that has been converted to a CommonJS file using a Babel-\n // compatible transform (i.e. "__esModule" has not been set), then set\n // "default" to the CommonJS "module.exports" for node compatibility.\n isNodeMode || !mod || !mod.__esModule ? __defProp(target, "default", { value: mod, enumerable: true }) : target,\n mod\n));\nvar __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);\n\n// src/history-storage-worker.ts\nvar history_storage_worker_exports = {};\n__export(history_storage_worker_exports, {\n openHistoryDatabase: () => openHistoryDatabase,\n privateDatabase: () => privateDatabase\n});\nmodule.exports = __toCommonJS(history_storage_worker_exports);\nvar import_node_fs = require("node:fs");\nvar import_node_path2 = __toESM(require("node:path"), 1);\nvar import_node_worker_threads = require("node:worker_threads");\n\n// src/lifetime.ts\nvar emptyActivity = () => ({\n reviews: 0,\n usageRequests: 0,\n retries: 0,\n autoApproved: 0,\n timedReviews: 0,\n meanFullReportMs: 0,\n meanRatingMs: 0,\n since: null\n});\nvar empty = () => ({\n requests: 0,\n tokenRequests: 0,\n input: 0,\n output: 0,\n priced: 0,\n cost: 0,\n since: null,\n safe: 0,\n unsafe: 0,\n ratingsSince: null,\n activity: emptyActivity()\n});\nvar count = (value) => typeof value === "number" && Number.isSafeInteger(value) && value >= 0;\nvar amount = (value) => typeof value === "number" && Number.isFinite(value) && value >= 0;\nfunction validate(value) {\n if (!value || ![value.requests, value.tokenRequests, value.input, value.output, value.priced, value.safe, value.unsafe, value.safe + value.unsafe].every(count) || !amount(value.cost) || value.priced > value.requests || value.tokenRequests > value.requests || value.requests > value.tokenRequests + value.priced || !value.tokenRequests && (value.input !== 0 || value.output !== 0) || (value.requests > 0 ? !count(value.since) || value.since > 864e13 : value.since !== null || value.input !== 0 || value.output !== 0) || !value.priced && value.cost !== 0 || (value.safe + value.unsafe > 0 ? !count(value.ratingsSince) || value.ratingsSince > 864e13 : value.ratingsSince !== null)) {\n throw new Error("Invalid lifetime usage totals");\n }\n const activity = value.activity;\n if (!activity || ![activity.reviews, activity.usageRequests, activity.retries, activity.autoApproved, activity.timedReviews].every(count) || activity.reviews > value.safe + value.unsafe || activity.usageRequests > value.requests || activity.timedReviews > activity.reviews || ![activity.meanFullReportMs, activity.meanRatingMs].every(amount) || activity.meanRatingMs > activity.meanFullReportMs || !activity.timedReviews && (activity.meanFullReportMs !== 0 || activity.meanRatingMs !== 0) || (activity.reviews || activity.usageRequests || activity.retries || activity.autoApproved ? !count(activity.since) || activity.since > 864e13 : activity.since !== null)) throw new Error("Invalid lifetime activity totals");\n return value;\n}\nfunction mean(left, leftCount, right, rightCount) {\n if (!leftCount) return right;\n if (!rightCount) return left;\n return left + (right - left) * (rightCount / (leftCount + rightCount));\n}\nfunction add(left, right) {\n return validate({\n requests: left.requests + right.requests,\n input: left.input + right.input,\n output: left.output + right.output,\n tokenRequests: left.tokenRequests + right.tokenRequests,\n priced: left.priced + right.priced,\n cost: left.cost + right.cost,\n since: left.since === null ? right.since : right.since === null ? left.since : Math.min(left.since, right.since),\n safe: left.safe + right.safe,\n unsafe: left.unsafe + right.unsafe,\n ratingsSince: left.ratingsSince === null ? right.ratingsSince : right.ratingsSince === null ? left.ratingsSince : Math.min(left.ratingsSince, right.ratingsSince),\n activity: {\n reviews: left.activity.reviews + right.activity.reviews,\n usageRequests: left.activity.usageRequests + right.activity.usageRequests,\n retries: left.activity.retries + right.activity.retries,\n autoApproved: left.activity.autoApproved + right.activity.autoApproved,\n timedReviews: left.activity.timedReviews + right.activity.timedReviews,\n meanFullReportMs: mean(left.activity.meanFullReportMs, left.activity.timedReviews, right.activity.meanFullReportMs, right.activity.timedReviews),\n meanRatingMs: mean(left.activity.meanRatingMs, left.activity.timedReviews, right.activity.meanRatingMs, right.activity.timedReviews),\n since: left.activity.since === null ? right.activity.since : right.activity.since === null ? left.activity.since : Math.min(left.activity.since, right.activity.since)\n }\n });\n}\n\n// src/history-records.ts\nvar import_node_crypto = require("node:crypto");\nvar import_node_path = __toESM(require("node:path"), 1);\nvar HISTORY_EVENT_BYTES = 512 * 1024;\nvar HISTORY_QUEUE_BYTES = 64 * 1024 * 1024;\nvar HISTORY_TEXT_BYTES = 4096;\nvar HISTORY_DESCRIPTION_BYTES = 65536;\nvar stringBytes = (bytes) => 2 + 6 * bytes;\nvar objectBytes = (fields) => 2 + Object.entries(fields).reduce((sum, [key, bytes]) => sum + JSON.stringify(key).length + 1 + bytes + 1, 0);\nvar numberBytes = 24;\nvar textBytes = stringBytes(HISTORY_TEXT_BYTES);\nvar usageBytes = objectBytes({ input: numberBytes, output: numberBytes, cost: numberBytes });\nvar acceptedFields = {\n safe: 5,\n completedAt: numberBytes,\n reportedModel: textBytes,\n timing: objectBytes({ fullReportMs: numberBytes, ratingMs: numberBytes })\n};\nvar HISTORY_RECORD_BYTES = {\n context: objectBytes({\n scope: textBytes,\n root: textBytes,\n session: textBytes,\n permission: textBytes,\n review: textBytes,\n category: JSON.stringify("external_directory").length,\n configuredModel: textBytes,\n provider: textBytes\n }),\n accepted: objectBytes(acceptedFields),\n dispatched: objectBytes({ at: numberBytes, retry: JSON.stringify("transport").length }),\n finalized: objectBytes({ at: numberBytes, usage: usageBytes, reportedModel: textBytes }),\n payload: objectBytes({ ...acceptedFields, desc: stringBytes(HISTORY_DESCRIPTION_BYTES), usage: usageBytes }),\n totals: objectBytes({\n requests: numberBytes,\n tokenRequests: numberBytes,\n input: numberBytes,\n output: numberBytes,\n priced: numberBytes,\n cost: numberBytes,\n since: numberBytes,\n safe: numberBytes,\n unsafe: numberBytes,\n ratingsSince: numberBytes,\n activity: objectBytes({\n reviews: numberBytes,\n usageRequests: numberBytes,\n retries: numberBytes,\n autoApproved: numberBytes,\n timedReviews: numberBytes,\n meanFullReportMs: numberBytes,\n meanRatingMs: numberBytes,\n since: numberBytes\n })\n })\n};\nvar HistoryInvalid = class extends Error {\n};\nvar opaque = (...parts) => (0, import_node_crypto.createHash)("sha256").update(JSON.stringify(parts)).digest("hex");\nvar entryID = (c) => opaque(c.scope, c.permission);\nvar rootID = (c) => opaque(c.scope, c.root);\nvar sessionID = (c) => opaque(c.scope, c.session);\nvar reviewID = (c) => opaque(c.scope, c.review);\nvar count2 = (v) => typeof v === "number" && Number.isSafeInteger(v) && v >= 0;\nvar amount2 = (v) => typeof v === "number" && Number.isFinite(v) && v >= 0;\nfunction requireValue(ok) {\n if (!ok) throw new HistoryInvalid("Invalid history record");\n}\nfunction object(v, keys) {\n requireValue(v && typeof v === "object" && !Array.isArray(v));\n requireValue(Object.getPrototypeOf(v) === Object.prototype || Object.getPrototypeOf(v) === null);\n requireValue(Reflect.ownKeys(v).every((k) => {\n const descriptor = Object.getOwnPropertyDescriptor(v, k);\n return typeof k === "string" && keys.includes(k) && descriptor.enumerable && "value" in descriptor;\n }));\n}\nfunction text(v, max = HISTORY_TEXT_BYTES) {\n requireValue(typeof v === "string" && v.trim().length > 0 && Buffer.byteLength(v) <= max && !v.includes("\\0"));\n}\nfunction timestamp(v) {\n requireValue(count2(v) && v <= 864e13);\n}\nfunction validateScope(c) {\n text(c.scope);\n requireValue(import_node_path.default.isAbsolute(c.scope));\n text(c.root);\n text(c.session);\n}\nfunction validateReview(c) {\n object(c, ["scope", "root", "session", "permission", "review", "category", "configuredModel", "provider"]);\n validateScope(c);\n text(c.permission);\n text(c.review);\n text(c.configuredModel);\n text(c.provider);\n requireValue(["bash", "edit", "mcp", "custom", "external_directory", "skill"].includes(c.category));\n const url = new URL(c.provider);\n requireValue(["http:", "https:"].includes(url.protocol) && !url.username && !url.password && !url.search && !url.hash && url.href.replace(/\\/+$/, "") === c.provider);\n}\nfunction usage(v) {\n object(v, ["input", "output", "cost"]);\n requireValue(v.input === void 0 && v.output === void 0 || count2(v.input) && count2(v.output));\n requireValue(v.cost === void 0 || amount2(v.cost));\n requireValue(v.input !== void 0 || v.cost !== void 0);\n}\nfunction accepted(v, payload = false) {\n object(v, ["safe", "completedAt", "reportedModel", "timing", ...payload ? ["desc", "usage"] : []]);\n requireValue(typeof v.safe === "boolean");\n timestamp(v.completedAt);\n if (v.reportedModel !== void 0) text(v.reportedModel);\n if (v.timing !== void 0) {\n object(v.timing, ["fullReportMs", "ratingMs"]);\n requireValue(amount2(v.timing.fullReportMs) && amount2(v.timing.ratingMs) && v.timing.ratingMs <= v.timing.fullReportMs);\n }\n}\nfunction validatePayload(v) {\n accepted(v, true);\n requireValue(typeof v.desc === "string" && v.desc.trim().length > 0 && Buffer.byteLength(v.desc) <= HISTORY_DESCRIPTION_BYTES);\n if (v.usage !== void 0) usage(v.usage);\n return v;\n}\nfunction validateEvent(e) {\n const extra = {\n attemptDispatched: ["attempt", "retry"],\n attemptFinalized: ["attempt", "usage", "reportedModel"],\n reviewAccepted: ["accepted"],\n approvalDispatched: ["approval", "automatic"],\n approvalSettled: ["approval", "result"],\n approvalConfirmed: ["approval", "automatic"],\n permissionResolved: ["outcome", "payload"],\n permissionOutcome: ["outcome"],\n sessionDeleted: []\n };\n requireValue(e && Object.hasOwn(extra, e.type));\n object(e, ["type", "context", "at", ...extra[e.type]]);\n timestamp(e.at);\n if (e.type === "sessionDeleted") {\n object(e.context, ["scope", "root", "session"]);\n validateScope(e.context);\n return e;\n }\n validateReview(e.context);\n switch (e.type) {\n case "attemptDispatched":\n text(e.attempt);\n requireValue(["initial", "transport", "format"].includes(e.retry));\n break;\n case "attemptFinalized":\n text(e.attempt);\n if (e.usage !== void 0) usage(e.usage);\n if (e.reportedModel !== void 0) text(e.reportedModel);\n break;\n case "reviewAccepted":\n accepted(e.accepted);\n break;\n case "approvalDispatched":\n case "approvalConfirmed":\n text(e.approval);\n requireValue(typeof e.automatic === "boolean");\n break;\n case "approvalSettled":\n text(e.approval);\n requireValue(["not-sent", "uncertain"].includes(e.result));\n break;\n case "permissionResolved":\n requireValue(["auto", "manual", "rejected", "cancelled"].includes(e.outcome));\n validatePayload(e.payload);\n break;\n case "permissionOutcome":\n requireValue(["manual", "rejected"].includes(e.outcome));\n break;\n }\n return e;\n}\nfunction decodeEvent(text2) {\n requireValue(typeof text2 === "string" && Buffer.byteLength(text2) <= HISTORY_EVENT_BYTES);\n try {\n return validateEvent(JSON.parse(text2));\n } catch {\n throw new HistoryInvalid("Invalid history event");\n }\n}\nfunction validateQuery(q) {\n requireValue(q && ["totals", "conversationTotals", "history", "sessions", "resolution", "session"].includes(q.type));\n if (q.type === "totals") {\n object(q, ["type", "conversation"]);\n if (q.conversation !== void 0) {\n object(q.conversation, ["scope", "root"]);\n text(q.conversation.scope);\n text(q.conversation.root);\n requireValue(import_node_path.default.isAbsolute(q.conversation.scope));\n }\n return;\n }\n text(q.scope);\n requireValue(import_node_path.default.isAbsolute(q.scope));\n if (q.type === "conversationTotals") {\n object(q, ["type", "scope", "root"]);\n text(q.root);\n return;\n }\n if (q.type === "session") {\n object(q, ["type", "scope", "session"]);\n text(q.session);\n return;\n }\n if (q.type === "resolution") {\n object(q, ["type", "scope", "root", "session", "permission"]);\n validateScope(q);\n text(q.permission);\n return;\n }\n if (q.type === "sessions") {\n object(q, ["type", "scope", "after", "limit"]);\n if (q.after !== void 0) text(q.after);\n requireValue(q.limit === void 0 || count2(q.limit) && q.limit > 0 && q.limit <= 100);\n } else {\n object(q, ["type", "scope", "root", "entry", "direction", "order"]);\n text(q.root);\n if (q.order !== void 0) {\n object(q.order, ["completed", "tie", "id"]);\n timestamp(q.order.completed);\n text(q.order.tie);\n requireValue(q.entry && q.order.id === q.entry && /^[a-f0-9]{64}$/.test(q.order.id));\n }\n if (q.entry !== void 0) requireValue(/^[a-f0-9]{64}$/.test(q.entry));\n requireValue(q.direction === void 0 || q.entry && ["older", "newer"].includes(q.direction));\n }\n}\n\n// src/history-statistics.ts\nfunction historyDelta(e) {\n const delta = empty(), activity = delta.activity;\n if (e.type === "attemptDispatched" && e.retry !== "initial") {\n activity.retries = 1;\n activity.since = e.at;\n } else if (e.type === "attemptFinalized" && e.usage) {\n delta.requests = activity.usageRequests = 1;\n delta.since = activity.since = e.at;\n if (e.usage.input !== void 0) {\n delta.tokenRequests = 1;\n delta.input = e.usage.input;\n delta.output = e.usage.output;\n }\n if (e.usage.cost !== void 0) {\n delta.priced = 1;\n delta.cost = e.usage.cost;\n }\n } else if (e.type === "reviewAccepted") {\n delta.safe = e.accepted.safe ? 1 : 0;\n delta.unsafe = e.accepted.safe ? 0 : 1;\n delta.ratingsSince = e.accepted.completedAt;\n activity.reviews = 1;\n activity.since = e.accepted.completedAt;\n if (e.accepted.timing) {\n activity.timedReviews = 1;\n activity.meanFullReportMs = e.accepted.timing.fullReportMs;\n activity.meanRatingMs = e.accepted.timing.ratingMs;\n }\n } else if (e.type === "approvalConfirmed" && e.automatic) {\n activity.autoApproved = 1;\n activity.since = e.at;\n }\n return delta;\n}\n\n// src/history-schema.ts\nvar schema = `\nCREATE TABLE meta (id INTEGER PRIMARY KEY CHECK(id=1), version INTEGER NOT NULL, revision INTEGER NOT NULL, totals TEXT NOT NULL);\nCREATE TABLE writers (id TEXT PRIMARY KEY, sequence INTEGER NOT NULL);\nCREATE TABLE contributions (id TEXT PRIMARY KEY);\nCREATE TABLE tombstones (id TEXT PRIMARY KEY);\nCREATE TABLE roots (id TEXT PRIMARY KEY, revision INTEGER NOT NULL);\nCREATE TABLE sessions (id TEXT PRIMARY KEY, scope TEXT NOT NULL, root TEXT NOT NULL, session TEXT NOT NULL);\nCREATE INDEX sessions_scope ON sessions(scope,id);\nCREATE TABLE reviews (id TEXT PRIMARY KEY, entry TEXT NOT NULL, root TEXT NOT NULL, session TEXT NOT NULL, context TEXT NOT NULL, accepted TEXT);\nCREATE INDEX reviews_root ON reviews(root);\nCREATE INDEX reviews_session ON reviews(session);\nCREATE INDEX reviews_entry ON reviews(entry);\nCREATE TABLE attempts (id TEXT PRIMARY KEY, review TEXT NOT NULL REFERENCES reviews(id) ON DELETE CASCADE, dispatched TEXT, finalized TEXT);\nCREATE TABLE approvals (id TEXT PRIMARY KEY, entry TEXT NOT NULL, root TEXT NOT NULL, session TEXT NOT NULL, review TEXT NOT NULL, state TEXT NOT NULL, automatic INTEGER NOT NULL, at INTEGER NOT NULL);\nCREATE INDEX approvals_entry ON approvals(entry);\nCREATE TABLE history (id TEXT PRIMARY KEY, scope TEXT NOT NULL, root TEXT NOT NULL, session TEXT NOT NULL, permission TEXT NOT NULL,\n completed INTEGER NOT NULL, tie TEXT NOT NULL, review TEXT NOT NULL, outcome TEXT, resolution TEXT NOT NULL, approving TEXT, resolvedAt INTEGER NOT NULL,\n UNIQUE(scope,permission));\nCREATE INDEX history_order ON history(scope,root,completed,tie,id);\nCREATE INDEX history_session ON history(session);\nCREATE TABLE payloads (id TEXT PRIMARY KEY REFERENCES history(id) ON DELETE CASCADE, body TEXT NOT NULL);\n`;\nvar HistorySQL = class {\n constructor(db) {\n this.db = db;\n db.exec("PRAGMA busy_timeout=250; PRAGMA foreign_keys=ON; PRAGMA journal_mode=WAL; PRAGMA synchronous=FULL; PRAGMA wal_autocheckpoint=256");\n this.transaction(() => {\n const tables = db.prepare("SELECT name FROM sqlite_master WHERE type=\'table\' AND name NOT LIKE \'sqlite_%\'").all();\n if (!tables.length) {\n db.exec(schema);\n db.prepare("INSERT INTO meta VALUES (1,1,0,?)").run(JSON.stringify(empty()));\n }\n const meta = db.prepare(`SELECT version,revision,CASE WHEN length(CAST(totals AS BLOB))<=${HISTORY_RECORD_BYTES.totals} THEN totals END AS totals FROM meta WHERE id=1`).get();\n if (meta?.version !== 1 || !count2(meta.revision)) throw new HistoryInvalid("Unsupported history schema");\n this.parseTotals(meta.totals);\n db.exec(`CREATE INDEX IF NOT EXISTS sessions_root ON sessions(scope,root);\n CREATE INDEX IF NOT EXISTS attempts_review ON attempts(review);\n CREATE INDEX IF NOT EXISTS approvals_root ON approvals(root);\n CREATE INDEX IF NOT EXISTS approvals_session ON approvals(session);\n CREATE INDEX IF NOT EXISTS history_root ON history(root);`);\n db.exec(`CREATE TABLE IF NOT EXISTS conversation_totals (id TEXT PRIMARY KEY, totals TEXT NOT NULL, partial INTEGER NOT NULL CHECK(partial IN (0,1)));\n CREATE INDEX IF NOT EXISTS reviews_root_id ON reviews(root,id);\n CREATE INDEX IF NOT EXISTS attempts_review_id ON attempts(review,id);\n CREATE INDEX IF NOT EXISTS approvals_review_id ON approvals(review,id);`);\n });\n }\n db;\n statements = /* @__PURE__ */ new Map();\n statement(sql) {\n let statement = this.statements.get(sql);\n if (!statement) {\n statement = this.db.prepare(sql);\n this.statements.set(sql, statement);\n }\n return statement;\n }\n get(sql, ...args) {\n return this.statement(sql).get(...args);\n }\n all(sql, ...args) {\n return this.statement(sql).all(...args);\n }\n run(sql, ...args) {\n this.statement(sql).run(...args);\n }\n transaction(run, write = true) {\n this.db.exec(write ? "BEGIN IMMEDIATE" : "BEGIN");\n try {\n const result = run();\n this.db.exec("COMMIT");\n return result;\n } catch (error) {\n this.db.exec("ROLLBACK");\n throw error;\n }\n }\n parseTotals(text2) {\n if (typeof text2 !== "string" || Buffer.byteLength(text2) > HISTORY_RECORD_BYTES.totals) throw new HistoryInvalid("Invalid history totals");\n return validate(JSON.parse(text2));\n }\n revision(root) {\n const previous = this.get("SELECT revision FROM roots WHERE id=?", root)?.revision ?? 0;\n if (!count2(previous) || !count2(previous + 1)) throw new HistoryInvalid("History revision overflow");\n this.run("INSERT INTO roots VALUES (?,1) ON CONFLICT(id) DO UPDATE SET revision=revision+1", root);\n }\n apply(writer, sequence, serialized, beforeCommit) {\n if (!/^[a-zA-Z0-9-]{1,128}$/.test(writer) || !count2(sequence) || !sequence) throw new HistoryInvalid("Invalid writer sequence");\n const event = decodeEvent(serialized);\n return this.transaction(() => {\n const previous = this.get("SELECT sequence FROM writers WHERE id=?", writer)?.sequence ?? 0;\n if (sequence <= previous) return { replay: true };\n if (sequence !== previous + 1) throw new HistoryInvalid("History sequence gap");\n this.event(event);\n this.run("INSERT INTO writers VALUES (?,?) ON CONFLICT(id) DO UPDATE SET sequence=excluded.sequence", writer, sequence);\n beforeCommit?.();\n return { replay: false };\n });\n }\n /** Indexed, bounded-memory baseline of attributable v1 facts. Never reads\n * report payloads or legacy aggregate snapshots, and never rebuilds lifetime.\n * Deletions before this feature may have removed facts, hence partial coverage.\n * Reads are pure; the next actual event materializes the baseline transactionally.\n */\n conversation(root) {\n const saved = this.get(`SELECT CASE WHEN length(CAST(totals AS BLOB))<=${HISTORY_RECORD_BYTES.totals} THEN totals END AS totals,partial FROM conversation_totals WHERE id=?`, root);\n if (saved) {\n if (saved.partial !== 0 && saved.partial !== 1) throw new HistoryInvalid("Invalid conversation coverage");\n return { totals: this.parseTotals(saved.totals), partialHistory: saved.partial === 1 };\n }\n let totals = empty(), after = "", partialHistory = !!this.get("SELECT id FROM roots WHERE id=?", root);\n const include = (event) => {\n validateEvent(event);\n totals = add(totals, historyDelta(event));\n };\n for (; ; ) {\n const reviews = this.all(`SELECT id,CASE WHEN length(CAST(context AS BLOB))<=${HISTORY_RECORD_BYTES.context} THEN context END AS context,\n CASE WHEN length(CAST(accepted AS BLOB))<=${HISTORY_RECORD_BYTES.accepted} THEN accepted END AS accepted,accepted IS NOT NULL AS hasAccepted\n FROM reviews WHERE root=? AND id>? ORDER BY id LIMIT 100`, root, after);\n if (!reviews.length) break;\n partialHistory = true;\n for (const row of reviews) {\n const context = JSON.parse(row.context);\n validateReview(context);\n if (rootID(context) !== root || reviewID(context) !== row.id) throw new HistoryInvalid("Invalid conversation ownership");\n if (row.hasAccepted) {\n const accepted2 = JSON.parse(row.accepted);\n include({ type: "reviewAccepted", context, at: accepted2?.completedAt, accepted: accepted2 });\n }\n for (const table of ["attempts", "approvals"]) {\n let cursor = "";\n for (; ; ) {\n const rows = table === "attempts" ? this.all(`SELECT id,\n CASE WHEN length(CAST(dispatched AS BLOB))<=${HISTORY_RECORD_BYTES.dispatched} THEN dispatched END AS dispatched,\n CASE WHEN length(CAST(finalized AS BLOB))<=${HISTORY_RECORD_BYTES.finalized} THEN finalized END AS finalized,\n dispatched IS NOT NULL AS hasDispatched,finalized IS NOT NULL AS hasFinalized\n FROM attempts WHERE review=? AND id>? ORDER BY id LIMIT 100`, row.id, cursor) : this.all("SELECT id,root,session,at,state,automatic FROM approvals WHERE review=? AND id>? ORDER BY id LIMIT 100", row.id, cursor);\n if (!rows.length) break;\n for (const fact of rows) {\n if (table === "attempts") {\n if (fact.hasDispatched) include({ ...JSON.parse(fact.dispatched), type: "attemptDispatched", context, attempt: fact.id });\n if (fact.hasFinalized) include({ ...JSON.parse(fact.finalized), type: "attemptFinalized", context, attempt: fact.id });\n } else {\n if (fact.root !== root || fact.session !== sessionID(context) || ![-1, 0, 1].includes(fact.automatic) || !["pending", "uncertain", "not-sent", "confirmed"].includes(fact.state)) throw new HistoryInvalid("Invalid conversation approval");\n if (fact.state === "confirmed") {\n if (fact.automatic === -1) throw new HistoryInvalid("Invalid conversation approval");\n include({ type: "approvalConfirmed", context, at: fact.at, approval: fact.id, automatic: fact.automatic === 1 });\n }\n }\n }\n cursor = rows.at(-1).id;\n }\n }\n }\n after = reviews.at(-1).id;\n }\n return { totals, partialHistory };\n }\n ensureConversation(root) {\n if (this.get("SELECT id FROM conversation_totals WHERE id=?", root)) return;\n const baseline = this.conversation(root);\n this.run("INSERT INTO conversation_totals VALUES (?,?,?)", root, JSON.stringify(baseline.totals), Number(baseline.partialHistory));\n }\n contribute(key, delta, root) {\n if (this.get("SELECT id FROM contributions WHERE id=?", key)) return;\n const meta = this.get(`SELECT CASE WHEN length(CAST(totals AS BLOB))<=${HISTORY_RECORD_BYTES.totals} THEN totals END AS totals,revision FROM meta WHERE id=1`);\n const totals = add(this.parseTotals(meta.totals), delta);\n const conversation = add(this.conversation(root).totals, delta);\n if (!count2(meta.revision + 1)) throw new HistoryInvalid("History revision overflow");\n this.run("INSERT INTO contributions VALUES (?)", key);\n if (JSON.stringify(totals) !== meta.totals) this.run("UPDATE meta SET totals=?,revision=revision+1 WHERE id=1", JSON.stringify(totals));\n this.run("UPDATE conversation_totals SET totals=? WHERE id=?", JSON.stringify(conversation), root);\n }\n event(e) {\n const c = e.context, root = rootID(c), session = sessionID(c);\n this.ensureConversation(root);\n if (e.type === "sessionDeleted") {\n const owner = this.get("SELECT root FROM sessions WHERE id=?", session);\n if (owner && owner.root !== c.root) throw new HistoryInvalid("Conflicting session ownership");\n const isRoot = c.root === c.session;\n this.run("INSERT OR IGNORE INTO tombstones VALUES (?)", isRoot ? root : session);\n for (const table of ["history", "approvals", "reviews"])\n this.run(`DELETE FROM ${table} WHERE ${isRoot ? "root" : "session"}=?`, isRoot ? root : session);\n if (isRoot) this.run("DELETE FROM sessions WHERE scope=? AND root=?", c.scope, c.root);\n else this.run("DELETE FROM sessions WHERE id=?", session);\n this.revision(root);\n return;\n }\n const context = e.context, review = reviewID(context), entry = entryID(context);\n const deleted = this.get("SELECT id FROM tombstones WHERE id IN (?,?) LIMIT 1", root, session);\n if (!deleted) {\n const existingSession = this.get("SELECT root FROM sessions WHERE id=?", session);\n if (existingSession && existingSession.root !== c.root) throw new HistoryInvalid("Conflicting session ownership");\n this.run("INSERT OR IGNORE INTO sessions VALUES (?,?,?,?)", session, c.scope, c.root, c.session);\n const existing = this.get("SELECT context FROM reviews WHERE id=?", review);\n if (existing) {\n const prior = JSON.parse(existing.context);\n if (Object.keys(context).some((key) => prior[key] !== context[key])) throw new HistoryInvalid("Conflicting review identity");\n }\n const owner = this.get("SELECT root,session FROM history WHERE id=?", entry);\n if (owner && (owner.root !== root || owner.session !== session)) throw new HistoryInvalid("Conflicting permission ownership");\n const pendingOwner = this.get("SELECT root,session FROM reviews WHERE entry=? LIMIT 1", entry);\n if (pendingOwner && (pendingOwner.root !== root || pendingOwner.session !== session)) throw new HistoryInvalid("Conflicting permission ownership");\n this.run("INSERT OR IGNORE INTO reviews VALUES (?,?,?,?,?,NULL)", review, entry, root, session, JSON.stringify(context));\n }\n const delta = historyDelta(e);\n if (e.type === "attemptDispatched" || e.type === "attemptFinalized") {\n const attempt = opaque(review, e.attempt);\n if (!deleted) {\n this.run("INSERT OR IGNORE INTO attempts VALUES (?,?,NULL,NULL)", attempt, review);\n if (e.type === "attemptDispatched") this.run("UPDATE attempts SET dispatched=COALESCE(dispatched,?) WHERE id=?", JSON.stringify({ at: e.at, retry: e.retry }), attempt);\n else this.run("UPDATE attempts SET finalized=COALESCE(finalized,?) WHERE id=?", JSON.stringify({ at: e.at, usage: e.usage, reportedModel: e.reportedModel }), attempt);\n }\n this.contribute(opaque(e.type, attempt), delta, root);\n } else if (e.type === "reviewAccepted") {\n if (!deleted) this.run("UPDATE reviews SET accepted=COALESCE(accepted,?) WHERE id=?", JSON.stringify(e.accepted), review);\n this.contribute(opaque(e.type, review), delta, root);\n } else if (e.type === "approvalConfirmed" || e.type === "approvalDispatched" || e.type === "approvalSettled") {\n const id = opaque(entry, e.approval);\n if (!deleted) {\n const old = this.get("SELECT * FROM approvals WHERE id=?", id);\n if (old && (old.review !== review || "automatic" in e && old.automatic !== -1 && old.automatic !== Number(e.automatic)))\n throw new HistoryInvalid("Conflicting approval identity");\n const state = e.type === "approvalConfirmed" ? "confirmed" : e.type === "approvalSettled" ? e.result : "pending";\n if (!old || old.state !== "confirmed" && e.type !== "approvalDispatched") {\n this.run(\n "INSERT INTO approvals VALUES (?,?,?,?,?,?,?,?) ON CONFLICT(id) DO UPDATE SET state=excluded.state,automatic=excluded.automatic,at=excluded.at",\n id,\n entry,\n root,\n session,\n review,\n state,\n "automatic" in e ? Number(e.automatic) : old?.automatic ?? -1,\n e.at\n );\n } else if (old.automatic === -1 && "automatic" in e)\n this.run("UPDATE approvals SET automatic=? WHERE id=?", Number(e.automatic), id);\n this.outcome(entry);\n this.revision(root);\n }\n if (e.type === "approvalConfirmed") {\n this.contribute(opaque("approvalConfirmed", id), delta, root);\n }\n } else if ((e.type === "permissionResolved" || e.type === "permissionOutcome") && !deleted) {\n const old = this.get("SELECT * FROM history WHERE id=?", entry);\n if (e.type === "permissionOutcome" && !old) return;\n const replace = e.type === "permissionResolved" && (!old || e.payload.completedAt > old.completed || e.payload.completedAt === old.completed && Buffer.compare(Buffer.from(context.review), Buffer.from(old.tie)) >= 0);\n const prior = old?.resolution;\n const resolution = !prior || prior === "cancelled" ? e.outcome : e.outcome === "cancelled" ? prior : prior === "conflict" || prior === "rejected" !== (e.outcome === "rejected") ? "conflict" : prior === "auto" || e.outcome === "auto" ? "auto" : e.outcome;\n if (replace) {\n this.run(\n `INSERT INTO history VALUES (?,?,?,?,?,?,?,?,NULL,?,NULL,?) ON CONFLICT(id) DO UPDATE SET\n completed=excluded.completed,tie=excluded.tie,review=excluded.review,resolution=excluded.resolution,resolvedAt=excluded.resolvedAt`,\n entry,\n c.scope,\n root,\n session,\n context.permission,\n e.payload.completedAt,\n context.review,\n review,\n resolution,\n e.at\n );\n this.run("INSERT INTO payloads VALUES (?,?) ON CONFLICT(id) DO UPDATE SET body=excluded.body", entry, JSON.stringify(e.payload));\n } else this.run("UPDATE history SET resolution=? WHERE id=?", resolution, entry);\n this.outcome(entry);\n this.revision(root);\n }\n }\n outcome(entry) {\n const row = this.get("SELECT resolution FROM history WHERE id=?", entry);\n if (!row) return;\n const auto = this.get("SELECT review FROM approvals WHERE entry=? AND state=\'confirmed\' AND automatic=1 ORDER BY id LIMIT 1", entry);\n const manual = this.get("SELECT review FROM approvals WHERE entry=? AND state=\'confirmed\' AND automatic=0 ORDER BY id LIMIT 1", entry);\n const uncertain = this.get("SELECT id FROM approvals WHERE entry=? AND state IN (\'pending\',\'uncertain\') LIMIT 1", entry);\n const outcome = row.resolution === "conflict" || row.resolution === "rejected" && (auto || manual) ? null : row.resolution === "rejected" ? "rejected" : auto ? "auto" : manual ? "manual" : uncertain ? null : row.resolution;\n this.run("UPDATE history SET outcome=?,approving=? WHERE id=?", outcome, auto?.review ?? manual?.review ?? null, entry);\n }\n query(q) {\n validateQuery(q);\n return this.transaction(() => {\n if (q.type === "totals") {\n const row = this.get(`SELECT revision,CASE WHEN length(CAST(totals AS BLOB))<=${HISTORY_RECORD_BYTES.totals} THEN totals END AS totals FROM meta WHERE id=1`);\n if (!count2(row.revision)) throw new HistoryInvalid("Invalid aggregate revision");\n return {\n revision: row.revision,\n totals: this.parseTotals(row.totals),\n ...q.conversation ? { conversation: this.conversation(rootID(q.conversation)) } : {}\n };\n }\n if (q.type === "conversationTotals") {\n const revision2 = this.get("SELECT revision FROM meta WHERE id=1").revision;\n if (!count2(revision2)) throw new HistoryInvalid("Invalid aggregate revision");\n return { revision: revision2, ...this.conversation(rootID(q)) };\n }\n if (q.type === "sessions") {\n const rows = this.all(`SELECT CASE WHEN length(id)=64 THEN id END AS id,scope,\n CASE WHEN length(CAST(root AS BLOB))<=${HISTORY_TEXT_BYTES} THEN root END AS root,\n CASE WHEN length(CAST(session AS BLOB))<=${HISTORY_TEXT_BYTES} THEN session END AS session\n FROM sessions WHERE scope=? AND id>? ORDER BY id LIMIT ?`, q.scope, q.after ?? "", q.limit ?? 100);\n return { sessions: rows.map((row) => {\n validateScope(row);\n if (row.id !== sessionID(row) || row.scope !== q.scope) throw new HistoryInvalid("Invalid session index");\n return { scope: row.scope, root: row.root, session: row.session };\n }), after: rows.at(-1)?.id };\n }\n if (q.type === "session") {\n const row = this.get(`SELECT CASE WHEN length(CAST(scope AS BLOB))<=${HISTORY_TEXT_BYTES} THEN scope END AS scope,\n CASE WHEN length(CAST(session AS BLOB))<=${HISTORY_TEXT_BYTES} THEN session END AS session,\n CASE WHEN length(CAST(root AS BLOB))<=${HISTORY_TEXT_BYTES} THEN root END AS root\n FROM sessions WHERE id=?`, opaque(q.scope, q.session));\n if (!row) return {};\n validateScope(row);\n if (row.scope !== q.scope || row.session !== q.session) throw new HistoryInvalid("Invalid session ownership");\n return { context: { scope: row.scope, root: row.root, session: row.session } };\n }\n if (q.type === "resolution") {\n const entry = opaque(q.scope, q.permission), root2 = rootID(q), session = sessionID(q);\n const deleted = !!this.get("SELECT id FROM tombstones WHERE id IN (?,?) LIMIT 1", root2, session);\n const invalid2 = this.get(`SELECT 1 AS invalid FROM approvals WHERE entry=? AND\n (root!=? OR session!=? OR state NOT IN (\'pending\',\'confirmed\',\'uncertain\',\'not-sent\') OR automatic NOT IN (-1,0,1)\n OR (state=\'confirmed\' AND automatic=-1)) LIMIT 1`, entry, root2, session);\n const invalidReview = this.get("SELECT 1 AS invalid FROM reviews WHERE entry=? AND (root!=? OR session!=?) LIMIT 1", entry, root2, session);\n const history = this.get(`SELECT CASE WHEN length(root)=64 THEN root END AS root,\n CASE WHEN length(session)=64 THEN session END AS session,\n CASE WHEN length(CAST(resolution AS BLOB))<=16 THEN resolution END AS resolution FROM history WHERE id=?`, entry);\n if (invalid2 || invalidReview || history && (history.root !== root2 || history.session !== session || !["auto", "manual", "rejected", "cancelled", "conflict"].includes(history.resolution))) throw new HistoryInvalid("Invalid resolution ownership");\n const auto = !!this.get("SELECT id FROM approvals WHERE entry=? AND state=\'confirmed\' AND automatic=1 LIMIT 1", entry);\n const manual = !!this.get("SELECT id FROM approvals WHERE entry=? AND state=\'confirmed\' AND automatic=0 LIMIT 1", entry);\n const uncertain = !!this.get("SELECT id FROM approvals WHERE entry=? AND state IN (\'pending\',\'uncertain\') LIMIT 1", entry);\n const conflict = history?.resolution === "conflict" || history?.resolution === "rejected" && (auto || manual);\n const outcome = deleted || conflict ? void 0 : auto ? "auto" : manual ? "manual" : history?.resolution === "rejected" ? "rejected" : history?.resolution === "manual" && !uncertain ? "manual" : void 0;\n return { outcome, uncertain, conflict, deleted };\n }\n const root = rootID(q), revision = this.get("SELECT revision FROM roots WHERE id=?", root)?.revision ?? 0;\n if (this.get("SELECT id FROM tombstones WHERE id=?", root)) return { revision, total: 0, rank: 0, deleted: true };\n const base = "scope=? AND root=? AND outcome IS NOT NULL";\n const invalid = this.get(`SELECT id FROM history WHERE scope=? AND root=? AND\n (typeof(completed)!=\'integer\' OR completed<0 OR completed>8640000000000000 OR length(id)!=64 OR length(session)!=64\n OR length(review)!=64 OR typeof(permission)!=\'text\' OR length(CAST(permission AS BLOB))>${HISTORY_TEXT_BYTES}\n OR typeof(tie)!=\'text\' OR length(tie)=0 OR length(CAST(tie AS BLOB))>${HISTORY_TEXT_BYTES}\n OR length(CAST(resolution AS BLOB))>16 OR length(approving)>64\n OR outcome NOT IN (\'auto\',\'manual\',\'rejected\',\'cancelled\')) LIMIT 1`, q.scope, root);\n const invalidOwner = this.get(`SELECT h.id FROM history h LEFT JOIN reviews r ON r.id=h.review\n LEFT JOIN sessions s ON s.id=h.session WHERE h.scope=? AND h.root=? AND\n (r.id IS NULL OR r.entry!=h.id OR r.root!=h.root OR r.session!=h.session OR s.id IS NULL OR s.scope!=h.scope OR s.root!=?) LIMIT 1`, q.scope, root, q.root);\n if (invalid || invalidOwner || !count2(revision)) throw new HistoryInvalid("Invalid history index");\n const total = this.get(`SELECT count(*) AS n FROM history WHERE ${base}`, q.scope, root).n;\n if (!count2(total)) throw new HistoryInvalid("History count overflow");\n let selected = q.entry ? this.get(`SELECT * FROM history WHERE ${base} AND id=?`, q.scope, root, q.entry) : this.get(`SELECT * FROM history WHERE ${base} ORDER BY completed DESC,tie DESC,id DESC LIMIT 1`, q.scope, root);\n const adjacent = (row, newer) => this.get(`SELECT * FROM history WHERE ${base} AND (completed,tie,id) ${newer ? ">" : "<"} (?,?,?)\n ORDER BY completed ${newer ? "ASC" : "DESC"},tie ${newer ? "ASC" : "DESC"},id ${newer ? "ASC" : "DESC"} LIMIT 1`, q.scope, root, row.completed, row.tie, row.id);\n if (selected && q.direction) selected = adjacent(selected, q.direction === "newer") ?? selected;\n if (!selected && q.order) selected = adjacent(q.order, true) ?? adjacent(q.order, false);\n if (!selected) return { revision, total, rank: 0 };\n const s = selected;\n if (s.id !== opaque(q.scope, s.permission) || s.review !== opaque(q.scope, s.tie)) throw new HistoryInvalid("Invalid history ownership");\n const rank = this.get(`SELECT count(*) AS n FROM history WHERE ${base} AND (completed,tie,id)<=(?,?,?)`, q.scope, root, s.completed, s.tie, s.id).n;\n if (!count2(rank) || rank < 1 || rank > total) throw new HistoryInvalid("Invalid history rank");\n const result = { revision, total, rank, entry: s.id, order: { completed: s.completed, tie: s.tie, id: s.id }, older: adjacent(s, false)?.id, newer: adjacent(s, true)?.id };\n const contextRow = this.get(`SELECT CASE WHEN length(CAST(context AS BLOB))<=${HISTORY_RECORD_BYTES.context} THEN context END AS context FROM reviews WHERE id=? AND root=? AND session=?`, s.review, root, s.session);\n if (typeof contextRow?.context !== "string") throw new HistoryInvalid("Invalid history ownership");\n const context = JSON.parse(contextRow.context);\n validateReview(context);\n if (rootID(context) !== root || sessionID(context) !== s.session || entryID(context) !== s.id || reviewID(context) !== s.review) throw new HistoryInvalid("Invalid history ownership");\n result.session = context.session;\n try {\n const body = this.get(`SELECT CASE WHEN length(CAST(body AS BLOB))<=${HISTORY_RECORD_BYTES.payload} THEN body END AS body FROM payloads WHERE id=?`, s.id)?.body;\n if (typeof body !== "string" || Buffer.byteLength(body) > HISTORY_RECORD_BYTES.payload) throw new Error();\n const payload = validatePayload(JSON.parse(body));\n if (payload.completedAt !== s.completed) throw new Error();\n result.record = { context, payload, outcome: s.outcome, approvingReview: s.approving ?? void 0 };\n } catch {\n result.unreadable = true;\n }\n return result;\n }, false);\n }\n close() {\n this.db.close();\n }\n};\n\n// src/history-storage-worker.ts\nfunction privateDatabase(file) {\n if (!import_node_path2.default.isAbsolute(file) || import_node_path2.default.basename(file) !== "history-v1.sqlite") throw new HistoryInvalid("Invalid history path");\n const directory = import_node_path2.default.dirname(file);\n let prefix = directory;\n for (; ; ) {\n try {\n if ((0, import_node_fs.realpathSync)(prefix) !== prefix || !(0, import_node_fs.lstatSync)(prefix).isDirectory()) throw new HistoryInvalid("Invalid history directory path");\n break;\n } catch (error) {\n if (error.code !== "ENOENT") throw error;\n prefix = import_node_path2.default.dirname(prefix);\n }\n }\n (0, import_node_fs.mkdirSync)(directory, { mode: 448, recursive: true });\n const stat = (0, import_node_fs.lstatSync)(directory);\n if (!stat.isDirectory() || stat.isSymbolicLink() || (0, import_node_fs.realpathSync)(directory) !== directory || stat.mode & 63 || stat.uid !== process.getuid?.()) throw new HistoryInvalid("History directory must be private");\n for (const suffix of ["", "-wal", "-shm", "-journal"]) {\n const name = file + suffix;\n let fd;\n try {\n fd = (0, import_node_fs.openSync)(name, import_node_fs.constants.O_RDWR | import_node_fs.constants.O_NOFOLLOW | import_node_fs.constants.O_NONBLOCK);\n } catch (error) {\n if (error.code !== "ENOENT") throw error;\n if (suffix) continue;\n try {\n fd = (0, import_node_fs.openSync)(name, import_node_fs.constants.O_RDWR | import_node_fs.constants.O_CREAT | import_node_fs.constants.O_EXCL | import_node_fs.constants.O_NOFOLLOW, 384);\n } catch (error2) {\n if (error2.code !== "EEXIST") throw error2;\n fd = (0, import_node_fs.openSync)(name, import_node_fs.constants.O_RDWR | import_node_fs.constants.O_NOFOLLOW | import_node_fs.constants.O_NONBLOCK);\n }\n }\n try {\n const stat2 = (0, import_node_fs.fstatSync)(fd);\n if (!stat2.isFile() || stat2.nlink !== 1 || stat2.uid !== process.getuid?.() || stat2.mode & 63) throw new HistoryInvalid("History file must be private");\n (0, import_node_fs.fchmodSync)(fd, 384);\n } finally {\n (0, import_node_fs.closeSync)(fd);\n }\n }\n}\nfunction openHistoryDatabase(file, adapter) {\n privateDatabase(file);\n const db = adapter === "bun" ? new (require("bun:sqlite")).Database(file) : new (require("node:sqlite")).DatabaseSync(file);\n try {\n return new HistorySQL(db);\n } catch (error) {\n db.close();\n throw error;\n }\n}\nif (import_node_worker_threads.parentPort) {\n let sql;\n import_node_worker_threads.parentPort.on("message", (message) => {\n try {\n let value;\n if (message.type === "open") {\n if (sql) throw new HistoryInvalid("History worker already open");\n sql = openHistoryDatabase(message.file, message.adapter);\n value = { environmentKeys: Object.keys(process.env).length };\n } else if (message.type === "close") {\n sql?.close();\n sql = void 0;\n } else {\n if (!sql) throw new Error("History worker not open");\n if (message.type === "apply") value = sql.apply(message.writer, message.sequence, message.event);\n else if (message.type === "query") value = sql.query(message.query);\n else throw new HistoryInvalid("Invalid history worker operation");\n }\n import_node_worker_threads.parentPort.postMessage({ id: message.id, value });\n } catch (error) {\n import_node_worker_threads.parentPort.postMessage({ id: message.id, error: error instanceof HistoryInvalid ? "invalid" : "storage" });\n }\n });\n}\n// Annotate the CommonJS export names for ESM import in node:\n0 && (module.exports = {\n openHistoryDatabase,\n privateDatabase\n});\n' : "";
8012
+ var historyWorkerSource = true ? '"use strict";\nvar __create = Object.create;\nvar __defProp = Object.defineProperty;\nvar __getOwnPropDesc = Object.getOwnPropertyDescriptor;\nvar __getOwnPropNames = Object.getOwnPropertyNames;\nvar __getProtoOf = Object.getPrototypeOf;\nvar __hasOwnProp = Object.prototype.hasOwnProperty;\nvar __export = (target, all) => {\n for (var name in all)\n __defProp(target, name, { get: all[name], enumerable: true });\n};\nvar __copyProps = (to, from, except, desc) => {\n if (from && typeof from === "object" || typeof from === "function") {\n for (let key of __getOwnPropNames(from))\n if (!__hasOwnProp.call(to, key) && key !== except)\n __defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });\n }\n return to;\n};\nvar __toESM = (mod, isNodeMode, target) => (target = mod != null ? __create(__getProtoOf(mod)) : {}, __copyProps(\n // If the importer is in node compatibility mode or this is not an ESM\n // file that has been converted to a CommonJS file using a Babel-\n // compatible transform (i.e. "__esModule" has not been set), then set\n // "default" to the CommonJS "module.exports" for node compatibility.\n isNodeMode || !mod || !mod.__esModule ? __defProp(target, "default", { value: mod, enumerable: true }) : target,\n mod\n));\nvar __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);\n\n// src/history-storage-worker.ts\nvar history_storage_worker_exports = {};\n__export(history_storage_worker_exports, {\n openHistoryDatabase: () => openHistoryDatabase,\n privateDatabase: () => privateDatabase\n});\nmodule.exports = __toCommonJS(history_storage_worker_exports);\nvar import_node_worker_threads = require("node:worker_threads");\n\n// src/lifetime.ts\nvar emptyActivity = () => ({\n reviews: 0,\n usageRequests: 0,\n retries: 0,\n autoApproved: 0,\n timedReviews: 0,\n meanFullReportMs: 0,\n meanRatingMs: 0,\n since: null\n});\nvar empty = () => ({\n requests: 0,\n tokenRequests: 0,\n input: 0,\n output: 0,\n priced: 0,\n cost: 0,\n since: null,\n safe: 0,\n unsafe: 0,\n ratingsSince: null,\n activity: emptyActivity()\n});\nvar count = (value) => typeof value === "number" && Number.isSafeInteger(value) && value >= 0;\nvar amount = (value) => typeof value === "number" && Number.isFinite(value) && value >= 0;\nfunction validate(value) {\n if (!value || ![value.requests, value.tokenRequests, value.input, value.output, value.priced, value.safe, value.unsafe, value.safe + value.unsafe].every(count) || !amount(value.cost) || value.priced > value.requests || value.tokenRequests > value.requests || value.requests > value.tokenRequests + value.priced || !value.tokenRequests && (value.input !== 0 || value.output !== 0) || (value.requests > 0 ? !count(value.since) || value.since > 864e13 : value.since !== null || value.input !== 0 || value.output !== 0) || !value.priced && value.cost !== 0 || (value.safe + value.unsafe > 0 ? !count(value.ratingsSince) || value.ratingsSince > 864e13 : value.ratingsSince !== null)) {\n throw new Error("Invalid lifetime usage totals");\n }\n const activity = value.activity;\n if (!activity || ![activity.reviews, activity.usageRequests, activity.retries, activity.autoApproved, activity.timedReviews].every(count) || activity.reviews > value.safe + value.unsafe || activity.usageRequests > value.requests || activity.timedReviews > activity.reviews || ![activity.meanFullReportMs, activity.meanRatingMs].every(amount) || activity.meanRatingMs > activity.meanFullReportMs || !activity.timedReviews && (activity.meanFullReportMs !== 0 || activity.meanRatingMs !== 0) || (activity.reviews || activity.usageRequests || activity.retries || activity.autoApproved ? !count(activity.since) || activity.since > 864e13 : activity.since !== null)) throw new Error("Invalid lifetime activity totals");\n return value;\n}\nfunction mean(left, leftCount, right, rightCount) {\n if (!leftCount) return right;\n if (!rightCount) return left;\n return left + (right - left) * (rightCount / (leftCount + rightCount));\n}\nfunction add(left, right) {\n return validate({\n requests: left.requests + right.requests,\n input: left.input + right.input,\n output: left.output + right.output,\n tokenRequests: left.tokenRequests + right.tokenRequests,\n priced: left.priced + right.priced,\n cost: left.cost + right.cost,\n since: left.since === null ? right.since : right.since === null ? left.since : Math.min(left.since, right.since),\n safe: left.safe + right.safe,\n unsafe: left.unsafe + right.unsafe,\n ratingsSince: left.ratingsSince === null ? right.ratingsSince : right.ratingsSince === null ? left.ratingsSince : Math.min(left.ratingsSince, right.ratingsSince),\n activity: {\n reviews: left.activity.reviews + right.activity.reviews,\n usageRequests: left.activity.usageRequests + right.activity.usageRequests,\n retries: left.activity.retries + right.activity.retries,\n autoApproved: left.activity.autoApproved + right.activity.autoApproved,\n timedReviews: left.activity.timedReviews + right.activity.timedReviews,\n meanFullReportMs: mean(left.activity.meanFullReportMs, left.activity.timedReviews, right.activity.meanFullReportMs, right.activity.timedReviews),\n meanRatingMs: mean(left.activity.meanRatingMs, left.activity.timedReviews, right.activity.meanRatingMs, right.activity.timedReviews),\n since: left.activity.since === null ? right.activity.since : right.activity.since === null ? left.activity.since : Math.min(left.activity.since, right.activity.since)\n }\n });\n}\n\n// src/history-records.ts\nvar import_node_crypto = require("node:crypto");\nvar import_node_path = __toESM(require("node:path"), 1);\nvar HISTORY_EVENT_BYTES = 512 * 1024;\nvar HISTORY_QUEUE_BYTES = 64 * 1024 * 1024;\nvar HISTORY_TEXT_BYTES = 4096;\nvar HISTORY_DESCRIPTION_BYTES = 65536;\nvar stringBytes = (bytes) => 2 + 6 * bytes;\nvar objectBytes = (fields) => 2 + Object.entries(fields).reduce((sum, [key, bytes]) => sum + JSON.stringify(key).length + 1 + bytes + 1, 0);\nvar numberBytes = 24;\nvar textBytes = stringBytes(HISTORY_TEXT_BYTES);\nvar usageBytes = objectBytes({ input: numberBytes, output: numberBytes, cost: numberBytes });\nvar acceptedFields = {\n safe: 5,\n completedAt: numberBytes,\n reportedModel: textBytes,\n timing: objectBytes({ fullReportMs: numberBytes, ratingMs: numberBytes })\n};\nvar HISTORY_RECORD_BYTES = {\n context: objectBytes({\n scope: textBytes,\n root: textBytes,\n session: textBytes,\n permission: textBytes,\n review: textBytes,\n category: JSON.stringify("external_directory").length,\n configuredModel: textBytes,\n provider: textBytes\n }),\n accepted: objectBytes(acceptedFields),\n dispatched: objectBytes({ at: numberBytes, retry: JSON.stringify("transport").length }),\n finalized: objectBytes({ at: numberBytes, usage: usageBytes, reportedModel: textBytes }),\n payload: objectBytes({ ...acceptedFields, desc: stringBytes(HISTORY_DESCRIPTION_BYTES), usage: usageBytes }),\n totals: objectBytes({\n requests: numberBytes,\n tokenRequests: numberBytes,\n input: numberBytes,\n output: numberBytes,\n priced: numberBytes,\n cost: numberBytes,\n since: numberBytes,\n safe: numberBytes,\n unsafe: numberBytes,\n ratingsSince: numberBytes,\n activity: objectBytes({\n reviews: numberBytes,\n usageRequests: numberBytes,\n retries: numberBytes,\n autoApproved: numberBytes,\n timedReviews: numberBytes,\n meanFullReportMs: numberBytes,\n meanRatingMs: numberBytes,\n since: numberBytes\n })\n })\n};\nvar HistoryInvalid = class extends Error {\n};\nvar opaque = (...parts) => (0, import_node_crypto.createHash)("sha256").update(JSON.stringify(parts)).digest("hex");\nvar entryID = (c) => opaque(c.scope, c.permission);\nvar rootID = (c) => opaque(c.scope, c.root);\nvar sessionID = (c) => opaque(c.scope, c.session);\nvar reviewID = (c) => opaque(c.scope, c.review);\nvar count2 = (v) => typeof v === "number" && Number.isSafeInteger(v) && v >= 0;\nvar amount2 = (v) => typeof v === "number" && Number.isFinite(v) && v >= 0;\nfunction requireValue(ok) {\n if (!ok) throw new HistoryInvalid("Invalid history record");\n}\nfunction object(v, keys) {\n requireValue(v && typeof v === "object" && !Array.isArray(v));\n requireValue(Object.getPrototypeOf(v) === Object.prototype || Object.getPrototypeOf(v) === null);\n requireValue(Reflect.ownKeys(v).every((k) => {\n const descriptor = Object.getOwnPropertyDescriptor(v, k);\n return typeof k === "string" && keys.includes(k) && descriptor.enumerable && "value" in descriptor;\n }));\n}\nfunction text(v, max = HISTORY_TEXT_BYTES) {\n requireValue(typeof v === "string" && v.trim().length > 0 && Buffer.byteLength(v) <= max && !v.includes("\\0"));\n}\nfunction timestamp(v) {\n requireValue(count2(v) && v <= 864e13);\n}\nfunction validateScope(c) {\n text(c.scope);\n requireValue(import_node_path.default.isAbsolute(c.scope));\n text(c.root);\n text(c.session);\n}\nfunction validateReview(c) {\n object(c, ["scope", "root", "session", "permission", "review", "category", "configuredModel", "provider"]);\n validateScope(c);\n text(c.permission);\n text(c.review);\n text(c.configuredModel);\n text(c.provider);\n requireValue(["bash", "edit", "mcp", "custom", "external_directory", "skill"].includes(c.category));\n const url = new URL(c.provider);\n requireValue(["http:", "https:"].includes(url.protocol) && !url.username && !url.password && !url.search && !url.hash && url.href.replace(/\\/+$/, "") === c.provider);\n}\nfunction usage(v) {\n object(v, ["input", "output", "cost"]);\n requireValue(v.input === void 0 && v.output === void 0 || count2(v.input) && count2(v.output));\n requireValue(v.cost === void 0 || amount2(v.cost));\n requireValue(v.input !== void 0 || v.cost !== void 0);\n}\nfunction accepted(v, payload = false) {\n object(v, ["safe", "completedAt", "reportedModel", "timing", ...payload ? ["desc", "usage"] : []]);\n requireValue(typeof v.safe === "boolean");\n timestamp(v.completedAt);\n if (v.reportedModel !== void 0) text(v.reportedModel);\n if (v.timing !== void 0) {\n object(v.timing, ["fullReportMs", "ratingMs"]);\n requireValue(amount2(v.timing.fullReportMs) && amount2(v.timing.ratingMs) && v.timing.ratingMs <= v.timing.fullReportMs);\n }\n}\nfunction validatePayload(v) {\n accepted(v, true);\n requireValue(typeof v.desc === "string" && v.desc.trim().length > 0 && Buffer.byteLength(v.desc) <= HISTORY_DESCRIPTION_BYTES);\n if (v.usage !== void 0) usage(v.usage);\n return v;\n}\nfunction validateEvent(e) {\n const extra = {\n attemptDispatched: ["attempt", "retry"],\n attemptFinalized: ["attempt", "usage", "reportedModel"],\n reviewAccepted: ["accepted"],\n approvalDispatched: ["approval", "automatic"],\n approvalSettled: ["approval", "result"],\n approvalConfirmed: ["approval", "automatic"],\n permissionResolved: ["outcome", "payload"],\n permissionOutcome: ["outcome"],\n sessionDeleted: []\n };\n requireValue(e && Object.hasOwn(extra, e.type));\n object(e, ["type", "context", "at", ...extra[e.type]]);\n timestamp(e.at);\n if (e.type === "sessionDeleted") {\n object(e.context, ["scope", "root", "session"]);\n validateScope(e.context);\n return e;\n }\n validateReview(e.context);\n switch (e.type) {\n case "attemptDispatched":\n text(e.attempt);\n requireValue(["initial", "transport", "format"].includes(e.retry));\n break;\n case "attemptFinalized":\n text(e.attempt);\n if (e.usage !== void 0) usage(e.usage);\n if (e.reportedModel !== void 0) text(e.reportedModel);\n break;\n case "reviewAccepted":\n accepted(e.accepted);\n break;\n case "approvalDispatched":\n case "approvalConfirmed":\n text(e.approval);\n requireValue(typeof e.automatic === "boolean");\n break;\n case "approvalSettled":\n text(e.approval);\n requireValue(["not-sent", "uncertain"].includes(e.result));\n break;\n case "permissionResolved":\n requireValue(["auto", "manual", "rejected", "cancelled"].includes(e.outcome));\n validatePayload(e.payload);\n break;\n case "permissionOutcome":\n requireValue(["manual", "rejected"].includes(e.outcome));\n break;\n }\n return e;\n}\nfunction decodeEvent(text2) {\n requireValue(typeof text2 === "string" && Buffer.byteLength(text2) <= HISTORY_EVENT_BYTES);\n try {\n return validateEvent(JSON.parse(text2));\n } catch {\n throw new HistoryInvalid("Invalid history event");\n }\n}\nfunction validateQuery(q) {\n requireValue(q && ["totals", "conversationTotals", "history", "sessions", "resolution", "session"].includes(q.type));\n if (q.type === "totals") {\n object(q, ["type", "conversation"]);\n if (q.conversation !== void 0) {\n object(q.conversation, ["scope", "root"]);\n text(q.conversation.scope);\n text(q.conversation.root);\n requireValue(import_node_path.default.isAbsolute(q.conversation.scope));\n }\n return;\n }\n text(q.scope);\n requireValue(import_node_path.default.isAbsolute(q.scope));\n if (q.type === "conversationTotals") {\n object(q, ["type", "scope", "root"]);\n text(q.root);\n return;\n }\n if (q.type === "session") {\n object(q, ["type", "scope", "session"]);\n text(q.session);\n return;\n }\n if (q.type === "resolution") {\n object(q, ["type", "scope", "root", "session", "permission"]);\n validateScope(q);\n text(q.permission);\n return;\n }\n if (q.type === "sessions") {\n object(q, ["type", "scope", "after", "limit"]);\n if (q.after !== void 0) text(q.after);\n requireValue(q.limit === void 0 || count2(q.limit) && q.limit > 0 && q.limit <= 100);\n } else {\n object(q, ["type", "scope", "root", "entry", "direction", "order"]);\n text(q.root);\n if (q.order !== void 0) {\n object(q.order, ["completed", "tie", "id"]);\n timestamp(q.order.completed);\n text(q.order.tie);\n requireValue(q.entry && q.order.id === q.entry && /^[a-f0-9]{64}$/.test(q.order.id));\n }\n if (q.entry !== void 0) requireValue(/^[a-f0-9]{64}$/.test(q.entry));\n requireValue(q.direction === void 0 || q.entry && ["older", "newer"].includes(q.direction));\n }\n}\n\n// src/history-statistics.ts\nfunction historyDelta(e) {\n const delta = empty(), activity = delta.activity;\n if (e.type === "attemptDispatched" && e.retry !== "initial") {\n activity.retries = 1;\n activity.since = e.at;\n } else if (e.type === "attemptFinalized" && e.usage) {\n delta.requests = activity.usageRequests = 1;\n delta.since = activity.since = e.at;\n if (e.usage.input !== void 0) {\n delta.tokenRequests = 1;\n delta.input = e.usage.input;\n delta.output = e.usage.output;\n }\n if (e.usage.cost !== void 0) {\n delta.priced = 1;\n delta.cost = e.usage.cost;\n }\n } else if (e.type === "reviewAccepted") {\n delta.safe = e.accepted.safe ? 1 : 0;\n delta.unsafe = e.accepted.safe ? 0 : 1;\n delta.ratingsSince = e.accepted.completedAt;\n activity.reviews = 1;\n activity.since = e.accepted.completedAt;\n if (e.accepted.timing) {\n activity.timedReviews = 1;\n activity.meanFullReportMs = e.accepted.timing.fullReportMs;\n activity.meanRatingMs = e.accepted.timing.ratingMs;\n }\n } else if (e.type === "approvalConfirmed" && e.automatic) {\n activity.autoApproved = 1;\n activity.since = e.at;\n }\n return delta;\n}\n\n// src/history-migration.ts\nvar HISTORY_BOOTSTRAP_STEPS = 128;\nvar HISTORY_BOOTSTRAP_MS = 40;\nvar migrationSchema = `CREATE TABLE IF NOT EXISTS conversation_bootstrap (\n id TEXT PRIMARY KEY, review TEXT NOT NULL, phase INTEGER NOT NULL, after TEXT NOT NULL,\n totals TEXT NOT NULL, partial INTEGER NOT NULL CHECK(partial IN (0,1)));`;\nfunction bootstrap(db, root) {\n if (!/^[a-f0-9]{64}$/.test(root)) throw new HistoryInvalid("Invalid bootstrap root");\n const get = (sql, ...args) => db.prepare(sql).get(...args);\n const run = (sql, ...args) => db.prepare(sql).run(...args);\n if (get("SELECT id FROM conversation_totals WHERE id=?", root)) return { done: true, steps: 0 };\n const saved = get(`SELECT review,phase,after,partial,CASE WHEN length(CAST(totals AS BLOB))<=${HISTORY_RECORD_BYTES.totals} THEN totals END AS totals\n FROM conversation_bootstrap WHERE id=?`, root);\n let review = saved?.review ?? "", phase = saved?.phase ?? 0, after = saved?.after ?? "";\n let totals = saved ? validate(JSON.parse(saved.totals)) : empty();\n let partial = saved?.partial ?? Number(!!get("SELECT id FROM roots WHERE id=?", root));\n if (![0, 1, 2].includes(phase) || ![0, 1].includes(partial) || !/^(?:[a-f0-9]{64})?$/.test(review) || !/^(?:[a-f0-9]{64})?$/.test(after)) throw new HistoryInvalid("Invalid bootstrap cursor");\n const include = (event) => {\n validateEvent(event);\n totals = add(totals, historyDelta(event));\n };\n const end = performance.now() + HISTORY_BOOTSTRAP_MS;\n let steps = 0, done = false;\n do {\n steps++;\n if (phase === 0) {\n const row2 = get(`SELECT id,CASE WHEN length(CAST(context AS BLOB))<=${HISTORY_RECORD_BYTES.context} THEN context END AS context,\n CASE WHEN length(CAST(accepted AS BLOB))<=${HISTORY_RECORD_BYTES.accepted} THEN accepted END AS accepted,accepted IS NOT NULL AS hasAccepted\n FROM reviews WHERE root=? AND id>? ORDER BY id LIMIT 1`, root, review);\n if (!row2) {\n done = true;\n break;\n }\n const context2 = JSON.parse(row2.context);\n validateReview(context2);\n if (rootID(context2) !== root || reviewID(context2) !== row2.id) throw new HistoryInvalid("Invalid conversation ownership");\n if (row2.hasAccepted) {\n const accepted2 = JSON.parse(row2.accepted);\n include({ type: "reviewAccepted", context: context2, at: accepted2?.completedAt, accepted: accepted2 });\n }\n partial = 1;\n review = row2.id;\n phase = 1;\n after = "";\n continue;\n }\n const row = get(`SELECT CASE WHEN length(CAST(context AS BLOB))<=${HISTORY_RECORD_BYTES.context} THEN context END AS context\n FROM reviews WHERE id=? AND root=?`, review, root);\n const context = JSON.parse(row?.context);\n validateReview(context);\n if (rootID(context) !== root || reviewID(context) !== review) throw new HistoryInvalid("Invalid conversation ownership");\n if (phase === 1) {\n const fact = get(`SELECT id,CASE WHEN length(CAST(dispatched AS BLOB))<=${HISTORY_RECORD_BYTES.dispatched} THEN dispatched END AS dispatched,\n CASE WHEN length(CAST(finalized AS BLOB))<=${HISTORY_RECORD_BYTES.finalized} THEN finalized END AS finalized,\n dispatched IS NOT NULL AS hasDispatched,finalized IS NOT NULL AS hasFinalized\n FROM attempts WHERE review=? AND id>? ORDER BY id LIMIT 1`, review, after);\n if (!fact) {\n phase = 2;\n after = "";\n continue;\n }\n if (!/^[a-f0-9]{64}$/.test(fact.id)) throw new HistoryInvalid("Invalid conversation attempt");\n if (fact.hasDispatched) include({ ...JSON.parse(fact.dispatched), type: "attemptDispatched", context, attempt: fact.id });\n if (fact.hasFinalized) include({ ...JSON.parse(fact.finalized), type: "attemptFinalized", context, attempt: fact.id });\n after = fact.id;\n } else {\n const fact = get("SELECT id,root,session,at,state,automatic FROM approvals WHERE review=? AND id>? ORDER BY id LIMIT 1", review, after);\n if (!fact) {\n phase = 0;\n after = "";\n continue;\n }\n if (!/^[a-f0-9]{64}$/.test(fact.id) || fact.root !== root || fact.session !== sessionID(context) || ![-1, 0, 1].includes(fact.automatic) || !["pending", "uncertain", "not-sent", "confirmed"].includes(fact.state)) throw new HistoryInvalid("Invalid conversation approval");\n if (fact.state === "confirmed") {\n if (fact.automatic === -1) throw new HistoryInvalid("Invalid conversation approval");\n include({ type: "approvalConfirmed", context, at: fact.at, approval: fact.id, automatic: fact.automatic === 1 });\n }\n after = fact.id;\n }\n } while (steps < HISTORY_BOOTSTRAP_STEPS && performance.now() < end);\n if (done) {\n run("INSERT INTO conversation_totals VALUES (?,?,?)", root, JSON.stringify(totals), partial);\n run("DELETE FROM conversation_bootstrap WHERE id=?", root);\n } else run(\n `INSERT INTO conversation_bootstrap VALUES (?,?,?,?,?,?) ON CONFLICT(id) DO UPDATE SET\n review=excluded.review,phase=excluded.phase,after=excluded.after,totals=excluded.totals,partial=excluded.partial`,\n root,\n review,\n phase,\n after,\n JSON.stringify(totals),\n partial\n );\n return { done, steps };\n}\n\n// src/history-browsing.ts\nvar browsingSchema = `\nCREATE TABLE IF NOT EXISTS history_generations (id TEXT PRIMARY KEY, generation INTEGER NOT NULL);\nCREATE INDEX IF NOT EXISTS history_session_visible_order ON history(scope,root,session,completed,tie,id) WHERE outcome IS NOT NULL;\n${["history", "reviews"].flatMap((table) => ["INSERT", "UPDATE", "DELETE"].map((operation) => {\n const columns = table === "reviews" && operation === "UPDATE" ? " OF id,entry,root,session" : "";\n const roots = operation === "INSERT" ? ["new"] : operation === "DELETE" ? ["old"] : ["old", "new"];\n return `DROP TRIGGER IF EXISTS browse_${table}_${operation};\n CREATE TRIGGER browse_${table}_${operation} AFTER ${operation}${columns} ON ${table} BEGIN\n ${roots.map((row) => `INSERT INTO history_generations SELECT ${row}.root,1\n WHERE NOT EXISTS (SELECT 1 FROM tombstones WHERE id IN (${row}.root,${row}.session))\n ON CONFLICT(id) DO UPDATE SET generation=generation+1;`).join("\\n")}\n END;`;\n})).join("\\n")}\n${["INSERT", "UPDATE", "DELETE"].map((operation) => {\n const rows = operation === "INSERT" ? ["new"] : operation === "DELETE" ? ["old"] : ["old", "new"];\n return `DROP TRIGGER IF EXISTS browse_sessions_${operation};\n CREATE TRIGGER browse_sessions_${operation} AFTER ${operation} ON sessions BEGIN\n ${rows.map((row) => `INSERT INTO history_generations SELECT DISTINCT root,1 FROM history WHERE session=${row}.id\n AND NOT EXISTS (SELECT 1 FROM tombstones WHERE id IN (history.root,history.session))\n ON CONFLICT(id) DO UPDATE SET generation=generation+1;`).join("\\n")}\n END;`;\n}).join("\\n")}\n${["INSERT", "UPDATE", "DELETE"].map((operation) => {\n const rows = operation === "INSERT" ? ["new"] : operation === "DELETE" ? ["old"] : ["old", "new"];\n return `CREATE TRIGGER IF NOT EXISTS browse_tombstones_${operation} AFTER ${operation} ON tombstones BEGIN\n ${rows.map((row) => `INSERT INTO history_generations VALUES (${row}.id,1)\n ON CONFLICT(id) DO UPDATE SET generation=generation+1;\n INSERT INTO history_generations SELECT root,1 FROM (SELECT root FROM history WHERE session=${row}.id LIMIT 1) WHERE 1\n ON CONFLICT(id) DO UPDATE SET generation=generation+1;`).join("\\n")}\n END;`;\n}).join("\\n")}`;\nvar HistoryBrowsing = class {\n roots = /* @__PURE__ */ new Map();\n get(root, generation) {\n const saved = this.roots.get(root);\n if (saved?.generation !== generation) {\n this.roots.delete(root);\n return void 0;\n }\n this.roots.delete(root);\n this.roots.set(root, saved);\n return saved;\n }\n save(root, generation, total) {\n const saved = { generation, total, ranks: /* @__PURE__ */ new Map(), neighbors: /* @__PURE__ */ new Map() };\n if (this.roots.size >= 32) this.roots.delete(this.roots.keys().next().value);\n this.roots.set(root, saved);\n return saved;\n }\n rank(index, entry, rank) {\n if (index.ranks.size >= 128) index.ranks.delete(index.ranks.keys().next().value);\n index.ranks.set(entry, rank);\n }\n neighbor(index, key, entry) {\n if (index.neighbors.size >= 128) index.neighbors.delete(index.neighbors.keys().next().value);\n index.neighbors.set(key, entry);\n }\n};\n\n// src/history-legacy-scrub.ts\nvar HISTORY_SCRUB_STEPS = 8;\nvar HISTORY_SCRUB_BYTES = 64 * 1024;\nvar HISTORY_SCRUB_MS = 40;\nvar legacyScrubSchema = `\n CREATE TABLE IF NOT EXISTS history_legacy_scrub (id INTEGER PRIMARY KEY CHECK(id=1), phase INTEGER NOT NULL CHECK(phase IN (0,1,2)));\n CREATE TABLE IF NOT EXISTS history_legacy_padding (id INTEGER PRIMARY KEY, value BLOB NOT NULL);\n INSERT OR IGNORE INTO history_legacy_scrub SELECT 1,0 FROM history_privacy WHERE id=1 AND legacy=1;`;\nfunction scrubLegacyFreelist(db) {\n const state = db.prepare("SELECT phase FROM history_legacy_scrub WHERE id=1").get();\n if (!state || state.phase === 2) return false;\n if (state.phase !== 0 && state.phase !== 1) throw new HistoryInvalid("Invalid history scrub phase");\n const free = db.prepare("PRAGMA freelist_count");\n const reserve = db.prepare("INSERT INTO history_legacy_padding(value) VALUES (zeroblob(?))");\n const first = db.prepare("SELECT id FROM history_legacy_padding ORDER BY id LIMIT 1");\n const release = db.prepare("DELETE FROM history_legacy_padding WHERE id=?");\n const phase = db.prepare("UPDATE history_legacy_scrub SET phase=? WHERE id=1");\n const end = performance.now() + HISTORY_SCRUB_MS;\n for (let step = 0; step < HISTORY_SCRUB_STEPS; step++) {\n if (state.phase === 0) {\n const before = free.get().freelist_count;\n if (!Number.isSafeInteger(before) || before < 0) throw new HistoryInvalid("Invalid history freelist");\n if (before === 0) {\n phase.run(1);\n break;\n }\n reserve.run(HISTORY_SCRUB_BYTES);\n if (free.get().freelist_count >= before) throw new HistoryInvalid("History scrub made no progress");\n } else {\n const row = first.get();\n if (!row) {\n phase.run(2);\n break;\n }\n release.run(row.id);\n }\n if (performance.now() >= end) break;\n }\n db.exec("UPDATE history_privacy SET generation=generation+1 WHERE id=1");\n return true;\n}\n\n// src/history-legacy-rebuild.ts\nvar tables = [\n "meta",\n "writers",\n "contributions",\n "tombstones",\n "roots",\n "sessions",\n "reviews",\n "attempts",\n "approvals",\n "history",\n "payloads",\n "conversation_totals",\n "conversation_bootstrap",\n "history_generations",\n "history_privacy",\n "history_deletions"\n];\nvar shadow = "history_legacy_copy";\nvar retired = "history_legacy_retired";\nvar prefix = "history_rebuilt_";\nvar mirrors = ["history_legacy_insert", "history_legacy_update", "history_legacy_delete"];\nvar quote = (name) => `"${name.replaceAll(\'"\', \'""\')}"`;\nvar legacyRebuildSchema = `CREATE TABLE IF NOT EXISTS history_legacy_rebuild (\n id INTEGER PRIMARY KEY CHECK(id=1), position INTEGER NOT NULL, phase INTEGER NOT NULL,\n cursor INTEGER, ceiling INTEGER);\n INSERT OR IGNORE INTO history_legacy_rebuild SELECT 1,0,0,NULL,NULL FROM history_privacy WHERE id=1 AND legacy=1;`;\nfunction historySchema(db, sql) {\n db.exec(sql.replace(/CREATE INDEX IF NOT EXISTS (\\w+) ON (\\w+)\\([^;]+;/g, (statement, name, table) => db.prepare("SELECT 1 FROM sqlite_schema WHERE type=\'index\' AND name=? AND tbl_name=?").get(prefix + name, table) ? "" : statement));\n}\nfunction rebuildLegacyPages(db) {\n const get = (sql, ...args) => db.prepare(sql).get(...args);\n const state = get("SELECT position,phase,cursor,ceiling FROM history_legacy_rebuild WHERE id=1");\n if (!state || state.position === tables.length) return false;\n if (!Number.isInteger(state.position) || state.position < 0 || state.position >= tables.length || ![0, 1, 2].includes(state.phase))\n throw new HistoryInvalid("Invalid history rebuild phase");\n const table = tables[state.position], name = quote(table);\n if (state.phase === 0) {\n const definition = get("SELECT sql FROM sqlite_schema WHERE type=\'table\' AND name=?", table)?.sql;\n if (typeof definition !== "string" || !/^CREATE TABLE (?:"\\w+"|\\w+)\\s*\\(/i.test(definition)) throw new HistoryInvalid("Invalid history rebuild table");\n db.exec(definition.replace(/^CREATE TABLE (?:"\\w+"|\\w+)/i, `CREATE TABLE ${shadow}`));\n for (const index of db.prepare("SELECT name,sql FROM sqlite_schema WHERE type=\'index\' AND tbl_name=? AND sql IS NOT NULL").all(table)) {\n if (!/^CREATE (?:UNIQUE )?INDEX \\w+ ON (?:"\\w+"|\\w+)/i.test(index.sql)) throw new HistoryInvalid("Invalid history rebuild index");\n db.exec(index.sql.replace(/^(CREATE (?:UNIQUE )?INDEX) \\w+ ON (?:"\\w+"|\\w+)/i, `$1 ${quote(prefix + index.name)} ON ${shadow}`));\n }\n const columns = db.prepare(`PRAGMA table_info(${name})`).all().map((row) => quote(row.name));\n const insert = `INSERT INTO ${shadow}(rowid,${columns.join(",")}) VALUES (new.rowid,${columns.map((column) => `new.${column}`).join(",")});`;\n db.exec(`CREATE TRIGGER ${mirrors[0]} AFTER INSERT ON ${name} BEGIN ${insert} END;\n CREATE TRIGGER ${mirrors[1]} AFTER UPDATE ON ${name} BEGIN DELETE FROM ${shadow} WHERE rowid=old.rowid; ${insert} END;\n CREATE TRIGGER ${mirrors[2]} AFTER DELETE ON ${name} BEGIN DELETE FROM ${shadow} WHERE rowid=old.rowid; END;`);\n const ceiling = get(`SELECT rowid AS n FROM ${name} ORDER BY rowid DESC LIMIT 1`)?.n ?? null;\n db.prepare("UPDATE history_legacy_rebuild SET phase=1,ceiling=? WHERE id=1").run(ceiling);\n } else if (state.phase === 1) {\n const end = performance.now() + HISTORY_SCRUB_MS;\n const next = db.prepare(`SELECT rowid AS n FROM ${name} WHERE rowid<=? ${state.cursor === null ? "" : "AND rowid>?"} ORDER BY rowid LIMIT 1`);\n const columns = db.prepare(`PRAGMA table_info(${name})`).all().map((row) => quote(row.name));\n const copy = db.prepare(`INSERT INTO ${shadow}(rowid,${columns.join(",")}) SELECT rowid,* FROM ${name} WHERE rowid=? ON CONFLICT(rowid) DO NOTHING`);\n let cursor = state.cursor;\n for (let step = 0; step < HISTORY_SCRUB_STEPS; step++) {\n const row = cursor === state.cursor ? next.get(state.ceiling, ...cursor === null ? [] : [cursor]) : get(`SELECT rowid AS n FROM ${name} WHERE rowid<=? AND rowid>? ORDER BY rowid LIMIT 1`, state.ceiling, cursor);\n if (!row) {\n for (const trigger of mirrors) db.exec(`DROP TRIGGER ${trigger}`);\n const triggers = db.prepare("SELECT name,sql FROM sqlite_schema WHERE type=\'trigger\' AND tbl_name=?").all(table);\n for (const trigger of triggers) db.exec(`DROP TRIGGER ${quote(trigger.name)}`);\n db.exec(`ALTER TABLE ${name} RENAME TO ${retired}; ALTER TABLE ${shadow} RENAME TO ${name};`);\n for (const trigger of triggers) db.exec(trigger.sql);\n db.exec("UPDATE history_legacy_rebuild SET phase=2 WHERE id=1");\n break;\n }\n copy.run(row.n);\n cursor = row.n;\n db.prepare("UPDATE history_legacy_rebuild SET cursor=? WHERE id=1").run(cursor);\n if (performance.now() >= end) break;\n }\n } else {\n const end = performance.now() + HISTORY_SCRUB_MS;\n for (let step = 0; step < HISTORY_SCRUB_STEPS; step++) {\n const row = get(`SELECT rowid AS n FROM ${retired} ORDER BY rowid LIMIT 1`);\n if (!row) {\n db.exec(`DROP TABLE ${retired}`);\n db.exec("UPDATE history_legacy_rebuild SET position=position+1,phase=0,cursor=NULL,ceiling=NULL WHERE id=1");\n if (state.position + 1 === tables.length) db.exec("UPDATE history_privacy SET legacy=0 WHERE id=1");\n break;\n }\n db.prepare(`DELETE FROM ${retired} WHERE rowid=?`).run(row.n);\n if (performance.now() >= end) break;\n }\n }\n db.exec("UPDATE history_privacy SET generation=generation+1 WHERE id=1");\n return true;\n}\n\n// src/history-privacy.ts\nfunction privacySchema(db, existing) {\n db.exec(`CREATE TABLE IF NOT EXISTS history_privacy (\n id INTEGER PRIMARY KEY CHECK(id=1), generation INTEGER NOT NULL, cleaned INTEGER NOT NULL,\n legacy INTEGER NOT NULL CHECK(legacy IN (0,1)));\n CREATE TABLE IF NOT EXISTS history_deletions (id TEXT PRIMARY KEY, scope TEXT NOT NULL, root TEXT NOT NULL, session TEXT NOT NULL);\n INSERT OR IGNORE INTO history_privacy VALUES (1,1,0,${Number(existing)});\n CREATE TRIGGER IF NOT EXISTS history_privacy_delete AFTER DELETE ON payloads BEGIN\n UPDATE history_privacy SET generation=generation+1 WHERE id=1; END;\n CREATE TRIGGER IF NOT EXISTS history_privacy_replace AFTER UPDATE OF body ON payloads BEGIN\n UPDATE history_privacy SET generation=generation+1 WHERE id=1; END;`);\n db.exec(legacyScrubSchema);\n db.exec(legacyRebuildSchema);\n}\nfunction cleanupHistory(db) {\n const before = db.prepare("SELECT generation,cleaned,legacy FROM history_privacy WHERE id=1").get();\n if (before.generation === before.cleaned) return { pending: false, legacy: !!before.legacy };\n db.exec("PRAGMA busy_timeout=0");\n try {\n const checkpoint = db.prepare("PRAGMA wal_checkpoint(TRUNCATE)").get();\n if (checkpoint.busy !== 0) return { pending: true, legacy: !!before.legacy };\n db.prepare("UPDATE history_privacy SET cleaned=? WHERE id=1 AND generation=?").run(before.generation, before.generation);\n const after = db.prepare("SELECT generation,cleaned,legacy FROM history_privacy WHERE id=1").get();\n return { pending: after.generation !== after.cleaned, legacy: !!after.legacy };\n } finally {\n db.exec("PRAGMA busy_timeout=250");\n }\n}\n\n// src/history-deletion.ts\nvar HISTORY_DELETE_STEPS = 128;\nvar HISTORY_DELETE_MS = 40;\nfunction deleteHistory(db, context) {\n const root = rootID(context), session = sessionID(context), isRoot = context.root === context.session;\n const owner = db.prepare("SELECT root FROM sessions WHERE id=?").get(session);\n if (owner && owner.root !== context.root) throw new HistoryInvalid("Conflicting session ownership");\n db.prepare("INSERT OR IGNORE INTO tombstones VALUES (?)").run(isRoot ? root : session);\n db.prepare("INSERT OR IGNORE INTO history_deletions VALUES (?,?,?,?)").run(session, context.scope, context.root, context.session);\n db.exec("UPDATE history_privacy SET generation=generation+1 WHERE id=1");\n const key = isRoot ? root : session, column = isRoot ? "root" : "session";\n const history = db.prepare(`SELECT id FROM history WHERE ${column}=? LIMIT 1`);\n const approval = db.prepare(`SELECT id FROM approvals WHERE ${column}=? LIMIT 1`);\n const review = db.prepare(`SELECT id FROM reviews WHERE ${column}=? LIMIT 1`);\n const attempt = db.prepare("SELECT id FROM attempts WHERE review=? LIMIT 1");\n const removeHistory = db.prepare("DELETE FROM history WHERE id=?");\n const removeApproval = db.prepare("DELETE FROM approvals WHERE id=?");\n const removeReview = db.prepare("DELETE FROM reviews WHERE id=?");\n const removeAttempt = db.prepare("DELETE FROM attempts WHERE id=?");\n const nextSession = db.prepare(isRoot ? "SELECT id FROM sessions WHERE scope=? AND root=? LIMIT 1" : "SELECT id FROM sessions WHERE id=?");\n const removeSession = db.prepare("DELETE FROM sessions WHERE id=?");\n const end = performance.now() + HISTORY_DELETE_MS;\n for (let step = 0; step < HISTORY_DELETE_STEPS; step++) {\n const h = history.get(key);\n if (h) removeHistory.run(h.id);\n else {\n const a = approval.get(key);\n if (a) removeApproval.run(a.id);\n else {\n const r = review.get(key);\n if (r) {\n const a2 = attempt.get(r.id);\n if (a2) removeAttempt.run(a2.id);\n else removeReview.run(r.id);\n } else {\n const s = isRoot ? nextSession.get(context.scope, context.root) : nextSession.get(session);\n if (s) removeSession.run(s.id);\n else {\n db.prepare("DELETE FROM history_deletions WHERE id=?").run(session);\n return true;\n }\n }\n }\n }\n if (performance.now() >= end) break;\n }\n return false;\n}\n\n// src/history-schema.ts\nvar schema = `\nCREATE TABLE meta (id INTEGER PRIMARY KEY CHECK(id=1), version INTEGER NOT NULL, revision INTEGER NOT NULL, totals TEXT NOT NULL);\nCREATE TABLE writers (id TEXT PRIMARY KEY, sequence INTEGER NOT NULL);\nCREATE TABLE contributions (id TEXT PRIMARY KEY);\nCREATE TABLE tombstones (id TEXT PRIMARY KEY);\nCREATE TABLE roots (id TEXT PRIMARY KEY, revision INTEGER NOT NULL);\nCREATE TABLE sessions (id TEXT PRIMARY KEY, scope TEXT NOT NULL, root TEXT NOT NULL, session TEXT NOT NULL);\nCREATE INDEX sessions_scope ON sessions(scope,id);\nCREATE TABLE reviews (id TEXT PRIMARY KEY, entry TEXT NOT NULL, root TEXT NOT NULL, session TEXT NOT NULL, context TEXT NOT NULL, accepted TEXT);\nCREATE INDEX reviews_root ON reviews(root);\nCREATE INDEX reviews_session ON reviews(session);\nCREATE INDEX reviews_entry ON reviews(entry);\nCREATE TABLE attempts (id TEXT PRIMARY KEY, review TEXT NOT NULL REFERENCES reviews(id) ON DELETE CASCADE, dispatched TEXT, finalized TEXT);\nCREATE TABLE approvals (id TEXT PRIMARY KEY, entry TEXT NOT NULL, root TEXT NOT NULL, session TEXT NOT NULL, review TEXT NOT NULL, state TEXT NOT NULL, automatic INTEGER NOT NULL, at INTEGER NOT NULL);\nCREATE INDEX approvals_entry ON approvals(entry);\nCREATE TABLE history (id TEXT PRIMARY KEY, scope TEXT NOT NULL, root TEXT NOT NULL, session TEXT NOT NULL, permission TEXT NOT NULL,\n completed INTEGER NOT NULL, tie TEXT NOT NULL, review TEXT NOT NULL, outcome TEXT, resolution TEXT NOT NULL, approving TEXT, resolvedAt INTEGER NOT NULL,\n UNIQUE(scope,permission));\nCREATE INDEX history_order ON history(scope,root,completed,tie,id);\nCREATE INDEX history_session ON history(session);\nCREATE TABLE payloads (id TEXT PRIMARY KEY REFERENCES history(id) ON DELETE CASCADE, body TEXT NOT NULL);\n`;\nvar HistorySQL = class {\n constructor(db) {\n this.db = db;\n db.exec("PRAGMA busy_timeout=250; PRAGMA secure_delete=ON; PRAGMA foreign_keys=ON; PRAGMA journal_mode=WAL; PRAGMA synchronous=FULL; PRAGMA wal_autocheckpoint=256");\n if (db.prepare("PRAGMA secure_delete").get()?.secure_delete !== 1) throw new HistoryInvalid("Secure history deletion unavailable");\n this.transaction(() => {\n const tables2 = db.prepare("SELECT name FROM sqlite_master WHERE type=\'table\' AND name NOT LIKE \'sqlite_%\'").all();\n if (!tables2.length) {\n db.exec(schema);\n db.prepare("INSERT INTO meta VALUES (1,1,0,?)").run(JSON.stringify(empty()));\n }\n const meta = db.prepare(`SELECT version,revision,CASE WHEN length(CAST(totals AS BLOB))<=${HISTORY_RECORD_BYTES.totals} THEN totals END AS totals FROM meta WHERE id=1`).get();\n if (meta?.version !== 1 || !count2(meta.revision)) throw new HistoryInvalid("Unsupported history schema");\n this.parseTotals(meta.totals);\n historySchema(db, `CREATE INDEX IF NOT EXISTS sessions_root ON sessions(scope,root);\n CREATE INDEX IF NOT EXISTS attempts_review ON attempts(review);\n CREATE INDEX IF NOT EXISTS approvals_root ON approvals(root);\n CREATE INDEX IF NOT EXISTS approvals_session ON approvals(session);\n CREATE INDEX IF NOT EXISTS history_root ON history(root);`);\n historySchema(db, `CREATE TABLE IF NOT EXISTS conversation_totals (id TEXT PRIMARY KEY, totals TEXT NOT NULL, partial INTEGER NOT NULL CHECK(partial IN (0,1)));\n CREATE INDEX IF NOT EXISTS reviews_root_id ON reviews(root,id);\n CREATE INDEX IF NOT EXISTS attempts_review_id ON attempts(review,id);\n CREATE INDEX IF NOT EXISTS approvals_review_id ON approvals(review,id);\n CREATE INDEX IF NOT EXISTS history_visible_order ON history(scope,root,completed,tie,id) WHERE outcome IS NOT NULL;`);\n db.exec(migrationSchema);\n historySchema(db, browsingSchema);\n privacySchema(db, tables2.length > 0);\n });\n }\n db;\n statements = /* @__PURE__ */ new Map();\n browsing = new HistoryBrowsing();\n statement(sql) {\n let statement = this.statements.get(sql);\n if (!statement) {\n statement = this.db.prepare(sql);\n this.statements.set(sql, statement);\n }\n return statement;\n }\n get(sql, ...args) {\n return this.statement(sql).get(...args);\n }\n all(sql, ...args) {\n return this.statement(sql).all(...args);\n }\n run(sql, ...args) {\n this.statement(sql).run(...args);\n }\n transaction(run, write = true) {\n this.db.exec(write ? "BEGIN IMMEDIATE" : "BEGIN");\n try {\n const result = run();\n this.db.exec("COMMIT");\n return result;\n } catch (error) {\n this.db.exec("ROLLBACK");\n throw error;\n }\n }\n parseTotals(text2) {\n if (typeof text2 !== "string" || Buffer.byteLength(text2) > HISTORY_RECORD_BYTES.totals) throw new HistoryInvalid("Invalid history totals");\n return validate(JSON.parse(text2));\n }\n revision(root) {\n const previous = this.get("SELECT revision FROM roots WHERE id=?", root)?.revision ?? 0;\n if (!count2(previous) || !count2(previous + 1)) throw new HistoryInvalid("History revision overflow");\n this.run("INSERT INTO roots VALUES (?,1) ON CONFLICT(id) DO UPDATE SET revision=revision+1", root);\n }\n apply(writer, sequence, serialized, beforeCommit) {\n for (; ; ) {\n const result = this.applyReady(writer, sequence, serialized, beforeCommit);\n if ("deleting" in result) continue;\n if (!("pending" in result)) return result;\n this.bootstrap(result.pending);\n }\n }\n /** Worker entrypoint: bootstrap is separate; deletion advances at most one bounded slice. */\n applyReady(writer, sequence, serialized, beforeCommit) {\n if (!/^[a-zA-Z0-9-]{1,128}$/.test(writer) || !count2(sequence) || !sequence) throw new HistoryInvalid("Invalid writer sequence");\n const event = decodeEvent(serialized);\n return this.transaction(() => {\n const previous = this.get("SELECT sequence FROM writers WHERE id=?", writer)?.sequence ?? 0;\n if (sequence <= previous) return { replay: true };\n if (sequence !== previous + 1) throw new HistoryInvalid("History sequence gap");\n const root = rootID(event.context);\n if (!this.ready(root)) return { pending: root };\n if (event.type === "sessionDeleted") {\n if (!deleteHistory(this.db, event.context)) {\n beforeCommit?.();\n return { deleting: true };\n }\n this.revision(root);\n } else this.event(event);\n this.run("INSERT INTO writers VALUES (?,?) ON CONFLICT(id) DO UPDATE SET sequence=excluded.sequence", writer, sequence);\n beforeCommit?.();\n return { replay: false };\n });\n }\n bootstrap(root, beforeCommit) {\n return this.transaction(() => {\n const result = bootstrap(this.db, root);\n beforeCommit?.();\n return result;\n });\n }\n ready(root) {\n if (this.get("SELECT id FROM conversation_totals WHERE id=?", root)) return true;\n if (this.get("SELECT id FROM conversation_bootstrap WHERE id=?", root)) return false;\n if (!this.get("SELECT id FROM reviews WHERE root=? LIMIT 1", root)) {\n this.run("INSERT INTO conversation_totals VALUES (?,?,?)", root, JSON.stringify(empty()), Number(!!this.get("SELECT id FROM roots WHERE id=?", root)));\n return true;\n }\n return false;\n }\n conversation(root) {\n const saved = this.get(`SELECT CASE WHEN length(CAST(totals AS BLOB))<=${HISTORY_RECORD_BYTES.totals} THEN totals END AS totals,partial FROM conversation_totals WHERE id=?`, root);\n if (saved) {\n if (saved.partial !== 0 && saved.partial !== 1) throw new HistoryInvalid("Invalid conversation coverage");\n return { totals: this.parseTotals(saved.totals), partialHistory: saved.partial === 1 };\n }\n if (!this.get("SELECT id FROM reviews WHERE root=? LIMIT 1", root))\n return { totals: empty(), partialHistory: !!this.get("SELECT id FROM roots WHERE id=?", root) };\n throw new HistoryInvalid("Conversation bootstrap required");\n }\n contribute(key, delta, root) {\n if (this.get("SELECT id FROM contributions WHERE id=?", key)) return;\n const meta = this.get(`SELECT CASE WHEN length(CAST(totals AS BLOB))<=${HISTORY_RECORD_BYTES.totals} THEN totals END AS totals,revision FROM meta WHERE id=1`);\n const totals = add(this.parseTotals(meta.totals), delta);\n const conversation = add(this.conversation(root).totals, delta);\n if (!count2(meta.revision + 1)) throw new HistoryInvalid("History revision overflow");\n this.run("INSERT INTO contributions VALUES (?)", key);\n if (JSON.stringify(totals) !== meta.totals) this.run("UPDATE meta SET totals=?,revision=revision+1 WHERE id=1", JSON.stringify(totals));\n this.run("UPDATE conversation_totals SET totals=? WHERE id=?", JSON.stringify(conversation), root);\n }\n event(e) {\n const c = e.context, root = rootID(c), session = sessionID(c);\n const context = e.context, review = reviewID(context), entry = entryID(context);\n const deleted = this.get("SELECT id FROM tombstones WHERE id IN (?,?) LIMIT 1", root, session);\n if (!deleted) {\n const existingSession = this.get("SELECT root FROM sessions WHERE id=?", session);\n if (existingSession && existingSession.root !== c.root) throw new HistoryInvalid("Conflicting session ownership");\n this.run("INSERT OR IGNORE INTO sessions VALUES (?,?,?,?)", session, c.scope, c.root, c.session);\n const existing = this.get("SELECT context FROM reviews WHERE id=?", review);\n if (existing) {\n const prior = JSON.parse(existing.context);\n if (Object.keys(context).some((key) => prior[key] !== context[key])) throw new HistoryInvalid("Conflicting review identity");\n }\n const owner = this.get("SELECT root,session FROM history WHERE id=?", entry);\n if (owner && (owner.root !== root || owner.session !== session)) throw new HistoryInvalid("Conflicting permission ownership");\n const pendingOwner = this.get("SELECT root,session FROM reviews WHERE entry=? LIMIT 1", entry);\n if (pendingOwner && (pendingOwner.root !== root || pendingOwner.session !== session)) throw new HistoryInvalid("Conflicting permission ownership");\n this.run("INSERT OR IGNORE INTO reviews VALUES (?,?,?,?,?,NULL)", review, entry, root, session, JSON.stringify(context));\n }\n const delta = historyDelta(e);\n if (e.type === "attemptDispatched" || e.type === "attemptFinalized") {\n const attempt = opaque(review, e.attempt);\n if (!deleted) {\n this.run("INSERT OR IGNORE INTO attempts VALUES (?,?,NULL,NULL)", attempt, review);\n if (e.type === "attemptDispatched") this.run("UPDATE attempts SET dispatched=COALESCE(dispatched,?) WHERE id=?", JSON.stringify({ at: e.at, retry: e.retry }), attempt);\n else this.run("UPDATE attempts SET finalized=COALESCE(finalized,?) WHERE id=?", JSON.stringify({ at: e.at, usage: e.usage, reportedModel: e.reportedModel }), attempt);\n }\n this.contribute(opaque(e.type, attempt), delta, root);\n } else if (e.type === "reviewAccepted") {\n if (!deleted) this.run("UPDATE reviews SET accepted=COALESCE(accepted,?) WHERE id=?", JSON.stringify(e.accepted), review);\n this.contribute(opaque(e.type, review), delta, root);\n } else if (e.type === "approvalConfirmed" || e.type === "approvalDispatched" || e.type === "approvalSettled") {\n const id = opaque(entry, e.approval);\n if (!deleted) {\n const old = this.get("SELECT * FROM approvals WHERE id=?", id);\n if (old && (old.review !== review || "automatic" in e && old.automatic !== -1 && old.automatic !== Number(e.automatic)))\n throw new HistoryInvalid("Conflicting approval identity");\n const state = e.type === "approvalConfirmed" ? "confirmed" : e.type === "approvalSettled" ? e.result : "pending";\n if (!old || old.state !== "confirmed" && e.type !== "approvalDispatched") {\n this.run(\n "INSERT INTO approvals VALUES (?,?,?,?,?,?,?,?) ON CONFLICT(id) DO UPDATE SET state=excluded.state,automatic=excluded.automatic,at=excluded.at",\n id,\n entry,\n root,\n session,\n review,\n state,\n "automatic" in e ? Number(e.automatic) : old?.automatic ?? -1,\n e.at\n );\n } else if (old.automatic === -1 && "automatic" in e)\n this.run("UPDATE approvals SET automatic=? WHERE id=?", Number(e.automatic), id);\n this.outcome(entry);\n this.revision(root);\n }\n if (e.type === "approvalConfirmed") {\n this.contribute(opaque("approvalConfirmed", id), delta, root);\n }\n } else if ((e.type === "permissionResolved" || e.type === "permissionOutcome") && !deleted) {\n const old = this.get("SELECT * FROM history WHERE id=?", entry);\n if (e.type === "permissionOutcome" && !old) return;\n const replace = e.type === "permissionResolved" && (!old || e.payload.completedAt > old.completed || e.payload.completedAt === old.completed && Buffer.compare(Buffer.from(context.review), Buffer.from(old.tie)) >= 0);\n const prior = old?.resolution;\n const resolution = !prior || prior === "cancelled" ? e.outcome : e.outcome === "cancelled" ? prior : prior === "conflict" || prior === "rejected" !== (e.outcome === "rejected") ? "conflict" : prior === "auto" || e.outcome === "auto" ? "auto" : e.outcome;\n if (replace) {\n this.run(\n `INSERT INTO history VALUES (?,?,?,?,?,?,?,?,NULL,?,NULL,?) ON CONFLICT(id) DO UPDATE SET\n completed=excluded.completed,tie=excluded.tie,review=excluded.review,resolution=excluded.resolution,resolvedAt=excluded.resolvedAt`,\n entry,\n c.scope,\n root,\n session,\n context.permission,\n e.payload.completedAt,\n context.review,\n review,\n resolution,\n e.at\n );\n this.run("INSERT INTO payloads VALUES (?,?) ON CONFLICT(id) DO UPDATE SET body=excluded.body", entry, JSON.stringify(e.payload));\n } else this.run("UPDATE history SET resolution=? WHERE id=?", resolution, entry);\n this.outcome(entry);\n this.revision(root);\n }\n }\n outcome(entry) {\n const row = this.get("SELECT resolution FROM history WHERE id=?", entry);\n if (!row) return;\n const auto = this.get("SELECT review FROM approvals WHERE entry=? AND state=\'confirmed\' AND automatic=1 ORDER BY id LIMIT 1", entry);\n const manual = this.get("SELECT review FROM approvals WHERE entry=? AND state=\'confirmed\' AND automatic=0 ORDER BY id LIMIT 1", entry);\n const uncertain = this.get("SELECT id FROM approvals WHERE entry=? AND state IN (\'pending\',\'uncertain\') LIMIT 1", entry);\n const outcome = row.resolution === "conflict" || row.resolution === "rejected" && (auto || manual) ? null : row.resolution === "rejected" ? "rejected" : auto ? "auto" : manual ? "manual" : uncertain ? null : row.resolution;\n this.run("UPDATE history SET outcome=?,approving=? WHERE id=?", outcome, auto?.review ?? manual?.review ?? null, entry);\n }\n query(q) {\n for (; ; ) {\n const result = this.queryReady(q);\n if (!("pending" in result)) return result;\n this.bootstrap(result.pending);\n }\n }\n queryReady(q) {\n validateQuery(q);\n return this.transaction(() => {\n const conversation = q.type === "conversationTotals" ? q : q.type === "totals" ? q.conversation : void 0;\n if (conversation) {\n const root2 = rootID(conversation);\n if (!this.get("SELECT id FROM conversation_totals WHERE id=?", root2) && (this.get("SELECT id FROM conversation_bootstrap WHERE id=?", root2) || this.get("SELECT id FROM reviews WHERE root=? LIMIT 1", root2))) return { pending: root2 };\n }\n if (q.type === "totals") {\n const row = this.get(`SELECT revision,CASE WHEN length(CAST(totals AS BLOB))<=${HISTORY_RECORD_BYTES.totals} THEN totals END AS totals FROM meta WHERE id=1`);\n if (!count2(row.revision)) throw new HistoryInvalid("Invalid aggregate revision");\n return {\n revision: row.revision,\n totals: this.parseTotals(row.totals),\n ...q.conversation ? { conversation: this.conversation(rootID(q.conversation)) } : {}\n };\n }\n if (q.type === "conversationTotals") {\n const revision2 = this.get("SELECT revision FROM meta WHERE id=1").revision;\n if (!count2(revision2)) throw new HistoryInvalid("Invalid aggregate revision");\n return { revision: revision2, ...this.conversation(rootID(q)) };\n }\n if (q.type === "sessions") {\n const rows = this.all(`SELECT CASE WHEN length(id)=64 THEN id END AS id,scope,\n CASE WHEN length(CAST(root AS BLOB))<=${HISTORY_TEXT_BYTES} THEN root END AS root,\n CASE WHEN length(CAST(session AS BLOB))<=${HISTORY_TEXT_BYTES} THEN session END AS session\n FROM sessions WHERE scope=? AND id>? ORDER BY id LIMIT ?`, q.scope, q.after ?? "", q.limit ?? 100);\n return { sessions: rows.map((row) => {\n validateScope(row);\n if (row.id !== sessionID(row) || row.scope !== q.scope) throw new HistoryInvalid("Invalid session index");\n return { scope: row.scope, root: row.root, session: row.session };\n }), after: rows.at(-1)?.id };\n }\n if (q.type === "session") {\n if (this.get("SELECT id FROM tombstones WHERE id=?", opaque(q.scope, q.session))) return {};\n const row = this.get(`SELECT CASE WHEN length(CAST(scope AS BLOB))<=${HISTORY_TEXT_BYTES} THEN scope END AS scope,\n CASE WHEN length(CAST(session AS BLOB))<=${HISTORY_TEXT_BYTES} THEN session END AS session,\n CASE WHEN length(CAST(root AS BLOB))<=${HISTORY_TEXT_BYTES} THEN root END AS root\n FROM sessions WHERE id=?`, opaque(q.scope, q.session));\n if (!row) return {};\n validateScope(row);\n if (row.scope !== q.scope || row.session !== q.session) throw new HistoryInvalid("Invalid session ownership");\n if (this.get("SELECT id FROM tombstones WHERE id=?", rootID(row))) return {};\n return { context: { scope: row.scope, root: row.root, session: row.session } };\n }\n if (q.type === "resolution") {\n const entry = opaque(q.scope, q.permission), root2 = rootID(q), session = sessionID(q);\n const deleted = !!this.get("SELECT id FROM tombstones WHERE id IN (?,?) LIMIT 1", root2, session);\n if (deleted) return { outcome: void 0, uncertain: false, conflict: false, deleted: true };\n const invalid = this.get(`SELECT 1 AS invalid FROM approvals WHERE entry=? AND\n (root!=? OR session!=? OR state NOT IN (\'pending\',\'confirmed\',\'uncertain\',\'not-sent\') OR automatic NOT IN (-1,0,1)\n OR (state=\'confirmed\' AND automatic=-1)) LIMIT 1`, entry, root2, session);\n const invalidReview = this.get("SELECT 1 AS invalid FROM reviews WHERE entry=? AND (root!=? OR session!=?) LIMIT 1", entry, root2, session);\n const history = this.get(`SELECT CASE WHEN length(root)=64 THEN root END AS root,\n CASE WHEN length(session)=64 THEN session END AS session,\n CASE WHEN length(CAST(resolution AS BLOB))<=16 THEN resolution END AS resolution FROM history WHERE id=?`, entry);\n if (invalid || invalidReview || history && (history.root !== root2 || history.session !== session || !["auto", "manual", "rejected", "cancelled", "conflict"].includes(history.resolution))) throw new HistoryInvalid("Invalid resolution ownership");\n const auto = !!this.get("SELECT id FROM approvals WHERE entry=? AND state=\'confirmed\' AND automatic=1 LIMIT 1", entry);\n const manual = !!this.get("SELECT id FROM approvals WHERE entry=? AND state=\'confirmed\' AND automatic=0 LIMIT 1", entry);\n const uncertain = !!this.get("SELECT id FROM approvals WHERE entry=? AND state IN (\'pending\',\'uncertain\') LIMIT 1", entry);\n const conflict = history?.resolution === "conflict" || history?.resolution === "rejected" && (auto || manual);\n const outcome = deleted || conflict ? void 0 : auto ? "auto" : manual ? "manual" : history?.resolution === "rejected" ? "rejected" : history?.resolution === "manual" && !uncertain ? "manual" : void 0;\n return { outcome, uncertain, conflict, deleted };\n }\n const root = rootID(q), revision = this.get("SELECT revision FROM roots WHERE id=?", root)?.revision ?? 0;\n if (this.get("SELECT id FROM tombstones WHERE id=?", root)) return { revision, total: 0, rank: 0, deleted: true };\n const visible = "NOT EXISTS (SELECT 1 FROM tombstones WHERE id=history.session)";\n const base = `scope=? AND root=? AND outcome IS NOT NULL AND ${visible}`;\n const generation = this.get("SELECT generation FROM history_generations WHERE id=?", root)?.generation ?? 0;\n if (!count2(generation) || !count2(revision)) throw new HistoryInvalid("Invalid history index");\n let index = this.browsing.get(root, generation);\n if (!index) {\n const invalid = this.get(`SELECT id FROM history WHERE scope=? AND root=? AND ${visible} AND\n (typeof(completed)!=\'integer\' OR completed<0 OR completed>8640000000000000 OR length(id)!=64 OR length(session)!=64\n OR length(review)!=64 OR typeof(permission)!=\'text\' OR length(CAST(permission AS BLOB))>${HISTORY_TEXT_BYTES}\n OR typeof(tie)!=\'text\' OR length(tie)=0 OR length(CAST(tie AS BLOB))>${HISTORY_TEXT_BYTES}\n OR length(CAST(resolution AS BLOB))>16 OR length(approving)>64\n OR outcome NOT IN (\'auto\',\'manual\',\'rejected\',\'cancelled\')) LIMIT 1`, q.scope, root);\n const invalidOwner = this.get(`SELECT h.id FROM history h LEFT JOIN reviews r ON r.id=h.review\n LEFT JOIN sessions s ON s.id=h.session WHERE h.scope=? AND h.root=?\n AND NOT EXISTS (SELECT 1 FROM tombstones WHERE id=h.session) AND\n (r.id IS NULL OR r.entry!=h.id OR r.root!=h.root OR r.session!=h.session OR s.id IS NULL OR s.scope!=h.scope OR s.root!=?) LIMIT 1`, q.scope, root, q.root);\n if (invalid || invalidOwner) throw new HistoryInvalid("Invalid history index");\n const total2 = this.get(`SELECT count(*) AS n FROM history WHERE ${base}`, q.scope, root).n;\n if (!count2(total2)) throw new HistoryInvalid("History count overflow");\n index = this.browsing.save(root, generation, total2);\n }\n const total = index.total;\n const byID = (id) => id === null ? void 0 : this.get(`SELECT * FROM history WHERE ${base} AND id=?`, q.scope, root, id);\n const seek = (row, newer = false) => {\n const order = newer ? "ASC" : "DESC";\n return this.get(\n `SELECT h.* FROM sessions s JOIN history h ON h.id=(\n SELECT id FROM history WHERE scope=? AND root=? AND session=s.id AND outcome IS NOT NULL\n ${row ? `AND (completed,tie,id) ${newer ? ">" : "<"} (?,?,?)` : ""}\n ORDER BY completed ${order},tie ${order},id ${order} LIMIT 1)\n WHERE s.scope=? AND s.root=? AND NOT EXISTS (SELECT 1 FROM tombstones WHERE id=s.id)\n ORDER BY h.completed ${order},h.tie ${order},h.id ${order} LIMIT 1`,\n q.scope,\n root,\n ...row ? [row.completed, row.tie, row.id] : [],\n q.scope,\n q.root\n );\n };\n let selected;\n if (q.entry) selected = byID(q.entry);\n else if (index.latest !== void 0) selected = byID(index.latest);\n else {\n selected = seek();\n index.latest = selected?.id ?? null;\n }\n const adjacent = (row, newer) => {\n const key = opaque(String(row.completed), row.tie, row.id, String(newer));\n if (index.neighbors.has(key)) return byID(index.neighbors.get(key));\n const candidate = seek(row, newer);\n this.browsing.neighbor(index, key, candidate?.id ?? null);\n return candidate;\n };\n if (selected && q.direction) selected = adjacent(selected, q.direction === "newer") ?? selected;\n if (!selected && q.order) selected = adjacent(q.order, true) ?? adjacent(q.order, false);\n if (!selected) return { revision, total, rank: 0 };\n const s = selected;\n if (s.id !== opaque(q.scope, s.permission) || s.review !== opaque(q.scope, s.tie)) throw new HistoryInvalid("Invalid history ownership");\n const rank = index.ranks.get(s.id) ?? this.get(`SELECT count(*) AS n FROM history WHERE ${base} AND (completed,tie,id)<=(?,?,?)`, q.scope, root, s.completed, s.tie, s.id).n;\n if (!count2(rank) || rank < 1 || rank > total) throw new HistoryInvalid("Invalid history rank");\n this.browsing.rank(index, s.id, rank);\n const result = { revision, total, rank, entry: s.id, order: { completed: s.completed, tie: s.tie, id: s.id }, older: adjacent(s, false)?.id, newer: adjacent(s, true)?.id };\n const contextRow = this.get(`SELECT CASE WHEN length(CAST(context AS BLOB))<=${HISTORY_RECORD_BYTES.context} THEN context END AS context FROM reviews WHERE id=? AND root=? AND session=?`, s.review, root, s.session);\n if (typeof contextRow?.context !== "string") throw new HistoryInvalid("Invalid history ownership");\n const context = JSON.parse(contextRow.context);\n validateReview(context);\n if (rootID(context) !== root || sessionID(context) !== s.session || entryID(context) !== s.id || reviewID(context) !== s.review) throw new HistoryInvalid("Invalid history ownership");\n result.session = context.session;\n try {\n const body = this.get(`SELECT CASE WHEN length(CAST(body AS BLOB))<=${HISTORY_RECORD_BYTES.payload} THEN body END AS body FROM payloads WHERE id=?`, s.id)?.body;\n if (typeof body !== "string" || Buffer.byteLength(body) > HISTORY_RECORD_BYTES.payload) throw new Error();\n const payload = validatePayload(JSON.parse(body));\n if (payload.completedAt !== s.completed) throw new Error();\n result.record = { context, payload, outcome: s.outcome, approvingReview: s.approving ?? void 0 };\n } catch {\n result.unreadable = true;\n }\n return result;\n }, false);\n }\n cleanup(beforeCommit) {\n let work = this.transaction(() => {\n const row = this.get(`SELECT id,\n CASE WHEN length(CAST(scope AS BLOB))<=${HISTORY_TEXT_BYTES} THEN scope END AS scope,\n CASE WHEN length(CAST(root AS BLOB))<=${HISTORY_TEXT_BYTES} THEN root END AS root,\n CASE WHEN length(CAST(session AS BLOB))<=${HISTORY_TEXT_BYTES} THEN session END AS session\n FROM history_deletions ORDER BY id LIMIT 1`);\n if (!row) return void 0;\n validateScope(row);\n if (row.id !== sessionID(row) || !this.get("SELECT id FROM tombstones WHERE id=?", row.id) || !this.get("SELECT id FROM conversation_totals WHERE id=?", rootID(row))) throw new HistoryInvalid("Invalid history deletion");\n if (deleteHistory(this.db, row)) this.revision(rootID(row));\n beforeCommit?.();\n return "deleting";\n });\n if (!work) {\n this.db.exec("PRAGMA foreign_keys=OFF; PRAGMA legacy_alter_table=ON");\n try {\n work = this.transaction(() => {\n if (!scrubLegacyFreelist(this.db) && !rebuildLegacyPages(this.db)) return void 0;\n beforeCommit?.();\n return "scrubbing";\n });\n } finally {\n this.db.exec("PRAGMA legacy_alter_table=OFF; PRAGMA foreign_keys=ON");\n }\n }\n if (work) return { pending: true, legacy: !!this.get("SELECT legacy FROM history_privacy WHERE id=1").legacy, [work]: true };\n return cleanupHistory(this.db);\n }\n close() {\n this.db.close();\n }\n};\n\n// src/history-private-path.ts\nvar import_node_fs = require("node:fs");\nvar import_node_path2 = __toESM(require("node:path"), 1);\nfunction privateDatabase(file) {\n const uid = process.geteuid?.();\n if (process.platform !== "linux" || uid === void 0 || !import_node_path2.default.isAbsolute(file) || import_node_path2.default.normalize(file) !== file || import_node_path2.default.basename(file) !== "history-v1.sqlite") throw new HistoryInvalid("Invalid history path");\n const directory = import_node_path2.default.dirname(file);\n let current = "/";\n for (const part of ["", ...directory.split("/").filter(Boolean)]) {\n if (part) current = import_node_path2.default.join(current, part);\n let stat;\n try {\n stat = (0, import_node_fs.lstatSync)(current);\n } catch (error) {\n if (error.code !== "ENOENT") throw error;\n try {\n (0, import_node_fs.mkdirSync)(current, { mode: 448 });\n } catch (error2) {\n if (error2.code !== "EEXIST") throw error2;\n }\n stat = (0, import_node_fs.lstatSync)(current);\n }\n if (!stat.isDirectory() || stat.isSymbolicLink() || stat.uid !== 0 && stat.uid !== uid || stat.mode & 18 && !(stat.mode & 512)) throw new HistoryInvalid("Untrusted history ancestor");\n if (current === directory && (stat.uid !== uid || stat.mode & 63)) throw new HistoryInvalid("History directory must be private");\n }\n for (const suffix of ["", "-wal", "-shm", "-journal"]) {\n const name = file + suffix;\n let fd;\n try {\n fd = (0, import_node_fs.openSync)(name, import_node_fs.constants.O_RDWR | import_node_fs.constants.O_NOFOLLOW | import_node_fs.constants.O_NONBLOCK);\n } catch (error) {\n if (error.code !== "ENOENT") throw error;\n if (suffix) continue;\n try {\n fd = (0, import_node_fs.openSync)(name, import_node_fs.constants.O_RDWR | import_node_fs.constants.O_CREAT | import_node_fs.constants.O_EXCL | import_node_fs.constants.O_NOFOLLOW, 384);\n } catch (error2) {\n if (error2.code !== "EEXIST") throw error2;\n fd = (0, import_node_fs.openSync)(name, import_node_fs.constants.O_RDWR | import_node_fs.constants.O_NOFOLLOW | import_node_fs.constants.O_NONBLOCK);\n }\n }\n try {\n const stat = (0, import_node_fs.fstatSync)(fd);\n if (!stat.isFile() || stat.nlink !== 1 || stat.uid !== uid || stat.mode & 63) throw new HistoryInvalid("History file must be private");\n } finally {\n (0, import_node_fs.closeSync)(fd);\n }\n }\n}\n\n// src/history-storage-worker.ts\nfunction openHistoryDatabase(file, adapter, construct) {\n privateDatabase(file);\n const db = construct ? construct(file) : adapter === "bun" ? new (require("bun:sqlite")).Database(file) : new (require("node:sqlite")).DatabaseSync(file);\n try {\n return new HistorySQL(db);\n } catch (error) {\n db.close();\n throw error;\n }\n}\nif (import_node_worker_threads.parentPort) {\n let sql;\n import_node_worker_threads.parentPort.on("message", (message) => {\n try {\n let value;\n if (message.type === "open") {\n if (sql) throw new HistoryInvalid("History worker already open");\n sql = openHistoryDatabase(message.file, message.adapter);\n value = { environmentKeys: Object.keys(process.env).length, cleanup: true };\n } else if (message.type === "close") {\n sql?.close();\n sql = void 0;\n } else {\n if (!sql) throw new Error("History worker not open");\n if (message.type === "apply") value = sql.applyReady(message.writer, message.sequence, message.event);\n else if (message.type === "query") value = sql.queryReady(message.query);\n else if (message.type === "bootstrap") value = sql.bootstrap(message.root);\n else if (message.type === "cleanup") value = sql.cleanup();\n else throw new HistoryInvalid("Invalid history worker operation");\n }\n import_node_worker_threads.parentPort.postMessage({ id: message.id, value });\n } catch (error) {\n import_node_worker_threads.parentPort.postMessage({ id: message.id, error: error instanceof HistoryInvalid ? "invalid" : "storage" });\n }\n });\n}\n// Annotate the CommonJS export names for ESM import in node:\n0 && (module.exports = {\n openHistoryDatabase,\n privateDatabase\n});\n' : "";
7493
8013
  var HistoryWorker = class {
7494
8014
  worker;
7495
8015
  id = 0;
@@ -7561,6 +8081,12 @@ var HistoryStore = class {
7561
8081
  blocked = false;
7562
8082
  writeFailed = false;
7563
8083
  preferRead = false;
8084
+ bootstraps = /* @__PURE__ */ new Set();
8085
+ preferBootstrap = true;
8086
+ cleanupEnabled = false;
8087
+ cleanupDue = 0;
8088
+ cleanupNeeded = false;
8089
+ legacyCleanup;
7564
8090
  failures = /* @__PURE__ */ new Set();
7565
8091
  commits = /* @__PURE__ */ new Set();
7566
8092
  disposal;
@@ -7577,6 +8103,13 @@ var HistoryStore = class {
7577
8103
  get pendingOperations() {
7578
8104
  return this.queue.length;
7579
8105
  }
8106
+ /** Local observation only; another client may have committed newer cleanup work. */
8107
+ get cleanupPending() {
8108
+ return this.cleanupNeeded;
8109
+ }
8110
+ get legacyCleanupRequired() {
8111
+ return this.legacyCleanup;
8112
+ }
7580
8113
  /** Saturated deletion admission leaves one bounded reconciliation signal, never an ID side queue. */
7581
8114
  get maintenanceDirty() {
7582
8115
  return this.dirty;
@@ -7643,7 +8176,7 @@ var HistoryStore = class {
7643
8176
  if (event.type === "sessionDeleted") this.markMaintenanceDirty();
7644
8177
  return false;
7645
8178
  }
7646
- this.queue.push({ sequence: ++this.sequence, event: serialized, bytes });
8179
+ this.queue.push({ sequence: ++this.sequence, event: serialized, bytes, root: rootID(event.context) });
7647
8180
  this.bytes += bytes;
7648
8181
  this.pump();
7649
8182
  return true;
@@ -7660,6 +8193,7 @@ var HistoryStore = class {
7660
8193
  const finish = (result, error) => {
7661
8194
  if (done) return;
7662
8195
  done = true;
8196
+ read.finished = true;
7663
8197
  clearTimeout(timer);
7664
8198
  signal?.removeEventListener("abort", abort);
7665
8199
  const index = this.reads.indexOf(read);
@@ -7667,7 +8201,7 @@ var HistoryStore = class {
7667
8201
  if (!read.started) settle();
7668
8202
  error ? reject(error) : resolve(result);
7669
8203
  };
7670
- const read = { query, resolve: (result) => finish(result), reject: (error) => finish(void 0, error), settled: settle, started: false };
8204
+ const read = { query, resolve: (result) => finish(result), reject: (error) => finish(void 0, error), settled: settle, started: false, finished: false };
7671
8205
  const abort = () => finish(void 0, new Error("History read aborted"));
7672
8206
  const timer = setTimeout(() => finish(void 0, new Error("History read timeout")), 3e3);
7673
8207
  if (signal?.aborted) {
@@ -7687,22 +8221,44 @@ var HistoryStore = class {
7687
8221
  this.cancel(this.timer);
7688
8222
  this.timer = void 0;
7689
8223
  }
8224
+ const cleanup = this.cleanupEnabled && this.now() >= this.cleanupDue && (!this.closing || this.cleanupNeeded);
8225
+ if (cleanup) {
8226
+ this.active = true;
8227
+ void this.run(void 0, void 0, true);
8228
+ return;
8229
+ }
7690
8230
  const write = !this.blocked && this.queue.length > 0 && this.now() >= this.retryAt;
7691
- const read = this.reads.length && (!write || this.preferRead) ? this.reads.shift() : void 0;
7692
- if (!read && !write) {
7693
- if (this.queue.length && !this.blocked) this.timer = this.schedule(() => {
7694
- this.timer = void 0;
7695
- this.pump();
7696
- }, Math.max(0, this.retryAt - this.now()));
8231
+ const bootstrap = !this.blocked && this.now() >= this.retryAt && (this.preferBootstrap || !write && !this.reads.length) ? this.bootstraps.values().next().value : void 0;
8232
+ const read = !bootstrap && this.reads.length && (!write || this.preferRead) ? this.reads.shift() : void 0;
8233
+ if (!read && !write && !bootstrap) {
8234
+ const next = Math.min(
8235
+ (this.queue.length || this.bootstraps.size) && !this.blocked ? this.retryAt : Infinity,
8236
+ this.cleanupEnabled && (!this.closing || this.cleanupNeeded) ? this.cleanupDue : Infinity
8237
+ );
8238
+ if (Number.isFinite(next)) {
8239
+ this.timer = this.schedule(() => {
8240
+ this.timer = void 0;
8241
+ this.pump();
8242
+ }, Math.max(0, next - this.now()));
8243
+ this.timer.unref?.();
8244
+ }
7697
8245
  return;
7698
8246
  }
7699
8247
  this.active = true;
7700
8248
  if (read) read.started = true;
7701
- void this.run(read);
8249
+ void this.run(read, bootstrap);
7702
8250
  }
7703
- async run(read) {
8251
+ rememberBootstrap(root) {
8252
+ if (!this.bootstraps.has(root) && this.bootstraps.size >= 9) this.bootstraps.delete(this.bootstraps.values().next().value);
8253
+ this.bootstraps.add(root);
8254
+ this.preferBootstrap = true;
8255
+ }
8256
+ async run(read, bootstrap, cleanup = false) {
8257
+ const bootstrapReaders = bootstrap ? this.reads.filter((reader2) => reader2.bootstrap === bootstrap) : [];
8258
+ for (const reader2 of bootstrapReaders) reader2.started = true;
7704
8259
  let watchdog;
7705
8260
  let terminating;
8261
+ let cleanupDelay = HISTORY_RETRY_MS;
7706
8262
  try {
7707
8263
  this.worker ??= this.options.transport?.() ?? new HistoryWorker(this.options.source);
7708
8264
  const worker = this.worker;
@@ -7712,23 +8268,55 @@ var HistoryStore = class {
7712
8268
  });
7713
8269
  }, 3e3);
7714
8270
  if (!this.opened) {
7715
- await worker.call({ type: "open", file: path14.join(this.stateDirectory, "opencode-reviewer/history-v1.sqlite"), adapter: this.options.adapter ?? "bun" });
8271
+ const opened = await worker.call({ type: "open", file: path14.join(this.stateDirectory, "opencode-reviewer/history-v1.sqlite"), adapter: this.options.adapter ?? "bun" });
7716
8272
  if (terminating) throw new Error("History worker terminating");
7717
8273
  this.opened = true;
8274
+ this.cleanupEnabled = opened?.cleanup === true;
8275
+ this.cleanupNeeded = this.cleanupEnabled;
8276
+ this.cleanupDue = 0;
7718
8277
  }
7719
8278
  if (this.stopped) return;
7720
- if (read) {
8279
+ if (cleanup) {
8280
+ const result = await worker.call({ type: "cleanup" });
8281
+ this.cleanupNeeded = result.pending;
8282
+ this.legacyCleanup = result.legacy;
8283
+ if (result.deleting || result.scrubbing) cleanupDelay = 10;
8284
+ } else if (bootstrap) {
8285
+ const result = await worker.call({ type: "bootstrap", root: bootstrap });
8286
+ this.bootstraps.delete(bootstrap);
8287
+ if (!result.done) this.bootstraps.add(bootstrap);
8288
+ this.preferBootstrap = false;
8289
+ this.retryAt = 0;
8290
+ } else if (read) {
7721
8291
  if ((read.query.type === "totals" || read.query.type === "conversationTotals") && this.writeFailed) throw new Error("History accounting unavailable");
7722
8292
  const revision = this.dirtyRevision;
7723
8293
  if (read.query.type === "history" && this.dirty) throw new Error("History reconciliation pending");
7724
8294
  const result = await worker.call({ type: "query", query: read.query });
8295
+ if (result?.pending) {
8296
+ read.bootstrap = result.pending;
8297
+ this.rememberBootstrap(result.pending);
8298
+ if (!read.finished) this.reads.push(read);
8299
+ return;
8300
+ }
7725
8301
  if (read.query.type === "history" && (this.dirty || revision !== this.dirtyRevision)) throw new Error("History reconciliation pending");
7726
8302
  read.resolve(result);
7727
8303
  this.preferRead = false;
7728
8304
  } else {
7729
8305
  const item = this.queue[0];
7730
- await worker.call({ type: "apply", writer: this.writer, sequence: item.sequence, event: item.event });
8306
+ const result = await worker.call({ type: "apply", writer: this.writer, sequence: item.sequence, event: item.event });
8307
+ if (result?.pending) {
8308
+ this.rememberBootstrap(result.pending);
8309
+ this.preferRead = true;
8310
+ return;
8311
+ }
8312
+ if (result?.deleting) {
8313
+ this.cleanupNeeded = this.cleanupEnabled;
8314
+ this.preferRead = true;
8315
+ this.retryAt = 0;
8316
+ return;
8317
+ }
7731
8318
  if (this.stopped) return;
8319
+ if (this.cleanupEnabled) this.cleanupNeeded = true;
7732
8320
  this.queue.shift();
7733
8321
  this.bytes -= item.bytes;
7734
8322
  this.retryAt = 0;
@@ -7737,8 +8325,12 @@ var HistoryStore = class {
7737
8325
  this.notify(this.commits);
7738
8326
  }
7739
8327
  } catch (error) {
7740
- if (read) read.reject(error);
7741
- else {
8328
+ if (cleanup) this.cleanupNeeded = true;
8329
+ else if (read) read.reject(error);
8330
+ else if (bootstrap && this.queue[0]?.root !== bootstrap) {
8331
+ this.bootstraps.delete(bootstrap);
8332
+ for (const reader2 of [...this.reads]) if (reader2.bootstrap === bootstrap) reader2.reject(error);
8333
+ } else {
7742
8334
  this.writeFailed = true;
7743
8335
  if (error instanceof HistoryInvalid) this.blocked = true;
7744
8336
  this.retryAt = this.now() + HISTORY_RETRY_MS;
@@ -7755,7 +8347,15 @@ var HistoryStore = class {
7755
8347
  this.worker = void 0;
7756
8348
  this.opened = false;
7757
8349
  }
7758
- read?.settled();
8350
+ if (cleanup) this.cleanupDue = this.now() + cleanupDelay;
8351
+ if (read) {
8352
+ read.started = false;
8353
+ if (read.finished) read.settled();
8354
+ }
8355
+ for (const reader2 of bootstrapReaders) {
8356
+ reader2.started = false;
8357
+ if (reader2.finished) reader2.settled();
8358
+ }
7759
8359
  this.active = false;
7760
8360
  this.pump();
7761
8361
  }
@@ -7771,7 +8371,7 @@ var HistoryStore = class {
7771
8371
  this.maintenanceListeners.clear();
7772
8372
  for (const read of this.reads.splice(0)) read.reject(new Error("History disposed"));
7773
8373
  const end = abortAt + 3500;
7774
- while ((this.queue.length || this.active) && performance.now() < end - 300 && !this.blocked) {
8374
+ while ((this.queue.length || this.active || this.cleanupNeeded) && performance.now() < end - 300 && !this.blocked) {
7775
8375
  this.pump();
7776
8376
  await new Promise((resolve) => setTimeout(resolve, 10));
7777
8377
  }
@@ -8104,16 +8704,18 @@ function sessionPresence(result, session) {
8104
8704
  }
8105
8705
  var HistoryMaintenance = class {
8106
8706
  constructor(scope, store, get, invalidate = () => {
8107
- }) {
8707
+ }, now = () => performance.now()) {
8108
8708
  this.scope = scope;
8109
8709
  this.store = store;
8110
8710
  this.get = get;
8111
8711
  this.invalidate = invalidate;
8712
+ this.now = now;
8112
8713
  }
8113
8714
  scope;
8114
8715
  store;
8115
8716
  get;
8116
8717
  invalidate;
8718
+ now;
8117
8719
  stopped = false;
8118
8720
  active = false;
8119
8721
  abort = new AbortController();
@@ -8133,7 +8735,7 @@ var HistoryMaintenance = class {
8133
8735
  if (!this.stopped) this.timer = setTimeout(() => {
8134
8736
  this.timer = void 0;
8135
8737
  void this.step().finally(() => this.schedule());
8136
- }, 2e3);
8738
+ }, this.store.maintenanceDirty ? 10 : 2e3);
8137
8739
  }
8138
8740
  /** Also exposed for deterministic fixtures. Concurrent calls cannot duplicate work. */
8139
8741
  async step() {
@@ -8141,6 +8743,7 @@ var HistoryMaintenance = class {
8141
8743
  this.active = true;
8142
8744
  const turn = this.turn = new AbortController();
8143
8745
  const deadline = setTimeout(() => turn.abort(), 5e3);
8746
+ const until = this.now() + 50, rows = this.store.maintenanceDirty ? 100 : 1;
8144
8747
  try {
8145
8748
  if (this.scanRevision !== this.store.maintenanceScanRevision) {
8146
8749
  this.scanRevision = this.store.maintenanceScanRevision;
@@ -8155,41 +8758,51 @@ var HistoryMaintenance = class {
8155
8758
  this.finishPass();
8156
8759
  return;
8157
8760
  }
8158
- if (!this.page.length) {
8159
- const read = this.store.query({ type: "sessions", scope: this.scope, after: this.after, limit: 100 }, turn.signal);
8160
- let result;
8161
- try {
8162
- result = await read;
8163
- } finally {
8164
- await read.settled?.catch(() => {
8165
- });
8166
- }
8761
+ for (let n = 0; n < rows; n++) {
8167
8762
  if (this.stopped || turn.signal.aborted) {
8168
8763
  this.healthy = false;
8169
8764
  return;
8170
8765
  }
8171
- if (!result.sessions.length) {
8172
- this.ending = true;
8173
- this.finishPass();
8766
+ if (this.scanRevision !== this.store.maintenanceScanRevision) return;
8767
+ if (!this.page.length) {
8768
+ const read = this.store.query({ type: "sessions", scope: this.scope, after: this.after, limit: 100 }, turn.signal);
8769
+ let result;
8770
+ try {
8771
+ result = await read;
8772
+ } finally {
8773
+ await read.settled?.catch(() => {
8774
+ });
8775
+ }
8776
+ if (this.stopped || turn.signal.aborted) {
8777
+ this.healthy = false;
8778
+ return;
8779
+ }
8780
+ if (this.scanRevision !== this.store.maintenanceScanRevision) return;
8781
+ if (!result.sessions.length) {
8782
+ this.ending = true;
8783
+ this.finishPass();
8784
+ return;
8785
+ }
8786
+ this.page = result.sessions;
8787
+ this.after = result.after;
8788
+ }
8789
+ if (this.now() >= until) return;
8790
+ const context = this.page.shift();
8791
+ if (context.scope !== this.scope) {
8792
+ this.healthy = false;
8174
8793
  return;
8175
8794
  }
8176
- this.page = result.sessions;
8177
- this.after = result.after;
8178
- }
8179
- const context = this.page.shift();
8180
- if (context.scope !== this.scope) {
8181
- this.healthy = false;
8182
- return;
8183
- }
8184
- const root = await this.presence(context.root);
8185
- if (this.stopped) return;
8186
- if (root === "missing") this.remove({ ...context, session: context.root });
8187
- else if (root === "unknown") this.healthy = false;
8188
- else if (context.session !== context.root) {
8189
- const child = await this.presence(context.session);
8190
- if (this.stopped) return;
8191
- if (child === "missing") this.remove(context);
8192
- else if (child === "unknown") this.healthy = false;
8795
+ const root = await this.presence(context.root);
8796
+ if (this.stopped || this.scanRevision !== this.store.maintenanceScanRevision) return;
8797
+ if (root === "missing") this.remove({ ...context, session: context.root });
8798
+ else if (root === "unknown") this.healthy = false;
8799
+ else if (context.session !== context.root) {
8800
+ const child = await this.presence(context.session);
8801
+ if (this.stopped || this.scanRevision !== this.store.maintenanceScanRevision) return;
8802
+ if (child === "missing") this.remove(context);
8803
+ else if (child === "unknown") this.healthy = false;
8804
+ }
8805
+ if (this.now() >= until) return;
8193
8806
  }
8194
8807
  } catch {
8195
8808
  this.healthy = false;
@@ -8395,7 +9008,14 @@ var HistoryController = class {
8395
9008
  const selected = this.state.selection;
8396
9009
  if (selected?.entry && (selected.session ?? selected.record?.context.session) === id) {
8397
9010
  this.deletedEntries.add(selected.entry);
8398
- this.update({ status: "loading" });
9011
+ this.update({ status: "loading", selection: {
9012
+ revision: selected.revision,
9013
+ total: selected.total,
9014
+ rank: selected.rank,
9015
+ entry: selected.entry,
9016
+ order: selected.order,
9017
+ session: id
9018
+ } });
8399
9019
  }
8400
9020
  this.refresh?.refresh();
8401
9021
  }
@@ -8411,7 +9031,8 @@ var HistoryController = class {
8411
9031
  clearTimeout(this.targetTimeout);
8412
9032
  this.target = void 0;
8413
9033
  this.deletedEntries.clear();
8414
- this.update({ open: false });
9034
+ this.scroll = 0;
9035
+ this.update({ open: false, status: "loading", selection: void 0 });
8415
9036
  }
8416
9037
  dispose() {
8417
9038
  this.stopped = true;
@@ -8431,7 +9052,7 @@ import { insert as _$insert2 } from "@opentui/solid";
8431
9052
  import { setProp as _$setProp3 } from "@opentui/solid";
8432
9053
  import { createElement as _$createElement3 } from "@opentui/solid";
8433
9054
  import { createEffect, createMemo as createMemo2, createSignal as createSignal2, onCleanup as onCleanup3, Show } from "solid-js";
8434
- import { CliRenderEvents, CodeRenderable, RGBA, ScrollBoxRenderable } from "@opentui/core";
9055
+ import { CliRenderEvents, CodeRenderable as CodeRenderable2, RGBA as RGBA2, ScrollBoxRenderable } from "@opentui/core";
8435
9056
 
8436
9057
  // src/history-layout.ts
8437
9058
  init_define_REVIEW_PROMPTS();
@@ -8466,20 +9087,45 @@ import { setProp as _$setProp2 } from "@opentui/solid";
8466
9087
  import { use as _$use } from "@opentui/solid";
8467
9088
  import { createElement as _$createElement2 } from "@opentui/solid";
8468
9089
  import { createMemo, onCleanup as onCleanup2 } from "solid-js";
8469
- import { SyntaxStyle } from "@opentui/core";
9090
+ import { CodeRenderable, SyntaxStyle } from "@opentui/core";
9091
+
9092
+ // src/syntax-retirement.ts
9093
+ init_define_REVIEW_PROMPTS();
9094
+ async function retireDefaultMarkdownStyle(style, latestHighlights2) {
9095
+ await Promise.allSettled(latestHighlights2);
9096
+ style.destroy();
9097
+ }
9098
+
9099
+ // src/review-description.tsx
9100
+ function latestHighlights(node) {
9101
+ if (!node) return [];
9102
+ return [...node instanceof CodeRenderable ? [node.highlightingDone] : [], ...node.getChildren().flatMap(latestHighlights)];
9103
+ }
8470
9104
  function ReviewDescription(props) {
8471
- const style = createMemo(() => {
9105
+ let description;
9106
+ const style = createMemo((previous) => {
8472
9107
  const syntax = SyntaxStyle.fromStyles(reviewSyntaxStyles(props.api.theme.current));
8473
- onCleanup2(() => {
8474
- void props.api.renderer.idle().catch(() => {
8475
- }).finally(() => syntax.destroy());
8476
- });
9108
+ if (previous) {
9109
+ const pending = latestHighlights(description);
9110
+ if (description && !description.isDestroyed) {
9111
+ description.syntaxStyle = syntax;
9112
+ description.refreshStyles();
9113
+ }
9114
+ void retireDefaultMarkdownStyle(previous, pending);
9115
+ }
8477
9116
  return syntax;
8478
9117
  });
9118
+ onCleanup2(() => {
9119
+ const pending = latestHighlights(description);
9120
+ description?.destroyRecursively();
9121
+ void retireDefaultMarkdownStyle(style(), pending);
9122
+ });
8479
9123
  return (() => {
8480
9124
  var _el$ = _$createElement2("markdown");
8481
- var _ref$ = props.ref;
8482
- typeof _ref$ === "function" ? _$use(_ref$, _el$) : props.ref = _el$;
9125
+ _$use((value) => {
9126
+ description = value;
9127
+ props.ref?.(value);
9128
+ }, _el$);
8483
9129
  _$setProp2(_el$, "conceal", true);
8484
9130
  _$setProp2(_el$, "tableOptions", {
8485
9131
  style: "grid"
@@ -8508,7 +9154,7 @@ function owns(node, hit) {
8508
9154
  return node.num === hit || node instanceof ScrollBoxRenderable && owns(node.wrapper, hit) || node.getChildren().some((child) => owns(child, hit));
8509
9155
  }
8510
9156
  function ready(node) {
8511
- return !(node instanceof CodeRenderable && node.isHighlighting) && node.getChildren().every(ready);
9157
+ return !(node instanceof CodeRenderable2 && node.isHighlighting) && node.getChildren().every(ready);
8512
9158
  }
8513
9159
  function Button(props) {
8514
9160
  const [hover, setHover] = createSignal2(false);
@@ -8522,7 +9168,7 @@ function Button(props) {
8522
9168
  g,
8523
9169
  b
8524
9170
  } = theme.warning;
8525
- return 0.299 * r + 0.587 * g + 0.114 * b > 0.5 ? RGBA.fromInts(0, 0, 0) : RGBA.fromInts(255, 255, 255);
9171
+ return 0.299 * r + 0.587 * g + 0.114 * b > 0.5 ? RGBA2.fromInts(0, 0, 0) : RGBA2.fromInts(255, 255, 255);
8526
9172
  };
8527
9173
  return (() => {
8528
9174
  var _el$ = _$createElement3("box"), _el$2 = _$createElement3("text");
@@ -8905,7 +9551,7 @@ import { createComponent as _$createComponent3 } from "@opentui/solid";
8905
9551
  import { setProp as _$setProp4 } from "@opentui/solid";
8906
9552
  import { createElement as _$createElement4 } from "@opentui/solid";
8907
9553
  import { createEffect as createEffect2, createMemo as createMemo3, createSignal as createSignal3, Index, Match, onCleanup as onCleanup4, Show as Show2, Switch } from "solid-js";
8908
- import { CliRenderEvents as CliRenderEvents2, CodeRenderable as CodeRenderable2, RGBA as RGBA2, ScrollBoxRenderable as ScrollBoxRenderable2 } from "@opentui/core";
9554
+ import { CliRenderEvents as CliRenderEvents2, CodeRenderable as CodeRenderable3, RGBA as RGBA3, ScrollBoxRenderable as ScrollBoxRenderable2 } from "@opentui/core";
8909
9555
 
8910
9556
  // src/review-geometry.ts
8911
9557
  init_define_REVIEW_PROMPTS();
@@ -8921,11 +9567,43 @@ function reviewGeometry(input, footerContents = true) {
8921
9567
  }
8922
9568
  var ReviewGeometryProof = class {
8923
9569
  painted;
8924
- visible(input, frame = false) {
9570
+ renewed = false;
9571
+ invalidate() {
9572
+ this.renewed = true;
9573
+ }
9574
+ state(input, frame = false) {
8925
9575
  const regions = [input.panel, input.heading, input.rating, ...input.fast ? [] : [input.report, input.footer]];
8926
9576
  const bounds = [input.width, input.height, ...regions.flatMap((region) => region ? [region.x, region.y, region.width, region.height] : [])];
8927
- if (frame) this.painted = reviewGeometry(input) ? { regions, bounds } : void 0;
8928
- return reviewGeometry(input, false) && !!this.painted && regions.length === this.painted.regions.length && regions.every((region, i) => region === this.painted.regions[i]) && bounds.length === this.painted.bounds.length && bounds.every((value, i) => value === this.painted.bounds[i]);
9577
+ if (frame) {
9578
+ this.painted = reviewGeometry(input) ? { regions, bounds, owner: input.owner } : void 0;
9579
+ this.renewed = false;
9580
+ return this.painted ? "visible" : "lost";
9581
+ }
9582
+ const same = !this.renewed && !!this.painted && input.owner === this.painted.owner && regions.length === this.painted.regions.length && regions.every((region, i) => region === this.painted.regions[i]) && bounds.length === this.painted.bounds.length && bounds.every((value, i) => value === this.painted.bounds[i]);
9583
+ if (reviewGeometry(input, false)) return same ? "visible" : "pending";
9584
+ if (this.painted && input.height === this.painted.bounds[1] && regions.every((region, i) => region === this.painted.regions[i]) && bounds.slice(2).every((value, i) => value === this.painted.bounds[i + 2])) {
9585
+ const dx = input.width - this.painted.bounds[0];
9586
+ const shift = (region) => region && {
9587
+ x: region.x + dx,
9588
+ y: region.y,
9589
+ width: region.width,
9590
+ height: region.height,
9591
+ visible: region.visible,
9592
+ isDestroyed: region.isDestroyed
9593
+ };
9594
+ if (reviewGeometry({
9595
+ ...input,
9596
+ panel: shift(input.panel),
9597
+ heading: shift(input.heading),
9598
+ rating: shift(input.rating),
9599
+ report: shift(input.report),
9600
+ footer: shift(input.footer)
9601
+ }, false)) return "pending";
9602
+ }
9603
+ return "lost";
9604
+ }
9605
+ visible(input, frame = false) {
9606
+ return this.state(input, frame) === "visible";
8929
9607
  }
8930
9608
  };
8931
9609
 
@@ -8962,7 +9640,7 @@ function ReviewLoading(props) {
8962
9640
  brightness,
8963
9641
  alpha
8964
9642
  } = cell();
8965
- return RGBA2.fromValues(Math.min(1, base.r * brightness), Math.min(1, base.g * brightness), Math.min(1, base.b * brightness), alpha);
9643
+ return RGBA3.fromValues(Math.min(1, base.r * brightness), Math.min(1, base.g * brightness), Math.min(1, base.b * brightness), alpha);
8966
9644
  });
8967
9645
  return (() => {
8968
9646
  var _el$3 = _$createElement4("span");
@@ -8985,7 +9663,7 @@ function ReviewLoading(props) {
8985
9663
  })();
8986
9664
  }
8987
9665
  function highlightingComplete(node) {
8988
- return !(node instanceof CodeRenderable2 && node.isHighlighting) && node.getChildren().every(highlightingComplete);
9666
+ return !(node instanceof CodeRenderable3 && node.isHighlighting) && node.getChildren().every(highlightingComplete);
8989
9667
  }
8990
9668
  function ownsHit(node, hit) {
8991
9669
  return node.num === hit || node instanceof ScrollBoxRenderable2 && ownsHit(node.wrapper, hit) || node.getChildren().some((child) => ownsHit(child, hit));
@@ -9001,7 +9679,7 @@ function ReviewButton(props) {
9001
9679
  g,
9002
9680
  b
9003
9681
  } = theme.warning;
9004
- return 0.299 * r + 0.587 * g + 0.114 * b > 0.5 ? RGBA2.fromInts(0, 0, 0) : RGBA2.fromInts(255, 255, 255);
9682
+ return 0.299 * r + 0.587 * g + 0.114 * b > 0.5 ? RGBA3.fromInts(0, 0, 0) : RGBA3.fromInts(255, 255, 255);
9005
9683
  };
9006
9684
  return (() => {
9007
9685
  var _el$4 = _$createElement4("box"), _el$5 = _$createElement4("text");
@@ -9187,14 +9865,15 @@ function LiveReviewPanel(props) {
9187
9865
  return false;
9188
9866
  }
9189
9867
  };
9190
- const observeRender = (stage, eligible) => {
9868
+ const observeRender = (stage, eligible, proof, check) => {
9191
9869
  if (!historyProbe || !panel) return;
9192
- const countCode = (node) => Number(node instanceof CodeRenderable2) + node.getChildren().reduce((total, child) => total + countCode(child), 0);
9870
+ const countCode = (node) => Number(node instanceof CodeRenderable3) + node.getChildren().reduce((total, child) => total + countCode(child), 0);
9193
9871
  const assessment = view().assessment;
9194
9872
  const auto = view().autoApproval;
9195
9873
  try {
9196
9874
  const pending = historyProbe.observe(Object.freeze({
9197
9875
  stage,
9876
+ check,
9198
9877
  at: performance.now(),
9199
9878
  panel: panel.num,
9200
9879
  markdown: description?.num,
@@ -9208,6 +9887,7 @@ function LiveReviewPanel(props) {
9208
9887
  covered: coveredByProbe(),
9209
9888
  physical: ownsPanelProbes(panel),
9210
9889
  eligible,
9890
+ presentation: proof ?? presentation().state,
9211
9891
  auto: auto?.status,
9212
9892
  seconds: auto?.status === "countdown" ? auto.seconds : void 0,
9213
9893
  history: historyState().open ? historyState().status : void 0
@@ -9217,39 +9897,64 @@ function LiveReviewPanel(props) {
9217
9897
  } catch {
9218
9898
  }
9219
9899
  };
9220
- const visible = (frame = false) => {
9221
- if (!props.sidebarSession()) return;
9222
- if (!geometry.visible({
9900
+ const presentation = (frame = false) => {
9901
+ const lost = {
9902
+ state: "lost"
9903
+ };
9904
+ const pending = {
9905
+ state: "pending",
9906
+ id
9907
+ };
9908
+ if (!props.sidebarSession() || select()?.request.id !== id) return lost;
9909
+ const sidebar = props.sidebarState();
9910
+ if (sidebar === "lost") return lost;
9911
+ const layout = geometry.state({
9223
9912
  width: api.renderer.width,
9224
9913
  height: api.renderer.height,
9225
9914
  fast: config?.fastMode === true,
9915
+ owner: props.sidebarOwner(),
9226
9916
  panel,
9227
9917
  heading,
9228
9918
  rating: ratingRegion,
9229
9919
  report: scroll,
9230
9920
  footer
9231
- }, frame) || select()?.request.id !== id) return;
9232
- if (config?.fastMode) return ownsPanelProbes(panel) ? id : void 0;
9233
- if (!paintedAssessment || paintedAssessment !== view().assessment) return;
9921
+ }, frame);
9922
+ if (layout === "lost") return lost;
9923
+ if (sidebar === "pending" || layout === "pending") return pending;
9924
+ if (config?.fastMode) return ownsPanelProbes(panel) ? {
9925
+ state: "visible",
9926
+ id
9927
+ } : lost;
9928
+ if (!paintedAssessment || paintedAssessment !== view().assessment) return pending;
9234
9929
  if (readyAssessment !== paintedAssessment) {
9235
- if (!description || description.isDestroyed || !description.getChildrenCount() || !highlightingComplete(description)) return;
9930
+ if (!description || description.isDestroyed || !description.getChildrenCount() || !highlightingComplete(description)) return pending;
9236
9931
  readyAssessment = paintedAssessment;
9237
9932
  }
9238
- if (!ownsPanelProbes(panel) && !coveredByProbe()) return;
9239
- return id;
9933
+ if (!ownsPanelProbes(panel) && !coveredByProbe()) return lost;
9934
+ return {
9935
+ state: "visible",
9936
+ id
9937
+ };
9240
9938
  };
9241
- const releasePresentation = config?.autoApprove ? props.registerPresentation(visible) : void 0;
9939
+ const releasePresentation = config?.autoApprove ? props.registerPresentation((check = "eligibility") => {
9940
+ const proof = presentation();
9941
+ observeRender("presentation", proof.state === "visible", proof.state, check);
9942
+ return proof;
9943
+ }) : void 0;
9242
9944
  if (config?.autoApprove || props.traceFor) {
9945
+ const resize = () => geometry.invalidate();
9946
+ api.renderer.on(CliRenderEvents2.RESIZE, resize);
9243
9947
  const frame = () => {
9244
9948
  historyCover.frame();
9245
- const presented = visible(true);
9949
+ const proof = presentation(true);
9950
+ const presented = proof.state === "visible";
9246
9951
  const trace = props.traceFor?.(view());
9247
9952
  if (trace) {
9248
9953
  if (select()?.request.id === id && panel && ownsHit(panel, api.renderer.hitTest(panel.x + 2, panel.y + 1)) && ownsHit(panel, api.renderer.hitTest(panel.x + 2, panel.y + 3)) && (rating() !== void 0 || report() && description && !description.isDestroyed && description.getChildrenCount() && highlightingComplete(description))) trace.once("first-display");
9249
9954
  if (presented && paintedAssessment && paintedAssessment === view().assessment && ownsPanelProbes(panel)) trace.once("final-render");
9250
9955
  }
9251
9956
  if (config?.autoApprove) {
9252
- props.onPresented(presented);
9957
+ props.onPresented(proof);
9253
9958
  if (presented && view().autoApproval?.status === "countdown") trace?.once("approval-countdown");
9254
9959
  }
9255
9960
  observeRender("frame", !!presented);
@@ -9257,6 +9962,7 @@ function LiveReviewPanel(props) {
9257
9962
  api.renderer.on(CliRenderEvents2.FRAME, frame);
9258
9963
  onCleanup4(() => {
9259
9964
  api.renderer.off(CliRenderEvents2.FRAME, frame);
9965
+ api.renderer.off(CliRenderEvents2.RESIZE, resize);
9260
9966
  if (config?.autoApprove) {
9261
9967
  releasePresentation();
9262
9968
  props.onPresented();
@@ -9266,8 +9972,8 @@ function LiveReviewPanel(props) {
9266
9972
  return (
9267
9973
  // The verified host's sidebar is 42 columns, including its padding.
9268
9974
  // The app slot lets this cover its title, sections, and footer while
9269
- // the original sidebar remains mounted beneath it on root routes. Child
9270
- // routes have no native sidebar, but use this same physical presentation.
9975
+ // the original sidebar remains mounted beneath it on root routes. Descendant
9976
+ // routes hide the enclosing slot; automation has its own route-only proof.
9271
9977
  (() => {
9272
9978
  var _el$1 = _$createElement4("box"), _el$10 = _$createElement4("text"), _el$11 = _$createElement4("b"), _el$12 = _$createElement4("box"), _el$17 = _$createElement4("scrollbox");
9273
9979
  _$insertNode3(_el$1, _el$10);
@@ -9441,6 +10147,39 @@ function LiveReviewPanel(props) {
9441
10147
  );
9442
10148
  }
9443
10149
 
10150
+ // src/review-presentation.ts
10151
+ init_define_REVIEW_PROMPTS();
10152
+ import { createEffect as createEffect3, createMemo as createMemo4, untrack } from "solid-js";
10153
+ function observePresentationOwnership(ownership, presentation, presented) {
10154
+ const current = createMemo4(ownership, void 0, { equals: (a, b) => a.session === b.session && a.root === b.root && a.owner === b.owner && a.state === b.state && a.suppressed === b.suppressed });
10155
+ createEffect3(() => {
10156
+ current();
10157
+ untrack(() => presented(presentation()));
10158
+ });
10159
+ }
10160
+ var SidebarPresentation = class {
10161
+ owner;
10162
+ handoff;
10163
+ mount(session, width) {
10164
+ const token = /* @__PURE__ */ Symbol();
10165
+ this.owner = { session, token, wide: width > 120 };
10166
+ this.handoff = void 0;
10167
+ return token;
10168
+ }
10169
+ unmount(token, session, width, hidden) {
10170
+ const owner = this.owner;
10171
+ if (!owner || owner.token !== token) return;
10172
+ this.owner = void 0;
10173
+ this.handoff = !hidden && owner.session === session && owner.wide !== width > 120 ? { session } : void 0;
10174
+ }
10175
+ state(session) {
10176
+ return this.owner?.session === session ? "visible" : this.handoff?.session === session ? "pending" : "lost";
10177
+ }
10178
+ frame() {
10179
+ this.handoff = void 0;
10180
+ }
10181
+ };
10182
+
9444
10183
  // src/tui.tsx
9445
10184
  function contextReader(api, trace) {
9446
10185
  const location = () => ({
@@ -9540,11 +10279,21 @@ async function reviewTui(api, options, fileIO, observer, notificationBackend, hi
9540
10279
  const historyStore = new HistoryStore(api.state.path.state);
9541
10280
  const [views, setViews] = createSignal4([]);
9542
10281
  const [sidebar, setSidebar] = createSignal4();
10282
+ const sidebarProof = new SidebarPresentation();
10283
+ const routeSessions = new RouteSessions((id) => api.state.session.get(id));
10284
+ const [sessionRevision, setSessionRevision] = createSignal4(0);
10285
+ const session = (id) => {
10286
+ sessionRevision();
10287
+ return routeSessions.get(id);
10288
+ };
10289
+ for (const type of ["session.created", "session.updated"]) api.event.on(type, (event) => {
10290
+ if (routeSessions.remember(event.properties.info)) setSessionRevision((value) => value + 1);
10291
+ });
9543
10292
  const hostTrace = observer ? new DiagnosticTrace(observer, 0) : void 0;
9544
10293
  const traces = observer ? /* @__PURE__ */ new WeakMap() : void 0;
9545
10294
  let reviewSequence = 0;
9546
10295
  const reader2 = contextReader(api, hostTrace);
9547
- const modes = new SessionModes(new SessionModeStore(path15.join(api.state.path.state, "opencode-reviewer", "session-mode-v1"), api.state.path.directory), async (id, signal) => api.state.session.get(id) ?? reader2.session(id, signal));
10296
+ const modes = new SessionModes(new SessionModeStore(path15.join(api.state.path.state, "opencode-reviewer", "session-mode-v1"), api.state.path.directory), async (id, signal) => session(id) ?? reader2.session(id, signal));
9548
10297
  const history = new HistoryCoordinator(api.state.path.directory, historyStore, (id, signal) => modes.root(id, signal));
9549
10298
  const lifetime = lifetimeTracker(api, historyStore, (id, signal) => history.root(id, signal));
9550
10299
  const [historyState, setHistoryState] = createSignal4({
@@ -9617,36 +10366,33 @@ async function reviewTui(api, options, fileIO, observer, notificationBackend, hi
9617
10366
  }
9618
10367
  const files = new FileAccess(fileIO);
9619
10368
  const approval = approvalTransport(api.client, api.state.path.directory);
9620
- let visibleApproval = () => void 0;
9621
- const controller = new Controller(async (request, parent, onIdentified, onProgress, execution = {
10369
+ let visibleApproval = () => ({
10370
+ state: "lost"
10371
+ });
10372
+ let approvalPresentation = () => ({
10373
+ state: "lost"
10374
+ });
10375
+ const controller = new Controller((request, parent, onIdentified, onProgress, execution = {
9622
10376
  review: randomUUID5()
9623
10377
  }) => {
9624
10378
  const trace = observer ? new DiagnosticTrace(observer, ++reviewSequence) : void 0;
9625
10379
  if (trace) traces.set(request, trace);
9626
10380
  const reviewReader = trace ? contextReader(api, trace) : reader2;
9627
- let worker;
9628
- try {
9629
- return await withDeadline(parent, config?.timeoutMs ?? 3e4, async (signal) => {
9630
- const evidence = await evaluateEvidence(request, reviewReader, reviewOptions ?? {
9631
- reviewBash: true,
9632
- reviewEdits: true
9633
- }, config, configError, signal, onIdentified, files);
9634
- if (!evidence) return null;
9635
- signal.throwIfAborted();
9636
- const result = review(evidence, config, signal, {
9637
- prompts,
9638
- pricing: (model) => modelPricing(api.state.provider, config.baseURL, model),
9639
- onProgress,
9640
- onDiagnostics: trace?.forward,
9641
- observation: history.observation(request, evidence.kind, config, execution)
9642
- });
9643
- worker = result;
9644
- return result;
9645
- });
9646
- } finally {
9647
- await worker?.catch(() => {
10381
+ return ownedReview(parent, config?.timeoutMs ?? 3e4, async (signal) => {
10382
+ const evidence = await evaluateEvidence(request, reviewReader, reviewOptions ?? {
10383
+ reviewBash: true,
10384
+ reviewEdits: true
10385
+ }, config, configError, signal, onIdentified, files);
10386
+ if (!evidence) return null;
10387
+ signal.throwIfAborted();
10388
+ return review(evidence, config, signal, {
10389
+ prompts,
10390
+ pricing: (model) => modelPricing(api.state.provider, config.baseURL, model),
10391
+ onProgress,
10392
+ onDiagnostics: trace?.forward,
10393
+ observation: history.observation(request, evidence.kind, config, execution)
9648
10394
  });
9649
- }
10395
+ });
9650
10396
  }, (views2) => {
9651
10397
  setViews(views2);
9652
10398
  if (!controller.reconciling) notifications?.snapshot(controller.pendingViews);
@@ -9661,9 +10407,14 @@ async function reviewTui(api, options, fileIO, observer, notificationBackend, hi
9661
10407
  },
9662
10408
  once: (request, signal) => measured(traces?.get(request), "approval-reply", () => approval.once(request, signal))
9663
10409
  } : approval,
9664
- visibleID: () => visibleApproval()
10410
+ visibleID: () => {
10411
+ const proof = visibleApproval();
10412
+ return proof.state === "visible" ? proof.id : void 0;
10413
+ },
10414
+ presentation: (check) => approvalPresentation(check)
9665
10415
  },
9666
10416
  modes,
10417
+ session,
9667
10418
  onApproval: (fact) => {
9668
10419
  history.approval(fact);
9669
10420
  notifications?.fact(fact);
@@ -9678,6 +10429,8 @@ async function reviewTui(api, options, fileIO, observer, notificationBackend, hi
9678
10429
  api.event.on("message.updated", (event) => history.message(event.properties.info));
9679
10430
  api.event.on("session.deleted", (event) => {
9680
10431
  const info = event.properties.info;
10432
+ routeSessions.deleted(info.id);
10433
+ setSessionRevision((value) => value + 1);
9681
10434
  browser.deleted(info.id);
9682
10435
  const deletion = history.sessionDeleted(info);
9683
10436
  controller.deleted(info.id);
@@ -9724,27 +10477,28 @@ async function reviewTui(api, options, fileIO, observer, notificationBackend, hi
9724
10477
  api.slots.register({
9725
10478
  slots: {
9726
10479
  sidebar_content: (_ctx, props) => {
9727
- const token = /* @__PURE__ */ Symbol();
9728
- createEffect3(() => {
10480
+ createEffect4(() => {
10481
+ const token = sidebarProof.mount(props.session_id, api.renderer.width);
9729
10482
  setSidebar({
9730
10483
  sessionID: props.session_id,
9731
10484
  token
9732
10485
  });
9733
- onCleanup5(() => setSidebar((current) => current?.token === token ? void 0 : current));
10486
+ onCleanup5(() => {
10487
+ const route = api.route.current;
10488
+ sidebarProof.unmount(token, route.name === "session" ? route.params?.sessionID : void 0, api.renderer.width, api.ui.dialog.open || api.kv.get("sidebar") === "hide");
10489
+ setSidebar((current) => current?.token === token ? void 0 : current);
10490
+ });
9734
10491
  });
9735
10492
  return null;
9736
10493
  },
9737
10494
  app: () => {
9738
- const [width, setWidth] = createSignal4(api.renderer.width);
9739
- const resized = () => setWidth(api.renderer.width);
9740
- api.renderer.on(CliRenderEvents3.RESIZE, resized);
9741
- onCleanup5(() => api.renderer.off(CliRenderEvents3.RESIZE, resized));
9742
- createEffect3(() => {
10495
+ const handoff = new RouteHandoff();
10496
+ createEffect4(() => {
9743
10497
  const route = api.route.current;
9744
10498
  notifications?.routeChanged();
9745
10499
  browser.route(route.name === "session" ? route.params?.sessionID : void 0);
9746
10500
  });
9747
- createEffect3(() => {
10501
+ createEffect4(() => {
9748
10502
  const requested = revealHistory();
9749
10503
  if (!requested) return;
9750
10504
  const route = api.route.current;
@@ -9769,27 +10523,92 @@ async function reviewTui(api, options, fileIO, observer, notificationBackend, hi
9769
10523
  setRevealHistory(void 0);
9770
10524
  if (sidebar()?.sessionID !== requested.session) api.keymap.dispatchCommand("session.sidebar.toggle");
9771
10525
  });
9772
- if (notifications) createEffect3(() => {
10526
+ if (notifications) createEffect4(() => {
9773
10527
  const route = api.route.current;
9774
10528
  notifications?.visit(route.name === "session" ? route.params?.sessionID : void 0);
9775
10529
  });
9776
- const presentationSession = () => {
10530
+ const routeSession = () => {
9777
10531
  const route = api.route.current;
9778
10532
  if (api.ui.dialog.open || route.name !== "session") return;
9779
10533
  const id = route.params?.sessionID;
9780
- if (typeof id !== "string") return;
9781
- if (sidebar()?.sessionID === id) return id;
9782
- if (id && api.state.session.get(id)?.parentID && width() >= 120 && api.renderer.width >= 120) return id;
10534
+ return typeof id === "string" ? id : void 0;
10535
+ };
10536
+ const rootSession = () => {
10537
+ const id = routeSession();
10538
+ return id && conversationRoot(id, session) === id ? id : void 0;
9783
10539
  };
10540
+ const sidebarState = () => {
10541
+ const id = rootSession();
10542
+ if (!id) return "lost";
10543
+ sidebar();
10544
+ return sidebarProof.state(id);
10545
+ };
10546
+ const presentationSession = () => sidebarState() === "visible" ? routeSession() : void 0;
9784
10547
  const select = () => {
9785
10548
  const route = api.route.current;
9786
10549
  if (api.ui.dialog.open || route.name !== "session") return;
9787
10550
  const id = route.params?.sessionID;
9788
- return visibleReview(controller.views, typeof id === "string" ? id : void 0, (id2) => api.state.session.get(id2));
10551
+ return visibleReview(controller.views, typeof id === "string" ? id : void 0, session);
10552
+ };
10553
+ const presentation = (check, painted) => {
10554
+ const route = routeSession();
10555
+ const root = conversationRoot(route, session);
10556
+ handoff.observe(route, root);
10557
+ if (!route || !root || api.kv.get("sidebar") === "hide") return {
10558
+ state: "lost"
10559
+ };
10560
+ const selected = select();
10561
+ if (!selected) return {
10562
+ state: "lost"
10563
+ };
10564
+ if (route !== root) return routeCovered(api.renderer.root, api.renderer.width, api.renderer.height) ? {
10565
+ state: "lost"
10566
+ } : {
10567
+ state: "route-hidden",
10568
+ id: selected.request.id,
10569
+ root
10570
+ };
10571
+ const proof = painted ?? visibleApproval(check);
10572
+ if (proof.state === "lost" && handoff.pending(root)) return {
10573
+ state: "pending",
10574
+ id: selected.request.id
10575
+ };
10576
+ return proof;
10577
+ };
10578
+ approvalPresentation = presentation;
10579
+ const sidebarFrame = () => {
10580
+ sidebarProof.frame();
10581
+ handoff.frame();
10582
+ const route = routeSession();
10583
+ if (!route || conversationRoot(route, session) !== route || !select()) controller.presented(presentation());
9789
10584
  };
9790
- const current = createMemo4(() => {
10585
+ api.renderer.on(CliRenderEvents3.FRAME, sidebarFrame);
10586
+ onCleanup5(() => {
10587
+ api.renderer.off(CliRenderEvents3.FRAME, sidebarFrame);
10588
+ if (approvalPresentation === presentation) approvalPresentation = () => ({
10589
+ state: "lost"
10590
+ });
10591
+ controller.presented();
10592
+ });
10593
+ const current = createMemo5(() => {
10594
+ views();
10595
+ return select();
10596
+ });
10597
+ observePresentationOwnership(() => {
10598
+ const route = routeSession(), root = conversationRoot(route, session);
10599
+ return {
10600
+ session: route,
10601
+ root,
10602
+ owner: sidebar()?.token,
10603
+ state: sidebarState(),
10604
+ suppressed: !!root && root !== route && api.kv.get("sidebar") !== "hide"
10605
+ };
10606
+ }, () => presentation(), (proof) => controller.presented(proof));
10607
+ createEffect4(() => {
10608
+ const route = routeSession(), root = conversationRoot(route, session);
10609
+ if (!root || root === route) return;
9791
10610
  views();
9792
- return presentationSession() ? select() : void 0;
10611
+ untrack2(() => controller.presented(presentation()));
9793
10612
  });
9794
10613
  return (() => {
9795
10614
  var _el$ = _$createElement5("box");
@@ -9813,7 +10632,9 @@ async function reviewTui(api, options, fileIO, observer, notificationBackend, hi
9813
10632
  view,
9814
10633
  select,
9815
10634
  config,
9816
- sidebarSession: presentationSession,
10635
+ sidebarSession: routeSession,
10636
+ sidebarState,
10637
+ sidebarOwner: () => sidebar()?.token,
9817
10638
  historyState,
9818
10639
  historyCover,
9819
10640
  historyProbe,
@@ -9823,11 +10644,13 @@ async function reviewTui(api, options, fileIO, observer, notificationBackend, hi
9823
10644
  },
9824
10645
  onApprove: (id2) => controller.approveNow(id2),
9825
10646
  onCancel: (id2) => controller.cancelAutoApproval(id2),
9826
- onPresented: (id2) => controller.presented(id2),
10647
+ onPresented: (proof) => controller.presented(presentation(void 0, proof)),
9827
10648
  registerPresentation: (visible) => {
9828
10649
  visibleApproval = visible;
9829
10650
  return () => {
9830
- if (visibleApproval === visible) visibleApproval = () => void 0;
10651
+ if (visibleApproval === visible) visibleApproval = () => ({
10652
+ state: "lost"
10653
+ });
9831
10654
  };
9832
10655
  }
9833
10656
  });
@@ -9835,22 +10658,19 @@ async function reviewTui(api, options, fileIO, observer, notificationBackend, hi
9835
10658
  }), null);
9836
10659
  _$insert4(_el$, _$createComponent4(Show3, {
9837
10660
  get when() {
9838
- return _$memo3(() => !!(historyState().open && sidebar() && !api.ui.dialog.open && api.route.current.name === "session"))() && api.route.current.params?.sessionID === sidebar()?.sessionID;
10661
+ return _$memo3(() => !!historyState().open)() && rootSession();
9839
10662
  },
9840
- get children() {
9841
- return _$createComponent4(HistoryView, {
9842
- api,
9843
- controller: browser,
9844
- get state() {
9845
- return historyState();
9846
- },
9847
- input: historyInput,
9848
- cover: historyCover,
9849
- get session() {
9850
- return sidebar().sessionID;
9851
- }
9852
- });
9853
- }
10663
+ keyed: true,
10664
+ children: (session2) => _$createComponent4(HistoryView, {
10665
+ api,
10666
+ controller: browser,
10667
+ get state() {
10668
+ return historyState();
10669
+ },
10670
+ input: historyInput,
10671
+ cover: historyCover,
10672
+ session: session2
10673
+ })
9854
10674
  }), null);
9855
10675
  _$effect5((_$p) => _$setProp5(_el$, "visible", !!presentationSession() && (!!current() || !!sidebar() && historyState().open), _$p));
9856
10676
  return _el$;