@cortexkit/aft-opencode 0.37.2 → 0.38.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.js CHANGED
@@ -14250,37 +14250,67 @@ var GREP_GUARD_FLAGS = new Set([
14250
14250
  function maybeStripCompressorPipe(command, compressionEnabled) {
14251
14251
  if (!compressionEnabled)
14252
14252
  return { command, stripped: false };
14253
- if (containsUnsplittableConstruct(command))
14254
- return { command, stripped: false };
14255
- const chain = splitTopLevelAndChain(command);
14253
+ const chain = splitTopLevelCommandChain(command);
14256
14254
  if (chain === null)
14257
14255
  return { command, stripped: false };
14258
- const prefix = chain.slice(0, -1).map((segment) => segment.trim()).filter(Boolean);
14259
- const pipeline3 = chain[chain.length - 1] ?? "";
14260
- const stages = splitTopLevelPipeline(pipeline3);
14261
- if (stages.length < 2)
14256
+ let stripped = false;
14257
+ const droppedFilterChains = [];
14258
+ const rebuilt = chain.map(({ segment, separator }) => {
14259
+ const result = stripSinglePipelineSegment(segment);
14260
+ if (result.stripped) {
14261
+ stripped = true;
14262
+ droppedFilterChains.push(result.filters);
14263
+ }
14264
+ return `${result.segment}${separator}`;
14265
+ }).join("");
14266
+ if (!stripped)
14262
14267
  return { command, stripped: false };
14268
+ return {
14269
+ command: rebuilt,
14270
+ stripped: true,
14271
+ note: formatStripNote(droppedFilterChains)
14272
+ };
14273
+ }
14274
+ function stripSinglePipelineSegment(segment) {
14275
+ const leading = /^\s*/.exec(segment)?.[0] ?? "";
14276
+ const trailing = /\s*$/.exec(segment)?.[0] ?? "";
14277
+ const coreStart = leading.length;
14278
+ const coreEnd = segment.length - trailing.length;
14279
+ if (coreEnd <= coreStart)
14280
+ return { segment, stripped: false };
14281
+ const core = segment.slice(coreStart, coreEnd);
14282
+ if (containsUnsplittableConstruct(core))
14283
+ return { segment, stripped: false };
14284
+ if (hasUnquotedBackground(core))
14285
+ return { segment, stripped: false };
14286
+ const stages = splitTopLevelPipeline(core);
14287
+ if (stages.length < 2)
14288
+ return { segment, stripped: false };
14263
14289
  const firstStage = stages[0]?.trim() ?? "";
14264
14290
  if (!isCompressorHandledRunner(firstStage))
14265
- return { command, stripped: false };
14291
+ return { segment, stripped: false };
14266
14292
  const filterStages = stages.slice(1).map((stage) => stage.trim());
14267
14293
  for (const stage of filterStages) {
14268
14294
  if (!filterStageIsSafeToDrop(stage))
14269
- return { command, stripped: false };
14295
+ return { segment, stripped: false };
14270
14296
  }
14271
- const filters = filterStages.join(" | ");
14272
- const rebuilt = [...prefix, firstStage].join(" && ");
14273
14297
  return {
14274
- command: rebuilt,
14298
+ segment: `${leading}${firstStage}${trailing}`,
14275
14299
  stripped: true,
14276
- note: `[AFT dropped \`| ${filters}\` (compressed:false to keep)]`
14300
+ filters: filterStages.join(" | ")
14277
14301
  };
14278
14302
  }
14279
- function splitTopLevelAndChain(command) {
14303
+ function formatStripNote(droppedFilterChains) {
14304
+ const filters = droppedFilterChains.map((filter) => `\`| ${filter}\``).join(", ");
14305
+ return `[AFT dropped ${filters} (compressed:false to keep)]`;
14306
+ }
14307
+ function splitTopLevelCommandChain(command) {
14280
14308
  const segments = [];
14281
14309
  let start = 0;
14282
14310
  let quote = null;
14283
14311
  let escaped = false;
14312
+ let inBacktick = false;
14313
+ let parenDepth = 0;
14284
14314
  for (let index = 0;index < command.length; index++) {
14285
14315
  const char = command[index];
14286
14316
  const next = command[index + 1];
@@ -14288,6 +14318,13 @@ function splitTopLevelAndChain(command) {
14288
14318
  escaped = false;
14289
14319
  continue;
14290
14320
  }
14321
+ if (inBacktick) {
14322
+ if (char === "\\")
14323
+ escaped = true;
14324
+ else if (char === "`")
14325
+ inBacktick = false;
14326
+ continue;
14327
+ }
14291
14328
  if (char === "\\" && quote !== "'") {
14292
14329
  escaped = true;
14293
14330
  continue;
@@ -14301,26 +14338,47 @@ function splitTopLevelAndChain(command) {
14301
14338
  quote = char;
14302
14339
  continue;
14303
14340
  }
14304
- if (char === "&" && next === "&") {
14305
- segments.push(command.slice(start, index));
14306
- start = index + 2;
14307
- index++;
14341
+ if (char === "`") {
14342
+ inBacktick = true;
14308
14343
  continue;
14309
14344
  }
14310
- if (char === "|" && next === "|")
14311
- return null;
14312
- if (char === ";")
14313
- return null;
14345
+ if (char === "(") {
14346
+ parenDepth++;
14347
+ continue;
14348
+ }
14349
+ if (char === ")") {
14350
+ if (parenDepth === 0)
14351
+ return null;
14352
+ parenDepth--;
14353
+ continue;
14354
+ }
14355
+ if (parenDepth > 0)
14356
+ continue;
14314
14357
  if (char === `
14315
14358
  ` || char === "\r")
14316
14359
  return null;
14317
- if (char === "&") {
14318
- const prev = command[index - 1];
14319
- if (prev !== ">" && next !== ">")
14320
- return null;
14360
+ if (char === "#" && (index === 0 || command[index - 1] === " " || command[index - 1] === "\t"))
14361
+ return null;
14362
+ if (char === "&" && next === "&") {
14363
+ segments.push({ segment: command.slice(start, index), separator: "&&" });
14364
+ start = index + 2;
14365
+ index++;
14366
+ continue;
14367
+ }
14368
+ if (char === "|" && next === "|") {
14369
+ segments.push({ segment: command.slice(start, index), separator: "||" });
14370
+ start = index + 2;
14371
+ index++;
14372
+ continue;
14373
+ }
14374
+ if (char === ";") {
14375
+ segments.push({ segment: command.slice(start, index), separator: ";" });
14376
+ start = index + 1;
14321
14377
  }
14322
14378
  }
14323
- segments.push(command.slice(start));
14379
+ if (escaped || quote || inBacktick || parenDepth !== 0)
14380
+ return null;
14381
+ segments.push({ segment: command.slice(start), separator: "" });
14324
14382
  return segments;
14325
14383
  }
14326
14384
  function splitTopLevelPipeline(command) {
@@ -14383,13 +14441,13 @@ function isCompressorHandledRunner(stage) {
14383
14441
  args = args.slice(1);
14384
14442
  const sub = args[0];
14385
14443
  const subNext = args[1];
14386
- return sub === "test" || sub === "run" && startsWithTest(subNext);
14444
+ return sub === "test" || sub === "run" && isJsVerificationScript(subNext);
14387
14445
  }
14388
14446
  if (first === "npm" || first === "pnpm") {
14389
- return second === "test" || second === "run" && startsWithTest(third);
14447
+ return second === "test" || second === "run" && isJsVerificationScript(third);
14390
14448
  }
14391
14449
  if (first === "yarn") {
14392
- return startsWithTest(second) || second === "run" && startsWithTest(third);
14450
+ return isJsVerificationScript(second) || second === "run" && isJsVerificationScript(third);
14393
14451
  }
14394
14452
  if (first === "deno")
14395
14453
  return ["test", "lint", "check", "bench"].includes(second ?? "");
@@ -14467,8 +14525,10 @@ function hasBuildTask(args, tasks) {
14467
14525
  }
14468
14526
  return sawAllowed;
14469
14527
  }
14470
- function startsWithTest(token) {
14471
- return token?.startsWith("test") === true;
14528
+ function isJsVerificationScript(token) {
14529
+ if (!token)
14530
+ return false;
14531
+ return token.startsWith("test") || token === "typecheck" || token.startsWith("typecheck:");
14472
14532
  }
14473
14533
  var XCODEBUILD_VALUE_FLAGS = new Set([
14474
14534
  "-scheme",
@@ -33127,6 +33187,7 @@ function writeTerminal(terminal, data) {
33127
33187
  // src/shared/rpc-server.ts
33128
33188
  import { randomBytes as randomBytes2 } from "node:crypto";
33129
33189
  import {
33190
+ existsSync as existsSync14,
33130
33191
  mkdirSync as mkdirSync10,
33131
33192
  readdirSync as readdirSync5,
33132
33193
  readFileSync as readFileSync13,
@@ -33185,6 +33246,8 @@ function parseRpcPortRecord(content) {
33185
33246
  }
33186
33247
 
33187
33248
  // src/shared/rpc-server.ts
33249
+ var PORT_FILE_HEARTBEAT_MS = 15000;
33250
+
33188
33251
  class AftRpcServer {
33189
33252
  server = null;
33190
33253
  port = 0;
@@ -33192,6 +33255,7 @@ class AftRpcServer {
33192
33255
  handlers = new Map;
33193
33256
  portFilePath;
33194
33257
  portsDir;
33258
+ heartbeatTimer = null;
33195
33259
  instanceId;
33196
33260
  constructor(storageDir, directory) {
33197
33261
  this.portsDir = rpcPortFileDir(storageDir, directory);
@@ -33229,6 +33293,8 @@ class AftRpcServer {
33229
33293
  }), { encoding: "utf-8", mode: 384 });
33230
33294
  renameSync8(tmpPath, this.portFilePath);
33231
33295
  log2(`RPC server listening on 127.0.0.1:${this.port}`);
33296
+ this.heartbeatTimer = setInterval(() => this.ensurePortFile(), PORT_FILE_HEARTBEAT_MS);
33297
+ this.heartbeatTimer.unref?.();
33232
33298
  this.sweepDeadPortFiles();
33233
33299
  } catch (err) {
33234
33300
  warn2(`Failed to write RPC port file: ${err}`);
@@ -33263,7 +33329,28 @@ class AftRpcServer {
33263
33329
  } catch {}
33264
33330
  }
33265
33331
  }
33332
+ ensurePortFile() {
33333
+ if (!this.server || this.token === null)
33334
+ return;
33335
+ try {
33336
+ if (existsSync14(this.portFilePath))
33337
+ return;
33338
+ const tmpPath = `${this.portFilePath}.tmp`;
33339
+ writeFileSync10(tmpPath, JSON.stringify({
33340
+ port: this.port,
33341
+ token: this.token,
33342
+ pid: process.pid,
33343
+ started_at: Date.now()
33344
+ }), { encoding: "utf-8", mode: 384 });
33345
+ renameSync8(tmpPath, this.portFilePath);
33346
+ log2(`RPC port file was missing; rewrote ${this.portFilePath}`);
33347
+ } catch {}
33348
+ }
33266
33349
  stop() {
33350
+ if (this.heartbeatTimer) {
33351
+ clearInterval(this.heartbeatTimer);
33352
+ this.heartbeatTimer = null;
33353
+ }
33267
33354
  if (this.server) {
33268
33355
  this.server.close();
33269
33356
  this.server = null;
@@ -33622,7 +33709,7 @@ function formatStatusMarkdown(status) {
33622
33709
  }
33623
33710
  if (status.status_bar) {
33624
33711
  const sb = status.status_bar;
33625
- lines.push("", `### Code Health${sb.tier2_stale ? " (~ stale)" : ""}`, `- **Errors:** ${formatCount(sb.errors)}`, `- **Warnings:** ${formatCount(sb.warnings)}`, `- **Duplicates:** ${formatCount(sb.duplicates)}`, `- **TODOs:** ${formatCount(sb.todos)}`);
33712
+ lines.push("", `### Code Health${sb.tier2_stale ? " (~ stale)" : ""}`, `- **Errors:** ${formatCount(sb.errors)}`, `- **Warnings:** ${formatCount(sb.warnings)}`, `- **Dead code:** ${formatCount(sb.dead_code)}`, `- **Unused exports:** ${formatCount(sb.unused_exports)}`, `- **Duplicates:** ${formatCount(sb.duplicates)}`, `- **TODOs:** ${formatCount(sb.todos)}`);
33626
33713
  }
33627
33714
  lines.push("", "### Current session", `- **ID:** \`${status.session.id}\``, `- **Tracked files:** ${formatCount(status.session.tracked_files)}`, `- **Checkpoints:** ${formatCount(status.session.checkpoints)}`, `- **Project checkpoints (all sessions):** ${formatCount(status.checkpoints_total)}`);
33628
33715
  return lines.join(`
@@ -33631,7 +33718,7 @@ function formatStatusMarkdown(status) {
33631
33718
 
33632
33719
  // src/shared/tui-config.ts
33633
33720
  var import_comment_json4 = __toESM(require_src2(), 1);
33634
- import { existsSync as existsSync14, mkdirSync as mkdirSync11, readFileSync as readFileSync14, writeFileSync as writeFileSync11 } from "node:fs";
33721
+ import { existsSync as existsSync15, mkdirSync as mkdirSync11, readFileSync as readFileSync14, writeFileSync as writeFileSync11 } from "node:fs";
33635
33722
  import { dirname as dirname10, join as join21 } from "node:path";
33636
33723
 
33637
33724
  // src/shared/opencode-config-dir.ts
@@ -33668,9 +33755,9 @@ function resolveTuiConfigPath() {
33668
33755
  const configDir = getOpenCodeConfigPaths({ binary: "opencode" }).configDir;
33669
33756
  const jsoncPath = join21(configDir, "tui.jsonc");
33670
33757
  const jsonPath = join21(configDir, "tui.json");
33671
- if (existsSync14(jsoncPath))
33758
+ if (existsSync15(jsoncPath))
33672
33759
  return jsoncPath;
33673
- if (existsSync14(jsonPath))
33760
+ if (existsSync15(jsonPath))
33674
33761
  return jsonPath;
33675
33762
  return jsonPath;
33676
33763
  }
@@ -33678,7 +33765,7 @@ function ensureTuiPluginEntry() {
33678
33765
  try {
33679
33766
  const configPath = resolveTuiConfigPath();
33680
33767
  let config2 = {};
33681
- if (existsSync14(configPath)) {
33768
+ if (existsSync15(configPath)) {
33682
33769
  config2 = import_comment_json4.parse(readFileSync14(configPath, "utf-8")) ?? {};
33683
33770
  }
33684
33771
  const plugins = Array.isArray(config2.plugin) ? config2.plugin.filter((value) => typeof value === "string") : [];
@@ -33730,6 +33817,12 @@ async function runCleanups(reason) {
33730
33817
  runningCleanups = false;
33731
33818
  }
33732
33819
  }
33820
+ var SIGNAL_EXIT_CODES = { SIGINT: 130, SIGTERM: 143, SIGHUP: 129 };
33821
+ var SIGNAL_CLEANUP_TIMEOUT_MS = 5000;
33822
+ function shouldForceExit(otherListenerCount) {
33823
+ return otherListenerCount === 0;
33824
+ }
33825
+ var signalShutdownStarted = false;
33733
33826
  function installProcessHandlers() {
33734
33827
  const state = getState();
33735
33828
  if (state.installed)
@@ -33737,9 +33830,27 @@ function installProcessHandlers() {
33737
33830
  state.installed = true;
33738
33831
  const signals = ["SIGTERM", "SIGINT", "SIGHUP"];
33739
33832
  for (const sig of signals) {
33740
- process.on(sig, () => {
33741
- runCleanups(sig);
33742
- });
33833
+ const handler = () => {
33834
+ const others = process.listenerCount(sig) - 1;
33835
+ if (!shouldForceExit(others)) {
33836
+ const names = process.listeners(sig).filter((fn) => fn !== handler).map((fn) => fn.name || fn.toString().slice(0, 80).replace(/\s+/g, " ")).join(" | ");
33837
+ log2(`${sig}: deferring termination to ${others} other listener(s); cleanup only. Others: ${names}`);
33838
+ runCleanups(sig);
33839
+ return;
33840
+ }
33841
+ if (signalShutdownStarted) {
33842
+ process.exit(SIGNAL_EXIT_CODES[sig]);
33843
+ }
33844
+ signalShutdownStarted = true;
33845
+ process.exitCode = SIGNAL_EXIT_CODES[sig];
33846
+ const timeout = new Promise((resolve5) => {
33847
+ setTimeout(resolve5, SIGNAL_CLEANUP_TIMEOUT_MS);
33848
+ });
33849
+ Promise.race([runCleanups(sig), timeout]).finally(() => {
33850
+ process.exit(SIGNAL_EXIT_CODES[sig]);
33851
+ });
33852
+ };
33853
+ process.on(sig, handler);
33743
33854
  }
33744
33855
  process.on("beforeExit", () => {
33745
33856
  runCleanups("beforeExit");
@@ -33910,7 +34021,15 @@ ${candidates.map((candidate) => ` - ${candidate}`).join(`
33910
34021
  `);
33911
34022
  }
33912
34023
  function collectStructuredExtras(response) {
33913
- const reserved = new Set(["id", "success", "code", "message", "data"]);
34024
+ const reserved = new Set([
34025
+ "id",
34026
+ "success",
34027
+ "code",
34028
+ "message",
34029
+ "data",
34030
+ "status_bar",
34031
+ "bg_completions"
34032
+ ]);
33914
34033
  const extras = {};
33915
34034
  for (const [key, value] of Object.entries(response)) {
33916
34035
  if (reserved.has(key))
@@ -34190,16 +34309,24 @@ async function checkAstPathsPermission(context, paths) {
34190
34309
  }
34191
34310
  return;
34192
34311
  }
34193
- var SUPPORTED_LANGS = ["typescript", "tsx", "javascript", "python", "rust", "go"];
34312
+ var SUPPORTED_LANGS = [
34313
+ "typescript",
34314
+ "tsx",
34315
+ "javascript",
34316
+ "python",
34317
+ "rust",
34318
+ "go",
34319
+ "pascal"
34320
+ ];
34194
34321
  function astTools(ctx) {
34195
34322
  const searchTool = {
34196
- description: `Search code patterns across filesystem using AST-aware matching. Supports 6 languages.
34323
+ description: `Search code patterns across filesystem using AST-aware matching. Supports 7 languages.
34197
34324
 
34198
34325
  ` + `Use meta-variables: $VAR matches a single AST node, $$$ matches multiple nodes (variadic).
34199
34326
  ` + `IMPORTANT: Patterns must be complete AST nodes (valid code fragments).
34200
34327
  ` + `For functions, include params and body: 'export async function $NAME($$$) { $$$ }' not just 'export async function $NAME'.
34201
34328
 
34202
- ` + "Examples: pattern='console.log($MSG)' lang='typescript', pattern='async function $NAME($$$) { $$$ }' lang='javascript', pattern='def $FUNC($$$): $$$' lang='python'",
34329
+ ` + "Examples: pattern='console.log($MSG)' lang='typescript', pattern='async function $NAME($$$) { $$$ }' lang='javascript', pattern='def $FUNC($$$): $$$' lang='python', pattern='Writeln($MSG);' lang='pascal'",
34203
34330
  args: {
34204
34331
  pattern: z3.string().describe("AST pattern with meta-variables ($VAR, $$$). Must be complete AST node."),
34205
34332
  lang: z3.enum(SUPPORTED_LANGS).describe("Target language"),
@@ -38091,12 +38218,12 @@ var PLUGIN_VERSION = (() => {
38091
38218
  return "0.0.0";
38092
38219
  }
38093
38220
  })();
38094
- var ANNOUNCEMENT_VERSION = "0.37.0";
38221
+ var ANNOUNCEMENT_VERSION = "0.38.0";
38095
38222
  var ANNOUNCEMENT_FEATURES = [
38096
- "Much lower background CPU: idle bridges shut down after 30 minutes, deleted project roots stop the watcher instead of spinning it, and background scans reuse warm caches on a bounded thread pool.",
38097
- "`aft_transform` removed: usage data showed agents never called it `edit` covers everything it did, and every request now carries a smaller tool surface.",
38098
- "Leaner tools: `bash`, `write`, `edit`, `apply_patch`, `aft_search`, and `aft_refactor` descriptions trimmed and config-aware; system-prompt guidance rewritten around what to do instead of what to avoid.",
38099
- "Background bash reminders are right-sized: failures keep head + tail context, successes show a short tail, and compressors never print a success summary for a non-zero exit."
38223
+ "Code Health you can trust: dead code and unused exports are back in the sidebar and status displays, rebuilt on a real TS/JS module-graph engine (oxc) barrel re-exports, entry points, and dynamic imports resolve correctly now.",
38224
+ "Tools stay responsive during builds: filesystem events are processed off the request thread, so a cargo/webpack compile no longer queues your tool calls behind an event flood.",
38225
+ "Pascal support in outline, zoom, and the AST tools.",
38226
+ "Sidebar reliability: the status panel reconnects after heavy host load instead of going dark, and transient environment errors (schema fetch timeouts) no longer count as code errors."
38100
38227
  ];
38101
38228
  var ANNOUNCEMENT_FOOTER = "Join us on Discord: https://discord.gg/DSa65w8wuf";
38102
38229
  var plugin = async (input) => initializePluginForDirectory(input);
@@ -1 +1 @@
1
- {"version":3,"file":"rpc-client.d.ts","sourceRoot":"","sources":["../../src/shared/rpc-client.ts"],"names":[],"mappings":"AAYA,MAAM,WAAW,iBAAiB;IAChC,MAAM,CAAC,EAAE,WAAW,CAAC;IACrB;;;;;;;;;OASG;IACH,MAAM,CAAC,EAAE,CAAC,MAAM,EAAE,OAAO,KAAK,OAAO,CAAC;CACvC;AA4BD,qBAAa,YAAY;IACvB,OAAO,CAAC,IAAI,CAAuB;IACnC,OAAO,CAAC,KAAK,CAAuB;IACpC,OAAO,CAAC,QAAQ,CAAS;IACzB,OAAO,CAAC,cAAc,CAAS;IAC/B,OAAO,CAAC,iBAAiB,CAA6B;gBAE1C,UAAU,EAAE,MAAM,EAAE,SAAS,EAAE,MAAM;IAKjD,iFAAiF;IAC3E,IAAI,CAAC,CAAC,GAAG,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,EACpC,MAAM,EAAE,MAAM,EACd,MAAM,GAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAM,EACpC,OAAO,GAAE,iBAAsB,GAC9B,OAAO,CAAC,CAAC,CAAC;YA8CC,OAAO;IAsBrB;;;;OAIG;IACH,OAAO,CAAC,oBAAoB;IAM5B,4CAA4C;IACtC,WAAW,IAAI,OAAO,CAAC,OAAO,CAAC;IASrC;;;;OAIG;YACW,gBAAgB;IAwB9B,OAAO,CAAC,gBAAgB;IAwDxB,8EAA8E;IAC9E,OAAO,CAAC,mBAAmB;IAQ3B,OAAO,CAAC,aAAa;IASrB,OAAO,CAAC,cAAc;IAKtB,OAAO,CAAC,gBAAgB;IAKxB,OAAO,CAAC,iBAAiB;YAsBX,WAAW;YAkCX,gBAAgB;IAmB9B,KAAK,IAAI,IAAI;CAKd"}
1
+ {"version":3,"file":"rpc-client.d.ts","sourceRoot":"","sources":["../../src/shared/rpc-client.ts"],"names":[],"mappings":"AAYA,MAAM,WAAW,iBAAiB;IAChC,MAAM,CAAC,EAAE,WAAW,CAAC;IACrB;;;;;;;;;OASG;IACH,MAAM,CAAC,EAAE,CAAC,MAAM,EAAE,OAAO,KAAK,OAAO,CAAC;CACvC;AA4BD,qBAAa,YAAY;IACvB,OAAO,CAAC,IAAI,CAAuB;IACnC,OAAO,CAAC,KAAK,CAAuB;IACpC,OAAO,CAAC,QAAQ,CAAS;IACzB,OAAO,CAAC,cAAc,CAAS;IAC/B,OAAO,CAAC,iBAAiB,CAA6B;gBAE1C,UAAU,EAAE,MAAM,EAAE,SAAS,EAAE,MAAM;IAKjD,iFAAiF;IAC3E,IAAI,CAAC,CAAC,GAAG,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,EACpC,MAAM,EAAE,MAAM,EACd,MAAM,GAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAM,EACpC,OAAO,GAAE,iBAAsB,GAC9B,OAAO,CAAC,CAAC,CAAC;YA8CC,OAAO;IAsBrB;;;;OAIG;IACH,OAAO,CAAC,oBAAoB;IAM5B,4CAA4C;IACtC,WAAW,IAAI,OAAO,CAAC,OAAO,CAAC;IASrC;;;;OAIG;YACW,gBAAgB;IAwB9B,OAAO,CAAC,gBAAgB;IAwDxB,8EAA8E;IAC9E,OAAO,CAAC,mBAAmB;IAQ3B,OAAO,CAAC,aAAa;IASrB,OAAO,CAAC,cAAc;IAKtB,OAAO,CAAC,gBAAgB;IAKxB,OAAO,CAAC,iBAAiB;YA6BX,WAAW;YAkCX,gBAAgB;IAmB9B,KAAK,IAAI,IAAI;CAKd"}
@@ -6,6 +6,7 @@ export declare class AftRpcServer {
6
6
  private handlers;
7
7
  private portFilePath;
8
8
  private portsDir;
9
+ private heartbeatTimer;
9
10
  /** Unique per-instance ID — distinguishes our entry from duplicate plugin loads. */
10
11
  private instanceId;
11
12
  constructor(storageDir: string, directory: string);
@@ -20,6 +21,8 @@ export declare class AftRpcServer {
20
21
  * Never touches our own freshly written file.
21
22
  */
22
23
  private sweepDeadPortFiles;
24
+ /** Rewrite the port file if it disappeared (wrongful deletion recovery). */
25
+ private ensurePortFile;
23
26
  /** Stop the server and clean up port file. */
24
27
  stop(): void;
25
28
  private dispatch;
@@ -1 +1 @@
1
- {"version":3,"file":"rpc-server.d.ts","sourceRoot":"","sources":["../../src/shared/rpc-server.ts"],"names":[],"mappings":"AAcA,KAAK,UAAU,GAAG,CAAC,MAAM,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,KAAK,OAAO,CAAC,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC,CAAC;AAExF,qBAAa,YAAY;IACvB,OAAO,CAAC,MAAM,CAAuB;IACrC,OAAO,CAAC,IAAI,CAAK;IACjB,OAAO,CAAC,KAAK,CAAuB;IACpC,OAAO,CAAC,QAAQ,CAAiC;IACjD,OAAO,CAAC,YAAY,CAAS;IAC7B,OAAO,CAAC,QAAQ,CAAS;IACzB,oFAAoF;IACpF,OAAO,CAAC,UAAU,CAAS;gBAEf,UAAU,EAAE,MAAM,EAAE,SAAS,EAAE,MAAM;IAMjD,sCAAsC;IACtC,MAAM,CAAC,MAAM,EAAE,MAAM,EAAE,OAAO,EAAE,UAAU,GAAG,IAAI;IAIjD,6DAA6D;IACvD,KAAK,IAAI,OAAO,CAAC,MAAM,CAAC;IA0D9B;;;;;OAKG;IACH,OAAO,CAAC,kBAAkB;IA6B1B,8CAA8C;IAC9C,IAAI,IAAI,IAAI;IAaZ,OAAO,CAAC,QAAQ;CAoEjB"}
1
+ {"version":3,"file":"rpc-server.d.ts","sourceRoot":"","sources":["../../src/shared/rpc-server.ts"],"names":[],"mappings":"AAeA,KAAK,UAAU,GAAG,CAAC,MAAM,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,KAAK,OAAO,CAAC,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC,CAAC;AAIxF,qBAAa,YAAY;IACvB,OAAO,CAAC,MAAM,CAAuB;IACrC,OAAO,CAAC,IAAI,CAAK;IACjB,OAAO,CAAC,KAAK,CAAuB;IACpC,OAAO,CAAC,QAAQ,CAAiC;IACjD,OAAO,CAAC,YAAY,CAAS;IAC7B,OAAO,CAAC,QAAQ,CAAS;IACzB,OAAO,CAAC,cAAc,CAA+C;IACrE,oFAAoF;IACpF,OAAO,CAAC,UAAU,CAAS;gBAEf,UAAU,EAAE,MAAM,EAAE,SAAS,EAAE,MAAM;IAMjD,sCAAsC;IACtC,MAAM,CAAC,MAAM,EAAE,MAAM,EAAE,OAAO,EAAE,UAAU,GAAG,IAAI;IAIjD,6DAA6D;IACvD,KAAK,IAAI,OAAO,CAAC,MAAM,CAAC;IAkE9B;;;;;OAKG;IACH,OAAO,CAAC,kBAAkB;IA6B1B,4EAA4E;IAC5E,OAAO,CAAC,cAAc;IAsBtB,8CAA8C;IAC9C,IAAI,IAAI,IAAI;IAiBZ,OAAO,CAAC,QAAQ;CAoEjB"}
@@ -1 +1 @@
1
- {"version":3,"file":"status.d.ts","sourceRoot":"","sources":["../../src/shared/status.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"status.d.ts","sourceRoot":"","sources":["../../src/shared/status.ts"],"names":[],"mappings":"AAAA,MAAM,WAAW,0BAA0B;IACzC,MAAM,EAAE,MAAM,CAAC;IACf,eAAe,EAAE,MAAM,CAAC;IACxB,iBAAiB,EAAE,MAAM,CAAC;IAC1B,cAAc,EAAE,MAAM,CAAC;CACxB;AAED,MAAM,WAAW,iBAAiB;IAChC,OAAO,EAAE,0BAA0B,CAAC;IACpC,OAAO,EAAE,0BAA0B,CAAC;CACrC;AAED;;;;;;;GAOG;AACH,MAAM,WAAW,SAAS;IACxB,MAAM,EAAE,MAAM,CAAC;IACf,QAAQ,EAAE,MAAM,CAAC;IACjB,SAAS,EAAE,MAAM,CAAC;IAClB,cAAc,EAAE,MAAM,CAAC;IACvB,UAAU,EAAE,MAAM,CAAC;IACnB,KAAK,EAAE,MAAM,CAAC;IACd,WAAW,EAAE,OAAO,CAAC;CACtB;AAED,MAAM,WAAW,iBAAiB;IAChC,OAAO,EAAE,MAAM,CAAC;IAChB,YAAY,EAAE,MAAM,GAAG,IAAI,CAAC;IAC5B,cAAc,EAAE,MAAM,GAAG,IAAI,CAAC;IAC9B,UAAU,EAAE,MAAM,CAAC;IACnB;;;;;;;OAOG;IACH,QAAQ,EAAE,OAAO,CAAC;IAClB,+EAA+E;IAC/E,gBAAgB,EAAE,MAAM,EAAE,CAAC;IAC3B,QAAQ,EAAE;QACR,cAAc,EAAE,OAAO,CAAC;QACxB,gBAAgB,EAAE,MAAM,CAAC;QACzB,wBAAwB,EAAE,OAAO,CAAC;QAClC,YAAY,EAAE,OAAO,CAAC;QACtB,eAAe,EAAE,OAAO,CAAC;KAC1B,CAAC;IACF,YAAY,EAAE;QACZ,MAAM,EAAE,MAAM,CAAC;QACf,KAAK,EAAE,MAAM,GAAG,IAAI,CAAC;QACrB,QAAQ,EAAE,MAAM,GAAG,IAAI,CAAC;KACzB,CAAC;IACF,cAAc,EAAE;QACd,MAAM,EAAE,MAAM,CAAC;QACf,OAAO,CAAC,EAAE,MAAM,GAAG,IAAI,CAAC;QACxB,KAAK,CAAC,EAAE,MAAM,GAAG,IAAI,CAAC;QACtB,KAAK,CAAC,EAAE,MAAM,GAAG,IAAI,CAAC;QACtB,KAAK,CAAC,EAAE,MAAM,GAAG,IAAI,CAAC;QACtB,YAAY,CAAC,EAAE,MAAM,GAAG,IAAI,CAAC;QAC7B,aAAa,CAAC,EAAE,MAAM,GAAG,IAAI,CAAC;QAC9B,gBAAgB,EAAE,MAAM,CAAC;QACzB,OAAO,EAAE,MAAM,GAAG,IAAI,CAAC;QACvB,SAAS,EAAE,MAAM,GAAG,IAAI,CAAC;QACzB,KAAK,CAAC,EAAE,MAAM,GAAG,IAAI,CAAC;KACvB,CAAC;IACF,IAAI,EAAE;QACJ,WAAW,EAAE,MAAM,GAAG,IAAI,CAAC;QAC3B,kBAAkB,EAAE,MAAM,CAAC;QAC3B,mBAAmB,EAAE,MAAM,CAAC;KAC7B,CAAC;IACF,WAAW,EAAE,MAAM,CAAC;IACpB,YAAY,EAAE;QACZ,aAAa,EAAE,MAAM,CAAC;QACtB,YAAY,EAAE,MAAM,CAAC;KACtB,CAAC;IACF,WAAW,EAAE,MAAM,GAAG,IAAI,CAAC;IAC3B,iEAAiE;IACjE,iBAAiB,EAAE,MAAM,CAAC;IAC1B,4DAA4D;IAC5D,OAAO,EAAE;QACP,EAAE,EAAE,MAAM,CAAC;QACX,aAAa,EAAE,MAAM,CAAC;QACtB,WAAW,EAAE,MAAM,CAAC;KACrB,CAAC;IACF,wEAAwE;IACxE,WAAW,CAAC,EAAE,iBAAiB,CAAC;IAChC;;;;OAIG;IACH,UAAU,CAAC,EAAE,SAAS,CAAC;IACvB;;;;;;OAMG;IACH,OAAO,EAAE,MAAM,CAAC;CACjB;AAsED,wBAAgB,yBAAyB,CAAC,MAAM,EAAE,MAAM,EAAE,KAAK,CAAC,EAAE,MAAM,GAAG,IAAI,GAAG,MAAM,CAKvF;AAED,wBAAgB,wBAAwB,CAAC,eAAe,EAAE,MAAM,GAAG,MAAM,GAAG,IAAI,CAI/E;AAED,wBAAgB,WAAW,CAAC,KAAK,EAAE,MAAM,GAAG,MAAM,CAajD;AAED,wBAAgB,eAAe,CAAC,QAAQ,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,GAAG,iBAAiB,CAqEpF;AAED;;;;;;GAMG;AACH,wBAAgB,yBAAyB,CAAC,MAAM,EAAE,iBAAiB,GAAG,MAAM,CAyF3E;AAED,wBAAgB,oBAAoB,CAAC,MAAM,EAAE,iBAAiB,GAAG,MAAM,CA4FtE"}
@@ -16,6 +16,23 @@
16
16
  */
17
17
  type Cleanup = () => Promise<void> | void;
18
18
  export declare function runCleanups(reason: string): Promise<void>;
19
+ /** Conventional exit codes for fatal signals (128 + signal number). */
20
+ export declare const SIGNAL_EXIT_CODES: {
21
+ readonly SIGINT: 130;
22
+ readonly SIGTERM: 143;
23
+ readonly SIGHUP: 129;
24
+ };
25
+ /**
26
+ * Decide whether AFT's handler must terminate the process after cleanup.
27
+ *
28
+ * Registering ANY listener for SIGINT/SIGTERM/SIGHUP disables Node/Bun's
29
+ * default terminate-on-signal behavior. If AFT's listener is the only one,
30
+ * the default was suppressed solely by us, so we must exit or the host hangs
31
+ * forever (OpenCode serve + Desktop /event SSE hung on Ctrl-C until SIGKILL).
32
+ * If the host or another plugin also listens, terminating is THEIR call —
33
+ * forcing an exit here would race a host's graceful shutdown.
34
+ */
35
+ export declare function shouldForceExit(otherListenerCount: number): boolean;
19
36
  /**
20
37
  * Register a shutdown cleanup. Call from plugin initialization; returned
21
38
  * function unregisters (use in `dispose` so plugin reloads don't leak).
@@ -1 +1 @@
1
- {"version":3,"file":"shutdown-hooks.d.ts","sourceRoot":"","sources":["../src/shutdown-hooks.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;GAeG;AAIH,KAAK,OAAO,GAAG,MAAM,OAAO,CAAC,IAAI,CAAC,GAAG,IAAI,CAAC;AAoB1C,wBAAsB,WAAW,CAAC,MAAM,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC,CAqB/D;AA0BD;;;GAGG;AACH,wBAAgB,uBAAuB,CAAC,EAAE,EAAE,OAAO,GAAG,MAAM,IAAI,CAO/D"}
1
+ {"version":3,"file":"shutdown-hooks.d.ts","sourceRoot":"","sources":["../src/shutdown-hooks.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;GAeG;AAIH,KAAK,OAAO,GAAG,MAAM,OAAO,CAAC,IAAI,CAAC,GAAG,IAAI,CAAC;AAoB1C,wBAAsB,WAAW,CAAC,MAAM,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC,CAqB/D;AAED,uEAAuE;AACvE,eAAO,MAAM,iBAAiB;;;;CAAsD,CAAC;AAKrF;;;;;;;;;GASG;AACH,wBAAgB,eAAe,CAAC,kBAAkB,EAAE,MAAM,GAAG,OAAO,CAEnE;AA2DD;;;GAGG;AACH,wBAAgB,uBAAuB,CAAC,EAAE,EAAE,OAAO,GAAG,MAAM,IAAI,CAO/D"}
@@ -1 +1 @@
1
- {"version":3,"file":"_shared.d.ts","sourceRoot":"","sources":["../../src/tools/_shared.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;GAgBG;AAKH,OAAO,KAAK,EAAE,YAAY,EAAE,oBAAoB,EAAE,MAAM,uBAAuB,CAAC;AAUhF,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,aAAa,CAAC;AAIjD;;;;;;;;;;;;;;;;;;;;;GAqBG;AAEH,eAAO,MAAM,WAAW,GAAI,KAAK,MAAM,EAAE,KAAK,MAAM,KAAG,GACR,CAAC;AAEhD;;;;;;;;;;;;;;GAcG;AACH,wBAAgB,iBAAiB,CAC/B,CAAC,EAAE,OAAO,EACV,SAAS,EAAE,MAAM,EACjB,GAAG,EAAE,MAAM,EACX,GAAG,EAAE,MAAM,GACV,MAAM,GAAG,SAAS,CAWpB;AAED;;;;;;;;;;;GAWG;AACH,wBAAgB,YAAY,CAAC,KAAK,EAAE,OAAO,GAAG,OAAO,CAMpD;AAMD,eAAO,MAAM,yBAAyB,QAAS,CAAC;AAIhD,OAAO,EAAE,+BAA+B,EAAE,iBAAiB,EAAE,MAAM,uBAAuB,CAAC;AA2B3F,8EAA8E;AAC9E,wBAAgB,wBAAwB,CACtC,OAAO,EAAE,MAAM,EACf,QAAQ,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,EACjC,MAAM,GAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAM,GACnC,MAAM,CAsDR;AAsBD;;;;;;GAMG;AACH,MAAM,WAAW,WAAW;IAC1B,wEAAwE;IACxE,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,oDAAoD;IACpD,SAAS,EAAE,MAAM,CAAC;IAClB,6EAA6E;IAC7E,SAAS,CAAC,EAAE,MAAM,CAAC;CACpB;AAkBD;;;;;;;;;;;;;;GAcG;AACH,wBAAgB,cAAc,CAAC,OAAO,EAAE,WAAW,GAAG,MAAM,CAW3D;AAED;;;;;GAKG;AACH,wBAAsB,kBAAkB,CACtC,GAAG,EAAE,aAAa,EAClB,OAAO,EAAE,WAAW,GACnB,OAAO,CAAC,MAAM,CAAC,CAKjB;AAED;;;;;;GAMG;AACH,wBAAgB,WAAW,CAAC,KAAK,EAAE,MAAM,GAAG,MAAM,CAQjD;AAED;;2BAE2B;AAC3B,wBAAgB,0BAA0B,CAAC,WAAW,EAAE,MAAM,EAAE,MAAM,EAAE,MAAM,GAAG,MAAM,CAGtF;AAED,wBAAsB,cAAc,CAClC,GAAG,EAAE,aAAa,EAClB,OAAO,EAAE,WAAW,EACpB,MAAM,EAAE,MAAM,GACb,OAAO,CAAC,MAAM,CAAC,CAEjB;AAED;;;;;;;;;GASG;AACH,wBAAgB,SAAS,CAAC,GAAG,EAAE,aAAa,EAAE,OAAO,EAAE,WAAW,GAAG,YAAY,CAEhF;AAED;;;;;;;;;;;;;;GAcG;AACH,wBAAsB,UAAU,CAC9B,GAAG,EAAE,aAAa,EAClB,OAAO,EAAE,WAAW,EACpB,OAAO,EAAE,MAAM,EACf,MAAM,GAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAM,EACpC,OAAO,CAAC,EAAE,oBAAoB,GAC7B,OAAO,CAAC,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC,CAgClC;AAED;;;;;GAKG;AACH,wBAAsB,cAAc,CAClC,GAAG,EAAE,aAAa,EAClB,OAAO,EAAE,WAAW,EACpB,OAAO,EAAE,MAAM,EACf,MAAM,GAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAM,EACpC,OAAO,CAAC,EAAE,oBAAoB,GAC7B,OAAO,CAAC,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC,CAMlC;AAED;;;;;GAKG;AACH,wBAAgB,+BAA+B,CAAC,GAAG,EAAE,aAAa,EAAE,OAAO,EAAE,WAAW,GAAG,IAAI,CAE9F"}
1
+ {"version":3,"file":"_shared.d.ts","sourceRoot":"","sources":["../../src/tools/_shared.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;GAgBG;AAKH,OAAO,KAAK,EAAE,YAAY,EAAE,oBAAoB,EAAE,MAAM,uBAAuB,CAAC;AAUhF,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,aAAa,CAAC;AAIjD;;;;;;;;;;;;;;;;;;;;;GAqBG;AAEH,eAAO,MAAM,WAAW,GAAI,KAAK,MAAM,EAAE,KAAK,MAAM,KAAG,GACR,CAAC;AAEhD;;;;;;;;;;;;;;GAcG;AACH,wBAAgB,iBAAiB,CAC/B,CAAC,EAAE,OAAO,EACV,SAAS,EAAE,MAAM,EACjB,GAAG,EAAE,MAAM,EACX,GAAG,EAAE,MAAM,GACV,MAAM,GAAG,SAAS,CAepB;AAED;;;;;;;;;;;GAWG;AACH,wBAAgB,YAAY,CAAC,KAAK,EAAE,OAAO,GAAG,OAAO,CAMpD;AAMD,eAAO,MAAM,yBAAyB,QAAS,CAAC;AAIhD,OAAO,EAAE,+BAA+B,EAAE,iBAAiB,EAAE,MAAM,uBAAuB,CAAC;AA2B3F,8EAA8E;AAC9E,wBAAgB,wBAAwB,CACtC,OAAO,EAAE,MAAM,EACf,QAAQ,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,EACjC,MAAM,GAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAM,GACnC,MAAM,CAsDR;AAiCD;;;;;;GAMG;AACH,MAAM,WAAW,WAAW;IAC1B,wEAAwE;IACxE,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,oDAAoD;IACpD,SAAS,EAAE,MAAM,CAAC;IAClB,6EAA6E;IAC7E,SAAS,CAAC,EAAE,MAAM,CAAC;CACpB;AAkBD;;;;;;;;;;;;;;GAcG;AACH,wBAAgB,cAAc,CAAC,OAAO,EAAE,WAAW,GAAG,MAAM,CAW3D;AAED;;;;;GAKG;AACH,wBAAsB,kBAAkB,CACtC,GAAG,EAAE,aAAa,EAClB,OAAO,EAAE,WAAW,GACnB,OAAO,CAAC,MAAM,CAAC,CAKjB;AAED;;;;;;GAMG;AACH,wBAAgB,WAAW,CAAC,KAAK,EAAE,MAAM,GAAG,MAAM,CAQjD;AAED;;2BAE2B;AAC3B,wBAAgB,0BAA0B,CAAC,WAAW,EAAE,MAAM,EAAE,MAAM,EAAE,MAAM,GAAG,MAAM,CAGtF;AAED,wBAAsB,cAAc,CAClC,GAAG,EAAE,aAAa,EAClB,OAAO,EAAE,WAAW,EACpB,MAAM,EAAE,MAAM,GACb,OAAO,CAAC,MAAM,CAAC,CAEjB;AAED;;;;;;;;;GASG;AACH,wBAAgB,SAAS,CAAC,GAAG,EAAE,aAAa,EAAE,OAAO,EAAE,WAAW,GAAG,YAAY,CAEhF;AAED;;;;;;;;;;;;;;GAcG;AACH,wBAAsB,UAAU,CAC9B,GAAG,EAAE,aAAa,EAClB,OAAO,EAAE,WAAW,EACpB,OAAO,EAAE,MAAM,EACf,MAAM,GAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAM,EACpC,OAAO,CAAC,EAAE,oBAAoB,GAC7B,OAAO,CAAC,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC,CAgClC;AAED;;;;;GAKG;AACH,wBAAsB,cAAc,CAClC,GAAG,EAAE,aAAa,EAClB,OAAO,EAAE,WAAW,EACpB,OAAO,EAAE,MAAM,EACf,MAAM,GAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAM,EACpC,OAAO,CAAC,EAAE,oBAAoB,GAC7B,OAAO,CAAC,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC,CAMlC;AAED;;;;;GAKG;AACH,wBAAgB,+BAA+B,CAAC,GAAG,EAAE,aAAa,EAAE,OAAO,EAAE,WAAW,GAAG,IAAI,CAE9F"}
@@ -1 +1 @@
1
- {"version":3,"file":"ast.d.ts","sourceRoot":"","sources":["../../src/tools/ast.ts"],"names":[],"mappings":"AAAA;;;;GAIG;AAMH,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,qBAAqB,CAAC;AAC1D,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,aAAa,CAAC;AA6DjD,wBAAgB,QAAQ,CAAC,GAAG,EAAE,aAAa,GAAG,MAAM,CAAC,MAAM,EAAE,cAAc,CAAC,CAwS3E"}
1
+ {"version":3,"file":"ast.d.ts","sourceRoot":"","sources":["../../src/tools/ast.ts"],"names":[],"mappings":"AAAA;;;;GAIG;AAMH,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,qBAAqB,CAAC;AAC1D,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,aAAa,CAAC;AAqEjD,wBAAgB,QAAQ,CAAC,GAAG,EAAE,aAAa,GAAG,MAAM,CAAC,MAAM,EAAE,cAAc,CAAC,CAwS3E"}
package/dist/tui.js CHANGED
@@ -1,7 +1,7 @@
1
1
  // src/tui/index.tsx
2
2
  import { createMemo as createMemo2, createSignal as createSignal2, onCleanup as onCleanup2 } from "solid-js";
3
3
  // package.json
4
- var version = "0.37.2";
4
+ var version = "0.38.0";
5
5
 
6
6
  // src/shared/rpc-client.ts
7
7
  import { existsSync, readdirSync, readFileSync, unlinkSync } from "node:fs";
@@ -248,6 +248,8 @@ class AftRpcClient {
248
248
  return;
249
249
  }
250
250
  this.stalePortFailures.delete(key);
251
+ if (info.pid === undefined || isPidAlive(info.pid))
252
+ return;
251
253
  try {
252
254
  const current = this.parsePortFile(info.path);
253
255
  if (current?.port === info.port && current.token === info.token) {
@@ -599,7 +601,7 @@ function collapsedHealthLights(statusBar) {
599
601
  if (!statusBar)
600
602
  return null;
601
603
  const diagnostics = statusBar.errors > 0 ? "err" : statusBar.warnings > 0 ? "warn" : "ok";
602
- const code = statusBar.duplicates > 0 ? "warn" : "ok";
604
+ const code = statusBar.dead_code > 0 || statusBar.unused_exports > 0 || statusBar.duplicates > 0 ? "warn" : "ok";
603
605
  const todos = statusBar.todos > 0 ? "warn" : "ok";
604
606
  return { diagnostics, code, todos };
605
607
  }
@@ -1022,6 +1024,18 @@ var SidebarContent = (props) => {
1022
1024
  value: formatCount(statusBar().warnings),
1023
1025
  tone: statusBar().warnings > 0 ? "warn" : "muted"
1024
1026
  }, undefined, false, undefined, this),
1027
+ /* @__PURE__ */ jsxDEV(StatRow, {
1028
+ theme: props.theme,
1029
+ label: "Dead Code",
1030
+ value: formatCount(statusBar().dead_code),
1031
+ tone: "muted"
1032
+ }, undefined, false, undefined, this),
1033
+ /* @__PURE__ */ jsxDEV(StatRow, {
1034
+ theme: props.theme,
1035
+ label: "Unused Exports",
1036
+ value: formatCount(statusBar().unused_exports),
1037
+ tone: "muted"
1038
+ }, undefined, false, undefined, this),
1025
1039
  /* @__PURE__ */ jsxDEV(StatRow, {
1026
1040
  theme: props.theme,
1027
1041
  label: "Duplicates",
@@ -1556,6 +1570,18 @@ var StatusDialog = (props) => {
1556
1570
  value: formatCountShort(status().status_bar.warnings),
1557
1571
  tone: status().status_bar.warnings > 0 ? "warn" : "muted"
1558
1572
  }, undefined, false, undefined, this),
1573
+ /* @__PURE__ */ jsxDEV2(R, {
1574
+ theme: t(),
1575
+ label: "Dead Code",
1576
+ value: formatCountShort(status().status_bar.dead_code),
1577
+ tone: "muted"
1578
+ }, undefined, false, undefined, this),
1579
+ /* @__PURE__ */ jsxDEV2(R, {
1580
+ theme: t(),
1581
+ label: "Unused Exports",
1582
+ value: formatCountShort(status().status_bar.unused_exports),
1583
+ tone: "muted"
1584
+ }, undefined, false, undefined, this),
1559
1585
  /* @__PURE__ */ jsxDEV2(R, {
1560
1586
  theme: t(),
1561
1587
  label: "Duplicates",
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@cortexkit/aft-opencode",
3
- "version": "0.37.2",
3
+ "version": "0.38.0",
4
4
  "type": "module",
5
5
  "description": "OpenCode plugin for Agent File Tools (AFT) — tree-sitter and lsp powered code analysis",
6
6
  "main": "dist/index.js",
@@ -30,19 +30,19 @@
30
30
  },
31
31
  "dependencies": {
32
32
  "@clack/prompts": "^1.2.0",
33
- "@cortexkit/aft-bridge": "0.37.2",
33
+ "@cortexkit/aft-bridge": "0.38.0",
34
34
  "@xterm/headless": "^5.5.0",
35
35
  "comment-json": "^4.6.2",
36
36
  "undici": "^7.25.0",
37
37
  "zod": "^4.1.8"
38
38
  },
39
39
  "optionalDependencies": {
40
- "@cortexkit/aft-darwin-arm64": "0.37.2",
41
- "@cortexkit/aft-darwin-x64": "0.37.2",
42
- "@cortexkit/aft-linux-arm64": "0.37.2",
43
- "@cortexkit/aft-linux-x64": "0.37.2",
44
- "@cortexkit/aft-win32-arm64": "0.37.2",
45
- "@cortexkit/aft-win32-x64": "0.37.2"
40
+ "@cortexkit/aft-darwin-arm64": "0.38.0",
41
+ "@cortexkit/aft-darwin-x64": "0.38.0",
42
+ "@cortexkit/aft-linux-arm64": "0.38.0",
43
+ "@cortexkit/aft-linux-x64": "0.38.0",
44
+ "@cortexkit/aft-win32-arm64": "0.38.0",
45
+ "@cortexkit/aft-win32-x64": "0.38.0"
46
46
  },
47
47
  "devDependencies": {
48
48
  "@opencode-ai/plugin": "^1.15.11",
@@ -281,6 +281,13 @@ export class AftRpcClient {
281
281
  }
282
282
 
283
283
  this.stalePortFailures.delete(key);
284
+ // Deletion requires a provably-dead owner. Health checks time out under
285
+ // host load (blocked event loop during builds/streaming), and unlinking a
286
+ // LIVE server's port file is permanent: the server only wrote it at
287
+ // startup, so the sidebar could never reconnect until host restart
288
+ // (issue #110). Live-pid and pid-less files are skipped this round and
289
+ // retried on the next poll instead.
290
+ if (info.pid === undefined || isPidAlive(info.pid)) return;
284
291
  try {
285
292
  // Do not unlink a replacement written after the failed health checks.
286
293
  const current = this.parsePortFile(info.path);
@@ -1,5 +1,6 @@
1
1
  import { randomBytes } from "node:crypto";
2
2
  import {
3
+ existsSync,
3
4
  mkdirSync,
4
5
  readdirSync,
5
6
  readFileSync,
@@ -14,6 +15,8 @@ import { isPidAlive, parseRpcPortRecord, rpcPortFileDir } from "./rpc-utils";
14
15
 
15
16
  type RpcHandler = (params: Record<string, unknown>) => Promise<Record<string, unknown>>;
16
17
 
18
+ const PORT_FILE_HEARTBEAT_MS = 15_000;
19
+
17
20
  export class AftRpcServer {
18
21
  private server: Server | null = null;
19
22
  private port = 0;
@@ -21,6 +24,7 @@ export class AftRpcServer {
21
24
  private handlers = new Map<string, RpcHandler>();
22
25
  private portFilePath: string;
23
26
  private portsDir: string;
27
+ private heartbeatTimer: ReturnType<typeof setInterval> | null = null;
24
28
  /** Unique per-instance ID — distinguishes our entry from duplicate plugin loads. */
25
29
  private instanceId: string;
26
30
 
@@ -75,6 +79,14 @@ export class AftRpcServer {
75
79
  );
76
80
  renameSync(tmpPath, this.portFilePath);
77
81
  log(`RPC server listening on 127.0.0.1:${this.port}`);
82
+ // Self-heal: the port file is this server's only discoverability
83
+ // record, and historically it was written exactly once — anything
84
+ // that deleted it (a client misjudging a load-induced health-check
85
+ // timeout as staleness, cache cleanup, manual sweeps) silently
86
+ // orphaned the server until host restart (issue #110: sidebar stuck
87
+ // on the lazy placeholder forever). Recreate it if it goes missing.
88
+ this.heartbeatTimer = setInterval(() => this.ensurePortFile(), PORT_FILE_HEARTBEAT_MS);
89
+ this.heartbeatTimer.unref?.();
78
90
  // Hygiene: sweep dead siblings while we're here. The client only
79
91
  // reclaims dead-pid files on a cold port scan, and it caches the
80
92
  // first warm port afterwards — so crash/restart leftovers piled up
@@ -129,8 +141,35 @@ export class AftRpcServer {
129
141
  }
130
142
  }
131
143
 
144
+ /** Rewrite the port file if it disappeared (wrongful deletion recovery). */
145
+ private ensurePortFile(): void {
146
+ if (!this.server || this.token === null) return;
147
+ try {
148
+ if (existsSync(this.portFilePath)) return;
149
+ const tmpPath = `${this.portFilePath}.tmp`;
150
+ writeFileSync(
151
+ tmpPath,
152
+ JSON.stringify({
153
+ port: this.port,
154
+ token: this.token,
155
+ pid: process.pid,
156
+ started_at: Date.now(),
157
+ }),
158
+ { encoding: "utf-8", mode: 0o600 },
159
+ );
160
+ renameSync(tmpPath, this.portFilePath);
161
+ log(`RPC port file was missing; rewrote ${this.portFilePath}`);
162
+ } catch {
163
+ // best-effort; retried on the next heartbeat
164
+ }
165
+ }
166
+
132
167
  /** Stop the server and clean up port file. */
133
168
  stop(): void {
169
+ if (this.heartbeatTimer) {
170
+ clearInterval(this.heartbeatTimer);
171
+ this.heartbeatTimer = null;
172
+ }
134
173
  if (this.server) {
135
174
  this.server.close();
136
175
  this.server = null;
@@ -337,9 +337,8 @@ export function formatStatusDialogMessage(status: AftStatusSnapshot): string {
337
337
  `Code Health${sb.tier2_stale ? " (~ stale)" : ""}`,
338
338
  `- errors: ${formatCount(sb.errors)}`,
339
339
  `- warnings: ${formatCount(sb.warnings)}`,
340
- // dead code / unused exports hidden until oxc resolver lands (restore both)
341
- // `- dead code: ${formatCount(sb.dead_code)}`,
342
- // `- unused exports: ${formatCount(sb.unused_exports)}`,
340
+ `- dead code: ${formatCount(sb.dead_code)}`,
341
+ `- unused exports: ${formatCount(sb.unused_exports)}`,
343
342
  `- duplicates: ${formatCount(sb.duplicates)}`,
344
343
  `- todos: ${formatCount(sb.todos)}`,
345
344
  );
@@ -447,9 +446,8 @@ export function formatStatusMarkdown(status: AftStatusSnapshot): string {
447
446
  `### Code Health${sb.tier2_stale ? " (~ stale)" : ""}`,
448
447
  `- **Errors:** ${formatCount(sb.errors)}`,
449
448
  `- **Warnings:** ${formatCount(sb.warnings)}`,
450
- // dead code / unused exports hidden until oxc resolver lands (restore both)
451
- // `- **Dead code:** ${formatCount(sb.dead_code)}`,
452
- // `- **Unused exports:** ${formatCount(sb.unused_exports)}`,
449
+ `- **Dead code:** ${formatCount(sb.dead_code)}`,
450
+ `- **Unused exports:** ${formatCount(sb.unused_exports)}`,
453
451
  `- **Duplicates:** ${formatCount(sb.duplicates)}`,
454
452
  `- **TODOs:** ${formatCount(sb.todos)}`,
455
453
  );
package/src/tui/index.tsx CHANGED
@@ -507,9 +507,7 @@ const StatusDialog = (props: StatusDialogProps) => {
507
507
  value={formatCountShort(status()!.status_bar!.warnings)}
508
508
  tone={status()!.status_bar!.warnings > 0 ? "warn" : "muted"}
509
509
  />
510
- {/* Dead Code / Unused Exports hidden until the oxc resolver lands
511
- (current scanner over-reports on barrel re-exports). Restore both. */}
512
- {/* <R
510
+ <R
513
511
  theme={t()}
514
512
  label="Dead Code"
515
513
  value={formatCountShort(status()!.status_bar!.dead_code)}
@@ -520,7 +518,7 @@ const StatusDialog = (props: StatusDialogProps) => {
520
518
  label="Unused Exports"
521
519
  value={formatCountShort(status()!.status_bar!.unused_exports)}
522
520
  tone="muted"
523
- /> */}
521
+ />
524
522
  <R
525
523
  theme={t()}
526
524
  label="Duplicates"
@@ -185,11 +185,8 @@ export type HealthLightTone = "ok" | "warn" | "err";
185
185
  export interface HealthLights {
186
186
  // Diagnostics: red if any errors, yellow if any warnings, else green.
187
187
  diagnostics: HealthLightTone;
188
- // Code cruft: yellow if there is any duplicate, green when zero. Never red —
189
- // cruft is not a build failure. NOTE: dead_code / unused_exports are
190
- // temporarily excluded from this signal (and the expanded rows below) until
191
- // the oxc-based resolver lands and makes those counts trustworthy on real
192
- // TS/JS codebases. Restore them here when that engine ships.
188
+ // Code cruft: yellow if there is any dead code, unused export, or duplicate,
189
+ // green when zero. Never red — cruft is not a build failure.
193
190
  code: HealthLightTone;
194
191
  // TODOs: yellow if any, else green.
195
192
  todos: HealthLightTone;
@@ -202,8 +199,9 @@ export function collapsedHealthLights(statusBar: StatusBar | undefined): HealthL
202
199
  const diagnostics: HealthLightTone =
203
200
  statusBar.errors > 0 ? "err" : statusBar.warnings > 0 ? "warn" : "ok";
204
201
  const code: HealthLightTone =
205
- // statusBar.dead_code > 0 || statusBar.unused_exports > 0 || // restore with oxc engine
206
- statusBar.duplicates > 0 ? "warn" : "ok";
202
+ statusBar.dead_code > 0 || statusBar.unused_exports > 0 || statusBar.duplicates > 0
203
+ ? "warn"
204
+ : "ok";
207
205
  const todos: HealthLightTone = statusBar.todos > 0 ? "warn" : "ok";
208
206
  return { diagnostics, code, todos };
209
207
  }
@@ -744,11 +742,7 @@ const SidebarContent = (props: {
744
742
  value={formatCount(statusBar()!.warnings)}
745
743
  tone={statusBar()!.warnings > 0 ? "warn" : "muted"}
746
744
  />
747
- {/* Dead Code / Unused Exports temporarily hidden until the
748
- oxc-based resolver lands and makes these trustworthy on real
749
- TS/JS codebases (current tree-sitter scanner over-reports via
750
- barrel re-export gaps). Restore both rows when that ships. */}
751
- {/* <StatRow
745
+ <StatRow
752
746
  theme={props.theme}
753
747
  label="Dead Code"
754
748
  value={formatCount(statusBar()!.dead_code)}
@@ -759,7 +753,7 @@ const SidebarContent = (props: {
759
753
  label="Unused Exports"
760
754
  value={formatCount(statusBar()!.unused_exports)}
761
755
  tone="muted"
762
- /> */}
756
+ />
763
757
  <StatRow
764
758
  theme={props.theme}
765
759
  label="Duplicates"