backeer-opencode 0.8.1 → 0.10.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.
@@ -1860,8 +1860,7 @@ import { homedir } from "node:os";
1860
1860
  import { join as join2 } from "node:path";
1861
1861
 
1862
1862
  // src/utilities.js
1863
- var MAX_TRACKED_ITEMS = 500;
1864
- var limit = (value, length = 1e3) => String(value ?? "").slice(0, length);
1863
+ var formatText = (value) => String(value ?? "");
1865
1864
  function enabled(value, fallback = true) {
1866
1865
  if (typeof value === "boolean") return value;
1867
1866
  if (typeof value !== "string") return fallback;
@@ -1875,10 +1874,9 @@ function stableHash(value) {
1875
1874
  }
1876
1875
  return (hash >>> 0).toString(16).padStart(8, "0");
1877
1876
  }
1878
- function rememberBounded(map, key, value) {
1877
+ function remember(map, key, value) {
1879
1878
  map.delete(key);
1880
1879
  map.set(key, value);
1881
- while (map.size > MAX_TRACKED_ITEMS) map.delete(map.keys().next().value);
1882
1880
  }
1883
1881
 
1884
1882
  // src/reporter.js
@@ -1905,10 +1903,10 @@ function createOpenCodeReporter(client, levelValue) {
1905
1903
  const body = {
1906
1904
  service: "backeer-opencode",
1907
1905
  level: kind === "warning" ? "warn" : kind === "applied" || kind === "success" ? "info" : kind,
1908
- message: limit(message, 500)
1906
+ message: formatText(message)
1909
1907
  };
1910
1908
  if (level === "debug" && detail) {
1911
- body.extra = { detail: limit(detail?.message ?? detail, 500) };
1909
+ body.extra = { detail: formatText(detail?.message ?? detail) };
1912
1910
  }
1913
1911
  setTimeout(() => {
1914
1912
  void Promise.resolve(method.call(client.app, { body })).catch(() => {
@@ -1927,7 +1925,6 @@ function createOpenCodeReporter(client, levelValue) {
1927
1925
  function rememberRecent(map, key, value) {
1928
1926
  map.delete(key);
1929
1927
  map.set(key, value);
1930
- while (map.size > 100) map.delete(map.keys().next().value);
1931
1928
  }
1932
1929
 
1933
1930
  // src/live-bridge-config.js
@@ -1941,14 +1938,14 @@ function readCredential(file) {
1941
1938
  try {
1942
1939
  descriptor = openSync(file, constants.O_RDONLY | (constants.O_NOFOLLOW ?? 0) | (constants.O_NONBLOCK ?? 0));
1943
1940
  const stat = fstatSync(descriptor);
1944
- if (!stat.isFile() || stat.size > 4096 || process.platform !== "win32" && ((stat.mode & 63) !== 0 || stat.uid !== process.getuid())) {
1941
+ if (!stat.isFile() || process.platform !== "win32" && ((stat.mode & 63) !== 0 || stat.uid !== process.getuid())) {
1945
1942
  throw new Error("invalid_file");
1946
1943
  }
1947
1944
  const token = readFileSync(descriptor, "utf8").trim();
1948
- if (!/^[A-Za-z0-9_-]{32,256}$/.test(token)) throw new Error("invalid_token");
1945
+ if (!/^[A-Za-z0-9_-]{32,}$/.test(token)) throw new Error("invalid_token");
1949
1946
  return token;
1950
1947
  } catch {
1951
- throw new Error("Live control credential must be an owner-only regular file containing a 32\u2013256 character token");
1948
+ throw new Error("Live control credential must be an owner-only regular file containing a token of at least 32 characters");
1952
1949
  } finally {
1953
1950
  if (descriptor !== void 0) closeSync(descriptor);
1954
1951
  }
@@ -1967,8 +1964,19 @@ function loadLiveBridgeConfig(option, { configDirectory, ingestToken } = {}) {
1967
1964
  const token = readCredential(tokenFile);
1968
1965
  if (token === ingestToken) throw new Error("Live control requires a credential distinct from the Backeer ingest token");
1969
1966
  const owner = values.owner ?? "server";
1970
- if (!["server", "tui"].includes(owner)) throw new Error("Live control owner must be server or tui");
1971
- return { enabled: true, token, tokenFile, port, owner };
1967
+ if (!["server", "standalone", "tui"].includes(owner)) throw new Error("Live control owner must be server, standalone or tui");
1968
+ const projectDirectory = values.projectDirectory;
1969
+ if (projectDirectory !== void 0 && (typeof projectDirectory !== "string" || !isAbsolute(projectDirectory))) {
1970
+ throw new Error("Live control projectDirectory must be an absolute path");
1971
+ }
1972
+ return {
1973
+ enabled: true,
1974
+ token,
1975
+ tokenFile,
1976
+ port,
1977
+ owner,
1978
+ ...projectDirectory === void 0 ? {} : { projectDirectory }
1979
+ };
1972
1980
  } catch (error) {
1973
1981
  return { enabled: false, error: error.message };
1974
1982
  }
@@ -2068,12 +2076,12 @@ function createDeliveryTracker() {
2068
2076
  return {
2069
2077
  begin(key) {
2070
2078
  if (states.has(key)) return false;
2071
- rememberBounded(states, key, "publishing");
2079
+ remember(states, key, "publishing");
2072
2080
  return true;
2073
2081
  },
2074
2082
  succeed(key) {
2075
2083
  if (states.get(key) === "publishing") {
2076
- rememberBounded(states, key, "published");
2084
+ remember(states, key, "published");
2077
2085
  }
2078
2086
  },
2079
2087
  fail(key) {
@@ -2090,7 +2098,7 @@ function createCompletionTracker() {
2090
2098
  let state = states.get(key);
2091
2099
  if (!state) {
2092
2100
  state = { complete: false, publishing: false, revision: 0 };
2093
- rememberBounded(states, key, state);
2101
+ remember(states, key, state);
2094
2102
  }
2095
2103
  return state;
2096
2104
  }
@@ -2126,8 +2134,89 @@ function createCompletionTracker() {
2126
2134
  };
2127
2135
  }
2128
2136
 
2137
+ // src/live-bridge-events.js
2138
+ var ID = /^[A-Za-z0-9_-]+$/;
2139
+ var validBridgeID = (value) => typeof value === "string" && ID.test(value);
2140
+ function createBridgeSanitizer(secrets = []) {
2141
+ const known = secrets.filter((value) => typeof value === "string" && value.length > 0);
2142
+ const assistants = /* @__PURE__ */ new Map();
2143
+ function text(value) {
2144
+ if (typeof value !== "string") return "";
2145
+ let result = value;
2146
+ for (const secret of known) result = result.split(secret).join("[REDACTED]");
2147
+ result = result.replace(/-----BEGIN [^-]*PRIVATE KEY-----[\s\S]*?(?:-----END [^-]*PRIVATE KEY-----|$)/g, "[REDACTED PRIVATE KEY]").replace(/\bBearer\s+[^\s"'<>]+/gi, "Bearer [REDACTED]").replace(/\b(?:sk|bgi)_[A-Za-z0-9_-]{12,}|\bsk-[A-Za-z0-9_-]{12,}/g, "[REDACTED]").replace(/((?:api[_-]?key|access[_-]?token|secret|password|authorization)\s*[=:]\s*)[^\s,;]+/gi, "$1[REDACTED]");
2148
+ return result;
2149
+ }
2150
+ function content(key, value) {
2151
+ return { [key]: text(value) };
2152
+ }
2153
+ function sanitize(event) {
2154
+ const p = event?.properties ?? event?.data ?? {};
2155
+ const sessionID = p.sessionID ?? p.info?.sessionID ?? p.part?.sessionID;
2156
+ if (!validBridgeID(sessionID) || sessionID === "global") return void 0;
2157
+ const base = { type: event.type, sessionID, data: {} };
2158
+ if (event.type === "message.updated") {
2159
+ const info = p.info;
2160
+ if (!validBridgeID(info?.id)) return void 0;
2161
+ if (info.role === "assistant") {
2162
+ assistants.set(info.id, sessionID);
2163
+ }
2164
+ return info.role === "assistant" ? { ...base, data: {
2165
+ messageID: info.id,
2166
+ completed: !!info.time?.completed,
2167
+ failed: !!info.error
2168
+ } } : void 0;
2169
+ }
2170
+ if (event.type === "message.part.updated") {
2171
+ const part = p.part;
2172
+ if (assistants.get(part?.messageID) !== sessionID) return void 0;
2173
+ if (part.type === "text") return { ...base, type: "assistant.text", data: {
2174
+ messageID: part.messageID,
2175
+ ...content("text", part.text),
2176
+ replace: true
2177
+ } };
2178
+ if (part.type !== "tool") return void 0;
2179
+ const state = part.state ?? {};
2180
+ return { ...base, type: "tool.status", data: {
2181
+ tool: text(part.tool, 80),
2182
+ status: text(state.status, 40),
2183
+ ...state.status === "completed" ? content("output", state.output) : {},
2184
+ ...state.status === "error" ? { failed: true } : {}
2185
+ } };
2186
+ }
2187
+ if (event.type === "session.text.ended") return { ...base, type: "assistant.text", data: {
2188
+ messageID: validBridgeID(p.assistantMessageID) ? p.assistantMessageID : void 0,
2189
+ ...content("text", p.text),
2190
+ replace: true
2191
+ } };
2192
+ if (["session.tool.called", "session.tool.success", "session.tool.failed"].includes(event.type)) {
2193
+ return { ...base, type: "tool.status", data: {
2194
+ status: event.type.split(".").at(-1),
2195
+ ...event.type === "session.tool.success" ? content(
2196
+ "output",
2197
+ (p.content ?? []).filter((item) => item?.type === "text").map((item) => item.text).join("\n")
2198
+ ) : {}
2199
+ } };
2200
+ }
2201
+ if (["permission.asked", "question.asked", "question.v2.asked"].includes(event.type)) {
2202
+ return { ...base, type: "approval.waiting", data: {
2203
+ kind: event.type.split(".")[0],
2204
+ ...event.type !== "permission.asked" && validBridgeID(p.id ?? p.requestID) ? { questionId: p.id ?? p.requestID } : {}
2205
+ } };
2206
+ }
2207
+ if (["permission.replied", "question.replied", "question.v2.replied", "question.rejected", "question.v2.rejected"].includes(event.type)) {
2208
+ return { ...base, type: "approval.settled", data: { kind: event.type.split(".")[0] } };
2209
+ }
2210
+ if (event.type === "session.status") return { ...base, data: {
2211
+ status: ["idle", "busy", "retry"].includes(p.status?.type) ? p.status.type : "unknown"
2212
+ } };
2213
+ if (["session.idle", "session.error", "session.deleted"].includes(event.type)) return base;
2214
+ return void 0;
2215
+ }
2216
+ return { sanitize, text, clear: () => assistants.clear() };
2217
+ }
2218
+
2129
2219
  // src/http.js
2130
- var RETRY_DELAYS_MS = [0, 500, 1500];
2131
2220
  var HttpError = class extends Error {
2132
2221
  constructor(message, status, responseBody = "") {
2133
2222
  super(message);
@@ -2143,31 +2232,31 @@ function isAbortError(error) {
2143
2232
  return error instanceof Error && error.name === "AbortError";
2144
2233
  }
2145
2234
  function sleep(milliseconds, signal2) {
2146
- return new Promise((resolve2) => {
2235
+ return new Promise((resolve3) => {
2147
2236
  if (signal2?.aborted) {
2148
- resolve2();
2237
+ resolve3();
2149
2238
  return;
2150
2239
  }
2151
2240
  const timeout = setTimeout(done, milliseconds);
2152
2241
  function done() {
2153
2242
  clearTimeout(timeout);
2154
2243
  signal2?.removeEventListener("abort", done);
2155
- resolve2();
2244
+ resolve3();
2156
2245
  }
2157
2246
  signal2?.addEventListener("abort", done, { once: true });
2158
2247
  });
2159
2248
  }
2160
2249
  async function postJsonWithRetry(config, url, body, options = {}) {
2161
- const retryDelays = options.retryDelays ?? RETRY_DELAYS_MS;
2162
- for (let attempt = 0; attempt < retryDelays.length; attempt++) {
2163
- if (retryDelays[attempt] > 0) {
2164
- await sleep(retryDelays[attempt], options.signal);
2165
- }
2166
- if (options.signal?.aborted) throw new DOMException("Aborted", "AbortError");
2250
+ const retryDelays = options.retryDelays;
2251
+ const signal2 = options.signal ?? config.signal;
2252
+ for (let attempt = 0; retryDelays === void 0 || attempt < retryDelays.length; attempt++) {
2253
+ const retryDelay = retryDelays?.[attempt] ?? (attempt === 0 ? 0 : 3e3);
2254
+ if (retryDelay > 0) await (options.sleepImplementation ?? sleep)(retryDelay, signal2);
2255
+ if (signal2?.aborted) throw new DOMException("Aborted", "AbortError");
2167
2256
  const controller = new AbortController();
2168
2257
  const abort = () => controller.abort();
2169
2258
  const timeout = setTimeout(abort, options.timeoutMs ?? 5e3);
2170
- options.signal?.addEventListener("abort", abort, { once: true });
2259
+ signal2?.addEventListener("abort", abort, { once: true });
2171
2260
  try {
2172
2261
  const response = await fetch(url, {
2173
2262
  method: "POST",
@@ -2179,22 +2268,22 @@ async function postJsonWithRetry(config, url, body, options = {}) {
2179
2268
  body: JSON.stringify(body),
2180
2269
  signal: controller.signal
2181
2270
  });
2182
- const responseBody = limit(await response.text(), 1e3);
2271
+ const responseBody = await response.text();
2183
2272
  if (response.ok) return { status: response.status, responseBody };
2184
- if (!isRetryable(response.status) || attempt === retryDelays.length - 1) {
2273
+ if (!isRetryable(response.status) || retryDelays !== void 0 && attempt === retryDelays.length - 1) {
2185
2274
  throw new HttpError(
2186
2275
  `HTTP request failed with status ${response.status}`,
2187
2276
  response.status,
2188
- responseBody
2277
+ createBridgeSanitizer([config.token]).text(responseBody)
2189
2278
  );
2190
2279
  }
2191
2280
  } catch (error) {
2192
- if (options.signal?.aborted) throw new DOMException("Aborted", "AbortError");
2281
+ if (signal2?.aborted) throw new DOMException("Aborted", "AbortError");
2193
2282
  if (error instanceof HttpError) throw error;
2194
- if (attempt === retryDelays.length - 1) throw error;
2283
+ if (retryDelays !== void 0 && attempt === retryDelays.length - 1) throw error;
2195
2284
  } finally {
2196
2285
  clearTimeout(timeout);
2197
- options.signal?.removeEventListener("abort", abort);
2286
+ signal2?.removeEventListener("abort", abort);
2198
2287
  }
2199
2288
  }
2200
2289
  throw new Error("HTTP retry loop ended unexpectedly");
@@ -2217,19 +2306,19 @@ async function publishPermission(config, reporter, request, directory, identity,
2217
2306
  const permissionType = request.permission ?? request.action ?? "unknown";
2218
2307
  const resources = Array.isArray(request.patterns) ? request.patterns.join(", ") : Array.isArray(request.resources) ? request.resources.join(", ") : "";
2219
2308
  const payload = [
2220
- { key: "Permission", value: limit(permissionType) },
2221
- { key: "Session", value: limit(identity.sessionID) },
2222
- { key: "Request ID", value: limit(identity.requestID) },
2223
- { key: "Patterns", value: limit(resources) },
2224
- { key: "Working directory", value: limit(directory) },
2309
+ { key: "Permission", value: formatText(permissionType) },
2310
+ { key: "Session", value: formatText(identity.sessionID) },
2311
+ { key: "Request ID", value: formatText(identity.requestID) },
2312
+ { key: "Patterns", value: formatText(resources) },
2313
+ { key: "Working directory", value: formatText(directory) },
2225
2314
  { key: "Action protocol", value: ACTION_PROTOCOL },
2226
2315
  { key: "Available decisions", value: "once, always, reject" }
2227
2316
  ].filter((item) => item.value.length > 0);
2228
2317
  const result = await postJsonWithRetry(config, config.eventsEndpoint, {
2229
2318
  type: "application",
2230
2319
  severity: "warning",
2231
- title: `OpenCode permission required: ${limit(permissionType, 200)}`,
2232
- body: resources ? `OpenCode is waiting for approval to access: ${limit(resources, 500)}` : `OpenCode is waiting for approval for: ${limit(permissionType, 500)}`,
2320
+ title: `OpenCode permission required: ${formatText(permissionType)}`,
2321
+ body: resources ? `OpenCode is waiting for approval to access: ${formatText(resources)}` : `OpenCode is waiting for approval for: ${formatText(permissionType)}`,
2233
2322
  payload,
2234
2323
  externalId: identity.externalId,
2235
2324
  timestamp: (/* @__PURE__ */ new Date()).toISOString(),
@@ -2252,8 +2341,8 @@ function questionIdentity(request) {
2252
2341
  function formatQuestionOptions(options) {
2253
2342
  if (!Array.isArray(options)) return "";
2254
2343
  return options.map((option) => {
2255
- const label = limit(option?.label, 200);
2256
- const description = limit(option?.description, 500);
2344
+ const label = formatText(option?.label);
2345
+ const description = formatText(option?.description);
2257
2346
  if (!label) return "";
2258
2347
  return description ? `${label} \u2014 ${description}` : label;
2259
2348
  }).filter(Boolean).join(" | ");
@@ -2266,7 +2355,7 @@ async function resolveSessionTitle(client, directory, sessionID, reporter) {
2266
2355
  query: { directory },
2267
2356
  throwOnError: true
2268
2357
  });
2269
- return limit((result?.data ?? result)?.title);
2358
+ return formatText((result?.data ?? result)?.title);
2270
2359
  } catch (error) {
2271
2360
  reporter.debug("Session title unavailable", error);
2272
2361
  return "";
@@ -2283,43 +2372,43 @@ async function publishQuestion(config, reporter, request, identity, client, dire
2283
2372
  );
2284
2373
  const payload = [
2285
2374
  { key: "OpenCode session", value: sessionTitle },
2286
- { key: "Session", value: limit(identity.sessionID) },
2287
- { key: "Request ID", value: limit(identity.requestID) },
2375
+ { key: "Session", value: formatText(identity.sessionID) },
2376
+ { key: "Request ID", value: formatText(identity.requestID) },
2288
2377
  { key: "Action protocol", value: QUESTION_ACTION_PROTOCOL },
2289
2378
  ...questions.flatMap((question, index) => {
2290
2379
  const suffix = questions.length > 1 ? ` ${index + 1}` : "";
2291
2380
  return [
2292
- { key: `Question title${suffix}`, value: limit(question?.header) },
2293
- { key: `Question${suffix}`, value: limit(question?.question) },
2381
+ { key: `Question title${suffix}`, value: formatText(question?.header) },
2382
+ { key: `Question${suffix}`, value: formatText(question?.question) },
2294
2383
  {
2295
2384
  key: `Options${suffix}`,
2296
- value: limit(formatQuestionOptions(question?.options))
2385
+ value: formatText(formatQuestionOptions(question?.options))
2297
2386
  }
2298
2387
  ];
2299
2388
  })
2300
2389
  ].filter((item) => item.value.length > 0);
2301
2390
  const firstQuestion = questions[0];
2302
2391
  const actionQuestions = questions.map((question) => ({
2303
- header: limit(question?.header, 200),
2304
- question: limit(question?.question, 2e3),
2305
- options: Array.isArray(question?.options) ? question.options.slice(0, 20).map((option) => ({
2306
- label: limit(option?.label, 200),
2307
- description: limit(option?.description, 500)
2392
+ header: formatText(question?.header),
2393
+ question: formatText(question?.question),
2394
+ options: Array.isArray(question?.options) ? question.options.map((option) => ({
2395
+ label: formatText(option?.label),
2396
+ description: formatText(option?.description)
2308
2397
  })).filter((option) => option.label) : [],
2309
2398
  multiple: question?.multiple === true,
2310
2399
  custom: question?.custom !== false
2311
2400
  }));
2312
2401
  const body = questions.map((question) => {
2313
- const header = limit(question?.header, 200);
2314
- const text = limit(question?.question, 500);
2315
- const options = limit(formatQuestionOptions(question?.options), 500);
2402
+ const header = formatText(question?.header);
2403
+ const text = formatText(question?.question);
2404
+ const options = formatText(formatQuestionOptions(question?.options));
2316
2405
  return [header, text, options ? `Options: ${options}` : ""].filter(Boolean).join("\n");
2317
2406
  }).join("\n\n");
2318
2407
  const result = await postJsonWithRetry(config, config.eventsEndpoint, {
2319
2408
  type: "application",
2320
2409
  severity: "info",
2321
- title: `OpenCode question: ${limit(firstQuestion?.header, 200) || "Question"}`,
2322
- body: limit(body, 2e3),
2410
+ title: `OpenCode question: ${formatText(firstQuestion?.header) || "Question"}`,
2411
+ body: formatText(body),
2323
2412
  payload,
2324
2413
  externalId: identity.externalId,
2325
2414
  timestamp: (/* @__PURE__ */ new Date()).toISOString(),
@@ -2348,19 +2437,19 @@ async function publishTodoCompletion(config, reporter, request, eventID, client,
2348
2437
  request.sessionID,
2349
2438
  reporter
2350
2439
  );
2351
- const correlation = typeof eventID === "string" && eventID.length > 0 ? limit(eventID, 200) : stableHash(JSON.stringify(todos));
2440
+ const correlation = typeof eventID === "string" && eventID.length > 0 ? formatText(eventID) : stableHash(JSON.stringify(todos));
2352
2441
  const externalId = `opencode-todo:${request.sessionID}:${correlation}`;
2353
- const visibleTodos = todos.slice(0, 20);
2442
+ const visibleTodos = todos;
2354
2443
  const payload = [
2355
2444
  { key: "OpenCode session", value: sessionTitle },
2356
2445
  { key: "Completed tasks", value: String(todos.length) },
2357
2446
  ...visibleTodos.flatMap((todo, index) => [
2358
- { key: `Task ${index + 1}`, value: limit(todo?.content) },
2359
- { key: `Priority ${index + 1}`, value: limit(todo?.priority, 100) }
2447
+ { key: `Task ${index + 1}`, value: formatText(todo?.content) },
2448
+ { key: `Priority ${index + 1}`, value: formatText(todo?.priority) }
2360
2449
  ]),
2361
2450
  ...todos.length > visibleTodos.length ? [{ key: "Additional tasks", value: String(todos.length - visibleTodos.length) }] : []
2362
2451
  ].filter((item) => item.value.length > 0);
2363
- const summary = todos.slice(0, 5).map((todo) => limit(todo?.content, 200)).filter(Boolean).join(" \u2022 ");
2452
+ const summary = todos.map((todo) => formatText(todo?.content)).filter(Boolean).join(" \u2022 ");
2364
2453
  const location = sessionTitle ? ` in ${sessionTitle}` : "";
2365
2454
  const body = [
2366
2455
  `All ${todos.length} OpenCode tasks completed${location}.`,
@@ -2370,7 +2459,7 @@ async function publishTodoCompletion(config, reporter, request, eventID, client,
2370
2459
  type: "application",
2371
2460
  severity: "info",
2372
2461
  title: "OpenCode todo list completed",
2373
- body: limit(body, 2e3),
2462
+ body: formatText(body),
2374
2463
  payload,
2375
2464
  externalId,
2376
2465
  timestamp: (/* @__PURE__ */ new Date()).toISOString(),
@@ -2397,7 +2486,7 @@ import { randomUUID } from "node:crypto";
2397
2486
  import { statSync } from "node:fs";
2398
2487
 
2399
2488
  // src/updates.js
2400
- var PLUGIN_VERSION = "0.8.1";
2489
+ var PLUGIN_VERSION = "0.10.0";
2401
2490
  var PACKAGE_NAME = "backeer-opencode";
2402
2491
  var NPM_REGISTRY_URL = "https://registry.npmjs.org";
2403
2492
  var CHECK_TIMEOUT_MS = 3e3;
@@ -2553,7 +2642,7 @@ async function probeSource(config, fetchImplementation = globalThis.fetch) {
2553
2642
  headers: { Authorization: `Bearer ${config.token}` },
2554
2643
  signal: controller.signal
2555
2644
  });
2556
- const responseBody = limit(await response.text(), 2e3);
2645
+ const responseBody = formatText(await response.text());
2557
2646
  if (!response.ok) {
2558
2647
  throw new HttpError(
2559
2648
  `Gateway source check failed with status ${response.status}`,
@@ -2600,7 +2689,7 @@ async function buildDoctorReport(config, { fetchImplementation = globalThis.fetc
2600
2689
  text: result?.capabilities?.apns === true ? "Gateway APNs delivery is enabled" : "Gateway APNs delivery is disabled"
2601
2690
  });
2602
2691
  } catch (error) {
2603
- const detail = error instanceof HttpError && error.status === 401 ? "Source authentication rejected (HTTP 401)" : error?.name === "AbortError" ? "Gateway source check timed out" : `Gateway source check failed: ${limit(error?.message || error, 300)}`;
2692
+ const detail = error instanceof HttpError && error.status === 401 ? "Source authentication rejected (HTTP 401)" : error?.name === "AbortError" ? "Gateway source check timed out" : `Gateway source check failed: ${formatText(error?.message || error)}`;
2604
2693
  checks.push({ ok: false, text: detail });
2605
2694
  }
2606
2695
  }
@@ -2637,7 +2726,6 @@ async function publishTestNotification(config, reporter) {
2637
2726
  }
2638
2727
 
2639
2728
  // src/feedback-session-store.js
2640
- var MAX_QUEUED_MESSAGES = 20;
2641
2729
  var FeedbackSessionStore = class {
2642
2730
  constructor({ send, reporter, flushDelayMs = 150 }) {
2643
2731
  this.send = send;
@@ -2655,10 +2743,9 @@ var FeedbackSessionStore = class {
2655
2743
  blockers: /* @__PURE__ */ new Set(),
2656
2744
  queue: [],
2657
2745
  queued: /* @__PURE__ */ new Set(),
2658
- queueOverflowReported: false,
2659
2746
  flushTimer: void 0
2660
2747
  };
2661
- rememberBounded(this.sessions, sessionID, state);
2748
+ remember(this.sessions, sessionID, state);
2662
2749
  }
2663
2750
  return state;
2664
2751
  }
@@ -2693,16 +2780,8 @@ var FeedbackSessionStore = class {
2693
2780
  const state = this.stateFor(sessionID);
2694
2781
  const key = `${sessionID}:${kind}:${message}`;
2695
2782
  if (state.queued.has(key)) return;
2696
- state.queue.push({ kind, message: limit(message, 300), key });
2783
+ state.queue.push({ kind, message: formatText(message), key });
2697
2784
  state.queued.add(key);
2698
- if (state.queue.length > MAX_QUEUED_MESSAGES) {
2699
- const removed = state.queue.shift();
2700
- state.queued.delete(removed.key);
2701
- if (!state.queueOverflowReported) {
2702
- state.queueOverflowReported = true;
2703
- this.reporter.warning("Conversation feedback queue full \u2014 oldest message discarded");
2704
- }
2705
- }
2706
2785
  this.scheduleFlush(sessionID, state);
2707
2786
  }
2708
2787
  setIdle(sessionID, idle) {
@@ -2815,7 +2894,7 @@ async function sendSessionMessage(client, sessionID, text) {
2815
2894
  }
2816
2895
  function formatBackeerFeedback(kind, message) {
2817
2896
  const symbol = FEEDBACK_SYMBOLS[kind] ?? FEEDBACK_SYMBOLS.info;
2818
- return `\u25A3 Backeer | ${symbol} ${limit(message, 300)}`;
2897
+ return `\u25A3 Backeer | ${symbol} ${formatText(message)}`;
2819
2898
  }
2820
2899
  function formatBackeerFeedbackBatch(items) {
2821
2900
  if (items.length === 1) {
@@ -2825,7 +2904,7 @@ function formatBackeerFeedbackBatch(items) {
2825
2904
  "\u25A3 Backeer",
2826
2905
  ...items.map((item) => {
2827
2906
  const symbol = FEEDBACK_SYMBOLS[item.kind] ?? FEEDBACK_SYMBOLS.info;
2828
- return `${symbol} ${limit(item.message, 300)}`;
2907
+ return `${symbol} ${formatText(item.message)}`;
2829
2908
  })
2830
2909
  ].join("\n");
2831
2910
  }
@@ -3073,17 +3152,17 @@ var URL_WITH_QUERY = /\bhttps?:\/\/[^\s?#]+(?:\?[^\s#]*)?(?:#[^\s]*)?/gi;
3073
3152
  var WINDOWS_PATH = /\b[A-Za-z]:\\(?:[^\\\s]+\\)+[^\s]*/g;
3074
3153
  var POSIX_PATH = /(^|\s)\/(?:[^/\s]+\/)+[^\s]*/g;
3075
3154
  function sanitizeErrorMessage(value) {
3076
- return limit(value, 2e3).replace(CONTROL_CHARACTERS, " ").replace(BEARER_TOKEN, "Bearer [redacted]").replace(SECRET_ASSIGNMENT, "$1=[redacted]").replace(URL_WITH_QUERY, (value2) => {
3155
+ return formatText(value).replace(CONTROL_CHARACTERS, " ").replace(BEARER_TOKEN, "Bearer [redacted]").replace(SECRET_ASSIGNMENT, "$1=[redacted]").replace(URL_WITH_QUERY, (value2) => {
3077
3156
  try {
3078
3157
  const url = new URL(value2);
3079
3158
  return `${url.origin}${url.pathname}`;
3080
3159
  } catch {
3081
3160
  return "[url redacted]";
3082
3161
  }
3083
- }).replace(WINDOWS_PATH, "[path redacted]").replace(POSIX_PATH, "$1[path redacted]").replace(/\s+/g, " ").trim().slice(0, 500);
3162
+ }).replace(WINDOWS_PATH, "[path redacted]").replace(POSIX_PATH, "$1[path redacted]").replace(/\s+/g, " ").trim();
3084
3163
  }
3085
3164
  function safeErrorDetails(error) {
3086
- const name = limit(error?.name || "OpenCodeError", 100);
3165
+ const name = formatText(error?.name || "OpenCodeError");
3087
3166
  const message = sanitizeErrorMessage(error?.data?.message ?? error?.message);
3088
3167
  const statusCode = Number.isInteger(error?.data?.statusCode) ? String(error.data.statusCode) : "";
3089
3168
  return { name, message, statusCode };
@@ -3096,7 +3175,7 @@ async function resolveSessionTitle2(client, directory, sessionID, reporter) {
3096
3175
  query: { directory },
3097
3176
  throwOnError: true
3098
3177
  });
3099
- return limit((result?.data ?? result)?.title);
3178
+ return formatText((result?.data ?? result)?.title);
3100
3179
  } catch (error) {
3101
3180
  reporter.debug("Session title unavailable", error);
3102
3181
  return "";
@@ -3203,7 +3282,7 @@ async function publishSessionError({
3203
3282
  // Backeer/iOS currently support info, warning and critical severities.
3204
3283
  severity: "critical",
3205
3284
  title: "OpenCode task failed",
3206
- body: limit(body, 1e3),
3285
+ body: formatText(body),
3207
3286
  payload: [
3208
3287
  { key: "OpenCode session", value: sessionTitle },
3209
3288
  { key: "Outcome", value: "Failed" },
@@ -3215,7 +3294,7 @@ async function publishSessionError({
3215
3294
  }, "Task failure sent");
3216
3295
  }
3217
3296
  async function publishOpenCodeUpdate({ config, reporter, version }) {
3218
- const safeVersion = limit(version, 100);
3297
+ const safeVersion = formatText(version);
3219
3298
  await publish(config, reporter, void 0, void 0, {
3220
3299
  severity: "info",
3221
3300
  title: "OpenCode update available",
@@ -3225,8 +3304,8 @@ async function publishOpenCodeUpdate({ config, reporter, version }) {
3225
3304
  }, "OpenCode update sent");
3226
3305
  }
3227
3306
  function errorCorrelation(event, sessionState) {
3228
- if (typeof event?.id === "string" && event.id) return limit(event.id, 200);
3229
- if (sessionState?.lastAssistant?.id) return limit(sessionState.lastAssistant.id, 200);
3307
+ if (typeof event?.id === "string" && event.id) return formatText(event.id);
3308
+ if (sessionState?.lastAssistant?.id) return formatText(sessionState.lastAssistant.id);
3230
3309
  const details = safeErrorDetails(event?.properties?.error);
3231
3310
  return stableHash(`${details.name}:${details.statusCode}:${details.message}`);
3232
3311
  }
@@ -3266,7 +3345,7 @@ function createLifecycleController({
3266
3345
  lastAssistant: void 0,
3267
3346
  completedMessageID: ""
3268
3347
  };
3269
- rememberBounded(sessions, sessionID, state);
3348
+ remember(sessions, sessionID, state);
3270
3349
  }
3271
3350
  return state;
3272
3351
  }
@@ -3301,7 +3380,7 @@ function createLifecycleController({
3301
3380
  client,
3302
3381
  directory,
3303
3382
  sessionID,
3304
- messageID: limit(assistant.id, 200),
3383
+ messageID: formatText(assistant.id),
3305
3384
  durationMs,
3306
3385
  conversation
3307
3386
  }),
@@ -3348,7 +3427,7 @@ function createLifecycleController({
3348
3427
  client,
3349
3428
  directory,
3350
3429
  sessionID,
3351
- correlation: limit(correlation, 200),
3430
+ correlation: formatText(correlation),
3352
3431
  attempt: status.attempt,
3353
3432
  next: status.next,
3354
3433
  conversation
@@ -3401,7 +3480,7 @@ function createLifecycleController({
3401
3480
  const version = event.properties?.version;
3402
3481
  if (typeof version !== "string" || !version.trim()) return;
3403
3482
  if (publishedUpdates.has(version)) return;
3404
- rememberBounded(publishedUpdates, version, true);
3483
+ remember(publishedUpdates, version, true);
3405
3484
  dispatch(
3406
3485
  "OpenCode update could not be sent",
3407
3486
  sendUpdate({ config, reporter, version: version.trim() })
@@ -3417,7 +3496,8 @@ function createLifecycleController({
3417
3496
 
3418
3497
  // src/live-bridge.js
3419
3498
  import { createServer } from "node:http";
3420
- import { randomUUID as randomUUID3, timingSafeEqual } from "node:crypto";
3499
+ import { randomUUID as randomUUID3, timingSafeEqual, createHash as createHash2 } from "node:crypto";
3500
+ import { resolve as resolve2 } from "node:path";
3421
3501
 
3422
3502
  // src/permission-command.js
3423
3503
  var ALLOWED_DECISIONS = /* @__PURE__ */ new Set(["once", "always", "reject"]);
@@ -3610,7 +3690,7 @@ async function openCommandStream(config, state, signal2, fetchImplementation) {
3610
3690
  signal: signal2
3611
3691
  });
3612
3692
  if (!response.ok) {
3613
- const responseBody = limit(await response.text(), 500);
3693
+ const responseBody = formatText(await response.text());
3614
3694
  throw new HttpError(
3615
3695
  `Backeer command stream failed with status ${response.status}`,
3616
3696
  response.status,
@@ -3642,7 +3722,7 @@ async function consumeConnection({
3642
3722
  }
3643
3723
  }
3644
3724
  function streamErrorState(error, lastLoggedError, reconnectDelay, reporter) {
3645
- const message = error instanceof HttpError ? `${error.message}: ${error.responseBody}` : limit(error, 500);
3725
+ const message = error instanceof HttpError ? `${error.message}: ${error.responseBody}` : formatText(error);
3646
3726
  if (message !== lastLoggedError) {
3647
3727
  const authenticationFailure = error instanceof HttpError && [401, 403].includes(error.status);
3648
3728
  reporter.error(
@@ -3652,7 +3732,7 @@ function streamErrorState(error, lastLoggedError, reconnectDelay, reporter) {
3652
3732
  }
3653
3733
  return {
3654
3734
  lastLoggedError: message,
3655
- reconnectDelay: error instanceof HttpError && [401, 403, 404].includes(error.status) ? 6e4 : reconnectDelay
3735
+ reconnectDelay: error instanceof HttpError && [401, 403, 404].includes(error.status) ? 3e3 : reconnectDelay
3656
3736
  };
3657
3737
  }
3658
3738
  async function runCommandStream(config, reporter, {
@@ -3665,7 +3745,7 @@ async function runCommandStream(config, reporter, {
3665
3745
  stableConnectionMs = 5e3
3666
3746
  }) {
3667
3747
  const state = { lastEventID: "", framesReceived: 0 };
3668
- let reconnectDelay = 1e3;
3748
+ let reconnectDelay = 3e3;
3669
3749
  let lastLoggedError = "";
3670
3750
  while (!signal2.aborted && hasPending()) {
3671
3751
  try {
@@ -3684,7 +3764,7 @@ async function runCommandStream(config, reporter, {
3684
3764
  now,
3685
3765
  stableConnectionMs,
3686
3766
  onStable: () => {
3687
- reconnectDelay = 1e3;
3767
+ reconnectDelay = 3e3;
3688
3768
  lastLoggedError = "";
3689
3769
  }
3690
3770
  });
@@ -3696,7 +3776,7 @@ async function runCommandStream(config, reporter, {
3696
3776
  }
3697
3777
  if (!signal2.aborted && hasPending()) {
3698
3778
  await sleepImplementation(reconnectDelay, signal2);
3699
- reconnectDelay = Math.min(reconnectDelay * 2, 6e4);
3779
+ reconnectDelay = 3e3;
3700
3780
  }
3701
3781
  }
3702
3782
  }
@@ -3784,7 +3864,7 @@ async function replyToQuestion(client, pending, response) {
3784
3864
  }
3785
3865
  async function postQuestionState(config, eventID, path, body = {}) {
3786
3866
  const url = new URL(`/v1/events/${encodeURIComponent(eventID)}/${path}`, config.origin);
3787
- await postJsonWithRetry(config, url, body, { retryDelays: [0, 500] });
3867
+ await postJsonWithRetry(config, url, body);
3788
3868
  }
3789
3869
  function createPermissionController({
3790
3870
  config,
@@ -3840,7 +3920,7 @@ function createPermissionController({
3840
3920
  async function handleCommand(command) {
3841
3921
  if (!command || typeof command !== "object") return;
3842
3922
  if (!["opencode_permission_reply", "opencode_question_reply"].includes(command.type)) return;
3843
- if (typeof command.id !== "string" || command.id.length > 200) return;
3923
+ if (typeof command.id !== "string") return;
3844
3924
  const previousAcknowledgement = completedCommands.get(command.id);
3845
3925
  if (previousAcknowledgement) {
3846
3926
  await acknowledgeCommand(config, command.id, previousAcknowledgement).catch((error) => reporter.warning("Decision receipt retry failed", error));
@@ -3890,7 +3970,7 @@ function createPermissionController({
3890
3970
  externalId: command.externalId,
3891
3971
  processedAt: (/* @__PURE__ */ new Date()).toISOString()
3892
3972
  };
3893
- rememberBounded(completedCommands, command.id, acknowledgement);
3973
+ remember(completedCommands, command.id, acknowledgement);
3894
3974
  if (isQuestion) {
3895
3975
  reportQuestionAcknowledgement(acknowledgement, pending, reporter, conversation);
3896
3976
  } else {
@@ -3997,8 +4077,8 @@ async function commonDirectory(directory) {
3997
4077
  return realpath(stdout.trim());
3998
4078
  }
3999
4079
  function approvedWorktreeSessions(value) {
4000
- if (!Array.isArray(value) || value.length > 16) return [];
4001
- return value.filter((item) => /^[A-Za-z0-9_-]{1,160}$/.test(item?.sessionID) && item.sessionID !== "global" && typeof item.directory === "string" && isAbsolute2(item.directory) && item.directory.length <= 4096);
4080
+ if (!Array.isArray(value)) return [];
4081
+ return value.filter((item) => /^[A-Za-z0-9_-]+$/.test(item?.sessionID) && item.sessionID !== "global" && typeof item.directory === "string" && isAbsolute2(item.directory));
4002
4082
  }
4003
4083
  function createSessionScope(directory, { resolveCommon = commonDirectory, approvedSessions = [] } = {}) {
4004
4084
  const observed = /* @__PURE__ */ new Map();
@@ -4009,7 +4089,6 @@ function createSessionScope(directory, { resolveCommon = commonDirectory, approv
4009
4089
  if (location === directory) {
4010
4090
  if (info.id) {
4011
4091
  observed.set(info.id, info.projectID);
4012
- if (observed.size > 256) observed.delete(observed.keys().next().value);
4013
4092
  }
4014
4093
  return true;
4015
4094
  }
@@ -4035,24 +4114,23 @@ function createV2LiveClient(ctx) {
4035
4114
  if (disposed) return info;
4036
4115
  await ownsSession(info);
4037
4116
  known.set(id, info);
4038
- if (known.size > 100) known.delete(known.keys().next().value);
4039
4117
  return info;
4040
4118
  }
4041
4119
  return {
4042
4120
  async list() {
4043
4121
  const refreshed = [];
4044
4122
  if (typeof ctx.session.list === "function") {
4045
- const result = await ctx.session.list({ directory: ctx.location.directory, limit: 100, order: "desc" });
4123
+ const result = await ctx.session.list({ directory: ctx.location.directory, order: "desc" });
4046
4124
  const values = Array.isArray(result) ? result : result?.data;
4047
4125
  if (!Array.isArray(values)) throw new Error("Invalid native session list");
4048
- for (const info of values.slice(0, 100)) {
4126
+ for (const info of values) {
4049
4127
  if (await ownsSession(info)) {
4050
4128
  refreshed.push(info);
4051
4129
  known.set(info.id, info);
4052
4130
  }
4053
4131
  }
4054
4132
  }
4055
- for (const id of [...known.keys()].slice(0, 100)) {
4133
+ for (const id of [...known.keys()]) {
4056
4134
  if (refreshed.some((info) => info.id === id)) continue;
4057
4135
  try {
4058
4136
  refreshed.push(await get(id));
@@ -4154,100 +4232,13 @@ function createLiveConnectionNotifier(config, reporter) {
4154
4232
  };
4155
4233
  }
4156
4234
 
4157
- // src/live-bridge-events.js
4158
- var ID = /^[A-Za-z0-9_-]{1,160}$/;
4159
- var validBridgeID = (value) => typeof value === "string" && ID.test(value);
4160
- function createBridgeSanitizer(secrets = []) {
4161
- const known = secrets.filter((value) => typeof value === "string" && value.length > 0);
4162
- const assistants = /* @__PURE__ */ new Map();
4163
- function clipped(value, max = 12e3) {
4164
- if (typeof value !== "string") return { value: "", truncated: false };
4165
- let result = value.slice(0, 64e3);
4166
- for (const secret of known) result = result.split(secret).join("[REDACTED]");
4167
- result = result.replace(/-----BEGIN [^-]*PRIVATE KEY-----[\s\S]*?(?:-----END [^-]*PRIVATE KEY-----|$)/g, "[REDACTED PRIVATE KEY]").replace(/\bBearer\s+[^\s"'<>]+/gi, "Bearer [REDACTED]").replace(/\b(?:sk|bgi)_[A-Za-z0-9_-]{12,}|\bsk-[A-Za-z0-9_-]{12,}/g, "[REDACTED]").replace(/((?:api[_-]?key|access[_-]?token|secret|password|authorization)\s*[=:]\s*)[^\s,;]+/gi, "$1[REDACTED]");
4168
- return { value: result.slice(0, max), truncated: value.length > 64e3 || result.length > max };
4169
- }
4170
- function text(value, max) {
4171
- return clipped(value, max).value;
4172
- }
4173
- function content(key, value) {
4174
- const result = clipped(value);
4175
- return { [key]: result.value, ...result.truncated ? { truncated: true } : {} };
4176
- }
4177
- function sanitize(event) {
4178
- const p = event?.properties ?? event?.data ?? {};
4179
- const sessionID = p.sessionID ?? p.info?.sessionID ?? p.part?.sessionID;
4180
- if (!validBridgeID(sessionID) || sessionID === "global") return void 0;
4181
- const base = { type: event.type, sessionID, data: {} };
4182
- if (event.type === "message.updated") {
4183
- const info = p.info;
4184
- if (!validBridgeID(info?.id)) return void 0;
4185
- if (info.role === "assistant") {
4186
- assistants.set(info.id, sessionID);
4187
- if (assistants.size > 256) assistants.delete(assistants.keys().next().value);
4188
- }
4189
- return info.role === "assistant" ? { ...base, data: {
4190
- messageID: info.id,
4191
- completed: !!info.time?.completed,
4192
- failed: !!info.error
4193
- } } : void 0;
4194
- }
4195
- if (event.type === "message.part.updated") {
4196
- const part = p.part;
4197
- if (assistants.get(part?.messageID) !== sessionID) return void 0;
4198
- if (part.type === "text") return { ...base, type: "assistant.text", data: {
4199
- messageID: part.messageID,
4200
- ...content("text", part.text),
4201
- replace: true
4202
- } };
4203
- if (part.type !== "tool") return void 0;
4204
- const state = part.state ?? {};
4205
- return { ...base, type: "tool.status", data: {
4206
- tool: text(part.tool, 80),
4207
- status: text(state.status, 40),
4208
- ...state.status === "completed" ? content("output", state.output) : {},
4209
- ...state.status === "error" ? { failed: true } : {}
4210
- } };
4211
- }
4212
- if (event.type === "session.text.ended") return { ...base, type: "assistant.text", data: {
4213
- messageID: validBridgeID(p.assistantMessageID) ? p.assistantMessageID : void 0,
4214
- ...content("text", p.text),
4215
- replace: true
4216
- } };
4217
- if (["session.tool.called", "session.tool.success", "session.tool.failed"].includes(event.type)) {
4218
- return { ...base, type: "tool.status", data: {
4219
- status: event.type.split(".").at(-1),
4220
- ...event.type === "session.tool.success" ? content(
4221
- "output",
4222
- (p.content ?? []).filter((item) => item?.type === "text").map((item) => item.text).join("\n")
4223
- ) : {}
4224
- } };
4225
- }
4226
- if (["permission.asked", "question.asked", "question.v2.asked"].includes(event.type)) {
4227
- return { ...base, type: "approval.waiting", data: {
4228
- kind: event.type.split(".")[0],
4229
- ...event.type !== "permission.asked" && validBridgeID(p.id ?? p.requestID) ? { questionId: p.id ?? p.requestID } : {}
4230
- } };
4231
- }
4232
- if (["permission.replied", "question.replied", "question.v2.replied", "question.rejected", "question.v2.rejected"].includes(event.type)) {
4233
- return { ...base, type: "approval.settled", data: { kind: event.type.split(".")[0] } };
4234
- }
4235
- if (event.type === "session.status") return { ...base, data: {
4236
- status: ["idle", "busy", "retry"].includes(p.status?.type) ? p.status.type : "unknown"
4237
- } };
4238
- if (["session.idle", "session.error", "session.deleted"].includes(event.type)) return base;
4239
- return void 0;
4240
- }
4241
- return { sanitize, text, clear: () => assistants.clear() };
4242
- }
4243
-
4244
4235
  // src/live-questions.js
4245
4236
  import { createHash } from "node:crypto";
4246
4237
  var invalid = (code = "invalid_question_answer", status = 422) => Object.assign(new Error(code), { code, status });
4247
4238
  function questionSnapshot(pending, text) {
4248
- if (!validBridgeID(pending?.id) || !validBridgeID(pending?.sessionID) || pending.sessionID === "global" || !Array.isArray(pending.questions) || !pending.questions.length || pending.questions.length > 16) throw invalid("unsupported_question");
4239
+ if (!validBridgeID(pending?.id) || !validBridgeID(pending?.sessionID) || pending.sessionID === "global" || !Array.isArray(pending.questions) || !pending.questions.length) throw invalid("unsupported_question");
4249
4240
  const questions = pending.questions.map((question, index) => {
4250
- if (typeof question.question !== "string" || !Array.isArray(question.options) || question.options.length > 32 || question.options.some((option) => typeof option?.label !== "string") || new Set(question.options.map((option) => option.label)).size !== question.options.length) {
4241
+ if (typeof question.question !== "string" || !Array.isArray(question.options) || question.options.some((option) => typeof option?.label !== "string") || new Set(question.options.map((option) => option.label)).size !== question.options.length) {
4251
4242
  throw invalid("unsupported_question");
4252
4243
  }
4253
4244
  return {
@@ -4270,14 +4261,13 @@ function questionSnapshot(pending, text) {
4270
4261
  ...pending.nativeRevision ? { nativeRevision: pending.nativeRevision } : {}
4271
4262
  })).digest("hex");
4272
4263
  const result = { questionId: pending.id, sessionID: pending.sessionID, revision, questions };
4273
- if (Buffer.byteLength(JSON.stringify(result)) > 3e4) throw invalid("question_too_large");
4274
4264
  return result;
4275
4265
  }
4276
4266
  function resolveQuestionAnswers(pending, answers) {
4277
4267
  if (!Array.isArray(answers) || answers.length !== pending.questions.length) throw invalid();
4278
4268
  const values = answers.map((answer, index) => {
4279
4269
  const question = pending.questions[index];
4280
- if (!answer || !Array.isArray(answer.choices) || answer.choices.length > 32 || Object.keys(answer).some((key) => !["choices", "text"].includes(key)) || new Set(answer.choices).size !== answer.choices.length || answer.choices.some((choice) => !Number.isInteger(choice) || choice < 0 || choice >= question.options.length) || answer.text !== void 0 && (typeof answer.text !== "string" || !answer.text.trim() || answer.text.length > 8e3)) throw invalid();
4270
+ if (!answer || !Array.isArray(answer.choices) || Object.keys(answer).some((key) => !["choices", "text"].includes(key)) || new Set(answer.choices).size !== answer.choices.length || answer.choices.some((choice) => !Number.isInteger(choice) || choice < 0 || choice >= question.options.length) || answer.text !== void 0 && (typeof answer.text !== "string" || !answer.text.trim())) throw invalid();
4281
4271
  if (answer.text !== void 0 && question.custom !== true) throw invalid("custom_not_allowed");
4282
4272
  return [
4283
4273
  ...answer.choices.map((choice) => question.options[choice].label),
@@ -4290,7 +4280,6 @@ function resolveQuestionAnswers(pending, answers) {
4290
4280
  }
4291
4281
 
4292
4282
  // src/live-bridge.js
4293
- var LIMITS = { events: 256, bytes: 2e6, clients: 8, commands: 1024, commandTTL: 864e5, body: 32768, requests: 16 };
4294
4283
  function failure(status, code) {
4295
4284
  return Object.assign(new Error(code), { status, code });
4296
4285
  }
@@ -4302,10 +4291,7 @@ function json(response, status, value) {
4302
4291
  }
4303
4292
  async function readBody(request) {
4304
4293
  const chunks = [];
4305
- let bytes = 0;
4306
4294
  for await (const chunk of request) {
4307
- bytes += chunk.length;
4308
- if (bytes > LIMITS.body) throw failure(413, "body_too_large");
4309
4295
  chunks.push(chunk);
4310
4296
  }
4311
4297
  try {
@@ -4316,9 +4302,11 @@ async function readBody(request) {
4316
4302
  }
4317
4303
  async function createLiveBridge({ config, client, directory, reporter }) {
4318
4304
  const options = config.liveBridge;
4319
- if (!options?.enabled) return { handle() {
4320
- }, async dispose() {
4321
- }, address: void 0 };
4305
+ if (!options?.enabled || options.projectDirectory !== void 0 && resolve2(options.projectDirectory) !== resolve2(directory)) {
4306
+ return { handle() {
4307
+ }, async dispose() {
4308
+ }, address: void 0 };
4309
+ }
4322
4310
  const token = options.token;
4323
4311
  if (typeof token !== "string" || token.length < 32 || token === config.token) {
4324
4312
  throw new Error("Live bridge requires a separate credential of at least 32 characters");
@@ -4328,45 +4316,44 @@ async function createLiveBridge({ config, client, directory, reporter }) {
4328
4316
  const sanitizer = createBridgeSanitizer([token, config.token]);
4329
4317
  const epoch = randomUUID3();
4330
4318
  const events = [], streams = /* @__PURE__ */ new Set(), commands = /* @__PURE__ */ new Map(), states = /* @__PURE__ */ new Map();
4331
- let sequence = 0, bytes = 0, active = 0, disposed = false, disposeTask;
4332
- let lookups = 0;
4333
- const lookupWaiters = /* @__PURE__ */ new Set();
4319
+ let sequence = 0, disposed = false, disposeTask;
4320
+ const lookupWaiters = /* @__PURE__ */ new Set(), lookups = /* @__PURE__ */ new Map();
4334
4321
  const cursor = () => `${epoch}:${sequence}`;
4335
- function lookup(id) {
4322
+ function lookup(id, signal2) {
4336
4323
  if (disposed) return Promise.reject(failure(503, "bridge_closed"));
4337
- if (lookups >= 16) return Promise.reject(failure(429, "session_lookup_busy"));
4338
- lookups++;
4339
- return new Promise((resolve2, reject) => {
4340
- let finished = false;
4341
- const finish = (complete, value) => {
4342
- if (finished) return;
4343
- finished = true;
4344
- clearTimeout(timer);
4324
+ let entry = lookups.get(id);
4325
+ if (!entry) {
4326
+ entry = { waiters: /* @__PURE__ */ new Set() };
4327
+ lookups.set(id, entry);
4328
+ const settled = (ok, value) => {
4329
+ lookups.delete(id);
4330
+ for (const finish of [...entry.waiters]) finish(ok, value);
4331
+ };
4332
+ Promise.resolve().then(() => api.get(id)).then((value) => settled(true, value), (error) => settled(false, error));
4333
+ }
4334
+ return new Promise((resolve3, reject) => {
4335
+ const finish = (ok, value) => {
4336
+ if (!entry.waiters.delete(finish)) return;
4345
4337
  lookupWaiters.delete(cancel);
4346
- complete(value);
4338
+ signal2?.removeEventListener("abort", aborted);
4339
+ if (ok) resolve3(value);
4340
+ else reject(value);
4347
4341
  };
4348
- const cancel = () => finish(reject, failure(503, "bridge_closed"));
4349
- const timer = setTimeout(() => finish(reject, failure(504, "session_lookup_timed_out")), 1e3);
4342
+ const cancel = () => finish(false, failure(503, "bridge_closed"));
4343
+ const aborted = () => finish(false, failure(499, "request_cancelled"));
4344
+ entry.waiters.add(finish);
4350
4345
  lookupWaiters.add(cancel);
4351
- Promise.resolve().then(() => api.get(id)).then(
4352
- (value) => {
4353
- lookups--;
4354
- finish(resolve2, value);
4355
- },
4356
- (error) => {
4357
- lookups--;
4358
- finish(reject, error);
4359
- }
4360
- );
4346
+ signal2?.addEventListener("abort", aborted, { once: true });
4347
+ if (signal2?.aborted) aborted();
4361
4348
  });
4362
4349
  }
4363
- async function session(id) {
4350
+ async function session(id, signal2) {
4364
4351
  if (!validBridgeID(id) || id === "global") throw failure(400, "invalid_session");
4365
4352
  let info;
4366
4353
  try {
4367
- info = await lookup(id);
4354
+ info = await lookup(id, signal2);
4368
4355
  } catch (error) {
4369
- if (["session_lookup_busy", "session_lookup_timed_out", "bridge_closed"].includes(error.code)) throw error;
4356
+ if (["bridge_closed", "request_cancelled"].includes(error.code)) throw error;
4370
4357
  throw failure(404, "session_not_found");
4371
4358
  }
4372
4359
  if (info?.id !== id || typeof info.time?.archived === "number") {
@@ -4388,18 +4375,12 @@ async function createLiveBridge({ config, client, directory, reporter }) {
4388
4375
  const removed = streams.delete(stream);
4389
4376
  clearInterval(stream.heartbeat);
4390
4377
  stream.queue.length = 0;
4391
- stream.queuedBytes = 0;
4392
4378
  if (removed && !streams.size && !disposed) connectionNotifications.update(false);
4393
4379
  }
4394
4380
  function write(stream, value) {
4395
4381
  if (!streams.has(stream)) return;
4396
4382
  if (stream.blocked) {
4397
4383
  stream.queue.push(value);
4398
- stream.queuedBytes += Buffer.byteLength(value);
4399
- if (stream.queuedBytes > LIMITS.bytes) {
4400
- removeStream(stream);
4401
- stream.response.destroy();
4402
- }
4403
4384
  return;
4404
4385
  }
4405
4386
  if (!stream.response.write(value)) {
@@ -4419,25 +4400,13 @@ async function createLiveBridge({ config, client, directory, reporter }) {
4419
4400
  if (safe.type === "session.status") states.set(safe.sessionID, safe.data.status);
4420
4401
  if (safe.type === "session.idle") states.set(safe.sessionID, "idle");
4421
4402
  if (safe.type === "session.deleted") states.delete(safe.sessionID);
4422
- if (states.size > 256) states.delete(states.keys().next().value);
4423
4403
  const record = { id: `${epoch}:${++sequence}`, ...safe, timestamp: Date.now() };
4424
- let frame = `id: ${record.id}
4425
- event: backeer.event
4426
- data: ${JSON.stringify(record)}
4427
-
4428
- `;
4429
- if (Buffer.byteLength(frame) > 32768) {
4430
- record.data = { truncated: true };
4431
- frame = `id: ${record.id}
4404
+ const frame = `id: ${record.id}
4432
4405
  event: backeer.event
4433
4406
  data: ${JSON.stringify(record)}
4434
4407
 
4435
4408
  `;
4436
- }
4437
- const size = Buffer.byteLength(frame);
4438
- events.push({ sequence, sessionID: record.sessionID, frame, size });
4439
- bytes += size;
4440
- while (events.length > LIMITS.events || bytes > LIMITS.bytes) bytes -= events.shift().size;
4409
+ events.push({ sequence, sessionID: record.sessionID, frame });
4441
4410
  for (const stream of streams) if (!stream.sessionID || stream.sessionID === record.sessionID) write(stream, frame);
4442
4411
  }
4443
4412
  function authorized(request) {
@@ -4448,7 +4417,7 @@ data: ${JSON.stringify(record)}
4448
4417
  }
4449
4418
  function parseCursor(value) {
4450
4419
  if (!value) return { after: 0 };
4451
- const match = /^([a-f0-9-]{36}):(\d{1,16})$/.exec(value);
4420
+ const match = /^([a-f0-9-]{36}):(\d+)$/.exec(value);
4452
4421
  if (!match) throw failure(400, "invalid_cursor");
4453
4422
  if (match[1] !== epoch) return { gap: "stream_restarted" };
4454
4423
  const after = Number(match[2]);
@@ -4457,9 +4426,6 @@ data: ${JSON.stringify(record)}
4457
4426
  return { after };
4458
4427
  }
4459
4428
  function cachedCommand(requestId, sessionID, signature) {
4460
- for (const [id, command] of commands) {
4461
- if (command.settled && Date.now() - command.created >= LIMITS.commandTTL) commands.delete(id);
4462
- }
4463
4429
  const existing = commands.get(requestId);
4464
4430
  if (existing && (existing.sessionID !== sessionID || existing.signature !== signature)) {
4465
4431
  throw failure(409, "request_id_conflict");
@@ -4469,7 +4435,6 @@ data: ${JSON.stringify(record)}
4469
4435
  async function execute(requestId, sessionID, signature, operation, questionId) {
4470
4436
  let command = cachedCommand(requestId, sessionID, signature);
4471
4437
  if (!command) {
4472
- if (commands.size >= LIMITS.commands) throw failure(429, "command_window_full");
4473
4438
  if (questionId && [...commands.values()].some((item) => item.sessionID === sessionID && item.questionId === questionId)) {
4474
4439
  throw failure(409, "question_reply_already_submitted");
4475
4440
  }
@@ -4477,8 +4442,6 @@ data: ${JSON.stringify(record)}
4477
4442
  sessionID,
4478
4443
  signature,
4479
4444
  questionId,
4480
- created: Date.now(),
4481
- settled: false,
4482
4445
  result: {
4483
4446
  requestId,
4484
4447
  sessionID,
@@ -4487,9 +4450,7 @@ data: ${JSON.stringify(record)}
4487
4450
  }
4488
4451
  };
4489
4452
  commands.set(requestId, command);
4490
- command.task = Promise.resolve().then(operation).finally(() => {
4491
- command.settled = true;
4492
- });
4453
+ command.task = Promise.resolve().then(operation).then(() => void 0);
4493
4454
  }
4494
4455
  await command.task;
4495
4456
  return command.result;
@@ -4499,7 +4460,6 @@ data: ${JSON.stringify(record)}
4499
4460
  const pending = await api.questions(sessionID);
4500
4461
  if (!Array.isArray(pending)) throw failure(502, "invalid_question_list");
4501
4462
  const own = pending.filter((item) => item.sessionID === sessionID);
4502
- if (own.length > 16) throw failure(413, "too_many_questions");
4503
4463
  return own;
4504
4464
  }
4505
4465
  async function route(request, response) {
@@ -4507,10 +4467,9 @@ data: ${JSON.stringify(record)}
4507
4467
  if (request.headers["x-backeer-project"] !== void 0 && request.headers["x-backeer-project"] !== encodeURIComponent(directory)) {
4508
4468
  return json(response, 409, { error: "project_mismatch" });
4509
4469
  }
4510
- if (active >= LIMITS.requests) return json(response, 429, { error: "bridge_busy" });
4511
- active++;
4512
- const timeout = setTimeout(() => json(response, 504, { error: "request_timed_out" }), 15e3);
4513
- timeout.unref();
4470
+ const controller = new AbortController();
4471
+ const closed = () => controller.abort();
4472
+ response.once("close", closed);
4514
4473
  try {
4515
4474
  const url = new URL(request.url, "http://127.0.0.1");
4516
4475
  if (url.username || url.password) throw failure(400, "invalid_url");
@@ -4519,7 +4478,7 @@ data: ${JSON.stringify(record)}
4519
4478
  const values = Array.isArray(result2) ? result2 : result2?.data;
4520
4479
  if (!Array.isArray(values)) throw failure(502, "invalid_session_list");
4521
4480
  const own = [];
4522
- for (const info of values.slice(0, 100)) {
4481
+ for (const info of values) {
4523
4482
  if (!validBridgeID(info?.id) || typeof info.time?.archived === "number") continue;
4524
4483
  if (api.ownsSession ? await api.ownsSession(info) : (info.location?.directory ?? info.directory) === directory) own.push(info);
4525
4484
  }
@@ -4548,11 +4507,9 @@ data: ${JSON.stringify(record)}
4548
4507
  }
4549
4508
  if (request.method === "GET" && url.pathname === "/v1/events") {
4550
4509
  const selected = url.searchParams.get("sessionID") || void 0;
4551
- if (selected) await session(selected);
4510
+ if (selected) await session(selected, controller.signal);
4552
4511
  const parsed = parseCursor(url.searchParams.get("after") ?? request.headers["last-event-id"]);
4553
4512
  if (response.headersSent) return;
4554
- if (streams.size >= LIMITS.clients) throw failure(429, "too_many_streams");
4555
- clearTimeout(timeout);
4556
4513
  response.writeHead(200, {
4557
4514
  "content-type": "text/event-stream",
4558
4515
  "cache-control": "no-cache, no-transform",
@@ -4571,8 +4528,7 @@ data: ${JSON.stringify({ reason: parsed.gap, cursor: cursor() })}
4571
4528
  sessionID: selected,
4572
4529
  heartbeat: void 0,
4573
4530
  blocked: false,
4574
- queue: [],
4575
- queuedBytes: 0
4531
+ queue: []
4576
4532
  };
4577
4533
  streams.add(stream);
4578
4534
  connectionNotifications.update(true);
@@ -4583,7 +4539,6 @@ data: ${JSON.stringify({ reason: parsed.gap, cursor: cursor() })}
4583
4539
  stream.blocked = false;
4584
4540
  while (streams.has(stream) && !stream.blocked && stream.queue.length) {
4585
4541
  const next = stream.queue.shift();
4586
- stream.queuedBytes -= Buffer.byteLength(next);
4587
4542
  write(stream, next);
4588
4543
  }
4589
4544
  });
@@ -4601,13 +4556,12 @@ retry: ${LIVE_RECONNECT_MS}
4601
4556
  }
4602
4557
  return;
4603
4558
  }
4604
- const match = /^\/v1\/sessions\/([A-Za-z0-9_-]{1,160})(\/instructions|\/questions(?:\/([A-Za-z0-9_-]{1,160})\/answers)?)?$/.exec(url.pathname);
4559
+ const match = /^\/v1\/sessions\/([A-Za-z0-9_-]+)(\/instructions|\/questions(?:\/([A-Za-z0-9_-]+)\/answers)?)?$/.exec(url.pathname);
4605
4560
  if (!match) throw failure(404, "route_not_found");
4606
- if (request.method === "GET" && !match[2]) return json(response, 200, await session(match[1]));
4561
+ if (request.method === "GET" && !match[2]) return json(response, 200, await session(match[1], controller.signal));
4607
4562
  if (request.method === "GET" && match[2] === "/questions") {
4608
- await session(match[1]);
4563
+ await session(match[1], controller.signal);
4609
4564
  const result2 = { questions: (await questions(match[1])).map((item) => questionSnapshot(item, sanitizer.text)) };
4610
- if (Buffer.byteLength(JSON.stringify(result2)) > 24e4) throw failure(413, "questions_too_large");
4611
4565
  return json(response, 200, result2);
4612
4566
  }
4613
4567
  if (request.method !== "POST" || !match[2]) throw failure(405, "method_not_allowed");
@@ -4615,8 +4569,8 @@ retry: ${LIVE_RECONNECT_MS}
4615
4569
  const body = await readBody(request);
4616
4570
  if (match[3]) {
4617
4571
  if (!body || !validBridgeID(body.requestId) || !/^[a-f0-9]{64}$/.test(body.revision) || Object.keys(body).some((key) => !["requestId", "revision", "answers"].includes(key))) throw failure(400, "invalid_question_answer");
4618
- await session(match[1]);
4619
- const signature2 = JSON.stringify({ questionId: match[3], revision: body.revision, answers: body.answers });
4572
+ await session(match[1], controller.signal);
4573
+ const signature2 = createHash2("sha256").update(JSON.stringify({ questionId: match[3], revision: body.revision, answers: body.answers })).digest("hex");
4620
4574
  const existing2 = cachedCommand(body.requestId, match[1], signature2);
4621
4575
  if (existing2) {
4622
4576
  await existing2.task;
@@ -4637,31 +4591,30 @@ retry: ${LIVE_RECONNECT_MS}
4637
4591
  ));
4638
4592
  }
4639
4593
  if (match[2] !== "/instructions") throw failure(405, "method_not_allowed");
4640
- if (!body || !validBridgeID(body.requestId) || typeof body.prompt !== "string" || !body.prompt.trim() || body.prompt.length > 16e3 || Object.keys(body).some((key) => !["requestId", "prompt"].includes(key))) throw failure(400, "invalid_instruction");
4641
- await session(match[1]);
4594
+ if (!body || !validBridgeID(body.requestId) || typeof body.prompt !== "string" || !body.prompt.trim() || Object.keys(body).some((key) => !["requestId", "prompt"].includes(key))) throw failure(400, "invalid_instruction");
4595
+ await session(match[1], controller.signal);
4642
4596
  if (response.headersSent || disposed) return;
4643
- const signature = JSON.stringify({ prompt: body.prompt });
4597
+ const signature = createHash2("sha256").update(JSON.stringify({ prompt: body.prompt })).digest("hex");
4644
4598
  const existing = cachedCommand(body.requestId, match[1], signature);
4645
4599
  const result = await execute(body.requestId, match[1], signature, () => api.prompt(match[1], body.prompt));
4646
4600
  return json(response, existing ? 200 : 202, result);
4647
4601
  } catch (error) {
4648
4602
  json(response, error.status ?? 502, { error: error.code ?? "opencode_request_failed" });
4649
4603
  } finally {
4650
- active--;
4651
- clearTimeout(timeout);
4604
+ response.removeListener("close", closed);
4652
4605
  }
4653
4606
  }
4654
4607
  const server = createServer((request, response) => {
4655
4608
  void route(request, response);
4656
4609
  });
4657
- server.requestTimeout = 15e3;
4658
- server.headersTimeout = 1e4;
4659
- server.keepAliveTimeout = 5e3;
4660
- await new Promise((resolve2, reject) => {
4610
+ server.requestTimeout = 0;
4611
+ server.headersTimeout = 0;
4612
+ server.keepAliveTimeout = 0;
4613
+ await new Promise((resolve3, reject) => {
4661
4614
  server.once("error", reject);
4662
4615
  server.listen(options.port ?? 43871, "127.0.0.1", () => {
4663
4616
  server.off("error", reject);
4664
- resolve2();
4617
+ resolve3();
4665
4618
  });
4666
4619
  });
4667
4620
  server.on("error", () => {
@@ -4670,7 +4623,7 @@ retry: ${LIVE_RECONNECT_MS}
4670
4623
  handle,
4671
4624
  address: server.address(),
4672
4625
  dispose() {
4673
- disposeTask ??= new Promise((resolve2) => {
4626
+ disposeTask ??= new Promise((resolve3) => {
4674
4627
  disposed = true;
4675
4628
  for (const cancel of lookupWaiters) cancel();
4676
4629
  for (const stream of streams) {
@@ -4678,12 +4631,13 @@ retry: ${LIVE_RECONNECT_MS}
4678
4631
  stream.response.destroy();
4679
4632
  }
4680
4633
  server.close(() => {
4681
- void connectionNotifications.dispose().then(resolve2);
4634
+ void connectionNotifications.dispose().then(resolve3);
4682
4635
  });
4683
4636
  server.closeAllConnections?.();
4684
4637
  events.length = 0;
4685
4638
  states.clear();
4686
4639
  commands.clear();
4640
+ lookups.clear();
4687
4641
  sanitizer.clear();
4688
4642
  api.dispose?.();
4689
4643
  });
@@ -4705,10 +4659,12 @@ async function BackeerPlugin({ client, directory, feedback, v2 = false }, option
4705
4659
  bootstrapReporter.error("Plugin disabled \u2014 invalid configuration", error);
4706
4660
  return {};
4707
4661
  }
4662
+ const shutdown = new AbortController();
4663
+ config.signal = shutdown.signal;
4708
4664
  const reporter = createOpenCodeReporter(client, config.logLevel);
4709
4665
  const conversation = feedback ?? createConversationFeedback({ client, reporter });
4710
4666
  if (config.liveBridge.error) reporter.error("Live control disabled", config.liveBridge.error);
4711
- const bridgeConfig = config.liveBridge.owner === "tui" ? { ...config, liveBridge: { enabled: false } } : config;
4667
+ const bridgeConfig = config.liveBridge.owner === "tui" || config.liveBridge.owner === "standalone" && (!v2 || !process.argv.includes("--stdio")) ? { ...config, liveBridge: { enabled: false } } : config;
4712
4668
  const liveBridge = await createLiveBridge({ config: bridgeConfig, client, directory, reporter }).catch((error) => {
4713
4669
  reporter.error("Live control unavailable", error);
4714
4670
  return { async handle() {
@@ -4864,6 +4820,7 @@ async function BackeerPlugin({ client, directory, feedback, v2 = false }, option
4864
4820
  await Promise.all([handleNotificationEvent(event), liveBridge.handle(event)]);
4865
4821
  },
4866
4822
  dispose: async () => {
4823
+ shutdown.abort();
4867
4824
  await liveBridge.dispose();
4868
4825
  await permissionController.dispose();
4869
4826
  lifecycleController.dispose();
@@ -5344,7 +5301,7 @@ async function terminate(info, options, timing) {
5344
5301
  await rm2(options.file ?? fallback2(), { force: true });
5345
5302
  }
5346
5303
  function delay(milliseconds) {
5347
- return new Promise((resolve2) => setTimeout(resolve2, milliseconds));
5304
+ return new Promise((resolve3) => setTimeout(resolve3, milliseconds));
5348
5305
  }
5349
5306
  var Service = { discover, ensure, stop, headers: headers2 };
5350
5307
 
@@ -5382,6 +5339,8 @@ async function privateEndpoint({
5382
5339
  argv = process.argv,
5383
5340
  env = process.env,
5384
5341
  pid = process.pid,
5342
+ platform = process.platform,
5343
+ initialEnvironment = () => readFile3("/proc/self/environ", "utf8"),
5385
5344
  urls = ownLoopbackUrls,
5386
5345
  request = fetch
5387
5346
  } = {}) {
@@ -5389,9 +5348,10 @@ async function privateEndpoint({
5389
5348
  const flag = argv.indexOf("--port");
5390
5349
  const rawPort = flag >= 0 ? argv[flag + 1] : argv.find((arg) => arg.startsWith("--port="))?.slice(7);
5391
5350
  const port = typeof rawPort === "string" && /^\d+$/.test(rawPort) ? Number(rawPort) : NaN;
5392
- const password = env.OPENCODE_PASSWORD;
5393
5351
  try {
5394
- if (!Number.isInteger(port) || port < 0 || port > 65535 || !password) throw new Error();
5352
+ if (!Number.isInteger(port) || port < 0 || port > 65535) throw new Error();
5353
+ const password = env.OPENCODE_PASSWORD || (platform === "linux" ? (await initialEnvironment()).split("\0").find((value) => value.startsWith("OPENCODE_PASSWORD="))?.slice(18) : void 0);
5354
+ if (!password) throw new Error();
5395
5355
  const candidates = port === 0 ? await urls() : [`http://127.0.0.1:${port}`];
5396
5356
  for (const url of candidates) {
5397
5357
  const endpoint = { url, auth: { type: "basic", username: "opencode", password } };
@@ -5444,7 +5404,7 @@ async function getFormApi(ctx, sessionID, { makeClient = exports_client.make, ow
5444
5404
  }
5445
5405
 
5446
5406
  // src/opencode-v2-client.js
5447
- import { createHash as createHash2 } from "node:crypto";
5407
+ import { createHash as createHash3 } from "node:crypto";
5448
5408
  async function nativeReply(operation) {
5449
5409
  try {
5450
5410
  return await operation();
@@ -5470,7 +5430,7 @@ function questionProperties(form) {
5470
5430
  return {
5471
5431
  id: form.id,
5472
5432
  sessionID: form.sessionID,
5473
- nativeRevision: createHash2("sha256").update(JSON.stringify({
5433
+ nativeRevision: createHash3("sha256").update(JSON.stringify({
5474
5434
  id: form.id,
5475
5435
  sessionID: form.sessionID,
5476
5436
  title: form.title,
@@ -5612,11 +5572,11 @@ function createV2Client(ctx, resolveFormApi) {
5612
5572
 
5613
5573
  // src/opencode-v2.js
5614
5574
  function waitForReconnect(signal2, delay2) {
5615
- return new Promise((resolve2) => {
5575
+ return new Promise((resolve3) => {
5616
5576
  const finish = () => {
5617
5577
  clearTimeout(timer);
5618
5578
  signal2.removeEventListener("abort", finish);
5619
- resolve2();
5579
+ resolve3();
5620
5580
  };
5621
5581
  const timer = setTimeout(finish, delay2);
5622
5582
  signal2.addEventListener("abort", finish, { once: true });