@adhdev/daemon-core 1.0.18-rc.2 → 1.0.18-rc.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.mjs CHANGED
@@ -437,10 +437,10 @@ function readInjected(value) {
437
437
  }
438
438
  function getDaemonBuildInfo() {
439
439
  if (cached) return cached;
440
- const commit = readInjected(true ? "dcf5798b77def59c325d533c744c7d57b4fc8eac" : void 0) ?? "unknown";
441
- const commitShort = readInjected(true ? "dcf5798b" : void 0) ?? (commit !== "unknown" ? commit.slice(0, 7) : "unknown");
442
- const version = readInjected(true ? "1.0.18-rc.2" : void 0) ?? readInjected(typeof process !== "undefined" ? process.env?.ADHDEV_PKG_VERSION : void 0) ?? "unknown";
443
- const builtAt = readInjected(true ? "2026-07-21T07:45:31.375Z" : void 0);
440
+ const commit = readInjected(true ? "96f3f1365be0d875f5089fc7ce7e4e254ca8e08d" : void 0) ?? "unknown";
441
+ const commitShort = readInjected(true ? "96f3f136" : void 0) ?? (commit !== "unknown" ? commit.slice(0, 7) : "unknown");
442
+ const version = readInjected(true ? "1.0.18-rc.3" : void 0) ?? readInjected(typeof process !== "undefined" ? process.env?.ADHDEV_PKG_VERSION : void 0) ?? "unknown";
443
+ const builtAt = readInjected(true ? "2026-07-21T13:00:17.395Z" : void 0);
444
444
  cached = builtAt ? { commit, commitShort, version, builtAt } : { commit, commitShort, version };
445
445
  return cached;
446
446
  }
@@ -25780,6 +25780,22 @@ var init_cli_state_engine = __esm({
25780
25780
  // ── Approval ─────────────────────────────────────
25781
25781
  lastApprovalResolvedAt = 0;
25782
25782
  lastResolvedModalMessage = "";
25783
+ /**
25784
+ * Normalized approval-context signature captured at resolve time (see
25785
+ * `computeApprovalContentSignature`) — the screen text with blank-line
25786
+ * padding and any manifest-declared chrome (transcriptPty.chromePatterns,
25787
+ * spinner.patterns) stripped out. Used by applyWaitingApproval's
25788
+ * isStaleResolvedRepaint check to tell "the same already-answered modal,
25789
+ * re-parsed from a screen that has not meaningfully changed" apart from
25790
+ * "a genuinely new approval" — content-based, not time-based, because
25791
+ * ordinary TUI chrome (status bar, context meter, blank-line repaint)
25792
+ * keeps producing fresh PTY bytes on an otherwise-unchanged screen and
25793
+ * defeats any output-timestamp discriminator within a few hundred ms.
25794
+ * Cleared whenever a turn starts or the session tears down (see
25795
+ * onTurnStarted / resetActiveTurnState / onPtyExit) so a next-turn
25796
+ * approval is never compared against stale prior-turn state.
25797
+ */
25798
+ lastApprovalResolvedContentSignature = "";
25783
25799
  /**
25784
25800
  * Monotonic counter bumped every time the FSM *enters* waiting_approval
25785
25801
  * with a freshly captured modal (see `applyWaitingApproval`). It is the
@@ -25899,11 +25915,17 @@ var init_cli_state_engine = __esm({
25899
25915
  this.currentTurnTaskId = typeof turnScope.taskId === "string" && turnScope.taskId.trim() ? turnScope.taskId : null;
25900
25916
  this.currentTurnStartedAt = Date.now();
25901
25917
  this.responseEpoch += 1;
25918
+ this.clearApprovalResolutionMemory();
25919
+ if (this.provider.transcriptAuthority === "provider" && this.currentStatus !== "waiting_approval") {
25920
+ this.setStatus("generating", "turn_started");
25921
+ this.callbacks.onStatusChange();
25922
+ }
25902
25923
  }
25903
25924
  /** Called when PTY exits */
25904
25925
  onPtyExit() {
25905
25926
  this.clearAllTimers();
25906
25927
  this.setStatus("stopped", "pty_exit");
25928
+ this.clearApprovalResolutionMemory();
25907
25929
  }
25908
25930
  /** Called when adapter starts up successfully */
25909
25931
  onSpawnReady() {
@@ -25945,6 +25967,7 @@ var init_cli_state_engine = __esm({
25945
25967
  this.activeModal = null;
25946
25968
  this.lastApprovalResolvedAt = Date.now();
25947
25969
  this.lastResolvedModalMessage = currentModalMessage;
25970
+ this.lastApprovalResolvedContentSignature = this.computeApprovalContentSignature(snap);
25948
25971
  this.lastResolvedEntrySeq = this.approvalEntrySeq;
25949
25972
  this.responseSettleIgnoreUntil = Date.now() + this.timeouts.outputSettle + 400;
25950
25973
  if (this.approvalExitTimeout) {
@@ -26067,6 +26090,7 @@ var init_cli_state_engine = __esm({
26067
26090
  this.pendingScriptStatusSince = 0;
26068
26091
  this.approvalResumeDeferSince = 0;
26069
26092
  this.approvalResumeDeferEpoch = -1;
26093
+ this.clearApprovalResolutionMemory();
26070
26094
  }
26071
26095
  clearIdleFinishCandidate(reason) {
26072
26096
  if (!this.idleFinishCandidate) return;
@@ -26225,7 +26249,15 @@ var init_cli_state_engine = __esm({
26225
26249
  }
26226
26250
  if (!status) return;
26227
26251
  const prevStatus = this.currentStatus;
26228
- const ctx = { now, modal, status, parsedMessages, lastParsedAssistant, parsedStatus: parsedStatus || null, prevStatus };
26252
+ const ctx = {
26253
+ now,
26254
+ modal,
26255
+ status,
26256
+ parsedMessages,
26257
+ lastParsedAssistant,
26258
+ parsedStatus: parsedStatus || null,
26259
+ prevStatus
26260
+ };
26229
26261
  if (!this.applyPendingScriptStatusDebounce(ctx)) return;
26230
26262
  const recentInteractiveActivity = this.hasRecentInteractiveActivity(snap, now);
26231
26263
  LOG.debug(
@@ -26261,7 +26293,7 @@ var init_cli_state_engine = __esm({
26261
26293
  return;
26262
26294
  }
26263
26295
  if (status === "waiting_approval") {
26264
- this.applyWaitingApproval(ctx);
26296
+ this.applyWaitingApproval(ctx, snap);
26265
26297
  return;
26266
26298
  }
26267
26299
  if (status === "generating") {
@@ -26321,7 +26353,7 @@ var init_cli_state_engine = __esm({
26321
26353
  }, this.timeouts.generatingIdle);
26322
26354
  this.callbacks.onStatusChange();
26323
26355
  }
26324
- applyWaitingApproval(ctx) {
26356
+ applyWaitingApproval(ctx, snap) {
26325
26357
  const { modal } = ctx;
26326
26358
  this.clearIdleFinishCandidate("waiting_approval");
26327
26359
  const inCooldown = this.lastApprovalResolvedAt && Date.now() - this.lastApprovalResolvedAt < this.timeouts.approvalCooldown;
@@ -26368,6 +26400,11 @@ var init_cli_state_engine = __esm({
26368
26400
  }
26369
26401
  return;
26370
26402
  }
26403
+ const isStaleResolvedRepaint = !this.activeModal && this.isStaleResolvedApproval(modal, snap);
26404
+ if (isStaleResolvedRepaint) {
26405
+ LOG.debug("CLI", `[${this.provider.type}] ignoring stale re-parsed approval matching the just-resolved modal (approval-context signature unchanged)`);
26406
+ return;
26407
+ }
26371
26408
  this.modalLostAt = 0;
26372
26409
  this.isWaitingForResponse = true;
26373
26410
  this.setStatus("waiting_approval", "script_detect");
@@ -26653,6 +26690,86 @@ var init_cli_state_engine = __esm({
26653
26690
  this.recordTrace("idle_finish_cancelled", { trigger: reason });
26654
26691
  }
26655
26692
  // ─── Helpers ────────────────────────────────────────────────────────────
26693
+ /**
26694
+ * Derive a stable approval-context signature from the current screen,
26695
+ * with blank-line padding and any manifest-declared chrome stripped out.
26696
+ *
26697
+ * Generic by design — no kimi- or provider-specific hardcoding: it reads
26698
+ * whatever `tui.transcriptPty.chromePatterns` and `tui.spinner.patterns`
26699
+ * the ACTIVE provider's own manifest already declares (present on any
26700
+ * declarative-TUI provider; simply absent/empty for scripted providers,
26701
+ * in which case this degrades to blank-line stripping only — never worse
26702
+ * than comparing the raw screen). Those pattern lists exist precisely to
26703
+ * name "known volatile repaint noise" (status bar, context meter, spinner
26704
+ * ticks, banners) — reusing them here means the SAME declared knowledge
26705
+ * that governs transcript-chrome stripping also governs staleness
26706
+ * detection, instead of re-encoding provider knowledge into daemon-core.
26707
+ *
26708
+ * Deliberately NOT a hash of the whole screen/buffer: an ordinary TUI
26709
+ * repaints its footer/status/context-meter chrome continuously even while
26710
+ * genuinely idle, so a raw whole-screen or whole-buffer fingerprint (or a
26711
+ * mere "did any bytes arrive" timestamp) changes on every repaint tick
26712
+ * regardless of whether anything approval-relevant actually happened.
26713
+ * Stripping the declared chrome first yields a signature that only
26714
+ * changes when the surrounding conversation/tool-output content itself
26715
+ * changes — exactly the discriminator applyWaitingApproval's
26716
+ * isStaleResolvedRepaint check needs.
26717
+ */
26718
+ computeApprovalContentSignature(snap) {
26719
+ const screenText = snap.screenText || snap.accumulatedBuffer || "";
26720
+ if (!screenText) return "";
26721
+ const tui = this.provider.tui;
26722
+ const patternSpecs = [
26723
+ ...Array.isArray(tui?.transcriptPty?.chromePatterns) ? tui.transcriptPty.chromePatterns : [],
26724
+ ...Array.isArray(tui?.spinner?.patterns) ? tui.spinner.patterns : []
26725
+ ];
26726
+ const chromeRegexes = [];
26727
+ for (const spec of patternSpecs) {
26728
+ if (spec && typeof spec.regex === "string") {
26729
+ try {
26730
+ chromeRegexes.push(new RegExp(spec.regex, typeof spec.flags === "string" ? spec.flags : ""));
26731
+ } catch {
26732
+ }
26733
+ }
26734
+ }
26735
+ const kept = [];
26736
+ for (const rawLine of screenText.split("\n")) {
26737
+ const line = rawLine.trim();
26738
+ if (!line) continue;
26739
+ if (chromeRegexes.some((re) => re.test(line))) continue;
26740
+ kept.push(line);
26741
+ }
26742
+ return kept.join("\n");
26743
+ }
26744
+ /**
26745
+ * True when `modal` is a stale re-parse of an already-resolved approval:
26746
+ * same message text, and the chrome-stripped approval-context signature
26747
+ * of `snap` is unchanged from the signature captured at resolve time.
26748
+ *
26749
+ * Public and reused verbatim by BOTH the settled-eval capture path
26750
+ * (applyWaitingApproval, below) and any OUTSIDE re-parse the adapter
26751
+ * performs independently of the settle loop — e.g. provider-cli-adapter's
26752
+ * getStatus()/getDebugState() startup-gate modal detection, which reads
26753
+ * `recentOutputBuffer` directly while `startupParseGate` is open and can
26754
+ * re-surface the same already-resolved modal before the gate closes.
26755
+ * Centralizing the discriminator here means there is exactly ONE
26756
+ * definition of "stale" for the whole session — no divergent duplicate
26757
+ * heuristic re-implemented per call site.
26758
+ */
26759
+ isStaleResolvedApproval(modal, snap) {
26760
+ if (!modal) return false;
26761
+ const normalizedMessage = typeof modal.message === "string" ? modal.message.trim() : "";
26762
+ if (!normalizedMessage) return false;
26763
+ return this.lastApprovalResolvedAt > 0 && normalizedMessage === this.lastResolvedModalMessage && this.computeApprovalContentSignature(snap) === this.lastApprovalResolvedContentSignature;
26764
+ }
26765
+ /** Clear all resolve-time approval bookkeeping (message, timestamp, content
26766
+ * signature) — called at turn/session boundaries so a next-turn or
26767
+ * next-session approval is never compared against stale prior state. */
26768
+ clearApprovalResolutionMemory() {
26769
+ this.lastApprovalResolvedAt = 0;
26770
+ this.lastResolvedModalMessage = "";
26771
+ this.lastApprovalResolvedContentSignature = "";
26772
+ }
26656
26773
  /**
26657
26774
  * Schedule one more settled evaluation while pinned to `waiting_approval`
26658
26775
  * with no actionable modal. The settled FSM normally only re-runs on new
@@ -27477,8 +27594,8 @@ ${lastSnapshot}`;
27477
27594
  }
27478
27595
  }
27479
27596
  });
27480
- this.ptyProcess.onExit(({ exitCode }) => {
27481
- LOG.info("CLI", `[${this.cliType}] Exit code ${exitCode}`);
27597
+ this.ptyProcess.onExit(({ exitCode, signal }) => {
27598
+ LOG.info("CLI", `[${this.cliType}] Exit code ${exitCode === null || exitCode === void 0 ? "unknown" : exitCode}${signal ? ` (signal ${signal})` : ""}`);
27482
27599
  this.flushPendingOutputParse();
27483
27600
  this.ptyProcess = null;
27484
27601
  this.engine.onPtyExit();
@@ -27784,7 +27901,10 @@ ${lastSnapshot}`;
27784
27901
  // ─── Public API (CliAdapter) ───────────────────
27785
27902
  getStatus(options = {}) {
27786
27903
  const allowParse = options.allowParse !== false;
27787
- const startupModal = allowParse && this.startupParseGate ? this.runParseApproval(this.recentOutputBuffer) : null;
27904
+ let startupModal = allowParse && this.startupParseGate ? this.runParseApproval(this.recentOutputBuffer) : null;
27905
+ if (startupModal && this.engine.isStaleResolvedApproval(startupModal, { screenText: this.terminalScreen.getText(), accumulatedBuffer: this.accumulatedBuffer })) {
27906
+ startupModal = null;
27907
+ }
27788
27908
  const startupDetectedStatus = allowParse && this.startupParseGate && !startupModal ? this.runDetectStatus(this.recentOutputBuffer || this.terminalScreen.getText()) : null;
27789
27909
  if (allowParse && this.engine.currentStatus === "generating" && !this.engine.currentTurnScope && !this.engine.activeModal) {
27790
27910
  const now = Date.now();
@@ -27818,7 +27938,7 @@ ${lastSnapshot}`;
27818
27938
  if (liveDetect === "waiting_approval") {
27819
27939
  const liveModal = this.runParseApproval(this.terminalScreen.getText()) || this.runParseApproval(this.recentOutputBuffer);
27820
27940
  const buttonsOk = liveModal && Array.isArray(liveModal.buttons) && liveModal.buttons.some((b) => typeof b === "string" && b.trim());
27821
- if (liveModal && buttonsOk) {
27941
+ if (liveModal && buttonsOk && !this.engine.isStaleResolvedApproval(liveModal, { screenText: this.terminalScreen.getText(), accumulatedBuffer: this.accumulatedBuffer })) {
27822
27942
  effectiveModal = liveModal;
27823
27943
  if (!this.engine.activeModal) this.engine.activeModal = liveModal;
27824
27944
  }
@@ -28944,7 +29064,10 @@ ${lastSnapshot}`;
28944
29064
  }
28945
29065
  getDebugState() {
28946
29066
  const screenText = sanitizeTerminalText(this.terminalScreen.getText());
28947
- const startupModal = this.startupParseGate ? this.runParseApproval(this.recentOutputBuffer) : null;
29067
+ let startupModal = this.startupParseGate ? this.runParseApproval(this.recentOutputBuffer) : null;
29068
+ if (startupModal && this.engine.isStaleResolvedApproval(startupModal, { screenText: this.terminalScreen.getText(), accumulatedBuffer: this.accumulatedBuffer })) {
29069
+ startupModal = null;
29070
+ }
28948
29071
  const startupDetectedStatus = this.startupParseGate && !startupModal ? this.runDetectStatus(this.recentOutputBuffer || screenText) : null;
28949
29072
  const effectiveReady = this.ready || !!startupModal || startupDetectedStatus === "waiting_approval";
28950
29073
  const parsedDebugState = this.getParsedDebugState();
@@ -42279,20 +42402,202 @@ var notificationHandlers = {
42279
42402
  init_config();
42280
42403
 
42281
42404
  // src/commands/upgrade-helper.ts
42282
- import { execFileSync as execFileSync4 } from "child_process";
42405
+ import { execFileSync as execFileSync5 } from "child_process";
42283
42406
  import { spawn as spawn2 } from "child_process";
42284
42407
  import * as fs15 from "fs";
42285
42408
  import * as os13 from "os";
42286
42409
  import * as path21 from "path";
42287
42410
 
42288
42411
  // src/commands/windows-atomic-upgrade.ts
42289
- import { execFileSync as execFileSync3, spawn, spawnSync } from "child_process";
42412
+ import { execFileSync as execFileSync4, spawn, spawnSync } from "child_process";
42290
42413
  import * as fs14 from "fs";
42291
42414
  import * as http2 from "http";
42292
42415
  import * as path20 from "path";
42416
+
42417
+ // src/commands/process-lifecycle.ts
42418
+ import { execFileSync as execFileSync3 } from "child_process";
42419
+ function defaultExecFileSync() {
42420
+ return execFileSync3;
42421
+ }
42422
+ function getWindowsProcessCommandLine(pid, exec7) {
42423
+ const pidFilter = `ProcessId=${pid}`;
42424
+ try {
42425
+ const psOut = exec7("powershell.exe", [
42426
+ "-NoProfile",
42427
+ "-NonInteractive",
42428
+ "-ExecutionPolicy",
42429
+ "Bypass",
42430
+ "-Command",
42431
+ `(Get-CimInstance Win32_Process -Filter "${pidFilter}").CommandLine`
42432
+ ], { encoding: "utf8", timeout: 5e3, stdio: ["ignore", "pipe", "ignore"], windowsHide: true });
42433
+ const text = String(psOut).trim();
42434
+ if (text) return text;
42435
+ } catch {
42436
+ }
42437
+ try {
42438
+ const wmicOut = exec7("wmic", [
42439
+ "process",
42440
+ "where",
42441
+ pidFilter,
42442
+ "get",
42443
+ "CommandLine"
42444
+ ], { encoding: "utf8", timeout: 3e3, stdio: ["ignore", "pipe", "ignore"], windowsHide: true });
42445
+ const text = String(wmicOut).trim();
42446
+ if (text) return text;
42447
+ } catch {
42448
+ }
42449
+ return null;
42450
+ }
42451
+ function getProcessCommandLine(pid, options = {}) {
42452
+ if (!Number.isFinite(pid) || pid <= 0) return null;
42453
+ const exec7 = options.execFileSync ?? defaultExecFileSync();
42454
+ const platform10 = options.platform ?? process.platform;
42455
+ if (platform10 === "win32") {
42456
+ return getWindowsProcessCommandLine(pid, exec7);
42457
+ }
42458
+ try {
42459
+ const text = String(exec7("ps", ["-o", "command=", "-p", String(pid)], {
42460
+ encoding: "utf8",
42461
+ timeout: 3e3,
42462
+ stdio: ["ignore", "pipe", "ignore"]
42463
+ })).trim();
42464
+ return text || null;
42465
+ } catch {
42466
+ return null;
42467
+ }
42468
+ }
42469
+ function parseNodeScriptPath(commandLine) {
42470
+ if (!commandLine) return null;
42471
+ let rest = commandLine.trim();
42472
+ if (rest.startsWith('"')) {
42473
+ const end = rest.indexOf('"', 1);
42474
+ if (end === -1) return null;
42475
+ rest = rest.slice(end + 1).trim();
42476
+ } else {
42477
+ const idx2 = rest.search(/\s/);
42478
+ if (idx2 === -1) return null;
42479
+ rest = rest.slice(idx2 + 1).trim();
42480
+ }
42481
+ if (!rest) return null;
42482
+ if (rest.startsWith('"')) {
42483
+ const end = rest.indexOf('"', 1);
42484
+ return end === -1 ? rest.slice(1) : rest.slice(1, end);
42485
+ }
42486
+ const idx = rest.search(/\s/);
42487
+ return idx === -1 ? rest : rest.slice(0, idx);
42488
+ }
42489
+ function normalizeWindowsPath(value) {
42490
+ return value.toLowerCase().replace(/\//g, "\\").replace(/\\+$/, "");
42491
+ }
42492
+ function killProcess(pid, options = {}) {
42493
+ if (!Number.isFinite(pid) || pid <= 0) return false;
42494
+ const exec7 = options.execFileSync ?? defaultExecFileSync();
42495
+ const platform10 = options.platform ?? process.platform;
42496
+ try {
42497
+ if (platform10 === "win32") {
42498
+ exec7("taskkill", ["/PID", String(pid), "/T", "/F"], {
42499
+ stdio: "ignore",
42500
+ windowsHide: true
42501
+ });
42502
+ } else {
42503
+ process.kill(pid, "SIGTERM");
42504
+ }
42505
+ return true;
42506
+ } catch {
42507
+ return false;
42508
+ }
42509
+ }
42510
+ async function waitForPidExit(pid, timeoutMs) {
42511
+ const start = Date.now();
42512
+ while (Date.now() - start < timeoutMs) {
42513
+ try {
42514
+ process.kill(pid, 0);
42515
+ await new Promise((resolve27) => setTimeout(resolve27, 250));
42516
+ } catch {
42517
+ return true;
42518
+ }
42519
+ }
42520
+ return false;
42521
+ }
42522
+ function listOwnedNodeProcesses(options) {
42523
+ const platform10 = options.platform ?? process.platform;
42524
+ if (platform10 !== "win32") return [];
42525
+ const exec7 = options.execFileSync ?? defaultExecFileSync();
42526
+ const prefixes = options.prefixes.map((p) => normalizeWindowsPath(p));
42527
+ const exclude = new Set((options.excludePids ?? []).filter((n) => Number.isFinite(n) && n > 0));
42528
+ const markers = (options.markers ?? []).map((m) => m.toLowerCase().replace(/\//g, "\\"));
42529
+ let pids = [];
42530
+ try {
42531
+ const out = String(exec7("powershell.exe", [
42532
+ "-NoProfile",
42533
+ "-NonInteractive",
42534
+ "-ExecutionPolicy",
42535
+ "Bypass",
42536
+ "-Command",
42537
+ "Get-Process node -ErrorAction SilentlyContinue | Select-Object -ExpandProperty Id | ConvertTo-Json -Compress"
42538
+ ], { encoding: "utf8", timeout: 8e3, stdio: ["ignore", "pipe", "ignore"], windowsHide: true })).trim();
42539
+ if (out) {
42540
+ const parsed = JSON.parse(out);
42541
+ pids = Array.isArray(parsed) ? parsed : [parsed];
42542
+ }
42543
+ } catch {
42544
+ return [];
42545
+ }
42546
+ const results = [];
42547
+ for (const pid of pids) {
42548
+ if (!Number.isFinite(pid) || pid <= 0 || exclude.has(pid)) continue;
42549
+ const commandLine = getProcessCommandLine(pid, { platform: platform10, execFileSync: exec7 });
42550
+ if (!commandLine) continue;
42551
+ const lower = commandLine.toLowerCase();
42552
+ const underPrefix = prefixes.some((prefix) => lower.includes(prefix));
42553
+ if (!underPrefix) continue;
42554
+ if (markers.length > 0 && !markers.some((marker) => lower.includes(marker))) continue;
42555
+ results.push({ pid, commandLine });
42556
+ }
42557
+ return results;
42558
+ }
42559
+ async function stopOwnedProcesses(options) {
42560
+ const waitMs = options.waitMs ?? 15e3;
42561
+ const killed = /* @__PURE__ */ new Set();
42562
+ for (const p of options.processes) {
42563
+ if (killProcess(p.pid, options)) killed.add(p.pid);
42564
+ }
42565
+ const survivors = [];
42566
+ for (const p of options.processes) {
42567
+ if (!killed.has(p.pid)) {
42568
+ survivors.push(p);
42569
+ continue;
42570
+ }
42571
+ const exited = await waitForPidExit(p.pid, waitMs);
42572
+ if (!exited) survivors.push(p);
42573
+ }
42574
+ return { stopped: options.processes.length - survivors.length, survivors };
42575
+ }
42576
+ async function stopOwnedProcessesForPrefixes(options) {
42577
+ const processes = listOwnedNodeProcesses(options);
42578
+ if (processes.length === 0) return { stopped: 0, survivors: [] };
42579
+ options.log?.(`Stopping ${processes.length} owned process(es) under prefixes: ${options.prefixes.join(", ")}`);
42580
+ const result = await stopOwnedProcesses({
42581
+ processes,
42582
+ waitMs: options.waitMs,
42583
+ platform: options.platform,
42584
+ execFileSync: options.execFileSync
42585
+ });
42586
+ if (result.survivors.length > 0) {
42587
+ options.log?.(`Could not stop ${result.survivors.length} owned process(es): ${result.survivors.map((s2) => s2.pid).join(", ")}`);
42588
+ }
42589
+ return result;
42590
+ }
42591
+
42592
+ // src/commands/windows-atomic-upgrade.ts
42293
42593
  var POINTER_NAME = ".adhdev-current";
42294
42594
  var STABLE_FILES = [POINTER_NAME, "adhdev.cmd", "adhdev.ps1", "adhdev"];
42295
42595
  var DEFAULT_HEALTH_PORT = 19222;
42596
+ var ADHDEV_OWNED_MARKERS = [
42597
+ "session-host-daemon",
42598
+ "node_modules/adhdev",
42599
+ "node_modules/@adhdev/daemon-standalone"
42600
+ ];
42296
42601
  function normalizeForCompare(value) {
42297
42602
  return path20.resolve(value).replace(/[\\/]+$/, "").toLowerCase();
42298
42603
  }
@@ -42315,7 +42620,7 @@ function resolveWindowsInstallerLayout(options) {
42315
42620
  }
42316
42621
  function nodeMajor(nodeExecutable) {
42317
42622
  try {
42318
- const version = String(execFileSync3(nodeExecutable, ["-p", "process.versions.node"], {
42623
+ const version = String(execFileSync4(nodeExecutable, ["-p", "process.versions.node"], {
42319
42624
  encoding: "utf8",
42320
42625
  timeout: 5e3,
42321
42626
  windowsHide: true,
@@ -42377,7 +42682,7 @@ exit $LASTEXITCODE\r
42377
42682
  }
42378
42683
  }
42379
42684
  function validateStagedCli(portableNode, cliEntry, targetVersion) {
42380
- const output = String(execFileSync3(portableNode, [cliEntry, "--version"], {
42685
+ const output = String(execFileSync4(portableNode, [cliEntry, "--version"], {
42381
42686
  encoding: "utf8",
42382
42687
  timeout: 15e3,
42383
42688
  windowsHide: true,
@@ -42491,6 +42796,19 @@ async function performWindowsAtomicUpgrade(options) {
42491
42796
  pinStagedShims(stagedPrefix, portableNode, stagedCliEntry);
42492
42797
  validateStagedCli(portableNode, stagedCliEntry, targetVersion);
42493
42798
  hooks.log(`Validated staged CLI and portable Node 22 shims in ${stagedPrefix}`);
42799
+ const excludedPids = new Set([process.pid, ...options.excludePids ?? []].filter((n) => Number.isFinite(n) && n > 0));
42800
+ const preStop = await stopOwnedProcessesForPrefixes({
42801
+ prefixes: [layout.activePrefix, layout.stablePrefix],
42802
+ excludePids: Array.from(excludedPids),
42803
+ markers: Array.from(ADHDEV_OWNED_MARKERS),
42804
+ waitMs: 15e3,
42805
+ log: hooks.log
42806
+ });
42807
+ if (preStop.survivors.length > 0) {
42808
+ throw new Error(
42809
+ `Cannot activate ${versionName}: ${preStop.survivors.length} owned process(es) still running under the current prefix`
42810
+ );
42811
+ }
42494
42812
  activated = true;
42495
42813
  publishStableShimsAndPointer(layout, versionName);
42496
42814
  hooks.log(`Atomically activated ${versionName}`);
@@ -42500,7 +42818,7 @@ async function performWindowsAtomicUpgrade(options) {
42500
42818
  throw new Error("replacement daemon did not pass the health/version gate");
42501
42819
  }
42502
42820
  hooks.log(`Replacement daemon pid ${daemonPid} passed health for ${targetVersion}`);
42503
- hooks.cleanup(layout, stagedPrefix);
42821
+ await hooks.cleanup(layout, stagedPrefix);
42504
42822
  return { stagedPrefix, stagedCliEntry, daemonPid };
42505
42823
  } catch (error) {
42506
42824
  if (restarted?.pid) hooks.stopProcess(restarted.pid);
@@ -42518,7 +42836,7 @@ async function performWindowsAtomicUpgrade(options) {
42518
42836
  hooks.log("Failed to restart the previous daemon during rollback");
42519
42837
  }
42520
42838
  try {
42521
- hooks.cleanup(layout, layout.activePrefix);
42839
+ await hooks.cleanup(layout, layout.activePrefix);
42522
42840
  } catch {
42523
42841
  }
42524
42842
  throw error;
@@ -42530,7 +42848,7 @@ function createDefaultWindowsAtomicHooks(options) {
42530
42848
  const env = { ...options.env, ADHDEV_BOOTSTRAP: "1" };
42531
42849
  const pathKey = Object.keys(env).find((key2) => key2.toLowerCase() === "path") || "Path";
42532
42850
  env[pathKey] = `${path20.dirname(portableNode)};${env[pathKey] || ""}`;
42533
- execFileSync3(portableNode, [
42851
+ execFileSync4(portableNode, [
42534
42852
  options.npmCliPath,
42535
42853
  "install",
42536
42854
  "-g",
@@ -42600,15 +42918,23 @@ function createDefaultWindowsAtomicHooks(options) {
42600
42918
  },
42601
42919
  stopProcess: (pid) => {
42602
42920
  try {
42603
- execFileSync3("taskkill", ["/PID", String(pid), "/T", "/F"], { stdio: "ignore", windowsHide: true });
42921
+ execFileSync4("taskkill", ["/PID", String(pid), "/T", "/F"], { stdio: "ignore", windowsHide: true });
42604
42922
  } catch {
42605
42923
  }
42606
42924
  },
42607
- cleanup: (layout, activePrefix) => boundedCleanupInactivePrefixes(layout, activePrefix, options.log),
42925
+ cleanup: async (layout, activePrefix) => cleanupInactivePrefixesWithGuard({
42926
+ layout,
42927
+ activePrefix,
42928
+ excludePids: [process.pid],
42929
+ markers: Array.from(ADHDEV_OWNED_MARKERS),
42930
+ waitMs: 15e3,
42931
+ log: options.log
42932
+ }),
42608
42933
  log: options.log
42609
42934
  };
42610
42935
  }
42611
- function boundedCleanupInactivePrefixes(layout, activePrefix, log) {
42936
+ async function cleanupInactivePrefixesWithGuard(options) {
42937
+ const { layout, activePrefix, log } = options;
42612
42938
  let candidates = [];
42613
42939
  try {
42614
42940
  candidates = fs14.readdirSync(layout.installRoot, { withFileTypes: true }).filter((entry) => entry.isDirectory() && entry.name.startsWith("version-")).map((entry) => path20.join(layout.installRoot, entry.name)).filter((entry) => normalizeForCompare(entry) !== normalizeForCompare(activePrefix)).sort().slice(0, 8);
@@ -42616,19 +42942,41 @@ function boundedCleanupInactivePrefixes(layout, activePrefix, log) {
42616
42942
  return;
42617
42943
  }
42618
42944
  if (candidates.length === 0) return;
42619
- const escaped = candidates.map((candidate) => quotePowerShellLiteral(candidate)).join(",");
42620
- const script = `$ErrorActionPreference='Stop'; @(${escaped}) | ForEach-Object { if (Test-Path -LiteralPath $_) { Remove-Item -LiteralPath $_ -Recurse -Force } }`;
42621
- const encoded = Buffer.from(script, "utf16le").toString("base64");
42622
- const result = spawnSync("powershell.exe", [
42623
- "-NoLogo",
42624
- "-NoProfile",
42625
- "-NonInteractive",
42626
- "-ExecutionPolicy",
42627
- "Bypass",
42628
- "-EncodedCommand",
42629
- encoded
42630
- ], { timeout: 5e3, windowsHide: true, stdio: "ignore" });
42631
- if (result.error || result.status !== 0) log("Bounded inactive-prefix cleanup was incomplete; future updates will retry");
42945
+ for (const candidate of candidates) {
42946
+ const stopResult = await stopOwnedProcessesForPrefixes({
42947
+ prefixes: [candidate],
42948
+ excludePids: options.excludePids,
42949
+ markers: options.markers,
42950
+ waitMs: options.waitMs ?? 15e3,
42951
+ log
42952
+ });
42953
+ if (stopResult.survivors.length > 0) {
42954
+ log?.(`Skipping cleanup of ${candidate}: ${stopResult.survivors.length} owned process(es) could not be stopped`);
42955
+ continue;
42956
+ }
42957
+ removeInactivePrefix(candidate, log);
42958
+ }
42959
+ }
42960
+ function removeInactivePrefix(target, log) {
42961
+ try {
42962
+ const escaped = quotePowerShellLiteral(target);
42963
+ const script = `if (Test-Path -LiteralPath ${escaped}) { Remove-Item -LiteralPath ${escaped} -Recurse -Force }`;
42964
+ const encoded = Buffer.from(script, "utf16le").toString("base64");
42965
+ const result = spawnSync("powershell.exe", [
42966
+ "-NoLogo",
42967
+ "-NoProfile",
42968
+ "-NonInteractive",
42969
+ "-ExecutionPolicy",
42970
+ "Bypass",
42971
+ "-EncodedCommand",
42972
+ encoded
42973
+ ], { timeout: 5e3, windowsHide: true, stdio: "ignore" });
42974
+ if (result.error || result.status !== 0) {
42975
+ log?.(`Failed to remove inactive prefix ${target}: ${result.error?.message || `exit ${result.status}`}`);
42976
+ }
42977
+ } catch (error) {
42978
+ log?.(`Failed to remove inactive prefix ${target}: ${error?.message || String(error)}`);
42979
+ }
42632
42980
  }
42633
42981
 
42634
42982
  // src/commands/upgrade-helper.ts
@@ -42757,7 +43105,7 @@ function getNpmExecOptions(platform10 = process.platform) {
42757
43105
  }
42758
43106
  function execNpmCommandSync(args, options = {}, surface) {
42759
43107
  const execOptions = surface?.execOptions || getNpmExecOptions();
42760
- return execFileSync4(
43108
+ return execFileSync5(
42761
43109
  surface?.npmExecutable || "npm",
42762
43110
  [...surface?.npmArgsPrefix || [], ...args],
42763
43111
  {
@@ -42767,74 +43115,10 @@ function execNpmCommandSync(args, options = {}, surface) {
42767
43115
  }
42768
43116
  );
42769
43117
  }
42770
- function killPid(pid) {
42771
- try {
42772
- if (process.platform === "win32") {
42773
- execFileSync4("taskkill", ["/PID", String(pid), "/T", "/F"], { stdio: "ignore", windowsHide: true });
42774
- } else {
42775
- process.kill(pid, "SIGTERM");
42776
- }
42777
- return true;
42778
- } catch {
42779
- return false;
42780
- }
42781
- }
42782
- function getWindowsProcessCommandLine(pid) {
42783
- const pidFilter = `ProcessId=${pid}`;
42784
- try {
42785
- const psOut = execFileSync4("powershell.exe", [
42786
- "-NoProfile",
42787
- "-NonInteractive",
42788
- "-ExecutionPolicy",
42789
- "Bypass",
42790
- "-Command",
42791
- `(Get-CimInstance Win32_Process -Filter "${pidFilter}").CommandLine`
42792
- ], { encoding: "utf8", timeout: 5e3, stdio: ["ignore", "pipe", "ignore"], windowsHide: true }).trim();
42793
- if (psOut) return psOut;
42794
- } catch {
42795
- }
42796
- try {
42797
- const wmicOut = execFileSync4("wmic", [
42798
- "process",
42799
- "where",
42800
- pidFilter,
42801
- "get",
42802
- "CommandLine"
42803
- ], { encoding: "utf8", timeout: 3e3, stdio: ["ignore", "pipe", "ignore"], windowsHide: true }).trim();
42804
- if (wmicOut) return wmicOut;
42805
- } catch {
42806
- }
42807
- return null;
42808
- }
42809
- function getProcessCommandLine(pid) {
42810
- if (!Number.isFinite(pid) || pid <= 0) return null;
42811
- if (process.platform === "win32") return getWindowsProcessCommandLine(pid);
42812
- try {
42813
- const text = execFileSync4("ps", ["-o", "command=", "-p", String(pid)], {
42814
- encoding: "utf8",
42815
- timeout: 3e3,
42816
- stdio: ["ignore", "pipe", "ignore"]
42817
- }).trim();
42818
- return text || null;
42819
- } catch {
42820
- return null;
42821
- }
42822
- }
42823
43118
  function isManagedSessionHostPid(pid) {
42824
43119
  const commandLine = getProcessCommandLine(pid);
42825
43120
  return !!commandLine && /session-host-daemon/i.test(commandLine);
42826
43121
  }
42827
- async function waitForPidExit(pid, timeoutMs) {
42828
- const start = Date.now();
42829
- while (Date.now() - start < timeoutMs) {
42830
- try {
42831
- process.kill(pid, 0);
42832
- await new Promise((resolve27) => setTimeout(resolve27, 250));
42833
- } catch {
42834
- return;
42835
- }
42836
- }
42837
- }
42838
43122
  async function stopSessionHostProcesses(appName) {
42839
43123
  const pidFile = path21.join(os13.homedir(), ".adhdev", `${appName}-session-host.pid`);
42840
43124
  let killedPid = null;
@@ -42842,7 +43126,7 @@ async function stopSessionHostProcesses(appName) {
42842
43126
  if (fs15.existsSync(pidFile)) {
42843
43127
  const pid = Number.parseInt(fs15.readFileSync(pidFile, "utf8").trim(), 10);
42844
43128
  if (Number.isFinite(pid) && pid !== process.pid && isManagedSessionHostPid(pid)) {
42845
- if (killPid(pid)) killedPid = pid;
43129
+ if (killProcess(pid)) killedPid = pid;
42846
43130
  }
42847
43131
  }
42848
43132
  } catch {
@@ -42878,7 +43162,7 @@ function listForeignNativeAddonHolders(packageRoot) {
42878
43162
  ].join("\n");
42879
43163
  let out = "";
42880
43164
  try {
42881
- out = String(execFileSync4("powershell.exe", [
43165
+ out = String(execFileSync5("powershell.exe", [
42882
43166
  "-NoProfile",
42883
43167
  "-NonInteractive",
42884
43168
  "-ExecutionPolicy",
@@ -42910,7 +43194,7 @@ async function stopForeignNativeAddonHolders(packageRoot, options = {}) {
42910
43194
  appendUpgradeLog(
42911
43195
  `Foreign native-addon holder found: pid ${holder.pid}${holder.commandLine ? ` \u2014 ${holder.commandLine}` : ""}`
42912
43196
  );
42913
- const killed = killPid(holder.pid);
43197
+ const killed = killProcess(holder.pid);
42914
43198
  if (killed) {
42915
43199
  await waitForPidExit(holder.pid, 15e3);
42916
43200
  appendUpgradeLog(`Terminated foreign native-addon holder pid ${holder.pid}`);
@@ -43043,11 +43327,25 @@ async function runDaemonUpgradeHelper(payload) {
43043
43327
  throw new Error(`portable Node.js 22 npm CLI is missing: ${npmCliPath}`);
43044
43328
  }
43045
43329
  appendUpgradeLog(`Installer-managed pointer layout detected; active prefix will remain untouched: ${windowsInstallerLayout.activePrefix}`);
43330
+ const upgradePids = [process.pid, payload.parentPid].filter((n) => Number.isFinite(n) && n > 0);
43331
+ const preStop = await stopOwnedProcessesForPrefixes({
43332
+ prefixes: [windowsInstallerLayout.activePrefix, windowsInstallerLayout.stablePrefix],
43333
+ excludePids: upgradePids,
43334
+ markers: Array.from(ADHDEV_OWNED_MARKERS),
43335
+ waitMs: 15e3,
43336
+ log: appendUpgradeLog
43337
+ });
43338
+ if (preStop.survivors.length > 0) {
43339
+ throw new Error(
43340
+ `Cannot upgrade: owned processes still running under current prefix: ${preStop.survivors.map((s2) => s2.pid).join(", ")}`
43341
+ );
43342
+ }
43046
43343
  await performWindowsAtomicUpgrade({
43047
43344
  layout: windowsInstallerLayout,
43048
43345
  packageName: payload.packageName,
43049
43346
  targetVersion: payload.targetVersion,
43050
43347
  portableNode,
43348
+ excludePids: upgradePids,
43051
43349
  hooks: createDefaultWindowsAtomicHooks({
43052
43350
  packageName: payload.packageName,
43053
43351
  targetVersion: payload.targetVersion,
@@ -43073,7 +43371,7 @@ async function runDaemonUpgradeHelper(payload) {
43073
43371
  let installOutput = "";
43074
43372
  for (let attempt = 1; attempt <= maxInstallAttempts; attempt++) {
43075
43373
  try {
43076
- installOutput = String(execFileSync4(
43374
+ installOutput = String(execFileSync5(
43077
43375
  installCommand.command,
43078
43376
  installCommand.args,
43079
43377
  {
@@ -43716,7 +44014,7 @@ import * as os22 from "os";
43716
44014
  import * as path30 from "path";
43717
44015
  import * as crypto6 from "crypto";
43718
44016
  import { existsSync as existsSync33, mkdirSync as mkdirSync14, writeFileSync as writeFileSync17 } from "fs";
43719
- import { execFileSync as execFileSync5 } from "child_process";
44017
+ import { execFileSync as execFileSync6 } from "child_process";
43720
44018
  import chalk from "chalk";
43721
44019
  init_summary_metadata();
43722
44020
 
@@ -52087,7 +52385,7 @@ function commandExists(command) {
52087
52385
  return existsSync33(expandExecutable(trimmed));
52088
52386
  }
52089
52387
  try {
52090
- execFileSync5(process.platform === "win32" ? "where" : "which", [trimmed], {
52388
+ execFileSync6(process.platform === "win32" ? "where" : "which", [trimmed], {
52091
52389
  stdio: "ignore",
52092
52390
  ...process.platform === "win32" ? { windowsHide: true } : {}
52093
52391
  });
@@ -57998,15 +58296,15 @@ var ideHandlers = {
57998
58296
  stop_ide: async (ctx, args) => {
57999
58297
  const ideType = args?.ideType;
58000
58298
  if (!ideType) throw new Error("ideType required");
58001
- const killProcess = args?.killProcess !== false;
58002
- await ctx.stopIde(ideType, killProcess);
58299
+ const killProcess2 = args?.killProcess !== false;
58300
+ await ctx.stopIde(ideType, killProcess2);
58003
58301
  try {
58004
58302
  const results = await detectIDEs(ctx.deps.providerLoader);
58005
58303
  ctx.deps.detectedIdes.value = results;
58006
58304
  ctx.deps.providerLoader.setIdeDetectionResults(results, true);
58007
58305
  } catch {
58008
58306
  }
58009
- return { success: true, ideType, stopped: true, processKilled: killProcess };
58307
+ return { success: true, ideType, stopped: true, processKilled: killProcess2 };
58010
58308
  },
58011
58309
  // ─── IDE restart ───
58012
58310
  restart_ide: async (ctx, args) => {
@@ -60279,7 +60577,7 @@ import * as fs32 from "fs";
60279
60577
  import { hostname as osHostname } from "os";
60280
60578
 
60281
60579
  // src/mesh/preview-freshness.ts
60282
- import { execFileSync as execFileSync7 } from "child_process";
60580
+ import { execFileSync as execFileSync8 } from "child_process";
60283
60581
  import { existsSync as existsSync42, readFileSync as readFileSync32 } from "fs";
60284
60582
  import { resolve as resolve21 } from "path";
60285
60583
  var PREVIEW_DEPLOY_RECORD = ".adhdev/preview-deploy.json";
@@ -60305,7 +60603,7 @@ function isPreviewPipelineConfigured(repoRoot) {
60305
60603
  }
60306
60604
  function runGit2(repoRoot, args) {
60307
60605
  try {
60308
- return execFileSync7("git", args, {
60606
+ return execFileSync8("git", args, {
60309
60607
  cwd: repoRoot,
60310
60608
  encoding: "utf8",
60311
60609
  stdio: ["ignore", "pipe", "ignore"],
@@ -61129,7 +61427,7 @@ init_dist();
61129
61427
  init_mesh_events();
61130
61428
  init_mesh_reconcile_identity();
61131
61429
  init_mesh_fast_forward();
61132
- import { execFileSync as execFileSync9 } from "child_process";
61430
+ import { execFileSync as execFileSync10 } from "child_process";
61133
61431
 
61134
61432
  // src/mesh/mesh-refine-batch.ts
61135
61433
  init_resolve_executable();
@@ -61232,7 +61530,7 @@ init_worktree_bootstrap_config();
61232
61530
  init_resolve_executable();
61233
61531
  import { basename as pathBasename, join as pathJoin2, resolve as pathResolve2 } from "path";
61234
61532
  import * as fs34 from "fs";
61235
- import { execFileSync as execFileSync8 } from "child_process";
61533
+ import { execFileSync as execFileSync9 } from "child_process";
61236
61534
  var GIT2 = process.platform === "win32" ? resolveWin32Executable("git") : "git";
61237
61535
  var REFINE_VALIDATION_TIMEOUT_MS = 12e4;
61238
61536
  var REFINE_VALIDATION_OUTPUT_LIMIT_BYTES = 128 * 1024;
@@ -61287,18 +61585,18 @@ function resolveRefineryAutoPublishSubmoduleMainCommits(mesh, workspace) {
61287
61585
  return { enabled: false };
61288
61586
  }
61289
61587
  async function computeGitPatchId(cwd, fromRef, toRef, excludePaths = []) {
61290
- const { execFileSync: execFileSync11 } = await import("child_process");
61588
+ const { execFileSync: execFileSync12 } = await import("child_process");
61291
61589
  const diffArgs = ["diff", "--patch", "--full-index", fromRef, toRef];
61292
61590
  if (excludePaths.length > 0) {
61293
61591
  diffArgs.push("--", ".", ...excludePaths.map((path46) => `:(exclude)${path46}`));
61294
61592
  }
61295
- const diff = execFileSync11(GIT2, diffArgs, {
61593
+ const diff = execFileSync12(GIT2, diffArgs, {
61296
61594
  cwd,
61297
61595
  encoding: "utf8",
61298
61596
  maxBuffer: REFINE_PATCH_EQUIVALENCE_OUTPUT_LIMIT_BYTES
61299
61597
  });
61300
61598
  if (!diff.trim()) return "";
61301
- const patchId = execFileSync11(GIT2, ["patch-id", "--stable"], {
61599
+ const patchId = execFileSync12(GIT2, ["patch-id", "--stable"], {
61302
61600
  cwd,
61303
61601
  input: diff,
61304
61602
  encoding: "utf8",
@@ -61309,8 +61607,8 @@ async function computeGitPatchId(cwd, fromRef, toRef, excludePaths = []) {
61309
61607
  async function runMeshRefinePatchEquivalenceGate(repoRoot, baseHead, branchHead) {
61310
61608
  const startedAt = Date.now();
61311
61609
  try {
61312
- const { execFileSync: execFileSync11 } = await import("child_process");
61313
- const git = (args) => execFileSync11(GIT2, args, {
61610
+ const { execFileSync: execFileSync12 } = await import("child_process");
61611
+ const git = (args) => execFileSync12(GIT2, args, {
61314
61612
  cwd: repoRoot,
61315
61613
  encoding: "utf8",
61316
61614
  maxBuffer: REFINE_PATCH_EQUIVALENCE_OUTPUT_LIMIT_BYTES
@@ -61411,7 +61709,7 @@ async function classifyPatchEquivalenceFailure(repoRoot, baseHead, branchHead, s
61411
61709
  ...autoPublish !== void 0 ? { autoPublishSubmoduleMainCommits: autoPublish } : {}
61412
61710
  };
61413
61711
  try {
61414
- const git = (args) => execFileSync8(GIT2, args, {
61712
+ const git = (args) => execFileSync9(GIT2, args, {
61415
61713
  cwd: repoRoot,
61416
61714
  encoding: "utf8",
61417
61715
  maxBuffer: REFINE_PATCH_EQUIVALENCE_OUTPUT_LIMIT_BYTES,
@@ -61536,7 +61834,7 @@ async function classifyPatchEquivalenceFailure(repoRoot, baseHead, branchHead, s
61536
61834
  }
61537
61835
  function execGitOk(cwd, args) {
61538
61836
  try {
61539
- execFileSync8(GIT2, args, { cwd, encoding: "utf8", maxBuffer: REFINE_PATCH_EQUIVALENCE_OUTPUT_LIMIT_BYTES, windowsHide: true });
61837
+ execFileSync9(GIT2, args, { cwd, encoding: "utf8", maxBuffer: REFINE_PATCH_EQUIVALENCE_OUTPUT_LIMIT_BYTES, windowsHide: true });
61540
61838
  return true;
61541
61839
  } catch {
61542
61840
  return false;
@@ -61545,8 +61843,8 @@ function execGitOk(cwd, args) {
61545
61843
  async function checkWorktreeChangesPatchEquivalentInRef(repoRoot, ref, worktreeHead) {
61546
61844
  const startedAt = Date.now();
61547
61845
  try {
61548
- const { execFileSync: execFileSync11 } = await import("child_process");
61549
- const git = (gitArgs) => execFileSync11(GIT2, gitArgs, {
61846
+ const { execFileSync: execFileSync12 } = await import("child_process");
61847
+ const git = (gitArgs) => execFileSync12(GIT2, gitArgs, {
61550
61848
  cwd: repoRoot,
61551
61849
  encoding: "utf8",
61552
61850
  maxBuffer: REFINE_PATCH_EQUIVALENCE_OUTPUT_LIMIT_BYTES
@@ -61609,8 +61907,8 @@ ${mergeTreeErr?.stderr || ""}`;
61609
61907
  async function runMeshRefineEffectiveDiffGate(repoRoot, baseHead, branchHead) {
61610
61908
  const startedAt = Date.now();
61611
61909
  try {
61612
- const { execFileSync: execFileSync11 } = await import("child_process");
61613
- const git = (args, opts) => execFileSync11(GIT2, args, {
61910
+ const { execFileSync: execFileSync12 } = await import("child_process");
61911
+ const git = (args, opts) => execFileSync12(GIT2, args, {
61614
61912
  cwd: opts?.cwd || repoRoot,
61615
61913
  encoding: "utf8",
61616
61914
  maxBuffer: REFINE_PATCH_EQUIVALENCE_OUTPUT_LIMIT_BYTES
@@ -61686,7 +61984,7 @@ function buildPatchEquivalenceSubmoduleConflictHint(repoRoot, baseHead, branchHe
61686
61984
  }
61687
61985
  function readChangedGitlinkPaths(repoRoot, fromRef, toRef) {
61688
61986
  try {
61689
- const output = execFileSync8(GIT2, ["diff", "--raw", "--no-abbrev", fromRef, toRef], {
61987
+ const output = execFileSync9(GIT2, ["diff", "--raw", "--no-abbrev", fromRef, toRef], {
61690
61988
  cwd: repoRoot,
61691
61989
  encoding: "utf8",
61692
61990
  maxBuffer: REFINE_PATCH_EQUIVALENCE_OUTPUT_LIMIT_BYTES
@@ -61710,7 +62008,7 @@ function readChangedGitlinkPaths(repoRoot, fromRef, toRef) {
61710
62008
  }
61711
62009
  function readTreeObject(repoRoot, ref, path46) {
61712
62010
  try {
61713
- const output = execFileSync8(GIT2, ["ls-tree", ref, "--", path46], {
62011
+ const output = execFileSync9(GIT2, ["ls-tree", ref, "--", path46], {
61714
62012
  cwd: repoRoot,
61715
62013
  encoding: "utf8",
61716
62014
  maxBuffer: 1024 * 1024
@@ -61722,7 +62020,7 @@ function readTreeObject(repoRoot, ref, path46) {
61722
62020
  }
61723
62021
  }
61724
62022
  function resolveGitDir(repoRoot) {
61725
- const out = execFileSync8(GIT2, ["rev-parse", "--absolute-git-dir"], {
62023
+ const out = execFileSync9(GIT2, ["rev-parse", "--absolute-git-dir"], {
61726
62024
  cwd: repoRoot,
61727
62025
  encoding: "utf8",
61728
62026
  maxBuffer: 1024 * 1024
@@ -61734,9 +62032,9 @@ function isSubmoduleFastForward(submoduleRepoPath, baseCommit, branchCommit) {
61734
62032
  if (baseCommit === branchCommit) return true;
61735
62033
  try {
61736
62034
  if (!fs34.existsSync(submoduleRepoPath)) return false;
61737
- execFileSync8(GIT2, ["cat-file", "-e", `${baseCommit}^{commit}`], { cwd: submoduleRepoPath, stdio: "ignore" });
61738
- execFileSync8(GIT2, ["cat-file", "-e", `${branchCommit}^{commit}`], { cwd: submoduleRepoPath, stdio: "ignore" });
61739
- execFileSync8(GIT2, ["merge-base", "--is-ancestor", baseCommit, branchCommit], { cwd: submoduleRepoPath, stdio: "ignore" });
62035
+ execFileSync9(GIT2, ["cat-file", "-e", `${baseCommit}^{commit}`], { cwd: submoduleRepoPath, stdio: "ignore" });
62036
+ execFileSync9(GIT2, ["cat-file", "-e", `${branchCommit}^{commit}`], { cwd: submoduleRepoPath, stdio: "ignore" });
62037
+ execFileSync9(GIT2, ["merge-base", "--is-ancestor", baseCommit, branchCommit], { cwd: submoduleRepoPath, stdio: "ignore" });
61740
62038
  return true;
61741
62039
  } catch {
61742
62040
  return false;
@@ -61744,7 +62042,7 @@ function isSubmoduleFastForward(submoduleRepoPath, baseCommit, branchCommit) {
61744
62042
  }
61745
62043
  function readChangedPathKinds(repoRoot, fromRef, toRef) {
61746
62044
  try {
61747
- const output = execFileSync8(GIT2, ["diff", "--raw", "--no-abbrev", fromRef, toRef], {
62045
+ const output = execFileSync9(GIT2, ["diff", "--raw", "--no-abbrev", fromRef, toRef], {
61748
62046
  cwd: repoRoot,
61749
62047
  encoding: "utf8",
61750
62048
  maxBuffer: REFINE_PATCH_EQUIVALENCE_OUTPUT_LIMIT_BYTES
@@ -61796,7 +62094,7 @@ function evaluateGitlinkTrivialFastForward(repoRoot, baseHead, branchHead) {
61796
62094
  }
61797
62095
  let mergeBase = "";
61798
62096
  try {
61799
- mergeBase = execFileSync8(GIT2, ["merge-base", baseHead, branchHead], {
62097
+ mergeBase = execFileSync9(GIT2, ["merge-base", baseHead, branchHead], {
61800
62098
  cwd: repoRoot,
61801
62099
  encoding: "utf8",
61802
62100
  maxBuffer: 1024 * 1024
@@ -61826,7 +62124,7 @@ function evaluateGitlinkTrivialFastForward(repoRoot, baseHead, branchHead) {
61826
62124
  }
61827
62125
  function buildTreeWithGitlinksEqualized(repoRoot, commitish, paths, placeholderCommit) {
61828
62126
  try {
61829
- const tree = execFileSync8(GIT2, ["rev-parse", `${commitish}^{tree}`], {
62127
+ const tree = execFileSync9(GIT2, ["rev-parse", `${commitish}^{tree}`], {
61830
62128
  cwd: repoRoot,
61831
62129
  encoding: "utf8",
61832
62130
  maxBuffer: 1024 * 1024
@@ -61837,8 +62135,8 @@ function buildTreeWithGitlinksEqualized(repoRoot, commitish, paths, placeholderC
61837
62135
  const tmpIndex = pathJoin2(resolveGitDir(repoRoot), `adhdev-refine-eq-${commitish.slice(0, 12)}.index`);
61838
62136
  const env = { ...process.env, GIT_INDEX_FILE: tmpIndex };
61839
62137
  try {
61840
- execFileSync8(GIT2, ["read-tree", tree], { cwd: repoRoot, env, stdio: "ignore" });
61841
- execFileSync8(GIT2, ["update-index", "--index-info"], {
62138
+ execFileSync9(GIT2, ["read-tree", tree], { cwd: repoRoot, env, stdio: "ignore" });
62139
+ execFileSync9(GIT2, ["update-index", "--index-info"], {
61842
62140
  cwd: repoRoot,
61843
62141
  env,
61844
62142
  input: `${updates}
@@ -61846,7 +62144,7 @@ function buildTreeWithGitlinksEqualized(repoRoot, commitish, paths, placeholderC
61846
62144
  encoding: "utf8",
61847
62145
  stdio: ["pipe", "ignore", "ignore"]
61848
62146
  });
61849
- const newTree = execFileSync8(GIT2, ["write-tree"], { cwd: repoRoot, env, encoding: "utf8" }).trim();
62147
+ const newTree = execFileSync9(GIT2, ["write-tree"], { cwd: repoRoot, env, encoding: "utf8" }).trim();
61850
62148
  return newTree || void 0;
61851
62149
  } finally {
61852
62150
  try {
@@ -61862,7 +62160,7 @@ function synthesizeTrivialFastForwardMergeTree(repoRoot, baseHead, branchHead, g
61862
62160
  try {
61863
62161
  const branchGitlinks = gitlinks.filter((entry) => entry.branchCommit);
61864
62162
  const gitlinkPaths = branchGitlinks.map((entry) => entry.path);
61865
- const mergeBase = execFileSync8(GIT2, ["merge-base", baseHead, branchHead], {
62163
+ const mergeBase = execFileSync9(GIT2, ["merge-base", baseHead, branchHead], {
61866
62164
  cwd: repoRoot,
61867
62165
  encoding: "utf8",
61868
62166
  maxBuffer: 1024 * 1024
@@ -61875,22 +62173,22 @@ function synthesizeTrivialFastForwardMergeTree(repoRoot, baseHead, branchHead, g
61875
62173
  const theirsEqTree = buildTreeWithGitlinksEqualized(repoRoot, branchHead, gitlinkPaths, placeholder);
61876
62174
  if (baseEqTree && oursEqTree && theirsEqTree) {
61877
62175
  try {
61878
- const baseEqCommit = execFileSync8(GIT2, ["commit-tree", baseEqTree, "-m", "refine-ff-base"], {
62176
+ const baseEqCommit = execFileSync9(GIT2, ["commit-tree", baseEqTree, "-m", "refine-ff-base"], {
61879
62177
  cwd: repoRoot,
61880
62178
  encoding: "utf8",
61881
62179
  maxBuffer: 1024 * 1024
61882
62180
  }).trim();
61883
- const oursEqCommit = execFileSync8(GIT2, ["commit-tree", oursEqTree, "-p", baseEqCommit, "-m", "refine-ff-ours"], {
62181
+ const oursEqCommit = execFileSync9(GIT2, ["commit-tree", oursEqTree, "-p", baseEqCommit, "-m", "refine-ff-ours"], {
61884
62182
  cwd: repoRoot,
61885
62183
  encoding: "utf8",
61886
62184
  maxBuffer: 1024 * 1024
61887
62185
  }).trim();
61888
- const theirsEqCommit = execFileSync8(GIT2, ["commit-tree", theirsEqTree, "-p", baseEqCommit, "-m", "refine-ff-theirs"], {
62186
+ const theirsEqCommit = execFileSync9(GIT2, ["commit-tree", theirsEqTree, "-p", baseEqCommit, "-m", "refine-ff-theirs"], {
61889
62187
  cwd: repoRoot,
61890
62188
  encoding: "utf8",
61891
62189
  maxBuffer: 1024 * 1024
61892
62190
  }).trim();
61893
- const mergeOut = execFileSync8(GIT2, ["merge-tree", "--write-tree", oursEqCommit, theirsEqCommit], {
62191
+ const mergeOut = execFileSync9(GIT2, ["merge-tree", "--write-tree", oursEqCommit, theirsEqCommit], {
61894
62192
  cwd: repoRoot,
61895
62193
  encoding: "utf8",
61896
62194
  maxBuffer: REFINE_PATCH_EQUIVALENCE_OUTPUT_LIMIT_BYTES
@@ -61901,7 +62199,7 @@ function synthesizeTrivialFastForwardMergeTree(repoRoot, baseHead, branchHead, g
61901
62199
  }
61902
62200
  }
61903
62201
  }
61904
- const contentTree = mergedContentTree || execFileSync8(GIT2, ["rev-parse", `${baseHead}^{tree}`], {
62202
+ const contentTree = mergedContentTree || execFileSync9(GIT2, ["rev-parse", `${baseHead}^{tree}`], {
61905
62203
  cwd: repoRoot,
61906
62204
  encoding: "utf8",
61907
62205
  maxBuffer: 1024 * 1024
@@ -61912,8 +62210,8 @@ function synthesizeTrivialFastForwardMergeTree(repoRoot, baseHead, branchHead, g
61912
62210
  const tmpIndex = pathJoin2(resolveGitDir(repoRoot), `adhdev-refine-ff-${baseHead.slice(0, 12)}-${branchHead.slice(0, 12)}.index`);
61913
62211
  const env = { ...process.env, GIT_INDEX_FILE: tmpIndex };
61914
62212
  try {
61915
- execFileSync8(GIT2, ["read-tree", contentTree], { cwd: repoRoot, env, stdio: "ignore" });
61916
- execFileSync8(GIT2, ["update-index", "--index-info"], {
62213
+ execFileSync9(GIT2, ["read-tree", contentTree], { cwd: repoRoot, env, stdio: "ignore" });
62214
+ execFileSync9(GIT2, ["update-index", "--index-info"], {
61917
62215
  cwd: repoRoot,
61918
62216
  env,
61919
62217
  input: `${updates}
@@ -61921,7 +62219,7 @@ function synthesizeTrivialFastForwardMergeTree(repoRoot, baseHead, branchHead, g
61921
62219
  encoding: "utf8",
61922
62220
  stdio: ["pipe", "ignore", "ignore"]
61923
62221
  });
61924
- const newTree = execFileSync8(GIT2, ["write-tree"], { cwd: repoRoot, env, encoding: "utf8" }).trim();
62222
+ const newTree = execFileSync9(GIT2, ["write-tree"], { cwd: repoRoot, env, encoding: "utf8" }).trim();
61925
62223
  return newTree || void 0;
61926
62224
  } finally {
61927
62225
  try {
@@ -62834,10 +63132,10 @@ async function refineSyncBaseStage(self, ctx) {
62834
63132
  }
62835
63133
  const rebaseStarted = Date.now();
62836
63134
  try {
62837
- execFileSync9("git", ["rebase", baseHead], { cwd: node.workspace, stdio: ["ignore", "pipe", "pipe"] });
63135
+ execFileSync10("git", ["rebase", baseHead], { cwd: node.workspace, stdio: ["ignore", "pipe", "pipe"] });
62838
63136
  } catch (rebaseErr) {
62839
63137
  try {
62840
- execFileSync9("git", ["rebase", "--abort"], { cwd: node.workspace, stdio: "ignore" });
63138
+ execFileSync10("git", ["rebase", "--abort"], { cwd: node.workspace, stdio: "ignore" });
62841
63139
  } catch {
62842
63140
  }
62843
63141
  let submoduleHintPatchEquivalence;
@@ -65719,7 +66017,7 @@ var DaemonCommandRouter = class {
65719
66017
  /**
65720
66018
  * IDE stop: CDP disconnect + InstanceManager cleanup + optionally kill OS process
65721
66019
  */
65722
- async stopIde(ideType, killProcess = false) {
66020
+ async stopIde(ideType, killProcess2 = false) {
65723
66021
  const cdpKeysToRemove = [];
65724
66022
  for (const key2 of this.deps.cdpManagers.keys()) {
65725
66023
  if (key2 === ideType || key2.startsWith(`${ideType}_`)) {
@@ -65757,7 +66055,7 @@ var DaemonCommandRouter = class {
65757
66055
  LOG.info("StopIDE", `Instance removed: ${instanceKey}`);
65758
66056
  }
65759
66057
  }
65760
- if (killProcess) {
66058
+ if (killProcess2) {
65761
66059
  const running = await isIdeRunning(ideType);
65762
66060
  if (running) {
65763
66061
  LOG.info("StopIDE", `Killing IDE process: ${ideType}`);
@@ -65772,7 +66070,7 @@ var DaemonCommandRouter = class {
65772
66070
  }
65773
66071
  }
65774
66072
  this.deps.onStatusChange?.();
65775
- LOG.info("StopIDE", `IDE stopped: ${ideType} (processKill=${killProcess})`);
66073
+ LOG.info("StopIDE", `IDE stopped: ${ideType} (processKill=${killProcess2})`);
65776
66074
  }
65777
66075
  };
65778
66076
 
@@ -72709,8 +73007,10 @@ var SessionHostRuntimeTransport = class {
72709
73007
  return;
72710
73008
  }
72711
73009
  if (event.type === "session_exit") {
73010
+ const exitCode = typeof event.exitCode === "number" ? event.exitCode : null;
73011
+ const signal = typeof event.signal === "number" ? event.signal : null;
72712
73012
  for (const callback of this.exitCallbacks) {
72713
- callback({ exitCode: event.exitCode ?? 0 });
73013
+ callback({ exitCode, signal });
72714
73014
  }
72715
73015
  void this.closeClient(false);
72716
73016
  }
@@ -73102,7 +73402,7 @@ async function listHostedCliRuntimes(endpoint) {
73102
73402
  }
73103
73403
 
73104
73404
  // src/session-host/managed-host.ts
73105
- import { execFileSync as execFileSync10, spawn as spawn5 } from "child_process";
73405
+ import { execFileSync as execFileSync11, spawn as spawn5 } from "child_process";
73106
73406
  import * as fs43 from "fs";
73107
73407
  import * as os31 from "os";
73108
73408
  import * as path45 from "path";
@@ -73110,6 +73410,7 @@ import {
73110
73410
  getDefaultSessionHostEndpoint as getDefaultSessionHostEndpoint2,
73111
73411
  sanitizeSpawnEnv as sanitizeSpawnEnv2
73112
73412
  } from "@adhdev/session-host-core";
73413
+ init_logger();
73113
73414
  init_runtime_defaults();
73114
73415
  function createManagedSessionHost(options) {
73115
73416
  const appName = options.appName;
@@ -73133,6 +73434,14 @@ function createManagedSessionHost(options) {
73133
73434
  }
73134
73435
  return __require.resolve("@adhdev/session-host-daemon");
73135
73436
  }
73437
+ function pathsEquivalent(left, right) {
73438
+ return path45.resolve(left).toLowerCase() === path45.resolve(right).toLowerCase();
73439
+ }
73440
+ function getRunningSessionHostScriptPath(pid) {
73441
+ const commandLine = getProcessCommandLine(pid);
73442
+ if (!commandLine || !/session-host-daemon/i.test(commandLine)) return null;
73443
+ return parseNodeScriptPath(commandLine);
73444
+ }
73136
73445
  function getPidFile() {
73137
73446
  return path45.join(os31.homedir(), ".adhdev", `${appName}-session-host.pid`);
73138
73447
  }
@@ -73146,12 +73455,12 @@ function createManagedSessionHost(options) {
73146
73455
  return null;
73147
73456
  }
73148
73457
  }
73149
- function killPid2(pid) {
73458
+ function killPid(pid) {
73150
73459
  try {
73151
73460
  if (process.platform === "win32") {
73152
73461
  const spawnOpts = { stdio: "ignore" };
73153
73462
  if (options.killWindowsHide) spawnOpts.windowsHide = true;
73154
- execFileSync10("taskkill", ["/PID", String(pid), "/T", "/F"], spawnOpts);
73463
+ execFileSync11("taskkill", ["/PID", String(pid), "/T", "/F"], spawnOpts);
73155
73464
  } else {
73156
73465
  process.kill(pid, "SIGTERM");
73157
73466
  }
@@ -73191,7 +73500,7 @@ function createManagedSessionHost(options) {
73191
73500
  if (fs43.existsSync(pidFile)) {
73192
73501
  const pid = Number.parseInt(fs43.readFileSync(pidFile, "utf8").trim(), 10);
73193
73502
  if (Number.isFinite(pid) && pid !== process.pid && isManagedPid(pid)) {
73194
- stopped = killPid2(pid) || stopped;
73503
+ stopped = killPid(pid) || stopped;
73195
73504
  }
73196
73505
  }
73197
73506
  } catch {
@@ -73208,6 +73517,20 @@ function createManagedSessionHost(options) {
73208
73517
  }
73209
73518
  async function ensureReady() {
73210
73519
  options.beforeEnsureReady?.();
73520
+ if (process.platform === "win32") {
73521
+ const existingPid = getPid();
73522
+ if (existingPid !== null) {
73523
+ const runningPath = getRunningSessionHostScriptPath(existingPid);
73524
+ const currentEntry = resolveEntry();
73525
+ if (runningPath && !pathsEquivalent(runningPath, currentEntry)) {
73526
+ LOG.warn(
73527
+ "SessionHost",
73528
+ `Detected stale host pid ${existingPid} running from ${runningPath}; restarting from ${currentEntry}`
73529
+ );
73530
+ stopManagedSessionHostProcess();
73531
+ }
73532
+ }
73533
+ }
73211
73534
  try {
73212
73535
  return await ensureSessionHostReady({
73213
73536
  appName,
@@ -73236,7 +73559,7 @@ function createManagedSessionHost(options) {
73236
73559
  getPid,
73237
73560
  buildEnv,
73238
73561
  resolveEntry,
73239
- killPid: killPid2,
73562
+ killPid,
73240
73563
  spawnHost,
73241
73564
  stopManagedSessionHostProcess,
73242
73565
  ensureReady,