@threadbase-sh/streamer 1.37.0 → 1.39.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.
package/dist/cli.cjs CHANGED
@@ -1490,6 +1490,9 @@ function isPermissionMode(value) {
1490
1490
  function isDangerousPermissionMode(mode) {
1491
1491
  return DANGEROUS_PERMISSION_MODES.includes(mode);
1492
1492
  }
1493
+ function isEffortLevel(value) {
1494
+ return typeof value === "string" && EFFORT_LEVELS.includes(value);
1495
+ }
1493
1496
  function findFlag(id) {
1494
1497
  return CLAUDE_FLAGS.find((f2) => f2.id === id);
1495
1498
  }
@@ -1554,7 +1557,7 @@ function buildFlagArgs(values, extraArgs) {
1554
1557
  const args = [];
1555
1558
  const safe = validateFlagValues(values ?? {});
1556
1559
  for (const def of CLAUDE_FLAGS) {
1557
- if (def.id === "permissionMode") continue;
1560
+ if (SPAWN_POSITIONAL_FLAG_IDS.has(def.id)) continue;
1558
1561
  const value = safe[def.id];
1559
1562
  if (value === void 0) continue;
1560
1563
  if (def.valueType === "boolean") {
@@ -1577,7 +1580,7 @@ function buildSettingsJson(permissionMode) {
1577
1580
  }
1578
1581
  return JSON.stringify(settings);
1579
1582
  }
1580
- var PERMISSION_MODES, DANGEROUS_PERMISSION_MODES, CLAUDE_FLAGS;
1583
+ var PERMISSION_MODES, DANGEROUS_PERMISSION_MODES, EFFORT_LEVELS, CLAUDE_FLAGS, SPAWN_POSITIONAL_FLAG_IDS;
1581
1584
  var init_claude_flags = __esm({
1582
1585
  "src/claude-flags.ts"() {
1583
1586
  "use strict";
@@ -1593,6 +1596,7 @@ var init_claude_flags = __esm({
1593
1596
  "bypassPermissions",
1594
1597
  "dontAsk"
1595
1598
  ];
1599
+ EFFORT_LEVELS = ["low", "medium", "high", "xhigh", "max"];
1596
1600
  CLAUDE_FLAGS = [
1597
1601
  {
1598
1602
  id: "permissionMode",
@@ -1604,9 +1608,10 @@ var init_claude_flags = __esm({
1604
1608
  { id: "addDir", flag: "--add-dir", valueType: "list", risk: "elevated" },
1605
1609
  { id: "allowedTools", flag: "--allowedTools", valueType: "list", risk: "elevated" },
1606
1610
  { id: "disallowedTools", flag: "--disallowedTools", valueType: "list", risk: "low" },
1607
- { id: "maxBudgetUsd", flag: "--max-budget-usd", valueType: "string", risk: "low" },
1608
- { id: "fallbackModel", flag: "--fallback-model", valueType: "string", risk: "low" }
1611
+ { id: "model", flag: "--model", valueType: "string", risk: "low" },
1612
+ { id: "effort", flag: "--effort", valueType: "enum", enumValues: EFFORT_LEVELS, risk: "low" }
1609
1613
  ];
1614
+ SPAWN_POSITIONAL_FLAG_IDS = /* @__PURE__ */ new Set(["permissionMode", "model", "effort"]);
1610
1615
  }
1611
1616
  });
1612
1617
 
@@ -4632,11 +4637,11 @@ var require_tools = __commonJS({
4632
4637
  }
4633
4638
  }
4634
4639
  }
4635
- function buildFormatters(level, bindings, log8) {
4640
+ function buildFormatters(level, bindings, log12) {
4636
4641
  return {
4637
4642
  level,
4638
4643
  bindings,
4639
- log: log8
4644
+ log: log12
4640
4645
  };
4641
4646
  }
4642
4647
  function normalizeDestFileDescriptor(destination) {
@@ -5018,11 +5023,11 @@ var require_proto = __commonJS({
5018
5023
  }
5019
5024
  } else instance[serializersSym] = serializers;
5020
5025
  if (options.hasOwnProperty("formatters")) {
5021
- const { level, bindings: chindings, log: log8 } = options.formatters;
5026
+ const { level, bindings: chindings, log: log12 } = options.formatters;
5022
5027
  instance[formattersSym] = buildFormatters(
5023
5028
  level || formatters.level,
5024
5029
  chindings || resetChildingsFormatter,
5025
- log8 || formatters.log
5030
+ log12 || formatters.log
5026
5031
  );
5027
5032
  } else {
5028
5033
  instance[formattersSym] = buildFormatters(
@@ -5932,7 +5937,7 @@ var require_pino = __commonJS({
5932
5937
  } = symbols;
5933
5938
  var { epochTime, nullTime } = time3;
5934
5939
  var { pid } = process;
5935
- var hostname4 = os2.hostname();
5940
+ var hostname5 = os2.hostname();
5936
5941
  var defaultErrorSerializer = stdSerializers.err;
5937
5942
  var defaultOptions2 = {
5938
5943
  level: "info",
@@ -5942,7 +5947,7 @@ var require_pino = __commonJS({
5942
5947
  errorKey: "err",
5943
5948
  nestedKey: null,
5944
5949
  enabled: true,
5945
- base: { pid, hostname: hostname4 },
5950
+ base: { pid, hostname: hostname5 },
5946
5951
  serializers: Object.assign(/* @__PURE__ */ Object.create(null), {
5947
5952
  err: defaultErrorSerializer
5948
5953
  }),
@@ -6133,6 +6138,86 @@ var init_logger = __esm({
6133
6138
  }
6134
6139
  });
6135
6140
 
6141
+ // src/feature-flags.ts
6142
+ function findFeatureFlag(id) {
6143
+ return FEATURE_FLAGS.find((f2) => f2.id === id);
6144
+ }
6145
+ function parseBooleanEnv(raw2) {
6146
+ if (raw2 === void 0) return void 0;
6147
+ const v2 = raw2.trim().toLowerCase();
6148
+ if (v2 === "") return false;
6149
+ return !(v2 === "0" || v2 === "false" || v2 === "no" || v2 === "off");
6150
+ }
6151
+ function validateFeatureFlagValues(raw2) {
6152
+ if (!raw2 || typeof raw2 !== "object" || Array.isArray(raw2)) return {};
6153
+ const out = {};
6154
+ const dropped = [];
6155
+ for (const [id, value] of Object.entries(raw2)) {
6156
+ if (!findFeatureFlag(id) || typeof value !== "boolean") {
6157
+ dropped.push(id);
6158
+ continue;
6159
+ }
6160
+ out[id] = value;
6161
+ }
6162
+ if (dropped.length > 0) {
6163
+ getLogger("feature-flags").warn(
6164
+ `Ignoring unknown or non-boolean feature flags: ${dropped.join(", ")}`,
6165
+ {
6166
+ event: "config.feature_flags_dropped",
6167
+ dropped
6168
+ }
6169
+ );
6170
+ }
6171
+ return out;
6172
+ }
6173
+ function parseFeatureFlagArgs(entries) {
6174
+ const values = {};
6175
+ const errors = [];
6176
+ for (const entry of entries) {
6177
+ const eq = entry.indexOf("=");
6178
+ const id = (eq === -1 ? entry : entry.slice(0, eq)).trim();
6179
+ const rawValue = eq === -1 ? "true" : entry.slice(eq + 1).trim().toLowerCase();
6180
+ if (!findFeatureFlag(id)) {
6181
+ errors.push(
6182
+ `Unknown feature flag "${id}". Known flags: ${FEATURE_FLAGS.map((f2) => f2.id).join(", ")}`
6183
+ );
6184
+ continue;
6185
+ }
6186
+ if (rawValue !== "true" && rawValue !== "false") {
6187
+ errors.push(`Invalid value "${rawValue}" for feature flag "${id}" \u2014 expected true/false`);
6188
+ continue;
6189
+ }
6190
+ values[id] = rawValue === "true";
6191
+ }
6192
+ return { values, errors };
6193
+ }
6194
+ function resolveFeatureFlags(opts) {
6195
+ const env = opts?.env ?? process.env;
6196
+ const out = {};
6197
+ for (const def of FEATURE_FLAGS) {
6198
+ out[def.id] = parseBooleanEnv(env[def.env]) ?? opts?.cli?.[def.id] ?? opts?.yaml?.[def.id] ?? def.default;
6199
+ }
6200
+ return out;
6201
+ }
6202
+ function nonDefaultFeatureFlags(values) {
6203
+ return FEATURE_FLAGS.filter((f2) => values[f2.id] !== f2.default).map((f2) => f2.id);
6204
+ }
6205
+ var FEATURE_FLAGS;
6206
+ var init_feature_flags = __esm({
6207
+ "src/feature-flags.ts"() {
6208
+ "use strict";
6209
+ init_logger();
6210
+ FEATURE_FLAGS = [
6211
+ {
6212
+ id: "codexSystemPrompt",
6213
+ description: "Send the built system prompt to fresh Codex sessions. Off by default: Codex has no --system-prompt flag, so the prompt goes in the positional [PROMPT] argument, which Codex treats as the user's opening turn rather than a system-level instruction.",
6214
+ default: false,
6215
+ env: "THREADBASE_FEATURE_CODEX_SYSTEM_PROMPT"
6216
+ }
6217
+ ];
6218
+ }
6219
+ });
6220
+
6136
6221
  // src/auth.ts
6137
6222
  function configDir() {
6138
6223
  return process.env.THREADBASE_CONFIG_DIR ?? (0, import_path.join)((0, import_os.homedir)(), ".threadbase");
@@ -6297,6 +6382,21 @@ function setClaudeExtraArgs(text) {
6297
6382
  }
6298
6383
  setConfigValue("claude_extra_args", trimmed && trimmed.length > 0 ? trimmed : void 0);
6299
6384
  }
6385
+ function loadFeatureFlags() {
6386
+ try {
6387
+ const content = (0, import_fs.readFileSync)(configFile(), "utf-8");
6388
+ const match2 = content.match(/^feature_flags:\s*(.+)$/m);
6389
+ if (!match2?.[1]) return {};
6390
+ return validateFeatureFlagValues(JSON.parse(match2[1].trim()));
6391
+ } catch (err) {
6392
+ if (err.code !== "ENOENT") {
6393
+ getLogger("auth").warn(`Ignoring unreadable feature_flags in server.yaml: ${String(err)}`, {
6394
+ event: "config.feature_flags_parse_failed"
6395
+ });
6396
+ }
6397
+ return {};
6398
+ }
6399
+ }
6300
6400
  function validatePublicUrl(raw2) {
6301
6401
  let parsed;
6302
6402
  try {
@@ -6354,6 +6454,7 @@ var init_auth = __esm({
6354
6454
  import_os = require("os");
6355
6455
  import_path = require("path");
6356
6456
  init_claude_flags();
6457
+ init_feature_flags();
6357
6458
  init_logger();
6358
6459
  }
6359
6460
  });
@@ -8078,7 +8179,7 @@ var require_merge = __commonJS({
8078
8179
  var require_addPairToJSMap = __commonJS({
8079
8180
  "node_modules/yaml/dist/nodes/addPairToJSMap.js"(exports2) {
8080
8181
  "use strict";
8081
- var log8 = require_log();
8182
+ var log12 = require_log();
8082
8183
  var merge2 = require_merge();
8083
8184
  var stringify = require_stringify();
8084
8185
  var identity = require_identity();
@@ -8127,7 +8228,7 @@ var require_addPairToJSMap = __commonJS({
8127
8228
  let jsonStr = JSON.stringify(strKey);
8128
8229
  if (jsonStr.length > 40)
8129
8230
  jsonStr = jsonStr.substring(0, 36) + '..."';
8130
- log8.warn(ctx.doc.options.logLevel, `Keys with collection values will be stringified due to JS Object restrictions: ${jsonStr}. Set mapAsMap: true to use object keys.`);
8231
+ log12.warn(ctx.doc.options.logLevel, `Keys with collection values will be stringified due to JS Object restrictions: ${jsonStr}. Set mapAsMap: true to use object keys.`);
8131
8232
  ctx.mapKeyWarned = true;
8132
8233
  }
8133
8234
  return strKey;
@@ -13543,7 +13644,7 @@ var require_public_api = __commonJS({
13543
13644
  var composer = require_composer();
13544
13645
  var Document = require_Document();
13545
13646
  var errors = require_errors();
13546
- var log8 = require_log();
13647
+ var log12 = require_log();
13547
13648
  var identity = require_identity();
13548
13649
  var lineCounter = require_line_counter();
13549
13650
  var parser = require_parser();
@@ -13595,7 +13696,7 @@ var require_public_api = __commonJS({
13595
13696
  const doc = parseDocument(src, options);
13596
13697
  if (!doc)
13597
13698
  return null;
13598
- doc.warnings.forEach((warning) => log8.warn(doc.options.logLevel, warning));
13699
+ doc.warnings.forEach((warning) => log12.warn(doc.options.logLevel, warning));
13599
13700
  if (doc.errors.length > 0) {
13600
13701
  if (doc.options.logLevel !== "silent")
13601
13702
  throw doc.errors[0];
@@ -14354,8 +14455,8 @@ function uint8ArrayToBase64(bytes) {
14354
14455
  }
14355
14456
  return btoa(binaryString);
14356
14457
  }
14357
- function base64urlToUint8Array(base64url3) {
14358
- const base643 = base64url3.replace(/-/g, "+").replace(/_/g, "/");
14458
+ function base64urlToUint8Array(base64url4) {
14459
+ const base643 = base64url4.replace(/-/g, "+").replace(/_/g, "/");
14359
14460
  const padding = "=".repeat((4 - base643.length % 4) % 4);
14360
14461
  return base64ToUint8Array(base643 + padding);
14361
14462
  }
@@ -37111,7 +37212,7 @@ var require_logging = __commonJS({
37111
37212
  _logVerbosity = verbosity;
37112
37213
  };
37113
37214
  exports2.setLoggerVerbosity = setLoggerVerbosity;
37114
- var log8 = (severity, ...args) => {
37215
+ var log12 = (severity, ...args) => {
37115
37216
  let logFunction;
37116
37217
  if (severity >= _logVerbosity) {
37117
37218
  switch (severity) {
@@ -37133,7 +37234,7 @@ var require_logging = __commonJS({
37133
37234
  }
37134
37235
  }
37135
37236
  };
37136
- exports2.log = log8;
37237
+ exports2.log = log12;
37137
37238
  var tracersString = (_d = (_c = process.env.GRPC_NODE_TRACE) !== null && _c !== void 0 ? _c : process.env.GRPC_TRACE) !== null && _d !== void 0 ? _d : "";
37138
37239
  var enabledTracers = /* @__PURE__ */ new Set();
37139
37240
  var disabledTracers = /* @__PURE__ */ new Set();
@@ -38519,8 +38620,8 @@ var require_service_config = __commonJS({
38519
38620
  }
38520
38621
  if (Array.isArray(validatedConfig.clientHostname)) {
38521
38622
  let hostnameMatched = false;
38522
- for (const hostname4 of validatedConfig.clientHostname) {
38523
- if (hostname4 === os2.hostname()) {
38623
+ for (const hostname5 of validatedConfig.clientHostname) {
38624
+ if (hostname5 === os2.hostname()) {
38524
38625
  hostnameMatched = true;
38525
38626
  }
38526
38627
  }
@@ -51089,8 +51190,8 @@ var require_single_subchannel_channel = __commonJS({
51089
51190
  if (splitPath2.length >= 2) {
51090
51191
  serviceName = splitPath2[1];
51091
51192
  }
51092
- const hostname4 = (_b = (_a3 = (0, uri_parser_1.splitHostPort)(this.options.host)) === null || _a3 === void 0 ? void 0 : _a3.host) !== null && _b !== void 0 ? _b : "localhost";
51093
- this.serviceUrl = `https://${hostname4}/${serviceName}`;
51193
+ const hostname5 = (_b = (_a3 = (0, uri_parser_1.splitHostPort)(this.options.host)) === null || _a3 === void 0 ? void 0 : _a3.host) !== null && _b !== void 0 ? _b : "localhost";
51194
+ this.serviceUrl = `https://${hostname5}/${serviceName}`;
51094
51195
  const timeout = (0, deadline_1.getRelativeTimeout)(options.deadline);
51095
51196
  if (timeout !== Infinity) {
51096
51197
  if (timeout <= 0) {
@@ -51737,8 +51838,8 @@ var require_resolver_dns = __commonJS({
51737
51838
  }
51738
51839
  trace("Looking up DNS hostname " + this.dnsHostname);
51739
51840
  this.latestLookupResult = null;
51740
- const hostname4 = this.dnsHostname;
51741
- this.pendingLookupPromise = this.lookup(hostname4);
51841
+ const hostname5 = this.dnsHostname;
51842
+ this.pendingLookupPromise = this.lookup(hostname5);
51742
51843
  this.pendingLookupPromise.then((addressList) => {
51743
51844
  if (this.pendingLookupPromise === null) {
51744
51845
  return;
@@ -51761,7 +51862,7 @@ var require_resolver_dns = __commonJS({
51761
51862
  this.listener((0, call_interface_1.statusOrFromError)(this.defaultResolutionError), {}, this.latestServiceConfigResult, "");
51762
51863
  });
51763
51864
  if (this.isServiceConfigEnabled && this.pendingTxtPromise === null) {
51764
- this.pendingTxtPromise = this.resolveTxt(hostname4);
51865
+ this.pendingTxtPromise = this.resolveTxt(hostname5);
51765
51866
  this.pendingTxtPromise.then((txtRecord) => {
51766
51867
  if (this.pendingTxtPromise === null) {
51767
51868
  return;
@@ -51803,12 +51904,12 @@ var require_resolver_dns = __commonJS({
51803
51904
  this.continueResolving = true;
51804
51905
  }
51805
51906
  }
51806
- async lookup(hostname4) {
51907
+ async lookup(hostname5) {
51807
51908
  if (environment_1.GRPC_NODE_USE_ALTERNATIVE_RESOLVER) {
51808
51909
  trace("Using alternative DNS resolver.");
51809
51910
  const records = await Promise.allSettled([
51810
- this.alternativeResolver.resolve4(hostname4),
51811
- this.alternativeResolver.resolve6(hostname4)
51911
+ this.alternativeResolver.resolve4(hostname5),
51912
+ this.alternativeResolver.resolve6(hostname5)
51812
51913
  ]);
51813
51914
  if (records.every((result) => result.status === "rejected")) {
51814
51915
  throw new Error(records[0].reason);
@@ -51820,15 +51921,15 @@ var require_resolver_dns = __commonJS({
51820
51921
  port: +this.port
51821
51922
  }));
51822
51923
  }
51823
- const addressList = await dns_1.promises.lookup(hostname4, { all: true });
51924
+ const addressList = await dns_1.promises.lookup(hostname5, { all: true });
51824
51925
  return addressList.map((addr) => ({ host: addr.address, port: +this.port }));
51825
51926
  }
51826
- async resolveTxt(hostname4) {
51927
+ async resolveTxt(hostname5) {
51827
51928
  if (environment_1.GRPC_NODE_USE_ALTERNATIVE_RESOLVER) {
51828
51929
  trace("Using alternative DNS resolver.");
51829
- return this.alternativeResolver.resolveTxt(hostname4);
51930
+ return this.alternativeResolver.resolveTxt(hostname5);
51830
51931
  }
51831
- return dns_1.promises.resolveTxt(hostname4);
51932
+ return dns_1.promises.resolveTxt(hostname5);
51832
51933
  }
51833
51934
  startNextResolutionTimer() {
51834
51935
  var _a3, _b;
@@ -51956,13 +52057,13 @@ var require_http_proxy = __commonJS({
51956
52057
  userCred = proxyUrl.username;
51957
52058
  }
51958
52059
  }
51959
- const hostname4 = proxyUrl.hostname;
52060
+ const hostname5 = proxyUrl.hostname;
51960
52061
  let port = proxyUrl.port;
51961
52062
  if (port === "") {
51962
52063
  port = "80";
51963
52064
  }
51964
52065
  const result = {
51965
- address: `${hostname4}:${port}`
52066
+ address: `${hostname5}:${port}`
51966
52067
  };
51967
52068
  if (userCred) {
51968
52069
  result.creds = userCred;
@@ -53275,8 +53376,8 @@ var require_load_balancing_call = __commonJS({
53275
53376
  if (splitPath2.length >= 2) {
53276
53377
  serviceName = splitPath2[1];
53277
53378
  }
53278
- const hostname4 = (_b = (_a3 = (0, uri_parser_1.splitHostPort)(this.host)) === null || _a3 === void 0 ? void 0 : _a3.host) !== null && _b !== void 0 ? _b : "localhost";
53279
- this.serviceUrl = `https://${hostname4}/${serviceName}`;
53379
+ const hostname5 = (_b = (_a3 = (0, uri_parser_1.splitHostPort)(this.host)) === null || _a3 === void 0 ? void 0 : _a3.host) !== null && _b !== void 0 ? _b : "localhost";
53380
+ this.serviceUrl = `https://${hostname5}/${serviceName}`;
53280
53381
  this.startTime = /* @__PURE__ */ new Date();
53281
53382
  }
53282
53383
  getDeadlineInfo() {
@@ -91085,9 +91186,9 @@ var require_parse_host_uri = __commonJS({
91085
91186
  var ipv4Hostname = "(?:\\d{1,3}(?:\\.\\d{1,3}){3})";
91086
91187
  var ipv6Hostname = "(?:\\[(?<ipv6>[0-9a-fA-F.:]+)\\])";
91087
91188
  var dnsHostname = "(?:[^:/]+)";
91088
- var hostname4 = `(?:${ipv4Hostname}|${ipv6Hostname}|${dnsHostname})`;
91189
+ var hostname5 = `(?:${ipv4Hostname}|${ipv6Hostname}|${dnsHostname})`;
91089
91190
  var port = "(?::(?<port>\\d+))";
91090
- var protoHostPortRegex = new RegExp(`^${scheme}??(?<hostname>${hostname4})${port}?$`);
91191
+ var protoHostPortRegex = new RegExp(`^${scheme}??(?<hostname>${hostname5})${port}?$`);
91091
91192
  function splitProtoHostPort(uri) {
91092
91193
  const match2 = protoHostPortRegex.exec(uri);
91093
91194
  if (!match2?.groups)
@@ -91099,9 +91200,9 @@ var require_parse_host_uri = __commonJS({
91099
91200
  };
91100
91201
  }
91101
91202
  function joinProtoHostPort(components) {
91102
- const { scheme: scheme2, hostname: hostname5, port: port2 } = components;
91203
+ const { scheme: scheme2, hostname: hostname6, port: port2 } = components;
91103
91204
  const schemeText = scheme2 ? `${scheme2}:` : "";
91104
- const hostnameText = hostname5.includes(":") ? `[${hostname5}]` : hostname5;
91205
+ const hostnameText = hostname6.includes(":") ? `[${hostname6}]` : hostname6;
91105
91206
  const portText = port2 !== void 0 ? `:${port2}` : "";
91106
91207
  return `${schemeText}${hostnameText}${portText}`;
91107
91208
  }
@@ -98687,7 +98788,7 @@ var require_scan = __commonJS({
98687
98788
  var require_parse4 = __commonJS({
98688
98789
  "node_modules/picomatch/lib/parse.js"(exports2, module2) {
98689
98790
  "use strict";
98690
- var constants = require_constants5();
98791
+ var constants2 = require_constants5();
98691
98792
  var utils = require_utils2();
98692
98793
  var {
98693
98794
  MAX_LENGTH,
@@ -98695,7 +98796,7 @@ var require_parse4 = __commonJS({
98695
98796
  REGEX_NON_SPECIAL_CHARS,
98696
98797
  REGEX_SPECIAL_CHARS_BACKREF,
98697
98798
  REPLACEMENTS
98698
- } = constants;
98799
+ } = constants2;
98699
98800
  var expandRange = (args, options) => {
98700
98801
  if (typeof options.expandRange === "function") {
98701
98802
  return options.expandRange(...args, options);
@@ -98901,7 +99002,7 @@ var require_parse4 = __commonJS({
98901
99002
  if (options.maxExtglobRecursion === false) {
98902
99003
  return { risky: false };
98903
99004
  }
98904
- const max = typeof options.maxExtglobRecursion === "number" ? options.maxExtglobRecursion : constants.DEFAULT_MAX_EXTGLOB_RECURSION;
99005
+ const max = typeof options.maxExtglobRecursion === "number" ? options.maxExtglobRecursion : constants2.DEFAULT_MAX_EXTGLOB_RECURSION;
98905
99006
  const branches = splitTopLevel(body).map((branch) => branch.trim());
98906
99007
  if (branches.length > 1) {
98907
99008
  if (branches.some((branch) => branch === "") || branches.some((branch) => /^[*?]+$/.test(branch)) || hasRepeatedCharPrefixOverlap(branches)) {
@@ -98934,8 +99035,8 @@ var require_parse4 = __commonJS({
98934
99035
  const tokens = [bos];
98935
99036
  const capture = opts.capture ? "" : "?:";
98936
99037
  const win32 = utils.isWindows(options);
98937
- const PLATFORM_CHARS = constants.globChars(win32);
98938
- const EXTGLOB_CHARS = constants.extglobChars(PLATFORM_CHARS);
99038
+ const PLATFORM_CHARS = constants2.globChars(win32);
99039
+ const EXTGLOB_CHARS = constants2.extglobChars(PLATFORM_CHARS);
98939
99040
  const {
98940
99041
  DOT_LITERAL,
98941
99042
  PLUS_LITERAL,
@@ -99634,7 +99735,7 @@ var require_parse4 = __commonJS({
99634
99735
  NO_DOTS_SLASH,
99635
99736
  STAR,
99636
99737
  START_ANCHOR
99637
- } = constants.globChars(win32);
99738
+ } = constants2.globChars(win32);
99638
99739
  const nodot = opts.dot ? NO_DOTS : NO_DOT;
99639
99740
  const slashDot = opts.dot ? NO_DOTS_SLASH : NO_DOT;
99640
99741
  const capture = opts.capture ? "" : "?:";
@@ -99693,7 +99794,7 @@ var require_picomatch = __commonJS({
99693
99794
  var scan = require_scan();
99694
99795
  var parse3 = require_parse4();
99695
99796
  var utils = require_utils2();
99696
- var constants = require_constants5();
99797
+ var constants2 = require_constants5();
99697
99798
  var isObject2 = (val) => val && typeof val === "object" && !Array.isArray(val);
99698
99799
  var picomatch = (glob, options, returnState = false) => {
99699
99800
  if (Array.isArray(glob)) {
@@ -99821,7 +99922,7 @@ var require_picomatch = __commonJS({
99821
99922
  return /$^/;
99822
99923
  }
99823
99924
  };
99824
- picomatch.constants = constants;
99925
+ picomatch.constants = constants2;
99825
99926
  module2.exports = picomatch;
99826
99927
  }
99827
99928
  });
@@ -104039,11 +104140,11 @@ var require_pg_connection_string = __commonJS({
104039
104140
  config2.client_encoding = result.searchParams.get("encoding");
104040
104141
  return config2;
104041
104142
  }
104042
- const hostname4 = dummyHost ? "" : result.hostname;
104143
+ const hostname5 = dummyHost ? "" : result.hostname;
104043
104144
  if (!config2.host) {
104044
- config2.host = decodeURIComponent(hostname4);
104045
- } else if (hostname4 && /^%2f/i.test(hostname4)) {
104046
- result.pathname = hostname4 + result.pathname;
104145
+ config2.host = decodeURIComponent(hostname5);
104146
+ } else if (hostname5 && /^%2f/i.test(hostname5)) {
104147
+ result.pathname = hostname5 + result.pathname;
104047
104148
  }
104048
104149
  if (!config2.port) {
104049
104150
  config2.port = result.port;
@@ -119963,7 +120064,7 @@ var require_crc = __commonJS({
119963
120064
  var require_parser3 = __commonJS({
119964
120065
  "node_modules/pngjs/lib/parser.js"(exports2, module2) {
119965
120066
  "use strict";
119966
- var constants = require_constants7();
120067
+ var constants2 = require_constants7();
119967
120068
  var CrcCalculator = require_crc();
119968
120069
  var Parser = module2.exports = function(options, dependencies) {
119969
120070
  this._options = options;
@@ -119974,12 +120075,12 @@ var require_parser3 = __commonJS({
119974
120075
  this._palette = [];
119975
120076
  this._colorType = 0;
119976
120077
  this._chunks = {};
119977
- this._chunks[constants.TYPE_IHDR] = this._handleIHDR.bind(this);
119978
- this._chunks[constants.TYPE_IEND] = this._handleIEND.bind(this);
119979
- this._chunks[constants.TYPE_IDAT] = this._handleIDAT.bind(this);
119980
- this._chunks[constants.TYPE_PLTE] = this._handlePLTE.bind(this);
119981
- this._chunks[constants.TYPE_tRNS] = this._handleTRNS.bind(this);
119982
- this._chunks[constants.TYPE_gAMA] = this._handleGAMA.bind(this);
120078
+ this._chunks[constants2.TYPE_IHDR] = this._handleIHDR.bind(this);
120079
+ this._chunks[constants2.TYPE_IEND] = this._handleIEND.bind(this);
120080
+ this._chunks[constants2.TYPE_IDAT] = this._handleIDAT.bind(this);
120081
+ this._chunks[constants2.TYPE_PLTE] = this._handlePLTE.bind(this);
120082
+ this._chunks[constants2.TYPE_tRNS] = this._handleTRNS.bind(this);
120083
+ this._chunks[constants2.TYPE_gAMA] = this._handleGAMA.bind(this);
119983
120084
  this.read = dependencies.read;
119984
120085
  this.error = dependencies.error;
119985
120086
  this.metadata = dependencies.metadata;
@@ -119994,10 +120095,10 @@ var require_parser3 = __commonJS({
119994
120095
  };
119995
120096
  };
119996
120097
  Parser.prototype.start = function() {
119997
- this.read(constants.PNG_SIGNATURE.length, this._parseSignature.bind(this));
120098
+ this.read(constants2.PNG_SIGNATURE.length, this._parseSignature.bind(this));
119998
120099
  };
119999
120100
  Parser.prototype._parseSignature = function(data) {
120000
- let signature = constants.PNG_SIGNATURE;
120101
+ let signature = constants2.PNG_SIGNATURE;
120001
120102
  for (let i = 0; i < signature.length; i++) {
120002
120103
  if (data[i] !== signature[i]) {
120003
120104
  this.error(new Error("Invalid file signature"));
@@ -120014,7 +120115,7 @@ var require_parser3 = __commonJS({
120014
120115
  name += String.fromCharCode(data[i]);
120015
120116
  }
120016
120117
  let ancillary = Boolean(data[4] & 32);
120017
- if (!this._hasIHDR && type !== constants.TYPE_IHDR) {
120118
+ if (!this._hasIHDR && type !== constants2.TYPE_IHDR) {
120018
120119
  this.error(new Error("Expected IHDR on beggining"));
120019
120120
  return;
120020
120121
  }
@@ -120062,7 +120163,7 @@ var require_parser3 = __commonJS({
120062
120163
  this.error(new Error("Unsupported bit depth " + depth));
120063
120164
  return;
120064
120165
  }
120065
- if (!(colorType in constants.COLORTYPE_TO_BPP_MAP)) {
120166
+ if (!(colorType in constants2.COLORTYPE_TO_BPP_MAP)) {
120066
120167
  this.error(new Error("Unsupported color type"));
120067
120168
  return;
120068
120169
  }
@@ -120079,16 +120180,16 @@ var require_parser3 = __commonJS({
120079
120180
  return;
120080
120181
  }
120081
120182
  this._colorType = colorType;
120082
- let bpp = constants.COLORTYPE_TO_BPP_MAP[this._colorType];
120183
+ let bpp = constants2.COLORTYPE_TO_BPP_MAP[this._colorType];
120083
120184
  this._hasIHDR = true;
120084
120185
  this.metadata({
120085
120186
  width,
120086
120187
  height,
120087
120188
  depth,
120088
120189
  interlace: Boolean(interlace),
120089
- palette: Boolean(colorType & constants.COLORTYPE_PALETTE),
120090
- color: Boolean(colorType & constants.COLORTYPE_COLOR),
120091
- alpha: Boolean(colorType & constants.COLORTYPE_ALPHA),
120190
+ palette: Boolean(colorType & constants2.COLORTYPE_PALETTE),
120191
+ color: Boolean(colorType & constants2.COLORTYPE_COLOR),
120192
+ alpha: Boolean(colorType & constants2.COLORTYPE_ALPHA),
120092
120193
  bpp,
120093
120194
  colorType
120094
120195
  });
@@ -120112,7 +120213,7 @@ var require_parser3 = __commonJS({
120112
120213
  };
120113
120214
  Parser.prototype._parseTRNS = function(data) {
120114
120215
  this._crc.write(data);
120115
- if (this._colorType === constants.COLORTYPE_PALETTE_COLOR) {
120216
+ if (this._colorType === constants2.COLORTYPE_PALETTE_COLOR) {
120116
120217
  if (this._palette.length === 0) {
120117
120218
  this.error(new Error("Transparency chunk must be after palette"));
120118
120219
  return;
@@ -120126,10 +120227,10 @@ var require_parser3 = __commonJS({
120126
120227
  }
120127
120228
  this.palette(this._palette);
120128
120229
  }
120129
- if (this._colorType === constants.COLORTYPE_GRAYSCALE) {
120230
+ if (this._colorType === constants2.COLORTYPE_GRAYSCALE) {
120130
120231
  this.transColor([data.readUInt16BE(0)]);
120131
120232
  }
120132
- if (this._colorType === constants.COLORTYPE_COLOR) {
120233
+ if (this._colorType === constants2.COLORTYPE_COLOR) {
120133
120234
  this.transColor([
120134
120235
  data.readUInt16BE(0),
120135
120236
  data.readUInt16BE(2),
@@ -120143,7 +120244,7 @@ var require_parser3 = __commonJS({
120143
120244
  };
120144
120245
  Parser.prototype._parseGAMA = function(data) {
120145
120246
  this._crc.write(data);
120146
- this.gamma(data.readUInt32BE(0) / constants.GAMMA_DIVISION);
120247
+ this.gamma(data.readUInt32BE(0) / constants2.GAMMA_DIVISION);
120147
120248
  this._handleChunkEnd();
120148
120249
  };
120149
120250
  Parser.prototype._handleIDAT = function(length) {
@@ -120155,7 +120256,7 @@ var require_parser3 = __commonJS({
120155
120256
  };
120156
120257
  Parser.prototype._parseIDAT = function(length, data) {
120157
120258
  this._crc.write(data);
120158
- if (this._colorType === constants.COLORTYPE_PALETTE_COLOR && this._palette.length === 0) {
120259
+ if (this._colorType === constants2.COLORTYPE_PALETTE_COLOR && this._palette.length === 0) {
120159
120260
  throw new Error("Expected palette not found");
120160
120261
  }
120161
120262
  this.inflateData(data);
@@ -120643,9 +120744,9 @@ var require_parser_async = __commonJS({
120643
120744
  var require_bitpacker = __commonJS({
120644
120745
  "node_modules/pngjs/lib/bitpacker.js"(exports2, module2) {
120645
120746
  "use strict";
120646
- var constants = require_constants7();
120747
+ var constants2 = require_constants7();
120647
120748
  module2.exports = function(dataIn, width, height, options) {
120648
- let outHasAlpha = [constants.COLORTYPE_COLOR_ALPHA, constants.COLORTYPE_ALPHA].indexOf(
120749
+ let outHasAlpha = [constants2.COLORTYPE_COLOR_ALPHA, constants2.COLORTYPE_ALPHA].indexOf(
120649
120750
  options.colorType
120650
120751
  ) !== -1;
120651
120752
  if (options.colorType === options.inputColorType) {
@@ -120665,11 +120766,11 @@ var require_bitpacker = __commonJS({
120665
120766
  }
120666
120767
  let data = options.bitDepth !== 16 ? dataIn : new Uint16Array(dataIn.buffer);
120667
120768
  let maxValue = 255;
120668
- let inBpp = constants.COLORTYPE_TO_BPP_MAP[options.inputColorType];
120769
+ let inBpp = constants2.COLORTYPE_TO_BPP_MAP[options.inputColorType];
120669
120770
  if (inBpp === 4 && !options.inputHasAlpha) {
120670
120771
  inBpp = 3;
120671
120772
  }
120672
- let outBpp = constants.COLORTYPE_TO_BPP_MAP[options.colorType];
120773
+ let outBpp = constants2.COLORTYPE_TO_BPP_MAP[options.colorType];
120673
120774
  if (options.bitDepth === 16) {
120674
120775
  maxValue = 65535;
120675
120776
  outBpp *= 2;
@@ -120693,24 +120794,24 @@ var require_bitpacker = __commonJS({
120693
120794
  let blue;
120694
120795
  let alpha = maxValue;
120695
120796
  switch (options.inputColorType) {
120696
- case constants.COLORTYPE_COLOR_ALPHA:
120797
+ case constants2.COLORTYPE_COLOR_ALPHA:
120697
120798
  alpha = data[inIndex + 3];
120698
120799
  red = data[inIndex];
120699
120800
  green = data[inIndex + 1];
120700
120801
  blue = data[inIndex + 2];
120701
120802
  break;
120702
- case constants.COLORTYPE_COLOR:
120803
+ case constants2.COLORTYPE_COLOR:
120703
120804
  red = data[inIndex];
120704
120805
  green = data[inIndex + 1];
120705
120806
  blue = data[inIndex + 2];
120706
120807
  break;
120707
- case constants.COLORTYPE_ALPHA:
120808
+ case constants2.COLORTYPE_ALPHA:
120708
120809
  alpha = data[inIndex + 1];
120709
120810
  red = data[inIndex];
120710
120811
  green = red;
120711
120812
  blue = red;
120712
120813
  break;
120713
- case constants.COLORTYPE_GRAYSCALE:
120814
+ case constants2.COLORTYPE_GRAYSCALE:
120714
120815
  red = data[inIndex];
120715
120816
  green = red;
120716
120817
  blue = red;
@@ -120743,8 +120844,8 @@ var require_bitpacker = __commonJS({
120743
120844
  for (let x = 0; x < width; x++) {
120744
120845
  let rgba = getRGBA(data, inIndex);
120745
120846
  switch (options.colorType) {
120746
- case constants.COLORTYPE_COLOR_ALPHA:
120747
- case constants.COLORTYPE_COLOR:
120847
+ case constants2.COLORTYPE_COLOR_ALPHA:
120848
+ case constants2.COLORTYPE_COLOR:
120748
120849
  if (options.bitDepth === 8) {
120749
120850
  outData[outIndex] = rgba.red;
120750
120851
  outData[outIndex + 1] = rgba.green;
@@ -120761,8 +120862,8 @@ var require_bitpacker = __commonJS({
120761
120862
  }
120762
120863
  }
120763
120864
  break;
120764
- case constants.COLORTYPE_ALPHA:
120765
- case constants.COLORTYPE_GRAYSCALE: {
120865
+ case constants2.COLORTYPE_ALPHA:
120866
+ case constants2.COLORTYPE_GRAYSCALE: {
120766
120867
  let grayscale = (rgba.red + rgba.green + rgba.blue) / 3;
120767
120868
  if (options.bitDepth === 8) {
120768
120869
  outData[outIndex] = grayscale;
@@ -120935,7 +121036,7 @@ var require_filter_pack = __commonJS({
120935
121036
  var require_packer = __commonJS({
120936
121037
  "node_modules/pngjs/lib/packer.js"(exports2, module2) {
120937
121038
  "use strict";
120938
- var constants = require_constants7();
121039
+ var constants2 = require_constants7();
120939
121040
  var CrcStream = require_crc();
120940
121041
  var bitPacker = require_bitpacker();
120941
121042
  var filter = require_filter_pack();
@@ -120948,23 +121049,23 @@ var require_packer = __commonJS({
120948
121049
  options.inputHasAlpha = options.inputHasAlpha != null ? options.inputHasAlpha : true;
120949
121050
  options.deflateFactory = options.deflateFactory || zlib.createDeflate;
120950
121051
  options.bitDepth = options.bitDepth || 8;
120951
- options.colorType = typeof options.colorType === "number" ? options.colorType : constants.COLORTYPE_COLOR_ALPHA;
120952
- options.inputColorType = typeof options.inputColorType === "number" ? options.inputColorType : constants.COLORTYPE_COLOR_ALPHA;
121052
+ options.colorType = typeof options.colorType === "number" ? options.colorType : constants2.COLORTYPE_COLOR_ALPHA;
121053
+ options.inputColorType = typeof options.inputColorType === "number" ? options.inputColorType : constants2.COLORTYPE_COLOR_ALPHA;
120953
121054
  if ([
120954
- constants.COLORTYPE_GRAYSCALE,
120955
- constants.COLORTYPE_COLOR,
120956
- constants.COLORTYPE_COLOR_ALPHA,
120957
- constants.COLORTYPE_ALPHA
121055
+ constants2.COLORTYPE_GRAYSCALE,
121056
+ constants2.COLORTYPE_COLOR,
121057
+ constants2.COLORTYPE_COLOR_ALPHA,
121058
+ constants2.COLORTYPE_ALPHA
120958
121059
  ].indexOf(options.colorType) === -1) {
120959
121060
  throw new Error(
120960
121061
  "option color type:" + options.colorType + " is not supported at present"
120961
121062
  );
120962
121063
  }
120963
121064
  if ([
120964
- constants.COLORTYPE_GRAYSCALE,
120965
- constants.COLORTYPE_COLOR,
120966
- constants.COLORTYPE_COLOR_ALPHA,
120967
- constants.COLORTYPE_ALPHA
121065
+ constants2.COLORTYPE_GRAYSCALE,
121066
+ constants2.COLORTYPE_COLOR,
121067
+ constants2.COLORTYPE_COLOR_ALPHA,
121068
+ constants2.COLORTYPE_ALPHA
120968
121069
  ].indexOf(options.inputColorType) === -1) {
120969
121070
  throw new Error(
120970
121071
  "option input color type:" + options.inputColorType + " is not supported at present"
@@ -120988,7 +121089,7 @@ var require_packer = __commonJS({
120988
121089
  };
120989
121090
  Packer.prototype.filterData = function(data, width, height) {
120990
121091
  let packedData = bitPacker(data, width, height, this._options);
120991
- let bpp = constants.COLORTYPE_TO_BPP_MAP[this._options.colorType];
121092
+ let bpp = constants2.COLORTYPE_TO_BPP_MAP[this._options.colorType];
120992
121093
  let filteredData = filter(packedData, width, height, this._options, bpp);
120993
121094
  return filteredData;
120994
121095
  };
@@ -121008,8 +121109,8 @@ var require_packer = __commonJS({
121008
121109
  };
121009
121110
  Packer.prototype.packGAMA = function(gamma) {
121010
121111
  let buf = Buffer.alloc(4);
121011
- buf.writeUInt32BE(Math.floor(gamma * constants.GAMMA_DIVISION), 0);
121012
- return this._packChunk(constants.TYPE_gAMA, buf);
121112
+ buf.writeUInt32BE(Math.floor(gamma * constants2.GAMMA_DIVISION), 0);
121113
+ return this._packChunk(constants2.TYPE_gAMA, buf);
121013
121114
  };
121014
121115
  Packer.prototype.packIHDR = function(width, height) {
121015
121116
  let buf = Buffer.alloc(13);
@@ -121020,13 +121121,13 @@ var require_packer = __commonJS({
121020
121121
  buf[10] = 0;
121021
121122
  buf[11] = 0;
121022
121123
  buf[12] = 0;
121023
- return this._packChunk(constants.TYPE_IHDR, buf);
121124
+ return this._packChunk(constants2.TYPE_IHDR, buf);
121024
121125
  };
121025
121126
  Packer.prototype.packIDAT = function(data) {
121026
- return this._packChunk(constants.TYPE_IDAT, data);
121127
+ return this._packChunk(constants2.TYPE_IDAT, data);
121027
121128
  };
121028
121129
  Packer.prototype.packIEND = function() {
121029
- return this._packChunk(constants.TYPE_IEND, null);
121130
+ return this._packChunk(constants2.TYPE_IEND, null);
121030
121131
  };
121031
121132
  }
121032
121133
  });
@@ -121037,7 +121138,7 @@ var require_packer_async = __commonJS({
121037
121138
  "use strict";
121038
121139
  var util = require("util");
121039
121140
  var Stream = require("stream");
121040
- var constants = require_constants7();
121141
+ var constants2 = require_constants7();
121041
121142
  var Packer = require_packer();
121042
121143
  var PackerAsync = module2.exports = function(opt) {
121043
121144
  Stream.call(this);
@@ -121048,7 +121149,7 @@ var require_packer_async = __commonJS({
121048
121149
  };
121049
121150
  util.inherits(PackerAsync, Stream);
121050
121151
  PackerAsync.prototype.pack = function(data, width, height, gamma) {
121051
- this.emit("data", Buffer.from(constants.PNG_SIGNATURE));
121152
+ this.emit("data", Buffer.from(constants2.PNG_SIGNATURE));
121052
121153
  this.emit("data", this._packer.packIHDR(width, height));
121053
121154
  if (gamma) {
121054
121155
  this.emit("data", this._packer.packGAMA(gamma));
@@ -121376,7 +121477,7 @@ var require_packer_sync = __commonJS({
121376
121477
  if (!zlib.deflateSync) {
121377
121478
  hasSyncZlib = false;
121378
121479
  }
121379
- var constants = require_constants7();
121480
+ var constants2 = require_constants7();
121380
121481
  var Packer = require_packer();
121381
121482
  module2.exports = function(metaData, opt) {
121382
121483
  if (!hasSyncZlib) {
@@ -121387,7 +121488,7 @@ var require_packer_sync = __commonJS({
121387
121488
  let options = opt || {};
121388
121489
  let packer = new Packer(options);
121389
121490
  let chunks = [];
121390
- chunks.push(Buffer.from(constants.PNG_SIGNATURE));
121491
+ chunks.push(Buffer.from(constants2.PNG_SIGNATURE));
121391
121492
  chunks.push(packer.packIHDR(metaData.width, metaData.height));
121392
121493
  if (metaData.gamma) {
121393
121494
  chunks.push(packer.packGAMA(metaData.gamma));
@@ -122490,10 +122591,10 @@ var require_truncate = __commonJS({
122490
122591
  "node_modules/semver/functions/truncate.js"(exports2, module2) {
122491
122592
  "use strict";
122492
122593
  var parse3 = require_parse5();
122493
- var constants = require_constants8();
122594
+ var constants2 = require_constants8();
122494
122595
  var SemVer = require_semver();
122495
122596
  var truncate = (version2, truncation, options) => {
122496
- if (!constants.RELEASE_TYPES.includes(truncation)) {
122597
+ if (!constants2.RELEASE_TYPES.includes(truncation)) {
122497
122598
  return null;
122498
122599
  }
122499
122600
  const clonedVersion = cloneInputVersion(version2, options);
@@ -123542,7 +123643,7 @@ var require_semver2 = __commonJS({
123542
123643
  "node_modules/semver/index.js"(exports2, module2) {
123543
123644
  "use strict";
123544
123645
  var internalRe = require_re();
123545
- var constants = require_constants8();
123646
+ var constants2 = require_constants8();
123546
123647
  var SemVer = require_semver();
123547
123648
  var identifiers = require_identifiers();
123548
123649
  var parse3 = require_parse5();
@@ -123626,8 +123727,8 @@ var require_semver2 = __commonJS({
123626
123727
  re: internalRe.re,
123627
123728
  src: internalRe.src,
123628
123729
  tokens: internalRe.t,
123629
- SEMVER_SPEC_VERSION: constants.SEMVER_SPEC_VERSION,
123630
- RELEASE_TYPES: constants.RELEASE_TYPES,
123730
+ SEMVER_SPEC_VERSION: constants2.SEMVER_SPEC_VERSION,
123731
+ RELEASE_TYPES: constants2.RELEASE_TYPES,
123631
123732
  compareIdentifiers: identifiers.compareIdentifiers,
123632
123733
  rcompareIdentifiers: identifiers.rcompareIdentifiers
123633
123734
  };
@@ -123850,7 +123951,7 @@ function readMarker() {
123850
123951
  const parsed = JSON.parse(raw2);
123851
123952
  return MarkerSchema.parse(parsed);
123852
123953
  } catch (err) {
123853
- log3.warn(`marker at ${markerPath()} is malformed; treating as absent`, {
123954
+ log7.warn(`marker at ${markerPath()} is malformed; treating as absent`, {
123854
123955
  error: err instanceof Error ? err.message : String(err)
123855
123956
  });
123856
123957
  return null;
@@ -123867,7 +123968,7 @@ function writeMarker(marker) {
123867
123968
  function clearMarker() {
123868
123969
  if ((0, import_node_fs21.existsSync)(markerPath())) (0, import_node_fs21.rmSync)(markerPath());
123869
123970
  }
123870
- var import_node_fs21, import_node_path25, log3;
123971
+ var import_node_fs21, import_node_path25, log7;
123871
123972
  var init_marker = __esm({
123872
123973
  "src/lifecycle/marker.ts"() {
123873
123974
  "use strict";
@@ -123876,7 +123977,7 @@ var init_marker = __esm({
123876
123977
  init_logger();
123877
123978
  init_constants();
123878
123979
  init_marker_schema();
123879
- log3 = getLogger("lifecycle.marker");
123980
+ log7 = getLogger("lifecycle.marker");
123880
123981
  }
123881
123982
  });
123882
123983
 
@@ -124077,7 +124178,7 @@ function readPrefs() {
124077
124178
  try {
124078
124179
  return PrefsSchema.parse(JSON.parse((0, import_node_fs23.readFileSync)(path2, "utf8")));
124079
124180
  } catch (err) {
124080
- log5.warn(`prefs at ${path2} are malformed; treating as empty`, {
124181
+ log9.warn(`prefs at ${path2} are malformed; treating as empty`, {
124081
124182
  error: err instanceof Error ? err.message : String(err)
124082
124183
  });
124083
124184
  return { repos: {} };
@@ -124112,7 +124213,7 @@ function forgetAll() {
124112
124213
  const path2 = prefsPath();
124113
124214
  if ((0, import_node_fs23.existsSync)(path2)) (0, import_node_fs23.rmSync)(path2);
124114
124215
  }
124115
- var import_node_fs23, import_node_path26, log5, PrefsSchema;
124216
+ var import_node_fs23, import_node_path26, log9, PrefsSchema;
124116
124217
  var init_prefs = __esm({
124117
124218
  "src/lifecycle/prefs.ts"() {
124118
124219
  "use strict";
@@ -124121,7 +124222,7 @@ var init_prefs = __esm({
124121
124222
  init_zod();
124122
124223
  init_logger();
124123
124224
  init_constants();
124124
- log5 = getLogger("lifecycle.prefs");
124225
+ log9 = getLogger("lifecycle.prefs");
124125
124226
  PrefsSchema = external_exports.object({
124126
124227
  repos: external_exports.record(
124127
124228
  external_exports.string(),
@@ -124224,7 +124325,7 @@ function takeoverProd(opts) {
124224
124325
  `prod is already suspended by dev pid ${existing.devPid} (since ${existing.suspendedAt}). Stop that dev session first, or run 'tb-streamer prod doctor'.`
124225
124326
  );
124226
124327
  }
124227
- log6.info(`stale marker found (pid ${existing.devPid} is gone) \u2014 clearing and proceeding`);
124328
+ log10.info(`stale marker found (pid ${existing.devPid} is gone) \u2014 clearing and proceeding`);
124228
124329
  }
124229
124330
  getSupervisor().bootoutAgent();
124230
124331
  writeMarker({
@@ -124239,7 +124340,7 @@ function takeoverProd(opts) {
124239
124340
  const m2 = readMarker();
124240
124341
  if (m2 && m2.devPid === process.pid) {
124241
124342
  writeMarker({ ...m2, userHeld: true });
124242
- log6.info(
124343
+ log10.info(
124243
124344
  `prod is suspended (userHeld). Run 'tb-streamer prod start' to restore the supervised instance.`
124244
124345
  );
124245
124346
  }
@@ -124258,14 +124359,14 @@ function takeoverProd(opts) {
124258
124359
  });
124259
124360
  process.on("uncaughtException", (err) => {
124260
124361
  flipUserHeld();
124261
- log6.error(`uncaught: ${err.message}`);
124362
+ log10.error(`uncaught: ${err.message}`);
124262
124363
  process.exit(1);
124263
124364
  });
124264
124365
  process.on("exit", () => {
124265
124366
  flipUserHeld();
124266
124367
  });
124267
124368
  }
124268
- var import_node_net, log6;
124369
+ var import_node_net, log10;
124269
124370
  var init_dev_takeover = __esm({
124270
124371
  "src/lifecycle/dev-takeover.ts"() {
124271
124372
  "use strict";
@@ -124274,7 +124375,7 @@ var init_dev_takeover = __esm({
124274
124375
  init_marker();
124275
124376
  init_platform();
124276
124377
  init_prefs();
124277
- log6 = getLogger("lifecycle.dev-takeover");
124378
+ log10 = getLogger("lifecycle.dev-takeover");
124278
124379
  }
124279
124380
  });
124280
124381
 
@@ -127787,6 +127888,9 @@ function appendDevSessionMarker() {
127787
127888
  }
127788
127889
  }
127789
127890
 
127891
+ // cli/index.ts
127892
+ init_feature_flags();
127893
+
127790
127894
  // src/lan-url.ts
127791
127895
  var import_os2 = require("os");
127792
127896
  function resolveServerUrl({ publicUrl, port }) {
@@ -132113,7 +132217,7 @@ var REF_PREFIX = "ref: refs/heads/";
132113
132217
  var MAX_DEPTH = 6;
132114
132218
  function readGitBranch(projectPath) {
132115
132219
  if (!projectPath) return null;
132116
- const log8 = getLogger2();
132220
+ const log12 = getLogger2();
132117
132221
  let dir = projectPath;
132118
132222
  let depth = 0;
132119
132223
  while (depth < MAX_DEPTH) {
@@ -132122,11 +132226,11 @@ function readGitBranch(projectPath) {
132122
132226
  const content = (0, import_fs2.readFileSync)(headPath, "utf-8").trim();
132123
132227
  if (content.startsWith(REF_PREFIX)) {
132124
132228
  const branch = content.slice(REF_PREFIX.length);
132125
- log8.trace({ projectPath, dir, branch }, "git: branch resolved");
132229
+ log12.trace({ projectPath, dir, branch }, "git: branch resolved");
132126
132230
  return branch;
132127
132231
  }
132128
132232
  if (content.length >= 7) {
132129
- log8.trace({ projectPath, dir }, "git: detached HEAD");
132233
+ log12.trace({ projectPath, dir }, "git: detached HEAD");
132130
132234
  return "(detached)";
132131
132235
  }
132132
132236
  return null;
@@ -132137,7 +132241,7 @@ function readGitBranch(projectPath) {
132137
132241
  dir = parent;
132138
132242
  depth++;
132139
132243
  }
132140
- log8.trace({ projectPath }, "git: no .git found within depth");
132244
+ log12.trace({ projectPath }, "git: no .git found within depth");
132141
132245
  return null;
132142
132246
  }
132143
132247
  function generateMatches(meta3, query) {
@@ -132430,8 +132534,8 @@ function cleanSystemTags(text) {
132430
132534
  return text.replace(SYSTEM_TAG_RE, "").replace(/[^\S\n]+/g, " ").replace(/\n{3,}/g, "\n\n").trim();
132431
132535
  }
132432
132536
  async function parseMeta(filePath, account, tier) {
132433
- const log8 = getLogger2();
132434
- log8.trace({ filePath, account, tier: tier.name }, "parseMeta: start");
132537
+ const log12 = getLogger2();
132538
+ log12.trace({ filePath, account, tier: tier.name }, "parseMeta: start");
132435
132539
  const state = initialReducerState();
132436
132540
  const fileStream = (0, import_fs3.createReadStream)(filePath);
132437
132541
  const rl = (0, import_readline.createInterface)({ input: fileStream, crlfDelay: Infinity });
@@ -132448,22 +132552,22 @@ async function parseMeta(filePath, account, tier) {
132448
132552
  reduceLine(state, entry, tier);
132449
132553
  }
132450
132554
  } catch (err) {
132451
- log8.warn({ filePath, err }, "parseMeta: read failed");
132555
+ log12.warn({ filePath, err }, "parseMeta: read failed");
132452
132556
  return null;
132453
132557
  }
132454
132558
  if (state.badJsonLines > 0) {
132455
- log8.warn(
132559
+ log12.warn(
132456
132560
  { filePath, badJsonLines: state.badJsonLines },
132457
132561
  "parseMeta: skipped malformed JSON lines"
132458
132562
  );
132459
132563
  }
132460
132564
  const meta3 = finalizeMeta(state, filePath, account, tier);
132461
- if (!meta3) log8.trace({ filePath }, "parseMeta: no messages");
132565
+ if (!meta3) log12.trace({ filePath }, "parseMeta: no messages");
132462
132566
  return meta3;
132463
132567
  }
132464
132568
  async function parseConversation(filePath, account) {
132465
- const log8 = getLogger2();
132466
- log8.trace({ filePath, account }, "parseConversation: start");
132569
+ const log12 = getLogger2();
132570
+ log12.trace({ filePath, account }, "parseConversation: start");
132467
132571
  const messages = [];
132468
132572
  let badJsonLines = 0;
132469
132573
  const textParts = [];
@@ -132496,17 +132600,17 @@ async function parseConversation(filePath, account) {
132496
132600
  }
132497
132601
  }
132498
132602
  } catch (err) {
132499
- log8.warn({ filePath, err }, "parseConversation: read failed");
132603
+ log12.warn({ filePath, err }, "parseConversation: read failed");
132500
132604
  return null;
132501
132605
  }
132502
132606
  if (badJsonLines > 0) {
132503
- log8.warn({ filePath, badJsonLines }, "parseConversation: skipped malformed JSON lines");
132607
+ log12.warn({ filePath, badJsonLines }, "parseConversation: skipped malformed JSON lines");
132504
132608
  }
132505
132609
  if (messages.length === 0) {
132506
- log8.trace({ filePath }, "parseConversation: no messages");
132610
+ log12.trace({ filePath }, "parseConversation: no messages");
132507
132611
  return null;
132508
132612
  }
132509
- log8.debug({ filePath, messageCount: messages.length }, "parseConversation: complete");
132613
+ log12.debug({ filePath, messageCount: messages.length }, "parseConversation: complete");
132510
132614
  applyTeamInfo(messages, state);
132511
132615
  return {
132512
132616
  id: filePath,
@@ -132771,15 +132875,15 @@ async function detectDefaultProfile() {
132771
132875
  };
132772
132876
  }
132773
132877
  async function loadProfiles(configPath) {
132774
- const log8 = getLogger2();
132878
+ const log12 = getLogger2();
132775
132879
  try {
132776
132880
  const resolved = resolveConfigDir(configPath);
132777
132881
  const data = await (0, import_promises4.readFile)((0, import_path5.join)(resolved, PROFILES_FILE), "utf-8");
132778
132882
  const profiles = JSON.parse(data);
132779
- log8.debug({ configPath, count: profiles.length }, "profiles: loaded");
132883
+ log12.debug({ configPath, count: profiles.length }, "profiles: loaded");
132780
132884
  return profiles;
132781
132885
  } catch (err) {
132782
- log8.debug({ configPath, err }, "profiles: load failed, using default");
132886
+ log12.debug({ configPath, err }, "profiles: load failed, using default");
132783
132887
  const defaultProfile = await detectDefaultProfile();
132784
132888
  return [defaultProfile];
132785
132889
  }
@@ -132790,7 +132894,7 @@ function canonicalPath(p2) {
132790
132894
  var CodexCliProvider = class {
132791
132895
  name = CODEX_CLI_PROVIDER;
132792
132896
  async discover(roots) {
132793
- const log8 = getLogger2();
132897
+ const log12 = getLogger2();
132794
132898
  const results = [];
132795
132899
  for (const root of roots) {
132796
132900
  let paths;
@@ -132802,7 +132906,7 @@ var CodexCliProvider = class {
132802
132906
  unique: true
132803
132907
  });
132804
132908
  } catch (err) {
132805
- log8.warn({ root, err }, "codex discovery: glob failed");
132909
+ log12.warn({ root, err }, "codex discovery: glob failed");
132806
132910
  continue;
132807
132911
  }
132808
132912
  for (const filePath of paths) {
@@ -132810,7 +132914,7 @@ var CodexCliProvider = class {
132810
132914
  const s3 = await (0, import_promises5.stat)(filePath);
132811
132915
  if (s3.size > 0) results.push({ filePath: canonicalPath(filePath), account: "codex" });
132812
132916
  } catch (err) {
132813
- log8.warn({ filePath, err }, "codex discovery: stat failed");
132917
+ log12.warn({ filePath, err }, "codex discovery: stat failed");
132814
132918
  }
132815
132919
  }
132816
132920
  }
@@ -132954,7 +133058,7 @@ function getShortProjectName3(fullPath) {
132954
133058
  return fullPath.split("/").filter(Boolean).slice(-3).join("/");
132955
133059
  }
132956
133060
  async function parseCodexConversation(filePath, account) {
132957
- const log8 = getLogger2();
133061
+ const log12 = getLogger2();
132958
133062
  const messages = [];
132959
133063
  const textParts = [];
132960
133064
  let sessionId = "";
@@ -132990,7 +133094,7 @@ async function parseCodexConversation(filePath, account) {
132990
133094
  if (role === "user") lastUserText = text;
132991
133095
  }
132992
133096
  } catch (err) {
132993
- log8.warn({ filePath, err }, "parseCodexConversation: read failed");
133097
+ log12.warn({ filePath, err }, "parseCodexConversation: read failed");
132994
133098
  return null;
132995
133099
  }
132996
133100
  if (messages.length === 0) return null;
@@ -133012,7 +133116,7 @@ async function parseCodexConversation(filePath, account) {
133012
133116
  var EXCLUDED_SEGMENTS = ["/memory/", "/tool-results/"];
133013
133117
  var STAT_CONCURRENCY = 32;
133014
133118
  async function discoverJsonlFiles(dirs, onProgress) {
133015
- const log8 = getLogger2();
133119
+ const log12 = getLogger2();
133016
133120
  const results = [];
133017
133121
  for (const { projectsDir, account } of dirs) {
133018
133122
  let filePaths;
@@ -133023,7 +133127,7 @@ async function discoverJsonlFiles(dirs, onProgress) {
133023
133127
  dot: false
133024
133128
  });
133025
133129
  } catch (err) {
133026
- log8.warn({ projectsDir, account, err }, "discovery: glob failed");
133130
+ log12.warn({ projectsDir, account, err }, "discovery: glob failed");
133027
133131
  continue;
133028
133132
  }
133029
133133
  const filtered = filePaths.filter((fp) => !EXCLUDED_SEGMENTS.some((seg) => fp.includes(seg)));
@@ -133038,7 +133142,7 @@ async function discoverJsonlFiles(dirs, onProgress) {
133038
133142
  const s3 = await (0, import_promises6.stat)(filePath);
133039
133143
  return { filePath, size: s3.size };
133040
133144
  } catch (err) {
133041
- log8.warn({ filePath, err }, "discovery: stat failed");
133145
+ log12.warn({ filePath, err }, "discovery: stat failed");
133042
133146
  return { filePath, size: -1 };
133043
133147
  }
133044
133148
  })
@@ -133054,7 +133158,7 @@ async function discoverJsonlFiles(dirs, onProgress) {
133054
133158
  }
133055
133159
  }
133056
133160
  }
133057
- log8.debug(
133161
+ log12.debug(
133058
133162
  {
133059
133163
  projectsDir,
133060
133164
  account,
@@ -133068,7 +133172,7 @@ async function discoverJsonlFiles(dirs, onProgress) {
133068
133172
  );
133069
133173
  onProgress?.(results.length);
133070
133174
  }
133071
- log8.debug({ totalFiles: results.length, dirs: dirs.length }, "discovery: complete");
133175
+ log12.debug({ totalFiles: results.length, dirs: dirs.length }, "discovery: complete");
133072
133176
  return results;
133073
133177
  }
133074
133178
  var ThreadbaseProvider = class {
@@ -133378,7 +133482,7 @@ function classify(filePath, existing) {
133378
133482
  return { change: "reindex", stat: stat42 };
133379
133483
  }
133380
133484
  async function parseMetaWithProvider(provider, filePath, account, tier) {
133381
- const log8 = getLogger2();
133485
+ const log12 = getLogger2();
133382
133486
  const acc = provider.createEmptyAccumulator();
133383
133487
  const rl = (0, import_readline3.createInterface)({
133384
133488
  input: (0, import_fs11.createReadStream)(filePath),
@@ -133396,11 +133500,11 @@ async function parseMetaWithProvider(provider, filePath, account, tier) {
133396
133500
  try {
133397
133501
  provider.reduceEntry(acc, entry, tier);
133398
133502
  } catch (err) {
133399
- log8.warn({ filePath, provider: provider.name, err }, "provider reduce threw; line skipped");
133503
+ log12.warn({ filePath, provider: provider.name, err }, "provider reduce threw; line skipped");
133400
133504
  }
133401
133505
  }
133402
133506
  } catch (err) {
133403
- log8.warn({ filePath, provider: provider.name, err }, "provider parse: read failed");
133507
+ log12.warn({ filePath, provider: provider.name, err }, "provider parse: read failed");
133404
133508
  return null;
133405
133509
  }
133406
133510
  return provider.finalize(acc, filePath, account, tier);
@@ -133574,8 +133678,8 @@ CREATE INDEX IF NOT EXISTS idx_scanned_dirs_parent_root ON scanned_dirs(parent_r
133574
133678
  function runMigrations(db) {
133575
133679
  const current = db.pragma("user_version", { simple: true });
133576
133680
  if (current >= SCHEMA_VERSION) return;
133577
- const log8 = getLogger2();
133578
- log8.info({ from: current, to: SCHEMA_VERSION }, "migrations: applying");
133681
+ const log12 = getLogger2();
133682
+ log12.info({ from: current, to: SCHEMA_VERSION }, "migrations: applying");
133579
133683
  if (current >= 1 && current < 2 && tableExists(db, "conversations")) {
133580
133684
  for (const [col, ddl] of [
133581
133685
  [
@@ -133617,7 +133721,7 @@ function openDatabase(dbPath) {
133617
133721
  }
133618
133722
  var FULL_RECONCILE_EVERY_N_SCANS = 20;
133619
133723
  async function discoverJsonlFilesGated(dirs, files, scannedDirs, options = {}) {
133620
- const log8 = getLogger2();
133724
+ const log12 = getLogger2();
133621
133725
  if (options.fullRescan) {
133622
133726
  return discoverJsonlFiles(dirs);
133623
133727
  }
@@ -133659,7 +133763,7 @@ async function discoverJsonlFilesGated(dirs, files, scannedDirs, options = {}) {
133659
133763
  }
133660
133764
  }
133661
133765
  }
133662
- log8.debug(
133766
+ log12.debug(
133663
133767
  { totalFiles: results.length, dirs: dirs.length },
133664
133768
  "dir-watermark: gated discovery complete"
133665
133769
  );
@@ -134165,7 +134269,7 @@ var PersistentEngine = class {
134165
134269
  // changed since the last index, and upsert their metadata. Returns the number
134166
134270
  // of files seen on disk this pass (the scan "scanned" count).
134167
134271
  async indexAll(activeProfiles, options) {
134168
- const log8 = getLogger2();
134272
+ const log12 = getLogger2();
134169
134273
  const tier = resolveTier(options.tier ?? "standard", options.tiers);
134170
134274
  const enabled = options.providers ?? [CLAUDE_CODE_PROVIDER];
134171
134275
  const discovered = [];
@@ -134229,7 +134333,7 @@ var PersistentEngine = class {
134229
134333
  for (const path2 of this.files.activePathsByAccounts([...coveredAccounts])) {
134230
134334
  if (!seen.has(path2)) this.markDeleted(path2);
134231
134335
  }
134232
- log8.info({ scanned, indexed: this.conversations.count() }, "persistent: indexAll complete");
134336
+ log12.info({ scanned, indexed: this.conversations.count() }, "persistent: indexAll complete");
134233
134337
  return { scanned };
134234
134338
  }
134235
134339
  // (Re)index a single file. Classifies the change vs. the persisted cursor:
@@ -134240,7 +134344,7 @@ var PersistentEngine = class {
134240
134344
  // alongside the meta so callers (refreshFile) can keep, extend, or evict
134241
134345
  // their own per-file caches without re-stat'ing the file (racy) themselves.
134242
134346
  async indexFile(rawFilePath, account, tierName, customTiers, resolveGitBranch, force = false, provider) {
134243
- const log8 = getLogger2();
134347
+ const log12 = getLogger2();
134244
134348
  const tier = resolveTier(tierName, customTiers);
134245
134349
  const filePath = canonicalPath(rawFilePath);
134246
134350
  const existing = this.files.getByPath(filePath);
@@ -134271,7 +134375,7 @@ var PersistentEngine = class {
134271
134375
  try {
134272
134376
  result = await tailReduce(filePath, startOffset, startLine, state, tier);
134273
134377
  } catch (err) {
134274
- log8.warn({ filePath, err }, "persistent: tail read failed");
134378
+ log12.warn({ filePath, err }, "persistent: tail read failed");
134275
134379
  return { meta: null, change };
134276
134380
  }
134277
134381
  const meta3 = finalizeMeta(state, filePath, account, tier);
@@ -134314,7 +134418,7 @@ var PersistentEngine = class {
134314
134418
  )
134315
134419
  );
134316
134420
  }
134317
- log8.debug(
134421
+ log12.debug(
134318
134422
  { filePath, change, bytesRead: result.newOffset - startOffset, msgs: meta3.messageCount },
134319
134423
  "persistent: indexed file"
134320
134424
  );
@@ -134326,7 +134430,7 @@ var PersistentEngine = class {
134326
134430
  // so the next pass classifies an unchanged file as "unchanged" and skips it;
134327
134431
  // any change reparses from 0 again. No reducer_state is persisted.
134328
134432
  async indexFileWithProvider(provider, filePath, account, tier, stat42, resolveGitBranch) {
134329
- const log8 = getLogger2();
134433
+ const log12 = getLogger2();
134330
134434
  const meta3 = await parseMetaWithProvider(provider, filePath, account, tier);
134331
134435
  if (!meta3) {
134332
134436
  this.markDeleted(filePath);
@@ -134354,7 +134458,7 @@ var PersistentEngine = class {
134354
134458
  });
134355
134459
  });
134356
134460
  upsert();
134357
- log8.debug(
134461
+ log12.debug(
134358
134462
  { filePath, provider: provider.name, msgs: meta3.messageCount },
134359
134463
  "persistent: indexed provider file"
134360
134464
  );
@@ -134468,7 +134572,7 @@ var FileWatcher = class {
134468
134572
  // Resolves once every underlying chokidar watcher has finished its initial
134469
134573
  // scan, so the caller knows subsequent FS changes will be observed.
134470
134574
  async start() {
134471
- const log8 = getLogger2();
134575
+ const log12 = getLogger2();
134472
134576
  const ready = [];
134473
134577
  for (const profile of this.profiles) {
134474
134578
  const dir = getProjectsDir(profile);
@@ -134480,7 +134584,7 @@ var FileWatcher = class {
134480
134584
  watcher.on("add", (p2) => this.dispatch(p2, profile.id, "add")).on("change", (p2) => this.dispatch(p2, profile.id, "change")).on("unlink", (p2) => this.dispatch(p2, profile.id, "unlink"));
134481
134585
  ready.push(new Promise((resolve4) => watcher.once("ready", () => resolve4())));
134482
134586
  this.watchers.push(watcher);
134483
- log8.debug({ dir, account: profile.id }, "watcher: watching");
134587
+ log12.debug({ dir, account: profile.id }, "watcher: watching");
134484
134588
  }
134485
134589
  await Promise.all(ready);
134486
134590
  }
@@ -134527,14 +134631,14 @@ var IndexQueue = class {
134527
134631
  async drain() {
134528
134632
  if (this.running) return;
134529
134633
  this.running = true;
134530
- const log8 = getLogger2();
134634
+ const log12 = getLogger2();
134531
134635
  while (this.pending.size > 0) {
134532
134636
  const [path2, job] = this.pending.entries().next().value;
134533
134637
  this.pending.delete(path2);
134534
134638
  try {
134535
134639
  await this.process(job);
134536
134640
  } catch (err) {
134537
- log8.warn({ filePath: job.filePath, err }, "index-queue: job failed");
134641
+ log12.warn({ filePath: job.filePath, err }, "index-queue: job failed");
134538
134642
  }
134539
134643
  }
134540
134644
  this.running = false;
@@ -134643,13 +134747,13 @@ var ConversationScanner = class {
134643
134747
  // active metas and run the identical filter/sort/view/paginate pipeline as
134644
134748
  // the in-memory path — guaranteeing an identical ScanResult shape.
134645
134749
  async scanPersistent(activeProfiles, options) {
134646
- const log8 = getLogger2();
134750
+ const log12 = getLogger2();
134647
134751
  const startedAt = Date.now();
134648
134752
  const engine = this.engine();
134649
134753
  const { scanned } = await engine.indexAll(activeProfiles, options);
134650
134754
  const allMetas = engine.allActive();
134651
134755
  const { conversations, total } = this.finalize(allMetas, options);
134652
- log8.info(
134756
+ log12.info(
134653
134757
  { scanned, kept: allMetas.length, filteredTotal: total, elapsedMs: Date.now() - startedAt },
134654
134758
  "scan: complete (persistent)"
134655
134759
  );
@@ -134676,10 +134780,10 @@ var ConversationScanner = class {
134676
134780
  return { conversations, total };
134677
134781
  }
134678
134782
  async scanInMemory(activeProfiles, options) {
134679
- const log8 = getLogger2();
134783
+ const log12 = getLogger2();
134680
134784
  const startedAt = Date.now();
134681
134785
  const tier = this.lastTier;
134682
- log8.info(
134786
+ log12.info(
134683
134787
  {
134684
134788
  activeProfiles: activeProfiles.length,
134685
134789
  tier: tier.name,
@@ -134733,7 +134837,7 @@ var ConversationScanner = class {
134733
134837
  return meta3;
134734
134838
  } catch (err) {
134735
134839
  parseFailures++;
134736
- log8.warn({ filePath, account, provider: provider.name, err }, "scan: parse threw");
134840
+ log12.warn({ filePath, account, provider: provider.name, err }, "scan: parse threw");
134737
134841
  return null;
134738
134842
  }
134739
134843
  })
@@ -134753,12 +134857,12 @@ var ConversationScanner = class {
134753
134857
  options.onBatch?.(batchMetas);
134754
134858
  }
134755
134859
  scanned += batch.length;
134756
- log8.debug({ scanned, totalFiles, batchKept: batchMetas.length }, "scan: batch complete");
134860
+ log12.debug({ scanned, totalFiles, batchKept: batchMetas.length }, "scan: batch complete");
134757
134861
  options.onProgress?.(scanned, totalFiles);
134758
134862
  }
134759
134863
  const { conversations, total } = this.finalize(allMetas, options);
134760
134864
  const elapsedMs = Date.now() - startedAt;
134761
- log8.info(
134865
+ log12.info(
134762
134866
  {
134763
134867
  totalFiles,
134764
134868
  scanned,
@@ -134772,13 +134876,13 @@ var ConversationScanner = class {
134772
134876
  return { conversations, total, scanned };
134773
134877
  }
134774
134878
  async search(query, options = {}) {
134775
- const log8 = getLogger2();
134776
- log8.debug({ query, indexSize: this.indexer.getDocumentCount() }, "search: start");
134879
+ const log12 = getLogger2();
134880
+ log12.debug({ query, indexSize: this.indexer.getDocumentCount() }, "search: start");
134777
134881
  let results;
134778
134882
  if (this.persistent) {
134779
134883
  const engine = this.engine();
134780
134884
  if (engine.conversations.count() === 0) {
134781
- log8.debug("search: persistent index empty, triggering scan");
134885
+ log12.debug("search: persistent index empty, triggering scan");
134782
134886
  const profiles = await this.resolveProfiles(options.profiles);
134783
134887
  const activeProfiles = profiles.filter((p2) => p2.enabled && p2.scanHistory !== false);
134784
134888
  await engine.indexAll(activeProfiles, { ...options, limit: void 0, offset: void 0 });
@@ -134791,7 +134895,7 @@ var ConversationScanner = class {
134791
134895
  }));
134792
134896
  } else {
134793
134897
  if (this.indexer.getDocumentCount() === 0) {
134794
- log8.debug("search: index empty, triggering scan");
134898
+ log12.debug("search: index empty, triggering scan");
134795
134899
  await this.scan({ ...options, limit: void 0, offset: void 0 });
134796
134900
  }
134797
134901
  results = this.indexer.search(query, {
@@ -134834,23 +134938,23 @@ var ConversationScanner = class {
134834
134938
  const limit = options.limit ?? 50;
134835
134939
  const offset = options.offset ?? 0;
134836
134940
  const sliced = results.slice(offset, offset + limit);
134837
- log8.debug({ query, matched: results.length, returned: sliced.length }, "search: complete");
134941
+ log12.debug({ query, matched: results.length, returned: sliced.length }, "search: complete");
134838
134942
  return sliced;
134839
134943
  }
134840
134944
  async getConversation(id, _options) {
134841
- const log8 = getLogger2();
134945
+ const log12 = getLogger2();
134842
134946
  const cid = canonicalPath(id);
134843
134947
  const cached3 = this.conversationLRU.get(cid);
134844
134948
  if (cached3) {
134845
- log8.debug({ id }, "getConversation: cache hit");
134949
+ log12.debug({ id }, "getConversation: cache hit");
134846
134950
  return cached3.conversation;
134847
134951
  }
134848
134952
  const meta3 = this.persistent ? this.engine().getByIdOrSession(cid) : this.metadataCache.get(cid) ?? this.resolveSessionId(cid);
134849
134953
  if (!meta3) {
134850
- log8.debug({ id }, "getConversation: not found in metadata");
134954
+ log12.debug({ id }, "getConversation: not found in metadata");
134851
134955
  return null;
134852
134956
  }
134853
- log8.debug({ id, filePath: meta3.filePath }, "getConversation: cache miss, parsing");
134957
+ log12.debug({ id, filePath: meta3.filePath }, "getConversation: cache miss, parsing");
134854
134958
  try {
134855
134959
  if (this.persistent && meta3.provider !== CODEX_CLI_PROVIDER) {
134856
134960
  const parsed = await parseConversationResumable(meta3.filePath, meta3.account);
@@ -134863,7 +134967,7 @@ var ConversationScanner = class {
134863
134967
  }
134864
134968
  return conversation;
134865
134969
  } catch (err) {
134866
- log8.warn({ id, filePath: meta3.filePath, err }, "getConversation: parse failed");
134970
+ log12.warn({ id, filePath: meta3.filePath, err }, "getConversation: parse failed");
134867
134971
  return null;
134868
134972
  }
134869
134973
  }
@@ -134949,7 +135053,7 @@ var ConversationScanner = class {
134949
135053
  return refresh;
134950
135054
  }
134951
135055
  async doRefreshFile(filePath, account) {
134952
- const log8 = getLogger2();
135056
+ const log12 = getLogger2();
134953
135057
  if (this.persistent) {
134954
135058
  const engine = this.engine();
134955
135059
  const previous2 = engine.getByIdOrSession(filePath);
@@ -134976,7 +135080,7 @@ var ConversationScanner = class {
134976
135080
  } else if (change === "appended") {
134977
135081
  await this.extendCachedConversations(cacheKeys, filePath, meta22.account);
134978
135082
  }
134979
- log8.debug({ filePath, change, kept: !!meta22 }, "refreshFile: updated persistent index");
135083
+ log12.debug({ filePath, change, kept: !!meta22 }, "refreshFile: updated persistent index");
134980
135084
  return meta22;
134981
135085
  }
134982
135086
  const previous = this.metadataCache.get(filePath);
@@ -134985,7 +135089,7 @@ var ConversationScanner = class {
134985
135089
  try {
134986
135090
  meta3 = await parseMeta(filePath, resolvedAccount, this.lastTier);
134987
135091
  } catch (err) {
134988
- log8.warn({ filePath, err }, "refreshFile: parseMeta threw");
135092
+ log12.warn({ filePath, err }, "refreshFile: parseMeta threw");
134989
135093
  meta3 = null;
134990
135094
  }
134991
135095
  const evict = (m2) => {
@@ -135001,7 +135105,7 @@ var ConversationScanner = class {
135001
135105
  this.removeFromSessionIndex(previous);
135002
135106
  this.indexer.removeDocument(previous.id);
135003
135107
  }
135004
- log8.debug({ filePath }, "refreshFile: dropped (no parseable messages)");
135108
+ log12.debug({ filePath }, "refreshFile: dropped (no parseable messages)");
135005
135109
  return null;
135006
135110
  }
135007
135111
  meta3.gitBranch = readGitBranch(meta3.projectPath);
@@ -135014,7 +135118,7 @@ var ConversationScanner = class {
135014
135118
  } else {
135015
135119
  this.indexer.addDocument(meta3);
135016
135120
  }
135017
- log8.debug(
135121
+ log12.debug(
135018
135122
  { filePath, messageCount: meta3.messageCount },
135019
135123
  "refreshFile: updated in-memory indexes"
135020
135124
  );
@@ -138222,6 +138326,7 @@ function readRawBody2(req) {
138222
138326
  var createConfigRoutes = (deps) => {
138223
138327
  const app = new Hono2();
138224
138328
  app.get("/claude-flags", (c) => c.json(deps.claudeFlagsConfig()));
138329
+ app.get("/feature-flags", (c) => c.json(deps.featureFlagsConfig()));
138225
138330
  app.put("/claude-flags", async (c) => {
138226
138331
  if (deps.localNoAuth) {
138227
138332
  return c.json({ error: "claude flag changes are disabled while localNoAuth is active" }, 403);
@@ -138475,7 +138580,266 @@ function createLogsRoutes() {
138475
138580
  var import_node_child_process2 = require("child_process");
138476
138581
  var import_node_crypto = require("crypto");
138477
138582
  var import_os5 = require("os");
138583
+
138584
+ // src/db/repositories/push.repository.ts
138585
+ var FAILURE_STREAK_LIMIT = 5;
138586
+ var PUSH_TOKEN_KINDS = ["expo", "liveactivity_start", "liveactivity_update"];
138587
+ var DEFAULT_PUSH_TOKEN_KIND = "expo";
138588
+ function isPushTokenKind(value) {
138589
+ return typeof value === "string" && PUSH_TOKEN_KINDS.includes(value);
138590
+ }
138591
+ function tokenState(row, now = Date.now()) {
138592
+ if (row.revoked_at != null) return "revoked";
138593
+ if (row.expires_at != null && row.expires_at <= now) return "expired";
138594
+ if (row.failure_streak >= FAILURE_STREAK_LIMIT) return "dead";
138595
+ if (row.failure_streak > 0) return "failing";
138596
+ if (row.last_success_at == null) return "never-delivered";
138597
+ return "healthy";
138598
+ }
138599
+ function toHealth(row, now = Date.now()) {
138600
+ return {
138601
+ platform: row.platform,
138602
+ deviceId: row.device_id,
138603
+ registeredAt: row.registered_at,
138604
+ lastSuccessAt: row.last_success_at,
138605
+ lastFailureAt: row.last_failure_at,
138606
+ lastFailureCode: row.last_failure_code,
138607
+ failureStreak: row.failure_streak,
138608
+ revokedAt: row.revoked_at,
138609
+ state: tokenState(row, now),
138610
+ kind: row.kind,
138611
+ activityId: row.activity_id,
138612
+ sessionId: row.session_id,
138613
+ expiresAt: row.expires_at
138614
+ };
138615
+ }
138616
+ var PushRepository = class {
138617
+ upsertStmt;
138618
+ getStmt;
138619
+ listActiveStmt;
138620
+ listAllStmt;
138621
+ successStmt;
138622
+ failureStmt;
138623
+ revokeStmt;
138624
+ claimEventStmt;
138625
+ markDeliveredStmt;
138626
+ listByKindSessionStmt;
138627
+ listByKindStmt;
138628
+ listRenewableStmt;
138629
+ claimRenewalStmt;
138630
+ expireStmt;
138631
+ expireSessionActivitiesStmt;
138632
+ constructor(db) {
138633
+ this.upsertStmt = db.prepare(`
138634
+ INSERT INTO push_tokens (
138635
+ token, platform, device_id, registered_at,
138636
+ kind, activity_id, session_id, expires_at, stale_date, started_at
138637
+ )
138638
+ VALUES (
138639
+ @token, @platform, @device_id, @registered_at,
138640
+ @kind, @activity_id, @session_id, @expires_at, @stale_date, @started_at
138641
+ )
138642
+ ON CONFLICT(token) DO UPDATE SET
138643
+ platform = excluded.platform,
138644
+ device_id = COALESCE(excluded.device_id, push_tokens.device_id),
138645
+ registered_at = excluded.registered_at,
138646
+ kind = excluded.kind,
138647
+ activity_id = COALESCE(excluded.activity_id, push_tokens.activity_id),
138648
+ session_id = COALESCE(excluded.session_id, push_tokens.session_id),
138649
+ expires_at = excluded.expires_at,
138650
+ stale_date = excluded.stale_date,
138651
+ -- Preserve the ORIGINAL start across a re-registration. iOS renders its
138652
+ -- own ticking timer from started_at, so overwriting it with a fresh
138653
+ -- value visibly resets the user's elapsed time to zero.
138654
+ started_at = COALESCE(push_tokens.started_at, excluded.started_at),
138655
+ -- A fresh registration clears prior failure state and any revocation:
138656
+ -- the client is telling us this token is live again. renewed_at clears
138657
+ -- too \u2014 this is a new activity generation, so it is renewable again.
138658
+ failure_streak = 0,
138659
+ last_failure_at = NULL,
138660
+ last_failure_code = NULL,
138661
+ revoked_at = NULL,
138662
+ renewed_at = NULL
138663
+ `);
138664
+ this.getStmt = db.prepare("SELECT * FROM push_tokens WHERE token = ?");
138665
+ this.listActiveStmt = db.prepare(`
138666
+ SELECT * FROM push_tokens
138667
+ WHERE revoked_at IS NULL AND failure_streak < ${FAILURE_STREAK_LIMIT}
138668
+ AND kind = 'expo'
138669
+ ORDER BY registered_at ASC
138670
+ `);
138671
+ this.listAllStmt = db.prepare("SELECT * FROM push_tokens ORDER BY registered_at ASC");
138672
+ this.successStmt = db.prepare(`
138673
+ UPDATE push_tokens
138674
+ SET last_success_at = @at, failure_streak = 0,
138675
+ last_failure_code = NULL
138676
+ WHERE token = @token
138677
+ `);
138678
+ this.failureStmt = db.prepare(`
138679
+ UPDATE push_tokens
138680
+ SET last_failure_at = @at, last_failure_code = @code,
138681
+ failure_streak = failure_streak + 1
138682
+ WHERE token = @token
138683
+ `);
138684
+ this.revokeStmt = db.prepare("UPDATE push_tokens SET revoked_at = ? WHERE token = ?");
138685
+ this.listByKindSessionStmt = db.prepare(`
138686
+ SELECT * FROM push_tokens
138687
+ WHERE kind = @kind AND session_id = @session_id
138688
+ AND revoked_at IS NULL AND failure_streak < ${FAILURE_STREAK_LIMIT}
138689
+ AND (expires_at IS NULL OR expires_at > @now)
138690
+ ORDER BY registered_at ASC
138691
+ `);
138692
+ this.listByKindStmt = db.prepare(`
138693
+ SELECT * FROM push_tokens
138694
+ WHERE kind = @kind
138695
+ AND revoked_at IS NULL AND failure_streak < ${FAILURE_STREAK_LIMIT}
138696
+ AND (expires_at IS NULL OR expires_at > @now)
138697
+ ORDER BY registered_at ASC
138698
+ `);
138699
+ this.listRenewableStmt = db.prepare(`
138700
+ SELECT * FROM push_tokens
138701
+ WHERE kind = 'liveactivity_update'
138702
+ AND stale_date IS NOT NULL AND renewed_at IS NULL
138703
+ AND revoked_at IS NULL AND failure_streak < ${FAILURE_STREAK_LIMIT}
138704
+ ORDER BY stale_date ASC
138705
+ `);
138706
+ this.claimRenewalStmt = db.prepare(`
138707
+ UPDATE push_tokens SET renewed_at = @at
138708
+ WHERE token = @token AND renewed_at IS NULL
138709
+ `);
138710
+ this.expireStmt = db.prepare("UPDATE push_tokens SET expires_at = ? WHERE token = ?");
138711
+ this.expireSessionActivitiesStmt = db.prepare(`
138712
+ UPDATE push_tokens SET expires_at = @at
138713
+ WHERE session_id = @session_id AND kind = 'liveactivity_update'
138714
+ AND (expires_at IS NULL OR expires_at > @at)
138715
+ `);
138716
+ this.claimEventStmt = db.prepare(`
138717
+ INSERT OR IGNORE INTO push_events (event_id, session_id, created_at)
138718
+ VALUES (@event_id, @session_id, @created_at)
138719
+ `);
138720
+ this.markDeliveredStmt = db.prepare(
138721
+ "UPDATE push_events SET delivered_at = ? WHERE event_id = ?"
138722
+ );
138723
+ }
138724
+ /**
138725
+ * Register or refresh a token.
138726
+ *
138727
+ * `kind` defaults to Expo so a released client posting `{ token, platform }`
138728
+ * keeps working — tb-mobile cannot be force-updated, and every client
138729
+ * predating Live Activities is registering an Expo relay token.
138730
+ *
138731
+ * Several rows per device is normal and intended: a device runs one activity
138732
+ * per live session, each with its own update token. The token itself is the
138733
+ * primary key, so distinct activities never collide.
138734
+ */
138735
+ register(args) {
138736
+ this.upsertStmt.run({
138737
+ token: args.token,
138738
+ platform: args.platform,
138739
+ device_id: args.deviceId ?? null,
138740
+ registered_at: args.now ?? Date.now(),
138741
+ kind: args.kind ?? DEFAULT_PUSH_TOKEN_KIND,
138742
+ activity_id: args.activityId ?? null,
138743
+ session_id: args.sessionId ?? null,
138744
+ expires_at: args.expiresAt ?? null,
138745
+ stale_date: args.staleDate ?? null,
138746
+ started_at: args.startedAt ?? null
138747
+ });
138748
+ }
138749
+ get(token) {
138750
+ return this.getStmt.get(token) ?? null;
138751
+ }
138752
+ /**
138753
+ * Expo tokens eligible for delivery — not revoked, not past the failure limit.
138754
+ *
138755
+ * Deliberately Expo-only. ActivityKit tokens go over direct APNs with a
138756
+ * different topic and are rejected by Expo's relay, so the ordinary
138757
+ * notification fan-out must not see them.
138758
+ */
138759
+ listDeliverable() {
138760
+ return this.listActiveStmt.all();
138761
+ }
138762
+ /** Live-activity tokens for one session, eligible for delivery. */
138763
+ listForSession(kind, sessionId, now = Date.now()) {
138764
+ return this.listByKindSessionStmt.all({
138765
+ kind,
138766
+ session_id: sessionId,
138767
+ now
138768
+ });
138769
+ }
138770
+ /**
138771
+ * Every deliverable token of one kind.
138772
+ *
138773
+ * Used for push-to-start, which is app-wide rather than session-scoped: the
138774
+ * activity does not exist yet, so there is no per-activity token to look up.
138775
+ */
138776
+ listByKind(kind, now = Date.now()) {
138777
+ return this.listByKindStmt.all({ kind, now });
138778
+ }
138779
+ /** Unrenewed activities with a renewal deadline, soonest first. */
138780
+ listRenewable() {
138781
+ return this.listRenewableStmt.all();
138782
+ }
138783
+ /**
138784
+ * Claim a row for renewal.
138785
+ *
138786
+ * Returns true exactly once per row. A restart re-arms timers from the
138787
+ * persisted deadline, so the same renewal can be attempted twice; the loser
138788
+ * gets false and must not send. Doing this as a conditional UPDATE rather
138789
+ * than read-then-write avoids the race where both attempts observe
138790
+ * "not yet renewed".
138791
+ */
138792
+ claimRenewal(token, now = Date.now()) {
138793
+ return this.claimRenewalStmt.run({ token, at: now }).changes > 0;
138794
+ }
138795
+ /** Mark one token expired, so it stops being a delivery target. */
138796
+ expire(token, now = Date.now()) {
138797
+ this.expireStmt.run(now, token);
138798
+ }
138799
+ /**
138800
+ * Expire every live activity for a session.
138801
+ *
138802
+ * Called when the session ends. Without this, a per-activity token outlives
138803
+ * its session and a later renewal sweep would resurrect an activity for a
138804
+ * session that is already gone.
138805
+ */
138806
+ expireSessionActivities(sessionId, now = Date.now()) {
138807
+ this.expireSessionActivitiesStmt.run({ session_id: sessionId, at: now });
138808
+ }
138809
+ /** Every token, including dead and revoked ones, for the health report. */
138810
+ listHealth(now = Date.now()) {
138811
+ return this.listAllStmt.all().map((r) => toHealth(r, now));
138812
+ }
138813
+ recordSuccess(token, now = Date.now()) {
138814
+ this.successStmt.run({ token, at: now });
138815
+ }
138816
+ recordFailure(token, code, now = Date.now()) {
138817
+ this.failureStmt.run({ token, at: now, code });
138818
+ }
138819
+ revoke(token, now = Date.now()) {
138820
+ return this.revokeStmt.run(now, token).changes > 0;
138821
+ }
138822
+ /**
138823
+ * Claim an event id for delivery.
138824
+ *
138825
+ * Returns true exactly once per event id. A retry, a reconnect
138826
+ * reconciliation, or two triggers firing for the same underlying event all
138827
+ * get false and must not notify — the user should never be told twice about
138828
+ * one thing.
138829
+ */
138830
+ claimEvent(eventId, sessionId, now = Date.now()) {
138831
+ return this.claimEventStmt.run({ event_id: eventId, session_id: sessionId, created_at: now }).changes > 0;
138832
+ }
138833
+ markDelivered(eventId, now = Date.now()) {
138834
+ this.markDeliveredStmt.run(now, eventId);
138835
+ }
138836
+ };
138837
+
138838
+ // src/api/routes/misc.routes.ts
138478
138839
  init_logger();
138840
+ function numberOrNull(value) {
138841
+ return typeof value === "number" && Number.isFinite(value) ? value : null;
138842
+ }
138479
138843
  function readJsonBody(req) {
138480
138844
  return new Promise((resolve4, reject) => {
138481
138845
  const chunks = [];
@@ -138522,7 +138886,11 @@ var createMiscRoutes = (deps) => {
138522
138886
  // Capability flag: this server serves /api/config/claude-flags. Additive —
138523
138887
  // older clients ignore it, and clients talking to an older server see it
138524
138888
  // absent and hide the UI rather than 404ing.
138525
- claudeFlags: true
138889
+ claudeFlags: true,
138890
+ // Same contract: this server serves GET /api/config/feature-flags. Lives
138891
+ // here rather than behind /api/config (admin-only) so a read-only client
138892
+ // still learns the server supports flags even if it can't read values.
138893
+ featureFlags: true
138526
138894
  });
138527
138895
  });
138528
138896
  app.get("/api/profiles", (c) => c.json([]));
@@ -138549,6 +138917,22 @@ var createMiscRoutes = (deps) => {
138549
138917
  if (platform3 !== "ios" && platform3 !== "android") {
138550
138918
  return c.json({ error: "platform must be 'ios' or 'android'" }, 400);
138551
138919
  }
138920
+ const kind = body?.kind === void 0 ? DEFAULT_PUSH_TOKEN_KIND : body.kind;
138921
+ if (!isPushTokenKind(kind)) {
138922
+ return c.json(
138923
+ { error: `kind must be one of ${PUSH_TOKEN_KINDS.join(", ")}`, code: "INVALID_KIND" },
138924
+ 400
138925
+ );
138926
+ }
138927
+ if (kind === "liveactivity_update" && typeof body?.activityId !== "string") {
138928
+ return c.json(
138929
+ {
138930
+ error: "activityId is required for kind 'liveactivity_update'",
138931
+ code: "MISSING_ACTIVITY"
138932
+ },
138933
+ 400
138934
+ );
138935
+ }
138552
138936
  const repo = deps.pushRepo();
138553
138937
  if (!repo) {
138554
138938
  return c.json({ error: "Push registration is unavailable", code: "STORE_UNAVAILABLE" }, 503);
@@ -138556,7 +138940,13 @@ var createMiscRoutes = (deps) => {
138556
138940
  repo.register({
138557
138941
  token,
138558
138942
  platform: platform3,
138559
- deviceId: typeof body?.deviceId === "string" ? body.deviceId : null
138943
+ deviceId: typeof body?.deviceId === "string" ? body.deviceId : null,
138944
+ kind,
138945
+ activityId: typeof body?.activityId === "string" ? body.activityId : null,
138946
+ sessionId: typeof body?.sessionId === "string" ? body.sessionId : null,
138947
+ expiresAt: numberOrNull(body?.expiresAt),
138948
+ staleDate: numberOrNull(body?.staleDate),
138949
+ startedAt: numberOrNull(body?.startedAt)
138560
138950
  });
138561
138951
  return c.json({ ok: true });
138562
138952
  });
@@ -139114,6 +139504,14 @@ var createSessionRoutes = (deps) => {
139114
139504
  await deps.handleSetSessionName(c.req.param("id"), c.env.incoming, c.env.outgoing);
139115
139505
  return alreadyHandled6();
139116
139506
  });
139507
+ app.patch("/:id/model", async (c) => {
139508
+ await deps.handleSetSessionModel(c.req.param("id"), c.env.incoming, c.env.outgoing);
139509
+ return alreadyHandled6();
139510
+ });
139511
+ app.patch("/:id/effort", async (c) => {
139512
+ await deps.handleSetSessionEffort(c.req.param("id"), c.env.incoming, c.env.outgoing);
139513
+ return alreadyHandled6();
139514
+ });
139117
139515
  app.post("/:id/adopt", async (c) => {
139118
139516
  await deps.handleAdopt(c.req.param("id"), c.env.outgoing);
139119
139517
  return alreadyHandled6();
@@ -139453,6 +139851,7 @@ CREATE TABLE IF NOT EXISTS conversation_meta (
139453
139851
  );
139454
139852
  CREATE INDEX IF NOT EXISTS idx_meta_last_activity ON conversation_meta(last_activity DESC);
139455
139853
  CREATE INDEX IF NOT EXISTS idx_meta_project ON conversation_meta(project_path);
139854
+ CREATE INDEX IF NOT EXISTS idx_meta_file_path ON conversation_meta(file_path);
139456
139855
 
139457
139856
  CREATE TABLE IF NOT EXISTS conversation_tail (
139458
139857
  conversation_id TEXT PRIMARY KEY REFERENCES conversation_meta(id) ON DELETE CASCADE,
@@ -141004,125 +141403,6 @@ function deriveNameFromPath(path2) {
141004
141403
  return parts.length > 0 ? parts[parts.length - 1] : null;
141005
141404
  }
141006
141405
 
141007
- // src/db/repositories/push.repository.ts
141008
- var FAILURE_STREAK_LIMIT = 5;
141009
- function tokenState(row) {
141010
- if (row.revoked_at != null) return "revoked";
141011
- if (row.failure_streak >= FAILURE_STREAK_LIMIT) return "dead";
141012
- if (row.failure_streak > 0) return "failing";
141013
- if (row.last_success_at == null) return "never-delivered";
141014
- return "healthy";
141015
- }
141016
- function toHealth(row) {
141017
- return {
141018
- platform: row.platform,
141019
- deviceId: row.device_id,
141020
- registeredAt: row.registered_at,
141021
- lastSuccessAt: row.last_success_at,
141022
- lastFailureAt: row.last_failure_at,
141023
- lastFailureCode: row.last_failure_code,
141024
- failureStreak: row.failure_streak,
141025
- revokedAt: row.revoked_at,
141026
- state: tokenState(row)
141027
- };
141028
- }
141029
- var PushRepository = class {
141030
- upsertStmt;
141031
- getStmt;
141032
- listActiveStmt;
141033
- listAllStmt;
141034
- successStmt;
141035
- failureStmt;
141036
- revokeStmt;
141037
- claimEventStmt;
141038
- markDeliveredStmt;
141039
- constructor(db) {
141040
- this.upsertStmt = db.prepare(`
141041
- INSERT INTO push_tokens (token, platform, device_id, registered_at)
141042
- VALUES (@token, @platform, @device_id, @registered_at)
141043
- ON CONFLICT(token) DO UPDATE SET
141044
- platform = excluded.platform,
141045
- device_id = COALESCE(excluded.device_id, push_tokens.device_id),
141046
- registered_at = excluded.registered_at,
141047
- -- A fresh registration clears prior failure state and any revocation:
141048
- -- the client is telling us this token is live again.
141049
- failure_streak = 0,
141050
- last_failure_at = NULL,
141051
- last_failure_code = NULL,
141052
- revoked_at = NULL
141053
- `);
141054
- this.getStmt = db.prepare("SELECT * FROM push_tokens WHERE token = ?");
141055
- this.listActiveStmt = db.prepare(`
141056
- SELECT * FROM push_tokens
141057
- WHERE revoked_at IS NULL AND failure_streak < ${FAILURE_STREAK_LIMIT}
141058
- ORDER BY registered_at ASC
141059
- `);
141060
- this.listAllStmt = db.prepare("SELECT * FROM push_tokens ORDER BY registered_at ASC");
141061
- this.successStmt = db.prepare(`
141062
- UPDATE push_tokens
141063
- SET last_success_at = @at, failure_streak = 0,
141064
- last_failure_code = NULL
141065
- WHERE token = @token
141066
- `);
141067
- this.failureStmt = db.prepare(`
141068
- UPDATE push_tokens
141069
- SET last_failure_at = @at, last_failure_code = @code,
141070
- failure_streak = failure_streak + 1
141071
- WHERE token = @token
141072
- `);
141073
- this.revokeStmt = db.prepare("UPDATE push_tokens SET revoked_at = ? WHERE token = ?");
141074
- this.claimEventStmt = db.prepare(`
141075
- INSERT OR IGNORE INTO push_events (event_id, session_id, created_at)
141076
- VALUES (@event_id, @session_id, @created_at)
141077
- `);
141078
- this.markDeliveredStmt = db.prepare(
141079
- "UPDATE push_events SET delivered_at = ? WHERE event_id = ?"
141080
- );
141081
- }
141082
- register(args) {
141083
- this.upsertStmt.run({
141084
- token: args.token,
141085
- platform: args.platform,
141086
- device_id: args.deviceId ?? null,
141087
- registered_at: args.now ?? Date.now()
141088
- });
141089
- }
141090
- get(token) {
141091
- return this.getStmt.get(token) ?? null;
141092
- }
141093
- /** Tokens eligible for delivery — not revoked, not past the failure limit. */
141094
- listDeliverable() {
141095
- return this.listActiveStmt.all();
141096
- }
141097
- /** Every token, including dead and revoked ones, for the health report. */
141098
- listHealth() {
141099
- return this.listAllStmt.all().map(toHealth);
141100
- }
141101
- recordSuccess(token, now = Date.now()) {
141102
- this.successStmt.run({ token, at: now });
141103
- }
141104
- recordFailure(token, code, now = Date.now()) {
141105
- this.failureStmt.run({ token, at: now, code });
141106
- }
141107
- revoke(token, now = Date.now()) {
141108
- return this.revokeStmt.run(now, token).changes > 0;
141109
- }
141110
- /**
141111
- * Claim an event id for delivery.
141112
- *
141113
- * Returns true exactly once per event id. A retry, a reconnect
141114
- * reconciliation, or two triggers firing for the same underlying event all
141115
- * get false and must not notify — the user should never be told twice about
141116
- * one thing.
141117
- */
141118
- claimEvent(eventId, sessionId, now = Date.now()) {
141119
- return this.claimEventStmt.run({ event_id: eventId, session_id: sessionId, created_at: now }).changes > 0;
141120
- }
141121
- markDelivered(eventId, now = Date.now()) {
141122
- this.markDeliveredStmt.run(now, eventId);
141123
- }
141124
- };
141125
-
141126
141406
  // src/db/repositories/sessions.repository.ts
141127
141407
  var SessionsRepository = class {
141128
141408
  constructor(store) {
@@ -141156,6 +141436,9 @@ async function recordUpload(pool2, instanceId, row) {
141156
141436
  );
141157
141437
  }
141158
141438
 
141439
+ // src/server.ts
141440
+ init_feature_flags();
141441
+
141159
141442
  // src/handlers/handleListProjects.ts
141160
141443
  var import_fs18 = require("fs");
141161
141444
  var import_os7 = require("os");
@@ -143429,10 +143712,10 @@ function fingerprintOf(ids) {
143429
143712
  return `sha256:${(0, import_crypto11.createHash)("sha256").update(sorted.join("\n")).digest("hex")}`;
143430
143713
  }
143431
143714
  var CacheIntegrityMonitor = class {
143432
- constructor(cache, wsHub, log8, cacheDir, rescan, runDuringReset) {
143715
+ constructor(cache, wsHub, log12, cacheDir, rescan, runDuringReset) {
143433
143716
  this.cache = cache;
143434
143717
  this.wsHub = wsHub;
143435
- this.log = log8;
143718
+ this.log = log12;
143436
143719
  this.cacheDir = cacheDir;
143437
143720
  this.rescan = rescan;
143438
143721
  this.runDuringReset = runDuringReset;
@@ -144264,6 +144547,585 @@ function deriveProjectChatTitle(input) {
144264
144547
  return `Untitled \xB7 ${input.id.slice(0, 8)}`;
144265
144548
  }
144266
144549
 
144550
+ // src/services/push/apnsClient.ts
144551
+ var import_node_crypto3 = require("crypto");
144552
+ var import_node_http2 = require("http2");
144553
+ init_logger();
144554
+ var log3 = getLogger("apns");
144555
+ var APNS_HOST_SANDBOX = "api.sandbox.push.apple.com";
144556
+ var APNS_MAX_PAYLOAD_BYTES = 4096;
144557
+ var JWT_TTL_SECONDS = 3e3;
144558
+ var DEAD_TOKEN_REASONS = /* @__PURE__ */ new Set([
144559
+ "BadDeviceToken",
144560
+ "DeviceTokenNotForTopic",
144561
+ "Unregistered",
144562
+ "ExpiredToken"
144563
+ ]);
144564
+ function base64url3(input) {
144565
+ return Buffer.from(input).toString("base64url");
144566
+ }
144567
+ function readApnsCredentialsFromEnv(env = process.env) {
144568
+ const key = env.APNS_KEY;
144569
+ if (!key || key.trim().length === 0) return null;
144570
+ const keyId = env.APNS_KEY_ID?.trim();
144571
+ const teamId = env.APNS_TEAM_ID?.trim();
144572
+ const bundleId = env.APNS_BUNDLE_ID?.trim();
144573
+ if (!keyId || !teamId || !bundleId) return null;
144574
+ const host = env.APNS_HOST ?? APNS_HOST_SANDBOX;
144575
+ return { key, keyId, teamId, bundleId, host };
144576
+ }
144577
+ function describeMissingApnsCredentials(env = process.env) {
144578
+ if (!env.APNS_KEY || env.APNS_KEY.trim().length === 0) {
144579
+ return "APNS_KEY is not set, so Live Activity push is disabled. Set it to the p8 key contents (not a path) to enable it.";
144580
+ }
144581
+ const missing = [
144582
+ ["APNS_KEY_ID", env.APNS_KEY_ID],
144583
+ ["APNS_TEAM_ID", env.APNS_TEAM_ID],
144584
+ ["APNS_BUNDLE_ID", env.APNS_BUNDLE_ID]
144585
+ ].filter(([, value]) => !value || value.trim().length === 0).map(([name]) => name);
144586
+ if (missing.length === 0) return null;
144587
+ return `APNS_KEY is set but ${missing.join(", ")} ${missing.length === 1 ? "is" : "are"} not, so Live Activity push is disabled. Under launchd, APNS_KEY_ID is derived from the AuthKey_<keyId>.p8 filename; the team and bundle ids must be set explicitly.`;
144588
+ }
144589
+ var ApnsClient = class {
144590
+ constructor(creds) {
144591
+ this.creds = creds;
144592
+ }
144593
+ creds;
144594
+ session = null;
144595
+ cachedJwt = null;
144596
+ /**
144597
+ * The `apns-topic` for Live Activity pushes.
144598
+ *
144599
+ * The `.push-type.liveactivity` suffix is mandatory and is why the signing key
144600
+ * must be Team Scoped (All Topics) — a key scoped to the bundle id alone
144601
+ * cannot sign this topic.
144602
+ */
144603
+ get topic() {
144604
+ return `${this.creds.bundleId}.push-type.liveactivity`;
144605
+ }
144606
+ /**
144607
+ * Mint or reuse the provider JWT.
144608
+ *
144609
+ * ES256 over the p8 key. Cached until shortly before expiry: Apple rejects a
144610
+ * token older than an hour, but minting one per request is wasteful and can
144611
+ * trip APNs' provider-token-update throttle.
144612
+ */
144613
+ getJwt(now = Date.now()) {
144614
+ const nowSeconds = Math.floor(now / 1e3);
144615
+ if (this.cachedJwt && this.cachedJwt.expiresAt > nowSeconds + 60) {
144616
+ return this.cachedJwt.token;
144617
+ }
144618
+ const header = base64url3(JSON.stringify({ alg: "ES256", kid: this.creds.keyId, typ: "JWT" }));
144619
+ const payload = base64url3(JSON.stringify({ iss: this.creds.teamId, iat: nowSeconds }));
144620
+ const signingInput = `${header}.${payload}`;
144621
+ const signature = (0, import_node_crypto3.createSign)("SHA256").update(signingInput).sign({ key: this.creds.key, dsaEncoding: "ieee-p1363" });
144622
+ const token = `${signingInput}.${base64url3(signature)}`;
144623
+ this.cachedJwt = { token, expiresAt: nowSeconds + JWT_TTL_SECONDS };
144624
+ return token;
144625
+ }
144626
+ /**
144627
+ * Reuse one HTTP/2 session across sends.
144628
+ *
144629
+ * APNs expects a long-lived connection; a fresh TLS handshake per push is slow
144630
+ * and Apple treats connection churn as abuse.
144631
+ */
144632
+ getSession() {
144633
+ if (this.session && !this.session.closed && !this.session.destroyed) {
144634
+ return this.session;
144635
+ }
144636
+ const session = (0, import_node_http2.connect)(`https://${this.creds.host}`);
144637
+ session.on("error", (err) => {
144638
+ log3.warn("apns.session_error", { event: "apns.session_error", err: String(err) });
144639
+ });
144640
+ this.session = session;
144641
+ return session;
144642
+ }
144643
+ /**
144644
+ * Send one push.
144645
+ *
144646
+ * Resolves with a result rather than rejecting on an APNs rejection: a
144647
+ * rejected push is an expected outcome the caller must act on (retire the
144648
+ * token), not an exception. Only a genuinely unexpected local failure throws,
144649
+ * and the caller logs it.
144650
+ */
144651
+ async send(args) {
144652
+ const body = Buffer.from(JSON.stringify(args.payload), "utf-8");
144653
+ if (body.byteLength > APNS_MAX_PAYLOAD_BYTES) {
144654
+ throw new Error(
144655
+ `APNs payload is ${body.byteLength} bytes, over the ${APNS_MAX_PAYLOAD_BYTES} byte limit`
144656
+ );
144657
+ }
144658
+ const session = this.getSession();
144659
+ const headers = {
144660
+ [import_node_http2.constants.HTTP2_HEADER_METHOD]: "POST",
144661
+ [import_node_http2.constants.HTTP2_HEADER_PATH]: `/3/device/${args.deviceToken}`,
144662
+ [import_node_http2.constants.HTTP2_HEADER_AUTHORIZATION]: `bearer ${this.getJwt()}`,
144663
+ "apns-push-type": "liveactivity",
144664
+ "apns-topic": this.topic,
144665
+ "apns-priority": String(args.priority ?? 10),
144666
+ ...args.expirationSeconds != null && {
144667
+ "apns-expiration": String(args.expirationSeconds)
144668
+ },
144669
+ [import_node_http2.constants.HTTP2_HEADER_CONTENT_TYPE]: "application/json",
144670
+ [import_node_http2.constants.HTTP2_HEADER_CONTENT_LENGTH]: String(body.byteLength)
144671
+ };
144672
+ return new Promise((resolve4, reject) => {
144673
+ const req = session.request(headers);
144674
+ req.setTimeout(args.timeoutMs ?? 1e4, () => {
144675
+ req.close(import_node_http2.constants.NGHTTP2_CANCEL);
144676
+ resolve4({ ok: false, status: 0, reason: "Timeout", tokenDead: false });
144677
+ });
144678
+ let status = 0;
144679
+ req.on("response", (resHeaders) => {
144680
+ status = Number(resHeaders[import_node_http2.constants.HTTP2_HEADER_STATUS] ?? 0);
144681
+ });
144682
+ const chunks = [];
144683
+ req.on("data", (chunk) => chunks.push(chunk));
144684
+ req.on("error", reject);
144685
+ req.on("end", () => {
144686
+ const raw2 = Buffer.concat(chunks).toString("utf-8");
144687
+ let reason;
144688
+ if (raw2.length > 0) {
144689
+ try {
144690
+ reason = JSON.parse(raw2).reason;
144691
+ } catch {
144692
+ reason = raw2.slice(0, 200);
144693
+ }
144694
+ }
144695
+ resolve4({
144696
+ ok: status === 200,
144697
+ status,
144698
+ reason,
144699
+ tokenDead: reason != null && DEAD_TOKEN_REASONS.has(reason)
144700
+ });
144701
+ });
144702
+ req.end(body);
144703
+ });
144704
+ }
144705
+ /** Close the shared connection. Called on server shutdown. */
144706
+ close() {
144707
+ this.session?.close();
144708
+ this.session = null;
144709
+ }
144710
+ };
144711
+
144712
+ // src/services/push/liveActivityNotifier.ts
144713
+ init_logger();
144714
+
144715
+ // src/services/push/liveActivityContentState.ts
144716
+ var LAST_OUTPUT_MAX_LENGTH = 90;
144717
+ function toLiveActivityStatus(status) {
144718
+ return status === "running" || status === "waiting_input" ? status : null;
144719
+ }
144720
+ function truncateLastOutput(raw2) {
144721
+ const oneLine = raw2.replace(/\s+/g, " ").trim();
144722
+ return oneLine.length <= LAST_OUTPUT_MAX_LENGTH ? oneLine : oneLine.slice(0, LAST_OUTPUT_MAX_LENGTH);
144723
+ }
144724
+
144725
+ // src/services/push/liveActivityNotifier.ts
144726
+ var log4 = getLogger("live-activity");
144727
+ function contentStateForSession(args) {
144728
+ const status = toLiveActivityStatus(args.session.status);
144729
+ if (!status) return null;
144730
+ return {
144731
+ sessionId: args.session.id,
144732
+ serverId: args.serverId,
144733
+ projectName: args.session.projectName,
144734
+ status,
144735
+ startedAt: args.startedAtOverride ?? args.session.startedAt.getTime(),
144736
+ lastOutput: truncateLastOutput(args.session.lastOutput ?? ""),
144737
+ ...args.serverLabel != null && { serverLabel: args.serverLabel }
144738
+ };
144739
+ }
144740
+ var LiveActivityNotifier = class {
144741
+ constructor(sender, serverId, serverLabel) {
144742
+ this.sender = sender;
144743
+ this.serverId = serverId;
144744
+ this.serverLabel = serverLabel;
144745
+ }
144746
+ sender;
144747
+ serverId;
144748
+ serverLabel;
144749
+ /**
144750
+ * Last status pushed per session.
144751
+ *
144752
+ * Live Activity pushes are rate-limited by iOS and the surface only renders
144753
+ * `running` vs `waiting_input`, so re-pushing an unchanged status is pure
144754
+ * budget spend for no visible change. This is what makes the notifier
144755
+ * edge-triggered rather than level-triggered.
144756
+ */
144757
+ lastPushed = /* @__PURE__ */ new Map();
144758
+ /**
144759
+ * React to a session status change.
144760
+ *
144761
+ * Fire-and-forget by design: a push must never delay or fail a session
144762
+ * transition, so this returns a promise the caller may ignore and every error
144763
+ * is logged rather than propagated.
144764
+ */
144765
+ async onStatusChange(session) {
144766
+ const status = toLiveActivityStatus(session.status);
144767
+ try {
144768
+ if (!status) {
144769
+ await this.endFor(session);
144770
+ return;
144771
+ }
144772
+ if (this.lastPushed.get(session.id) === status) return;
144773
+ const contentState = contentStateForSession({
144774
+ session,
144775
+ serverId: this.serverId,
144776
+ serverLabel: this.serverLabel
144777
+ });
144778
+ if (!contentState) return;
144779
+ const outcome = await this.sender.send({
144780
+ sessionId: session.id,
144781
+ event: "update",
144782
+ contentState
144783
+ });
144784
+ this.lastPushed.set(session.id, status);
144785
+ if (outcome.attempted > 0) {
144786
+ log4.info("live_activity.updated", {
144787
+ event: "live_activity.updated",
144788
+ sessionId: session.id,
144789
+ status,
144790
+ ...outcome
144791
+ });
144792
+ }
144793
+ } catch (err) {
144794
+ log4.error("live_activity.notify_failed", {
144795
+ event: "live_activity.notify_failed",
144796
+ sessionId: session.id,
144797
+ status: session.status,
144798
+ err: String(err)
144799
+ });
144800
+ }
144801
+ }
144802
+ async endFor(session) {
144803
+ const lastStatus = this.lastPushed.get(session.id);
144804
+ this.lastPushed.delete(session.id);
144805
+ const contentState = contentStateForSession({
144806
+ session: {
144807
+ ...session,
144808
+ status: lastStatus === "waiting_input" ? "waiting_input" : "running"
144809
+ },
144810
+ serverId: this.serverId,
144811
+ serverLabel: this.serverLabel
144812
+ });
144813
+ if (!contentState) return;
144814
+ const outcome = await this.sender.end({ sessionId: session.id, contentState });
144815
+ if (outcome.attempted > 0) {
144816
+ log4.info("live_activity.ended", {
144817
+ event: "live_activity.ended",
144818
+ sessionId: session.id,
144819
+ ...outcome
144820
+ });
144821
+ }
144822
+ }
144823
+ /** Drop cached state for a session, so a resume re-pushes its first status. */
144824
+ forget(sessionId) {
144825
+ this.lastPushed.delete(sessionId);
144826
+ }
144827
+ };
144828
+
144829
+ // src/services/push/liveActivityRenewal.ts
144830
+ init_logger();
144831
+
144832
+ // src/services/push/liveActivitySender.ts
144833
+ init_logger();
144834
+ var log5 = getLogger("live-activity");
144835
+ var ACTIVITY_MAX_LIFETIME_MS = 8 * 60 * 60 * 1e3;
144836
+ function buildActivityKitPayload(args) {
144837
+ return {
144838
+ aps: {
144839
+ timestamp: Math.floor(args.now / 1e3),
144840
+ event: args.event,
144841
+ "content-state": args.contentState,
144842
+ ...args.staleDate != null && { "stale-date": Math.floor(args.staleDate / 1e3) },
144843
+ ...args.dismissalDate != null && {
144844
+ "dismissal-date": Math.floor(args.dismissalDate / 1e3)
144845
+ }
144846
+ }
144847
+ };
144848
+ }
144849
+ var LiveActivitySender = class {
144850
+ constructor(apns, repo) {
144851
+ this.apns = apns;
144852
+ this.repo = repo;
144853
+ }
144854
+ apns;
144855
+ repo;
144856
+ /**
144857
+ * Push to every live activity of a session.
144858
+ *
144859
+ * Sends are independent: one rejected token must not stop the others, because
144860
+ * a single dead device would otherwise silence every other device watching the
144861
+ * same session.
144862
+ */
144863
+ async send(args) {
144864
+ const now = args.now ?? Date.now();
144865
+ return this.sendToTokens({
144866
+ tokens: this.repo.listForSession("liveactivity_update", args.sessionId, now),
144867
+ sessionId: args.sessionId,
144868
+ event: args.event,
144869
+ contentState: args.contentState,
144870
+ now,
144871
+ priority: args.priority
144872
+ });
144873
+ }
144874
+ /**
144875
+ * Push to an explicit token list.
144876
+ *
144877
+ * Renewal needs this: a replacement activity does not exist yet, so it is
144878
+ * started via the app-wide push-to-start token rather than any per-session
144879
+ * lookup. Shares one fan-out body with `send()` so failure handling cannot
144880
+ * drift between the two paths.
144881
+ */
144882
+ async sendToTokens(args) {
144883
+ const now = args.now ?? Date.now();
144884
+ const tokens = args.tokens;
144885
+ const outcome = {
144886
+ attempted: tokens.length,
144887
+ succeeded: 0,
144888
+ retired: 0
144889
+ };
144890
+ if (tokens.length === 0) return outcome;
144891
+ const results = await Promise.all(
144892
+ tokens.map(
144893
+ (row) => this.sendToToken(row, args.event, args.contentState, now, args.priority, args.staleDate)
144894
+ )
144895
+ );
144896
+ for (const { row, result, error: error51 } of results) {
144897
+ if (error51) {
144898
+ log5.error("live_activity.send_failed", {
144899
+ event: "live_activity.send_failed",
144900
+ sessionId: args.sessionId,
144901
+ activityId: row.activity_id,
144902
+ apnsEvent: args.event,
144903
+ err: String(error51)
144904
+ });
144905
+ this.repo.recordFailure(row.token, "SendError", now);
144906
+ continue;
144907
+ }
144908
+ if (!result) continue;
144909
+ if (result.ok) {
144910
+ this.repo.recordSuccess(row.token, now);
144911
+ outcome.succeeded += 1;
144912
+ continue;
144913
+ }
144914
+ this.repo.recordFailure(row.token, result.reason ?? `HTTP_${result.status}`, now);
144915
+ if (result.tokenDead) {
144916
+ this.repo.expire(row.token, now);
144917
+ outcome.retired += 1;
144918
+ }
144919
+ log5.warn("live_activity.send_rejected", {
144920
+ event: "live_activity.send_rejected",
144921
+ sessionId: args.sessionId,
144922
+ activityId: row.activity_id,
144923
+ apnsEvent: args.event,
144924
+ status: result.status,
144925
+ reason: result.reason,
144926
+ tokenDead: result.tokenDead
144927
+ });
144928
+ }
144929
+ return outcome;
144930
+ }
144931
+ /**
144932
+ * End every live activity for a session and stop tracking them.
144933
+ *
144934
+ * Expiring locally is what stops the renewal sweep from later resurrecting an
144935
+ * activity for a session that has already finished.
144936
+ */
144937
+ async end(args) {
144938
+ const now = args.now ?? Date.now();
144939
+ const outcome = await this.send({
144940
+ sessionId: args.sessionId,
144941
+ event: "end",
144942
+ contentState: args.contentState,
144943
+ now
144944
+ });
144945
+ this.repo.expireSessionActivities(args.sessionId, now);
144946
+ return outcome;
144947
+ }
144948
+ async sendToToken(row, event, contentState, now, priority, staleDateOverride) {
144949
+ const staleDate = event === "update" ? staleDateOverride ?? row.stale_date ?? contentState.startedAt + ACTIVITY_MAX_LIFETIME_MS : null;
144950
+ try {
144951
+ const result = await this.apns.send({
144952
+ deviceToken: row.token,
144953
+ payload: buildActivityKitPayload({ event, contentState, now, staleDate }),
144954
+ priority
144955
+ });
144956
+ return { row, result };
144957
+ } catch (error51) {
144958
+ return { row, error: error51 };
144959
+ }
144960
+ }
144961
+ };
144962
+
144963
+ // src/services/push/liveActivityRenewal.ts
144964
+ var log6 = getLogger("live-activity");
144965
+ var RENEWAL_LEAD_MS = 30 * 60 * 1e3;
144966
+ var MAX_TIMER_MS = 60 * 60 * 1e3;
144967
+ function renewalDueAt(row) {
144968
+ return row.stale_date == null ? null : row.stale_date - RENEWAL_LEAD_MS;
144969
+ }
144970
+ var LiveActivityRenewalScheduler = class {
144971
+ constructor(deps) {
144972
+ this.deps = deps;
144973
+ this.now = deps.now ?? (() => Date.now());
144974
+ }
144975
+ deps;
144976
+ timer = null;
144977
+ stopped = false;
144978
+ now;
144979
+ /**
144980
+ * Arm the scheduler from persisted state.
144981
+ *
144982
+ * Called on boot, which is what makes a renewal survive a restart: the
144983
+ * deadlines were never in memory to begin with.
144984
+ */
144985
+ start() {
144986
+ this.stopped = false;
144987
+ void this.tick();
144988
+ }
144989
+ stop() {
144990
+ this.stopped = true;
144991
+ if (this.timer) {
144992
+ clearTimeout(this.timer);
144993
+ this.timer = null;
144994
+ }
144995
+ }
144996
+ /**
144997
+ * Renew everything due, then sleep until the next deadline.
144998
+ *
144999
+ * Re-reads from the DB every tick rather than caching a schedule in memory, so
145000
+ * an activity registered after boot is picked up without re-arming anything.
145001
+ */
145002
+ async tick() {
145003
+ if (this.stopped) return;
145004
+ const now = this.now();
145005
+ try {
145006
+ for (const row of this.deps.repo.listRenewable()) {
145007
+ const dueAt = renewalDueAt(row);
145008
+ if (dueAt == null || dueAt > now) continue;
145009
+ await this.renew(row, now);
145010
+ }
145011
+ } catch (err) {
145012
+ log6.error("live_activity.renewal_sweep_failed", {
145013
+ event: "live_activity.renewal_sweep_failed",
145014
+ err: String(err)
145015
+ });
145016
+ }
145017
+ this.scheduleNext();
145018
+ }
145019
+ scheduleNext() {
145020
+ if (this.stopped) return;
145021
+ const now = this.now();
145022
+ const pending = this.deps.repo.listRenewable().map(renewalDueAt).filter((d) => d != null);
145023
+ const nextDue = pending.length > 0 ? Math.min(...pending) : now + MAX_TIMER_MS;
145024
+ const delay = Math.min(Math.max(nextDue - now, 0), MAX_TIMER_MS);
145025
+ this.timer = setTimeout(() => void this.tick(), delay);
145026
+ this.timer.unref?.();
145027
+ }
145028
+ /**
145029
+ * Renew one activity.
145030
+ *
145031
+ * Claims first: `claimRenewal()` succeeds exactly once per row, so a timer
145032
+ * re-armed after a restart mid-window cannot send a second time.
145033
+ */
145034
+ async renew(row, now) {
145035
+ if (!row.session_id) return;
145036
+ const session = this.deps.sessionStore.getManaged(row.session_id);
145037
+ const status = session ? toLiveActivityStatus(session.status) : null;
145038
+ if (!session || !status) {
145039
+ this.deps.repo.claimRenewal(row.token, now);
145040
+ this.deps.repo.expire(row.token, now);
145041
+ log6.info("live_activity.renewal_skipped", {
145042
+ event: "live_activity.renewal_skipped",
145043
+ sessionId: row.session_id,
145044
+ activityId: row.activity_id,
145045
+ reason: session ? `status_${session.status}` : "session_gone"
145046
+ });
145047
+ return;
145048
+ }
145049
+ if (!this.deps.repo.claimRenewal(row.token, now)) {
145050
+ return;
145051
+ }
145052
+ const startedAt = row.started_at ?? session.startedAt.getTime();
145053
+ const contentState = {
145054
+ sessionId: session.id,
145055
+ serverId: this.deps.serverId,
145056
+ projectName: session.projectName,
145057
+ status,
145058
+ startedAt,
145059
+ lastOutput: session.lastOutput ?? "",
145060
+ ...this.deps.serverLabel != null && { serverLabel: this.deps.serverLabel }
145061
+ };
145062
+ try {
145063
+ await this.deps.sender.send({
145064
+ sessionId: session.id,
145065
+ event: "end",
145066
+ contentState: { ...contentState, lastOutput: truncateLastOutput(contentState.lastOutput) },
145067
+ now
145068
+ });
145069
+ this.deps.repo.expire(row.token, now);
145070
+ const started = await this.startReplacement({
145071
+ sessionId: session.id,
145072
+ startedAt,
145073
+ now
145074
+ });
145075
+ log6.info("live_activity.renewed", {
145076
+ event: "live_activity.renewed",
145077
+ sessionId: session.id,
145078
+ activityId: row.activity_id,
145079
+ // Logged because a regression here is invisible on the server and only
145080
+ // shows up as a reset timer on someone's Lock Screen.
145081
+ startedAt,
145082
+ replacementRequested: started
145083
+ });
145084
+ } catch (err) {
145085
+ log6.error("live_activity.renewal_failed", {
145086
+ event: "live_activity.renewal_failed",
145087
+ sessionId: session.id,
145088
+ activityId: row.activity_id,
145089
+ err: String(err)
145090
+ });
145091
+ }
145092
+ }
145093
+ /**
145094
+ * Ask the device to start a replacement activity.
145095
+ *
145096
+ * Uses the app-wide push-to-start token, because the replacement does not
145097
+ * exist yet and therefore has no per-activity token. Returns false when the
145098
+ * device never registered one, which is not an error: the app simply cannot be
145099
+ * asked to start an activity remotely, and the next foreground WS update
145100
+ * recreates it.
145101
+ */
145102
+ async startReplacement(args) {
145103
+ const starters = this.deps.repo.listByKind("liveactivity_start", args.now);
145104
+ if (starters.length === 0) return false;
145105
+ const session = this.deps.sessionStore.getManaged(args.sessionId);
145106
+ const status = session ? toLiveActivityStatus(session.status) : null;
145107
+ if (!session || !status) return false;
145108
+ await this.deps.sender.sendToTokens({
145109
+ tokens: starters,
145110
+ event: "update",
145111
+ sessionId: args.sessionId,
145112
+ contentState: {
145113
+ sessionId: session.id,
145114
+ serverId: this.deps.serverId,
145115
+ projectName: session.projectName,
145116
+ status,
145117
+ // Carried through unchanged — the whole point of the renewal.
145118
+ startedAt: args.startedAt,
145119
+ lastOutput: truncateLastOutput(session.lastOutput ?? ""),
145120
+ ...this.deps.serverLabel != null && { serverLabel: this.deps.serverLabel }
145121
+ },
145122
+ now: args.now,
145123
+ staleDate: args.startedAt + ACTIVITY_MAX_LIFETIME_MS
145124
+ });
145125
+ return true;
145126
+ }
145127
+ };
145128
+
144267
145129
  // src/services/questions/parseStatusLine.ts
144268
145130
  var MODEL_RE = /(Opus|Sonnet|Haiku|Fable)\s+[\d.]+(?:\s*\([^)]*\))?/;
144269
145131
  var EFFORT_RE = /●\s*([A-Za-z]+)\s*·\s*\/effort/;
@@ -144972,13 +145834,13 @@ function hashPrefix(text) {
144972
145834
  }
144973
145835
 
144974
145836
  // src/utils/conversationEtag.ts
144975
- var import_node_crypto3 = require("crypto");
145837
+ var import_node_crypto4 = require("crypto");
144976
145838
  function computeConversationEtag({
144977
145839
  filePath,
144978
145840
  messageCount,
144979
145841
  timestamp: timestamp2
144980
145842
  }) {
144981
- const digest = (0, import_node_crypto3.createHash)("sha1").update(`${filePath}:${messageCount}:${timestamp2}`).digest("hex").slice(0, 16);
145843
+ const digest = (0, import_node_crypto4.createHash)("sha1").update(`${filePath}:${messageCount}:${timestamp2}`).digest("hex").slice(0, 16);
144982
145844
  return `"${digest}"`;
144983
145845
  }
144984
145846
 
@@ -145067,6 +145929,24 @@ var WSHub = class {
145067
145929
  this.clients.delete(client);
145068
145930
  }
145069
145931
  }
145932
+ // Scoped broadcast for high-frequency per-session messages (terminal_output,
145933
+ // user_message). Sending to every connected client for every PTY output
145934
+ // chunk made broadcast() cost scale with connections x active sessions;
145935
+ // this bounds it to only that session's subscribers.
145936
+ broadcastToClients(clients, message) {
145937
+ const data = JSON.stringify(message);
145938
+ for (const client of clients) {
145939
+ try {
145940
+ if (client.readyState === client.OPEN) {
145941
+ client.send(data);
145942
+ } else {
145943
+ this.clients.delete(client);
145944
+ }
145945
+ } catch {
145946
+ this.clients.delete(client);
145947
+ }
145948
+ }
145949
+ }
145070
145950
  unicast(ws2, message) {
145071
145951
  try {
145072
145952
  if (ws2.readyState === ws2.OPEN) {
@@ -145129,6 +146009,7 @@ var ADOPT_KILL_TIMEOUT_MS = 5e3;
145129
146009
  var ADOPT_KILL_POLL_MS = 100;
145130
146010
  var REFRESH_TTL_MS = 2e3;
145131
146011
  var START_READY_TIMEOUT_MS = 1e4;
146012
+ var MODEL_NAME_RE = /^[A-Za-z0-9][A-Za-z0-9._-]{0,63}$/;
145132
146013
  var EXTERNAL_TAIL_RECENCY_MS = RESUME_BUSY_WINDOW_MS;
145133
146014
  var EXTERNAL_TAIL_MAX = 32;
145134
146015
  var EXTERNAL_TAIL_IDLE_MS = 3e5;
@@ -145232,6 +146113,8 @@ var StreamerServer = class {
145232
146113
  dbPool = null;
145233
146114
  dbInstanceId = null;
145234
146115
  disableDb = false;
146116
+ // Skip the startup warm-up scan (test hook; see ServerConfig.skipStartupWarmup).
146117
+ skipStartupWarmup;
145235
146118
  browseRoot = null;
145236
146119
  publicUrl = null;
145237
146120
  browserCors;
@@ -145241,6 +146124,12 @@ var StreamerServer = class {
145241
146124
  sessionInputAttempts = /* @__PURE__ */ new Map();
145242
146125
  ptyGracePeriodMs;
145243
146126
  defaultSystemPrompt;
146127
+ // Resolved once at boot; see src/feature-flags.ts. Total map — every registry
146128
+ // id is present, so indexing it never yields undefined.
146129
+ featureFlags;
146130
+ // Derived from featureFlags.codexSystemPrompt. Kept as its own field so the
146131
+ // read site in startFresh() is unchanged.
146132
+ codexSystemPromptEnabled;
145244
146133
  defaultPermissionMode;
145245
146134
  defaultModel;
145246
146135
  defaultEffort;
@@ -145265,6 +146154,11 @@ var StreamerServer = class {
145265
146154
  // every provider; read only by the idle reaper. Entries are dropped when the
145266
146155
  // session leaves the runner (reap/exit/hold).
145267
146156
  lastAgentChunkAt = /* @__PURE__ */ new Map();
146157
+ // sessionId → last terminal_output seq broadcast (starts at 1, per session).
146158
+ // Stamped on every terminal_output/terminal_replay so a client can detect a
146159
+ // stale chunk delivered after a reconnect race instead of trusting raw WS
146160
+ // arrival order. Entries dropped alongside lastAgentChunkAt.
146161
+ terminalSeq = /* @__PURE__ */ new Map();
145268
146162
  // Recently accepted input idempotency keys (C4). A retried POST replays its
145269
146163
  // original outcome instead of submitting the prompt to the agent twice.
145270
146164
  idempotency = new IdempotencyStore();
@@ -145296,6 +146190,12 @@ var StreamerServer = class {
145296
146190
  // Paired-device registry (C5). Null when the cache DB failed to open — auth
145297
146191
  // then falls back to the shared API key alone, which is the pre-C5 behaviour.
145298
146192
  devicesRepo = null;
146193
+ // Live Activity push (Feature 12). Null when APNS_KEY is unset — the ordinary
146194
+ // case on a dev machine and in CI, where the feature is simply off. Missing an
146195
+ // optional push credential must never stop the server from booting.
146196
+ apnsClient = null;
146197
+ liveActivityNotifier = null;
146198
+ liveActivityRenewal = null;
145299
146199
  discoveryCache = null;
145300
146200
  cacheDir;
145301
146201
  tailSize;
@@ -145326,11 +146226,17 @@ var StreamerServer = class {
145326
146226
  }
145327
146227
  this.verbose = config2.verbose ?? false;
145328
146228
  this.disableDb = config2.disableDb ?? false;
146229
+ this.skipStartupWarmup = config2.skipStartupWarmup ?? false;
145329
146230
  this.scannerPersistenceDisabled = config2.scannerPersistent === false;
145330
146231
  this.scanProfiles = config2.scanProfiles;
145331
146232
  this.codexRoots = config2.codexRoots ?? [(0, import_path29.join)((0, import_os12.homedir)(), ".codex", "sessions")];
145332
146233
  this.ptyGracePeriodMs = config2.ptyGracePeriodMs ?? DEFAULT_PTY_GRACE_PERIOD_MS;
145333
146234
  this.defaultSystemPrompt = config2.defaultSystemPrompt ?? DEFAULT_SYSTEM_PROMPT;
146235
+ this.featureFlags = resolveFeatureFlags({ cli: config2.featureFlags, yaml: loadFeatureFlags() });
146236
+ if (config2.codexSystemPromptEnabled !== void 0) {
146237
+ this.featureFlags.codexSystemPrompt = config2.codexSystemPromptEnabled;
146238
+ }
146239
+ this.codexSystemPromptEnabled = this.featureFlags.codexSystemPrompt;
145334
146240
  this.defaultPermissionMode = config2.defaultPermissionMode ?? loadDefaultPermissionMode() ?? "acceptEdits";
145335
146241
  this.defaultModel = config2.defaultModel ?? "sonnet";
145336
146242
  this.defaultEffort = config2.defaultEffort ?? "low";
@@ -145346,6 +146252,13 @@ var StreamerServer = class {
145346
146252
  }, this.directoryDebounceMs);
145347
146253
  this.includeAgents = parseIncludeAgentsEnv(process.env.THREADBASE_INCLUDE_AGENTS);
145348
146254
  this.agentEntrypoints = parseAgentEntrypointsEnv(process.env.THREADBASE_AGENT_ENTRYPOINTS);
146255
+ const enabledFlags = nonDefaultFeatureFlags(this.featureFlags);
146256
+ if (enabledFlags.length > 0) {
146257
+ this.log.info(`Feature flags active: ${enabledFlags.join(", ")}`, {
146258
+ event: "config.feature_flags_active",
146259
+ flags: enabledFlags
146260
+ });
146261
+ }
145349
146262
  const rawRoot = process.env.THREADBASE_BROWSE_ROOT ?? loadBrowseRoot() ?? config2.browseRoot;
145350
146263
  if (rawRoot) {
145351
146264
  (0, import_promises16.realpath)(rawRoot).then((resolved) => {
@@ -145462,10 +146375,22 @@ var StreamerServer = class {
145462
146375
  logger: getLogger("pty"),
145463
146376
  onOutput: (sessionId, data) => {
145464
146377
  this.lastAgentChunkAt.set(sessionId, Date.now());
145465
- this.wsHub.broadcast({ type: "terminal_output", sessionId, data });
146378
+ const seq = (this.terminalSeq.get(sessionId) ?? 0) + 1;
146379
+ this.terminalSeq.set(sessionId, seq);
146380
+ this.wsHub.broadcastToClients(this.sessionSubscribers.get(sessionId) ?? [], {
146381
+ type: "terminal_output",
146382
+ sessionId,
146383
+ data,
146384
+ seq
146385
+ });
145466
146386
  },
145467
146387
  onUserMessage: (sessionId, text, ts2) => {
145468
- this.wsHub.broadcast({ type: "user_message", sessionId, text, ts: ts2 });
146388
+ this.wsHub.broadcastToClients(this.sessionSubscribers.get(sessionId) ?? [], {
146389
+ type: "user_message",
146390
+ sessionId,
146391
+ text,
146392
+ ts: ts2
146393
+ });
145469
146394
  },
145470
146395
  onPermissionChange: (sessionId, gate) => {
145471
146396
  this.handlePermissionChange(sessionId, gate);
@@ -145539,6 +146464,7 @@ var StreamerServer = class {
145539
146464
  if (resp) {
145540
146465
  this.wsHub.broadcast({ type: "session_update", session: resp });
145541
146466
  }
146467
+ void this.liveActivityNotifier?.onStatusChange(session);
145542
146468
  this.sessionStatusBus.emit(`status:${session.id}`, session.status);
145543
146469
  }
145544
146470
  });
@@ -145573,6 +146499,7 @@ var StreamerServer = class {
145573
146499
  logMenubarRequests: this.logMenubarRequests,
145574
146500
  rotateApiKey: () => this.rotateApiKey(),
145575
146501
  claudeFlagsConfig: () => this.getClaudeFlagsConfig(),
146502
+ featureFlagsConfig: () => this.getFeatureFlagsConfig(),
145576
146503
  setClaudeFlagsConfig: (values, extraArgs) => this.setClaudeFlagsConfig(values, extraArgs),
145577
146504
  publicUrl: this.publicUrl,
145578
146505
  browseRoot: this.browseRoot,
@@ -145600,6 +146527,8 @@ var StreamerServer = class {
145600
146527
  handleCancel: (id, res) => this.handleCancel(id, res),
145601
146528
  handleStopSession: (id, res) => this.handleStopSession(id, res),
145602
146529
  handleSetSessionName: (id, req, res) => this.handleSetSessionName(id, req, res),
146530
+ handleSetSessionModel: (id, req, res) => this.applyLiveSessionSetting(id, req, res, "model"),
146531
+ handleSetSessionEffort: (id, req, res) => this.applyLiveSessionSetting(id, req, res, "effort"),
145603
146532
  handleUploadFile: (id, req, res) => this.handleUploadFile(id, req, res),
145604
146533
  handleAdopt: (id, res) => this.handleAdopt(id, res),
145605
146534
  handleResume: (req, res) => this.handleResume(req, res),
@@ -145646,7 +146575,8 @@ var StreamerServer = class {
145646
146575
  type: "terminal_replay",
145647
146576
  sessionId: msg.sessionId,
145648
146577
  lines,
145649
- userMessages
146578
+ userMessages,
146579
+ seq: this.terminalSeq.get(msg.sessionId)
145650
146580
  })
145651
146581
  );
145652
146582
  }
@@ -145804,6 +146734,41 @@ var StreamerServer = class {
145804
146734
  }
145805
146735
  this.ptyGraceDeferCounts.delete(sessionId);
145806
146736
  }
146737
+ /**
146738
+ * Bring up Live Activity push, if credentials are present (Feature 12).
146739
+ *
146740
+ * APNS_KEY absent is the ordinary case on a dev machine and in CI, so this
146741
+ * logs once at info and leaves the feature off rather than failing: the server
146742
+ * must not refuse to boot over a missing optional push credential.
146743
+ *
146744
+ * The key is read from the environment as PEM contents and never from a path
146745
+ * on disk; neither it nor any device token is ever logged.
146746
+ */
146747
+ initLiveActivityPush(pushRepo) {
146748
+ const creds = readApnsCredentialsFromEnv();
146749
+ if (!creds) {
146750
+ const why = describeMissingApnsCredentials();
146751
+ if (why) this.log.info(why, { event: "live_activity.disabled" });
146752
+ return;
146753
+ }
146754
+ this.apnsClient = new ApnsClient(creds);
146755
+ const sender = new LiveActivitySender(this.apnsClient, pushRepo);
146756
+ const serverId = process.env.THREADBASE_INSTANCE_ID ?? (0, import_os12.hostname)();
146757
+ this.liveActivityNotifier = new LiveActivityNotifier(sender, serverId, (0, import_os12.hostname)());
146758
+ this.liveActivityRenewal = new LiveActivityRenewalScheduler({
146759
+ repo: pushRepo,
146760
+ sender,
146761
+ sessionStore: this.sessionStore,
146762
+ serverId,
146763
+ serverLabel: (0, import_os12.hostname)()
146764
+ });
146765
+ this.liveActivityRenewal.start();
146766
+ this.log.info("Live Activity push enabled", {
146767
+ event: "live_activity.enabled",
146768
+ host: creds.host,
146769
+ topic: `${creds.bundleId}.push-type.liveactivity`
146770
+ });
146771
+ }
145807
146772
  /**
145808
146773
  * Classify sessions left behind by previous streamer runs (C1 Phase 3a).
145809
146774
  *
@@ -145971,6 +146936,7 @@ var StreamerServer = class {
145971
146936
  );
145972
146937
  this.ptyManager.putOnHold(session.id);
145973
146938
  this.lastAgentChunkAt.delete(session.id);
146939
+ this.terminalSeq.delete(session.id);
145974
146940
  this.idempotency.clear(session.id);
145975
146941
  this.sessionSubscribers.delete(session.id);
145976
146942
  reaped.push(session.id);
@@ -146113,6 +147079,7 @@ var StreamerServer = class {
146113
147079
  this.cacheMetadataRepo = new CacheMetadataRepository(db);
146114
147080
  this.pushRepo = new PushRepository(db);
146115
147081
  this.devicesRepo = new DevicesRepository(db);
147082
+ this.initLiveActivityPush(this.pushRepo);
146116
147083
  this.cacheMonitor = new CacheIntegrityMonitor(
146117
147084
  this.cache,
146118
147085
  this.wsHub,
@@ -146144,6 +147111,14 @@ var StreamerServer = class {
146144
147111
  );
146145
147112
  this.scannerPersistenceDisabled = true;
146146
147113
  }
147114
+ if (this.skipStartupWarmup) {
147115
+ this.log.debug?.("startup warm-up scan skipped (skipStartupWarmup)", {
147116
+ event: "cache.warmup_skipped"
147117
+ });
147118
+ this.finishWarmup(0);
147119
+ resolveWarm();
147120
+ return;
147121
+ }
146147
147122
  const warmupStatCache = this.buildStatCache(null);
146148
147123
  const warmupScanner = this.newScanner(warmupStatCache ? { persistent: false } : void 0);
146149
147124
  this.allScanners.add(warmupScanner);
@@ -146335,6 +147310,7 @@ var StreamerServer = class {
146335
147310
  this.idleReaperTimer = null;
146336
147311
  }
146337
147312
  this.lastAgentChunkAt.clear();
147313
+ this.terminalSeq.clear();
146338
147314
  this.recordShutdownState();
146339
147315
  this.markScannerStaleDebounced.cancel();
146340
147316
  await Promise.all([...this.inFlightCacheWrites]);
@@ -146347,6 +147323,8 @@ var StreamerServer = class {
146347
147323
  this.externalTails.clear();
146348
147324
  this.wsHub.dispose();
146349
147325
  this.pairTokens.dispose();
147326
+ this.liveActivityRenewal?.stop();
147327
+ this.apnsClient?.close();
146350
147328
  if (this.dbPool) {
146351
147329
  await this.dbPool.end();
146352
147330
  }
@@ -146418,7 +147396,6 @@ var StreamerServer = class {
146418
147396
  json2(res, 400, { error: message });
146419
147397
  return;
146420
147398
  }
146421
- const { hostname: hostname4 } = require("os");
146422
147399
  const ts2 = (/* @__PURE__ */ new Date()).toISOString();
146423
147400
  this.log.info(`[pair] token exchanged from ${ip} at ${ts2}`, {
146424
147401
  event: "pair.token_exchanged",
@@ -146441,7 +147418,7 @@ var StreamerServer = class {
146441
147418
  nonce: sealed.nonce,
146442
147419
  ephemeralPublicKey: sealed.ephemeralPublicKey,
146443
147420
  publicUrl: this.publicUrl,
146444
- machineName: hostname4(),
147421
+ machineName: (0, import_os12.hostname)(),
146445
147422
  ...device && {
146446
147423
  deviceId: device.deviceId,
146447
147424
  deviceToken: device.deviceToken,
@@ -146463,6 +147440,16 @@ var StreamerServer = class {
146463
147440
  });
146464
147441
  return { newKey, persisted };
146465
147442
  }
147443
+ /**
147444
+ * The registry ships with the values so a client renders the list from one
147445
+ * round-trip, same as getClaudeFlagsConfig().
147446
+ *
147447
+ * Deliberately no `persisted` field: unlike claude-flags there is no PUT, and
147448
+ * the absence of that field is the signal that this endpoint is read-only.
147449
+ */
147450
+ getFeatureFlagsConfig() {
147451
+ return { registry: FEATURE_FLAGS, values: this.featureFlags };
147452
+ }
146466
147453
  getClaudeFlagsConfig() {
146467
147454
  return {
146468
147455
  registry: CLAUDE_FLAGS,
@@ -146505,6 +147492,30 @@ var StreamerServer = class {
146505
147492
  persisted: this.claudeFlagsPersistable
146506
147493
  };
146507
147494
  }
147495
+ /**
147496
+ * The three spawn options that a configured claude-flag can override, with
147497
+ * the boot-time CLI/yaml default as the fallback. Spread into every
147498
+ * start/resume/adopt call so all three paths agree.
147499
+ *
147500
+ * These ids are excluded from buildFlagArgs (SPAWN_POSITIONAL_FLAG_IDS)
147501
+ * precisely because they arrive here instead — the PTY spawn paths pass them
147502
+ * as explicit positionals, so emitting them from the allowlist too would
147503
+ * duplicate the flag.
147504
+ *
147505
+ * Narrowed with the type guards rather than cast: ClaudeFlagValues is a loose
147506
+ * Record by design, and while validateFlagValues already guarantees the shape
147507
+ * on the way in, TypeScript cannot see that through the record.
147508
+ */
147509
+ spawnFlagOverrides() {
147510
+ const mode = this.claudeFlags.permissionMode;
147511
+ const model = this.claudeFlags.model;
147512
+ const effort = this.claudeFlags.effort;
147513
+ return {
147514
+ permissionMode: isPermissionMode(mode) ? mode : this.defaultPermissionMode,
147515
+ model: typeof model === "string" ? model : this.defaultModel,
147516
+ effort: isEffortLevel(effort) ? effort : this.defaultEffort
147517
+ };
147518
+ }
146508
147519
  checkRateLimit(map2, key, limit, windowMs) {
146509
147520
  const now = Date.now();
146510
147521
  const arr = (map2.get(key) ?? []).filter((t) => now - t < windowMs);
@@ -147654,11 +148665,9 @@ var StreamerServer = class {
147654
148665
  projectPath,
147655
148666
  projectName: body.projectName,
147656
148667
  branch: body.branch,
147657
- permissionMode: this.defaultPermissionMode,
147658
148668
  claudeFlags: this.claudeFlags,
147659
148669
  claudeExtraArgs: this.claudeExtraArgs,
147660
- model: this.defaultModel,
147661
- effort: this.defaultEffort
148670
+ ...this.spawnFlagOverrides()
147662
148671
  });
147663
148672
  this.sessionStore.addManaged(session);
147664
148673
  this.recordSessionSpawn(session);
@@ -148105,11 +149114,9 @@ var StreamerServer = class {
148105
149114
  projectPath,
148106
149115
  projectName,
148107
149116
  branch,
148108
- permissionMode: this.defaultPermissionMode,
148109
149117
  claudeFlags: this.claudeFlags,
148110
149118
  claudeExtraArgs: this.claudeExtraArgs,
148111
- model: this.defaultModel,
148112
- effort: this.defaultEffort
149119
+ ...this.spawnFlagOverrides()
148113
149120
  });
148114
149121
  this.sessionStore.addManaged(session);
148115
149122
  this.recordSessionSpawn(session);
@@ -148175,17 +149182,16 @@ var StreamerServer = class {
148175
149182
  BROWSE_SYSTEM_PROMPT(this.browseRoot),
148176
149183
  typeof clientPrompt === "string" ? clientPrompt : null
148177
149184
  ].filter(Boolean);
149185
+ const includeSystemPrompt = provider !== CODEX_CLI_PROVIDER2 || this.codexSystemPromptEnabled;
148178
149186
  try {
148179
149187
  const session = await this.ptyManager.startFresh({
148180
149188
  provider,
148181
149189
  projectPath: resolvedPath,
148182
149190
  projectName: body.projectName,
148183
- systemPrompt: systemPromptParts.join("\n"),
148184
- permissionMode: this.defaultPermissionMode,
149191
+ ...includeSystemPrompt && { systemPrompt: systemPromptParts.join("\n") },
148185
149192
  claudeFlags: this.claudeFlags,
148186
149193
  claudeExtraArgs: this.claudeExtraArgs,
148187
- model: this.defaultModel,
148188
- effort: this.defaultEffort
149194
+ ...this.spawnFlagOverrides()
148189
149195
  });
148190
149196
  this.sessionStore.addManaged(session);
148191
149197
  this.recordSessionSpawn(session);
@@ -148545,6 +149551,87 @@ var StreamerServer = class {
148545
149551
  this.cache.upsertSessionName(sessionId, name);
148546
149552
  json2(res, 200, { ok: true });
148547
149553
  }
149554
+ /**
149555
+ * Retarget a LIVE session's model or effort by typing the corresponding
149556
+ * Claude Code slash command into its PTY.
149557
+ *
149558
+ * There is no CLI or IPC channel for this — `--model`/`--effort` are spawn
149559
+ * arguments — so the interactive `/model <x>` / `/effort <y>` commands are the
149560
+ * only way to change a session already running. Both accept an argument and
149561
+ * apply it without opening the picker (verified against Claude Code v2.1.220).
149562
+ *
149563
+ * Answers 202, not 200: the value is applied by the TUI on its next render, so
149564
+ * there is nothing truthful to echo back synchronously. Clients confirm with
149565
+ * `GET /api/sessions/:id`, which scrapes the applied value off the live status
149566
+ * line.
149567
+ */
149568
+ async applyLiveSessionSetting(sessionId, req, res, setting) {
149569
+ const session = this.ptyManager.getSession(sessionId);
149570
+ if (!session) {
149571
+ const known = this.sessionStore.getManaged(sessionId);
149572
+ if (known) {
149573
+ json2(res, 409, {
149574
+ error: "Session has no live PTY; resume it first",
149575
+ code: "SESSION_IDLE"
149576
+ });
149577
+ return;
149578
+ }
149579
+ json2(res, 404, { error: "Session not found" });
149580
+ return;
149581
+ }
149582
+ if ((session.provider ?? CLAUDE_CODE_PROVIDER2) !== CLAUDE_CODE_PROVIDER2) {
149583
+ json2(res, 501, {
149584
+ error: `Setting ${setting} on a ${session.provider} session is not supported`,
149585
+ code: "UNSUPPORTED_PROVIDER"
149586
+ });
149587
+ return;
149588
+ }
149589
+ if (session.status === "running") {
149590
+ json2(res, 409, {
149591
+ error: "Session is mid-turn; retry once it is waiting for input",
149592
+ code: "SESSION_BUSY"
149593
+ });
149594
+ return;
149595
+ }
149596
+ let parsed;
149597
+ try {
149598
+ parsed = await readBody(req);
149599
+ } catch {
149600
+ json2(res, 400, { error: "Invalid JSON" });
149601
+ return;
149602
+ }
149603
+ let value;
149604
+ if (setting === "effort") {
149605
+ if (!isEffortLevel(parsed.effort)) {
149606
+ json2(res, 400, {
149607
+ error: `effort must be one of ${EFFORT_LEVELS.join(", ")}`
149608
+ });
149609
+ return;
149610
+ }
149611
+ value = parsed.effort;
149612
+ } else {
149613
+ if (typeof parsed.model !== "string" || !MODEL_NAME_RE.test(parsed.model)) {
149614
+ json2(res, 400, {
149615
+ error: "model must be an alias or full model name (letters, digits, dot, dash, underscore)"
149616
+ });
149617
+ return;
149618
+ }
149619
+ value = parsed.model;
149620
+ }
149621
+ try {
149622
+ this.ptyManager.sendKeys(sessionId, `/${setting} ${value}\r`);
149623
+ } catch (err) {
149624
+ json2(res, 400, { error: err instanceof Error ? err.message : "Failed to write to session" });
149625
+ return;
149626
+ }
149627
+ this.log.info(`Live session ${setting} set to ${value}`, {
149628
+ event: "session.setting_applied",
149629
+ sessionId,
149630
+ setting,
149631
+ value
149632
+ });
149633
+ json2(res, 202, { id: sessionId, [setting]: value });
149634
+ }
148548
149635
  handleGetSessionNames(res) {
148549
149636
  if (!this.cache) {
148550
149637
  json2(res, 200, {});
@@ -148886,7 +149973,7 @@ function isBrewInstall(scriptPath = process.argv[1] ?? "") {
148886
149973
  }
148887
149974
 
148888
149975
  // src/updater/download.ts
148889
- var import_node_crypto4 = require("crypto");
149976
+ var import_node_crypto5 = require("crypto");
148890
149977
  var import_node_fs9 = require("fs");
148891
149978
  var import_node_path10 = require("path");
148892
149979
  var import_promises17 = require("stream/promises");
@@ -148953,7 +150040,7 @@ async function downloadAndVerify(opts) {
148953
150040
  if (!res.ok || !res.body) {
148954
150041
  throw new Error(`Failed to download ${artifact.filename}: ${res.status} ${res.statusText}`);
148955
150042
  }
148956
- const hash2 = (0, import_node_crypto4.createHash)("sha256");
150043
+ const hash2 = (0, import_node_crypto5.createHash)("sha256");
148957
150044
  const out = (0, import_node_fs9.createWriteStream)(targetPath);
148958
150045
  let bytes = 0;
148959
150046
  const measured = new TransformStream({
@@ -149258,7 +150345,7 @@ var import_node_path17 = require("path");
149258
150345
  var import_path32 = __toESM(require("path"), 1);
149259
150346
  var import_node_fs13 = __toESM(require("fs"), 1);
149260
150347
  var import_node_assert = __toESM(require("assert"), 1);
149261
- var import_node_crypto5 = require("crypto");
150348
+ var import_node_crypto6 = require("crypto");
149262
150349
  var import_node_fs14 = __toESM(require("fs"), 1);
149263
150350
  var import_node_path18 = __toESM(require("path"), 1);
149264
150351
  var import_fs33 = __toESM(require("fs"), 1);
@@ -151666,7 +152753,7 @@ var Te = fo === "win32";
151666
152753
  var mo = 1024;
151667
152754
  var uo = (s3, t) => {
151668
152755
  if (!Te) return import_node_fs14.default.unlink(s3, t);
151669
- let e = s3 + ".DELETE." + (0, import_node_crypto5.randomBytes)(16).toString("hex");
152756
+ let e = s3 + ".DELETE." + (0, import_node_crypto6.randomBytes)(16).toString("hex");
151670
152757
  import_node_fs14.default.rename(s3, e, (i) => {
151671
152758
  if (i) return t(i);
151672
152759
  import_node_fs14.default.unlink(e, t);
@@ -151674,7 +152761,7 @@ var uo = (s3, t) => {
151674
152761
  };
151675
152762
  var po = (s3) => {
151676
152763
  if (!Te) return import_node_fs14.default.unlinkSync(s3);
151677
- let t = s3 + ".DELETE." + (0, import_node_crypto5.randomBytes)(16).toString("hex");
152764
+ let t = s3 + ".DELETE." + (0, import_node_crypto6.randomBytes)(16).toString("hex");
151678
152765
  import_node_fs14.default.renameSync(s3, t), import_node_fs14.default.unlinkSync(t);
151679
152766
  };
151680
152767
  var vr = (s3, t, e) => s3 !== void 0 && s3 === s3 >>> 0 ? s3 : t !== void 0 && t === t >>> 0 ? t : e;
@@ -152331,7 +153418,7 @@ init_launchd();
152331
153418
  init_marker();
152332
153419
  init_platform();
152333
153420
  init_logger();
152334
- var log4 = getLogger("prod");
153421
+ var log8 = getLogger("prod");
152335
153422
  function clearSupervisorLogs() {
152336
153423
  let paths;
152337
153424
  try {
@@ -152488,12 +153575,12 @@ function registerProdCommands(program3) {
152488
153575
  prod.command("start").description("Restore prod after a user-held suspension").option("--clear-logs", "Clear existing stdout + stderr logs", false).action(async (opts) => {
152489
153576
  if (opts.clearLogs === true) clearSupervisorLogs();
152490
153577
  const r = await runProdStart();
152491
- log4.info(r.message, void 0, "console");
153578
+ log8.info(r.message, void 0, "console");
152492
153579
  if (!r.ok) process.exitCode = 1;
152493
153580
  });
152494
153581
  prod.command("stop").description("Unload the launchd agent (prod will not auto-restart)").action(async () => {
152495
153582
  const r = await runProdStop();
152496
- log4.info(r.message, void 0, "console");
153583
+ log8.info(r.message, void 0, "console");
152497
153584
  });
152498
153585
  prod.command("status").description("Report whether prod is supervised, suspended, or down").action(async () => {
152499
153586
  const s3 = await runProdStatus();
@@ -152502,7 +153589,7 @@ function registerProdCommands(program3) {
152502
153589
  `pid: ${s3.agentPid ?? "(none)"}`,
152503
153590
  s3.marker ? `marker: ${s3.marker.userHeld ? "userHeld (intentional stop)" : "dev-suspended"}, devPid=${s3.marker.devPid}, port=${s3.marker.port}, repo=${s3.marker.repoToplevel}` : "marker: none"
152504
153591
  ];
152505
- log4.info(parts.join("\n "), void 0, "console");
153592
+ log8.info(parts.join("\n "), void 0, "console");
152506
153593
  });
152507
153594
  prod.command("restart").description("Stop + restart the supervised streamer (re-reads service definition)").option("--clear-logs", "Clear existing stdout + stderr logs", false).action(async (opts) => {
152508
153595
  const sup = getSupervisor();
@@ -152511,17 +153598,17 @@ function registerProdCommands(program3) {
152511
153598
  sup.bootoutAgent();
152512
153599
  sup.bootstrapAgent(specPath, { afterBootout: true });
152513
153600
  const what = process.platform === "darwin" ? `agent restarted from ${specPath}` : `task '${TASK_NAME}' restarted`;
152514
- log4.info(what, void 0, "console");
153601
+ log8.info(what, void 0, "console");
152515
153602
  });
152516
153603
  prod.command("doctor").description("Detect & repair stale markers, missing agent, plist drift").option("--fix", "Apply repairs (default is dry-run)", false).action(async (opts) => {
152517
153604
  const r = await runProdDoctor({ fix: opts.fix === true });
152518
- log4.info(`findings: ${r.findings.length === 0 ? "(none)" : ""}`, void 0, "console");
152519
- for (const f2 of r.findings) log4.info(` - ${f2}`, void 0, "console");
153605
+ log8.info(`findings: ${r.findings.length === 0 ? "(none)" : ""}`, void 0, "console");
153606
+ for (const f2 of r.findings) log8.info(` - ${f2}`, void 0, "console");
152520
153607
  if (r.repairs.length) {
152521
- log4.info(`repairs:`, void 0, "console");
152522
- for (const fix of r.repairs) log4.info(` - ${fix}`, void 0, "console");
153608
+ log8.info(`repairs:`, void 0, "console");
153609
+ for (const fix of r.repairs) log8.info(` - ${fix}`, void 0, "console");
152523
153610
  } else if (!opts.fix && r.findings.length > 0) {
152524
- log4.info(`(re-run with --fix to apply repairs)`, void 0, "console");
153611
+ log8.info(`(re-run with --fix to apply repairs)`, void 0, "console");
152525
153612
  }
152526
153613
  });
152527
153614
  prod.command("logs").description("Tail the supervised streamer's stdout + stderr log files").option("-n, --lines <count>", "Seed with the last N lines (default 50)", "50").option("--no-follow", "Print last N lines and exit (do not follow)").option("--errors-only", "Tail only stderr", false).option("--clear", "Truncate stdout + stderr logs in place, then exit", false).action(async (opts) => {
@@ -152532,14 +153619,14 @@ function registerProdCommands(program3) {
152532
153619
  errorsOnly: opts.errorsOnly === true,
152533
153620
  clear: opts.clear === true
152534
153621
  });
152535
- if (r.message) log4.info(r.message, void 0, "console");
153622
+ if (r.message) log8.info(r.message, void 0, "console");
152536
153623
  if (!r.ok) process.exitCode = 1;
152537
153624
  });
152538
153625
  program3.addCommand(prod);
152539
153626
  }
152540
153627
 
152541
153628
  // cli/index.ts
152542
- var log7 = getLogger("cli");
153629
+ var log11 = getLogger("cli");
152543
153630
  var program2 = new Command();
152544
153631
  program2.name("threadbase-streamer").description("PTY session management, WebSocket streaming, and REST API server for Claude Code").version(getVersion());
152545
153632
  program2.command("serve").description("Start the streamer server").option("-p, --port <number>", "Port to listen on", "8766").option("--api-key <key>", "API key for authentication").option("--local-no-auth", "Skip auth for localhost requests", false).option("-v, --verbose", "Verbose output", false).option("--log-menubar-requests", "Log /healthz requests from the menubar app", false).option("--browse-root <path>", "Root directory for file browsing").option(
@@ -152561,6 +153648,10 @@ program2.command("serve").description("Start the streamer server").option("-p, -
152561
153648
  "--claude-flag <id=value>",
152562
153649
  "Allowlisted Claude CLI flag applied to every spawned session, e.g. --claude-flag permissionMode=bypassPermissions. Repeatable; repeat the same id to build a list (--claude-flag addDir=/a --claude-flag addDir=/b). Overrides claude_flags: in ~/.threadbase/server.yaml and makes the value non-persistable.",
152563
153650
  (value, previous = []) => [...previous, value]
153651
+ ).option(
153652
+ "--feature <id=bool>",
153653
+ "Enable or disable a server feature flag, e.g. --feature codexSystemPrompt=true. Repeatable. Overridden by the flag's THREADBASE_FEATURE_* env var; overrides feature_flags: in ~/.threadbase/server.yaml.",
153654
+ (value, previous = []) => [...previous, value]
152564
153655
  ).option(
152565
153656
  "--claude-extra-args <args>",
152566
153657
  "Free-text argv appended verbatim to every spawned Claude session, after the allowlisted flags. Unvalidated escape hatch."
@@ -152577,14 +153668,14 @@ program2.command("serve").description("Start the streamer server").option("-p, -
152577
153668
  const { checkSqliteAbi: checkSqliteAbi2 } = await Promise.resolve().then(() => (init_check_sqlite_abi(), check_sqlite_abi_exports));
152578
153669
  checkSqliteAbi2();
152579
153670
  } catch (err) {
152580
- log7.error(err instanceof Error ? err.message : String(err), void 0, "console");
153671
+ log11.error(err instanceof Error ? err.message : String(err), void 0, "console");
152581
153672
  process.exit(1);
152582
153673
  }
152583
153674
  if (opts.multiAgentFlow) {
152584
153675
  process.env.MULTI_AGENT_FLOW = "true";
152585
153676
  }
152586
153677
  if (opts.defaultPermissionMode !== void 0 && !isPermissionMode(opts.defaultPermissionMode)) {
152587
- log7.error(
153678
+ log11.error(
152588
153679
  `Invalid --default-permission-mode: ${opts.defaultPermissionMode} (expected one of ${PERMISSION_MODES.join(", ")})`,
152589
153680
  void 0,
152590
153681
  "console"
@@ -152600,7 +153691,7 @@ program2.command("serve").description("Start the streamer server").option("-p, -
152600
153691
  const value = eq === -1 ? true : entry.slice(eq + 1);
152601
153692
  const def = findFlag(id);
152602
153693
  if (!def) {
152603
- log7.error(
153694
+ log11.error(
152604
153695
  `Invalid --claude-flag id: ${id} (expected one of ${CLAUDE_FLAGS.map((f2) => f2.id).join(", ")})`,
152605
153696
  void 0,
152606
153697
  "console"
@@ -152617,15 +153708,23 @@ program2.command("serve").description("Start the streamer server").option("-p, -
152617
153708
  claudeFlags = validateFlagValues(raw2);
152618
153709
  for (const id of Object.keys(raw2)) {
152619
153710
  if (!(id in claudeFlags)) {
152620
- log7.error(`Invalid value for --claude-flag ${id}`, void 0, "console");
153711
+ log11.error(`Invalid value for --claude-flag ${id}`, void 0, "console");
152621
153712
  process.exit(1);
152622
153713
  }
152623
153714
  }
152624
153715
  }
152625
- const validEfforts = ["low", "medium", "high", "xhigh", "max"];
152626
- if (opts.defaultEffort !== void 0 && !validEfforts.includes(opts.defaultEffort)) {
152627
- log7.error(
152628
- `Invalid --default-effort: ${opts.defaultEffort} (expected one of ${validEfforts.join(", ")})`,
153716
+ let featureFlags;
153717
+ if (Array.isArray(opts.feature) && opts.feature.length > 0) {
153718
+ const parsed = parseFeatureFlagArgs(opts.feature);
153719
+ if (parsed.errors.length > 0) {
153720
+ for (const error51 of parsed.errors) log11.error(`--feature: ${error51}`, void 0, "console");
153721
+ process.exit(1);
153722
+ }
153723
+ featureFlags = parsed.values;
153724
+ }
153725
+ if (opts.defaultEffort !== void 0 && !isEffortLevel(opts.defaultEffort)) {
153726
+ log11.error(
153727
+ `Invalid --default-effort: ${opts.defaultEffort} (expected one of ${EFFORT_LEVELS.join(", ")})`,
152629
153728
  void 0,
152630
153729
  "console"
152631
153730
  );
@@ -152635,7 +153734,7 @@ program2.command("serve").description("Start the streamer server").option("-p, -
152635
153734
  if (opts.ptyGracePeriodMs !== void 0) {
152636
153735
  const parsed = Number(opts.ptyGracePeriodMs);
152637
153736
  if (!Number.isInteger(parsed) || parsed < 0) {
152638
- log7.error(
153737
+ log11.error(
152639
153738
  `Invalid --pty-grace-period-ms: ${opts.ptyGracePeriodMs} (expected a non-negative integer; 0 disables auto-hold)`,
152640
153739
  void 0,
152641
153740
  "console"
@@ -152655,7 +153754,7 @@ program2.command("serve").description("Start the streamer server").option("-p, -
152655
153754
  const { detectConflictingAgents: detectConflictingAgents2, formatConflictMessage: formatConflictMessage2 } = await Promise.resolve().then(() => (init_conflict_check(), conflict_check_exports));
152656
153755
  const conflicts = detectConflictingAgents2();
152657
153756
  if (conflicts.length > 0) {
152658
- log7.warn(formatConflictMessage2(conflicts), void 0, "console");
153757
+ log11.warn(formatConflictMessage2(conflicts), void 0, "console");
152659
153758
  }
152660
153759
  }
152661
153760
  let resolvedDefaultPermissionMode = opts.defaultPermissionMode;
@@ -152701,20 +153800,21 @@ program2.command("serve").description("Start the streamer server").option("-p, -
152701
153800
  defaultEffort: opts.defaultEffort,
152702
153801
  ptyGracePeriodMs,
152703
153802
  claudeFlags,
153803
+ featureFlags,
152704
153804
  claudeExtraArgs: opts.claudeExtraArgs
152705
153805
  });
152706
153806
  await server.listen(resolvedPort);
152707
153807
  {
152708
153808
  const v2 = getVersion();
152709
- log7.info(`Threadbase Streamer v${v2}`, { version: v2, port: resolvedPort });
153809
+ log11.info(`Threadbase Streamer v${v2}`, { version: v2, port: resolvedPort });
152710
153810
  }
152711
- log7.info(`Listening on http://localhost:${resolvedPort}`, {
153811
+ log11.info(`Listening on http://localhost:${resolvedPort}`, {
152712
153812
  url: `http://localhost:${resolvedPort}`
152713
153813
  });
152714
- log7.info(`WebSocket at ws://localhost:${resolvedPort}/ws`, {
153814
+ log11.info(`WebSocket at ws://localhost:${resolvedPort}/ws`, {
152715
153815
  wsUrl: `ws://localhost:${resolvedPort}/ws`
152716
153816
  });
152717
- log7.info(`API key: ${apiKey}`, { apiKeyMasked: `${apiKey.slice(0, 6)}\u2026` });
153817
+ log11.info(`API key: ${apiKey}`, { apiKeyMasked: `${apiKey.slice(0, 6)}\u2026` });
152718
153818
  try {
152719
153819
  await printServerBanner({
152720
153820
  port: resolvedPort,
@@ -152724,15 +153824,15 @@ program2.command("serve").description("Start the streamer server").option("-p, -
152724
153824
  });
152725
153825
  } catch (err) {
152726
153826
  const message = err instanceof Error ? err.message : String(err);
152727
- log7.warn(`(skipped pairing QR: ${message})`, { reason: message });
152728
- log7.info(
153827
+ log11.warn(`(skipped pairing QR: ${message})`, { reason: message });
153828
+ log11.info(
152729
153829
  printUrlBanner({ url: resolveServerUrl({ publicUrl, port: resolvedPort }) }),
152730
153830
  void 0,
152731
153831
  "console"
152732
153832
  );
152733
153833
  }
152734
153834
  const shutdown = async () => {
152735
- log7.info("Shutting down...");
153835
+ log11.info("Shutting down...");
152736
153836
  await server.close();
152737
153837
  process.exit(0);
152738
153838
  };
@@ -152740,7 +153840,7 @@ program2.command("serve").description("Start the streamer server").option("-p, -
152740
153840
  process.on("SIGINT", shutdown);
152741
153841
  process.on("SIGTERM", shutdown);
152742
153842
  process.on("uncaughtException", (err) => {
152743
- log7.error(`uncaught: ${err.message}`, {
153843
+ log11.error(`uncaught: ${err.message}`, {
152744
153844
  error: err.message,
152745
153845
  stack: err.stack,
152746
153846
  event: "process.uncaught"
@@ -152749,7 +153849,7 @@ program2.command("serve").description("Start the streamer server").option("-p, -
152749
153849
  });
152750
153850
  process.on("unhandledRejection", (reason) => {
152751
153851
  const msg = reason instanceof Error ? reason.message : String(reason);
152752
- log7.error(`unhandled rejection: ${msg}`, {
153852
+ log11.error(`unhandled rejection: ${msg}`, {
152753
153853
  error: msg,
152754
153854
  event: "process.unhandled_rejection"
152755
153855
  });
@@ -152774,10 +153874,10 @@ program2.command("cache").description("Manage the local SQLite conversation cach
152774
153874
  const f2 = dbPath + suffix;
152775
153875
  if (existsSync15(f2)) {
152776
153876
  rmSync6(f2);
152777
- log7.info(`Deleted ${f2}`, { path: f2 }, "console");
153877
+ log11.info(`Deleted ${f2}`, { path: f2 }, "console");
152778
153878
  }
152779
153879
  }
152780
- log7.info("Cache cleared. Restart the server to rebuild.", void 0, "console");
153880
+ log11.info("Cache cleared. Restart the server to rebuild.", void 0, "console");
152781
153881
  })
152782
153882
  );
152783
153883
  program2.command("pair").description("Print a pairing QR code (server must already be running)").option("-p, --port <number>", "Port the server is listening on", "8766").action(async (opts) => {
@@ -152802,7 +153902,7 @@ program2.command("set-key [key]").description("Set the streamer API key in ~/.th
152802
153902
  program2.command("update").description("Check for streamer updates from GitHub Releases and install them").option("--check", "Check only; do not install", false).option("--version <version>", "Pin to a specific release tag").option("--allow-major", "Allow a major-version bump", false).option("--force", "Skip the active-session defer check", false).option("--dry-run", "Print what would be installed without writing to disk", false).option("-p, --port <number>", "Port of the running streamer for active-session check", "8766").action(async (opts) => {
152803
153903
  const cfg = loadUpdateConfig();
152804
153904
  if (!cfg) {
152805
- log7.warn(
153905
+ log11.warn(
152806
153906
  `No update config found at ${DEFAULT_CONFIG_PATH}. Create one with at least 'github_repo: owner/name' to enable updates.`,
152807
153907
  void 0,
152808
153908
  "console"
@@ -152821,11 +153921,11 @@ program2.command("update").description("Check for streamer updates from GitHub R
152821
153921
  appendUpdateLog(
152822
153922
  `[check] current=${result2.current} latest=${result2.latest ?? "none"} status=${result2.reason}`
152823
153923
  );
152824
- log7.info(`Current : ${result2.current}`, void 0, "console");
152825
- log7.info(`Latest : ${result2.latest ?? "(none)"}`, void 0, "console");
152826
- log7.info(`Channel : ${cfg.channel}`, void 0, "console");
152827
- log7.info(`Diff : ${result2.diff ?? "(none)"}`, void 0, "console");
152828
- log7.info(`Status : ${result2.reason}`, void 0, "console");
153924
+ log11.info(`Current : ${result2.current}`, void 0, "console");
153925
+ log11.info(`Latest : ${result2.latest ?? "(none)"}`, void 0, "console");
153926
+ log11.info(`Channel : ${cfg.channel}`, void 0, "console");
153927
+ log11.info(`Diff : ${result2.diff ?? "(none)"}`, void 0, "console");
153928
+ log11.info(`Status : ${result2.reason}`, void 0, "console");
152829
153929
  return;
152830
153930
  }
152831
153931
  const port = Number.parseInt(opts.port, 10);
@@ -152841,20 +153941,20 @@ program2.command("update").description("Check for streamer updates from GitHub R
152841
153941
  });
152842
153942
  switch (result.kind) {
152843
153943
  case "no-op":
152844
- log7.info(`Current : ${result.current}`, void 0, "console");
152845
- log7.info(`Latest : ${result.latest ?? "(none)"}`, void 0, "console");
152846
- log7.info(`Status : ${result.reason}`, void 0, "console");
153944
+ log11.info(`Current : ${result.current}`, void 0, "console");
153945
+ log11.info(`Latest : ${result.latest ?? "(none)"}`, void 0, "console");
153946
+ log11.info(`Status : ${result.reason}`, void 0, "console");
152847
153947
  break;
152848
153948
  case "unsupported-install":
152849
- log7.warn(result.reason, void 0, "console");
153949
+ log11.warn(result.reason, void 0, "console");
152850
153950
  process.exitCode = 2;
152851
153951
  break;
152852
153952
  case "deferred":
152853
- log7.warn(`Deferred: ${result.reason}`, void 0, "console");
153953
+ log11.warn(`Deferred: ${result.reason}`, void 0, "console");
152854
153954
  process.exitCode = 2;
152855
153955
  break;
152856
153956
  case "dry-run":
152857
- log7.info(
153957
+ log11.info(
152858
153958
  `Would install ${result.latest} from ${result.tarballUrl}`,
152859
153959
  void 0,
152860
153960
  "console"
@@ -152862,28 +153962,28 @@ program2.command("update").description("Check for streamer updates from GitHub R
152862
153962
  break;
152863
153963
  case "installed":
152864
153964
  if (result.restart.method.startsWith("failed:")) {
152865
- log7.error(
153965
+ log11.error(
152866
153966
  `Installed ${result.installed} on disk, but the running service was not updated. Restart: ${result.restart.method}.`,
152867
153967
  void 0,
152868
153968
  "console"
152869
153969
  );
152870
153970
  process.exitCode = 1;
152871
153971
  } else {
152872
- log7.info(
153972
+ log11.info(
152873
153973
  `Installed ${result.installed} (was ${result.previous}). Restart: ${result.restart.method}.`,
152874
153974
  void 0,
152875
153975
  "console"
152876
153976
  );
152877
153977
  }
152878
153978
  if (result.pruned.length > 0) {
152879
- log7.info(`Pruned old releases: ${result.pruned.join(", ")}`, void 0, "console");
153979
+ log11.info(`Pruned old releases: ${result.pruned.join(", ")}`, void 0, "console");
152880
153980
  }
152881
153981
  break;
152882
153982
  }
152883
153983
  } catch (err) {
152884
153984
  const message = err instanceof Error ? err.message : String(err);
152885
153985
  appendUpdateLog(`[error] ${message}`);
152886
- log7.error(`Update failed: ${message}`, { error: message }, "console");
153986
+ log11.error(`Update failed: ${message}`, { error: message }, "console");
152887
153987
  process.exitCode = 1;
152888
153988
  }
152889
153989
  });
@@ -152929,7 +154029,7 @@ async function printServerBanner({
152929
154029
  }) {
152930
154030
  const url2 = resolveServerUrl({ publicUrl, port });
152931
154031
  if (!includeQr) {
152932
- log7.info(printUrlBanner({ url: url2 }), void 0, "console");
154032
+ log11.info(printUrlBanner({ url: url2 }), void 0, "console");
152933
154033
  return;
152934
154034
  }
152935
154035
  const res = await fetch(`http://localhost:${port}/api/pair/start`, {
@@ -152944,9 +154044,9 @@ async function printServerBanner({
152944
154044
  const expSeconds = Math.floor(expiresAt / 1e3);
152945
154045
  const payload = `threadbase://pair?url=${encodeURIComponent(url2)}&token=${token}&exp=${expSeconds}`;
152946
154046
  const qr2 = await generateQr(payload);
152947
- log7.info(printUrlBanner({ url: url2, qr: qr2, expiresAt }), void 0, "console");
152948
- log7.info(`Pair URL: ${payload}`, void 0, "console");
152949
- log7.info(`Expires in ${expiresInSeconds}s`, void 0, "console");
154047
+ log11.info(printUrlBanner({ url: url2, qr: qr2, expiresAt }), void 0, "console");
154048
+ log11.info(`Pair URL: ${payload}`, void 0, "console");
154049
+ log11.info(`Expires in ${expiresInSeconds}s`, void 0, "console");
152950
154050
  }
152951
154051
  /*! Bundled license information:
152952
154052