@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.js CHANGED
@@ -442,10 +442,10 @@ function readInjected(value) {
442
442
  }
443
443
  function getDaemonBuildInfo() {
444
444
  if (cached) return cached;
445
- const commit = readInjected(true ? "dcf5798b77def59c325d533c744c7d57b4fc8eac" : void 0) ?? "unknown";
446
- const commitShort = readInjected(true ? "dcf5798b" : void 0) ?? (commit !== "unknown" ? commit.slice(0, 7) : "unknown");
447
- const version = readInjected(true ? "1.0.18-rc.2" : void 0) ?? readInjected(typeof process !== "undefined" ? process.env?.ADHDEV_PKG_VERSION : void 0) ?? "unknown";
448
- const builtAt = readInjected(true ? "2026-07-21T07:45:31.375Z" : void 0);
445
+ const commit = readInjected(true ? "96f3f1365be0d875f5089fc7ce7e4e254ca8e08d" : void 0) ?? "unknown";
446
+ const commitShort = readInjected(true ? "96f3f136" : void 0) ?? (commit !== "unknown" ? commit.slice(0, 7) : "unknown");
447
+ const version = readInjected(true ? "1.0.18-rc.3" : void 0) ?? readInjected(typeof process !== "undefined" ? process.env?.ADHDEV_PKG_VERSION : void 0) ?? "unknown";
448
+ const builtAt = readInjected(true ? "2026-07-21T13:00:17.395Z" : void 0);
449
449
  cached = builtAt ? { commit, commitShort, version, builtAt } : { commit, commitShort, version };
450
450
  return cached;
451
451
  }
@@ -25779,6 +25779,22 @@ var init_cli_state_engine = __esm({
25779
25779
  // ── Approval ─────────────────────────────────────
25780
25780
  lastApprovalResolvedAt = 0;
25781
25781
  lastResolvedModalMessage = "";
25782
+ /**
25783
+ * Normalized approval-context signature captured at resolve time (see
25784
+ * `computeApprovalContentSignature`) — the screen text with blank-line
25785
+ * padding and any manifest-declared chrome (transcriptPty.chromePatterns,
25786
+ * spinner.patterns) stripped out. Used by applyWaitingApproval's
25787
+ * isStaleResolvedRepaint check to tell "the same already-answered modal,
25788
+ * re-parsed from a screen that has not meaningfully changed" apart from
25789
+ * "a genuinely new approval" — content-based, not time-based, because
25790
+ * ordinary TUI chrome (status bar, context meter, blank-line repaint)
25791
+ * keeps producing fresh PTY bytes on an otherwise-unchanged screen and
25792
+ * defeats any output-timestamp discriminator within a few hundred ms.
25793
+ * Cleared whenever a turn starts or the session tears down (see
25794
+ * onTurnStarted / resetActiveTurnState / onPtyExit) so a next-turn
25795
+ * approval is never compared against stale prior-turn state.
25796
+ */
25797
+ lastApprovalResolvedContentSignature = "";
25782
25798
  /**
25783
25799
  * Monotonic counter bumped every time the FSM *enters* waiting_approval
25784
25800
  * with a freshly captured modal (see `applyWaitingApproval`). It is the
@@ -25898,11 +25914,17 @@ var init_cli_state_engine = __esm({
25898
25914
  this.currentTurnTaskId = typeof turnScope.taskId === "string" && turnScope.taskId.trim() ? turnScope.taskId : null;
25899
25915
  this.currentTurnStartedAt = Date.now();
25900
25916
  this.responseEpoch += 1;
25917
+ this.clearApprovalResolutionMemory();
25918
+ if (this.provider.transcriptAuthority === "provider" && this.currentStatus !== "waiting_approval") {
25919
+ this.setStatus("generating", "turn_started");
25920
+ this.callbacks.onStatusChange();
25921
+ }
25901
25922
  }
25902
25923
  /** Called when PTY exits */
25903
25924
  onPtyExit() {
25904
25925
  this.clearAllTimers();
25905
25926
  this.setStatus("stopped", "pty_exit");
25927
+ this.clearApprovalResolutionMemory();
25906
25928
  }
25907
25929
  /** Called when adapter starts up successfully */
25908
25930
  onSpawnReady() {
@@ -25944,6 +25966,7 @@ var init_cli_state_engine = __esm({
25944
25966
  this.activeModal = null;
25945
25967
  this.lastApprovalResolvedAt = Date.now();
25946
25968
  this.lastResolvedModalMessage = currentModalMessage;
25969
+ this.lastApprovalResolvedContentSignature = this.computeApprovalContentSignature(snap);
25947
25970
  this.lastResolvedEntrySeq = this.approvalEntrySeq;
25948
25971
  this.responseSettleIgnoreUntil = Date.now() + this.timeouts.outputSettle + 400;
25949
25972
  if (this.approvalExitTimeout) {
@@ -26066,6 +26089,7 @@ var init_cli_state_engine = __esm({
26066
26089
  this.pendingScriptStatusSince = 0;
26067
26090
  this.approvalResumeDeferSince = 0;
26068
26091
  this.approvalResumeDeferEpoch = -1;
26092
+ this.clearApprovalResolutionMemory();
26069
26093
  }
26070
26094
  clearIdleFinishCandidate(reason) {
26071
26095
  if (!this.idleFinishCandidate) return;
@@ -26224,7 +26248,15 @@ var init_cli_state_engine = __esm({
26224
26248
  }
26225
26249
  if (!status) return;
26226
26250
  const prevStatus = this.currentStatus;
26227
- const ctx = { now, modal, status, parsedMessages, lastParsedAssistant, parsedStatus: parsedStatus || null, prevStatus };
26251
+ const ctx = {
26252
+ now,
26253
+ modal,
26254
+ status,
26255
+ parsedMessages,
26256
+ lastParsedAssistant,
26257
+ parsedStatus: parsedStatus || null,
26258
+ prevStatus
26259
+ };
26228
26260
  if (!this.applyPendingScriptStatusDebounce(ctx)) return;
26229
26261
  const recentInteractiveActivity = this.hasRecentInteractiveActivity(snap, now);
26230
26262
  LOG.debug(
@@ -26260,7 +26292,7 @@ var init_cli_state_engine = __esm({
26260
26292
  return;
26261
26293
  }
26262
26294
  if (status === "waiting_approval") {
26263
- this.applyWaitingApproval(ctx);
26295
+ this.applyWaitingApproval(ctx, snap);
26264
26296
  return;
26265
26297
  }
26266
26298
  if (status === "generating") {
@@ -26320,7 +26352,7 @@ var init_cli_state_engine = __esm({
26320
26352
  }, this.timeouts.generatingIdle);
26321
26353
  this.callbacks.onStatusChange();
26322
26354
  }
26323
- applyWaitingApproval(ctx) {
26355
+ applyWaitingApproval(ctx, snap) {
26324
26356
  const { modal } = ctx;
26325
26357
  this.clearIdleFinishCandidate("waiting_approval");
26326
26358
  const inCooldown = this.lastApprovalResolvedAt && Date.now() - this.lastApprovalResolvedAt < this.timeouts.approvalCooldown;
@@ -26367,6 +26399,11 @@ var init_cli_state_engine = __esm({
26367
26399
  }
26368
26400
  return;
26369
26401
  }
26402
+ const isStaleResolvedRepaint = !this.activeModal && this.isStaleResolvedApproval(modal, snap);
26403
+ if (isStaleResolvedRepaint) {
26404
+ LOG.debug("CLI", `[${this.provider.type}] ignoring stale re-parsed approval matching the just-resolved modal (approval-context signature unchanged)`);
26405
+ return;
26406
+ }
26370
26407
  this.modalLostAt = 0;
26371
26408
  this.isWaitingForResponse = true;
26372
26409
  this.setStatus("waiting_approval", "script_detect");
@@ -26652,6 +26689,86 @@ var init_cli_state_engine = __esm({
26652
26689
  this.recordTrace("idle_finish_cancelled", { trigger: reason });
26653
26690
  }
26654
26691
  // ─── Helpers ────────────────────────────────────────────────────────────
26692
+ /**
26693
+ * Derive a stable approval-context signature from the current screen,
26694
+ * with blank-line padding and any manifest-declared chrome stripped out.
26695
+ *
26696
+ * Generic by design — no kimi- or provider-specific hardcoding: it reads
26697
+ * whatever `tui.transcriptPty.chromePatterns` and `tui.spinner.patterns`
26698
+ * the ACTIVE provider's own manifest already declares (present on any
26699
+ * declarative-TUI provider; simply absent/empty for scripted providers,
26700
+ * in which case this degrades to blank-line stripping only — never worse
26701
+ * than comparing the raw screen). Those pattern lists exist precisely to
26702
+ * name "known volatile repaint noise" (status bar, context meter, spinner
26703
+ * ticks, banners) — reusing them here means the SAME declared knowledge
26704
+ * that governs transcript-chrome stripping also governs staleness
26705
+ * detection, instead of re-encoding provider knowledge into daemon-core.
26706
+ *
26707
+ * Deliberately NOT a hash of the whole screen/buffer: an ordinary TUI
26708
+ * repaints its footer/status/context-meter chrome continuously even while
26709
+ * genuinely idle, so a raw whole-screen or whole-buffer fingerprint (or a
26710
+ * mere "did any bytes arrive" timestamp) changes on every repaint tick
26711
+ * regardless of whether anything approval-relevant actually happened.
26712
+ * Stripping the declared chrome first yields a signature that only
26713
+ * changes when the surrounding conversation/tool-output content itself
26714
+ * changes — exactly the discriminator applyWaitingApproval's
26715
+ * isStaleResolvedRepaint check needs.
26716
+ */
26717
+ computeApprovalContentSignature(snap) {
26718
+ const screenText = snap.screenText || snap.accumulatedBuffer || "";
26719
+ if (!screenText) return "";
26720
+ const tui = this.provider.tui;
26721
+ const patternSpecs = [
26722
+ ...Array.isArray(tui?.transcriptPty?.chromePatterns) ? tui.transcriptPty.chromePatterns : [],
26723
+ ...Array.isArray(tui?.spinner?.patterns) ? tui.spinner.patterns : []
26724
+ ];
26725
+ const chromeRegexes = [];
26726
+ for (const spec of patternSpecs) {
26727
+ if (spec && typeof spec.regex === "string") {
26728
+ try {
26729
+ chromeRegexes.push(new RegExp(spec.regex, typeof spec.flags === "string" ? spec.flags : ""));
26730
+ } catch {
26731
+ }
26732
+ }
26733
+ }
26734
+ const kept = [];
26735
+ for (const rawLine of screenText.split("\n")) {
26736
+ const line = rawLine.trim();
26737
+ if (!line) continue;
26738
+ if (chromeRegexes.some((re) => re.test(line))) continue;
26739
+ kept.push(line);
26740
+ }
26741
+ return kept.join("\n");
26742
+ }
26743
+ /**
26744
+ * True when `modal` is a stale re-parse of an already-resolved approval:
26745
+ * same message text, and the chrome-stripped approval-context signature
26746
+ * of `snap` is unchanged from the signature captured at resolve time.
26747
+ *
26748
+ * Public and reused verbatim by BOTH the settled-eval capture path
26749
+ * (applyWaitingApproval, below) and any OUTSIDE re-parse the adapter
26750
+ * performs independently of the settle loop — e.g. provider-cli-adapter's
26751
+ * getStatus()/getDebugState() startup-gate modal detection, which reads
26752
+ * `recentOutputBuffer` directly while `startupParseGate` is open and can
26753
+ * re-surface the same already-resolved modal before the gate closes.
26754
+ * Centralizing the discriminator here means there is exactly ONE
26755
+ * definition of "stale" for the whole session — no divergent duplicate
26756
+ * heuristic re-implemented per call site.
26757
+ */
26758
+ isStaleResolvedApproval(modal, snap) {
26759
+ if (!modal) return false;
26760
+ const normalizedMessage = typeof modal.message === "string" ? modal.message.trim() : "";
26761
+ if (!normalizedMessage) return false;
26762
+ return this.lastApprovalResolvedAt > 0 && normalizedMessage === this.lastResolvedModalMessage && this.computeApprovalContentSignature(snap) === this.lastApprovalResolvedContentSignature;
26763
+ }
26764
+ /** Clear all resolve-time approval bookkeeping (message, timestamp, content
26765
+ * signature) — called at turn/session boundaries so a next-turn or
26766
+ * next-session approval is never compared against stale prior state. */
26767
+ clearApprovalResolutionMemory() {
26768
+ this.lastApprovalResolvedAt = 0;
26769
+ this.lastResolvedModalMessage = "";
26770
+ this.lastApprovalResolvedContentSignature = "";
26771
+ }
26655
26772
  /**
26656
26773
  * Schedule one more settled evaluation while pinned to `waiting_approval`
26657
26774
  * 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();
@@ -42712,20 +42835,202 @@ var notificationHandlers = {
42712
42835
  init_config();
42713
42836
 
42714
42837
  // src/commands/upgrade-helper.ts
42715
- var import_child_process6 = require("child_process");
42716
42838
  var import_child_process7 = require("child_process");
42839
+ var import_child_process8 = require("child_process");
42717
42840
  var fs15 = __toESM(require("fs"));
42718
42841
  var os13 = __toESM(require("os"));
42719
42842
  var path21 = __toESM(require("path"));
42720
42843
 
42721
42844
  // src/commands/windows-atomic-upgrade.ts
42722
- var import_child_process5 = require("child_process");
42845
+ var import_child_process6 = require("child_process");
42723
42846
  var fs14 = __toESM(require("fs"));
42724
42847
  var http2 = __toESM(require("http"));
42725
42848
  var path20 = __toESM(require("path"));
42849
+
42850
+ // src/commands/process-lifecycle.ts
42851
+ var import_child_process5 = require("child_process");
42852
+ function defaultExecFileSync() {
42853
+ return import_child_process5.execFileSync;
42854
+ }
42855
+ function getWindowsProcessCommandLine(pid, exec7) {
42856
+ const pidFilter = `ProcessId=${pid}`;
42857
+ try {
42858
+ const psOut = exec7("powershell.exe", [
42859
+ "-NoProfile",
42860
+ "-NonInteractive",
42861
+ "-ExecutionPolicy",
42862
+ "Bypass",
42863
+ "-Command",
42864
+ `(Get-CimInstance Win32_Process -Filter "${pidFilter}").CommandLine`
42865
+ ], { encoding: "utf8", timeout: 5e3, stdio: ["ignore", "pipe", "ignore"], windowsHide: true });
42866
+ const text = String(psOut).trim();
42867
+ if (text) return text;
42868
+ } catch {
42869
+ }
42870
+ try {
42871
+ const wmicOut = exec7("wmic", [
42872
+ "process",
42873
+ "where",
42874
+ pidFilter,
42875
+ "get",
42876
+ "CommandLine"
42877
+ ], { encoding: "utf8", timeout: 3e3, stdio: ["ignore", "pipe", "ignore"], windowsHide: true });
42878
+ const text = String(wmicOut).trim();
42879
+ if (text) return text;
42880
+ } catch {
42881
+ }
42882
+ return null;
42883
+ }
42884
+ function getProcessCommandLine(pid, options = {}) {
42885
+ if (!Number.isFinite(pid) || pid <= 0) return null;
42886
+ const exec7 = options.execFileSync ?? defaultExecFileSync();
42887
+ const platform10 = options.platform ?? process.platform;
42888
+ if (platform10 === "win32") {
42889
+ return getWindowsProcessCommandLine(pid, exec7);
42890
+ }
42891
+ try {
42892
+ const text = String(exec7("ps", ["-o", "command=", "-p", String(pid)], {
42893
+ encoding: "utf8",
42894
+ timeout: 3e3,
42895
+ stdio: ["ignore", "pipe", "ignore"]
42896
+ })).trim();
42897
+ return text || null;
42898
+ } catch {
42899
+ return null;
42900
+ }
42901
+ }
42902
+ function parseNodeScriptPath(commandLine) {
42903
+ if (!commandLine) return null;
42904
+ let rest = commandLine.trim();
42905
+ if (rest.startsWith('"')) {
42906
+ const end = rest.indexOf('"', 1);
42907
+ if (end === -1) return null;
42908
+ rest = rest.slice(end + 1).trim();
42909
+ } else {
42910
+ const idx2 = rest.search(/\s/);
42911
+ if (idx2 === -1) return null;
42912
+ rest = rest.slice(idx2 + 1).trim();
42913
+ }
42914
+ if (!rest) return null;
42915
+ if (rest.startsWith('"')) {
42916
+ const end = rest.indexOf('"', 1);
42917
+ return end === -1 ? rest.slice(1) : rest.slice(1, end);
42918
+ }
42919
+ const idx = rest.search(/\s/);
42920
+ return idx === -1 ? rest : rest.slice(0, idx);
42921
+ }
42922
+ function normalizeWindowsPath(value) {
42923
+ return value.toLowerCase().replace(/\//g, "\\").replace(/\\+$/, "");
42924
+ }
42925
+ function killProcess(pid, options = {}) {
42926
+ if (!Number.isFinite(pid) || pid <= 0) return false;
42927
+ const exec7 = options.execFileSync ?? defaultExecFileSync();
42928
+ const platform10 = options.platform ?? process.platform;
42929
+ try {
42930
+ if (platform10 === "win32") {
42931
+ exec7("taskkill", ["/PID", String(pid), "/T", "/F"], {
42932
+ stdio: "ignore",
42933
+ windowsHide: true
42934
+ });
42935
+ } else {
42936
+ process.kill(pid, "SIGTERM");
42937
+ }
42938
+ return true;
42939
+ } catch {
42940
+ return false;
42941
+ }
42942
+ }
42943
+ async function waitForPidExit(pid, timeoutMs) {
42944
+ const start = Date.now();
42945
+ while (Date.now() - start < timeoutMs) {
42946
+ try {
42947
+ process.kill(pid, 0);
42948
+ await new Promise((resolve27) => setTimeout(resolve27, 250));
42949
+ } catch {
42950
+ return true;
42951
+ }
42952
+ }
42953
+ return false;
42954
+ }
42955
+ function listOwnedNodeProcesses(options) {
42956
+ const platform10 = options.platform ?? process.platform;
42957
+ if (platform10 !== "win32") return [];
42958
+ const exec7 = options.execFileSync ?? defaultExecFileSync();
42959
+ const prefixes = options.prefixes.map((p) => normalizeWindowsPath(p));
42960
+ const exclude = new Set((options.excludePids ?? []).filter((n) => Number.isFinite(n) && n > 0));
42961
+ const markers = (options.markers ?? []).map((m) => m.toLowerCase().replace(/\//g, "\\"));
42962
+ let pids = [];
42963
+ try {
42964
+ const out = String(exec7("powershell.exe", [
42965
+ "-NoProfile",
42966
+ "-NonInteractive",
42967
+ "-ExecutionPolicy",
42968
+ "Bypass",
42969
+ "-Command",
42970
+ "Get-Process node -ErrorAction SilentlyContinue | Select-Object -ExpandProperty Id | ConvertTo-Json -Compress"
42971
+ ], { encoding: "utf8", timeout: 8e3, stdio: ["ignore", "pipe", "ignore"], windowsHide: true })).trim();
42972
+ if (out) {
42973
+ const parsed = JSON.parse(out);
42974
+ pids = Array.isArray(parsed) ? parsed : [parsed];
42975
+ }
42976
+ } catch {
42977
+ return [];
42978
+ }
42979
+ const results = [];
42980
+ for (const pid of pids) {
42981
+ if (!Number.isFinite(pid) || pid <= 0 || exclude.has(pid)) continue;
42982
+ const commandLine = getProcessCommandLine(pid, { platform: platform10, execFileSync: exec7 });
42983
+ if (!commandLine) continue;
42984
+ const lower = commandLine.toLowerCase();
42985
+ const underPrefix = prefixes.some((prefix) => lower.includes(prefix));
42986
+ if (!underPrefix) continue;
42987
+ if (markers.length > 0 && !markers.some((marker) => lower.includes(marker))) continue;
42988
+ results.push({ pid, commandLine });
42989
+ }
42990
+ return results;
42991
+ }
42992
+ async function stopOwnedProcesses(options) {
42993
+ const waitMs = options.waitMs ?? 15e3;
42994
+ const killed = /* @__PURE__ */ new Set();
42995
+ for (const p of options.processes) {
42996
+ if (killProcess(p.pid, options)) killed.add(p.pid);
42997
+ }
42998
+ const survivors = [];
42999
+ for (const p of options.processes) {
43000
+ if (!killed.has(p.pid)) {
43001
+ survivors.push(p);
43002
+ continue;
43003
+ }
43004
+ const exited = await waitForPidExit(p.pid, waitMs);
43005
+ if (!exited) survivors.push(p);
43006
+ }
43007
+ return { stopped: options.processes.length - survivors.length, survivors };
43008
+ }
43009
+ async function stopOwnedProcessesForPrefixes(options) {
43010
+ const processes = listOwnedNodeProcesses(options);
43011
+ if (processes.length === 0) return { stopped: 0, survivors: [] };
43012
+ options.log?.(`Stopping ${processes.length} owned process(es) under prefixes: ${options.prefixes.join(", ")}`);
43013
+ const result = await stopOwnedProcesses({
43014
+ processes,
43015
+ waitMs: options.waitMs,
43016
+ platform: options.platform,
43017
+ execFileSync: options.execFileSync
43018
+ });
43019
+ if (result.survivors.length > 0) {
43020
+ options.log?.(`Could not stop ${result.survivors.length} owned process(es): ${result.survivors.map((s2) => s2.pid).join(", ")}`);
43021
+ }
43022
+ return result;
43023
+ }
43024
+
43025
+ // src/commands/windows-atomic-upgrade.ts
42726
43026
  var POINTER_NAME = ".adhdev-current";
42727
43027
  var STABLE_FILES = [POINTER_NAME, "adhdev.cmd", "adhdev.ps1", "adhdev"];
42728
43028
  var DEFAULT_HEALTH_PORT = 19222;
43029
+ var ADHDEV_OWNED_MARKERS = [
43030
+ "session-host-daemon",
43031
+ "node_modules/adhdev",
43032
+ "node_modules/@adhdev/daemon-standalone"
43033
+ ];
42729
43034
  function normalizeForCompare(value) {
42730
43035
  return path20.resolve(value).replace(/[\\/]+$/, "").toLowerCase();
42731
43036
  }
@@ -42748,7 +43053,7 @@ function resolveWindowsInstallerLayout(options) {
42748
43053
  }
42749
43054
  function nodeMajor(nodeExecutable) {
42750
43055
  try {
42751
- const version = String((0, import_child_process5.execFileSync)(nodeExecutable, ["-p", "process.versions.node"], {
43056
+ const version = String((0, import_child_process6.execFileSync)(nodeExecutable, ["-p", "process.versions.node"], {
42752
43057
  encoding: "utf8",
42753
43058
  timeout: 5e3,
42754
43059
  windowsHide: true,
@@ -42810,7 +43115,7 @@ exit $LASTEXITCODE\r
42810
43115
  }
42811
43116
  }
42812
43117
  function validateStagedCli(portableNode, cliEntry, targetVersion) {
42813
- const output = String((0, import_child_process5.execFileSync)(portableNode, [cliEntry, "--version"], {
43118
+ const output = String((0, import_child_process6.execFileSync)(portableNode, [cliEntry, "--version"], {
42814
43119
  encoding: "utf8",
42815
43120
  timeout: 15e3,
42816
43121
  windowsHide: true,
@@ -42839,7 +43144,7 @@ function atomicWrite(destination, content, encoding) {
42839
43144
  `} finally { if ([IO.File]::Exists($temporary)) { [IO.File]::Delete($temporary) } }`
42840
43145
  ].join("\n");
42841
43146
  const encoded = Buffer.from(script, "utf16le").toString("base64");
42842
- const result = (0, import_child_process5.spawnSync)("powershell.exe", [
43147
+ const result = (0, import_child_process6.spawnSync)("powershell.exe", [
42843
43148
  "-NoLogo",
42844
43149
  "-NoProfile",
42845
43150
  "-NonInteractive",
@@ -42924,6 +43229,19 @@ async function performWindowsAtomicUpgrade(options) {
42924
43229
  pinStagedShims(stagedPrefix, portableNode, stagedCliEntry);
42925
43230
  validateStagedCli(portableNode, stagedCliEntry, targetVersion);
42926
43231
  hooks.log(`Validated staged CLI and portable Node 22 shims in ${stagedPrefix}`);
43232
+ const excludedPids = new Set([process.pid, ...options.excludePids ?? []].filter((n) => Number.isFinite(n) && n > 0));
43233
+ const preStop = await stopOwnedProcessesForPrefixes({
43234
+ prefixes: [layout.activePrefix, layout.stablePrefix],
43235
+ excludePids: Array.from(excludedPids),
43236
+ markers: Array.from(ADHDEV_OWNED_MARKERS),
43237
+ waitMs: 15e3,
43238
+ log: hooks.log
43239
+ });
43240
+ if (preStop.survivors.length > 0) {
43241
+ throw new Error(
43242
+ `Cannot activate ${versionName}: ${preStop.survivors.length} owned process(es) still running under the current prefix`
43243
+ );
43244
+ }
42927
43245
  activated = true;
42928
43246
  publishStableShimsAndPointer(layout, versionName);
42929
43247
  hooks.log(`Atomically activated ${versionName}`);
@@ -42933,7 +43251,7 @@ async function performWindowsAtomicUpgrade(options) {
42933
43251
  throw new Error("replacement daemon did not pass the health/version gate");
42934
43252
  }
42935
43253
  hooks.log(`Replacement daemon pid ${daemonPid} passed health for ${targetVersion}`);
42936
- hooks.cleanup(layout, stagedPrefix);
43254
+ await hooks.cleanup(layout, stagedPrefix);
42937
43255
  return { stagedPrefix, stagedCliEntry, daemonPid };
42938
43256
  } catch (error) {
42939
43257
  if (restarted?.pid) hooks.stopProcess(restarted.pid);
@@ -42951,7 +43269,7 @@ async function performWindowsAtomicUpgrade(options) {
42951
43269
  hooks.log("Failed to restart the previous daemon during rollback");
42952
43270
  }
42953
43271
  try {
42954
- hooks.cleanup(layout, layout.activePrefix);
43272
+ await hooks.cleanup(layout, layout.activePrefix);
42955
43273
  } catch {
42956
43274
  }
42957
43275
  throw error;
@@ -42963,7 +43281,7 @@ function createDefaultWindowsAtomicHooks(options) {
42963
43281
  const env = { ...options.env, ADHDEV_BOOTSTRAP: "1" };
42964
43282
  const pathKey = Object.keys(env).find((key2) => key2.toLowerCase() === "path") || "Path";
42965
43283
  env[pathKey] = `${path20.dirname(portableNode)};${env[pathKey] || ""}`;
42966
- (0, import_child_process5.execFileSync)(portableNode, [
43284
+ (0, import_child_process6.execFileSync)(portableNode, [
42967
43285
  options.npmCliPath,
42968
43286
  "install",
42969
43287
  "-g",
@@ -42983,7 +43301,7 @@ function createDefaultWindowsAtomicHooks(options) {
42983
43301
  restart: (portableNode, stagedCliEntry) => {
42984
43302
  const restartArgv = options.restartArgv.map((arg, index) => index === 0 ? stagedCliEntry : arg);
42985
43303
  if (restartArgv.length === 0) throw new Error("replacement daemon restart arguments are missing");
42986
- const child = (0, import_child_process5.spawn)(portableNode, restartArgv, {
43304
+ const child = (0, import_child_process6.spawn)(portableNode, restartArgv, {
42987
43305
  detached: true,
42988
43306
  stdio: "ignore",
42989
43307
  windowsHide: true,
@@ -42995,7 +43313,7 @@ function createDefaultWindowsAtomicHooks(options) {
42995
43313
  },
42996
43314
  restartOld: (portableNode) => {
42997
43315
  if (options.restartArgv.length === 0) return;
42998
- const child = (0, import_child_process5.spawn)(portableNode, options.restartArgv, {
43316
+ const child = (0, import_child_process6.spawn)(portableNode, options.restartArgv, {
42999
43317
  detached: true,
43000
43318
  stdio: "ignore",
43001
43319
  windowsHide: true,
@@ -43033,15 +43351,23 @@ function createDefaultWindowsAtomicHooks(options) {
43033
43351
  },
43034
43352
  stopProcess: (pid) => {
43035
43353
  try {
43036
- (0, import_child_process5.execFileSync)("taskkill", ["/PID", String(pid), "/T", "/F"], { stdio: "ignore", windowsHide: true });
43354
+ (0, import_child_process6.execFileSync)("taskkill", ["/PID", String(pid), "/T", "/F"], { stdio: "ignore", windowsHide: true });
43037
43355
  } catch {
43038
43356
  }
43039
43357
  },
43040
- cleanup: (layout, activePrefix) => boundedCleanupInactivePrefixes(layout, activePrefix, options.log),
43358
+ cleanup: async (layout, activePrefix) => cleanupInactivePrefixesWithGuard({
43359
+ layout,
43360
+ activePrefix,
43361
+ excludePids: [process.pid],
43362
+ markers: Array.from(ADHDEV_OWNED_MARKERS),
43363
+ waitMs: 15e3,
43364
+ log: options.log
43365
+ }),
43041
43366
  log: options.log
43042
43367
  };
43043
43368
  }
43044
- function boundedCleanupInactivePrefixes(layout, activePrefix, log) {
43369
+ async function cleanupInactivePrefixesWithGuard(options) {
43370
+ const { layout, activePrefix, log } = options;
43045
43371
  let candidates = [];
43046
43372
  try {
43047
43373
  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);
@@ -43049,19 +43375,41 @@ function boundedCleanupInactivePrefixes(layout, activePrefix, log) {
43049
43375
  return;
43050
43376
  }
43051
43377
  if (candidates.length === 0) return;
43052
- const escaped = candidates.map((candidate) => quotePowerShellLiteral(candidate)).join(",");
43053
- const script = `$ErrorActionPreference='Stop'; @(${escaped}) | ForEach-Object { if (Test-Path -LiteralPath $_) { Remove-Item -LiteralPath $_ -Recurse -Force } }`;
43054
- const encoded = Buffer.from(script, "utf16le").toString("base64");
43055
- const result = (0, import_child_process5.spawnSync)("powershell.exe", [
43056
- "-NoLogo",
43057
- "-NoProfile",
43058
- "-NonInteractive",
43059
- "-ExecutionPolicy",
43060
- "Bypass",
43061
- "-EncodedCommand",
43062
- encoded
43063
- ], { timeout: 5e3, windowsHide: true, stdio: "ignore" });
43064
- if (result.error || result.status !== 0) log("Bounded inactive-prefix cleanup was incomplete; future updates will retry");
43378
+ for (const candidate of candidates) {
43379
+ const stopResult = await stopOwnedProcessesForPrefixes({
43380
+ prefixes: [candidate],
43381
+ excludePids: options.excludePids,
43382
+ markers: options.markers,
43383
+ waitMs: options.waitMs ?? 15e3,
43384
+ log
43385
+ });
43386
+ if (stopResult.survivors.length > 0) {
43387
+ log?.(`Skipping cleanup of ${candidate}: ${stopResult.survivors.length} owned process(es) could not be stopped`);
43388
+ continue;
43389
+ }
43390
+ removeInactivePrefix(candidate, log);
43391
+ }
43392
+ }
43393
+ function removeInactivePrefix(target, log) {
43394
+ try {
43395
+ const escaped = quotePowerShellLiteral(target);
43396
+ const script = `if (Test-Path -LiteralPath ${escaped}) { Remove-Item -LiteralPath ${escaped} -Recurse -Force }`;
43397
+ const encoded = Buffer.from(script, "utf16le").toString("base64");
43398
+ const result = (0, import_child_process6.spawnSync)("powershell.exe", [
43399
+ "-NoLogo",
43400
+ "-NoProfile",
43401
+ "-NonInteractive",
43402
+ "-ExecutionPolicy",
43403
+ "Bypass",
43404
+ "-EncodedCommand",
43405
+ encoded
43406
+ ], { timeout: 5e3, windowsHide: true, stdio: "ignore" });
43407
+ if (result.error || result.status !== 0) {
43408
+ log?.(`Failed to remove inactive prefix ${target}: ${result.error?.message || `exit ${result.status}`}`);
43409
+ }
43410
+ } catch (error) {
43411
+ log?.(`Failed to remove inactive prefix ${target}: ${error?.message || String(error)}`);
43412
+ }
43065
43413
  }
43066
43414
 
43067
43415
  // src/commands/upgrade-helper.ts
@@ -43190,7 +43538,7 @@ function getNpmExecOptions(platform10 = process.platform) {
43190
43538
  }
43191
43539
  function execNpmCommandSync(args, options = {}, surface) {
43192
43540
  const execOptions = surface?.execOptions || getNpmExecOptions();
43193
- return (0, import_child_process6.execFileSync)(
43541
+ return (0, import_child_process7.execFileSync)(
43194
43542
  surface?.npmExecutable || "npm",
43195
43543
  [...surface?.npmArgsPrefix || [], ...args],
43196
43544
  {
@@ -43200,74 +43548,10 @@ function execNpmCommandSync(args, options = {}, surface) {
43200
43548
  }
43201
43549
  );
43202
43550
  }
43203
- function killPid(pid) {
43204
- try {
43205
- if (process.platform === "win32") {
43206
- (0, import_child_process6.execFileSync)("taskkill", ["/PID", String(pid), "/T", "/F"], { stdio: "ignore", windowsHide: true });
43207
- } else {
43208
- process.kill(pid, "SIGTERM");
43209
- }
43210
- return true;
43211
- } catch {
43212
- return false;
43213
- }
43214
- }
43215
- function getWindowsProcessCommandLine(pid) {
43216
- const pidFilter = `ProcessId=${pid}`;
43217
- try {
43218
- const psOut = (0, import_child_process6.execFileSync)("powershell.exe", [
43219
- "-NoProfile",
43220
- "-NonInteractive",
43221
- "-ExecutionPolicy",
43222
- "Bypass",
43223
- "-Command",
43224
- `(Get-CimInstance Win32_Process -Filter "${pidFilter}").CommandLine`
43225
- ], { encoding: "utf8", timeout: 5e3, stdio: ["ignore", "pipe", "ignore"], windowsHide: true }).trim();
43226
- if (psOut) return psOut;
43227
- } catch {
43228
- }
43229
- try {
43230
- const wmicOut = (0, import_child_process6.execFileSync)("wmic", [
43231
- "process",
43232
- "where",
43233
- pidFilter,
43234
- "get",
43235
- "CommandLine"
43236
- ], { encoding: "utf8", timeout: 3e3, stdio: ["ignore", "pipe", "ignore"], windowsHide: true }).trim();
43237
- if (wmicOut) return wmicOut;
43238
- } catch {
43239
- }
43240
- return null;
43241
- }
43242
- function getProcessCommandLine(pid) {
43243
- if (!Number.isFinite(pid) || pid <= 0) return null;
43244
- if (process.platform === "win32") return getWindowsProcessCommandLine(pid);
43245
- try {
43246
- const text = (0, import_child_process6.execFileSync)("ps", ["-o", "command=", "-p", String(pid)], {
43247
- encoding: "utf8",
43248
- timeout: 3e3,
43249
- stdio: ["ignore", "pipe", "ignore"]
43250
- }).trim();
43251
- return text || null;
43252
- } catch {
43253
- return null;
43254
- }
43255
- }
43256
43551
  function isManagedSessionHostPid(pid) {
43257
43552
  const commandLine = getProcessCommandLine(pid);
43258
43553
  return !!commandLine && /session-host-daemon/i.test(commandLine);
43259
43554
  }
43260
- async function waitForPidExit(pid, timeoutMs) {
43261
- const start = Date.now();
43262
- while (Date.now() - start < timeoutMs) {
43263
- try {
43264
- process.kill(pid, 0);
43265
- await new Promise((resolve27) => setTimeout(resolve27, 250));
43266
- } catch {
43267
- return;
43268
- }
43269
- }
43270
- }
43271
43555
  async function stopSessionHostProcesses(appName) {
43272
43556
  const pidFile = path21.join(os13.homedir(), ".adhdev", `${appName}-session-host.pid`);
43273
43557
  let killedPid = null;
@@ -43275,7 +43559,7 @@ async function stopSessionHostProcesses(appName) {
43275
43559
  if (fs15.existsSync(pidFile)) {
43276
43560
  const pid = Number.parseInt(fs15.readFileSync(pidFile, "utf8").trim(), 10);
43277
43561
  if (Number.isFinite(pid) && pid !== process.pid && isManagedSessionHostPid(pid)) {
43278
- if (killPid(pid)) killedPid = pid;
43562
+ if (killProcess(pid)) killedPid = pid;
43279
43563
  }
43280
43564
  }
43281
43565
  } catch {
@@ -43311,7 +43595,7 @@ function listForeignNativeAddonHolders(packageRoot) {
43311
43595
  ].join("\n");
43312
43596
  let out = "";
43313
43597
  try {
43314
- out = String((0, import_child_process6.execFileSync)("powershell.exe", [
43598
+ out = String((0, import_child_process7.execFileSync)("powershell.exe", [
43315
43599
  "-NoProfile",
43316
43600
  "-NonInteractive",
43317
43601
  "-ExecutionPolicy",
@@ -43343,7 +43627,7 @@ async function stopForeignNativeAddonHolders(packageRoot, options = {}) {
43343
43627
  appendUpgradeLog(
43344
43628
  `Foreign native-addon holder found: pid ${holder.pid}${holder.commandLine ? ` \u2014 ${holder.commandLine}` : ""}`
43345
43629
  );
43346
- const killed = killPid(holder.pid);
43630
+ const killed = killProcess(holder.pid);
43347
43631
  if (killed) {
43348
43632
  await waitForPidExit(holder.pid, 15e3);
43349
43633
  appendUpgradeLog(`Terminated foreign native-addon holder pid ${holder.pid}`);
@@ -43435,7 +43719,7 @@ function cleanupStaleGlobalInstallDirs(pkgName, surface) {
43435
43719
  }
43436
43720
  function spawnDetachedDaemonUpgradeHelper(payload) {
43437
43721
  const env = { ...process.env, [UPGRADE_HELPER_ENV]: JSON.stringify(payload) };
43438
- const child = (0, import_child_process7.spawn)(process.execPath, process.argv.slice(1), {
43722
+ const child = (0, import_child_process8.spawn)(process.execPath, process.argv.slice(1), {
43439
43723
  detached: true,
43440
43724
  stdio: "ignore",
43441
43725
  windowsHide: true,
@@ -43476,11 +43760,25 @@ async function runDaemonUpgradeHelper(payload) {
43476
43760
  throw new Error(`portable Node.js 22 npm CLI is missing: ${npmCliPath}`);
43477
43761
  }
43478
43762
  appendUpgradeLog(`Installer-managed pointer layout detected; active prefix will remain untouched: ${windowsInstallerLayout.activePrefix}`);
43763
+ const upgradePids = [process.pid, payload.parentPid].filter((n) => Number.isFinite(n) && n > 0);
43764
+ const preStop = await stopOwnedProcessesForPrefixes({
43765
+ prefixes: [windowsInstallerLayout.activePrefix, windowsInstallerLayout.stablePrefix],
43766
+ excludePids: upgradePids,
43767
+ markers: Array.from(ADHDEV_OWNED_MARKERS),
43768
+ waitMs: 15e3,
43769
+ log: appendUpgradeLog
43770
+ });
43771
+ if (preStop.survivors.length > 0) {
43772
+ throw new Error(
43773
+ `Cannot upgrade: owned processes still running under current prefix: ${preStop.survivors.map((s2) => s2.pid).join(", ")}`
43774
+ );
43775
+ }
43479
43776
  await performWindowsAtomicUpgrade({
43480
43777
  layout: windowsInstallerLayout,
43481
43778
  packageName: payload.packageName,
43482
43779
  targetVersion: payload.targetVersion,
43483
43780
  portableNode,
43781
+ excludePids: upgradePids,
43484
43782
  hooks: createDefaultWindowsAtomicHooks({
43485
43783
  packageName: payload.packageName,
43486
43784
  targetVersion: payload.targetVersion,
@@ -43506,7 +43804,7 @@ async function runDaemonUpgradeHelper(payload) {
43506
43804
  let installOutput = "";
43507
43805
  for (let attempt = 1; attempt <= maxInstallAttempts; attempt++) {
43508
43806
  try {
43509
- installOutput = String((0, import_child_process6.execFileSync)(
43807
+ installOutput = String((0, import_child_process7.execFileSync)(
43510
43808
  installCommand.command,
43511
43809
  installCommand.args,
43512
43810
  {
@@ -43559,7 +43857,7 @@ async function runDaemonUpgradeHelper(payload) {
43559
43857
  const env = { ...process.env };
43560
43858
  delete env[UPGRADE_HELPER_ENV];
43561
43859
  appendUpgradeLog(`Restarting daemon with args: ${restartArgv.join(" ")}`);
43562
- const child = (0, import_child_process7.spawn)(process.execPath, restartArgv, {
43860
+ const child = (0, import_child_process8.spawn)(process.execPath, restartArgv, {
43563
43861
  detached: true,
43564
43862
  stdio: "ignore",
43565
43863
  windowsHide: true,
@@ -44141,7 +44439,7 @@ var os22 = __toESM(require("os"));
44141
44439
  var path30 = __toESM(require("path"));
44142
44440
  var crypto6 = __toESM(require("crypto"));
44143
44441
  var import_fs16 = require("fs");
44144
- var import_child_process9 = require("child_process");
44442
+ var import_child_process10 = require("child_process");
44145
44443
  var import_chalk = __toESM(require("chalk"));
44146
44444
  init_provider_cli_adapter();
44147
44445
  init_cli_detector();
@@ -51241,7 +51539,7 @@ ${buttons.join("\n")}`;
51241
51539
  // src/providers/acp-provider-instance.ts
51242
51540
  var path29 = __toESM(require("path"));
51243
51541
  var import_stream = require("stream");
51244
- var import_child_process8 = require("child_process");
51542
+ var import_child_process9 = require("child_process");
51245
51543
  var import_sdk = require("@agentclientprotocol/sdk");
51246
51544
  init_contracts2();
51247
51545
  init_provider_input_support();
@@ -51765,7 +52063,7 @@ var AcpProviderInstance = class {
51765
52063
  this.errorMessage = null;
51766
52064
  this.errorReason = null;
51767
52065
  this.stderrBuffer = [];
51768
- this.process = (0, import_child_process8.spawn)(command, args, {
52066
+ this.process = (0, import_child_process9.spawn)(command, args, {
51769
52067
  cwd: this.workingDir,
51770
52068
  env,
51771
52069
  stdio: ["pipe", "pipe", "pipe"],
@@ -52515,7 +52813,7 @@ function commandExists(command) {
52515
52813
  return (0, import_fs16.existsSync)(expandExecutable(trimmed));
52516
52814
  }
52517
52815
  try {
52518
- (0, import_child_process9.execFileSync)(process.platform === "win32" ? "where" : "which", [trimmed], {
52816
+ (0, import_child_process10.execFileSync)(process.platform === "win32" ? "where" : "which", [trimmed], {
52519
52817
  stdio: "ignore",
52520
52818
  ...process.platform === "win32" ? { windowsHide: true } : {}
52521
52819
  });
@@ -53995,7 +54293,7 @@ var cliAgentHandlers = {
53995
54293
  };
53996
54294
 
53997
54295
  // src/launch.ts
53998
- var import_child_process10 = require("child_process");
54296
+ var import_child_process11 = require("child_process");
53999
54297
  var net = __toESM(require("net"));
54000
54298
  var os27 = __toESM(require("os"));
54001
54299
  var path38 = __toESM(require("path"));
@@ -57917,7 +58215,7 @@ function findMacAppProcessPids(psOutput, appPaths) {
57917
58215
  // src/launch.ts
57918
58216
  async function execQuiet(command, options = {}) {
57919
58217
  return new Promise((resolve27) => {
57920
- (0, import_child_process10.exec)(command, options, (error, stdout) => {
58218
+ (0, import_child_process11.exec)(command, options, (error, stdout) => {
57921
58219
  if (error) return resolve27("");
57922
58220
  resolve27(stdout.toString());
57923
58221
  });
@@ -58317,10 +58615,10 @@ async function launchMacOS(ide, port, workspace, newWindow) {
58317
58615
  const canUseAppLauncher = !!appName;
58318
58616
  const useAppLauncher = preferredMethod === "app" ? canUseAppLauncher : preferredMethod === "cli" ? false : !canUseCli && canUseAppLauncher;
58319
58617
  if (!useAppLauncher && ide.cliCommand) {
58320
- (0, import_child_process10.spawn)(ide.cliCommand, args, { detached: true, stdio: "ignore", windowsHide: true }).unref();
58618
+ (0, import_child_process11.spawn)(ide.cliCommand, args, { detached: true, stdio: "ignore", windowsHide: true }).unref();
58321
58619
  } else if (appName) {
58322
58620
  const openArgs = ["-a", appName, "--args", ...args];
58323
- (0, import_child_process10.spawn)("open", openArgs, { detached: true, stdio: "ignore" }).unref();
58621
+ (0, import_child_process11.spawn)("open", openArgs, { detached: true, stdio: "ignore" }).unref();
58324
58622
  } else {
58325
58623
  throw new Error(`No app identifier or CLI for ${ide.displayName}`);
58326
58624
  }
@@ -58346,7 +58644,7 @@ async function launchLinux(ide, port, workspace, newWindow) {
58346
58644
  const args = ["--remote-debugging-port=" + port];
58347
58645
  if (newWindow) args.push("--new-window");
58348
58646
  if (workspace) args.push(workspace);
58349
- (0, import_child_process10.spawn)(cli, args, { detached: true, stdio: "ignore", windowsHide: true }).unref();
58647
+ (0, import_child_process11.spawn)(cli, args, { detached: true, stdio: "ignore", windowsHide: true }).unref();
58350
58648
  }
58351
58649
  function getAvailableIdeIds() {
58352
58650
  return getProviderLoader().getAvailableIdeTypes();
@@ -58426,15 +58724,15 @@ var ideHandlers = {
58426
58724
  stop_ide: async (ctx, args) => {
58427
58725
  const ideType = args?.ideType;
58428
58726
  if (!ideType) throw new Error("ideType required");
58429
- const killProcess = args?.killProcess !== false;
58430
- await ctx.stopIde(ideType, killProcess);
58727
+ const killProcess2 = args?.killProcess !== false;
58728
+ await ctx.stopIde(ideType, killProcess2);
58431
58729
  try {
58432
58730
  const results = await detectIDEs(ctx.deps.providerLoader);
58433
58731
  ctx.deps.detectedIdes.value = results;
58434
58732
  ctx.deps.providerLoader.setIdeDetectionResults(results, true);
58435
58733
  } catch {
58436
58734
  }
58437
- return { success: true, ideType, stopped: true, processKilled: killProcess };
58735
+ return { success: true, ideType, stopped: true, processKilled: killProcess2 };
58438
58736
  },
58439
58737
  // ─── IDE restart ───
58440
58738
  restart_ide: async (ctx, args) => {
@@ -61715,18 +62013,18 @@ function resolveRefineryAutoPublishSubmoduleMainCommits(mesh, workspace) {
61715
62013
  return { enabled: false };
61716
62014
  }
61717
62015
  async function computeGitPatchId(cwd, fromRef, toRef, excludePaths = []) {
61718
- const { execFileSync: execFileSync11 } = await import("child_process");
62016
+ const { execFileSync: execFileSync12 } = await import("child_process");
61719
62017
  const diffArgs = ["diff", "--patch", "--full-index", fromRef, toRef];
61720
62018
  if (excludePaths.length > 0) {
61721
62019
  diffArgs.push("--", ".", ...excludePaths.map((path46) => `:(exclude)${path46}`));
61722
62020
  }
61723
- const diff = execFileSync11(GIT2, diffArgs, {
62021
+ const diff = execFileSync12(GIT2, diffArgs, {
61724
62022
  cwd,
61725
62023
  encoding: "utf8",
61726
62024
  maxBuffer: REFINE_PATCH_EQUIVALENCE_OUTPUT_LIMIT_BYTES
61727
62025
  });
61728
62026
  if (!diff.trim()) return "";
61729
- const patchId = execFileSync11(GIT2, ["patch-id", "--stable"], {
62027
+ const patchId = execFileSync12(GIT2, ["patch-id", "--stable"], {
61730
62028
  cwd,
61731
62029
  input: diff,
61732
62030
  encoding: "utf8",
@@ -61737,8 +62035,8 @@ async function computeGitPatchId(cwd, fromRef, toRef, excludePaths = []) {
61737
62035
  async function runMeshRefinePatchEquivalenceGate(repoRoot, baseHead, branchHead) {
61738
62036
  const startedAt = Date.now();
61739
62037
  try {
61740
- const { execFileSync: execFileSync11 } = await import("child_process");
61741
- const git = (args) => execFileSync11(GIT2, args, {
62038
+ const { execFileSync: execFileSync12 } = await import("child_process");
62039
+ const git = (args) => execFileSync12(GIT2, args, {
61742
62040
  cwd: repoRoot,
61743
62041
  encoding: "utf8",
61744
62042
  maxBuffer: REFINE_PATCH_EQUIVALENCE_OUTPUT_LIMIT_BYTES
@@ -61973,8 +62271,8 @@ function execGitOk(cwd, args) {
61973
62271
  async function checkWorktreeChangesPatchEquivalentInRef(repoRoot, ref, worktreeHead) {
61974
62272
  const startedAt = Date.now();
61975
62273
  try {
61976
- const { execFileSync: execFileSync11 } = await import("child_process");
61977
- const git = (gitArgs) => execFileSync11(GIT2, gitArgs, {
62274
+ const { execFileSync: execFileSync12 } = await import("child_process");
62275
+ const git = (gitArgs) => execFileSync12(GIT2, gitArgs, {
61978
62276
  cwd: repoRoot,
61979
62277
  encoding: "utf8",
61980
62278
  maxBuffer: REFINE_PATCH_EQUIVALENCE_OUTPUT_LIMIT_BYTES
@@ -62037,8 +62335,8 @@ ${mergeTreeErr?.stderr || ""}`;
62037
62335
  async function runMeshRefineEffectiveDiffGate(repoRoot, baseHead, branchHead) {
62038
62336
  const startedAt = Date.now();
62039
62337
  try {
62040
- const { execFileSync: execFileSync11 } = await import("child_process");
62041
- const git = (args, opts) => execFileSync11(GIT2, args, {
62338
+ const { execFileSync: execFileSync12 } = await import("child_process");
62339
+ const git = (args, opts) => execFileSync12(GIT2, args, {
62042
62340
  cwd: opts?.cwd || repoRoot,
62043
62341
  encoding: "utf8",
62044
62342
  maxBuffer: REFINE_PATCH_EQUIVALENCE_OUTPUT_LIMIT_BYTES
@@ -66147,7 +66445,7 @@ var DaemonCommandRouter = class {
66147
66445
  /**
66148
66446
  * IDE stop: CDP disconnect + InstanceManager cleanup + optionally kill OS process
66149
66447
  */
66150
- async stopIde(ideType, killProcess = false) {
66448
+ async stopIde(ideType, killProcess2 = false) {
66151
66449
  const cdpKeysToRemove = [];
66152
66450
  for (const key2 of this.deps.cdpManagers.keys()) {
66153
66451
  if (key2 === ideType || key2.startsWith(`${ideType}_`)) {
@@ -66185,7 +66483,7 @@ var DaemonCommandRouter = class {
66185
66483
  LOG.info("StopIDE", `Instance removed: ${instanceKey}`);
66186
66484
  }
66187
66485
  }
66188
- if (killProcess) {
66486
+ if (killProcess2) {
66189
66487
  const running = await isIdeRunning(ideType);
66190
66488
  if (running) {
66191
66489
  LOG.info("StopIDE", `Killing IDE process: ${ideType}`);
@@ -66200,7 +66498,7 @@ var DaemonCommandRouter = class {
66200
66498
  }
66201
66499
  }
66202
66500
  this.deps.onStatusChange?.();
66203
- LOG.info("StopIDE", `IDE stopped: ${ideType} (processKill=${killProcess})`);
66501
+ LOG.info("StopIDE", `IDE stopped: ${ideType} (processKill=${killProcess2})`);
66204
66502
  }
66205
66503
  };
66206
66504
 
@@ -67910,7 +68208,7 @@ var fs38 = __toESM(require("fs"));
67910
68208
  var path40 = __toESM(require("path"));
67911
68209
  var os29 = __toESM(require("os"));
67912
68210
  var import_os5 = require("os");
67913
- var import_child_process11 = require("child_process");
68211
+ var import_child_process12 = require("child_process");
67914
68212
  var ARCHIVE_PATH = path40.join(os29.homedir(), ".adhdev", "version-history.json");
67915
68213
  var MAX_ENTRIES_PER_PROVIDER = 20;
67916
68214
  var VersionArchive = class {
@@ -67966,7 +68264,7 @@ var VersionArchive = class {
67966
68264
  };
67967
68265
  async function runCommand(cmd, timeout = 1e4) {
67968
68266
  return new Promise((resolve27) => {
67969
- (0, import_child_process11.exec)(cmd, {
68267
+ (0, import_child_process12.exec)(cmd, {
67970
68268
  encoding: "utf-8",
67971
68269
  timeout
67972
68270
  }, (error, stdout) => {
@@ -73135,8 +73433,10 @@ var SessionHostRuntimeTransport = class {
73135
73433
  return;
73136
73434
  }
73137
73435
  if (event.type === "session_exit") {
73436
+ const exitCode = typeof event.exitCode === "number" ? event.exitCode : null;
73437
+ const signal = typeof event.signal === "number" ? event.signal : null;
73138
73438
  for (const callback of this.exitCallbacks) {
73139
- callback({ exitCode: event.exitCode ?? 0 });
73439
+ callback({ exitCode, signal });
73140
73440
  }
73141
73441
  void this.closeClient(false);
73142
73442
  }
@@ -73523,11 +73823,12 @@ async function listHostedCliRuntimes(endpoint) {
73523
73823
  }
73524
73824
 
73525
73825
  // src/session-host/managed-host.ts
73526
- var import_child_process12 = require("child_process");
73826
+ var import_child_process13 = require("child_process");
73527
73827
  var fs43 = __toESM(require("fs"));
73528
73828
  var os31 = __toESM(require("os"));
73529
73829
  var path45 = __toESM(require("path"));
73530
73830
  var import_session_host_core13 = require("@adhdev/session-host-core");
73831
+ init_logger();
73531
73832
  init_runtime_defaults();
73532
73833
  function createManagedSessionHost(options) {
73533
73834
  const appName = options.appName;
@@ -73551,6 +73852,14 @@ function createManagedSessionHost(options) {
73551
73852
  }
73552
73853
  return require.resolve("@adhdev/session-host-daemon");
73553
73854
  }
73855
+ function pathsEquivalent(left, right) {
73856
+ return path45.resolve(left).toLowerCase() === path45.resolve(right).toLowerCase();
73857
+ }
73858
+ function getRunningSessionHostScriptPath(pid) {
73859
+ const commandLine = getProcessCommandLine(pid);
73860
+ if (!commandLine || !/session-host-daemon/i.test(commandLine)) return null;
73861
+ return parseNodeScriptPath(commandLine);
73862
+ }
73554
73863
  function getPidFile() {
73555
73864
  return path45.join(os31.homedir(), ".adhdev", `${appName}-session-host.pid`);
73556
73865
  }
@@ -73564,12 +73873,12 @@ function createManagedSessionHost(options) {
73564
73873
  return null;
73565
73874
  }
73566
73875
  }
73567
- function killPid2(pid) {
73876
+ function killPid(pid) {
73568
73877
  try {
73569
73878
  if (process.platform === "win32") {
73570
73879
  const spawnOpts = { stdio: "ignore" };
73571
73880
  if (options.killWindowsHide) spawnOpts.windowsHide = true;
73572
- (0, import_child_process12.execFileSync)("taskkill", ["/PID", String(pid), "/T", "/F"], spawnOpts);
73881
+ (0, import_child_process13.execFileSync)("taskkill", ["/PID", String(pid), "/T", "/F"], spawnOpts);
73573
73882
  } else {
73574
73883
  process.kill(pid, "SIGTERM");
73575
73884
  }
@@ -73588,7 +73897,7 @@ function createManagedSessionHost(options) {
73588
73897
  logFd = fs43.openSync(path45.join(logDir, "session-host.log"), "a");
73589
73898
  stdio = ["ignore", logFd, logFd];
73590
73899
  }
73591
- const child = (0, import_child_process12.spawn)(process.execPath, [entry], {
73900
+ const child = (0, import_child_process13.spawn)(process.execPath, [entry], {
73592
73901
  detached: true,
73593
73902
  stdio,
73594
73903
  windowsHide: true,
@@ -73609,7 +73918,7 @@ function createManagedSessionHost(options) {
73609
73918
  if (fs43.existsSync(pidFile)) {
73610
73919
  const pid = Number.parseInt(fs43.readFileSync(pidFile, "utf8").trim(), 10);
73611
73920
  if (Number.isFinite(pid) && pid !== process.pid && isManagedPid(pid)) {
73612
- stopped = killPid2(pid) || stopped;
73921
+ stopped = killPid(pid) || stopped;
73613
73922
  }
73614
73923
  }
73615
73924
  } catch {
@@ -73626,6 +73935,20 @@ function createManagedSessionHost(options) {
73626
73935
  }
73627
73936
  async function ensureReady() {
73628
73937
  options.beforeEnsureReady?.();
73938
+ if (process.platform === "win32") {
73939
+ const existingPid = getPid();
73940
+ if (existingPid !== null) {
73941
+ const runningPath = getRunningSessionHostScriptPath(existingPid);
73942
+ const currentEntry = resolveEntry();
73943
+ if (runningPath && !pathsEquivalent(runningPath, currentEntry)) {
73944
+ LOG.warn(
73945
+ "SessionHost",
73946
+ `Detected stale host pid ${existingPid} running from ${runningPath}; restarting from ${currentEntry}`
73947
+ );
73948
+ stopManagedSessionHostProcess();
73949
+ }
73950
+ }
73951
+ }
73629
73952
  try {
73630
73953
  return await ensureSessionHostReady({
73631
73954
  appName,
@@ -73654,7 +73977,7 @@ function createManagedSessionHost(options) {
73654
73977
  getPid,
73655
73978
  buildEnv,
73656
73979
  resolveEntry,
73657
- killPid: killPid2,
73980
+ killPid,
73658
73981
  spawnHost,
73659
73982
  stopManagedSessionHostProcess,
73660
73983
  ensureReady,
@@ -73676,7 +73999,7 @@ function shouldAutoRestoreHostedSessionsOnStartup(env = process.env) {
73676
73999
  }
73677
74000
 
73678
74001
  // src/installer.ts
73679
- var import_child_process13 = require("child_process");
74002
+ var import_child_process14 = require("child_process");
73680
74003
  var import_util3 = require("util");
73681
74004
  var EXTENSION_CATALOG = [
73682
74005
  // AI Agent extensions
@@ -73764,7 +74087,7 @@ var EXTENSION_CATALOG = [
73764
74087
  apiKeyName: "OpenAI/Anthropic API key"
73765
74088
  }
73766
74089
  ];
73767
- var execAsync4 = (0, import_util3.promisify)(import_child_process13.exec);
74090
+ var execAsync4 = (0, import_util3.promisify)(import_child_process14.exec);
73768
74091
  async function isExtensionInstalled(ide, marketplaceId) {
73769
74092
  if (!ide.cliCommand) return false;
73770
74093
  try {
@@ -73808,7 +74131,7 @@ async function installExtension(ide, extension) {
73808
74131
  fs44.writeFileSync(vsixPath, buffer);
73809
74132
  return new Promise((resolve27) => {
73810
74133
  const cmd = `"${ide.cliCommand}" --install-extension "${vsixPath}" --force`;
73811
- (0, import_child_process13.exec)(cmd, { timeout: 6e4 }, (error, _stdout, stderr) => {
74134
+ (0, import_child_process14.exec)(cmd, { timeout: 6e4 }, (error, _stdout, stderr) => {
73812
74135
  resolve27({
73813
74136
  extensionId: extension.id,
73814
74137
  marketplaceId: extension.marketplaceId,
@@ -73824,7 +74147,7 @@ async function installExtension(ide, extension) {
73824
74147
  }
73825
74148
  return new Promise((resolve27) => {
73826
74149
  const cmd = `"${ide.cliCommand}" --install-extension ${extension.marketplaceId} --force`;
73827
- (0, import_child_process13.exec)(cmd, { timeout: 6e4 }, (error, stdout, stderr) => {
74150
+ (0, import_child_process14.exec)(cmd, { timeout: 6e4 }, (error, stdout, stderr) => {
73828
74151
  if (error) {
73829
74152
  resolve27({
73830
74153
  extensionId: extension.id,
@@ -73861,7 +74184,7 @@ function launchIDE(ide, workspacePath) {
73861
74184
  if (!ide.cliCommand) return false;
73862
74185
  try {
73863
74186
  const args = workspacePath ? `"${workspacePath}"` : "";
73864
- (0, import_child_process13.exec)(`"${ide.cliCommand}" ${args}`, { timeout: 1e4 });
74187
+ (0, import_child_process14.exec)(`"${ide.cliCommand}" ${args}`, { timeout: 1e4 });
73865
74188
  return true;
73866
74189
  } catch {
73867
74190
  return false;