@cortexkit/aft-opencode 0.27.0 → 0.28.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/index.js CHANGED
@@ -7803,7 +7803,7 @@ var require_src2 = __commonJS((exports, module) => {
7803
7803
  });
7804
7804
 
7805
7805
  // src/index.ts
7806
- import { existsSync as existsSync15, mkdirSync as mkdirSync13, readFileSync as readFileSync12, writeFileSync as writeFileSync12 } from "fs";
7806
+ import { existsSync as existsSync15, mkdirSync as mkdirSync13, readFileSync as readFileSync13, writeFileSync as writeFileSync12 } from "fs";
7807
7807
  import { createRequire as createRequire2 } from "module";
7808
7808
  import { join as join22 } from "path";
7809
7809
 
@@ -8597,13 +8597,14 @@ import { pipeline } from "stream/promises";
8597
8597
  var PLATFORM_ARCH_MAP = {
8598
8598
  darwin: { arm64: "darwin-arm64", x64: "darwin-x64" },
8599
8599
  linux: { arm64: "linux-arm64", x64: "linux-x64" },
8600
- win32: { arm64: "win32-x64", x64: "win32-x64" }
8600
+ win32: { arm64: "win32-arm64", x64: "win32-x64" }
8601
8601
  };
8602
8602
  var PLATFORM_ASSET_MAP = {
8603
8603
  "darwin-arm64": "aft-darwin-arm64",
8604
8604
  "darwin-x64": "aft-darwin-x64",
8605
8605
  "linux-arm64": "aft-linux-arm64",
8606
8606
  "linux-x64": "aft-linux-x64",
8607
+ "win32-arm64": "aft-win32-arm64.exe",
8607
8608
  "win32-x64": "aft-win32-x64.exe"
8608
8609
  };
8609
8610
 
@@ -9664,6 +9665,7 @@ class BridgePool {
9664
9665
  idleTimeoutMs;
9665
9666
  bridgeOptions;
9666
9667
  configOverrides;
9668
+ projectConfigLoader;
9667
9669
  logger;
9668
9670
  cleanupTimer = null;
9669
9671
  constructor(binaryPath, options = {}, configOverrides = {}) {
@@ -9671,6 +9673,7 @@ class BridgePool {
9671
9673
  this.maxPoolSize = options.maxPoolSize ?? DEFAULT_MAX_POOL_SIZE;
9672
9674
  this.idleTimeoutMs = options.idleTimeoutMs ?? DEFAULT_IDLE_TIMEOUT_MS;
9673
9675
  this.logger = options.logger;
9676
+ this.projectConfigLoader = options.projectConfigLoader;
9674
9677
  this.bridgeOptions = {
9675
9678
  timeoutMs: options.timeoutMs,
9676
9679
  maxRestarts: options.maxRestarts,
@@ -9706,7 +9709,17 @@ class BridgePool {
9706
9709
  if (this.bridges.size >= this.maxPoolSize) {
9707
9710
  this.evictLRU();
9708
9711
  }
9709
- const bridge = new BinaryBridge(this.binaryPath, key, this.bridgeOptions, this.configOverrides);
9712
+ let projectOverrides = {};
9713
+ if (this.projectConfigLoader) {
9714
+ try {
9715
+ projectOverrides = this.projectConfigLoader(key) ?? {};
9716
+ } catch (err) {
9717
+ const message = err instanceof Error ? err.message : String(err);
9718
+ this.error(`projectConfigLoader failed; using global overrides only: ${message}`);
9719
+ }
9720
+ }
9721
+ const mergedOverrides = { ...this.configOverrides, ...projectOverrides };
9722
+ const bridge = new BinaryBridge(this.binaryPath, key, this.bridgeOptions, mergedOverrides);
9710
9723
  this.bridges.set(key, { bridge, lastUsed: Date.now() });
9711
9724
  return bridge;
9712
9725
  }
@@ -10172,6 +10185,9 @@ function formatZoomText(targetLabel, response) {
10172
10185
  return out.join(`
10173
10186
  `);
10174
10187
  }
10188
+ // src/bg-notifications.ts
10189
+ import { createHash as createHash4, randomUUID } from "crypto";
10190
+
10175
10191
  // src/logger.ts
10176
10192
  import * as fs from "fs";
10177
10193
  import * as os from "os";
@@ -10348,6 +10364,9 @@ async function resolvePromptContext(client, sessionId) {
10348
10364
  }
10349
10365
 
10350
10366
  // src/bg-notifications.ts
10367
+ function hashReminder(text) {
10368
+ return createHash4("sha256").update(text).digest("hex").slice(0, 16);
10369
+ }
10351
10370
  var sessionBgStates = new Map;
10352
10371
  var SESSION_BG_STATE_IDLE_TTL_MS = 60 * 60 * 1000;
10353
10372
  var DEBOUNCE_STEP_MS = 200;
@@ -10418,6 +10437,13 @@ async function appendInTurnBgCompletions(drainContext, output) {
10418
10437
  const deliveredCompletions = [...state.pendingCompletions];
10419
10438
  const reminder = formatCombinedSystemReminder(state.pendingCompletions, state.pendingLongRunning);
10420
10439
  output.output = appendReminder(output.output ?? "", reminder);
10440
+ sessionLog(drainContext.sessionID, `${LOG_PREFIX} in-turn append`, {
10441
+ event: "bash_completion_in_turn_append",
10442
+ task_ids: deliveredCompletions.map((c) => c.task_id),
10443
+ long_running_task_ids: state.pendingLongRunning.map((r) => r.task_id),
10444
+ reminder_sha256: hashReminder(reminder),
10445
+ reminder_chars: reminder.length
10446
+ });
10421
10447
  state.pendingCompletions = [];
10422
10448
  state.pendingLongRunning = [];
10423
10449
  state.wakeRetryAttempts = 0;
@@ -10461,14 +10487,53 @@ async function triggerWakeIfPending(drainContext, skipDrain) {
10461
10487
  }
10462
10488
  if (promptContext?.variant)
10463
10489
  body.variant = promptContext.variant;
10464
- await client.session.promptAsync({
10465
- path: { id: drainContext.sessionID },
10466
- body
10490
+ const deliveryID = `aftdel_${randomUUID()}`;
10491
+ const taskIDs = deliveredCompletions.map((c) => c.task_id);
10492
+ const wakeMeta = {
10493
+ delivery_id: deliveryID,
10494
+ attempt: state.wakeRetryAttempts + 1,
10495
+ task_ids: taskIDs,
10496
+ directory: drainContext.directory,
10497
+ reminder_sha256: hashReminder(reminder),
10498
+ reminder_chars: reminder.length,
10499
+ prompt_context: promptContext ? {
10500
+ agent: promptContext.agent,
10501
+ model: promptContext.model ? {
10502
+ providerID: promptContext.model.providerID,
10503
+ modelID: promptContext.model.modelID
10504
+ } : null,
10505
+ variant: promptContext.variant ?? null
10506
+ } : null
10507
+ };
10508
+ sessionLog(drainContext.sessionID, `${LOG_PREFIX} wake promptAsync start`, {
10509
+ event: "bash_completion_wake_prompt_async_start",
10510
+ ...wakeMeta
10467
10511
  });
10468
- await ackCompletions(drainContext, deliveredCompletions);
10512
+ try {
10513
+ await client.session.promptAsync({
10514
+ path: { id: drainContext.sessionID },
10515
+ body
10516
+ });
10517
+ } catch (err) {
10518
+ sessionWarn(drainContext.sessionID, `${LOG_PREFIX} wake promptAsync error`, {
10519
+ event: "bash_completion_wake_prompt_async_error",
10520
+ delivery_id: deliveryID,
10521
+ attempt: state.wakeRetryAttempts + 1,
10522
+ task_ids: taskIDs,
10523
+ error: err instanceof Error ? err.message : String(err)
10524
+ });
10525
+ throw err;
10526
+ }
10527
+ sessionLog(drainContext.sessionID, `${LOG_PREFIX} wake promptAsync ok`, {
10528
+ event: "bash_completion_wake_prompt_async_ok",
10529
+ delivery_id: deliveryID,
10530
+ attempt: state.wakeRetryAttempts + 1,
10531
+ task_ids: taskIDs
10532
+ });
10533
+ await ackCompletions(drainContext, deliveredCompletions, deliveryID);
10469
10534
  }, (err, hardStopped) => {
10470
10535
  sessionWarn(drainContext.sessionID, hardStopped ? `${LOG_PREFIX} wake send failed ${MAX_WAKE_SEND_ATTEMPTS} times; stopping retries: ${err instanceof Error ? err.message : String(err)}` : `${LOG_PREFIX} wake send failed: ${err instanceof Error ? err.message : String(err)}`);
10471
- });
10536
+ }, drainContext.sessionID);
10472
10537
  }
10473
10538
  function formatSystemReminder(completions) {
10474
10539
  const bullets = completions.map((completion) => formatCompletion(completion)).join(`
@@ -10532,7 +10597,7 @@ async function drainCompletions({ ctx, directory, sessionID }) {
10532
10597
  sessionWarn(sessionID, `${LOG_PREFIX} drain failed: ${err instanceof Error ? err.message : String(err)}`);
10533
10598
  }
10534
10599
  }
10535
- async function ackCompletions({ ctx, directory, sessionID }, completions) {
10600
+ async function ackCompletions({ ctx, directory, sessionID }, completions, deliveryID) {
10536
10601
  const taskIds = [...new Set(completions.map((completion) => completion.task_id))];
10537
10602
  if (taskIds.length === 0)
10538
10603
  return;
@@ -10544,12 +10609,18 @@ async function ackCompletions({ ctx, directory, sessionID }, completions) {
10544
10609
  });
10545
10610
  if (response.success === false) {
10546
10611
  sessionWarn(sessionID, `${LOG_PREFIX} ack failed: ${String(response.message ?? "unknown error")}`);
10612
+ return;
10547
10613
  }
10614
+ sessionLog(sessionID, `${LOG_PREFIX} ack ok`, {
10615
+ event: "bash_completion_ack_ok",
10616
+ delivery_id: deliveryID ?? null,
10617
+ task_ids: taskIds
10618
+ });
10548
10619
  } catch (err) {
10549
10620
  sessionWarn(sessionID, `${LOG_PREFIX} ack failed: ${err instanceof Error ? err.message : String(err)}`);
10550
10621
  }
10551
10622
  }
10552
- function scheduleWake(state, sendWake, onSendFailure) {
10623
+ function scheduleWake(state, sendWake, onSendFailure, sessionID) {
10553
10624
  if (state.wakeHardStopped)
10554
10625
  return;
10555
10626
  const now = Date.now();
@@ -10568,6 +10639,13 @@ function scheduleWake(state, sendWake, onSendFailure) {
10568
10639
  if (state.debounceTimer)
10569
10640
  clearTimeout(state.debounceTimer);
10570
10641
  const delay = state.retryDelayMs ?? Math.max(0, (state.scheduledFireAt ?? now) - now);
10642
+ sessionLog(sessionID, `${LOG_PREFIX} wake scheduled`, {
10643
+ event: "bash_completion_wake_scheduled",
10644
+ delay_ms: delay,
10645
+ pending_completions: state.pendingCompletions.length,
10646
+ pending_long_running: state.pendingLongRunning.length,
10647
+ retry_attempt: state.wakeRetryAttempts
10648
+ });
10571
10649
  state.debounceTimer = setTimeout(() => {
10572
10650
  const pending = state.pendingCompletions;
10573
10651
  const pendingLongRunning = state.pendingLongRunning;
@@ -10578,6 +10656,14 @@ function scheduleWake(state, sendWake, onSendFailure) {
10578
10656
  if (pending.length === 0 && pendingLongRunning.length === 0)
10579
10657
  return;
10580
10658
  const reminder = formatCombinedSystemReminder(pending, pendingLongRunning);
10659
+ sessionLog(sessionID, `${LOG_PREFIX} wake fire`, {
10660
+ event: "bash_completion_wake_fire",
10661
+ task_ids: pending.map((c) => c.task_id),
10662
+ long_running_task_ids: pendingLongRunning.map((r) => r.task_id),
10663
+ reminder_sha256: hashReminder(reminder),
10664
+ reminder_chars: reminder.length,
10665
+ retry_attempt: state.wakeRetryAttempts
10666
+ });
10581
10667
  state.pendingCompletions = [];
10582
10668
  state.pendingLongRunning = [];
10583
10669
  sendWake(reminder, pending).then(() => {
@@ -10596,7 +10682,7 @@ function scheduleWake(state, sendWake, onSendFailure) {
10596
10682
  }
10597
10683
  state.retryDelayMs = Math.min((delay || DEBOUNCE_STEP_MS) * 2, DEBOUNCE_CAP_MS);
10598
10684
  onSendFailure(err, false);
10599
- scheduleWake(state, sendWake, onSendFailure);
10685
+ scheduleWake(state, sendWake, onSendFailure, sessionID);
10600
10686
  });
10601
10687
  }, delay);
10602
10688
  state.debounceTimer.unref?.();
@@ -24334,7 +24420,16 @@ var ExperimentalConfigSchema = exports_external.object({
24334
24420
  }).optional(),
24335
24421
  lsp_ty: exports_external.boolean().optional()
24336
24422
  });
24423
+ var BashFeaturesSchema = exports_external.object({
24424
+ rewrite: exports_external.boolean().optional(),
24425
+ compress: exports_external.boolean().optional(),
24426
+ background: exports_external.boolean().optional(),
24427
+ long_running_reminder_enabled: exports_external.boolean().optional(),
24428
+ long_running_reminder_interval_ms: exports_external.number().int().positive().optional()
24429
+ });
24430
+ var BashConfigSchema = exports_external.union([exports_external.boolean(), BashFeaturesSchema]);
24337
24431
  var AftConfigSchema = exports_external.object({
24432
+ $schema: exports_external.string().optional(),
24338
24433
  format_on_edit: exports_external.boolean().optional(),
24339
24434
  formatter_timeout_secs: exports_external.number().int().min(1).max(600).optional(),
24340
24435
  validate_on_edit: exports_external.enum(["syntax", "full"]).optional(),
@@ -24346,6 +24441,7 @@ var AftConfigSchema = exports_external.object({
24346
24441
  restrict_to_project_root: exports_external.boolean().optional(),
24347
24442
  search_index: exports_external.boolean().optional(),
24348
24443
  semantic_search: exports_external.boolean().optional(),
24444
+ bash: BashConfigSchema.optional(),
24349
24445
  experimental: ExperimentalConfigSchema.optional(),
24350
24446
  lsp: LspConfigSchema.optional(),
24351
24447
  url_fetch_allow_private: exports_external.boolean().optional(),
@@ -24400,22 +24496,93 @@ function resolveLspConfigForConfigure(config2) {
24400
24496
  }
24401
24497
  return overrides;
24402
24498
  }
24403
- function resolveExperimentalConfigForConfigure(config2) {
24499
+ function resolveProjectOverridesForConfigure(config2) {
24404
24500
  const overrides = {};
24405
- if (config2.experimental?.bash?.rewrite !== undefined) {
24406
- overrides.experimental_bash_rewrite = config2.experimental.bash.rewrite;
24407
- }
24408
- if (config2.experimental?.bash?.compress !== undefined) {
24409
- overrides.experimental_bash_compress = config2.experimental.bash.compress;
24501
+ if (config2.format_on_edit !== undefined)
24502
+ overrides.format_on_edit = config2.format_on_edit;
24503
+ if (config2.formatter_timeout_secs !== undefined)
24504
+ overrides.formatter_timeout_secs = config2.formatter_timeout_secs;
24505
+ if (config2.validate_on_edit !== undefined)
24506
+ overrides.validate_on_edit = config2.validate_on_edit;
24507
+ if (config2.formatter !== undefined)
24508
+ overrides.formatter = config2.formatter;
24509
+ if (config2.checker !== undefined)
24510
+ overrides.checker = config2.checker;
24511
+ overrides.restrict_to_project_root = config2.restrict_to_project_root ?? false;
24512
+ if (config2.search_index !== undefined)
24513
+ overrides.search_index = config2.search_index;
24514
+ if (config2.semantic_search !== undefined)
24515
+ overrides.semantic_search = config2.semantic_search;
24516
+ Object.assign(overrides, resolveExperimentalConfigForConfigure(config2));
24517
+ Object.assign(overrides, resolveLspConfigForConfigure(config2));
24518
+ if (config2.semantic !== undefined)
24519
+ overrides.semantic = config2.semantic;
24520
+ if (config2.max_callgraph_files !== undefined)
24521
+ overrides.max_callgraph_files = config2.max_callgraph_files;
24522
+ return overrides;
24523
+ }
24524
+ function resolveBashConfig(config2) {
24525
+ const top = config2.bash;
24526
+ const legacy = config2.experimental?.bash;
24527
+ const surface = config2.tool_surface ?? "recommended";
24528
+ const surfaceDefaultEnabled = surface !== "minimal";
24529
+ const reminderEnabled = (typeof top === "object" && top !== null ? top.long_running_reminder_enabled : undefined) ?? legacy?.long_running_reminder_enabled;
24530
+ const reminderInterval = (typeof top === "object" && top !== null ? top.long_running_reminder_interval_ms : undefined) ?? legacy?.long_running_reminder_interval_ms;
24531
+ const base = {
24532
+ enabled: false,
24533
+ rewrite: false,
24534
+ compress: false,
24535
+ background: false,
24536
+ long_running_reminder_enabled: reminderEnabled,
24537
+ long_running_reminder_interval_ms: reminderInterval
24538
+ };
24539
+ if (top === false) {
24540
+ return base;
24541
+ }
24542
+ if (top === true) {
24543
+ return { ...base, enabled: true, rewrite: true, compress: true, background: true };
24544
+ }
24545
+ if (typeof top === "object" && top !== null) {
24546
+ return {
24547
+ ...base,
24548
+ enabled: true,
24549
+ rewrite: top.rewrite ?? true,
24550
+ compress: top.compress ?? true,
24551
+ background: top.background ?? true
24552
+ };
24410
24553
  }
24411
- if (config2.experimental?.bash?.background !== undefined) {
24412
- overrides.experimental_bash_background = config2.experimental.bash.background;
24554
+ const hasLegacyFeatureFlag = legacy && (legacy.rewrite !== undefined || legacy.compress !== undefined || legacy.background !== undefined);
24555
+ if (hasLegacyFeatureFlag) {
24556
+ const rewrite = legacy.rewrite === true;
24557
+ const compress = legacy.compress === true;
24558
+ const background = legacy.background === true;
24559
+ return {
24560
+ ...base,
24561
+ enabled: rewrite || compress || background,
24562
+ rewrite,
24563
+ compress,
24564
+ background
24565
+ };
24413
24566
  }
24414
- if (config2.experimental?.bash?.long_running_reminder_enabled !== undefined) {
24415
- overrides.bash_long_running_reminder_enabled = config2.experimental.bash.long_running_reminder_enabled;
24567
+ return {
24568
+ ...base,
24569
+ enabled: surfaceDefaultEnabled,
24570
+ rewrite: surfaceDefaultEnabled,
24571
+ compress: surfaceDefaultEnabled,
24572
+ background: surfaceDefaultEnabled
24573
+ };
24574
+ }
24575
+ function resolveExperimentalConfigForConfigure(config2) {
24576
+ const overrides = {};
24577
+ const bash = resolveBashConfig(config2);
24578
+ overrides.experimental_bash_rewrite = bash.rewrite;
24579
+ overrides.experimental_bash_compress = bash.compress;
24580
+ overrides.experimental_bash_background = bash.background;
24581
+ if (bash.long_running_reminder_enabled !== undefined) {
24582
+ overrides.bash_long_running_reminder_enabled = bash.long_running_reminder_enabled;
24416
24583
  }
24417
- if (config2.experimental?.bash?.long_running_reminder_interval_ms !== undefined) {
24418
- overrides.bash_long_running_reminder_interval_ms = config2.experimental.bash.long_running_reminder_interval_ms;
24584
+ if (bash.long_running_reminder_interval_ms !== undefined) {
24585
+ overrides.bash_long_running_reminder_interval_ms = bash.long_running_reminder_interval_ms;
24419
24586
  }
24420
24587
  if (config2.experimental?.lsp_ty !== undefined) {
24421
24588
  overrides.experimental_lsp_ty = config2.experimental.lsp_ty;
@@ -24486,8 +24653,51 @@ function migrateRawConfig(rawConfig, configPath, logger) {
24486
24653
  delete rawConfig[migration.oldKey];
24487
24654
  oldKeys.push(migration.oldKey);
24488
24655
  }
24656
+ oldKeys.push(...migrateExperimentalBashBlock(rawConfig, configPath, logger));
24489
24657
  return oldKeys;
24490
24658
  }
24659
+ function migrateExperimentalBashBlock(rawConfig, configPath, logger) {
24660
+ const experimental = rawConfig.experimental;
24661
+ if (typeof experimental !== "object" || experimental === null || Array.isArray(experimental)) {
24662
+ return [];
24663
+ }
24664
+ const expRecord = experimental;
24665
+ if (!Object.hasOwn(expRecord, "bash"))
24666
+ return [];
24667
+ const legacyBash = expRecord.bash;
24668
+ if (typeof legacyBash !== "object" || legacyBash === null || Array.isArray(legacyBash)) {
24669
+ delete expRecord.bash;
24670
+ if (Object.keys(expRecord).length === 0)
24671
+ delete rawConfig.experimental;
24672
+ return ["experimental.bash"];
24673
+ }
24674
+ const bashRecord = legacyBash;
24675
+ const hasFeatureFlag = "rewrite" in bashRecord || "compress" in bashRecord || "background" in bashRecord;
24676
+ if (!hasFeatureFlag)
24677
+ return [];
24678
+ const movedKeys = Object.keys(bashRecord).map((k) => `experimental.bash.${k}`);
24679
+ if (Object.hasOwn(rawConfig, "bash")) {
24680
+ logger?.warn(`Config migration conflict at ${configPath}: experimental.bash dropped because top-level "bash" is already set`);
24681
+ } else {
24682
+ const migrated = {
24683
+ rewrite: bashRecord.rewrite === true,
24684
+ compress: bashRecord.compress === true,
24685
+ background: bashRecord.background === true
24686
+ };
24687
+ if (bashRecord.long_running_reminder_enabled !== undefined) {
24688
+ migrated.long_running_reminder_enabled = bashRecord.long_running_reminder_enabled;
24689
+ }
24690
+ if (bashRecord.long_running_reminder_interval_ms !== undefined) {
24691
+ migrated.long_running_reminder_interval_ms = bashRecord.long_running_reminder_interval_ms;
24692
+ }
24693
+ rawConfig.bash = migrated;
24694
+ }
24695
+ delete expRecord.bash;
24696
+ if (Object.keys(expRecord).length === 0) {
24697
+ delete rawConfig.experimental;
24698
+ }
24699
+ return movedKeys;
24700
+ }
24491
24701
  function migrateAftConfigFile(configPath, logger = { log: log2, warn: warn2 }) {
24492
24702
  if (!existsSync6(configPath)) {
24493
24703
  return { migrated: false, oldKeys: [] };
@@ -24623,6 +24833,22 @@ function mergeLspConfig(baseLsp, overrideLsp) {
24623
24833
  }
24624
24834
  return Object.fromEntries(Object.entries(lsp).filter(([, value]) => value !== undefined));
24625
24835
  }
24836
+ function mergeBashConfig(baseBash, overrideBash) {
24837
+ if (baseBash === undefined && overrideBash === undefined)
24838
+ return;
24839
+ if (baseBash === undefined)
24840
+ return overrideBash;
24841
+ if (overrideBash === undefined)
24842
+ return baseBash;
24843
+ const expand = (value) => {
24844
+ if (value === true)
24845
+ return { rewrite: true, compress: true, background: true };
24846
+ if (value === false)
24847
+ return { rewrite: false, compress: false, background: false };
24848
+ return { ...value ?? {} };
24849
+ };
24850
+ return { ...expand(baseBash), ...expand(overrideBash) };
24851
+ }
24626
24852
  function mergeExperimentalConfig(baseExperimental, overrideExperimental) {
24627
24853
  const bash = {
24628
24854
  ...baseExperimental?.bash,
@@ -24666,7 +24892,8 @@ var PROJECT_SAFE_TOP_LEVEL_FIELDS = new Set([
24666
24892
  "validate_on_edit",
24667
24893
  "search_index",
24668
24894
  "semantic_search",
24669
- "experimental"
24895
+ "experimental",
24896
+ "bash"
24670
24897
  ]);
24671
24898
  function pickProjectSafeFields(override) {
24672
24899
  const safe = {};
@@ -24696,13 +24923,16 @@ function mergeConfigs(base, override) {
24696
24923
  const semantic = mergeSemanticConfig(base.semantic, override.semantic);
24697
24924
  const lsp = mergeLspConfig(base.lsp, override.lsp);
24698
24925
  const experimental = mergeExperimentalConfig(base.experimental, override.experimental);
24926
+ const bash = mergeBashConfig(base.bash, override.bash);
24699
24927
  const safeOverride = pickProjectSafeFields(override);
24928
+ delete safeOverride.bash;
24700
24929
  return {
24701
24930
  ...base,
24702
24931
  ...safeOverride,
24703
24932
  ...Object.keys(formatter).length > 0 ? { formatter } : {},
24704
24933
  ...Object.keys(checker).length > 0 ? { checker } : {},
24705
24934
  ...lsp ? { lsp } : {},
24935
+ ...bash !== undefined ? { bash } : {},
24706
24936
  experimental,
24707
24937
  semantic,
24708
24938
  ...disabledTools.length > 0 ? { disabled_tools: [...new Set(disabledTools)] } : {}
@@ -25360,8 +25590,8 @@ function showToast(ctx, title, message, variant = "info", duration3 = 3000) {
25360
25590
 
25361
25591
  // src/lsp-auto-install.ts
25362
25592
  import { spawn as spawn3 } from "child_process";
25363
- import { createHash as createHash4 } from "crypto";
25364
- import { createReadStream, mkdirSync as mkdirSync8, readFileSync as readFileSync8, renameSync as renameSync5, rmSync as rmSync4, statSync as statSync5 } from "fs";
25593
+ import { createHash as createHash5 } from "crypto";
25594
+ import { createReadStream, mkdirSync as mkdirSync8, readFileSync as readFileSync9, renameSync as renameSync5, rmSync as rmSync4, statSync as statSync5 } from "fs";
25365
25595
  import { join as join15 } from "path";
25366
25596
 
25367
25597
  // src/lsp-cache.ts
@@ -25695,19 +25925,33 @@ var NPM_LSP_TABLE = [
25695
25925
  id: "vue",
25696
25926
  npm: "@vue/language-server",
25697
25927
  binary: "vue-language-server",
25698
- extensions: ["vue"]
25928
+ extensions: ["vue"],
25929
+ rootMarkers: [
25930
+ "vue.config.js",
25931
+ "vue.config.mjs",
25932
+ "vue.config.ts",
25933
+ "nuxt.config.js",
25934
+ "nuxt.config.mjs",
25935
+ "nuxt.config.ts",
25936
+ "nuxt.config.cjs"
25937
+ ],
25938
+ packageJsonDeps: ["vue", "@vue/runtime-core", "nuxt"]
25699
25939
  },
25700
25940
  {
25701
25941
  id: "astro",
25702
25942
  npm: "@astrojs/language-server",
25703
25943
  binary: "astro-ls",
25704
- extensions: ["astro"]
25944
+ extensions: ["astro"],
25945
+ rootMarkers: ["astro.config.js", "astro.config.mjs", "astro.config.ts", "astro.config.cjs"],
25946
+ packageJsonDeps: ["astro"]
25705
25947
  },
25706
25948
  {
25707
25949
  id: "svelte",
25708
25950
  npm: "svelte-language-server",
25709
25951
  binary: "svelteserver",
25710
- extensions: ["svelte"]
25952
+ extensions: ["svelte"],
25953
+ rootMarkers: ["svelte.config.js", "svelte.config.mjs", "svelte.config.ts", "svelte.config.cjs"],
25954
+ packageJsonDeps: ["svelte", "@sveltejs/kit"]
25711
25955
  },
25712
25956
  {
25713
25957
  id: "php-intelephense",
@@ -25725,7 +25969,7 @@ var NPM_LSP_TABLE = [
25725
25969
  ];
25726
25970
 
25727
25971
  // src/lsp-project-relevance.ts
25728
- import { existsSync as existsSync10, readdirSync as readdirSync3 } from "fs";
25972
+ import { existsSync as existsSync10, readdirSync as readdirSync3, readFileSync as readFileSync8 } from "fs";
25729
25973
  import { join as join14 } from "path";
25730
25974
  var MAX_WALK_DIRS = 200;
25731
25975
  var MAX_WALK_DEPTH = 4;
@@ -25748,6 +25992,34 @@ function hasRootMarker(projectRoot, rootMarkers) {
25748
25992
  }
25749
25993
  return false;
25750
25994
  }
25995
+ function hasPackageJsonDep(projectRoot, depNames) {
25996
+ if (!depNames || depNames.length === 0)
25997
+ return false;
25998
+ const pkg = readPackageJson(projectRoot);
25999
+ if (!pkg)
26000
+ return false;
26001
+ const merged = {
26002
+ ...typeof pkg.dependencies === "object" && pkg.dependencies ? pkg.dependencies : {},
26003
+ ...typeof pkg.devDependencies === "object" && pkg.devDependencies ? pkg.devDependencies : {},
26004
+ ...typeof pkg.peerDependencies === "object" && pkg.peerDependencies ? pkg.peerDependencies : {}
26005
+ };
26006
+ for (const name of depNames) {
26007
+ if (Object.hasOwn(merged, name))
26008
+ return true;
26009
+ }
26010
+ return false;
26011
+ }
26012
+ function readPackageJson(projectRoot) {
26013
+ try {
26014
+ const raw = readFileSync8(join14(projectRoot, "package.json"), "utf8");
26015
+ const parsed = JSON.parse(raw);
26016
+ if (typeof parsed !== "object" || parsed === null)
26017
+ return null;
26018
+ return parsed;
26019
+ } catch {
26020
+ return null;
26021
+ }
26022
+ }
25751
26023
  function relevantExtensionsInProject(projectRoot, extToServer) {
25752
26024
  const wanted = new Set(Object.keys(extToServer).map((ext) => ext.toLowerCase()));
25753
26025
  const found = new Set;
@@ -25840,6 +26112,8 @@ async function probeRegistry(npmPackage, graceDays, fetchImpl = fetch) {
25840
26112
  function isProjectRelevant(spec, projectRoot, projectExtensions) {
25841
26113
  if (hasRootMarker(projectRoot, spec.rootMarkers))
25842
26114
  return true;
26115
+ if (hasPackageJsonDep(projectRoot, spec.packageJsonDeps))
26116
+ return true;
25843
26117
  const extensions = projectExtensions();
25844
26118
  return spec.extensions.some((ext) => extensions.has(ext.toLowerCase()));
25845
26119
  }
@@ -25901,8 +26175,9 @@ function runInstall(spec, version2, cwd, signal) {
25901
26175
  resolve3(false);
25902
26176
  return;
25903
26177
  }
25904
- const child = spawn3("bun", ["add", target, "--cwd", cwd, "--ignore-scripts", "--silent"], {
25905
- stdio: ["ignore", "pipe", "pipe"]
26178
+ const child = spawn3("npm", ["install", "--no-save", "--ignore-scripts", "--silent", target], {
26179
+ stdio: ["ignore", "pipe", "pipe"],
26180
+ cwd
25906
26181
  });
25907
26182
  child.unref();
25908
26183
  let stderrBuf = "";
@@ -26039,7 +26314,7 @@ function hashInstalledBinary(spec) {
26039
26314
  reject(new Error(`installed binary not found at any of: ${candidates.join(", ")}`));
26040
26315
  return;
26041
26316
  }
26042
- const hash2 = createHash4("sha256");
26317
+ const hash2 = createHash5("sha256");
26043
26318
  const stream = createReadStream(pathToHash);
26044
26319
  stream.on("error", reject);
26045
26320
  stream.on("data", (chunk) => hash2.update(chunk));
@@ -26062,7 +26337,7 @@ function installedBinaryPath(spec) {
26062
26337
  return null;
26063
26338
  }
26064
26339
  function sha256OfFileSync(path2) {
26065
- return createHash4("sha256").update(readFileSync8(path2)).digest("hex");
26340
+ return createHash5("sha256").update(readFileSync9(path2)).digest("hex");
26066
26341
  }
26067
26342
  function quarantineCachedNpmInstall(spec, reason) {
26068
26343
  const packageDir = lspPackageDir(spec.npm);
@@ -26147,7 +26422,7 @@ function runAutoInstall(projectRoot, config2, fetchImpl = fetch) {
26147
26422
 
26148
26423
  // src/lsp-github-install.ts
26149
26424
  import { execFileSync as execFileSync2 } from "child_process";
26150
- import { createHash as createHash5, randomBytes } from "crypto";
26425
+ import { createHash as createHash6, randomBytes } from "crypto";
26151
26426
  import {
26152
26427
  copyFileSync as copyFileSync3,
26153
26428
  createReadStream as createReadStream2,
@@ -26156,7 +26431,7 @@ import {
26156
26431
  lstatSync as lstatSync2,
26157
26432
  mkdirSync as mkdirSync9,
26158
26433
  readdirSync as readdirSync4,
26159
- readFileSync as readFileSync9,
26434
+ readFileSync as readFileSync10,
26160
26435
  readlinkSync as readlinkSync2,
26161
26436
  realpathSync as realpathSync3,
26162
26437
  renameSync as renameSync6,
@@ -26290,7 +26565,7 @@ function readGithubInstalledMetaIn(installDir) {
26290
26565
  const path2 = join16(installDir, INSTALLED_META_FILE2);
26291
26566
  if (!statSync6(path2).isFile())
26292
26567
  return null;
26293
- const parsed = JSON.parse(readFileSync9(path2, "utf8"));
26568
+ const parsed = JSON.parse(readFileSync10(path2, "utf8"));
26294
26569
  if (typeof parsed.version !== "string" || parsed.version.length === 0)
26295
26570
  return null;
26296
26571
  return {
@@ -26323,7 +26598,7 @@ var MAX_DOWNLOAD_BYTES3 = 256 * 1024 * 1024;
26323
26598
  var MAX_EXTRACT_BYTES2 = 1024 * 1024 * 1024;
26324
26599
  function sha256OfFile(path2) {
26325
26600
  return new Promise((resolve4, reject) => {
26326
- const hash2 = createHash5("sha256");
26601
+ const hash2 = createHash6("sha256");
26327
26602
  const stream = createReadStream2(path2);
26328
26603
  stream.on("error", reject);
26329
26604
  stream.on("data", (chunk) => hash2.update(chunk));
@@ -26331,7 +26606,7 @@ function sha256OfFile(path2) {
26331
26606
  });
26332
26607
  }
26333
26608
  function sha256OfFileSync2(path2) {
26334
- return createHash5("sha256").update(readFileSync9(path2)).digest("hex");
26609
+ return createHash6("sha256").update(readFileSync10(path2)).digest("hex");
26335
26610
  }
26336
26611
  async function fetchReleaseByTag(githubRepo, tag, fetchImpl, signal) {
26337
26612
  const candidates = [];
@@ -27030,7 +27305,7 @@ function normalizeToolMap(tools) {
27030
27305
  }
27031
27306
 
27032
27307
  // src/notifications.ts
27033
- import { existsSync as existsSync12, mkdirSync as mkdirSync10, readFileSync as readFileSync10, writeFileSync as writeFileSync9 } from "fs";
27308
+ import { existsSync as existsSync12, mkdirSync as mkdirSync10, readFileSync as readFileSync11, writeFileSync as writeFileSync9 } from "fs";
27034
27309
  import { homedir as homedir9, platform as platform2 } from "os";
27035
27310
  import { join as join17 } from "path";
27036
27311
  function isTuiMode() {
@@ -27073,7 +27348,7 @@ function readDesktopState(directory) {
27073
27348
  return { sessionId: null, serverUrl: null };
27074
27349
  }
27075
27350
  try {
27076
- const raw = readFileSync10(statePath, "utf-8");
27351
+ const raw = readFileSync11(statePath, "utf-8");
27077
27352
  const state = JSON.parse(raw);
27078
27353
  let serverUrl = null;
27079
27354
  const serverStr = state.server;
@@ -27169,25 +27444,34 @@ async function sendWarning(opts, message) {
27169
27444
  sessionLog(sessionId, `[aft-plugin] sending warning to session ${sessionId}`);
27170
27445
  await sendIgnoredMessage(opts.client, sessionId, text);
27171
27446
  }
27172
- async function sendFeatureAnnouncement(opts, version2, features, storageDir) {
27447
+ async function sendFeatureAnnouncement(opts, version2, features, footer, storageDir) {
27173
27448
  if (storageDir) {
27174
27449
  const versionFile = join17(storageDir, "last_announced_version");
27175
27450
  try {
27176
27451
  if (existsSync12(versionFile)) {
27177
- const lastVersion = readFileSync10(versionFile, "utf-8").trim();
27452
+ const lastVersion = readFileSync11(versionFile, "utf-8").trim();
27178
27453
  if (lastVersion === version2)
27179
27454
  return;
27180
27455
  }
27181
27456
  } catch {}
27182
27457
  }
27183
- const featureText = features.map((f) => `\u2022 ${f}`).join(`
27458
+ const hasFooter = typeof footer === "string" && footer.trim().length > 0;
27459
+ const featureText = hasFooter ? [features.map((f) => `\u2022 ${f}`).join(`
27460
+ `), "", footer].join(`
27461
+ `) : features.map((f) => `\u2022 ${f}`).join(`
27184
27462
  `);
27185
27463
  const toastSent = await showTuiToast(opts.client, `AFT v${version2}`, featureText, "info", 12000);
27186
27464
  if (!toastSent) {
27187
27465
  const { sessionId } = readDesktopState(opts.directory);
27188
27466
  if (!sessionId)
27189
27467
  return;
27190
- const text = [`${FEATURE_MARKER} v${version2}:`, ...features.map((f) => ` \u2022 ${f}`)].join(`
27468
+ const sections = [
27469
+ `${FEATURE_MARKER} v${version2}:`,
27470
+ ...features.map((f) => ` \u2022 ${f}`)
27471
+ ];
27472
+ if (hasFooter)
27473
+ sections.push("", footer);
27474
+ const text = sections.join(`
27191
27475
  `);
27192
27476
  sessionLog(sessionId, `[aft-plugin] sending feature announcement for v${version2}`);
27193
27477
  await sendIgnoredMessage(opts.client, sessionId, text);
@@ -27317,11 +27601,11 @@ import { createServer } from "http";
27317
27601
  import { dirname as dirname7 } from "path";
27318
27602
 
27319
27603
  // src/shared/rpc-utils.ts
27320
- import { createHash as createHash6 } from "crypto";
27604
+ import { createHash as createHash7 } from "crypto";
27321
27605
  import { join as join18 } from "path";
27322
27606
  function projectHash(directory) {
27323
27607
  const normalized = directory.replace(/\/+$/, "");
27324
- return createHash6("sha256").update(normalized).digest("hex").slice(0, 16);
27608
+ return createHash7("sha256").update(normalized).digest("hex").slice(0, 16);
27325
27609
  }
27326
27610
  function rpcPortFilePath(storageDir, directory) {
27327
27611
  const hash2 = projectHash(directory);
@@ -27674,7 +27958,7 @@ function formatStatusMarkdown(status) {
27674
27958
 
27675
27959
  // src/shared/tui-config.ts
27676
27960
  var import_comment_json4 = __toESM(require_src2(), 1);
27677
- import { existsSync as existsSync13, mkdirSync as mkdirSync12, readFileSync as readFileSync11, writeFileSync as writeFileSync11 } from "fs";
27961
+ import { existsSync as existsSync13, mkdirSync as mkdirSync12, readFileSync as readFileSync12, writeFileSync as writeFileSync11 } from "fs";
27678
27962
  import { dirname as dirname8, join as join20 } from "path";
27679
27963
 
27680
27964
  // src/shared/opencode-config-dir.ts
@@ -27722,7 +28006,7 @@ function ensureTuiPluginEntry() {
27722
28006
  const configPath = resolveTuiConfigPath();
27723
28007
  let config2 = {};
27724
28008
  if (existsSync13(configPath)) {
27725
- config2 = import_comment_json4.parse(readFileSync11(configPath, "utf-8")) ?? {};
28009
+ config2 = import_comment_json4.parse(readFileSync12(configPath, "utf-8")) ?? {};
27726
28010
  }
27727
28011
  const plugins = Array.isArray(config2.plugin) ? config2.plugin.filter((value) => typeof value === "string") : [];
27728
28012
  if (plugins.some((plugin) => plugin === PLUGIN_NAME || plugin.startsWith(`${PLUGIN_NAME}@`) || plugin.includes("opencode-plugin") || plugin.includes("aft-opencode"))) {
@@ -29327,6 +29611,9 @@ function createWriteTool(ctx, editToolName = "edit") {
29327
29611
  let output = data.created ? "Created new file." : "File updated.";
29328
29612
  if (data.formatted)
29329
29613
  output += " Auto-formatted.";
29614
+ if (data.no_op === true) {
29615
+ output += " No net change \u2014 the written content is byte-identical to what was already on disk.";
29616
+ }
29330
29617
  const diags = data.lsp_diagnostics;
29331
29618
  if (diags && diags.length > 0) {
29332
29619
  const errors3 = diags.filter((d) => d.severity === "error");
@@ -29575,6 +29862,11 @@ function createEditTool(ctx, writeToolName = "write") {
29575
29862
  result += `
29576
29863
 
29577
29864
  ${globSkipNote}`;
29865
+ if (data.no_op === true) {
29866
+ result += `
29867
+
29868
+ Note: no net file change \u2014 the match was found and applied, but the file content is byte-identical to before. Likely causes: oldString and newString are identical, or a formatter normalized the change away.`;
29869
+ }
29578
29870
  const diags = data.lsp_diagnostics;
29579
29871
  if (diags && diags.length > 0) {
29580
29872
  const errors3 = diags.filter((d) => d.severity === "error");
@@ -30032,11 +30324,7 @@ function hoistedTools(ctx) {
30032
30324
  aft_delete: createDeleteTool(ctx),
30033
30325
  aft_move: createMoveTool(ctx)
30034
30326
  };
30035
- const bashRewrite = ctx.config.experimental?.bash?.rewrite === true;
30036
- const bashCompress = ctx.config.experimental?.bash?.compress === true;
30037
- const bashBackground = ctx.config.experimental?.bash?.background === true;
30038
- const anyBashExperimental = bashRewrite || bashCompress || bashBackground;
30039
- if (anyBashExperimental) {
30327
+ if (resolveBashConfig(ctx.config).enabled) {
30040
30328
  tools.bash = createBashTool(ctx);
30041
30329
  tools.bash_status = createBashStatusTool(ctx);
30042
30330
  tools.bash_kill = createBashKillTool(ctx);
@@ -30090,11 +30378,7 @@ function aftPrefixedTools(ctx) {
30090
30378
  aft_delete: createDeleteTool(ctx),
30091
30379
  aft_move: createMoveTool(ctx)
30092
30380
  };
30093
- const bashRewrite = ctx.config.experimental?.bash?.rewrite === true;
30094
- const bashCompress = ctx.config.experimental?.bash?.compress === true;
30095
- const bashBackground = ctx.config.experimental?.bash?.background === true;
30096
- const anyBashExperimental = bashRewrite || bashCompress || bashBackground;
30097
- if (anyBashExperimental) {
30381
+ if (resolveBashConfig(ctx.config).enabled) {
30098
30382
  tools.aft_bash = createBashTool(ctx);
30099
30383
  tools.bash_status = createBashStatusTool(ctx);
30100
30384
  tools.bash_kill = createBashKillTool(ctx);
@@ -31077,7 +31361,7 @@ function buildHintsFromConfig(config2, disabledTools) {
31077
31361
  toolSurface: config2.tool_surface ?? "recommended",
31078
31362
  hoistBuiltins: config2.hoist_builtin_tools !== false,
31079
31363
  semanticEnabled: config2.semantic_search === true,
31080
- bashBackgroundEnabled: config2.experimental?.bash?.background === true,
31364
+ bashBackgroundEnabled: resolveBashConfig(config2).background,
31081
31365
  disabledTools
31082
31366
  });
31083
31367
  }
@@ -31158,45 +31442,23 @@ var PLUGIN_VERSION = (() => {
31158
31442
  return "0.0.0";
31159
31443
  }
31160
31444
  })();
31161
- var ANNOUNCEMENT_VERSION = "0.18.0";
31445
+ var ANNOUNCEMENT_VERSION = "0.28.0";
31162
31446
  var ANNOUNCEMENT_FEATURES = [
31163
- `New experimental features \u2014 AFT now optionally hoists bash:
31164
- - Run bash scripts in the background.
31165
- - Initial output compression for git, cargo, npm, bun, pnpm, pytest, tsc (more in 0.19).
31166
- - Rewrite cat/grep/find/sed/ls into AFT counterparts for faster, formatted output.
31167
- Check GitHub for how to enable.`,
31168
- "Trigram grep/glob and semantic search (aft_search) graduated out of experimental.",
31169
- "Lots of bugfixes and new end-to-end test coverage."
31447
+ "Bash hoisting is now default-on. Configure with top-level `bash: { rewrite, compress, background }` instead of `experimental.bash.*` \u2014 old config migrates automatically on first launch.",
31448
+ "Vue, Astro, and Svelte language servers now auto-install when the framework appears in your package.json (fixes #48).",
31449
+ "Native Windows ARM64 binary \u2014 ARM64 hosts no longer fall back to x64 under emulation."
31170
31450
  ];
31451
+ var ANNOUNCEMENT_FOOTER = "Join us on Discord: https://discord.gg/F2uWxjGnU";
31171
31452
  var plugin = async (input) => initializePluginForDirectory(input);
31172
31453
  async function initializePluginForDirectory(input) {
31173
31454
  const binaryPath = await findBinary(PLUGIN_VERSION);
31174
31455
  await ensureStorageMigrated({ harness: "opencode", binaryPath, logger: bridgeLogger });
31175
31456
  const aftConfig = loadAftConfig(input.directory);
31176
31457
  const autoUpdateAbort = new AbortController;
31177
- const configOverrides = {};
31178
- if (aftConfig.format_on_edit !== undefined)
31179
- configOverrides.format_on_edit = aftConfig.format_on_edit;
31180
- if (aftConfig.formatter_timeout_secs !== undefined)
31181
- configOverrides.formatter_timeout_secs = aftConfig.formatter_timeout_secs;
31182
- if (aftConfig.validate_on_edit !== undefined)
31183
- configOverrides.validate_on_edit = aftConfig.validate_on_edit;
31184
- if (aftConfig.formatter !== undefined)
31185
- configOverrides.formatter = aftConfig.formatter;
31186
- if (aftConfig.checker !== undefined)
31187
- configOverrides.checker = aftConfig.checker;
31188
- configOverrides.restrict_to_project_root = aftConfig.restrict_to_project_root ?? false;
31189
- configOverrides.bash_permissions = true;
31190
- if (aftConfig.search_index !== undefined)
31191
- configOverrides.search_index = aftConfig.search_index;
31192
- if (aftConfig.semantic_search !== undefined)
31193
- configOverrides.semantic_search = aftConfig.semantic_search;
31194
- Object.assign(configOverrides, resolveExperimentalConfigForConfigure(aftConfig));
31195
- Object.assign(configOverrides, resolveLspConfigForConfigure(aftConfig));
31196
- if (aftConfig.semantic !== undefined)
31197
- configOverrides.semantic = aftConfig.semantic;
31198
- if (aftConfig.max_callgraph_files !== undefined)
31199
- configOverrides.max_callgraph_files = aftConfig.max_callgraph_files;
31458
+ const configOverrides = {
31459
+ ...resolveProjectOverridesForConfigure(aftConfig),
31460
+ bash_permissions: true
31461
+ };
31200
31462
  const isFastembedSemanticBackend = (aftConfig.semantic?.backend ?? "fastembed") === "fastembed";
31201
31463
  configOverrides.storage_dir = resolveCortexKitStorageRoot();
31202
31464
  let onnxRuntimePromise = null;
@@ -31277,6 +31539,15 @@ ${lines}
31277
31539
  const poolOptions = {
31278
31540
  errorPrefix: "[aft-plugin]",
31279
31541
  minVersion: PLUGIN_VERSION,
31542
+ projectConfigLoader: (projectRoot) => {
31543
+ try {
31544
+ const projectConfig = loadAftConfig(projectRoot);
31545
+ return resolveProjectOverridesForConfigure(projectConfig);
31546
+ } catch (err) {
31547
+ warn2(`loadAftConfig(${projectRoot}) failed; falling back to plugin-init config: ${err instanceof Error ? err.message : String(err)}`);
31548
+ return {};
31549
+ }
31550
+ },
31280
31551
  onVersionMismatch: async (binaryVersion, minVersion) => {
31281
31552
  const existing = versionUpgradePromises.get(minVersion);
31282
31553
  if (existing) {
@@ -31389,7 +31660,7 @@ ${lines}
31389
31660
  return {
31390
31661
  success: true,
31391
31662
  status: "not_initialized",
31392
- message: "Waiting for first tool call to populate"
31663
+ message: "AFT bridge is now spawned lazily, information here will be populated after first tool call."
31393
31664
  };
31394
31665
  }
31395
31666
  const cached2 = bridge.getCachedStatus();
@@ -31413,13 +31684,18 @@ ${lines}
31413
31684
  const versionFile = join22(storageDir, "last_announced_version");
31414
31685
  try {
31415
31686
  if (existsSync15(versionFile)) {
31416
- const lastVersion = readFileSync12(versionFile, "utf-8").trim();
31687
+ const lastVersion = readFileSync13(versionFile, "utf-8").trim();
31417
31688
  if (lastVersion === ANNOUNCEMENT_VERSION)
31418
31689
  return { show: false };
31419
31690
  }
31420
31691
  } catch {}
31421
31692
  }
31422
- return { show: true, version: ANNOUNCEMENT_VERSION, features: ANNOUNCEMENT_FEATURES };
31693
+ return {
31694
+ show: true,
31695
+ version: ANNOUNCEMENT_VERSION,
31696
+ features: ANNOUNCEMENT_FEATURES,
31697
+ footer: ANNOUNCEMENT_FOOTER
31698
+ };
31423
31699
  });
31424
31700
  rpcServer.handle("mark-announced", async () => {
31425
31701
  if (storageDir && ANNOUNCEMENT_VERSION) {
@@ -31453,7 +31729,7 @@ Install: ${getManualInstallHint()}`);
31453
31729
  };
31454
31730
  if (ANNOUNCEMENT_VERSION && ANNOUNCEMENT_FEATURES.length > 0) {
31455
31731
  setTimeout(() => {
31456
- sendFeatureAnnouncement(notifyOpts, ANNOUNCEMENT_VERSION, ANNOUNCEMENT_FEATURES, storageDir).catch(() => {});
31732
+ sendFeatureAnnouncement(notifyOpts, ANNOUNCEMENT_VERSION, ANNOUNCEMENT_FEATURES, ANNOUNCEMENT_FOOTER, storageDir).catch(() => {});
31457
31733
  }, 8000);
31458
31734
  }
31459
31735
  if (onnxRuntimePromise) {