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.
- package/README.md +33 -16
- package/dist/tui.js +1152 -332
- 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
|
|
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
|
|
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
|
|
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
|
|
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
|
-
|
|
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
|
-
|
|
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
|
|
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
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
2181
|
-
|
|
2182
|
-
if (!
|
|
2183
|
-
|
|
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
|
-
|
|
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 = (
|
|
2456
|
-
for (const scope of
|
|
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
|
-
|
|
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 (
|
|
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
|
-
/**
|
|
2641
|
-
presented(
|
|
2898
|
+
/** Physical presentation and intentional same-root suppression are distinct proofs. */
|
|
2899
|
+
presented(value) {
|
|
2642
2900
|
if (this.stopped) return;
|
|
2643
|
-
|
|
2644
|
-
|
|
2645
|
-
|
|
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 (
|
|
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.
|
|
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.
|
|
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
|
-
|
|
2735
|
-
|
|
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
|
-
|
|
2745
|
-
|
|
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.
|
|
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,
|
|
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.
|
|
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.
|
|
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
|
|
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.
|
|
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
|
|
3170
|
+
const abort2 = () => {
|
|
2836
3171
|
if (!dispatched || signal.reason !== permissionResolved) acknowledgement.abort(signal.reason);
|
|
2837
3172
|
};
|
|
2838
|
-
signal.addEventListener("abort",
|
|
2839
|
-
if (signal.aborted)
|
|
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.
|
|
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
|
|
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",
|
|
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.
|
|
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)
|
|
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.
|
|
2975
|
-
|
|
2976
|
-
if (
|
|
2977
|
-
|
|
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
|
-
|
|
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
|
|
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
|
-
|
|
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
|
|
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
|
|
4256
|
-
while (
|
|
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 (
|
|
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,
|
|
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
|
-
|
|
4272
|
-
|
|
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.
|
|
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
|
-
|
|
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")
|
|
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$
|
|
4803
|
-
_$insertNode(_el$, _el$
|
|
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",
|
|
4820
|
-
_$
|
|
4821
|
-
|
|
4822
|
-
|
|
4823
|
-
|
|
4824
|
-
|
|
4825
|
-
|
|
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.
|
|
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(
|
|
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
|
|
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
|
-
|
|
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
|
|
5596
|
-
|
|
5597
|
-
|
|
5598
|
-
|
|
5599
|
-
|
|
5600
|
-
|
|
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
|
|
5676
|
-
if (!
|
|
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(
|
|
6203
|
+
const root = this.roots.get(rootID2);
|
|
5695
6204
|
if (!root || request.received < root.since) return;
|
|
5696
|
-
const target = { root:
|
|
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
|
-
|
|
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.
|
|
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
|
|
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 (
|
|
6193
|
-
clearTimeout(
|
|
6194
|
-
delete
|
|
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
|
|
6718
|
+
delete timers2[which];
|
|
6199
6719
|
callUserCallback(() => __emscripten_timeout(which, _emscripten_get_now()));
|
|
6200
6720
|
}, timeout_ms);
|
|
6201
|
-
|
|
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
|
|
7692
|
-
|
|
7693
|
-
|
|
7694
|
-
|
|
7695
|
-
this.
|
|
7696
|
-
|
|
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
|
-
|
|
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 (
|
|
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 (
|
|
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
|
-
|
|
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
|
-
|
|
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 (
|
|
8172
|
-
|
|
8173
|
-
this.
|
|
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
|
-
|
|
8177
|
-
this.
|
|
8178
|
-
|
|
8179
|
-
|
|
8180
|
-
|
|
8181
|
-
|
|
8182
|
-
|
|
8183
|
-
|
|
8184
|
-
|
|
8185
|
-
|
|
8186
|
-
|
|
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.
|
|
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
|
-
|
|
9105
|
+
let description;
|
|
9106
|
+
const style = createMemo((previous) => {
|
|
8472
9107
|
const syntax = SyntaxStyle.fromStyles(reviewSyntaxStyles(props.api.theme.current));
|
|
8473
|
-
|
|
8474
|
-
|
|
8475
|
-
|
|
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
|
-
|
|
8482
|
-
|
|
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
|
|
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 ?
|
|
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
|
|
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
|
-
|
|
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)
|
|
8928
|
-
|
|
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
|
|
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
|
|
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 ?
|
|
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
|
|
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
|
|
9221
|
-
|
|
9222
|
-
|
|
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)
|
|
9232
|
-
if (
|
|
9233
|
-
if (
|
|
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
|
|
9933
|
+
if (!ownsPanelProbes(panel) && !coveredByProbe()) return lost;
|
|
9934
|
+
return {
|
|
9935
|
+
state: "visible",
|
|
9936
|
+
id
|
|
9937
|
+
};
|
|
9240
9938
|
};
|
|
9241
|
-
const releasePresentation = config?.autoApprove ? props.registerPresentation(
|
|
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
|
|
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(
|
|
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.
|
|
9270
|
-
// routes
|
|
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) =>
|
|
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 = () =>
|
|
9621
|
-
|
|
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
|
-
|
|
9628
|
-
|
|
9629
|
-
|
|
9630
|
-
|
|
9631
|
-
|
|
9632
|
-
|
|
9633
|
-
|
|
9634
|
-
|
|
9635
|
-
|
|
9636
|
-
|
|
9637
|
-
|
|
9638
|
-
|
|
9639
|
-
|
|
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: () =>
|
|
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
|
-
|
|
9728
|
-
|
|
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(() =>
|
|
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
|
|
9739
|
-
|
|
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
|
-
|
|
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)
|
|
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
|
|
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
|
-
|
|
9781
|
-
|
|
9782
|
-
|
|
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,
|
|
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
|
-
|
|
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
|
-
|
|
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:
|
|
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: (
|
|
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 = () =>
|
|
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(() => !!
|
|
10661
|
+
return _$memo3(() => !!historyState().open)() && rootSession();
|
|
9839
10662
|
},
|
|
9840
|
-
|
|
9841
|
-
|
|
9842
|
-
|
|
9843
|
-
|
|
9844
|
-
|
|
9845
|
-
|
|
9846
|
-
|
|
9847
|
-
|
|
9848
|
-
|
|
9849
|
-
|
|
9850
|
-
|
|
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$;
|