@cortexkit/aft-pi 0.39.2 → 0.39.3

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
@@ -12940,6 +12940,208 @@ class BinaryBridge {
12940
12940
  }
12941
12941
  }
12942
12942
  }
12943
+ // ../aft-bridge/dist/callgraph-format.js
12944
+ import { homedir as homedir3 } from "node:os";
12945
+ var PLAIN_CALLGRAPH_THEME = {
12946
+ fg: (_role, text) => text
12947
+ };
12948
+ function asRecord(value) {
12949
+ if (!value || typeof value !== "object" || Array.isArray(value))
12950
+ return;
12951
+ return value;
12952
+ }
12953
+ function asRecords(value) {
12954
+ return Array.isArray(value) ? value.map(asRecord).filter(Boolean) : [];
12955
+ }
12956
+ function asString(value) {
12957
+ return typeof value === "string" ? value : undefined;
12958
+ }
12959
+ function asNumber(value) {
12960
+ return typeof value === "number" && Number.isFinite(value) ? value : undefined;
12961
+ }
12962
+ function asBoolean(value) {
12963
+ return typeof value === "boolean" ? value : undefined;
12964
+ }
12965
+ function shortenPath(path2) {
12966
+ const home = homedir3();
12967
+ if (path2.startsWith(home))
12968
+ return `~${path2.slice(home.length)}`;
12969
+ return path2;
12970
+ }
12971
+ function joinNonEmpty(parts, separator = " · ") {
12972
+ return parts.filter((part) => Boolean(part && part.length > 0)).join(separator);
12973
+ }
12974
+ function treeLine(depth, text) {
12975
+ return `${" ".repeat(depth)}${depth === 0 ? "" : "↳ "}${text}`;
12976
+ }
12977
+ function renderCallTreeNode(node, depth, lines, theme) {
12978
+ const name = asString(node.name) ?? "(unknown)";
12979
+ const file = shortenPath(asString(node.file) ?? "(unknown file)");
12980
+ const line = asNumber(node.line);
12981
+ const unresolved = node.resolved === false ? ` ${theme.fg("warning", "[unresolved]")}` : "";
12982
+ const location = line !== undefined ? `[${file}:${line}]` : `[${file}]`;
12983
+ lines.push(treeLine(depth, `${name} ${location}${unresolved}`));
12984
+ asRecords(node.children).forEach((child) => {
12985
+ renderCallTreeNode(child, depth + 1, lines, theme);
12986
+ });
12987
+ }
12988
+ function depthWarning(response, theme, depthField = "depth_limited", truncatedField = "truncated") {
12989
+ const limited = asBoolean(response[depthField]);
12990
+ const truncated = asNumber(response[truncatedField]) ?? 0;
12991
+ if (!limited && truncated === 0)
12992
+ return "";
12993
+ const detail = truncated > 0 ? `, ${truncated} truncated` : "";
12994
+ return theme.fg("warning", `(depth limited${detail})`);
12995
+ }
12996
+ function renderTracePath(path2, index, lines) {
12997
+ lines.push(`Path ${index + 1}`);
12998
+ asRecords(path2.hops).forEach((hop, hopIndex) => {
12999
+ const symbol = asString(hop.symbol) ?? "(unknown)";
13000
+ const file = shortenPath(asString(hop.file) ?? "(unknown file)");
13001
+ const line = asNumber(hop.line);
13002
+ const entry = hop.is_entry_point === true ? " [entry]" : "";
13003
+ lines.push(treeLine(hopIndex + 1, `${symbol}${entry} ${line !== undefined ? `[${file}:${line}]` : `[${file}]`}`));
13004
+ });
13005
+ }
13006
+ function renderCallersGroupLines(group, theme) {
13007
+ const file = shortenPath(asString(group.file) ?? "(unknown file)");
13008
+ const lines = [theme.fg("accent", file)];
13009
+ const callers = asRecords(group.callers);
13010
+ const bySymbol = new Map;
13011
+ for (const caller of callers) {
13012
+ const symbol = asString(caller.symbol) ?? "(unknown)";
13013
+ const line = asNumber(caller.line);
13014
+ const bucket = bySymbol.get(symbol) ?? [];
13015
+ if (line !== undefined)
13016
+ bucket.push(line);
13017
+ bySymbol.set(symbol, bucket);
13018
+ }
13019
+ const symbols = [...bySymbol.keys()].sort((a, b) => a.localeCompare(b));
13020
+ for (const symbol of symbols) {
13021
+ const lineNums = (bySymbol.get(symbol) ?? []).sort((a, b) => a - b);
13022
+ const linePart = lineNums.length > 0 ? lineNums.map(String).join(", ") : "?";
13023
+ lines.push(` ↳ ${symbol}:${linePart}`);
13024
+ }
13025
+ return lines;
13026
+ }
13027
+ function formatCallgraphSections(op, response, theme = PLAIN_CALLGRAPH_THEME) {
13028
+ const record = asRecord(response);
13029
+ if (!record)
13030
+ return [theme.fg("muted", "No navigation result.")];
13031
+ if (op === "call_tree") {
13032
+ const lines = [];
13033
+ renderCallTreeNode(record, 0, lines, theme);
13034
+ const warning = depthWarning(record, theme);
13035
+ if (warning)
13036
+ lines.push(warning);
13037
+ return lines.length > 0 ? lines : [theme.fg("muted", "No call tree available.")];
13038
+ }
13039
+ if (op === "callers") {
13040
+ const groups = asRecords(record.callers);
13041
+ const warning = depthWarning(record, theme);
13042
+ const total = asNumber(record.total_callers) ?? 0;
13043
+ const sections2 = [
13044
+ joinNonEmpty([
13045
+ theme.fg("success", `${total} caller${total === 1 ? "" : "s"}`),
13046
+ theme.fg("muted", `${groups.length} file group${groups.length === 1 ? "" : "s"}`),
13047
+ warning
13048
+ ])
13049
+ ];
13050
+ groups.forEach((group) => {
13051
+ sections2.push(renderCallersGroupLines(group, theme).join(`
13052
+ `));
13053
+ });
13054
+ return sections2;
13055
+ }
13056
+ if (op === "trace_to_symbol") {
13057
+ const path2 = asRecords(record.path);
13058
+ const complete = asBoolean(record.complete);
13059
+ const reason = asString(record.reason);
13060
+ if (path2.length === 0) {
13061
+ const prefix = complete === false ? theme.fg("warning", "No complete path") : theme.fg("muted", "No path");
13062
+ return [`${prefix}${reason ? ` (${reason})` : ""}`];
13063
+ }
13064
+ const lines = [theme.fg("success", `${path2.length} hop${path2.length === 1 ? "" : "s"}`)];
13065
+ path2.forEach((hop, index) => {
13066
+ const symbol = asString(hop.symbol) ?? "(unknown)";
13067
+ const file = shortenPath(asString(hop.file) ?? "(unknown file)");
13068
+ const line = asNumber(hop.line);
13069
+ lines.push(treeLine(index + 1, `${symbol} ${line !== undefined ? `[${file}:${line}]` : `[${file}]`}`));
13070
+ });
13071
+ return lines;
13072
+ }
13073
+ if (op === "trace_to") {
13074
+ const paths = asRecords(record.paths);
13075
+ const warning = depthWarning(record, theme, "max_depth_reached", "truncated_paths");
13076
+ const totalPaths = asNumber(record.total_paths) ?? paths.length;
13077
+ const entryPoints = asNumber(record.entry_points_found) ?? 0;
13078
+ const sections2 = [
13079
+ joinNonEmpty([
13080
+ theme.fg("success", `${totalPaths} path${totalPaths === 1 ? "" : "s"}`),
13081
+ theme.fg("muted", `${entryPoints} entry point${entryPoints === 1 ? "" : "s"}`),
13082
+ warning
13083
+ ])
13084
+ ];
13085
+ if (paths.length === 0)
13086
+ sections2.push(theme.fg("muted", "No entry paths found."));
13087
+ paths.forEach((path2, index) => {
13088
+ const lines = [];
13089
+ renderTracePath(path2, index, lines);
13090
+ sections2.push(lines.join(`
13091
+ `));
13092
+ });
13093
+ return sections2;
13094
+ }
13095
+ if (op === "impact") {
13096
+ const callers = asRecords(record.callers);
13097
+ const warning = depthWarning(record, theme);
13098
+ const totalAffected = asNumber(record.total_affected) ?? callers.length;
13099
+ const affectedFiles = asNumber(record.affected_files) ?? 0;
13100
+ const sections2 = [
13101
+ joinNonEmpty([
13102
+ theme.fg("warning", `${totalAffected} affected call site${totalAffected === 1 ? "" : "s"}`),
13103
+ theme.fg("muted", `${affectedFiles} file${affectedFiles === 1 ? "" : "s"}`),
13104
+ warning
13105
+ ])
13106
+ ];
13107
+ if (callers.length === 0)
13108
+ sections2.push(theme.fg("muted", "No impacted callers found."));
13109
+ callers.forEach((caller) => {
13110
+ const file = shortenPath(asString(caller.caller_file) ?? "(unknown file)");
13111
+ const symbol = asString(caller.caller_symbol) ?? "(unknown)";
13112
+ const line = asNumber(caller.line) ?? 0;
13113
+ const entry = caller.is_entry_point === true ? ` ${theme.fg("warning", "[entry]")}` : "";
13114
+ const expression = asString(caller.call_expression);
13115
+ const params = Array.isArray(caller.parameters) ? caller.parameters.map(String).join(", ") : "";
13116
+ sections2.push([
13117
+ `${theme.fg("accent", file)}:${line}`,
13118
+ ` ↳ ${symbol}${entry}`,
13119
+ expression ? ` ${theme.fg("muted", expression)}` : undefined,
13120
+ params ? ` ${theme.fg("muted", `params: ${params}`)}` : undefined
13121
+ ].filter(Boolean).join(`
13122
+ `));
13123
+ });
13124
+ return sections2;
13125
+ }
13126
+ const hops = asRecords(record.hops);
13127
+ const sections = [
13128
+ joinNonEmpty([
13129
+ theme.fg("success", `${hops.length} hop${hops.length === 1 ? "" : "s"}`),
13130
+ asBoolean(record.depth_limited) ? theme.fg("warning", "(depth limited)") : undefined
13131
+ ])
13132
+ ];
13133
+ if (hops.length === 0)
13134
+ sections.push(theme.fg("muted", "No data-flow hops found."));
13135
+ hops.forEach((hop, index) => {
13136
+ const file = shortenPath(asString(hop.file) ?? "(unknown file)");
13137
+ const symbol = asString(hop.symbol) ?? "(unknown)";
13138
+ const variable = asString(hop.variable) ?? "(unknown)";
13139
+ const line = asNumber(hop.line) ?? 0;
13140
+ const approximate = hop.approximate === true ? ` ${theme.fg("warning", "[approx]")}` : "";
13141
+ sections.push(treeLine(index, `${variable} ${theme.fg("muted", `${asString(hop.flow_type) ?? "flow"}`)} ${symbol} [${file}:${line}]${approximate}`));
13142
+ });
13143
+ return sections;
13144
+ }
12943
13145
  // ../aft-bridge/dist/coerce.js
12944
13146
  function coerceStringArray(value) {
12945
13147
  if (Array.isArray(value)) {
@@ -12990,7 +13192,7 @@ function isEmptyParam(value) {
12990
13192
  import { spawnSync } from "node:child_process";
12991
13193
  import { createHash, randomUUID } from "node:crypto";
12992
13194
  import { chmodSync, closeSync, copyFileSync, createWriteStream, existsSync, mkdirSync, openSync, readFileSync, renameSync, rmSync, statSync, unlinkSync, writeSync } from "node:fs";
12993
- import { homedir as homedir3 } from "node:os";
13195
+ import { homedir as homedir4 } from "node:os";
12994
13196
  import { join as join3 } from "node:path";
12995
13197
  import { Readable } from "node:stream";
12996
13198
  import { pipeline } from "node:stream/promises";
@@ -13048,10 +13250,10 @@ function isExpectedCachedBinary(binaryPath, tag) {
13048
13250
  function getCacheDir() {
13049
13251
  if (process.platform === "win32") {
13050
13252
  const localAppData = process.env.LOCALAPPDATA || process.env.APPDATA;
13051
- const base2 = localAppData || join3(homedir3(), "AppData", "Local");
13253
+ const base2 = localAppData || join3(homedir4(), "AppData", "Local");
13052
13254
  return join3(base2, "aft", "bin");
13053
13255
  }
13054
- const base = process.env.XDG_CACHE_HOME || join3(homedir3(), ".cache");
13256
+ const base = process.env.XDG_CACHE_HOME || join3(homedir4(), ".cache");
13055
13257
  return join3(base, "aft", "bin");
13056
13258
  }
13057
13259
  function getBinaryName() {
@@ -13331,7 +13533,7 @@ function stripJsoncSymbols(value) {
13331
13533
  // ../aft-bridge/dist/migration.js
13332
13534
  import { spawnSync as spawnSync2 } from "node:child_process";
13333
13535
  import { existsSync as existsSync4, mkdirSync as mkdirSync4 } from "node:fs";
13334
- import { homedir as homedir5, tmpdir } from "node:os";
13536
+ import { homedir as homedir6, tmpdir } from "node:os";
13335
13537
  import { dirname as dirname2, join as join6 } from "node:path";
13336
13538
 
13337
13539
  // ../aft-bridge/dist/paths.js
@@ -13388,7 +13590,7 @@ function markAnnouncementSeen(storageRoot, harness, currentVersion) {
13388
13590
  import { execSync } from "node:child_process";
13389
13591
  import { chmodSync as chmodSync2, closeSync as closeSync2, copyFileSync as copyFileSync2, existsSync as existsSync3, mkdirSync as mkdirSync3, openSync as openSync2, readSync, renameSync as renameSync3, unlinkSync as unlinkSync2 } from "node:fs";
13390
13592
  import { createRequire as createRequire2 } from "node:module";
13391
- import { homedir as homedir4 } from "node:os";
13593
+ import { homedir as homedir5 } from "node:os";
13392
13594
  import { join as join5 } from "node:path";
13393
13595
  var ensureBinaryForResolver = ensureBinary;
13394
13596
  function copyToVersionedCache(npmBinaryPath, knownVersion) {
@@ -13430,7 +13632,7 @@ function normalizeBareVersion(version) {
13430
13632
  return version.startsWith("v") ? version.slice(1) : version;
13431
13633
  }
13432
13634
  function homeDirFromEnv(env) {
13433
- return (process.platform === "win32" ? env.USERPROFILE || env.HOME : env.HOME) || homedir4();
13635
+ return (process.platform === "win32" ? env.USERPROFILE || env.HOME : env.HOME) || homedir5();
13434
13636
  }
13435
13637
  function cacheDirFromEnv(env) {
13436
13638
  if (process.platform === "win32") {
@@ -13612,8 +13814,8 @@ function dataHome() {
13612
13814
  }
13613
13815
  function homeDir() {
13614
13816
  if (process.platform === "win32")
13615
- return process.env.USERPROFILE || process.env.HOME || homedir5();
13616
- return process.env.HOME || homedir5();
13817
+ return process.env.USERPROFILE || process.env.HOME || homedir6();
13818
+ return process.env.HOME || homedir6();
13617
13819
  }
13618
13820
  function resolveLegacyStorageRoot(harness) {
13619
13821
  if (harness === "pi")
@@ -13703,13 +13905,13 @@ async function ensureStorageMigrated(opts) {
13703
13905
  }
13704
13906
  // ../aft-bridge/dist/npm-resolver.js
13705
13907
  import { readdirSync, statSync as statSync2 } from "node:fs";
13706
- import { homedir as homedir6 } from "node:os";
13908
+ import { homedir as homedir7 } from "node:os";
13707
13909
  import { delimiter, dirname as dirname3, isAbsolute as isAbsolute2, join as join7 } from "node:path";
13708
13910
  function defaultDeps() {
13709
13911
  return {
13710
13912
  platform: process.platform,
13711
13913
  env: process.env,
13712
- home: homedir6(),
13914
+ home: homedir7(),
13713
13915
  execPath: process.execPath
13714
13916
  };
13715
13917
  }
@@ -15044,13 +15246,13 @@ function tokenizeStage(stage) {
15044
15246
  }
15045
15247
  // ../aft-bridge/dist/pool.js
15046
15248
  import { realpathSync as realpathSync2 } from "node:fs";
15047
- import { homedir as homedir7 } from "node:os";
15249
+ import { homedir as homedir8 } from "node:os";
15048
15250
  var DEFAULT_IDLE_TIMEOUT_MS = 30 * 60 * 1000;
15049
15251
  var DEFAULT_MAX_POOL_SIZE = 8;
15050
15252
  var CLEANUP_INTERVAL_MS = 60 * 1000;
15051
15253
  function canonicalHomeDir() {
15052
15254
  try {
15053
- const home = homedir7();
15255
+ const home = homedir8();
15054
15256
  if (!home)
15055
15257
  return null;
15056
15258
  try {
@@ -16073,7 +16275,7 @@ import {
16073
16275
  // package.json
16074
16276
  var package_default = {
16075
16277
  name: "@cortexkit/aft-pi",
16076
- version: "0.39.2",
16278
+ version: "0.39.3",
16077
16279
  type: "module",
16078
16280
  description: "Pi coding agent extension for Agent File Tools (AFT) — tree-sitter and LSP-powered code analysis",
16079
16281
  main: "dist/index.js",
@@ -16096,7 +16298,7 @@ var package_default = {
16096
16298
  "test:unit": "bun test src/__tests__ --path-ignore-patterns 'src/__tests__/e2e/**'"
16097
16299
  },
16098
16300
  dependencies: {
16099
- "@cortexkit/aft-bridge": "0.39.2",
16301
+ "@cortexkit/aft-bridge": "0.39.3",
16100
16302
  "@xterm/headless": "^5.5.0",
16101
16303
  "comment-json": "^5.0.0",
16102
16304
  diff: "^8.0.4",
@@ -16104,12 +16306,12 @@ var package_default = {
16104
16306
  zod: "^4.1.8"
16105
16307
  },
16106
16308
  optionalDependencies: {
16107
- "@cortexkit/aft-darwin-arm64": "0.39.2",
16108
- "@cortexkit/aft-darwin-x64": "0.39.2",
16109
- "@cortexkit/aft-linux-arm64": "0.39.2",
16110
- "@cortexkit/aft-linux-x64": "0.39.2",
16111
- "@cortexkit/aft-win32-arm64": "0.39.2",
16112
- "@cortexkit/aft-win32-x64": "0.39.2"
16309
+ "@cortexkit/aft-darwin-arm64": "0.39.3",
16310
+ "@cortexkit/aft-darwin-x64": "0.39.3",
16311
+ "@cortexkit/aft-linux-arm64": "0.39.3",
16312
+ "@cortexkit/aft-linux-x64": "0.39.3",
16313
+ "@cortexkit/aft-win32-arm64": "0.39.3",
16314
+ "@cortexkit/aft-win32-x64": "0.39.3"
16113
16315
  },
16114
16316
  devDependencies: {
16115
16317
  "@earendil-works/pi-coding-agent": "*",
@@ -16136,7 +16338,7 @@ var package_default = {
16136
16338
  };
16137
16339
 
16138
16340
  // src/shared/status.ts
16139
- function asRecord(value) {
16341
+ function asRecord2(value) {
16140
16342
  return typeof value === "object" && value !== null ? value : {};
16141
16343
  }
16142
16344
  function readString(value, fallback = "") {
@@ -16155,7 +16357,7 @@ function readOptionalNumber(value) {
16155
16357
  return typeof value === "number" && Number.isFinite(value) ? value : null;
16156
16358
  }
16157
16359
  function readCompressionAggregate(value) {
16158
- const aggregate = asRecord(value);
16360
+ const aggregate = asRecord2(value);
16159
16361
  return {
16160
16362
  events: readNumber(aggregate.events),
16161
16363
  original_tokens: readNumber(aggregate.original_tokens),
@@ -16166,7 +16368,7 @@ function readCompressionAggregate(value) {
16166
16368
  function readCompression(value) {
16167
16369
  if (typeof value !== "object" || value === null)
16168
16370
  return;
16169
- const compression = asRecord(value);
16371
+ const compression = asRecord2(value);
16170
16372
  return {
16171
16373
  project: readCompressionAggregate(compression.project),
16172
16374
  session: readCompressionAggregate(compression.session)
@@ -16175,7 +16377,7 @@ function readCompression(value) {
16175
16377
  function readStatusBar(value) {
16176
16378
  if (typeof value !== "object" || value === null)
16177
16379
  return;
16178
- const bar = asRecord(value);
16380
+ const bar = asRecord2(value);
16179
16381
  return {
16180
16382
  errors: readNumber(bar.errors),
16181
16383
  warnings: readNumber(bar.warnings),
@@ -16219,16 +16421,16 @@ function formatBytes(bytes) {
16219
16421
  return `${value.toFixed(decimals)} ${units[unitIndex]}`;
16220
16422
  }
16221
16423
  function coerceAftStatus(response) {
16222
- const features = asRecord(response.features);
16223
- const searchIndex = asRecord(response.search_index);
16224
- const semanticIndex = asRecord(response.semantic_index);
16424
+ const features = asRecord2(response.features);
16425
+ const searchIndex = asRecord2(response.search_index);
16426
+ const semanticIndex = asRecord2(response.semantic_index);
16225
16427
  const semanticConfig = {
16226
- ...asRecord(response.semantic),
16227
- ...asRecord(response.semantic_config)
16428
+ ...asRecord2(response.semantic),
16429
+ ...asRecord2(response.semantic_config)
16228
16430
  };
16229
- const disk = asRecord(response.disk);
16230
- const symbolCache = asRecord(response.symbol_cache);
16231
- const session = asRecord(response.session);
16431
+ const disk = asRecord2(response.disk);
16432
+ const symbolCache = asRecord2(response.symbol_cache);
16433
+ const session = asRecord2(response.session);
16232
16434
  return {
16233
16435
  version: readString(response.version, "unknown"),
16234
16436
  project_root: readNullableString(response.project_root),
@@ -16684,7 +16886,7 @@ function registerStatusCommand(pi, ctx) {
16684
16886
 
16685
16887
  // src/config.ts
16686
16888
  import { existsSync as existsSync6, readFileSync as readFileSync4, renameSync as renameSync4, unlinkSync as unlinkSync4, writeFileSync as writeFileSync3 } from "node:fs";
16687
- import { homedir as homedir8 } from "node:os";
16889
+ import { homedir as homedir9 } from "node:os";
16688
16890
  import { join as join10 } from "node:path";
16689
16891
  var import_comment_json = __toESM(require_src2(), 1);
16690
16892
 
@@ -30381,6 +30583,8 @@ var AftConfigSchema = exports_external.object({
30381
30583
  restrict_to_project_root: exports_external.boolean().optional(),
30382
30584
  search_index: exports_external.boolean().optional(),
30383
30585
  semantic_search: exports_external.boolean().optional(),
30586
+ callgraph_store: exports_external.boolean().optional(),
30587
+ callgraph_chunk_size: exports_external.number().optional(),
30384
30588
  inspect: InspectConfigSchema.optional(),
30385
30589
  bash: BashConfigSchema.optional(),
30386
30590
  experimental: ExperimentalConfigSchema.optional(),
@@ -30441,6 +30645,37 @@ function resolveLspConfigForConfigure(config2) {
30441
30645
  }
30442
30646
  return overrides;
30443
30647
  }
30648
+ function resolveProjectOverridesForConfigure(config2) {
30649
+ const overrides = {};
30650
+ if (config2.format_on_edit !== undefined)
30651
+ overrides.format_on_edit = config2.format_on_edit;
30652
+ if (config2.formatter_timeout_secs !== undefined)
30653
+ overrides.formatter_timeout_secs = config2.formatter_timeout_secs;
30654
+ if (config2.validate_on_edit !== undefined)
30655
+ overrides.validate_on_edit = config2.validate_on_edit;
30656
+ if (config2.formatter !== undefined)
30657
+ overrides.formatter = config2.formatter;
30658
+ if (config2.checker !== undefined)
30659
+ overrides.checker = config2.checker;
30660
+ overrides.restrict_to_project_root = config2.restrict_to_project_root ?? false;
30661
+ if (config2.search_index !== undefined)
30662
+ overrides.search_index = config2.search_index;
30663
+ if (config2.semantic_search !== undefined)
30664
+ overrides.semantic_search = config2.semantic_search;
30665
+ if (config2.callgraph_store !== undefined)
30666
+ overrides.callgraph_store = config2.callgraph_store;
30667
+ if (config2.callgraph_chunk_size !== undefined)
30668
+ overrides.callgraph_chunk_size = config2.callgraph_chunk_size;
30669
+ Object.assign(overrides, resolveExperimentalConfigForConfigure(config2));
30670
+ Object.assign(overrides, resolveLspConfigForConfigure(config2));
30671
+ if (config2.semantic !== undefined)
30672
+ overrides.semantic = config2.semantic;
30673
+ if (config2.inspect !== undefined)
30674
+ overrides.inspect = config2.inspect;
30675
+ if (config2.max_callgraph_files !== undefined)
30676
+ overrides.max_callgraph_files = config2.max_callgraph_files;
30677
+ return overrides;
30678
+ }
30444
30679
  function resolveExperimentalConfigForConfigure(config2) {
30445
30680
  const overrides = {};
30446
30681
  const bash = resolveBashConfig(config2);
@@ -30617,6 +30852,17 @@ function detectConfigFile(basePath) {
30617
30852
  return { format: "json", path: jsonPath };
30618
30853
  return { format: "none", path: jsonPath };
30619
30854
  }
30855
+ var configLoadErrors = [];
30856
+ function getConfigLoadErrors() {
30857
+ return configLoadErrors;
30858
+ }
30859
+ function formatConfigParseFailureMessage(configPath, errorMessage) {
30860
+ return `AFT config at ${configPath} failed to parse and was ignored (running on defaults): ${errorMessage}. ` + "Fix the syntax or run `npx @cortexkit/aft doctor`.";
30861
+ }
30862
+ function recordConfigParseFailure(configPath, errorMessage) {
30863
+ configLoadErrors.push({ path: configPath, message: errorMessage });
30864
+ warn2(formatConfigParseFailureMessage(configPath, errorMessage));
30865
+ }
30620
30866
  function loadConfigFromPath(configPath) {
30621
30867
  try {
30622
30868
  if (!existsSync6(configPath))
@@ -30624,7 +30870,7 @@ function loadConfigFromPath(configPath) {
30624
30870
  const content = readFileSync4(configPath, "utf-8");
30625
30871
  const rawConfig = import_comment_json.parse(content);
30626
30872
  if (!rawConfig || typeof rawConfig !== "object" || Array.isArray(rawConfig)) {
30627
- warn2(`Config validation error in ${configPath}: root must be an object`);
30873
+ recordConfigParseFailure(configPath, "root must be an object");
30628
30874
  return null;
30629
30875
  }
30630
30876
  migrateRawConfig(rawConfig, configPath, { log: log2, warn: warn2 });
@@ -30640,6 +30886,7 @@ function loadConfigFromPath(configPath) {
30640
30886
  } catch (err) {
30641
30887
  const errorMsg = err instanceof Error ? err.message : String(err);
30642
30888
  error2(`Error loading config from ${configPath}: ${errorMsg}`);
30889
+ recordConfigParseFailure(configPath, errorMsg);
30643
30890
  return null;
30644
30891
  }
30645
30892
  }
@@ -30775,6 +31022,8 @@ var PROJECT_SAFE_TOP_LEVEL_FIELDS = new Set([
30775
31022
  "configure_warnings_delivery",
30776
31023
  "search_index",
30777
31024
  "semantic_search",
31025
+ "callgraph_store",
31026
+ "callgraph_chunk_size",
30778
31027
  "inspect",
30779
31028
  "experimental",
30780
31029
  "bash"
@@ -30836,9 +31085,10 @@ function resolveBridgePoolTransportOptions(config2) {
30836
31085
  };
30837
31086
  }
30838
31087
  function getGlobalPiDir() {
30839
- return join10(homedir8(), ".pi", "agent");
31088
+ return join10(homedir9(), ".pi", "agent");
30840
31089
  }
30841
31090
  function loadAftConfig(projectDirectory) {
31091
+ configLoadErrors = [];
30842
31092
  const userBasePath = join10(getGlobalPiDir(), "aft");
30843
31093
  migrateAftConfigFile(`${userBasePath}.jsonc`);
30844
31094
  migrateAftConfigFile(`${userBasePath}.json`);
@@ -30893,7 +31143,7 @@ import {
30893
31143
  unlinkSync as unlinkSync5,
30894
31144
  writeFileSync as writeFileSync4
30895
31145
  } from "node:fs";
30896
- import { homedir as homedir9 } from "node:os";
31146
+ import { homedir as homedir10 } from "node:os";
30897
31147
  import { join as join11 } from "node:path";
30898
31148
  function aftCacheBase() {
30899
31149
  const override = process.env.AFT_CACHE_DIR;
@@ -30901,10 +31151,10 @@ function aftCacheBase() {
30901
31151
  return override;
30902
31152
  if (process.platform === "win32") {
30903
31153
  const localAppData = process.env.LOCALAPPDATA || process.env.APPDATA;
30904
- const base2 = localAppData || join11(homedir9(), "AppData", "Local");
31154
+ const base2 = localAppData || join11(homedir10(), "AppData", "Local");
30905
31155
  return join11(base2, "aft");
30906
31156
  }
30907
- const base = process.env.XDG_CACHE_HOME || join11(homedir9(), ".cache");
31157
+ const base = process.env.XDG_CACHE_HOME || join11(homedir10(), ".cache");
30908
31158
  return join11(base, "aft");
30909
31159
  }
30910
31160
  function lspCacheRoot() {
@@ -32617,6 +32867,8 @@ function warningTitle(warning) {
32617
32867
  return "Checker is not installed";
32618
32868
  case "lsp_binary_missing":
32619
32869
  return "LSP binary is missing";
32870
+ case "config_parse_failed":
32871
+ return "Config failed to parse";
32620
32872
  }
32621
32873
  }
32622
32874
  function formatConfigureWarning(warning) {
@@ -32862,7 +33114,7 @@ import { Type as Type3 } from "typebox";
32862
33114
 
32863
33115
  // src/tools/hoisted.ts
32864
33116
  import { stat } from "node:fs/promises";
32865
- import { homedir as homedir10 } from "node:os";
33117
+ import { homedir as homedir11 } from "node:os";
32866
33118
  import { isAbsolute as isAbsolute4, relative as relative4, resolve as resolve4, sep } from "node:path";
32867
33119
  import {
32868
33120
  renderDiff
@@ -32984,9 +33236,9 @@ function expandTilde2(path3) {
32984
33236
  if (!path3 || !path3.startsWith("~"))
32985
33237
  return path3;
32986
33238
  if (path3 === "~")
32987
- return homedir10();
33239
+ return homedir11();
32988
33240
  if (path3.startsWith(`~${sep}`) || path3.startsWith("~/")) {
32989
- return resolve4(homedir10(), path3.slice(2));
33241
+ return resolve4(homedir11(), path3.slice(2));
32990
33242
  }
32991
33243
  return path3;
32992
33244
  }
@@ -33380,7 +33632,7 @@ function reuseContainer(last) {
33380
33632
  }
33381
33633
  function renderMutationCall(toolName, filePath, theme, context) {
33382
33634
  const text = reuseText(context.lastComponent);
33383
- const pathDisplay = filePath ? theme.fg("accent", shortenPath(filePath)) : theme.fg("toolOutput", "...");
33635
+ const pathDisplay = filePath ? theme.fg("accent", shortenPath2(filePath)) : theme.fg("toolOutput", "...");
33384
33636
  text.setText(`${theme.fg("toolTitle", theme.bold(toolName))} ${pathDisplay}`);
33385
33637
  return text;
33386
33638
  }
@@ -33416,8 +33668,8 @@ ${summary}${suffix}`);
33416
33668
  container.addChild(new Text(renderDiff(diff), 1, 0));
33417
33669
  return container;
33418
33670
  }
33419
- function shortenPath(path3) {
33420
- const home = homedir10();
33671
+ function shortenPath2(path3) {
33672
+ const home = homedir11();
33421
33673
  if (path3.startsWith(home))
33422
33674
  return `~${path3.slice(home.length)}`;
33423
33675
  return path3;
@@ -33467,7 +33719,7 @@ function formatReadFooter2(agentSpecifiedRange, data) {
33467
33719
  }
33468
33720
 
33469
33721
  // src/tools/render-helpers.ts
33470
- import { homedir as homedir11 } from "node:os";
33722
+ import { homedir as homedir12 } from "node:os";
33471
33723
  import { renderDiff as renderDiff2 } from "@earendil-works/pi-coding-agent";
33472
33724
  import { Container as Container2, Spacer as Spacer2, Text as Text2 } from "@earendil-works/pi-tui";
33473
33725
  function reuseText2(last) {
@@ -33476,8 +33728,8 @@ function reuseText2(last) {
33476
33728
  function reuseContainer2(last) {
33477
33729
  return last instanceof Container2 ? last : new Container2;
33478
33730
  }
33479
- function shortenPath2(path3) {
33480
- const home = homedir11();
33731
+ function shortenPath3(path3) {
33732
+ const home = homedir12();
33481
33733
  if (path3.startsWith(home))
33482
33734
  return `~${path3.slice(home.length)}`;
33483
33735
  return path3;
@@ -33491,7 +33743,7 @@ function renderToolCall(toolName, summary, theme, context) {
33491
33743
  function accentPath(theme, path3) {
33492
33744
  if (!path3)
33493
33745
  return theme.fg("toolOutput", "...");
33494
- return theme.fg("accent", shortenPath2(path3));
33746
+ return theme.fg("accent", shortenPath3(path3));
33495
33747
  }
33496
33748
  function collectTextContent(result) {
33497
33749
  return result.content.filter((part) => part.type === "text").map((part) => part.text ?? "").join(`
@@ -33530,23 +33782,20 @@ function renderSections(sections, context) {
33530
33782
  });
33531
33783
  return container;
33532
33784
  }
33533
- function asRecord2(value) {
33785
+ function asRecord3(value) {
33534
33786
  if (!value || typeof value !== "object" || Array.isArray(value))
33535
33787
  return;
33536
33788
  return value;
33537
33789
  }
33538
- function asRecords(value) {
33539
- return Array.isArray(value) ? value.map(asRecord2).filter(Boolean) : [];
33790
+ function asRecords2(value) {
33791
+ return Array.isArray(value) ? value.map(asRecord3).filter(Boolean) : [];
33540
33792
  }
33541
- function asString(value) {
33793
+ function asString2(value) {
33542
33794
  return typeof value === "string" ? value : undefined;
33543
33795
  }
33544
- function asNumber(value) {
33796
+ function asNumber2(value) {
33545
33797
  return typeof value === "number" && Number.isFinite(value) ? value : undefined;
33546
33798
  }
33547
- function asBoolean(value) {
33548
- return typeof value === "boolean" ? value : undefined;
33549
- }
33550
33799
  function formatValue(value) {
33551
33800
  if (Array.isArray(value))
33552
33801
  return value.map(formatValue).join(", ");
@@ -33659,7 +33908,7 @@ var ReplaceParams = Type3.Object({
33659
33908
  dryRun: Type3.Optional(Type3.Boolean({ description: "Preview without applying (default: false)" }))
33660
33909
  });
33661
33910
  function appendHintSection(response, sections, theme) {
33662
- const hint = asString(response.hint);
33911
+ const hint = asString2(response.hint);
33663
33912
  if (hint && hint.length > 0) {
33664
33913
  sections.push(theme.fg("warning", hint));
33665
33914
  }
@@ -33668,8 +33917,8 @@ function appendScopeSections(response, sections, theme) {
33668
33917
  if (response.no_files_matched_scope === true) {
33669
33918
  sections.push(theme.fg("warning", "No files matched the scope (paths/globs resolved to zero files)"));
33670
33919
  }
33671
- const warnings = asRecords(response.scope_warnings);
33672
- const warningStrings = Array.isArray(response.scope_warnings) ? response.scope_warnings.filter((w) => typeof w === "string") : warnings.map((w) => asString(w.warning) ?? "").filter(Boolean);
33920
+ const warnings = asRecords2(response.scope_warnings);
33921
+ const warningStrings = Array.isArray(response.scope_warnings) ? response.scope_warnings.filter((w) => typeof w === "string") : warnings.map((w) => asString2(w.warning) ?? "").filter(Boolean);
33673
33922
  if (warningStrings.length > 0) {
33674
33923
  sections.push(`${theme.fg("muted", "Scope warnings:")}
33675
33924
  ${warningStrings.map((w) => ` ${w}`).join(`
@@ -33693,13 +33942,13 @@ async function assertAstPathsPermission(extCtx, paths, action, restrictToProject
33693
33942
  }
33694
33943
  }
33695
33944
  function buildAstSearchSections(payload, theme) {
33696
- const response = asRecord2(payload);
33945
+ const response = asRecord3(payload);
33697
33946
  if (!response)
33698
33947
  return [theme.fg("muted", "No AST search results.")];
33699
- const matches = asRecords(response.matches);
33700
- const totalMatches = asNumber(response.total_matches) ?? matches.length;
33701
- const filesWithMatches = asNumber(response.files_with_matches) ?? groupByFile(matches, (match) => asString(match.file)).size;
33702
- const filesSearched = asNumber(response.files_searched);
33948
+ const matches = asRecords2(response.matches);
33949
+ const totalMatches = asNumber2(response.total_matches) ?? matches.length;
33950
+ const filesWithMatches = asNumber2(response.files_with_matches) ?? groupByFile(matches, (match) => asString2(match.file)).size;
33951
+ const filesSearched = asNumber2(response.files_searched);
33703
33952
  const header = [
33704
33953
  theme.fg("success", `${totalMatches} match${totalMatches === 1 ? "" : "es"}`),
33705
33954
  theme.fg("accent", `${filesWithMatches} file${filesWithMatches === 1 ? "" : "s"}`),
@@ -33711,26 +33960,26 @@ function buildAstSearchSections(payload, theme) {
33711
33960
  appendHintSection(response, sections2, theme);
33712
33961
  return sections2;
33713
33962
  }
33714
- const grouped = groupByFile(matches, (match) => asString(match.file));
33963
+ const grouped = groupByFile(matches, (match) => asString2(match.file));
33715
33964
  const sections = [header];
33716
33965
  for (const [file2, fileMatches] of grouped.entries()) {
33717
- const lines = [theme.fg("accent", shortenPath2(file2))];
33966
+ const lines = [theme.fg("accent", shortenPath3(file2))];
33718
33967
  fileMatches.forEach((match, index) => {
33719
- const line = asNumber(match.line) ?? 0;
33720
- const column = asNumber(match.column) ?? 0;
33721
- const snippet = asString(match.text)?.trim() || "(empty match)";
33968
+ const line = asNumber2(match.line) ?? 0;
33969
+ const column = asNumber2(match.column) ?? 0;
33970
+ const snippet = asString2(match.text)?.trim() || "(empty match)";
33722
33971
  lines.push(` ${index + 1}. ${theme.fg("muted", `${line}:${column}`)} ${snippet}`);
33723
- const metaVars = asRecord2(match.meta_variables);
33972
+ const metaVars = asRecord3(match.meta_variables);
33724
33973
  if (metaVars && Object.keys(metaVars).length > 0) {
33725
33974
  Object.entries(metaVars).forEach(([name, value]) => {
33726
33975
  lines.push(` ${theme.fg("muted", `${name} =`)} ${formatValue(value)}`);
33727
33976
  });
33728
33977
  }
33729
- const context = asRecords(match.context);
33978
+ const context = asRecords2(match.context);
33730
33979
  context.forEach((ctxLine) => {
33731
- const ctxNumber = asNumber(ctxLine.line) ?? 0;
33980
+ const ctxNumber = asNumber2(ctxLine.line) ?? 0;
33732
33981
  const prefix = ctxLine.is_match === true ? theme.fg("accent", ">") : theme.fg("muted", "|");
33733
- lines.push(` ${prefix} ${ctxNumber}: ${asString(ctxLine.text) ?? ""}`);
33982
+ lines.push(` ${prefix} ${ctxNumber}: ${asString2(ctxLine.text) ?? ""}`);
33734
33983
  });
33735
33984
  });
33736
33985
  sections.push(lines.join(`
@@ -33739,13 +33988,13 @@ function buildAstSearchSections(payload, theme) {
33739
33988
  return sections;
33740
33989
  }
33741
33990
  function buildAstReplaceSections(payload, theme) {
33742
- const response = asRecord2(payload);
33991
+ const response = asRecord3(payload);
33743
33992
  if (!response)
33744
33993
  return [theme.fg("muted", "No AST replace results.")];
33745
- const files = asRecords(response.files);
33746
- const totalReplacements = asNumber(response.total_replacements) ?? 0;
33747
- const totalFiles = asNumber(response.total_files) ?? files.length;
33748
- const filesWithMatches = asNumber(response.files_with_matches);
33994
+ const files = asRecords2(response.files);
33995
+ const totalReplacements = asNumber2(response.total_replacements) ?? 0;
33996
+ const totalFiles = asNumber2(response.total_files) ?? files.length;
33997
+ const filesWithMatches = asNumber2(response.files_with_matches);
33749
33998
  const dryRun = response.dry_run === true;
33750
33999
  const headerParts = [
33751
34000
  dryRun ? theme.fg("warning", "[dry run]") : theme.fg("success", "[applied]"),
@@ -33761,10 +34010,10 @@ function buildAstReplaceSections(payload, theme) {
33761
34010
  return sections;
33762
34011
  }
33763
34012
  files.forEach((fileResult) => {
33764
- const file2 = shortenPath2(asString(fileResult.file) ?? "(unknown file)");
33765
- const replacements = asNumber(fileResult.replacements) ?? 0;
33766
- const error50 = asString(fileResult.error);
33767
- const diff = asString(fileResult.diff);
34013
+ const file2 = shortenPath3(asString2(fileResult.file) ?? "(unknown file)");
34014
+ const replacements = asNumber2(fileResult.replacements) ?? 0;
34015
+ const error50 = asString2(fileResult.error);
34016
+ const diff = asString2(fileResult.diff);
33768
34017
  const lines = [
33769
34018
  `${theme.fg("accent", file2)} ${theme.fg("muted", `(${replacements} replacement${replacements === 1 ? "" : "s"})`)}`
33770
34019
  ];
@@ -33774,7 +34023,7 @@ function buildAstReplaceSections(payload, theme) {
33774
34023
  const rendered = renderUnifiedDiff(diff);
33775
34024
  lines.push(rendered || theme.fg("muted", "No diff available."));
33776
34025
  } else {
33777
- const backupId = asString(fileResult.backup_id);
34026
+ const backupId = asString2(fileResult.backup_id);
33778
34027
  lines.push(backupId ? `${theme.fg("success", "saved")} ${theme.fg("muted", backupId)}` : theme.fg("success", "saved"));
33779
34028
  }
33780
34029
  sections.push(lines.join(`
@@ -33871,7 +34120,7 @@ import { Type as Type4 } from "typebox";
33871
34120
  var FOREGROUND_POLL_INTERVAL_MS = 100;
33872
34121
  var BASH_WAIT_POLL_INTERVAL_MS = 100;
33873
34122
  var DEFAULT_BASH_STATUS_WAIT_TIMEOUT_MS = 30000;
33874
- var MAX_BASH_STATUS_WAIT_TIMEOUT_MS = 300000;
34123
+ var MAX_BASH_STATUS_WAIT_TIMEOUT_MS = 30 * 60 * 1000;
33875
34124
  var REGEX_WAIT_SCAN_WINDOW_BYTES = 64 * 1024;
33876
34125
  function resolveForegroundWaitMs(configured) {
33877
34126
  const override = process.env.AFT_TEST_FOREGROUND_WAIT_MS;
@@ -33883,7 +34132,8 @@ function resolveForegroundWaitMs(configured) {
33883
34132
  return configured;
33884
34133
  }
33885
34134
  var BASH_TRANSPORT_TIMEOUT_MS = 30000;
33886
- var BashParams = Type4.Object({
34135
+ var DEFAULT_HARD_TIMEOUT_MS = 30 * 60 * 1000;
34136
+ var BashBaseParams = {
33887
34137
  command: Type4.String({
33888
34138
  description: "Shell command to execute. Supports pipes, redirections, and shell syntax."
33889
34139
  }),
@@ -33893,19 +34143,38 @@ var BashParams = Type4.Object({
33893
34143
  })),
33894
34144
  description: Type4.Optional(Type4.String({
33895
34145
  description: "Human-readable description shown in UI logs. Helps users understand what the command does without reading shell syntax."
33896
- })),
34146
+ }))
34147
+ };
34148
+ var BashBackgroundFlagParam = {
33897
34149
  background: Type4.Optional(Type4.Boolean({
33898
- description: "Spawn command in background and return immediately with a task_id. Use bash_status to poll completion and bash_kill to terminate. Ideal for long-running tasks like builds or dev servers."
33899
- })),
34150
+ description: "Spawn command in background and return immediately with a task_id. Use bash_watch to wait for completion or output patterns; bash_status for a one-shot snapshot only. Use bash_kill to terminate. Ideal for long-running tasks like builds or dev servers."
34151
+ }))
34152
+ };
34153
+ var BashCompressionParam = {
33900
34154
  compressed: Type4.Optional(Type4.Boolean({
33901
34155
  description: "Compress output by removing ANSI codes, carriage returns, and excessive blank lines. Default: true. Set to false for raw terminal output including color codes."
33902
- })),
34156
+ }))
34157
+ };
34158
+ var BashPtyParams = {
33903
34159
  pty: Type4.Optional(Type4.Boolean({
33904
34160
  description: 'Spawn the command in a real PTY for interactive programs. Implies background: true automatically. Inspect with bash_status({ task_id, output_mode: "screen" }) and send input with bash_write.'
33905
34161
  })),
33906
34162
  ptyRows: optionalInt(1, 60),
33907
34163
  ptyCols: optionalInt(1, 140)
34164
+ };
34165
+ var BashParams = Type4.Object({
34166
+ ...BashBaseParams,
34167
+ ...BashBackgroundFlagParam,
34168
+ ...BashCompressionParam,
34169
+ ...BashPtyParams
33908
34170
  });
34171
+ var BashForegroundOnlyParams = Type4.Object({
34172
+ ...BashBaseParams,
34173
+ ...BashCompressionParam
34174
+ });
34175
+ function bashParamsForConfig(backgroundEnabled) {
34176
+ return backgroundEnabled ? BashParams : BashForegroundOnlyParams;
34177
+ }
33909
34178
  var BashTaskParams = Type4.Object({
33910
34179
  task_id: Type4.String({
33911
34180
  description: "Background bash task id returned by bash({ background: true })."
@@ -33979,28 +34248,30 @@ function registerBashTool(pi, ctx, aftSearchRegistered = false) {
33979
34248
  const searchSteer = aftSearchRegistered ? "use `aft_search` (concepts, identifiers, regex, literals), `read`, `aft_outline`, or `aft_zoom` instead" : "use the `grep` tool, `read`, `aft_outline`, or `aft_zoom` instead";
33980
34249
  const bashCfg = resolveBashConfig(ctx.config);
33981
34250
  const compressionSentence = bashCfg.compress ? " Output is compressed by default; pass `compressed: false` for raw output." : "";
33982
- const tasksSentence = bashCfg.background ? ' Pass `background: true` to run in the background and get a task_id for `bash_status`/`bash_kill`. Pass `pty: true` for interactive programs (REPLs, TUIs) and drive them with `bash_status({ output_mode: "screen" })` plus `bash_write`.' : " Commands that outlive the foreground wait window are promoted to background tasks; inspect them with `bash_status({ task_id })` or terminate with `bash_kill`.";
34251
+ const tasksSentence = bashCfg.background ? ' Pass `background: true` to run in the background and get a task_id for `bash_status`/`bash_watch`/`bash_kill`. Pass `pty: true` for interactive programs (REPLs, TUIs) and drive them with `bash_status({ output_mode: "screen" })` plus `bash_write`. Use `bash_watch` to wait for exit or output patterns (sync blocks, async notifies). Do not loop `bash_status` to wait.' : " Commands run in the foreground to completion; `timeout` is the hard kill cap (default 30 minutes).";
33983
34252
  pi.registerTool({
33984
34253
  name: "bash",
33985
34254
  label: "bash",
33986
34255
  description: `Execute shell commands.${compressionSentence}${tasksSentence}
33987
34256
 
33988
34257
  DO NOT use bash for code search or code exploration. If you are about to run grep, rg, sed, awk, find, or cat through bash to locate or read code: STOP — ${searchSteer}.`,
33989
- promptSnippet: "Run shell commands (timeout in milliseconds; supports workdir, background tasks, compressed output, PTY mode)",
34258
+ promptSnippet: bashCfg.background ? "Run shell commands (timeout in milliseconds; supports workdir, background tasks, compressed output, PTY mode)" : "Run shell commands (timeout in milliseconds; supports workdir and compressed output)",
33990
34259
  promptGuidelines: [
33991
34260
  `DO NOT use bash for code search or exploration — ${searchSteer}.`,
33992
34261
  "Set compressed: false when you need ANSI color codes in the output."
33993
34262
  ],
33994
- parameters: BashParams,
34263
+ parameters: bashParamsForConfig(bashCfg.background),
33995
34264
  async execute(_toolCallId, params, _signal, onUpdate, extCtx) {
33996
34265
  const bridge = bridgeFor(ctx, extCtx.cwd);
33997
34266
  const bashCfg2 = resolveBashConfig(ctx.config);
33998
34267
  const foregroundWaitMs = resolveForegroundWaitMs(bashCfg2.foreground_wait_window_ms);
34268
+ const backgroundDisabled = !bashCfg2.background;
33999
34269
  const timeout = coerceOptionalInt(params.timeout, "timeout", 1, Number.MAX_SAFE_INTEGER);
34000
- const ptyRows = coerceOptionalInt(params.ptyRows, "ptyRows", 1, 60);
34001
- const ptyCols = coerceOptionalInt(params.ptyCols, "ptyCols", 1, 140);
34002
- const effectiveBackground = params.background === true || params.pty === true;
34003
- const effectiveTimeout = effectiveBackground ? timeout : resolveBashKillTimeout(timeout, foregroundWaitMs);
34270
+ const ptyRows = backgroundDisabled ? undefined : coerceOptionalInt(params.ptyRows, "ptyRows", 1, 60);
34271
+ const ptyCols = backgroundDisabled ? undefined : coerceOptionalInt(params.ptyCols, "ptyCols", 1, 140);
34272
+ const requestedPty = !backgroundDisabled && params.pty === true;
34273
+ const effectiveBackground = !backgroundDisabled && (params.background === true || requestedPty);
34274
+ const effectiveTimeout = effectiveBackground || backgroundDisabled ? timeout : resolveBashKillTimeout(timeout, foregroundWaitMs);
34004
34275
  let spawnContext = {
34005
34276
  command: params.command,
34006
34277
  cwd: params.workdir
@@ -34025,7 +34296,7 @@ DO NOT use bash for code search or code exploration. If you are about to run gre
34025
34296
  background: effectiveBackground,
34026
34297
  notify_on_completion: effectiveBackground,
34027
34298
  compressed: params.compressed,
34028
- pty: params.pty,
34299
+ pty: requestedPty,
34029
34300
  pty_rows: ptyRows,
34030
34301
  pty_cols: ptyCols
34031
34302
  }, extCtx, {
@@ -34047,9 +34318,9 @@ DO NOT use bash for code search or code exploration. If you are about to run gre
34047
34318
  if (response.status === "running" && taskId) {
34048
34319
  if (effectiveBackground) {
34049
34320
  trackBgTask(resolveSessionId(extCtx), taskId);
34050
- return bashResult(appendPipeStripNote(formatBackgroundLaunch(taskId, params.pty === true), pipeStrip.note), { task_id: taskId });
34321
+ return bashResult(appendPipeStripNote(formatBackgroundLaunch(taskId, requestedPty), pipeStrip.note), { task_id: taskId });
34051
34322
  }
34052
- const waitTimeoutMs = effectiveTimeout !== undefined ? Math.min(effectiveTimeout, foregroundWaitMs) : foregroundWaitMs;
34323
+ const waitTimeoutMs = backgroundDisabled ? effectiveTimeout ?? DEFAULT_HARD_TIMEOUT_MS : effectiveTimeout !== undefined ? Math.min(effectiveTimeout, foregroundWaitMs) : foregroundWaitMs;
34053
34324
  const startedAt = Date.now();
34054
34325
  while (true) {
34055
34326
  const status = await callBashBridge(bridge, "bash_status", { task_id: taskId }, extCtx);
@@ -34066,6 +34337,10 @@ DO NOT use bash for code search or code exploration. If you are about to run gre
34066
34337
  });
34067
34338
  }
34068
34339
  if (Date.now() - startedAt >= waitTimeoutMs) {
34340
+ if (backgroundDisabled) {
34341
+ await sleep(FOREGROUND_POLL_INTERVAL_MS);
34342
+ continue;
34343
+ }
34069
34344
  const promoted = await callBashBridge(bridge, "bash_promote", { task_id: taskId }, extCtx);
34070
34345
  if (promoted.success === false) {
34071
34346
  throw new Error(promoted.message ?? "bash_promote failed");
@@ -34093,10 +34368,12 @@ DO NOT use bash for code search or code exploration. If you are about to run gre
34093
34368
  return renderBashResult(result, theme, context);
34094
34369
  }
34095
34370
  });
34096
- pi.registerTool(createBashStatusTool(ctx));
34097
- pi.registerTool(createBashWatchTool(ctx));
34098
- pi.registerTool(createBashWriteTool(ctx));
34099
- pi.registerTool(createBashKillTool(ctx));
34371
+ if (bashCfg.background) {
34372
+ pi.registerTool(createBashStatusTool(ctx));
34373
+ pi.registerTool(createBashWatchTool(ctx));
34374
+ pi.registerTool(createBashWriteTool(ctx));
34375
+ pi.registerTool(createBashKillTool(ctx));
34376
+ }
34100
34377
  }
34101
34378
  function formatBackgroundLaunch(taskId, isPty) {
34102
34379
  if (isPty) {
@@ -34115,7 +34392,7 @@ function createBashStatusTool(ctx) {
34115
34392
  return {
34116
34393
  name: "bash_status",
34117
34394
  label: "bash_status",
34118
- description: "Read-only snapshot of a background bash task. Returns immediately. Never waits. Use bash_watch to block on or register for pattern matches and exit events.",
34395
+ description: "Read-only snapshot of a background bash task. Returns immediately. Never waits. One look to check on a task is fine — never loop it to wait for completion. To wait, use bash_watch.",
34119
34396
  promptSnippet: "Inspect a background bash task by task_id",
34120
34397
  parameters: BashStatusParams,
34121
34398
  async execute(_toolCallId, params, _signal, _onUpdate, extCtx) {
@@ -34130,7 +34407,7 @@ function createBashWatchTool(ctx) {
34130
34407
  return {
34131
34408
  name: "bash_watch",
34132
34409
  label: "bash_watch",
34133
- description: "Watch a background bash task. Two modes. Async (background:true, requires pattern) registers a non-blocking notification and returns immediately — use this to be pinged when a specific line appears or the task exits, without freezing your turn. Sync (default) blocks until a pattern matches/the task exits/timeout, and is ONLY for short bounded waits (seconds, e.g. a dev server printing a readiness line). Do NOT sync-wait for a long task (build/test/install): blocking locks the user out until it ends instead end your turn and let the automatic completion reminder arrive, or use async mode.",
34410
+ description: "Watch a background bash task. Sync (default) blocks until a pattern matches, the task exits, or timeout — use it when the result is the next thing you need, even for long builds/tests/installs (pass timeout_ms up to 30 min for those). The user can interrupt anytime; the wait auto-converts to an async notification. Async (background:true, requires pattern) registers a non-blocking notification and returns immediately use when you have parallel work or want to end your turn. Never loop bash_status to wait.",
34134
34411
  promptSnippet: "Wait for or watch a background bash task",
34135
34412
  parameters: BashWatchParams,
34136
34413
  async execute(_toolCallId, params, _signal, _onUpdate, extCtx) {
@@ -34703,10 +34980,10 @@ function renderFsResult(toolName, args, result, theme, context) {
34703
34980
  const skipped = data.skipped_files ?? [];
34704
34981
  const lines = [];
34705
34982
  for (const entry of deletedPaths) {
34706
- lines.push(`${theme.fg("success", "✓ deleted")} ${theme.fg("accent", shortenPath2(entry))}`);
34983
+ lines.push(`${theme.fg("success", "✓ deleted")} ${theme.fg("accent", shortenPath3(entry))}`);
34707
34984
  }
34708
34985
  for (const entry of skipped) {
34709
- lines.push(`${theme.fg("error", "✗ skipped")} ${theme.fg("accent", shortenPath2(entry.file))} ${theme.fg("muted", `(${entry.reason})`)}`);
34986
+ lines.push(`${theme.fg("error", "✗ skipped")} ${theme.fg("accent", shortenPath3(entry.file))} ${theme.fg("muted", `(${entry.reason})`)}`);
34710
34987
  }
34711
34988
  if (lines.length === 0) {
34712
34989
  lines.push(theme.fg("muted", "(no files deleted)"));
@@ -34716,8 +34993,8 @@ function renderFsResult(toolName, args, result, theme, context) {
34716
34993
  }
34717
34994
  const moveArgs = args;
34718
34995
  return renderSections([
34719
- `${theme.fg("success", "✓ moved")} ${theme.fg("accent", shortenPath2(moveArgs.filePath))}`,
34720
- `${theme.fg("muted", "to")} ${theme.fg("accent", shortenPath2(moveArgs.destination))}`
34996
+ `${theme.fg("success", "✓ moved")} ${theme.fg("accent", shortenPath3(moveArgs.filePath))}`,
34997
+ `${theme.fg("muted", "to")} ${theme.fg("accent", shortenPath3(moveArgs.destination))}`
34721
34998
  ], context);
34722
34999
  }
34723
35000
  function registerFsTools(pi, ctx, surface) {
@@ -34831,33 +35108,33 @@ var ImportParams = Type7.Object({
34831
35108
  }))
34832
35109
  });
34833
35110
  function buildImportSections(args, payload, theme) {
34834
- const response = asRecord2(payload);
35111
+ const response = asRecord3(payload);
34835
35112
  if (!response)
34836
35113
  return [theme.fg("muted", "No import result.")];
34837
35114
  if (args.op === "organize") {
34838
- const groups = asRecords(response.groups);
34839
- const groupText = groups.length > 0 ? groups.map((group) => `${asString(group.name) ?? "unknown"}: ${asNumber(group.count) ?? 0}`).join(" · ") : "No imports found";
35115
+ const groups = asRecords2(response.groups);
35116
+ const groupText = groups.length > 0 ? groups.map((group) => `${asString2(group.name) ?? "unknown"}: ${asNumber2(group.count) ?? 0}`).join(" · ") : "No imports found";
34840
35117
  return [
34841
- `${theme.fg("success", "organized")} ${theme.fg("accent", asString(response.file) ?? args.filePath)}`,
35118
+ `${theme.fg("success", "organized")} ${theme.fg("accent", asString2(response.file) ?? args.filePath)}`,
34842
35119
  `${theme.fg("muted", "groups")} ${groupText}`,
34843
- `${theme.fg("muted", "duplicates removed")} ${asNumber(response.removed_duplicates) ?? 0}`
35120
+ `${theme.fg("muted", "duplicates removed")} ${asNumber2(response.removed_duplicates) ?? 0}`
34844
35121
  ];
34845
35122
  }
34846
35123
  if (args.op === "add") {
34847
- const moduleName2 = asString(response.module) ?? args.module ?? "(module)";
35124
+ const moduleName2 = asString2(response.module) ?? args.module ?? "(module)";
34848
35125
  const status = response.already_present === true ? theme.fg("warning", "already present") : theme.fg("success", "added");
34849
35126
  return [
34850
35127
  `${status} ${theme.fg("accent", moduleName2)}`,
34851
- `${theme.fg("muted", "file")} ${theme.fg("accent", asString(response.file) ?? args.filePath)}`,
34852
- `${theme.fg("muted", "group")} ${asString(response.group) ?? "—"}`
35128
+ `${theme.fg("muted", "file")} ${theme.fg("accent", asString2(response.file) ?? args.filePath)}`,
35129
+ `${theme.fg("muted", "group")} ${asString2(response.group) ?? "—"}`
34853
35130
  ];
34854
35131
  }
34855
- const moduleName = asString(response.module) ?? args.module ?? "(module)";
35132
+ const moduleName = asString2(response.module) ?? args.module ?? "(module)";
34856
35133
  const didRemove = response.removed !== false;
34857
35134
  const removeStatus = didRemove ? `${theme.fg("success", "removed")} ${theme.fg("accent", moduleName)}` : `${theme.fg("warning", "not present")} ${theme.fg("accent", moduleName)}`;
34858
35135
  return [
34859
35136
  removeStatus,
34860
- `${theme.fg("muted", "file")} ${theme.fg("accent", asString(response.file) ?? args.filePath)}`,
35137
+ `${theme.fg("muted", "file")} ${theme.fg("accent", asString2(response.file) ?? args.filePath)}`,
34861
35138
  args.removeName ? `${theme.fg("muted", "name")} ${args.removeName}` : `${theme.fg("muted", "scope")} entire import`
34862
35139
  ];
34863
35140
  }
@@ -34990,16 +35267,16 @@ function diagnosticsServerSummary(section) {
34990
35267
  return parts.length > 0 ? parts.join("; ") : "none reported";
34991
35268
  }
34992
35269
  function diagnosticsSummaryPart(summary) {
34993
- const section = asRecord2(summary?.diagnostics);
35270
+ const section = asRecord3(summary?.diagnostics);
34994
35271
  if (!section)
34995
35272
  return;
34996
- const errors3 = asNumber(section.errors);
34997
- const warnings = asNumber(section.warnings);
34998
- const info = asNumber(section.info);
34999
- const hints = asNumber(section.hints);
35273
+ const errors3 = asNumber2(section.errors);
35274
+ const warnings = asNumber2(section.warnings);
35275
+ const info = asNumber2(section.info);
35276
+ const hints = asNumber2(section.hints);
35000
35277
  const hasCounts = [errors3, warnings, info, hints].some((value) => value !== undefined);
35001
35278
  const counts = `${errors3 ?? 0} errors/${warnings ?? 0} warnings/${info ?? 0} info/${hints ?? 0} hints`;
35002
- const status = asString(section.status);
35279
+ const status = asString2(section.status);
35003
35280
  if (status === "pending") {
35004
35281
  return hasCounts ? `diagnostics ${counts} so far — still pending (servers: ${diagnosticsServerSummary(section)})` : `diagnostics pending (servers: ${diagnosticsServerSummary(section)})`;
35005
35282
  }
@@ -35012,9 +35289,9 @@ function diagnosticsSummaryPart(summary) {
35012
35289
  return;
35013
35290
  }
35014
35291
  function diagnosticLocation(diagnostic) {
35015
- const file2 = asString(diagnostic.file) ?? "(unknown file)";
35016
- const line = asNumber(diagnostic.line);
35017
- const column = asNumber(diagnostic.column);
35292
+ const file2 = asString2(diagnostic.file) ?? "(unknown file)";
35293
+ const line = asNumber2(diagnostic.line);
35294
+ const column = asNumber2(diagnostic.column);
35018
35295
  if (line === undefined)
35019
35296
  return file2;
35020
35297
  if (column === undefined)
@@ -35022,14 +35299,14 @@ function diagnosticLocation(diagnostic) {
35022
35299
  return `${file2}:${line}:${column}`;
35023
35300
  }
35024
35301
  function diagnosticsDetailSection(details) {
35025
- const diagnostics = asRecords(details?.diagnostics);
35302
+ const diagnostics = asRecords2(details?.diagnostics);
35026
35303
  if (diagnostics.length === 0)
35027
35304
  return;
35028
35305
  const lines = ["diagnostics"];
35029
35306
  for (const diagnostic of diagnostics) {
35030
- const severity = asString(diagnostic.severity) ?? "information";
35031
- const message = asString(diagnostic.message) ?? "(no message)";
35032
- const source = asString(diagnostic.source);
35307
+ const severity = asString2(diagnostic.severity) ?? "information";
35308
+ const message = asString2(diagnostic.message) ?? "(no message)";
35309
+ const source = asString2(diagnostic.source);
35033
35310
  const suffix = source ? ` [${source}]` : "";
35034
35311
  lines.push(`- ${diagnosticLocation(diagnostic)} ${severity} ${message}${suffix}`);
35035
35312
  }
@@ -35037,15 +35314,15 @@ function diagnosticsDetailSection(details) {
35037
35314
  `);
35038
35315
  }
35039
35316
  function countFrom(summary, key) {
35040
- const section = asRecord2(summary?.[key]);
35041
- return asNumber(section?.count);
35317
+ const section = asRecord3(summary?.[key]);
35318
+ return asNumber2(section?.count);
35042
35319
  }
35043
35320
  function tier2SummaryPart(summary, key, label) {
35044
- const section = asRecord2(summary?.[key]);
35045
- const count = asNumber(section?.count);
35321
+ const section = asRecord3(summary?.[key]);
35322
+ const count = asNumber2(section?.count);
35046
35323
  if (count !== undefined)
35047
35324
  return `${label} ${count}`;
35048
- const status = asString(section?.status);
35325
+ const status = asString2(section?.status);
35049
35326
  return `${label} ${status ?? "unavailable"}`;
35050
35327
  }
35051
35328
  function shortDupOccurrence(entry) {
@@ -35054,12 +35331,12 @@ function shortDupOccurrence(entry) {
35054
35331
  }
35055
35332
  function tier2TopPreview(summary, theme) {
35056
35333
  const lines = [];
35057
- const dup = asRecord2(summary?.duplicates);
35334
+ const dup = asRecord3(summary?.duplicates);
35058
35335
  const dupTop = Array.isArray(dup?.top) ? dup.top : [];
35059
35336
  for (const group of dupTop) {
35060
- const record2 = asRecord2(group);
35337
+ const record2 = asRecord3(group);
35061
35338
  const files = Array.isArray(record2?.files) ? record2.files : [];
35062
- const cost = asNumber(record2?.cost);
35339
+ const cost = asNumber2(record2?.cost);
35063
35340
  if (files.length < 2)
35064
35341
  continue;
35065
35342
  const a = shortDupOccurrence(String(files[0]));
@@ -35070,12 +35347,12 @@ function tier2TopPreview(summary, theme) {
35070
35347
  ["dead_code", "dead"],
35071
35348
  ["unused_exports", "unused"]
35072
35349
  ]) {
35073
- const section = asRecord2(summary?.[key]);
35350
+ const section = asRecord3(summary?.[key]);
35074
35351
  const top = Array.isArray(section?.top) ? section.top : [];
35075
35352
  for (const item of top) {
35076
- const record2 = asRecord2(item);
35077
- const file2 = asString(record2?.file);
35078
- const symbol2 = asString(record2?.symbol);
35353
+ const record2 = asRecord3(item);
35354
+ const file2 = asString2(record2?.file);
35355
+ const symbol2 = asString2(record2?.symbol);
35079
35356
  if (!file2 || !symbol2)
35080
35357
  continue;
35081
35358
  lines.push(` ${label} ${symbol2} (${file2.split("/").pop()})`);
@@ -35088,7 +35365,7 @@ ${lines.join(`
35088
35365
  `)}`;
35089
35366
  }
35090
35367
  function tier2RefreshCategories(response) {
35091
- const scannerState = asRecord2(response.scanner_state);
35368
+ const scannerState = asRecord3(response.scanner_state);
35092
35369
  const categories = new Set;
35093
35370
  for (const key of ["pending_categories", "stale_categories"]) {
35094
35371
  const values = scannerState?.[key];
@@ -35135,18 +35412,18 @@ function runPendingTier2Categories(bridge, categories, extCtx) {
35135
35412
  });
35136
35413
  }
35137
35414
  function buildInspectSections(payload, theme) {
35138
- const response = asRecord2(payload);
35415
+ const response = asRecord3(payload);
35139
35416
  if (!response)
35140
35417
  return [theme.fg("muted", "No inspect snapshot available.")];
35141
- const summary = asRecord2(response.summary);
35142
- const metrics = asRecord2(summary?.metrics);
35143
- const scannerState = asRecord2(response.scanner_state);
35418
+ const summary = asRecord3(response.summary);
35419
+ const metrics = asRecord3(summary?.metrics);
35420
+ const scannerState = asRecord3(response.scanner_state);
35144
35421
  const stale = Array.isArray(scannerState?.stale_categories) ? scannerState.stale_categories.length : 0;
35145
35422
  const pending = Array.isArray(scannerState?.pending_categories) ? scannerState.pending_categories.length : 0;
35146
35423
  const parts = [
35147
35424
  `todos ${countFrom(summary, "todos") ?? 0}`,
35148
35425
  diagnosticsSummaryPart(summary),
35149
- `metrics ${asNumber(metrics?.files) ?? 0} files/${asNumber(metrics?.symbols) ?? 0} symbols`,
35426
+ `metrics ${asNumber2(metrics?.files) ?? 0} files/${asNumber2(metrics?.symbols) ?? 0} symbols`,
35150
35427
  tier2SummaryPart(summary, "dead_code", "dead code"),
35151
35428
  tier2SummaryPart(summary, "unused_exports", "unused exports"),
35152
35429
  tier2SummaryPart(summary, "duplicates", "duplicates")
@@ -35158,7 +35435,7 @@ function buildInspectSections(payload, theme) {
35158
35435
  const topPreview = tier2TopPreview(summary, theme);
35159
35436
  if (topPreview)
35160
35437
  sections.push(topPreview);
35161
- const details = asRecord2(response.details);
35438
+ const details = asRecord3(response.details);
35162
35439
  if (details) {
35163
35440
  const names = Object.keys(details);
35164
35441
  sections.push(names.length > 0 ? `details: ${names.join(", ")}` : theme.fg("muted", "No drill-down details returned."));
@@ -35166,7 +35443,7 @@ function buildInspectSections(payload, theme) {
35166
35443
  if (diagnosticsDetails)
35167
35444
  sections.push(diagnosticsDetails);
35168
35445
  }
35169
- const text = asString(response.text);
35446
+ const text = asString2(response.text);
35170
35447
  if (text)
35171
35448
  sections.push(text);
35172
35449
  return sections;
@@ -35199,7 +35476,7 @@ function registerInspectTool(pi, ctx) {
35199
35476
  runPendingTier2Categories(bridge, tier2RefreshCategories(response), extCtx);
35200
35477
  const body = response.text;
35201
35478
  if (typeof body === "string") {
35202
- const diagnostics = diagnosticsSummaryPart(asRecord2(response.summary));
35479
+ const diagnostics = diagnosticsSummaryPart(asRecord3(response.summary));
35203
35480
  const text = diagnostics ? body ? `${body}
35204
35481
 
35205
35482
  ${diagnostics}` : diagnostics : body;
@@ -35239,138 +35516,11 @@ function navigateParamsSchema() {
35239
35516
  }))
35240
35517
  });
35241
35518
  }
35242
- function treeLine(depth, text) {
35243
- return `${" ".repeat(depth)}${depth === 0 ? "" : "↳ "}${text}`;
35244
- }
35245
- function renderCallTreeNode(node, depth, lines) {
35246
- const name = asString(node.name) ?? "(unknown)";
35247
- const file2 = shortenPath2(asString(node.file) ?? "(unknown file)");
35248
- const line = asNumber(node.line);
35249
- lines.push(treeLine(depth, `${name} ${line !== undefined ? `[${file2}:${line}]` : `[${file2}]`}`));
35250
- asRecords(node.children).forEach((child) => {
35251
- renderCallTreeNode(child, depth + 1, lines);
35252
- });
35253
- }
35254
- function depthWarning(response, theme, depthField = "depth_limited", truncatedField = "truncated") {
35255
- const limited = asBoolean(response[depthField]);
35256
- const truncated = asNumber(response[truncatedField]) ?? 0;
35257
- if (!limited && truncated === 0)
35258
- return "";
35259
- const detail = truncated > 0 ? `, ${truncated} truncated` : "";
35260
- return theme.fg("warning", `(depth limited${detail})`);
35261
- }
35262
- function renderTracePath(path3, index, lines) {
35263
- lines.push(`Path ${index + 1}`);
35264
- asRecords(path3.hops).forEach((hop, hopIndex) => {
35265
- const symbol2 = asString(hop.symbol) ?? "(unknown)";
35266
- const file2 = shortenPath2(asString(hop.file) ?? "(unknown file)");
35267
- const line = asNumber(hop.line);
35268
- const entry = hop.is_entry_point === true ? " [entry]" : "";
35269
- lines.push(treeLine(hopIndex + 1, `${symbol2}${entry} ${line !== undefined ? `[${file2}:${line}]` : `[${file2}]`}`));
35270
- });
35271
- }
35272
35519
  function buildNavigateSections(args, payload, theme) {
35273
- const response = asRecord2(payload);
35274
- if (!response)
35275
- return [theme.fg("muted", "No navigation result.")];
35276
- if (args.op === "call_tree") {
35277
- const lines = [];
35278
- renderCallTreeNode(response, 0, lines);
35279
- const warning = depthWarning(response, theme);
35280
- if (warning)
35281
- lines.push(warning);
35282
- return lines.length > 0 ? lines : [theme.fg("muted", "No call tree available.")];
35283
- }
35284
- if (args.op === "callers") {
35285
- const groups = asRecords(response.callers);
35286
- const warning = depthWarning(response, theme);
35287
- const sections2 = [
35288
- `${theme.fg("success", `${asNumber(response.total_callers) ?? 0} caller${(asNumber(response.total_callers) ?? 0) === 1 ? "" : "s"}`)} ${theme.fg("muted", `${groups.length} file group${groups.length === 1 ? "" : "s"}`)} ${warning}`.trim()
35289
- ];
35290
- groups.forEach((group) => {
35291
- const file2 = shortenPath2(asString(group.file) ?? "(unknown file)");
35292
- const lines = [theme.fg("accent", file2)];
35293
- asRecords(group.callers).forEach((caller) => {
35294
- lines.push(` ↳ ${asString(caller.symbol) ?? "(unknown)"} ${theme.fg("muted", `line ${asNumber(caller.line) ?? "?"}`)}`);
35295
- });
35296
- sections2.push(lines.join(`
35297
- `));
35298
- });
35299
- return sections2;
35300
- }
35301
- if (args.op === "trace_to_symbol") {
35302
- const path3 = asRecords(response.path);
35303
- const complete = asBoolean(response.complete);
35304
- const reason = asString(response.reason);
35305
- if (path3.length === 0) {
35306
- const prefix = complete === false ? theme.fg("warning", "No complete path") : theme.fg("muted", "No path");
35307
- return [`${prefix}${reason ? ` (${reason})` : ""}`];
35308
- }
35309
- const lines = [theme.fg("success", `${path3.length} hop${path3.length === 1 ? "" : "s"}`)];
35310
- path3.forEach((hop, index) => {
35311
- const symbol2 = asString(hop.symbol) ?? "(unknown)";
35312
- const file2 = shortenPath2(asString(hop.file) ?? "(unknown file)");
35313
- const line = asNumber(hop.line);
35314
- lines.push(treeLine(index + 1, `${symbol2} ${line !== undefined ? `[${file2}:${line}]` : `[${file2}]`}`));
35315
- });
35316
- return lines;
35317
- }
35318
- if (args.op === "trace_to") {
35319
- const paths = asRecords(response.paths);
35320
- const warning = depthWarning(response, theme, "max_depth_reached", "truncated_paths");
35321
- const sections2 = [
35322
- `${theme.fg("success", `${asNumber(response.total_paths) ?? paths.length} path${(asNumber(response.total_paths) ?? paths.length) === 1 ? "" : "s"}`)} ${theme.fg("muted", `${asNumber(response.entry_points_found) ?? 0} entry point${(asNumber(response.entry_points_found) ?? 0) === 1 ? "" : "s"}`)} ${warning}`.trim()
35323
- ];
35324
- if (paths.length === 0)
35325
- sections2.push(theme.fg("muted", "No entry paths found."));
35326
- paths.forEach((path3, index) => {
35327
- const lines = [];
35328
- renderTracePath(path3, index, lines);
35329
- sections2.push(lines.join(`
35330
- `));
35331
- });
35332
- return sections2;
35333
- }
35334
- if (args.op === "impact") {
35335
- const callers = asRecords(response.callers);
35336
- const warning = depthWarning(response, theme);
35337
- const sections2 = [
35338
- `${theme.fg("warning", `${asNumber(response.total_affected) ?? callers.length} affected call site${(asNumber(response.total_affected) ?? callers.length) === 1 ? "" : "s"}`)} ${theme.fg("muted", `${asNumber(response.affected_files) ?? 0} file${(asNumber(response.affected_files) ?? 0) === 1 ? "" : "s"}`)} ${warning}`.trim()
35339
- ];
35340
- if (callers.length === 0)
35341
- sections2.push(theme.fg("muted", "No impacted callers found."));
35342
- callers.forEach((caller) => {
35343
- const file2 = shortenPath2(asString(caller.caller_file) ?? "(unknown file)");
35344
- const symbol2 = asString(caller.caller_symbol) ?? "(unknown)";
35345
- const line = asNumber(caller.line) ?? 0;
35346
- const entry = caller.is_entry_point === true ? ` ${theme.fg("warning", "[entry]")}` : "";
35347
- const expression = asString(caller.call_expression);
35348
- const params = Array.isArray(caller.parameters) ? caller.parameters.map(String).join(", ") : "";
35349
- sections2.push([
35350
- `${theme.fg("accent", file2)}:${line}`,
35351
- ` ↳ ${symbol2}${entry}`,
35352
- expression ? ` ${theme.fg("muted", expression)}` : undefined,
35353
- params ? ` ${theme.fg("muted", `params: ${params}`)}` : undefined
35354
- ].filter(Boolean).join(`
35355
- `));
35356
- });
35357
- return sections2;
35358
- }
35359
- const hops = asRecords(response.hops);
35360
- const sections = [
35361
- `${theme.fg("success", `${hops.length} hop${hops.length === 1 ? "" : "s"}`)} ${asBoolean(response.depth_limited) ? theme.fg("warning", "(depth limited)") : ""}`.trim()
35362
- ];
35363
- if (hops.length === 0)
35364
- sections.push(theme.fg("muted", "No data-flow hops found."));
35365
- hops.forEach((hop, index) => {
35366
- const file2 = shortenPath2(asString(hop.file) ?? "(unknown file)");
35367
- const symbol2 = asString(hop.symbol) ?? "(unknown)";
35368
- const variable = asString(hop.variable) ?? "(unknown)";
35369
- const line = asNumber(hop.line) ?? 0;
35370
- const approximate = hop.approximate === true ? ` ${theme.fg("warning", "[approx]")}` : "";
35371
- sections.push(treeLine(index, `${variable} ${theme.fg("muted", `${asString(hop.flow_type) ?? "flow"}`)} ${symbol2} [${file2}:${line}]${approximate}`));
35372
- });
35373
- return sections;
35520
+ const themeAdapter = {
35521
+ fg: (role, s) => theme.fg(role, s)
35522
+ };
35523
+ return formatCallgraphSections(args.op, payload, themeAdapter);
35374
35524
  }
35375
35525
  function renderNavigateCall(args, theme, context) {
35376
35526
  const summary = [
@@ -35433,7 +35583,8 @@ function registerNavigateTool(pi, ctx) {
35433
35583
  req.toFile = toFile;
35434
35584
  try {
35435
35585
  const response = await callBridge(bridge, params.op, req, extCtx);
35436
- return textResult(JSON.stringify(response, null, 2));
35586
+ return textResult(formatCallgraphSections(params.op, response, PLAIN_CALLGRAPH_THEME).join(`
35587
+ `), response);
35437
35588
  } catch (error50) {
35438
35589
  if (error50 instanceof BridgeError && CALLGRAPH_SOFT_CODES.has(error50.code)) {
35439
35590
  return textResult(error50.message);
@@ -35511,25 +35662,25 @@ function buildOutlineSections(text, theme) {
35511
35662
  `)];
35512
35663
  }
35513
35664
  function buildZoomSections(args, payload, theme) {
35514
- const batch = asRecord2(payload);
35665
+ const batch = asRecord3(payload);
35515
35666
  const batchItems = Array.isArray(batch?.symbols) ? batch.symbols : Array.isArray(batch?.entries) ? batch.entries : null;
35516
35667
  if (batchItems) {
35517
35668
  const header = batch?.complete === false ? [theme.fg("warning", "Incomplete zoom results")] : [];
35518
35669
  return [
35519
35670
  ...header,
35520
35671
  ...batchItems.map((item) => {
35521
- const record2 = asRecord2(item);
35672
+ const record2 = asRecord3(item);
35522
35673
  if (!record2)
35523
35674
  return theme.fg("muted", "No zoom result available.");
35524
- const name = asString(record2.name) ?? "(unknown symbol)";
35525
- const itemTargetLabel = asString(record2.targetLabel) ?? zoomTargetLabel(args);
35675
+ const name = asString2(record2.name) ?? "(unknown symbol)";
35676
+ const itemTargetLabel = asString2(record2.targetLabel) ?? zoomTargetLabel(args);
35526
35677
  if (record2.success === false) {
35527
- const location = record2.targetLabel ? ` in ${shortenPath2(itemTargetLabel)}` : "";
35528
- return theme.fg("error", `Symbol "${name}" not found${location}: ${asString(record2.error) ?? "zoom failed"}`);
35678
+ const location = record2.targetLabel ? ` in ${shortenPath3(itemTargetLabel)}` : "";
35679
+ return theme.fg("error", `Symbol "${name}" not found${location}: ${asString2(record2.error) ?? "zoom failed"}`);
35529
35680
  }
35530
- const content = asString(record2.content);
35681
+ const content = asString2(record2.content);
35531
35682
  return [
35532
- `${theme.fg("accent", name)} ${theme.fg("muted", shortenPath2(itemTargetLabel))}`,
35683
+ `${theme.fg("accent", name)} ${theme.fg("muted", shortenPath3(itemTargetLabel))}`,
35533
35684
  content
35534
35685
  ].filter(Boolean).join(`
35535
35686
  `);
@@ -35540,32 +35691,32 @@ function buildZoomSections(args, payload, theme) {
35540
35691
  if (items.length === 0)
35541
35692
  return [theme.fg("muted", "No zoom result available.")];
35542
35693
  return items.map((item) => {
35543
- const record2 = asRecord2(item);
35694
+ const record2 = asRecord3(item);
35544
35695
  if (!record2)
35545
35696
  return theme.fg("muted", "No zoom result available.");
35546
- const name = asString(record2.name) ?? "(unknown symbol)";
35547
- const kind = asString(record2.kind) ?? "symbol";
35548
- const range = asRecord2(record2.range);
35697
+ const name = asString2(record2.name) ?? "(unknown symbol)";
35698
+ const kind = asString2(record2.kind) ?? "symbol";
35699
+ const range = asRecord3(record2.range);
35549
35700
  const startLine = range && typeof range.start_line === "number" ? range.start_line : undefined;
35550
35701
  const endLine = range && typeof range.end_line === "number" ? range.end_line : undefined;
35551
35702
  const targetLabel = zoomTargetLabel(args);
35552
- const location = startLine !== undefined ? `${shortenPath2(targetLabel)}:${startLine}${endLine && endLine !== startLine ? `-${endLine}` : ""}` : shortenPath2(targetLabel);
35703
+ const location = startLine !== undefined ? `${shortenPath3(targetLabel)}:${startLine}${endLine && endLine !== startLine ? `-${endLine}` : ""}` : shortenPath3(targetLabel);
35553
35704
  const lines = [`${theme.fg("accent", name)} ${theme.fg("muted", `[${kind}] ${location}`)}`];
35554
- const content = asString(record2.content);
35705
+ const content = asString2(record2.content);
35555
35706
  if (content) {
35556
35707
  lines.push(content.split(`
35557
35708
  `).map((line) => ` ${line}`).join(`
35558
35709
  `));
35559
35710
  }
35560
- const annotations = asRecord2(record2.annotations);
35561
- const callsOut = annotations ? asRecords(annotations.calls_out) : [];
35562
- const calledBy = annotations ? asRecords(annotations.called_by) : [];
35711
+ const annotations = asRecord3(record2.annotations);
35712
+ const callsOut = annotations ? asRecords2(annotations.calls_out) : [];
35713
+ const calledBy = annotations ? asRecords2(annotations.called_by) : [];
35563
35714
  if (callsOut.length > 0) {
35564
- lines.push(`${theme.fg("muted", "calls out")}`, callsOut.map((call) => ` ↳ ${asString(call.name) ?? "(unknown)"}${typeof call.line === "number" ? `:${call.line}` : ""}`).join(`
35715
+ lines.push(`${theme.fg("muted", "calls out")}`, callsOut.map((call) => ` ↳ ${asString2(call.name) ?? "(unknown)"}${typeof call.line === "number" ? `:${call.line}` : ""}`).join(`
35565
35716
  `));
35566
35717
  }
35567
35718
  if (calledBy.length > 0) {
35568
- lines.push(`${theme.fg("muted", "called by")}`, calledBy.map((call) => ` ↳ ${asString(call.name) ?? "(unknown)"}${typeof call.line === "number" ? `:${call.line}` : ""}`).join(`
35719
+ lines.push(`${theme.fg("muted", "called by")}`, calledBy.map((call) => ` ↳ ${asString2(call.name) ?? "(unknown)"}${typeof call.line === "number" ? `:${call.line}` : ""}`).join(`
35569
35720
  `));
35570
35721
  }
35571
35722
  return lines.join(`
@@ -35904,32 +36055,32 @@ var RefactorParams = Type11.Object({
35904
36055
  callSiteLine: optionalInt(1, Number.MAX_SAFE_INTEGER)
35905
36056
  });
35906
36057
  function buildRefactorSections(args, payload, theme) {
35907
- const response = asRecord2(payload);
36058
+ const response = asRecord3(payload);
35908
36059
  if (!response)
35909
36060
  return [theme.fg("muted", "No refactor result.")];
35910
36061
  if (args.op === "move") {
35911
- const results = asRecords(response.results);
36062
+ const results = asRecords2(response.results);
35912
36063
  return [
35913
36064
  `${theme.fg("success", "moved symbol")} ${theme.fg("toolOutput", args.symbol ?? "(symbol)")}`,
35914
- `${theme.fg("muted", "files modified")} ${asNumber(response.files_modified) ?? results.length}`,
35915
- `${theme.fg("muted", "consumers updated")} ${asNumber(response.consumers_updated) ?? 0}`,
35916
- results.length > 0 ? results.map((entry) => ` ↳ ${shortenPath2(asString(entry.file) ?? "(unknown file)")}`).join(`
36065
+ `${theme.fg("muted", "files modified")} ${asNumber2(response.files_modified) ?? results.length}`,
36066
+ `${theme.fg("muted", "consumers updated")} ${asNumber2(response.consumers_updated) ?? 0}`,
36067
+ results.length > 0 ? results.map((entry) => ` ↳ ${shortenPath3(asString2(entry.file) ?? "(unknown file)")}`).join(`
35917
36068
  `) : theme.fg("muted", "No files reported.")
35918
36069
  ];
35919
36070
  }
35920
36071
  if (args.op === "extract") {
35921
36072
  return [
35922
- `${theme.fg("success", "extracted")} ${theme.fg("toolOutput", asString(response.name) ?? args.name ?? "(function)")}`,
35923
- `${theme.fg("muted", "file")} ${theme.fg("accent", shortenPath2(asString(response.file) ?? args.filePath))}`,
36073
+ `${theme.fg("success", "extracted")} ${theme.fg("toolOutput", asString2(response.name) ?? args.name ?? "(function)")}`,
36074
+ `${theme.fg("muted", "file")} ${theme.fg("accent", shortenPath3(asString2(response.file) ?? args.filePath))}`,
35924
36075
  `${theme.fg("muted", "params")} ${Array.isArray(response.parameters) ? response.parameters.join(", ") || "none" : "none"}`,
35925
- `${theme.fg("muted", "return type")} ${asString(response.return_type) ?? "unknown"}`
36076
+ `${theme.fg("muted", "return type")} ${asString2(response.return_type) ?? "unknown"}`
35926
36077
  ];
35927
36078
  }
35928
36079
  return [
35929
- `${theme.fg("success", "inlined")} ${theme.fg("toolOutput", asString(response.symbol) ?? args.symbol ?? "(symbol)")}`,
35930
- `${theme.fg("muted", "file")} ${theme.fg("accent", shortenPath2(asString(response.file) ?? args.filePath))}`,
35931
- `${theme.fg("muted", "context")} ${asString(response.call_context) ?? "unknown"}`,
35932
- `${theme.fg("muted", "substitutions")} ${asNumber(response.substitutions) ?? 0}`
36080
+ `${theme.fg("success", "inlined")} ${theme.fg("toolOutput", asString2(response.symbol) ?? args.symbol ?? "(symbol)")}`,
36081
+ `${theme.fg("muted", "file")} ${theme.fg("accent", shortenPath3(asString2(response.file) ?? args.filePath))}`,
36082
+ `${theme.fg("muted", "context")} ${asString2(response.call_context) ?? "unknown"}`,
36083
+ `${theme.fg("muted", "substitutions")} ${asNumber2(response.substitutions) ?? 0}`
35933
36084
  ];
35934
36085
  }
35935
36086
  function renderRefactorCall(args, theme, context) {
@@ -36038,34 +36189,34 @@ var SafetyParams = Type12.Object({
36038
36189
  }))
36039
36190
  });
36040
36191
  function buildSafetySections(args, payload, theme) {
36041
- const response = asRecord2(payload);
36192
+ const response = asRecord3(payload);
36042
36193
  if (!response)
36043
36194
  return [theme.fg("muted", "No safety result.")];
36044
36195
  if (args.op === "undo") {
36045
36196
  if (response.operation === true) {
36046
36197
  return [
36047
- `${theme.fg("success", "restored operation")} ${theme.fg("accent", asString(response.op_id) ?? "(operation)")}`,
36048
- `${theme.fg("muted", "files")} ${asNumber(response.restored_count) ?? asRecords(response.restored).length}`
36198
+ `${theme.fg("success", "restored operation")} ${theme.fg("accent", asString2(response.op_id) ?? "(operation)")}`,
36199
+ `${theme.fg("muted", "files")} ${asNumber2(response.restored_count) ?? asRecords2(response.restored).length}`
36049
36200
  ];
36050
36201
  }
36051
36202
  return [
36052
- `${theme.fg("success", "restored")} ${theme.fg("accent", shortenPath2(asString(response.path) ?? args.filePath ?? "(file)"))}`,
36053
- `${theme.fg("muted", "backup")} ${asString(response.backup_id) ?? "—"}`
36203
+ `${theme.fg("success", "restored")} ${theme.fg("accent", shortenPath3(asString2(response.path) ?? args.filePath ?? "(file)"))}`,
36204
+ `${theme.fg("muted", "backup")} ${asString2(response.backup_id) ?? "—"}`
36054
36205
  ];
36055
36206
  }
36056
36207
  if (args.op === "history") {
36057
- const entries = asRecords(response.entries);
36208
+ const entries = asRecords2(response.entries);
36058
36209
  const sections2 = [
36059
- theme.fg("accent", shortenPath2(asString(response.file) ?? args.filePath ?? "(file)"))
36210
+ theme.fg("accent", shortenPath3(asString2(response.file) ?? args.filePath ?? "(file)"))
36060
36211
  ];
36061
36212
  if (entries.length === 0) {
36062
36213
  sections2.push(theme.fg("muted", "No history entries."));
36063
36214
  return sections2;
36064
36215
  }
36065
36216
  sections2.push(entries.map((entry, index) => {
36066
- const backupId = asString(entry.backup_id) ?? `entry-${index + 1}`;
36217
+ const backupId = asString2(entry.backup_id) ?? `entry-${index + 1}`;
36067
36218
  const timestamp = formatTimestamp(entry.timestamp) ?? "unknown time";
36068
- const description = asString(entry.description) ?? "";
36219
+ const description = asString2(entry.description) ?? "";
36069
36220
  return `${index + 1}. ${backupId} ${theme.fg("muted", timestamp)}${description ? `
36070
36221
  ${description}` : ""}`;
36071
36222
  }).join(`
@@ -36073,22 +36224,22 @@ function buildSafetySections(args, payload, theme) {
36073
36224
  return sections2;
36074
36225
  }
36075
36226
  if (args.op === "checkpoint") {
36076
- const skipped = asRecords(response.skipped);
36227
+ const skipped = asRecords2(response.skipped);
36077
36228
  return [
36078
- `${theme.fg("success", "checkpoint created")} ${theme.fg("accent", asString(response.name) ?? args.name ?? "(checkpoint)")}`,
36079
- `${theme.fg("muted", "files")} ${asNumber(response.file_count) ?? 0}`,
36229
+ `${theme.fg("success", "checkpoint created")} ${theme.fg("accent", asString2(response.name) ?? args.name ?? "(checkpoint)")}`,
36230
+ `${theme.fg("muted", "files")} ${asNumber2(response.file_count) ?? 0}`,
36080
36231
  skipped.length > 0 ? `${theme.fg("warning", "skipped")}
36081
- ${skipped.map((entry) => ` ↳ ${shortenPath2(asString(entry.file) ?? "(file)")}: ${asString(entry.error) ?? "unknown error"}`).join(`
36232
+ ${skipped.map((entry) => ` ↳ ${shortenPath3(asString2(entry.file) ?? "(file)")}: ${asString2(entry.error) ?? "unknown error"}`).join(`
36082
36233
  `)}` : theme.fg("muted", "No skipped files.")
36083
36234
  ];
36084
36235
  }
36085
36236
  if (args.op === "restore") {
36086
36237
  return [
36087
- `${theme.fg("success", "checkpoint restored")} ${theme.fg("accent", asString(response.name) ?? args.name ?? "(checkpoint)")}`,
36088
- `${theme.fg("muted", "files")} ${asNumber(response.file_count) ?? 0}`
36238
+ `${theme.fg("success", "checkpoint restored")} ${theme.fg("accent", asString2(response.name) ?? args.name ?? "(checkpoint)")}`,
36239
+ `${theme.fg("muted", "files")} ${asNumber2(response.file_count) ?? 0}`
36089
36240
  ];
36090
36241
  }
36091
- const checkpoints = asRecords(response.checkpoints);
36242
+ const checkpoints = asRecords2(response.checkpoints);
36092
36243
  const sections = [
36093
36244
  theme.fg("accent", `${checkpoints.length} checkpoint${checkpoints.length === 1 ? "" : "s"}`)
36094
36245
  ];
@@ -36097,8 +36248,8 @@ ${skipped.map((entry) => ` ↳ ${shortenPath2(asString(entry.file) ?? "(file)")
36097
36248
  return sections;
36098
36249
  }
36099
36250
  sections.push(checkpoints.map((checkpoint, index) => {
36100
- const name = asString(checkpoint.name) ?? `checkpoint-${index + 1}`;
36101
- const count = asNumber(checkpoint.file_count) ?? 0;
36251
+ const name = asString2(checkpoint.name) ?? `checkpoint-${index + 1}`;
36252
+ const count = asNumber2(checkpoint.file_count) ?? 0;
36102
36253
  const created = formatTimestamp(checkpoint.created_at) ?? "unknown time";
36103
36254
  return `${index + 1}. ${name} ${theme.fg("muted", `${count} file${count === 1 ? "" : "s"} · ${created}`)}`;
36104
36255
  }).join(`
@@ -36242,13 +36393,13 @@ var SearchParams2 = Type13.Object({
36242
36393
  }))
36243
36394
  });
36244
36395
  function buildSemanticSections(args, payload, theme) {
36245
- const response = asRecord2(payload);
36396
+ const response = asRecord3(payload);
36246
36397
  if (!response)
36247
36398
  return [theme.fg("muted", "No search result.")];
36248
- const status = asString(response.status) ?? "unknown";
36249
- const semanticStatus = asString(response.semantic_status) ?? status;
36250
- const interpretedAs = asString(response.interpreted_as) ?? "unknown";
36251
- const queryKind = asString(response.query_kind);
36399
+ const status = asString2(response.status) ?? "unknown";
36400
+ const semanticStatus = asString2(response.semantic_status) ?? status;
36401
+ const interpretedAs = asString2(response.interpreted_as) ?? "unknown";
36402
+ const queryKind = asString2(response.query_kind);
36252
36403
  const sections = [
36253
36404
  `${theme.fg(semanticStatus === "ready" ? "success" : "warning", `semantic: ${semanticStatus}`)} ${theme.fg("muted", `mode=${interpretedAs}${queryKind ? ` kind=${queryKind}` : ""} query=${JSON.stringify(args.query)} topK=${args.topK ?? 10}`)}`
36254
36405
  ];
@@ -36260,34 +36411,34 @@ function buildSemanticSections(args, payload, theme) {
36260
36411
  const honestyNote = semanticHonestyNote(response, theme);
36261
36412
  if (honestyNote)
36262
36413
  sections.push(honestyNote);
36263
- const results = asRecords(response.results);
36414
+ const results = asRecords2(response.results);
36264
36415
  if (status !== "ready" && results.length === 0) {
36265
- sections.push(asString(response.text) ?? theme.fg("muted", "Semantic index is not ready."));
36416
+ sections.push(asString2(response.text) ?? theme.fg("muted", "Semantic index is not ready."));
36266
36417
  return sections;
36267
36418
  }
36268
36419
  if (results.length === 0) {
36269
36420
  sections.push(theme.fg("muted", "No matches found."));
36270
36421
  return sections;
36271
36422
  }
36272
- const grouped = groupByFile(results, (result) => asString(result.file));
36423
+ const grouped = groupByFile(results, (result) => asString2(result.file));
36273
36424
  for (const [file2, fileResults] of grouped.entries()) {
36274
- const lines = [theme.fg("accent", shortenPath2(file2))];
36425
+ const lines = [theme.fg("accent", shortenPath3(file2))];
36275
36426
  fileResults.forEach((result) => {
36276
- if (asString(result.kind) === "GrepLine") {
36277
- const line = asNumber(result.line);
36278
- const column = asNumber(result.column);
36279
- const lineText = asString(result.line_text) ?? "";
36427
+ if (asString2(result.kind) === "GrepLine") {
36428
+ const line = asNumber2(result.line);
36429
+ const column = asNumber2(result.column);
36430
+ const lineText = asString2(result.line_text) ?? "";
36280
36431
  const location2 = line !== undefined ? `${line}${column !== undefined ? `:${column}` : ""}` : "?";
36281
36432
  lines.push(` ↳ ${theme.fg("muted", `line ${location2}`)} ${lineText}`);
36282
36433
  return;
36283
36434
  }
36284
- const score = asNumber(result.score);
36285
- const source = asString(result.source);
36286
- const kind = asString(result.kind) ?? "symbol";
36287
- const location = asString(result.location);
36435
+ const score = asNumber2(result.score);
36436
+ const source = asString2(result.source);
36437
+ const kind = asString2(result.kind) ?? "symbol";
36438
+ const location = asString2(result.location);
36288
36439
  if (source === "lexical") {
36289
36440
  lines.push(` ↳ ${theme.fg("muted", `[lexical match${score !== undefined ? ` — score ${score.toFixed(3)}` : ""}]`)}`);
36290
- const snippet2 = asString(result.snippet);
36441
+ const snippet2 = asString2(result.snippet);
36291
36442
  if (snippet2) {
36292
36443
  lines.push(...snippet2.split(`
36293
36444
  `).map((line) => ` ${line}`));
@@ -36295,16 +36446,16 @@ function buildSemanticSections(args, payload, theme) {
36295
36446
  return;
36296
36447
  }
36297
36448
  if (kind === "file_summary" || location === "[file summary]") {
36298
- const summary = asString(result.snippet) ?? asString(result.name) ?? "(no summary)";
36449
+ const summary = asString2(result.snippet) ?? asString2(result.name) ?? "(no summary)";
36299
36450
  lines.push(` ↳ ${summary} ${theme.fg("muted", `[file summary${score !== undefined ? ` score ${score.toFixed(3)}` : ""}]`)}`);
36300
36451
  return;
36301
36452
  }
36302
- const startLine = asNumber(result.start_line);
36303
- const endLine = asNumber(result.end_line);
36453
+ const startLine = asNumber2(result.start_line);
36454
+ const endLine = asNumber2(result.end_line);
36304
36455
  const range = startLine !== undefined ? `${startLine}${endLine && endLine !== startLine ? `-${endLine}` : ""}` : "?";
36305
- const name = asString(result.name) ?? "(unknown)";
36456
+ const name = asString2(result.name) ?? "(unknown)";
36306
36457
  lines.push(` ↳ ${name} ${theme.fg("muted", `[${kind}] lines ${range}${score !== undefined ? ` score ${score.toFixed(3)}` : ""}`)}`);
36307
- const snippet = asString(result.snippet);
36458
+ const snippet = asString2(result.snippet);
36308
36459
  if (snippet) {
36309
36460
  lines.push(...snippet.split(`
36310
36461
  `).map((line) => ` ${line}`));
@@ -36416,11 +36567,11 @@ function buildWorkflowHints(opts) {
36416
36567
  }
36417
36568
  if (hasBgBash) {
36418
36569
  sections.push([
36419
- `**Long-running commands** (builds, installs, full test suites): \`${bashName}({ background: true })\` returns a \`task_id\` immediately. Then do ONE of these:`,
36420
- "1. Keep working on something independent of the result.",
36421
- "2. End your turn a completion reminder with the result arrives automatically.",
36422
- `3. Need to react to a specific output line early? Register \`bash_watch({ task_id, pattern, background: true })\`, then end your turn — it pings you the moment the pattern appears.`,
36423
- `\`bash_status({ task_id })\` is for inspecting output AFTER the reminder arrives, or one quick look at a live task — never call it repeatedly to wait: the reminder already delivers the result, and each poll wastes a turn. Sync \`bash_watch\` (without \`background: true\`) blocks your turn and locks the user out reserve it for waits of a few seconds (a dev server printing its ready line), never for builds or test suites.`
36570
+ `**Long-running commands** (builds, installs, full test suites): \`${bashName}({ background: true })\` returns a \`task_id\` immediately.`,
36571
+ "1. Nothing else useful to do (a build/test/validation whose result is the next thing you need) → sync `bash_watch` to block until it exits (pass a longer timeout_ms for long commands; the user can interrupt).",
36572
+ "2. Useful parallel work available → end your turn; the completion reminder delivers the result. (Or spawn a subagent for the side work.)",
36573
+ "3. Want to react to a specific early output line async `bash_watch` (background:true + pattern).",
36574
+ "Never loop `bash_status` to wait — it's a one-shot inspector, not a wait primitive."
36424
36575
  ].join(`
36425
36576
  `));
36426
36577
  sections.push(`**PTY / interactive commands**: PTY mode is for interactive REPLs and terminal apps (python, node, bash itself, vim). Start with \`${bashName}({ command: "python", pty: true, background: true })\`, read the screen with \`bash_status({ task_id, output_mode: "screen" })\`, and send input with \`bash_write({ task_id, input: "..." })\`.`);
@@ -36535,7 +36686,7 @@ function isConfigureWarning(value) {
36535
36686
  if (!value || typeof value !== "object" || Array.isArray(value))
36536
36687
  return false;
36537
36688
  const warning = value;
36538
- return (warning.kind === "formatter_not_installed" || warning.kind === "checker_not_installed" || warning.kind === "lsp_binary_missing") && typeof warning.hint === "string";
36689
+ return (warning.kind === "formatter_not_installed" || warning.kind === "checker_not_installed" || warning.kind === "lsp_binary_missing" || warning.kind === "config_parse_failed") && typeof warning.hint === "string";
36539
36690
  }
36540
36691
  function coerceConfigureWarnings(warnings) {
36541
36692
  return warnings.filter(isConfigureWarning);
@@ -36545,6 +36696,18 @@ function drainPendingEagerWarnings(projectRoot) {
36545
36696
  pendingEagerWarnings.delete(projectRoot);
36546
36697
  return pending;
36547
36698
  }
36699
+ function enqueueConfigParseWarnings(projectRoot, errors3) {
36700
+ if (!projectRoot || errors3.length === 0)
36701
+ return;
36702
+ const pending = pendingEagerWarnings.get(projectRoot) ?? [];
36703
+ for (const entry of errors3) {
36704
+ const hint = formatConfigParseFailureMessage(entry.path, entry.message);
36705
+ if (!pending.some((item) => item.kind === "config_parse_failed" && item.hint === hint)) {
36706
+ pending.push({ kind: "config_parse_failed", hint });
36707
+ }
36708
+ }
36709
+ pendingEagerWarnings.set(projectRoot, pending);
36710
+ }
36548
36711
  function shouldPrepareOnnxRuntime(config2) {
36549
36712
  const isFastembedSemanticBackend = (config2.semantic?.backend ?? "fastembed") === "fastembed";
36550
36713
  return config2.semantic_search === true && isFastembedSemanticBackend;
@@ -36653,6 +36816,7 @@ async function src_default(pi) {
36653
36816
  }
36654
36817
  await ensureStorageMigrated({ harness: "pi", binaryPath, logger: bridgeLogger });
36655
36818
  const config2 = loadAftConfig(process.cwd());
36819
+ enqueueConfigParseWarnings(process.cwd(), getConfigLoadErrors());
36656
36820
  const storageDir = resolveCortexKitStorageRoot();
36657
36821
  let onnxRuntimePromise = null;
36658
36822
  if (shouldPrepareOnnxRuntime(config2)) {
@@ -36661,30 +36825,7 @@ async function src_default(pi) {
36661
36825
  return null;
36662
36826
  });
36663
36827
  }
36664
- const configOverrides = {};
36665
- if (config2.format_on_edit !== undefined)
36666
- configOverrides.format_on_edit = config2.format_on_edit;
36667
- if (config2.formatter_timeout_secs !== undefined)
36668
- configOverrides.formatter_timeout_secs = config2.formatter_timeout_secs;
36669
- if (config2.validate_on_edit !== undefined)
36670
- configOverrides.validate_on_edit = config2.validate_on_edit;
36671
- if (config2.formatter !== undefined)
36672
- configOverrides.formatter = config2.formatter;
36673
- if (config2.checker !== undefined)
36674
- configOverrides.checker = config2.checker;
36675
- configOverrides.restrict_to_project_root = config2.restrict_to_project_root ?? false;
36676
- if (config2.search_index !== undefined)
36677
- configOverrides.search_index = config2.search_index;
36678
- if (config2.semantic_search !== undefined)
36679
- configOverrides.semantic_search = config2.semantic_search;
36680
- Object.assign(configOverrides, resolveExperimentalConfigForConfigure(config2));
36681
- Object.assign(configOverrides, resolveLspConfigForConfigure(config2));
36682
- if (config2.semantic !== undefined)
36683
- configOverrides.semantic = config2.semantic;
36684
- if (config2.inspect !== undefined)
36685
- configOverrides.inspect = config2.inspect;
36686
- if (config2.max_callgraph_files !== undefined)
36687
- configOverrides.max_callgraph_files = config2.max_callgraph_files;
36828
+ const configOverrides = resolveProjectOverridesForConfigure(config2);
36688
36829
  if (config2.url_fetch_allow_private !== undefined)
36689
36830
  configOverrides.url_fetch_allow_private = config2.url_fetch_allow_private;
36690
36831
  configOverrides.storage_dir = storageDir;
@@ -36892,12 +37033,25 @@ ${lines}
36892
37033
  return { content, details: event.details, isError: event.isError };
36893
37034
  });
36894
37035
  pi.on("turn_end", async (_event, extCtx) => {
37036
+ const sessionID = resolveSessionId(extCtx);
36895
37037
  await handleTurnEndBgCompletions({
36896
37038
  ctx,
36897
37039
  directory: extCtx.cwd,
36898
- sessionID: resolveSessionId(extCtx),
37040
+ sessionID,
36899
37041
  runtime: pi
36900
37042
  });
37043
+ const bridge = pool.getActiveBridgeForRoot(extCtx.cwd);
37044
+ if (bridge && sessionID) {
37045
+ handleConfigureWarningsForSession({
37046
+ projectRoot: extCtx.cwd,
37047
+ sessionId: sessionID,
37048
+ client: pi,
37049
+ bridge,
37050
+ warnings: [],
37051
+ storageDir,
37052
+ pluginVersion: PLUGIN_VERSION
37053
+ });
37054
+ }
36901
37055
  });
36902
37056
  pi.on("input", (_event, extCtx) => {
36903
37057
  signalSyncWatchAbort(resolveSessionId(extCtx));
@@ -36926,6 +37080,7 @@ ${lines}
36926
37080
  log2(`AFT extension ready (surface=${config2.tool_surface ?? "recommended"})`);
36927
37081
  }
36928
37082
  var __test__2 = {
37083
+ enqueueConfigParseWarnings,
36929
37084
  bridgeDirectoryFromCallback,
36930
37085
  resolveToolSurface,
36931
37086
  handleConfigureWarningsForSession,