opencode-reviewer 0.5.0 → 0.6.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (3) hide show
  1. package/README.md +19 -9
  2. package/dist/tui.js +416 -192
  3. package/package.json +1 -1
package/README.md CHANGED
@@ -92,6 +92,12 @@ The rating remains provisional until the full response is validated. A format re
92
92
  clears the preview and restores the loading indicator. Auto-approval
93
93
  starts only after the full response is validated and rendered.
94
94
 
95
+ Transient connection failures and HTTP 408/429/500/502/503/504 responses get up to
96
+ two internal retries within `timeoutMs`, honoring server cooldowns. A stream that
97
+ has already delivered assessment text is not restarted. This is separate from
98
+ `formatRetries`; no additional setting is needed. Retried requests can incur
99
+ additional provider charges, and unreported usage remains unknown.
100
+
95
101
  During an auto-approval countdown, click the countdown to allow once immediately,
96
102
  or Cancel to leave the request manual. Hiding or covering the panel also cancels
97
103
  that request's countdown. Cancel before using native Allow always or rejection
@@ -110,6 +116,12 @@ costs use reported charges; other endpoints use available catalog estimates.
110
116
  Received usage counts even if a review fails or is interrupted. Unreported charges
111
117
  are missing from the totals.
112
118
 
119
+ Lifetime statistics also show **Safe** and **Unsafe** review counts. Each completed,
120
+ validated review counts once, even without usage data. Previews, retries and failed
121
+ or interrupted analyses do not count. Re-enabling a conversation can produce a new
122
+ review, which counts again. The totals persist across restarts; earlier ratings
123
+ were not recorded and cannot be reconstructed.
124
+
113
125
  Reviews send the pending request, latest user prompt, project context, and relevant
114
126
  file snapshots, diffs, or tool arguments to your endpoint before approval. Shell
115
127
  file snapshots can follow symlinks outside the project. Missing evidence is noted
@@ -117,21 +129,19 @@ in the report; ratings are advice based on the supplied evidence.
117
129
 
118
130
  ### Desktop notifications
119
131
 
120
- Transient banners have an **Opencode** heading and a small status icon: green
121
- checkmark for approvals, orange exclamation mark for attention/countdowns, red X
132
+ Transient banners have an **Opencode (Session name)** heading and a small status icon: green
133
+ checkmark for approvals, orange exclamation mark for attention, red X
122
134
  for errors, and a neutral code mark for completed responses. GNOME controls the
123
- heading's font weight. The event
124
- message and conversation title appear beneath it, even while the terminal is focused.
125
- An explicit separator keeps them distinct on desktops that flatten banner text:
135
+ heading's font weight. The event message appears beneath it, even while the
136
+ terminal is focused:
126
137
 
127
138
  - **Session needs attention:** questions and manual permissions. Reviewed requests
128
139
  wait for a final validated assessment; failures, canceled automation, and a Safe
129
140
  review blocked from starting its countdown also notify. Unreviewed requests
130
141
  notify immediately, including when conversation review is disabled.
131
- - **Reviewer will approve permission in X s:** one announcement when a positive
132
- countdown starts, using the attention sound. Confirmed automatic approval then
133
- sends **Reviewer approved a permission** with the approval sound. Zero delay
134
- sends only the confirmed approval notification. Approval sounds are limited to
142
+ - **Reviewer approved a permission:** sent with the approval sound after confirmed
143
+ automatic approval, for both positive and zero delays. The countdown is silent.
144
+ Approval sounds are limited to
135
145
  one every two seconds; every eligible banner is retained for delivery.
136
146
  - **Session error:** an unrecovered session/provider failure, not a review failure.
137
147
  - **Session ended:** a completed root-agent response, not a question/permission
package/dist/tui.js CHANGED
@@ -550,10 +550,10 @@ var require_shell_quote = __commonJS({
550
550
  // src/tui.tsx
551
551
  init_define_REVIEW_PROMPTS();
552
552
  init_define_REVIEW_SOUNDS();
553
- import { createTextNode as _$createTextNode } from "@opentui/solid";
554
- import { insertNode as _$insertNode } from "@opentui/solid";
555
553
  import { use as _$use } from "@opentui/solid";
556
554
  import { effect as _$effect } from "@opentui/solid";
555
+ import { createTextNode as _$createTextNode } from "@opentui/solid";
556
+ import { insertNode as _$insertNode } from "@opentui/solid";
557
557
  import { insert as _$insert } from "@opentui/solid";
558
558
  import { memo as _$memo2 } from "@opentui/solid";
559
559
  import { createComponent as _$createComponent2 } from "@opentui/solid";
@@ -1128,6 +1128,79 @@ async function override(directory, name, signal) {
1128
1128
  // src/usage.ts
1129
1129
  init_define_REVIEW_PROMPTS();
1130
1130
  init_define_REVIEW_SOUNDS();
1131
+
1132
+ // src/ui-text.ts
1133
+ init_define_REVIEW_PROMPTS();
1134
+ init_define_REVIEW_SOUNDS();
1135
+ var uiText = {
1136
+ review: {
1137
+ heading: "Permission analysis",
1138
+ safe: "\u2713 Safe",
1139
+ unsafe: "\u2717 Unsafe",
1140
+ unavailable: "! Analysis unavailable",
1141
+ evaluating: "Evaluating",
1142
+ retrying: "Retrying",
1143
+ staticIndicator: "[\u22EF]",
1144
+ failed: "Review failed",
1145
+ invalidConfiguration: "Invalid configuration"
1146
+ },
1147
+ autoApproval: {
1148
+ countdown: (seconds) => `Allowed in ${seconds}s`,
1149
+ checking: "Checking\u2026",
1150
+ cancel: "Cancel",
1151
+ allowing: "Allowing\u2026",
1152
+ cancelled: "Auto-approval canceled",
1153
+ unavailable: "! Auto-approval unavailable. Use native controls."
1154
+ },
1155
+ commands: {
1156
+ category: "Reviewer",
1157
+ enable: "Reviewer: Enable for conversation",
1158
+ disable: "Reviewer: Disable for conversation",
1159
+ lifetime: "Reviewer: Lifetime usage"
1160
+ },
1161
+ sessionMode: {
1162
+ saved: (enabled) => enabled ? "Reviewer enabled for this conversation." : "Reviewer disabled for this conversation.",
1163
+ saveFailed: (enabled) => enabled ? "Reviewer enabled locally, but saving failed. Resume may use the previous setting." : "Reviewer disabled locally, but saving failed. Resume may use the previous setting.",
1164
+ unavailable: "Reviewer setting unavailable. Session ancestry or saved mode could not be read."
1165
+ },
1166
+ usage: {
1167
+ tokens: (input, output) => `token: ${input} in ${output} out`,
1168
+ cost: (amount2) => `cost: $${amount2}`
1169
+ },
1170
+ lifetime: {
1171
+ title: "Reviewer lifetime usage",
1172
+ loading: "Loading recorded usage\u2026",
1173
+ unavailable: "Lifetime usage unavailable. Recorded totals have not been reset.",
1174
+ inlineUnavailable: "lifetime: usage unavailable",
1175
+ empty: "lifetime: no recorded usage",
1176
+ costUnavailable: "lifetime: cost unavailable",
1177
+ cost: (amount2, partial) => `lifetime: $${amount2}${partial ? " (partial pricing)" : ""}`,
1178
+ requests: (count3) => `${count3} requests with recorded usage`,
1179
+ tokens: (input, output, partial) => `token: ${input} in ${output} out${partial ? " (partial coverage)" : ""}`,
1180
+ tokensUnavailable: "tokens unavailable",
1181
+ tokenCoverage: (available, total) => `Token counts available: ${available}/${total} requests`,
1182
+ pricingCoverage: (available, total) => `Pricing available: ${available}/${total} requests`,
1183
+ since: (date) => `Recorded since: ${date}`,
1184
+ ratings: (safe, unsafe) => `Safe: ${safe} \xB7 Unsafe: ${unsafe}`,
1185
+ ratingsSince: (date) => `Ratings recorded since: ${date}`,
1186
+ ratingsExplanation: "Ratings count completed, validated reviews, including those without usage data. Previews, retries and failed or interrupted analyses do not count. A fresh review after re-enabling counts again. Earlier ratings were not recorded and cannot be recovered.",
1187
+ explanation: "Received usage only, including interrupted or unsuccessful reviews. Unreported charges remain unknown. Costs combine endpoint-reported amounts and catalog estimates; legacy history retains its original estimates. Earlier unrecorded usage cannot be recovered."
1188
+ },
1189
+ notifications: {
1190
+ application: "Opencode",
1191
+ heading: (session) => `Opencode (${session})`,
1192
+ openSession: "Open session",
1193
+ fallbackSession: "OpenCode session",
1194
+ attention: "Session needs attention",
1195
+ approved: "Reviewer approved a permission",
1196
+ error: "Session error",
1197
+ ended: "Session ended",
1198
+ // Arguments are escaped by the desktop adapter before formatting.
1199
+ body: (event) => event
1200
+ }
1201
+ };
1202
+
1203
+ // src/usage.ts
1131
1204
  var record = (value) => value && typeof value === "object" && !Array.isArray(value) ? value : {};
1132
1205
  var count = (value) => typeof value === "number" && Number.isSafeInteger(value) && value >= 0;
1133
1206
  var rate = (value) => typeof value === "number" && Number.isFinite(value) && value >= 0;
@@ -1217,8 +1290,8 @@ function sumUsage(previous, next) {
1217
1290
  }
1218
1291
  function usageText(usage) {
1219
1292
  return [
1220
- ...count(usage.input) && count(usage.output) ? [`token: ${usage.input} in ${usage.output} out`] : [],
1221
- ...usage.cost === void 0 ? [] : [`cost: $${usage.cost.toFixed(4)}`]
1293
+ ...count(usage.input) && count(usage.output) ? [uiText.usage.tokens(usage.input, usage.output)] : [],
1294
+ ...usage.cost === void 0 ? [] : [uiText.usage.cost(usage.cost.toFixed(4))]
1222
1295
  ].join("\n");
1223
1296
  }
1224
1297
 
@@ -1532,6 +1605,74 @@ function measured(trace, phase, run) {
1532
1605
  return trace ? trace.measure(phase, run) : run();
1533
1606
  }
1534
1607
 
1608
+ // src/transport-retry.ts
1609
+ init_define_REVIEW_PROMPTS();
1610
+ init_define_REVIEW_SOUNDS();
1611
+ import { setTimeout as sleep } from "node:timers/promises";
1612
+ var statuses = /* @__PURE__ */ new Set([408, 429, 500, 502, 503, 504]);
1613
+ var networkCodes = /* @__PURE__ */ new Set([
1614
+ "EAI_AGAIN",
1615
+ "ECONNRESET",
1616
+ "ECONNREFUSED",
1617
+ "ECONNABORTED",
1618
+ "EPIPE",
1619
+ "ETIMEDOUT",
1620
+ "ENETUNREACH",
1621
+ "EHOSTUNREACH",
1622
+ "UND_ERR_CONNECT_TIMEOUT",
1623
+ "UND_ERR_HEADERS_TIMEOUT",
1624
+ "UND_ERR_BODY_TIMEOUT",
1625
+ "UND_ERR_SOCKET"
1626
+ ]);
1627
+ var TransientTransportError = class extends Error {
1628
+ constructor(message, notBefore = 0) {
1629
+ super(message);
1630
+ this.notBefore = notBefore;
1631
+ }
1632
+ notBefore;
1633
+ };
1634
+ function networkFailure(error, message) {
1635
+ for (let depth = 0; depth < 4 && error && typeof error === "object"; depth++) {
1636
+ const value = error;
1637
+ if (typeof value.code === "string") {
1638
+ return networkCodes.has(value.code) ? new TransientTransportError(message) : new Error(message);
1639
+ }
1640
+ error = value.cause;
1641
+ }
1642
+ return new Error(message);
1643
+ }
1644
+ function retryAfter(value, now = Date.now()) {
1645
+ if (value === null) return 0;
1646
+ value = value.trim();
1647
+ if (/^\d+$/.test(value)) return Number(value) * 1e3;
1648
+ if (!/^(?:[A-Za-z]{3}, \d{2} [A-Za-z]{3} \d{4} \d{2}:\d{2}:\d{2} GMT|[A-Za-z]+, \d{2}-[A-Za-z]{3}-\d{2} \d{2}:\d{2}:\d{2} GMT|[A-Za-z]{3} [A-Za-z]{3} [ \d]\d \d{2}:\d{2}:\d{2} \d{4})$/.test(value)) return 0;
1649
+ const date = Date.parse(value.endsWith("GMT") ? value : `${value} GMT`);
1650
+ return Number.isFinite(date) ? Math.max(0, date - now) : 0;
1651
+ }
1652
+ function httpFailure(response) {
1653
+ const message = `Reviewer HTTP ${response.status}`;
1654
+ return statuses.has(response.status) ? new TransientTransportError(message, performance.now() + retryAfter(response.headers.get("retry-after"))) : new Error(message);
1655
+ }
1656
+ var TransportRetries = class {
1657
+ used = 0;
1658
+ async wait(error, signal) {
1659
+ reviewStage(signal, "Reviewer response");
1660
+ if (!(error instanceof TransientTransportError) || this.used >= 2) return false;
1661
+ const base = 250 * 2 ** this.used;
1662
+ const delay = Math.max(base + Math.random() * base, error.notBefore - performance.now());
1663
+ if (delay + 250 >= remainingTime(signal)) return false;
1664
+ this.used++;
1665
+ try {
1666
+ await sleep(Math.ceil(delay), void 0, { signal });
1667
+ } catch {
1668
+ signal.throwIfAborted();
1669
+ throw error;
1670
+ }
1671
+ reviewStage(signal, "Reviewer response");
1672
+ return remainingTime(signal) > 250;
1673
+ }
1674
+ };
1675
+
1535
1676
  // src/reviewer.ts
1536
1677
  function abortable(promise, signal, late) {
1537
1678
  return new Promise((resolve, reject) => {
@@ -1576,9 +1717,9 @@ async function readBody(response, signal, consume) {
1576
1717
  let part;
1577
1718
  try {
1578
1719
  part = await abortable(reader2.read(), signal);
1579
- } catch {
1720
+ } catch (error) {
1580
1721
  signal.throwIfAborted();
1581
- throw new Error("Reviewer response read failed");
1722
+ throw networkFailure(error, "Reviewer response read failed");
1582
1723
  }
1583
1724
  signal.throwIfAborted();
1584
1725
  if (part.done) break;
@@ -1677,6 +1818,15 @@ async function streamedAssessment(response, signal, accounting, progress, firstC
1677
1818
  return assessment;
1678
1819
  }
1679
1820
  async function review(evidence, config, signal, fetcher = fetch, environment = process.env, prompts = BUILTIN_PROMPTS, pricing, onUsage, onProgress, onDiagnostics) {
1821
+ if (remainingTime(signal) === Infinity) {
1822
+ let worker;
1823
+ try {
1824
+ return await withDeadline(signal, config.timeoutMs, (bounded) => worker = review(evidence, config, bounded, fetcher, environment, prompts, pricing, onUsage, onProgress, onDiagnostics));
1825
+ } finally {
1826
+ await worker?.catch(() => {
1827
+ });
1828
+ }
1829
+ }
1680
1830
  const key = config.apiKey ?? (config.apiKeyEnv ? environment[config.apiKeyEnv]?.trim() : void 0);
1681
1831
  if (config.apiKeyEnv && !key) throw new Error(`API key environment variable ${config.apiKeyEnv} is unset or empty`);
1682
1832
  const prompt2 = prompts[evidence.kind];
@@ -1685,7 +1835,9 @@ async function review(evidence, config, signal, fetcher = fetch, environment = p
1685
1835
  { role: "user", content: JSON.stringify(evidence) }
1686
1836
  ];
1687
1837
  let usage;
1688
- for (let attempt = 0; attempt <= config.formatRetries; attempt++) {
1838
+ let corrections = 0;
1839
+ const retries = new TransportRetries();
1840
+ for (let attempt = 0; ; attempt++) {
1689
1841
  signal.throwIfAborted();
1690
1842
  const progress = (phase, preview) => {
1691
1843
  if (signal.aborted) return;
@@ -1697,6 +1849,10 @@ async function review(evidence, config, signal, fetcher = fetch, environment = p
1697
1849
  };
1698
1850
  progress(attempt ? "retrying" : "evaluating");
1699
1851
  const accounting = usageAttempt(config.baseURL, config.model, pricing, onUsage);
1852
+ const requestAbort = new AbortController();
1853
+ let contentStarted = false;
1854
+ let failed = false;
1855
+ let failure;
1700
1856
  try {
1701
1857
  reviewStage(signal, "Reviewer response");
1702
1858
  let response;
@@ -1708,28 +1864,32 @@ async function review(evidence, config, signal, fetcher = fetch, environment = p
1708
1864
  stream: config.stream,
1709
1865
  ...config.stream ? { stream_options: { include_usage: true } } : {}
1710
1866
  });
1867
+ reviewStage(signal, "Reviewer response");
1711
1868
  diagnose = onDiagnostics ? diagnosticAttempt(onDiagnostics, attempt) : void 0;
1712
1869
  response = await abortable(fetcher(`${config.baseURL}/chat/completions`, {
1713
1870
  method: "POST",
1714
1871
  redirect: "error",
1715
- signal,
1872
+ signal: AbortSignal.any([signal, requestAbort.signal]),
1716
1873
  headers: { "Content-Type": "application/json", ...key ? { Authorization: `Bearer ${key}` } : {} },
1717
1874
  body
1718
1875
  }), signal, cancelBody);
1719
1876
  diagnose?.("headers");
1720
- } catch {
1877
+ } catch (error) {
1721
1878
  signal.throwIfAborted();
1722
- throw new Error("Reviewer network request failed");
1879
+ throw networkFailure(error, "Reviewer network request failed");
1723
1880
  }
1724
1881
  if (!response.ok) {
1725
1882
  cancelBody(response);
1726
- throw new Error(`Reviewer HTTP ${response.status}`);
1883
+ throw httpFailure(response);
1727
1884
  }
1728
1885
  let assessment;
1729
1886
  if (config.stream) assessment = await streamedAssessment(response, signal, accounting, (preview) => {
1730
1887
  if (typeof preview?.safe === "boolean") diagnose?.("first-rating");
1731
1888
  progress("streaming", preview);
1732
- }, diagnose ? () => diagnose("first-content") : void 0);
1889
+ }, () => {
1890
+ contentStarted = true;
1891
+ diagnose?.("first-content");
1892
+ });
1733
1893
  else {
1734
1894
  let envelope;
1735
1895
  try {
@@ -1747,14 +1907,17 @@ async function review(evidence, config, signal, fetcher = fetch, environment = p
1747
1907
  if (!object(choice) || !object(message) || typeof message.content !== "string" || !textMetadata(message) || object(envelope) && envelope.error != null || choice.error != null || choice.index !== void 0 && choice.index !== 0 || choice.finish_reason !== void 0 && choice.finish_reason !== "stop") {
1748
1908
  throw new Error("Reviewer API did not return a text assessment");
1749
1909
  }
1750
- if (message.content) diagnose?.("first-content");
1910
+ if (message.content) {
1911
+ contentStarted = true;
1912
+ diagnose?.("first-content");
1913
+ }
1751
1914
  assessment = new StreamingAssessment();
1752
1915
  assessment.push(message.content);
1753
1916
  if (diagnose && typeof assessment.preview()?.safe === "boolean") diagnose("first-rating");
1754
1917
  }
1755
1918
  reviewStage(signal, "Assessment validation");
1756
1919
  const currentUsage = accounting.current();
1757
- usage = attempt === 0 ? currentUsage : sumUsage(usage, currentUsage);
1920
+ const reportUsage = attempt === 0 ? currentUsage : sumUsage(usage, currentUsage);
1758
1921
  try {
1759
1922
  let result;
1760
1923
  try {
@@ -1764,20 +1927,26 @@ async function review(evidence, config, signal, fetcher = fetch, environment = p
1764
1927
  }
1765
1928
  if (config.stream) progress("streaming", result);
1766
1929
  signal.throwIfAborted();
1767
- return { ...result, ...usage ? { usage } : {} };
1930
+ return { ...result, ...reportUsage ? { usage: reportUsage } : {} };
1768
1931
  } catch (error) {
1769
1932
  if (!(error instanceof AssessmentFormatError)) throw error;
1770
- if (attempt === config.formatRetries) throw new Error("Reviewer response format invalid after configured attempts");
1933
+ if (corrections++ === config.formatRetries) throw new Error("Reviewer response format invalid after configured attempts");
1771
1934
  messages.push(
1772
1935
  { role: "assistant", content: assessment.content },
1773
1936
  { role: "user", content: correctionPrompt(error.message) }
1774
1937
  );
1775
1938
  }
1939
+ } catch (error) {
1940
+ requestAbort.abort();
1941
+ if (contentStarted) throw error;
1942
+ failed = true;
1943
+ failure = error;
1776
1944
  } finally {
1777
- accounting.finalize();
1945
+ const currentUsage = accounting.finalize();
1946
+ usage = attempt === 0 ? currentUsage : sumUsage(usage, currentUsage);
1778
1947
  }
1948
+ if (failed && !await retries.wait(failure, signal)) throw failure;
1779
1949
  }
1780
- throw new Error("Reviewer exhausted attempts");
1781
1950
  }
1782
1951
 
1783
1952
  // src/classification.ts
@@ -1909,7 +2078,7 @@ var clock = {
1909
2078
  };
1910
2079
  var permissionResolved = Object.freeze({ reason: "permission resolved" });
1911
2080
  var Controller = class {
1912
- constructor(evaluate, changed, options = { reviewBash: true, reviewEdits: true }, approval, time = clock, modes, approvalObserver) {
2081
+ constructor(evaluate, changed, options = { reviewBash: true, reviewEdits: true }, approval, time = clock, modes, approvalObserver, ratingObserver) {
1913
2082
  this.evaluate = evaluate;
1914
2083
  this.changed = changed;
1915
2084
  this.options = options;
@@ -1917,6 +2086,7 @@ var Controller = class {
1917
2086
  this.time = time;
1918
2087
  this.modes = modes;
1919
2088
  this.approvalObserver = approvalObserver;
2089
+ this.ratingObserver = ratingObserver;
1920
2090
  }
1921
2091
  evaluate;
1922
2092
  changed;
@@ -1925,6 +2095,7 @@ var Controller = class {
1925
2095
  time;
1926
2096
  modes;
1927
2097
  approvalObserver;
2098
+ ratingObserver;
1928
2099
  entries = /* @__PURE__ */ new Map();
1929
2100
  stopped = false;
1930
2101
  version = 0;
@@ -2217,12 +2388,19 @@ var Controller = class {
2217
2388
  this.clearProgress(entry);
2218
2389
  if (active()) {
2219
2390
  entry.view = assessment ? { ...entry.view, status: "complete", assessment, progress: void 0 } : { ...entry.view, status: "unrelated", progress: void 0 };
2391
+ if (assessment) {
2392
+ try {
2393
+ void Promise.resolve(this.ratingObserver?.(assessment.safe)).catch(() => {
2394
+ });
2395
+ } catch {
2396
+ }
2397
+ }
2220
2398
  this.publish();
2221
2399
  }
2222
2400
  }, (error) => {
2223
2401
  this.clearProgress(entry);
2224
2402
  if (active()) {
2225
- entry.view = { ...entry.view, status: entry.view.status === "identifying" ? "unidentified" : "unavailable", progress: void 0, error: error instanceof Error ? error.message : "Review failed" };
2403
+ entry.view = { ...entry.view, status: entry.view.status === "identifying" ? "unidentified" : "unavailable", progress: void 0, error: error instanceof Error ? error.message : uiText.review.failed };
2226
2404
  this.publish();
2227
2405
  }
2228
2406
  });
@@ -3344,7 +3522,7 @@ async function evaluateEvidence(request, reader2, options, config, configError,
3344
3522
  if (!classified) return null;
3345
3523
  if (!enabledKind(classified.kind, options)) return null;
3346
3524
  onIdentified();
3347
- if (!config) throw new Error(configError || "Invalid configuration");
3525
+ if (!config) throw new Error(configError || uiText.review.invalidConfiguration);
3348
3526
  const scope = files.scope(signal);
3349
3527
  if (classified.kind === "shell") {
3350
3528
  const context = await loadContext(request, reader2, signal, scope, invocation);
@@ -3467,12 +3645,25 @@ import { constants as constants3 } from "node:fs";
3467
3645
  import { mkdir, open as open3, readdir as readdir2, rename, unlink } from "node:fs/promises";
3468
3646
  import { randomUUID } from "node:crypto";
3469
3647
  import path8 from "node:path";
3470
- var empty = () => ({ requests: 0, tokenRequests: 0, input: 0, output: 0, priced: 0, cost: 0, since: null });
3648
+ var empty = () => ({
3649
+ requests: 0,
3650
+ tokenRequests: 0,
3651
+ input: 0,
3652
+ output: 0,
3653
+ priced: 0,
3654
+ cost: 0,
3655
+ since: null,
3656
+ safe: 0,
3657
+ unsafe: 0,
3658
+ ratingsSince: null
3659
+ });
3471
3660
  var count2 = (value) => typeof value === "number" && Number.isSafeInteger(value) && value >= 0;
3472
3661
  var amount = (value) => typeof value === "number" && Number.isFinite(value) && value >= 0;
3473
3662
  var snapshotName = /^[0-9a-f]{8}(?:-[0-9a-f]{4}){3}-[0-9a-f]{12}\.json$/;
3474
3663
  function validate(value) {
3475
- if (!value || ![value.requests, value.tokenRequests, value.input, value.output, value.priced].every(count2) || !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 ? !count2(value.since) || value.since > 864e13 : value.since !== null || value.input !== 0 || value.output !== 0) || !value.priced && value.cost !== 0) throw new Error("Invalid lifetime usage totals");
3664
+ if (!value || ![value.requests, value.tokenRequests, value.input, value.output, value.priced, value.safe, value.unsafe, value.safe + value.unsafe].every(count2) || !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 ? !count2(value.since) || value.since > 864e13 : value.since !== null || value.input !== 0 || value.output !== 0) || !value.priced && value.cost !== 0 || (value.safe + value.unsafe > 0 ? !count2(value.ratingsSince) || value.ratingsSince > 864e13 : value.ratingsSince !== null)) {
3665
+ throw new Error("Invalid lifetime usage totals");
3666
+ }
3476
3667
  return value;
3477
3668
  }
3478
3669
  function add(left, right) {
@@ -3483,28 +3674,32 @@ function add(left, right) {
3483
3674
  tokenRequests: left.tokenRequests + right.tokenRequests,
3484
3675
  priced: left.priced + right.priced,
3485
3676
  cost: left.cost + right.cost,
3486
- since: left.since === null ? right.since : right.since === null ? left.since : Math.min(left.since, right.since)
3677
+ since: left.since === null ? right.since : right.since === null ? left.since : Math.min(left.since, right.since),
3678
+ safe: left.safe + right.safe,
3679
+ unsafe: left.unsafe + right.unsafe,
3680
+ ratingsSince: left.ratingsSince === null ? right.ratingsSince : right.ratingsSince === null ? left.ratingsSince : Math.min(left.ratingsSince, right.ratingsSince)
3487
3681
  });
3488
3682
  }
3489
3683
  var LifetimeUsage = class {
3490
- constructor(directory, legacyDirectory) {
3684
+ constructor(directory, ...legacyDirectories) {
3491
3685
  this.directory = directory;
3492
- this.legacyDirectory = legacyDirectory;
3493
- if (!path8.isAbsolute(directory) || legacyDirectory !== void 0 && !path8.isAbsolute(legacyDirectory)) {
3686
+ if (![directory, ...legacyDirectories].every((value) => path8.isAbsolute(value))) {
3494
3687
  throw new Error("Lifetime usage directory must be absolute");
3495
3688
  }
3689
+ this.legacyDirectories = legacyDirectories;
3496
3690
  }
3497
3691
  directory;
3498
- legacyDirectory;
3499
3692
  id = randomUUID();
3500
3693
  local = empty();
3501
3694
  pending = Promise.resolve();
3695
+ legacyDirectories;
3502
3696
  record(usage) {
3503
3697
  const tokens = count2(usage.input) && count2(usage.output);
3504
3698
  if (!tokens && (usage.input !== void 0 || usage.output !== void 0 || usage.cost === void 0) || usage.cost !== void 0 && !amount(usage.cost)) {
3505
3699
  return Promise.reject(new Error("Invalid request usage"));
3506
3700
  }
3507
- const increment = {
3701
+ return this.increment({
3702
+ ...empty(),
3508
3703
  requests: 1,
3509
3704
  tokenRequests: tokens ? 1 : 0,
3510
3705
  input: usage.input ?? 0,
@@ -3512,7 +3707,14 @@ var LifetimeUsage = class {
3512
3707
  priced: usage.cost === void 0 ? 0 : 1,
3513
3708
  cost: usage.cost ?? 0,
3514
3709
  since: Date.now()
3515
- };
3710
+ });
3711
+ }
3712
+ /** One accepted final review, independent of POST count or received usage. */
3713
+ recordRating(safe) {
3714
+ if (typeof safe !== "boolean") return Promise.reject(new Error("Invalid review rating"));
3715
+ return this.increment({ ...empty(), safe: safe ? 1 : 0, unsafe: safe ? 0 : 1, ratingsSince: Date.now() });
3716
+ }
3717
+ increment(increment) {
3516
3718
  this.pending = this.pending.catch(() => {
3517
3719
  }).then(async () => {
3518
3720
  this.local = add(this.local, increment);
@@ -3521,7 +3723,7 @@ var LifetimeUsage = class {
3521
3723
  try {
3522
3724
  const file = await open3(temporary, "wx", 384);
3523
3725
  try {
3524
- await file.writeFile(JSON.stringify({ version: 2, ...this.local }));
3726
+ await file.writeFile(JSON.stringify({ version: 3, ...this.local }));
3525
3727
  await file.sync();
3526
3728
  } finally {
3527
3729
  await file.close();
@@ -3549,7 +3751,7 @@ var LifetimeUsage = class {
3549
3751
  await this.flush();
3550
3752
  signal?.throwIfAborted();
3551
3753
  let total = empty();
3552
- const directories = new Set([this.directory, ...this.legacyDirectory ? [this.legacyDirectory] : []].map((directory) => path8.resolve(directory)));
3754
+ const directories = new Set([this.directory, ...this.legacyDirectories].map((directory) => path8.resolve(directory)));
3553
3755
  for (const directory of directories) total = add(total, await this.readDirectory(directory, signal));
3554
3756
  signal?.throwIfAborted();
3555
3757
  return total;
@@ -3581,7 +3783,7 @@ var LifetimeUsage = class {
3581
3783
  }
3582
3784
  if (size > 1024) throw new Error("Invalid lifetime usage snapshot");
3583
3785
  const value = JSON.parse(bytes.subarray(0, size).toString("utf8"));
3584
- if (value?.version !== 1 && value?.version !== 2) throw new Error("Unsupported lifetime usage snapshot");
3786
+ if (value?.version !== 1 && value?.version !== 2 && value?.version !== 3) throw new Error("Unsupported lifetime usage snapshot");
3585
3787
  total = add(total, validate({
3586
3788
  requests: value.requests,
3587
3789
  input: value.input,
@@ -3589,7 +3791,10 @@ var LifetimeUsage = class {
3589
3791
  tokenRequests: value.version === 1 ? value.requests : value.tokenRequests,
3590
3792
  priced: value.priced,
3591
3793
  cost: value.cost,
3592
- since: value.since
3794
+ since: value.since,
3795
+ safe: value.version === 3 ? value.safe : 0,
3796
+ unsafe: value.version === 3 ? value.unsafe : 0,
3797
+ ratingsSince: value.version === 3 ? value.ratingsSince : null
3593
3798
  }));
3594
3799
  } finally {
3595
3800
  await file.close();
@@ -3601,20 +3806,24 @@ var LifetimeUsage = class {
3601
3806
  }
3602
3807
  };
3603
3808
  function lifetimeCost(totals) {
3604
- if (!totals.requests) return "lifetime: no recorded usage";
3605
- if (!totals.priced) return "lifetime: cost unavailable";
3606
- return `lifetime: $${totals.cost.toFixed(4)}${totals.priced < totals.requests ? " (partial pricing)" : ""}`;
3809
+ if (!totals.requests) return uiText.lifetime.empty;
3810
+ if (!totals.priced) return uiText.lifetime.costUnavailable;
3811
+ return uiText.lifetime.cost(totals.cost.toFixed(4), totals.priced < totals.requests);
3607
3812
  }
3608
3813
  function lifetimeReport(totals) {
3609
3814
  return [
3610
3815
  lifetimeCost(totals),
3611
- `${totals.requests} requests with recorded usage`,
3612
- totals.tokenRequests ? `token: ${totals.input} in ${totals.output} out${totals.tokenRequests < totals.requests ? " (partial coverage)" : ""}` : "tokens unavailable",
3613
- `Token counts available: ${totals.tokenRequests}/${totals.requests} requests`,
3614
- `Pricing available: ${totals.priced}/${totals.requests} requests`,
3615
- ...totals.since === null ? [] : [`Recorded since: ${new Date(totals.since).toISOString().slice(0, 10)}`],
3816
+ uiText.lifetime.ratings(totals.safe, totals.unsafe),
3817
+ uiText.lifetime.requests(totals.requests),
3818
+ totals.tokenRequests ? uiText.lifetime.tokens(totals.input, totals.output, totals.tokenRequests < totals.requests) : uiText.lifetime.tokensUnavailable,
3819
+ uiText.lifetime.tokenCoverage(totals.tokenRequests, totals.requests),
3820
+ uiText.lifetime.pricingCoverage(totals.priced, totals.requests),
3821
+ ...totals.since === null ? [] : [uiText.lifetime.since(new Date(totals.since).toISOString().slice(0, 10))],
3822
+ ...totals.ratingsSince === null ? [] : [uiText.lifetime.ratingsSince(new Date(totals.ratingsSince).toISOString().slice(0, 10))],
3823
+ "",
3824
+ uiText.lifetime.explanation,
3616
3825
  "",
3617
- "Received usage only, including interrupted or unsuccessful reviews. Unreported charges remain unknown. Costs combine endpoint-reported amounts and catalog estimates; legacy history retains its original estimates. Earlier unrecorded usage cannot be recovered."
3826
+ uiText.lifetime.ratingsExplanation
3618
3827
  ].join("\n");
3619
3828
  }
3620
3829
 
@@ -3700,7 +3909,7 @@ var LifetimeRefresh = class {
3700
3909
  // src/lifetime-view.tsx
3701
3910
  function lifetimeTracker(api) {
3702
3911
  const directory = path9.join(api.state.path.state, "opencode-reviewer");
3703
- const store = new LifetimeUsage(path9.join(directory, "usage-v2"), path9.join(directory, "usage-v1"));
3912
+ const store = new LifetimeUsage(path9.join(directory, "usage-v3"), path9.join(directory, "usage-v2"), path9.join(directory, "usage-v1"));
3704
3913
  const [totals, setTotals] = createSignal();
3705
3914
  const [unavailable, setUnavailable] = createSignal(false);
3706
3915
  const refresh = new LifetimeRefresh((signal) => store.totals(signal), (value) => {
@@ -3713,14 +3922,16 @@ function lifetimeTracker(api) {
3713
3922
  commands: [{
3714
3923
  name: "opencode-reviewer.lifetime",
3715
3924
  namespace: "palette",
3716
- title: "Reviewer: Lifetime usage",
3717
- category: "Reviewer",
3925
+ title: uiText.commands.lifetime,
3926
+ category: uiText.commands.category,
3718
3927
  run: () => {
3719
3928
  refresh.refresh();
3720
3929
  api.ui.dialog.replace(() => _$createComponent(api.ui.DialogAlert, {
3721
- title: "Reviewer lifetime usage",
3930
+ get title() {
3931
+ return uiText.lifetime.title;
3932
+ },
3722
3933
  get message() {
3723
- return _$memo(() => !!unavailable())() ? "Lifetime usage unavailable. Recorded totals have not been reset." : _$memo(() => !!totals())() ? lifetimeReport(totals()) : "Loading recorded usage\u2026";
3934
+ return _$memo(() => !!unavailable())() ? uiText.lifetime.unavailable : _$memo(() => !!totals())() ? lifetimeReport(totals()) : uiText.lifetime.loading;
3724
3935
  }
3725
3936
  }));
3726
3937
  }
@@ -3735,9 +3946,12 @@ function lifetimeTracker(api) {
3735
3946
  // The controller must settle aborted review workers before this final queue drain.
3736
3947
  flush: () => store.flush().catch(() => {
3737
3948
  }),
3738
- text: () => unavailable() ? "lifetime: usage unavailable" : totals()?.requests ? lifetimeCost(totals()) : void 0,
3949
+ text: () => unavailable() ? uiText.lifetime.inlineUnavailable : totals()?.requests ? [lifetimeCost(totals()), uiText.lifetime.ratings(totals().safe, totals().unsafe)].join("\n") : void 0,
3739
3950
  record: (usage) => {
3740
3951
  void store.record(usage).then(() => refresh.refresh(), () => refresh.failed());
3952
+ },
3953
+ recordRating: (safe) => {
3954
+ void store.recordRating(safe).then(() => refresh.refresh(), () => refresh.failed());
3741
3955
  }
3742
3956
  };
3743
3957
  }
@@ -3939,8 +4153,8 @@ function sessionModeCommands(api, modes, changed) {
3939
4153
  name: `opencode-reviewer.${enabled ? "enable" : "disable"}`,
3940
4154
  namespace: "palette",
3941
4155
  slashName: `reviewer-${enabled ? "enable" : "disable"}`,
3942
- title: `Reviewer: ${enabled ? "Enable" : "Disable"} for conversation`,
3943
- category: "Reviewer",
4156
+ title: enabled ? uiText.commands.enable : uiText.commands.disable,
4157
+ category: uiText.commands.category,
3944
4158
  enabled: () => selected() !== void 0,
3945
4159
  run: async () => {
3946
4160
  const sessionID = selected();
@@ -3961,10 +4175,10 @@ function sessionModeCommands(api, modes, changed) {
3961
4175
  await persisted;
3962
4176
  if (!api.lifecycle.signal.aborted && latest.get(root) === order) api.ui.toast({
3963
4177
  variant: "success",
3964
- message: `Reviewer ${enabled ? "enabled" : "disabled"} for this conversation and descendants. Saved for resume.`
4178
+ message: uiText.sessionMode.saved(enabled)
3965
4179
  });
3966
4180
  } catch {
3967
- if (!api.lifecycle.signal.aborted && (root === void 0 || latest.get(root) === order)) api.ui.toast({ variant: "error", message: applied ? `Reviewer ${enabled ? "enabled" : "disabled"} locally, but saving failed. Resume may use the previous setting.` : "Reviewer setting unavailable. Session ancestry or saved mode could not be read." });
4181
+ if (!api.lifecycle.signal.aborted && (root === void 0 || latest.get(root) === order)) api.ui.toast({ variant: "error", message: applied ? uiText.sessionMode.saveFailed(enabled) : uiText.sessionMode.unavailable });
3968
4182
  }
3969
4183
  }
3970
4184
  })) });
@@ -3985,7 +4199,7 @@ var notificationClock = {
3985
4199
  }
3986
4200
  };
3987
4201
  function notificationText(value) {
3988
- return Array.from(value.replace(/[\u0000-\u001f\u007f-\u009f\u202a-\u202e\u2066-\u2069]/g, " ")).slice(0, 256).join("").trim() || "OpenCode session";
4202
+ return Array.from(value.replace(/[\u0000-\u001f\u007f-\u009f\u202a-\u202e\u2066-\u2069]/g, " ")).slice(0, 256).join("").trim() || uiText.notifications.fallbackSession;
3989
4203
  }
3990
4204
  function notificationMarkup(value) {
3991
4205
  return notificationText(value).replace(/&/g, "&amp;").replace(/</g, "&lt;").replace(/>/g, "&gt;");
@@ -3993,16 +4207,14 @@ function notificationMarkup(value) {
3993
4207
 
3994
4208
  // src/notification-policy.ts
3995
4209
  var NotificationPolicy = class {
3996
- constructor(config, auto, delay, backend, clock2 = notificationClock) {
4210
+ constructor(config, auto, backend, clock2 = notificationClock) {
3997
4211
  this.config = config;
3998
4212
  this.auto = auto;
3999
- this.delay = delay;
4000
4213
  this.backend = backend;
4001
4214
  this.clock = clock2;
4002
4215
  }
4003
4216
  config;
4004
4217
  auto;
4005
- delay;
4006
4218
  backend;
4007
4219
  clock;
4008
4220
  permissions = /* @__PURE__ */ new Map();
@@ -4082,7 +4294,7 @@ var NotificationPolicy = class {
4082
4294
  if (entry.phase === "attention") return;
4083
4295
  this.withdraw(entry.banner);
4084
4296
  entry.phase = "attention";
4085
- entry.banner = this.show("attention", "Session needs attention", entry.target);
4297
+ entry.banner = this.show("attention", uiText.notifications.attention, entry.target);
4086
4298
  }
4087
4299
  snapshot(views) {
4088
4300
  if (this.stopped) return;
@@ -4103,7 +4315,7 @@ var NotificationPolicy = class {
4103
4315
  if (entry.phase !== "automatic") {
4104
4316
  this.withdraw(entry.banner);
4105
4317
  entry.phase = "automatic";
4106
- entry.banner = state === "countdown" && this.delay > 0 ? this.show("attention", `Reviewer will approve permission in ${view.autoApproval.status === "countdown" ? view.autoApproval.seconds : this.delay} s`, entry.target) : void 0;
4318
+ entry.banner = void 0;
4107
4319
  }
4108
4320
  return;
4109
4321
  }
@@ -4135,7 +4347,7 @@ var NotificationPolicy = class {
4135
4347
  approved(request, target) {
4136
4348
  if (!this.remember(`approved:${request.id}`)) return;
4137
4349
  const current = this.permissions.get(request.id);
4138
- if (current || target) this.show("approved", "Reviewer approved a permission", current?.target ?? target);
4350
+ if (current || target) this.show("approved", uiText.notifications.approved, current?.target ?? target);
4139
4351
  }
4140
4352
  resolved(kind, id) {
4141
4353
  const entries = kind === "permission" ? this.permissions : this.questions;
@@ -4146,7 +4358,7 @@ var NotificationPolicy = class {
4146
4358
  this.remember(`${kind}:${id}`);
4147
4359
  }
4148
4360
  turn(kind, id, target) {
4149
- if (this.remember(`turn:${target.root}:${id}`)) this.show(kind, kind === "error" ? "Session error" : "Session ended", target);
4361
+ if (this.remember(`turn:${target.root}:${id}`)) this.show(kind, uiText.notifications[kind], target);
4150
4362
  }
4151
4363
  deleted(sessionID) {
4152
4364
  for (const banner of this.banners) if (banner.target.root === sessionID || banner.target.sessionID === sessionID) this.withdraw(banner);
@@ -4313,7 +4525,7 @@ var NotificationHost = class {
4313
4525
  }
4314
4526
  const root = this.roots.get(rootID);
4315
4527
  if (!root || request.received < root.since) return;
4316
- const target = { root: rootID, sessionID: rootID, title: this.api.state.session.get(rootID)?.title ?? "OpenCode session" };
4528
+ const target = { root: rootID, sessionID: rootID, title: this.api.state.session.get(rootID)?.title ?? uiText.notifications.fallbackSession };
4317
4529
  request.target = target;
4318
4530
  if (kind === "question") this.policy.question(id, target, true);
4319
4531
  else {
@@ -4327,7 +4539,7 @@ var NotificationHost = class {
4327
4539
  if (this.stopped) return;
4328
4540
  this.views = views;
4329
4541
  for (const request of this.permissions.values()) if (request.target) {
4330
- request.target.title = this.api.state.session.get(request.target.root)?.title ?? "OpenCode session";
4542
+ request.target.title = this.api.state.session.get(request.target.root)?.title ?? uiText.notifications.fallbackSession;
4331
4543
  }
4332
4544
  this.policy.snapshot(views);
4333
4545
  for (const [id, request] of this.permissions) this.admit("permission", id, request);
@@ -4343,7 +4555,7 @@ var NotificationHost = class {
4343
4555
  const dispatched = this.dispatched.get(fact.request.id);
4344
4556
  this.dispatched.delete(fact.request.id);
4345
4557
  if (dispatched?.automatic) {
4346
- dispatched.target.title = this.api.state.session.get(dispatched.target.root)?.title ?? "OpenCode session";
4558
+ dispatched.target.title = this.api.state.session.get(dispatched.target.root)?.title ?? uiText.notifications.fallbackSession;
4347
4559
  this.policy.approved(fact.request, dispatched.target);
4348
4560
  }
4349
4561
  } else this.dispatched.delete(fact.request.id);
@@ -4392,7 +4604,7 @@ var NotificationHost = class {
4392
4604
  if (assistant?.role === "assistant") root.assistant = assistant;
4393
4605
  if (root.assistant?.error?.name === "MessageAbortedError" || root.error?.name === "MessageAbortedError") return;
4394
4606
  if (this.views.some((v) => this.rootOf(v.request.sessionID) === id) || [...this.questions.values()].some((q) => this.rootOf(q.sessionID) === id) || this.api.state.session.question(id).length || this.api.state.session.permission(id).length) return;
4395
- const target = { root: id, sessionID: id, title: this.api.state.session.get(id)?.title ?? "OpenCode session" };
4607
+ const target = { root: id, sessionID: id, title: this.api.state.session.get(id)?.title ?? uiText.notifications.fallbackSession };
4396
4608
  const message = root.assistant;
4397
4609
  if (message?.error || root.error && (!message?.time.completed || !message.finish || message.finish === "error")) {
4398
4610
  this.policy.turn("error", root.user ?? root.error.id, target);
@@ -5712,7 +5924,7 @@ var LinuxNotifications = class {
5712
5924
  const process2 = this.start(
5713
5925
  "notify-send",
5714
5926
  [
5715
- "--app-name=Opencode",
5927
+ `--app-name=${uiText.notifications.application}`,
5716
5928
  ...icon ? [`--icon=${icon}`] : [],
5717
5929
  // GNOME uses the desktop-entry's source icon, ignoring --icon there.
5718
5930
  // image-path supplies the separate image beside the notification heading.
@@ -5724,13 +5936,12 @@ var LinuxNotifications = class {
5724
5936
  "--print-id",
5725
5937
  // GNOME emits an activation token only for a recognized application.
5726
5938
  ...this.identity ? ["--hint=string:desktop-entry:org.gnome.Terminal"] : [],
5727
- ...this.identity ? ["--action=default=Open session"] : [],
5939
+ ...this.identity ? [`--action=default=${uiText.notifications.openSession}`] : [],
5728
5940
  "--wait",
5729
5941
  "--",
5730
- "Opencode",
5731
- // GNOME flattens newlines in collapsed banners. Keep an explicit separator
5732
- // so the session title cannot run into the event message.
5733
- `${notificationMarkup(message.title)} \xB7 ${notificationMarkup(message.body)}`.replaceAll("\\", "\\\\")
5942
+ // Summaries are plain text; only the body supports desktop markup.
5943
+ uiText.notifications.heading(notificationText(message.body)).replaceAll("\\", "\\\\"),
5944
+ uiText.notifications.body(notificationMarkup(message.title)).replaceAll("\\", "\\\\")
5734
5945
  ],
5735
5946
  12e4,
5736
5947
  AbortSignal.any([local.signal, this.abort.signal]),
@@ -5795,13 +6006,16 @@ function ReviewLoading(props) {
5795
6006
  onCleanup(() => clearInterval(timer));
5796
6007
  });
5797
6008
  return (() => {
5798
- var _el$ = _$createElement("text");
6009
+ var _el$ = _$createElement("text"), _el$2 = _$createTextNode(` `);
6010
+ _$insertNode(_el$, _el$2);
5799
6011
  _$setProp(_el$, "height", 1);
5800
6012
  _$insert(_el$, _$createComponent2(Show, {
5801
6013
  get when() {
5802
6014
  return animated();
5803
6015
  },
5804
- fallback: "[\u22EF]",
6016
+ get fallback() {
6017
+ return uiText.review.staticIndicator;
6018
+ },
5805
6019
  get children() {
5806
6020
  return _$createComponent2(Index, {
5807
6021
  get each() {
@@ -5817,18 +6031,21 @@ function ReviewLoading(props) {
5817
6031
  return RGBA.fromValues(Math.min(1, base.r * brightness), Math.min(1, base.g * brightness), Math.min(1, base.b * brightness), alpha);
5818
6032
  });
5819
6033
  return (() => {
5820
- var _el$2 = _$createElement("span");
5821
- _$insert(_el$2, () => cell().character);
5822
- _$effect((_$p) => _$setProp(_el$2, "style", {
6034
+ var _el$3 = _$createElement("span");
6035
+ _$insert(_el$3, () => cell().character);
6036
+ _$effect((_$p) => _$setProp(_el$3, "style", {
5823
6037
  fg: color()
5824
6038
  }, _$p));
5825
- return _el$2;
6039
+ return _el$3;
5826
6040
  })();
5827
6041
  }
5828
6042
  });
5829
6043
  }
5830
- }), null);
5831
- _$insert(_el$, () => props.retrying ? " Retrying" : " Evaluating", null);
6044
+ }), _el$2);
6045
+ _$insert(_el$, (() => {
6046
+ var _c$ = _$memo2(() => !!props.retrying);
6047
+ return () => _c$() ? uiText.review.retrying : uiText.review.evaluating;
6048
+ })(), null);
5832
6049
  _$effect((_$p) => _$setProp(_el$, "fg", props.api.theme.current.textMuted, _$p));
5833
6050
  return _el$;
5834
6051
  })();
@@ -5843,21 +6060,21 @@ function ReviewDescription(props) {
5843
6060
  return syntax;
5844
6061
  });
5845
6062
  return (() => {
5846
- var _el$3 = _$createElement("markdown");
6063
+ var _el$4 = _$createElement("markdown");
5847
6064
  var _ref$ = props.ref;
5848
- typeof _ref$ === "function" ? _$use(_ref$, _el$3) : props.ref = _el$3;
5849
- _$setProp(_el$3, "conceal", true);
5850
- _$setProp(_el$3, "tableOptions", {
6065
+ typeof _ref$ === "function" ? _$use(_ref$, _el$4) : props.ref = _el$4;
6066
+ _$setProp(_el$4, "conceal", true);
6067
+ _$setProp(_el$4, "tableOptions", {
5851
6068
  style: "grid"
5852
6069
  });
5853
- _$setProp(_el$3, "width", "100%");
5854
- _$setProp(_el$3, "flexShrink", 0);
6070
+ _$setProp(_el$4, "width", "100%");
6071
+ _$setProp(_el$4, "flexShrink", 0);
5855
6072
  _$effect((_p$) => {
5856
6073
  var _v$ = displayText(props.text), _v$2 = style(), _v$3 = props.api.theme.current.markdownText, _v$4 = props.streaming;
5857
- _v$ !== _p$.e && (_p$.e = _$setProp(_el$3, "content", _v$, _p$.e));
5858
- _v$2 !== _p$.t && (_p$.t = _$setProp(_el$3, "syntaxStyle", _v$2, _p$.t));
5859
- _v$3 !== _p$.a && (_p$.a = _$setProp(_el$3, "fg", _v$3, _p$.a));
5860
- _v$4 !== _p$.o && (_p$.o = _$setProp(_el$3, "streaming", _v$4, _p$.o));
6074
+ _v$ !== _p$.e && (_p$.e = _$setProp(_el$4, "content", _v$, _p$.e));
6075
+ _v$2 !== _p$.t && (_p$.t = _$setProp(_el$4, "syntaxStyle", _v$2, _p$.t));
6076
+ _v$3 !== _p$.a && (_p$.a = _$setProp(_el$4, "fg", _v$3, _p$.a));
6077
+ _v$4 !== _p$.o && (_p$.o = _$setProp(_el$4, "streaming", _v$4, _p$.o));
5861
6078
  return _p$;
5862
6079
  }, {
5863
6080
  e: void 0,
@@ -5865,7 +6082,7 @@ function ReviewDescription(props) {
5865
6082
  a: void 0,
5866
6083
  o: void 0
5867
6084
  });
5868
- return _el$3;
6085
+ return _el$4;
5869
6086
  })();
5870
6087
  }
5871
6088
  function highlightingComplete(node) {
@@ -5888,28 +6105,28 @@ function ReviewButton(props) {
5888
6105
  return 0.299 * r + 0.587 * g + 0.114 * b > 0.5 ? RGBA.fromInts(0, 0, 0) : RGBA.fromInts(255, 255, 255);
5889
6106
  };
5890
6107
  return (() => {
5891
- var _el$4 = _$createElement("box"), _el$5 = _$createElement("text");
5892
- _$insertNode(_el$4, _el$5);
5893
- _$setProp(_el$4, "paddingLeft", 1);
5894
- _$setProp(_el$4, "paddingRight", 1);
5895
- _$setProp(_el$4, "onMouseOver", () => {
6108
+ var _el$5 = _$createElement("box"), _el$6 = _$createElement("text");
6109
+ _$insertNode(_el$5, _el$6);
6110
+ _$setProp(_el$5, "paddingLeft", 1);
6111
+ _$setProp(_el$5, "paddingRight", 1);
6112
+ _$setProp(_el$5, "onMouseOver", () => {
5896
6113
  if (!props.disabled) props.onHover?.();
5897
6114
  });
5898
- _$setProp(_el$4, "onMouseUp", (event) => {
6115
+ _$setProp(_el$5, "onMouseUp", (event) => {
5899
6116
  event.stopPropagation();
5900
6117
  if (event.button === 0 && !props.disabled) props.onClick();
5901
6118
  });
5902
- _$insert(_el$5, () => props.label);
6119
+ _$insert(_el$6, () => props.label);
5903
6120
  _$effect((_p$) => {
5904
6121
  var _v$5 = selected() ? props.api.theme.current.warning : props.api.theme.current.backgroundMenu, _v$6 = foreground();
5905
- _v$5 !== _p$.e && (_p$.e = _$setProp(_el$4, "backgroundColor", _v$5, _p$.e));
5906
- _v$6 !== _p$.t && (_p$.t = _$setProp(_el$5, "fg", _v$6, _p$.t));
6122
+ _v$5 !== _p$.e && (_p$.e = _$setProp(_el$5, "backgroundColor", _v$5, _p$.e));
6123
+ _v$6 !== _p$.t && (_p$.t = _$setProp(_el$6, "fg", _v$6, _p$.t));
5907
6124
  return _p$;
5908
6125
  }, {
5909
6126
  e: void 0,
5910
6127
  t: void 0
5911
6128
  });
5912
- return _el$4;
6129
+ return _el$5;
5913
6130
  })();
5914
6131
  }
5915
6132
  function ReviewFooter(props) {
@@ -5917,30 +6134,30 @@ function ReviewFooter(props) {
5917
6134
  const state = () => props.view.autoApproval;
5918
6135
  const label = () => {
5919
6136
  const current = state();
5920
- return current?.status === "countdown" ? `Allowed in ${current.seconds}s` : "Checking\u2026";
6137
+ return current?.status === "countdown" ? uiText.autoApproval.countdown(current.seconds) : uiText.autoApproval.checking;
5921
6138
  };
5922
6139
  return _$createComponent2(Show, {
5923
6140
  get when() {
5924
6141
  return _$memo2(() => !!props.enabled)() && props.view.assessment?.safe;
5925
6142
  },
5926
6143
  get children() {
5927
- var _el$6 = _$createElement("box");
5928
- _$setProp(_el$6, "marginTop", 1);
5929
- _$setProp(_el$6, "paddingTop", 1);
5930
- _$setProp(_el$6, "minHeight", 3);
5931
- _$setProp(_el$6, "flexShrink", 0);
5932
- _$setProp(_el$6, "border", ["top"]);
5933
- _$insert(_el$6, _$createComponent2(Switch, {
6144
+ var _el$7 = _$createElement("box");
6145
+ _$setProp(_el$7, "marginTop", 1);
6146
+ _$setProp(_el$7, "paddingTop", 1);
6147
+ _$setProp(_el$7, "minHeight", 3);
6148
+ _$setProp(_el$7, "flexShrink", 0);
6149
+ _$setProp(_el$7, "border", ["top"]);
6150
+ _$insert(_el$7, _$createComponent2(Switch, {
5934
6151
  get children() {
5935
6152
  return [_$createComponent2(Match, {
5936
6153
  get when() {
5937
6154
  return state()?.status === "countdown" || state()?.status === "checking";
5938
6155
  },
5939
6156
  get children() {
5940
- var _el$7 = _$createElement("box");
5941
- _$setProp(_el$7, "flexDirection", "row");
5942
- _$setProp(_el$7, "gap", 1);
5943
- _$insert(_el$7, _$createComponent2(ReviewButton, {
6157
+ var _el$8 = _$createElement("box");
6158
+ _$setProp(_el$8, "flexDirection", "row");
6159
+ _$setProp(_el$8, "gap", 1);
6160
+ _$insert(_el$8, _$createComponent2(ReviewButton, {
5944
6161
  get api() {
5945
6162
  return props.api;
5946
6163
  },
@@ -5958,18 +6175,20 @@ function ReviewFooter(props) {
5958
6175
  void props.controller.approveNow(props.view.request.id);
5959
6176
  }
5960
6177
  }), null);
5961
- _$insert(_el$7, _$createComponent2(ReviewButton, {
6178
+ _$insert(_el$8, _$createComponent2(ReviewButton, {
5962
6179
  get api() {
5963
6180
  return props.api;
5964
6181
  },
5965
- label: "Cancel",
6182
+ get label() {
6183
+ return uiText.autoApproval.cancel;
6184
+ },
5966
6185
  get selected() {
5967
6186
  return selected() === "cancel";
5968
6187
  },
5969
6188
  onHover: () => setSelected("cancel"),
5970
6189
  onClick: () => props.controller.cancelAutoApproval(props.view.request.id)
5971
6190
  }), null);
5972
- return _el$7;
6191
+ return _el$8;
5973
6192
  }
5974
6193
  }), _$createComponent2(Match, {
5975
6194
  get when() {
@@ -5980,7 +6199,9 @@ function ReviewFooter(props) {
5980
6199
  get api() {
5981
6200
  return props.api;
5982
6201
  },
5983
- label: "Allowing\u2026",
6202
+ get label() {
6203
+ return uiText.autoApproval.allowing;
6204
+ },
5984
6205
  disabled: true,
5985
6206
  onClick: () => {
5986
6207
  }
@@ -5991,10 +6212,10 @@ function ReviewFooter(props) {
5991
6212
  return state()?.status === "cancelled";
5992
6213
  },
5993
6214
  get children() {
5994
- var _el$8 = _$createElement("text");
5995
- _$insertNode(_el$8, _$createTextNode(`Auto-approval canceled`));
5996
- _$effect((_$p) => _$setProp(_el$8, "fg", props.api.theme.current.textMuted, _$p));
5997
- return _el$8;
6215
+ var _el$9 = _$createElement("text");
6216
+ _$insert(_el$9, () => uiText.autoApproval.cancelled);
6217
+ _$effect((_$p) => _$setProp(_el$9, "fg", props.api.theme.current.textMuted, _$p));
6218
+ return _el$9;
5998
6219
  }
5999
6220
  }), _$createComponent2(Match, {
6000
6221
  get when() {
@@ -6002,15 +6223,15 @@ function ReviewFooter(props) {
6002
6223
  },
6003
6224
  get children() {
6004
6225
  var _el$0 = _$createElement("text");
6005
- _$insertNode(_el$0, _$createTextNode(`! Auto-approval unavailable. Use native controls.`));
6226
+ _$insert(_el$0, () => uiText.autoApproval.unavailable);
6006
6227
  _$effect((_$p) => _$setProp(_el$0, "fg", props.api.theme.current.warning, _$p));
6007
6228
  return _el$0;
6008
6229
  }
6009
6230
  })];
6010
6231
  }
6011
6232
  }));
6012
- _$effect((_$p) => _$setProp(_el$6, "borderColor", props.api.theme.current.borderSubtle, _$p));
6013
- return _el$6;
6233
+ _$effect((_$p) => _$setProp(_el$7, "borderColor", props.api.theme.current.borderSubtle, _$p));
6234
+ return _el$7;
6014
6235
  }
6015
6236
  });
6016
6237
  }
@@ -6090,7 +6311,7 @@ async function reviewTui(api, options, fileIO, observer, notificationBackend) {
6090
6311
  prompts = await withDeadline(api.lifecycle.signal, parsed.timeoutMs, (signal) => loadPrompts(parsed.instructions, signal), "Prompt loading");
6091
6312
  config = parsed;
6092
6313
  } catch (error) {
6093
- configError = error instanceof Error ? error.message : "Invalid configuration";
6314
+ configError = error instanceof Error ? error.message : uiText.review.invalidConfiguration;
6094
6315
  }
6095
6316
  api.lifecycle.signal.throwIfAborted();
6096
6317
  const lifetime = lifetimeTracker(api);
@@ -6106,7 +6327,7 @@ async function reviewTui(api, options, fileIO, observer, notificationBackend) {
6106
6327
  try {
6107
6328
  const click = (id) => notifications?.click(id);
6108
6329
  const backend = notificationBackend ? notificationBackend(click, notificationConfig) : new LinuxNotifications(notificationConfig, click);
6109
- notifications = new NotificationHost(api, new NotificationPolicy(notificationConfig, reviewOptions?.autoApprove === true, reviewOptions?.autoApproveDelaySeconds ?? 15, backend), (id, signal) => modes.root(id, signal));
6330
+ notifications = new NotificationHost(api, new NotificationPolicy(notificationConfig, reviewOptions?.autoApprove === true, backend), (id, signal) => modes.root(id, signal));
6110
6331
  } catch {
6111
6332
  }
6112
6333
  }
@@ -6147,7 +6368,7 @@ async function reviewTui(api, options, fileIO, observer, notificationBackend) {
6147
6368
  once: (request, signal) => measured(traces?.get(request), "approval-reply", () => approval.once(request, signal))
6148
6369
  } : approval,
6149
6370
  visibleID: () => visibleApproval()
6150
- }, void 0, modes, (fact) => notifications?.fact(fact));
6371
+ }, void 0, modes, (fact) => notifications?.fact(fact), lifetime.recordRating);
6151
6372
  api.event.on("permission.asked", (event) => controller.asked(event.properties));
6152
6373
  api.event.on("permission.replied", (event) => controller.replied(event.properties.requestID));
6153
6374
  api.event.on("session.deleted", (event) => {
@@ -6265,45 +6486,48 @@ async function reviewTui(api, options, fileIO, observer, notificationBackend) {
6265
6486
  // The app slot lets this cover its title, sections, and footer while
6266
6487
  // the original sidebar remains mounted beneath it.
6267
6488
  (() => {
6268
- var _el$10 = _$createElement("box"), _el$11 = _$createElement("text"), _el$12 = _$createElement("b"), _el$14 = _$createElement("box"), _el$20 = _$createElement("scrollbox");
6269
- _$insertNode(_el$10, _el$11);
6270
- _$insertNode(_el$10, _el$14);
6271
- _$insertNode(_el$10, _el$20);
6489
+ var _el$1 = _$createElement("box"), _el$10 = _$createElement("text"), _el$11 = _$createElement("b"), _el$12 = _$createElement("box"), _el$17 = _$createElement("scrollbox");
6490
+ _$insertNode(_el$1, _el$10);
6491
+ _$insertNode(_el$1, _el$12);
6492
+ _$insertNode(_el$1, _el$17);
6272
6493
  _$use((value) => {
6273
6494
  panel = value;
6274
- }, _el$10);
6275
- _$setProp(_el$10, "renderAfter", () => {
6495
+ }, _el$1);
6496
+ _$setProp(_el$1, "renderAfter", () => {
6276
6497
  const assessment = view().assessment;
6277
6498
  if (assessment && paintedAssessment !== assessment && description && !description.isDestroyed && !description.streaming && description.content === displayText(assessment.desc) && description.getChildrenCount() && highlightingComplete(description)) paintedAssessment = assessment;
6278
6499
  });
6279
- _$setProp(_el$10, "position", "absolute");
6280
- _$setProp(_el$10, "top", 0);
6281
- _$setProp(_el$10, "right", 0);
6282
- _$setProp(_el$10, "bottom", 0);
6283
- _$setProp(_el$10, "width", 42);
6284
- _$setProp(_el$10, "zIndex", 1);
6285
- _$setProp(_el$10, "paddingTop", 1);
6286
- _$setProp(_el$10, "paddingBottom", 1);
6287
- _$setProp(_el$10, "paddingLeft", 2);
6288
- _$setProp(_el$10, "paddingRight", 2);
6289
- _$insertNode(_el$11, _el$12);
6290
- _$setProp(_el$11, "flexShrink", 0);
6291
- _$insertNode(_el$12, _$createTextNode(`Permission analysis`));
6292
- _$setProp(_el$14, "marginTop", 1);
6293
- _$setProp(_el$14, "flexShrink", 0);
6294
- _$insert(_el$14, _$createComponent2(Show, {
6500
+ _$setProp(_el$1, "position", "absolute");
6501
+ _$setProp(_el$1, "top", 0);
6502
+ _$setProp(_el$1, "right", 0);
6503
+ _$setProp(_el$1, "bottom", 0);
6504
+ _$setProp(_el$1, "width", 42);
6505
+ _$setProp(_el$1, "zIndex", 1);
6506
+ _$setProp(_el$1, "paddingTop", 1);
6507
+ _$setProp(_el$1, "paddingBottom", 1);
6508
+ _$setProp(_el$1, "paddingLeft", 2);
6509
+ _$setProp(_el$1, "paddingRight", 2);
6510
+ _$insertNode(_el$10, _el$11);
6511
+ _$setProp(_el$10, "flexShrink", 0);
6512
+ _$insert(_el$11, () => uiText.review.heading);
6513
+ _$setProp(_el$12, "marginTop", 1);
6514
+ _$setProp(_el$12, "flexShrink", 0);
6515
+ _$insert(_el$12, _$createComponent2(Show, {
6295
6516
  get when() {
6296
6517
  return rating() !== void 0;
6297
6518
  },
6298
6519
  get children() {
6299
- var _el$15 = _$createElement("text"), _el$16 = _$createElement("b");
6300
- _$insertNode(_el$15, _el$16);
6301
- _$insert(_el$16, () => rating() ? "\u2713 Safe" : "\u2717 Unsafe");
6302
- _$effect((_$p) => _$setProp(_el$15, "fg", rating() ? api.theme.current.success : api.theme.current.error, _$p));
6303
- return _el$15;
6520
+ var _el$13 = _$createElement("text"), _el$14 = _$createElement("b");
6521
+ _$insertNode(_el$13, _el$14);
6522
+ _$insert(_el$14, (() => {
6523
+ var _c$2 = _$memo2(() => !!rating());
6524
+ return () => _c$2() ? uiText.review.safe : uiText.review.unsafe;
6525
+ })());
6526
+ _$effect((_$p) => _$setProp(_el$13, "fg", rating() ? api.theme.current.success : api.theme.current.error, _$p));
6527
+ return _el$13;
6304
6528
  }
6305
6529
  }), null);
6306
- _$insert(_el$14, _$createComponent2(Show, {
6530
+ _$insert(_el$12, _$createComponent2(Show, {
6307
6531
  get when() {
6308
6532
  return _$memo2(() => view().status === "analyzing")() && rating() === void 0;
6309
6533
  },
@@ -6316,28 +6540,28 @@ async function reviewTui(api, options, fileIO, observer, notificationBackend) {
6316
6540
  });
6317
6541
  }
6318
6542
  }), null);
6319
- _$insert(_el$14, _$createComponent2(Show, {
6543
+ _$insert(_el$12, _$createComponent2(Show, {
6320
6544
  get when() {
6321
6545
  return view().status === "unavailable";
6322
6546
  },
6323
6547
  get children() {
6324
- var _el$17 = _$createElement("text"), _el$18 = _$createElement("b");
6325
- _$insertNode(_el$17, _el$18);
6326
- _$insertNode(_el$18, _$createTextNode(`! Analysis unavailable`));
6327
- _$effect((_$p) => _$setProp(_el$17, "fg", api.theme.current.warning, _$p));
6328
- return _el$17;
6548
+ var _el$15 = _$createElement("text"), _el$16 = _$createElement("b");
6549
+ _$insertNode(_el$15, _el$16);
6550
+ _$insert(_el$16, () => uiText.review.unavailable);
6551
+ _$effect((_$p) => _$setProp(_el$15, "fg", api.theme.current.warning, _$p));
6552
+ return _el$15;
6329
6553
  }
6330
6554
  }), null);
6331
6555
  _$use((value) => {
6332
6556
  scroll = value;
6333
- }, _el$20);
6334
- _$setProp(_el$20, "marginTop", 1);
6335
- _$setProp(_el$20, "flexGrow", 1);
6336
- _$setProp(_el$20, "minHeight", 0);
6337
- _$setProp(_el$20, "contentOptions", {
6557
+ }, _el$17);
6558
+ _$setProp(_el$17, "marginTop", 1);
6559
+ _$setProp(_el$17, "flexGrow", 1);
6560
+ _$setProp(_el$17, "minHeight", 0);
6561
+ _$setProp(_el$17, "contentOptions", {
6338
6562
  minHeight: 0
6339
6563
  });
6340
- _$insert(_el$20, _$createComponent2(ReviewDescription, {
6564
+ _$insert(_el$17, _$createComponent2(ReviewDescription, {
6341
6565
  api,
6342
6566
  get text() {
6343
6567
  return report();
@@ -6349,46 +6573,46 @@ async function reviewTui(api, options, fileIO, observer, notificationBackend) {
6349
6573
  description = value;
6350
6574
  }
6351
6575
  }), null);
6352
- _$insert(_el$20, _$createComponent2(Show, {
6576
+ _$insert(_el$17, _$createComponent2(Show, {
6353
6577
  get when() {
6354
6578
  return view().status === "unavailable";
6355
6579
  },
6356
6580
  get children() {
6357
- var _el$21 = _$createElement("text");
6358
- _$setProp(_el$21, "width", "100%");
6359
- _$setProp(_el$21, "flexShrink", 0);
6360
- _$insert(_el$21, () => displayText(view().error ?? "Review failed"));
6361
- _$effect((_$p) => _$setProp(_el$21, "fg", api.theme.current.text, _$p));
6362
- return _el$21;
6581
+ var _el$18 = _$createElement("text");
6582
+ _$setProp(_el$18, "width", "100%");
6583
+ _$setProp(_el$18, "flexShrink", 0);
6584
+ _$insert(_el$18, () => displayText(view().error ?? uiText.review.failed));
6585
+ _$effect((_$p) => _$setProp(_el$18, "fg", api.theme.current.text, _$p));
6586
+ return _el$18;
6363
6587
  }
6364
6588
  }), null);
6365
- _$insert(_el$20, _$createComponent2(Show, {
6589
+ _$insert(_el$17, _$createComponent2(Show, {
6366
6590
  get when() {
6367
6591
  return view().assessment?.usage;
6368
6592
  },
6369
6593
  children: (usage) => [(() => {
6370
- var _el$22 = _$createElement("text");
6371
- _$setProp(_el$22, "marginTop", 1);
6372
- _$setProp(_el$22, "width", "100%");
6373
- _$setProp(_el$22, "flexShrink", 0);
6374
- _$insert(_el$22, () => usageText(usage()));
6375
- _$effect((_$p) => _$setProp(_el$22, "fg", api.theme.current.textMuted, _$p));
6376
- return _el$22;
6594
+ var _el$19 = _$createElement("text");
6595
+ _$setProp(_el$19, "marginTop", 1);
6596
+ _$setProp(_el$19, "width", "100%");
6597
+ _$setProp(_el$19, "flexShrink", 0);
6598
+ _$insert(_el$19, () => usageText(usage()));
6599
+ _$effect((_$p) => _$setProp(_el$19, "fg", api.theme.current.textMuted, _$p));
6600
+ return _el$19;
6377
6601
  })(), _$createComponent2(Show, {
6378
6602
  get when() {
6379
6603
  return lifetime.text();
6380
6604
  },
6381
6605
  children: (text) => (() => {
6382
- var _el$23 = _$createElement("text");
6383
- _$setProp(_el$23, "width", "100%");
6384
- _$setProp(_el$23, "flexShrink", 0);
6385
- _$insert(_el$23, text);
6386
- _$effect((_$p) => _$setProp(_el$23, "fg", api.theme.current.textMuted, _$p));
6387
- return _el$23;
6606
+ var _el$20 = _$createElement("text");
6607
+ _$setProp(_el$20, "width", "100%");
6608
+ _$setProp(_el$20, "flexShrink", 0);
6609
+ _$insert(_el$20, text);
6610
+ _$effect((_$p) => _$setProp(_el$20, "fg", api.theme.current.textMuted, _$p));
6611
+ return _el$20;
6388
6612
  })()
6389
6613
  })]
6390
6614
  }), null);
6391
- _$insert(_el$10, _$createComponent2(ReviewFooter, {
6615
+ _$insert(_el$1, _$createComponent2(ReviewFooter, {
6392
6616
  api,
6393
6617
  get view() {
6394
6618
  return view();
@@ -6405,16 +6629,16 @@ async function reviewTui(api, options, fileIO, observer, notificationBackend) {
6405
6629
  foregroundColor: api.theme.current.textMuted
6406
6630
  }
6407
6631
  };
6408
- _v$7 !== _p$.e && (_p$.e = _$setProp(_el$10, "backgroundColor", _v$7, _p$.e));
6409
- _v$8 !== _p$.t && (_p$.t = _$setProp(_el$11, "fg", _v$8, _p$.t));
6410
- _v$9 !== _p$.a && (_p$.a = _$setProp(_el$20, "scrollbarOptions", _v$9, _p$.a));
6632
+ _v$7 !== _p$.e && (_p$.e = _$setProp(_el$1, "backgroundColor", _v$7, _p$.e));
6633
+ _v$8 !== _p$.t && (_p$.t = _$setProp(_el$10, "fg", _v$8, _p$.t));
6634
+ _v$9 !== _p$.a && (_p$.a = _$setProp(_el$17, "scrollbarOptions", _v$9, _p$.a));
6411
6635
  return _p$;
6412
6636
  }, {
6413
6637
  e: void 0,
6414
6638
  t: void 0,
6415
6639
  a: void 0
6416
6640
  });
6417
- return _el$10;
6641
+ return _el$1;
6418
6642
  })()
6419
6643
  );
6420
6644
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "opencode-reviewer",
3
- "version": "0.5.0",
3
+ "version": "0.6.0",
4
4
  "license": "MIT",
5
5
  "publishConfig": {
6
6
  "access": "public",