adhdev 0.9.33 → 0.9.34

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/cli/index.js CHANGED
@@ -12369,13 +12369,14 @@ function sliceFromOffset(text, start) {
12369
12369
  function hydrateCliParsedMessages(parsedMessages, options) {
12370
12370
  const { committedMessages, scope, lastOutputAt } = options;
12371
12371
  const referenceMessages = [...committedMessages];
12372
+ const referenceComparables = referenceMessages.map((message) => normalizeComparableMessageContent(message?.content || ""));
12372
12373
  const usedReferenceIndexes = /* @__PURE__ */ new Set();
12373
12374
  const now = options.now ?? Date.now();
12374
12375
  const findReferenceTimestamp = (role, content, parsedIndex) => {
12375
12376
  const normalizedContent = normalizeComparableMessageContent(content);
12376
12377
  if (!normalizedContent) return void 0;
12377
12378
  const sameIndex = referenceMessages[parsedIndex];
12378
- if (sameIndex && !usedReferenceIndexes.has(parsedIndex) && sameIndex.role === role && normalizeComparableMessageContent(sameIndex.content) === normalizedContent && typeof sameIndex.timestamp === "number" && Number.isFinite(sameIndex.timestamp)) {
12379
+ if (sameIndex && !usedReferenceIndexes.has(parsedIndex) && sameIndex.role === role && referenceComparables[parsedIndex] === normalizedContent && typeof sameIndex.timestamp === "number" && Number.isFinite(sameIndex.timestamp)) {
12379
12380
  usedReferenceIndexes.add(parsedIndex);
12380
12381
  return sameIndex.timestamp;
12381
12382
  }
@@ -12383,7 +12384,7 @@ function hydrateCliParsedMessages(parsedMessages, options) {
12383
12384
  if (usedReferenceIndexes.has(i)) continue;
12384
12385
  const candidate = referenceMessages[i];
12385
12386
  if (!candidate || candidate.role !== role) continue;
12386
- const candidateContent = normalizeComparableMessageContent(candidate.content);
12387
+ const candidateContent = referenceComparables[i];
12387
12388
  if (!candidateContent) continue;
12388
12389
  const exactMatch = candidateContent === normalizedContent;
12389
12390
  const fuzzyMatch = candidateContent.includes(normalizedContent) || normalizedContent.includes(candidateContent);
@@ -13475,7 +13476,7 @@ var init_provider_cli_adapter = __esm({
13475
13476
  return;
13476
13477
  }
13477
13478
  if (this.currentTurnScope && !lastParsedAssistant) {
13478
- LOG.info(
13479
+ LOG.debug(
13479
13480
  "CLI",
13480
13481
  `[${this.cliType}] Settled without assistant: prompt=${JSON.stringify(this.currentTurnScope.prompt).slice(0, 140)} responseBuffer=${JSON.stringify(summarizeCliTraceText(this.responseBuffer, 220)).slice(0, 260)} screen=${JSON.stringify(summarizeCliTraceText(screenText, 220)).slice(0, 260)} providerDir=${this.providerResolutionMeta.providerDir || "-"} scriptDir=${this.providerResolutionMeta.scriptDir || "-"}`
13481
13482
  );
@@ -14291,9 +14292,43 @@ var init_provider_cli_adapter = __esm({
14291
14292
  }
14292
14293
  armResponseTimeout() {
14293
14294
  if (this.responseTimeout) clearTimeout(this.responseTimeout);
14295
+ const timeoutMs = this.timeouts.maxResponse;
14296
+ if (!Number.isFinite(timeoutMs) || timeoutMs <= 0) {
14297
+ this.responseTimeout = null;
14298
+ return;
14299
+ }
14294
14300
  this.responseTimeout = setTimeout(() => {
14295
- if (this.isWaitingForResponse) this.finishResponse();
14296
- }, this.timeouts.maxResponse);
14301
+ this.responseTimeout = null;
14302
+ if (!this.isWaitingForResponse) return;
14303
+ const detectedStatusBeforeEval = this.runDetectStatus(this.recentOutputBuffer);
14304
+ this.recordTrace("response_timeout_check", {
14305
+ timeoutMs,
14306
+ detectedStatus: detectedStatusBeforeEval,
14307
+ currentStatus: this.currentStatus,
14308
+ isWaitingForResponse: this.isWaitingForResponse,
14309
+ hasActionableApproval: this.hasActionableApproval(),
14310
+ ...buildCliTraceParseSnapshot({
14311
+ accumulatedBuffer: this.accumulatedBuffer,
14312
+ accumulatedRawBuffer: this.accumulatedRawBuffer,
14313
+ responseBuffer: this.responseBuffer,
14314
+ partialResponse: this.responseBuffer,
14315
+ scope: this.currentTurnScope
14316
+ })
14317
+ });
14318
+ this.settledBuffer = this.recentOutputBuffer;
14319
+ this.evaluateSettled();
14320
+ if (this.isWaitingForResponse && !this.hasActionableApproval()) {
14321
+ const detectedStatusAfterEval = this.runDetectStatus(this.recentOutputBuffer);
14322
+ this.recordTrace("response_timeout_kept_open", {
14323
+ timeoutMs,
14324
+ detectedStatusBeforeEval,
14325
+ detectedStatusAfterEval,
14326
+ currentStatus: this.currentStatus,
14327
+ isWaitingForResponse: this.isWaitingForResponse
14328
+ });
14329
+ this.armResponseTimeout();
14330
+ }
14331
+ }, timeoutMs);
14297
14332
  }
14298
14333
  writeSubmitKeyForRetry(mode) {
14299
14334
  void this.writeToPty(this.sendKey).catch((error48) => {
@@ -15184,6 +15219,20 @@ var init_cli_provider_instance = __esm({
15184
15219
  getPresentationMode() {
15185
15220
  return this.presentationMode;
15186
15221
  }
15222
+ getHotChatSessionState() {
15223
+ const adapterStatus = this.adapter.getStatus();
15224
+ const autoApproveActive = adapterStatus.status === "waiting_approval" && this.shouldAutoApprove();
15225
+ const visibleStatus = autoApproveActive ? "generating" : adapterStatus.status;
15226
+ const runtime = this.adapter.getRuntimeMetadata();
15227
+ return {
15228
+ id: this.instanceId,
15229
+ status: visibleStatus,
15230
+ runtimeLifecycle: runtime?.lifecycle ?? null,
15231
+ runtimeSurfaceKind: runtime?.surfaceKind,
15232
+ runtimeRestoredFromStorage: runtime?.restoredFromStorage === true,
15233
+ runtimeRecoveryState: runtime?.recoveryState ?? null
15234
+ };
15235
+ }
15187
15236
  updateSettings(newSettings) {
15188
15237
  this.settings = { ...newSettings };
15189
15238
  this.adapter.updateRuntimeSettings?.(this.settings);
@@ -15339,6 +15388,15 @@ var init_cli_provider_instance = __esm({
15339
15388
  this.completedDebouncePending = { chatTitle, duration: duration3, timestamp: now };
15340
15389
  this.completedDebounceTimer = setTimeout(() => {
15341
15390
  if (this.completedDebouncePending) {
15391
+ const latestStatus = this.adapter.getStatus();
15392
+ const latestAutoApproveActive = latestStatus.status === "waiting_approval" && this.shouldAutoApprove();
15393
+ const latestVisibleStatus = latestAutoApproveActive ? "generating" : latestStatus.status;
15394
+ if (latestVisibleStatus !== "idle") {
15395
+ LOG.info("CLI", `[${this.type}] cancelled pending completed (resumed ${latestVisibleStatus})`);
15396
+ this.completedDebouncePending = null;
15397
+ this.completedDebounceTimer = null;
15398
+ return;
15399
+ }
15342
15400
  LOG.info("CLI", `[${this.type}] completed in ${this.completedDebouncePending.duration}s`);
15343
15401
  this.pushEvent({ event: "agent:generating_completed", ...this.completedDebouncePending });
15344
15402
  this.completedDebouncePending = null;
@@ -38458,6 +38516,51 @@ function killPid(pid) {
38458
38516
  return false;
38459
38517
  }
38460
38518
  }
38519
+ function getWindowsProcessCommandLine(pid) {
38520
+ const pidFilter = `ProcessId=${pid}`;
38521
+ try {
38522
+ const psOut = (0, import_child_process8.execFileSync)("powershell.exe", [
38523
+ "-NoProfile",
38524
+ "-NonInteractive",
38525
+ "-ExecutionPolicy",
38526
+ "Bypass",
38527
+ "-Command",
38528
+ `(Get-CimInstance Win32_Process -Filter "${pidFilter}").CommandLine`
38529
+ ], { encoding: "utf8", timeout: 5e3, stdio: ["ignore", "pipe", "ignore"] }).trim();
38530
+ if (psOut) return psOut;
38531
+ } catch {
38532
+ }
38533
+ try {
38534
+ const wmicOut = (0, import_child_process8.execFileSync)("wmic", [
38535
+ "process",
38536
+ "where",
38537
+ pidFilter,
38538
+ "get",
38539
+ "CommandLine"
38540
+ ], { encoding: "utf8", timeout: 3e3, stdio: ["ignore", "pipe", "ignore"] }).trim();
38541
+ if (wmicOut) return wmicOut;
38542
+ } catch {
38543
+ }
38544
+ return null;
38545
+ }
38546
+ function getProcessCommandLine(pid) {
38547
+ if (!Number.isFinite(pid) || pid <= 0) return null;
38548
+ if (process.platform === "win32") return getWindowsProcessCommandLine(pid);
38549
+ try {
38550
+ const text = (0, import_child_process8.execFileSync)("ps", ["-o", "command=", "-p", String(pid)], {
38551
+ encoding: "utf8",
38552
+ timeout: 3e3,
38553
+ stdio: ["ignore", "pipe", "ignore"]
38554
+ }).trim();
38555
+ return text || null;
38556
+ } catch {
38557
+ return null;
38558
+ }
38559
+ }
38560
+ function isManagedSessionHostPid(pid) {
38561
+ const commandLine = getProcessCommandLine(pid);
38562
+ return !!commandLine && /session-host-daemon/i.test(commandLine);
38563
+ }
38461
38564
  async function waitForPidExit(pid, timeoutMs) {
38462
38565
  const start = Date.now();
38463
38566
  while (Date.now() - start < timeoutMs) {
@@ -38474,7 +38577,7 @@ function stopSessionHostProcesses(appName) {
38474
38577
  try {
38475
38578
  if (fs8.existsSync(pidFile)) {
38476
38579
  const pid = Number.parseInt(fs8.readFileSync(pidFile, "utf8").trim(), 10);
38477
- if (Number.isFinite(pid)) {
38580
+ if (Number.isFinite(pid) && pid !== process.pid && isManagedSessionHostPid(pid)) {
38478
38581
  killPid(pid);
38479
38582
  }
38480
38583
  }
@@ -38485,18 +38588,6 @@ function stopSessionHostProcesses(appName) {
38485
38588
  } catch {
38486
38589
  }
38487
38590
  }
38488
- if (process.platform !== "win32") {
38489
- try {
38490
- const raw = (0, import_child_process8.execFileSync)("pgrep", ["-f", "session-host-daemon"], { encoding: "utf8" }).trim();
38491
- for (const line of raw.split("\n")) {
38492
- const pid = Number.parseInt(line.trim(), 10);
38493
- if (Number.isFinite(pid)) {
38494
- killPid(pid);
38495
- }
38496
- }
38497
- } catch {
38498
- }
38499
- }
38500
38591
  }
38501
38592
  function removeDaemonPidFile() {
38502
38593
  const pidFile = path17.join(os20.homedir(), ".adhdev", "daemon.pid");
@@ -40838,6 +40929,20 @@ var init_forward = __esm({
40838
40929
  });
40839
40930
 
40840
40931
  // ../../oss/packages/daemon-core/src/providers/provider-instance-manager.ts
40932
+ function projectHotChatSessionStatesFromProviderState(state) {
40933
+ const project = (item) => ({
40934
+ id: item.instanceId,
40935
+ status: item.activeChat?.status || item.status,
40936
+ runtimeLifecycle: item.runtime?.lifecycle ?? null,
40937
+ runtimeSurfaceKind: item.runtime?.surfaceKind,
40938
+ runtimeRestoredFromStorage: item.runtime?.restoredFromStorage === true,
40939
+ runtimeRecoveryState: item.runtime?.recoveryState ?? null
40940
+ });
40941
+ if (state.category === "ide") {
40942
+ return [project(state), ...state.extensions.map(project)];
40943
+ }
40944
+ return [project(state)];
40945
+ }
40841
40946
  var ProviderInstanceManager;
40842
40947
  var init_provider_instance_manager = __esm({
40843
40948
  "../../oss/packages/daemon-core/src/providers/provider-instance-manager.ts"() {
@@ -40938,6 +41043,27 @@ var init_provider_instance_manager = __esm({
40938
41043
  }
40939
41044
  return states;
40940
41045
  }
41046
+ collectHotChatSessionStates() {
41047
+ const sessions = [];
41048
+ for (const [id, instance] of this.instances) {
41049
+ try {
41050
+ const projected = instance.getHotChatSessionState?.();
41051
+ if (Array.isArray(projected)) {
41052
+ sessions.push(...projected.filter((session) => !!session?.id));
41053
+ continue;
41054
+ }
41055
+ if (projected?.id) {
41056
+ sessions.push(projected);
41057
+ continue;
41058
+ }
41059
+ const state = instance.getState();
41060
+ sessions.push(...projectHotChatSessionStatesFromProviderState(state));
41061
+ } catch (e) {
41062
+ LOG.warn("InstanceMgr", `[InstanceManager] Failed to collect hot chat metadata from ${id}: ${e.message}`);
41063
+ }
41064
+ }
41065
+ return sessions;
41066
+ }
40941
41067
  /**
40942
41068
  * Per-category status collect
40943
41069
  */
@@ -79678,19 +79804,20 @@ var init_screenshot_sender = __esm({
79678
79804
  }
79679
79805
  return sentAny;
79680
79806
  }
79681
- sendScreenshot(peers, base64Data) {
79807
+ sendScreenshot(peers, base64Data, targetSessionId) {
79682
79808
  const buffer = Buffer.from(base64Data, "base64");
79683
- return this.sendScreenshotBuffer(peers, buffer);
79809
+ return this.sendScreenshotBuffer(peers, buffer, targetSessionId);
79684
79810
  }
79685
79811
  /** Send screenshot as raw Buffer (no base64 conversion overhead) */
79686
- sendScreenshotBuffer(peers, buffer) {
79812
+ sendScreenshotBuffer(peers, buffer, targetSessionId) {
79687
79813
  let sentAny = false;
79688
79814
  let debugOnce = !this._ssDebugDone;
79689
79815
  for (const [pid, peer] of peers.entries()) {
79690
79816
  if (debugOnce) {
79691
- logDebug(`sendScreenshot peer=${pid}: state=${peer.state}, hasCh=${!!peer.dataChannel}, ssActive=${peer.screenshotActive}, chOpen=${peer.dataChannel?.isOpen?.() ?? "N/A"}, bufSize=${buffer.length}`);
79817
+ logDebug(`sendScreenshot peer=${pid}: state=${peer.state}, hasCh=${!!peer.dataChannel}, ssActive=${peer.screenshotActive}, target=${peer.screenshotTargetSessionId || "none"}, chOpen=${peer.dataChannel?.isOpen?.() ?? "N/A"}, bufSize=${buffer.length}`);
79692
79818
  }
79693
79819
  if (peer.state !== "connected" || !peer.dataChannel || !peer.screenshotActive) continue;
79820
+ if (targetSessionId && peer.screenshotTargetSessionId !== targetSessionId) continue;
79694
79821
  try {
79695
79822
  if (!peer.dataChannel.isOpen()) continue;
79696
79823
  const header = Buffer.alloc(4);
@@ -79722,18 +79849,32 @@ async function initiateConnection(deps, peerId, sharePermission) {
79722
79849
  log("Cannot initiate \u2014 node-datachannel not available");
79723
79850
  return;
79724
79851
  }
79852
+ const pid = peerId || `legacy_${Date.now()}`;
79853
+ const existing = deps.peers.get(pid);
79854
+ if (existing?.state === "connected") {
79855
+ log(`initiateconnection() ignored for peer ${pid} \u2014 already connected`);
79856
+ return;
79857
+ }
79858
+ if (existing?.state === "connecting") {
79859
+ log(`initiateconnection() ignored for peer ${pid} \u2014 connection already in progress`);
79860
+ return;
79861
+ }
79725
79862
  const limits = deps.serverConn.getPlanLimits();
79726
79863
  if (limits && limits.maxP2PConnections !== -1) {
79727
79864
  let connectedCount = 0;
79865
+ let reservedCount = 0;
79728
79866
  for (const peer of deps.peers.values()) {
79729
79867
  if (peer.state === "connected") connectedCount++;
79868
+ if (peer.state === "connected" || peer.state === "connecting") reservedCount++;
79730
79869
  }
79731
- if (connectedCount >= limits.maxP2PConnections) {
79870
+ if (reservedCount >= limits.maxP2PConnections) {
79732
79871
  let oldestPeer = null;
79733
- for (const [pid2, peer] of deps.peers) {
79734
- if (peer.state === "connected") {
79735
- if (!oldestPeer || peer.connectedAt < oldestPeer.at) {
79736
- oldestPeer = { id: pid2, at: peer.connectedAt };
79872
+ if (connectedCount >= limits.maxP2PConnections) {
79873
+ for (const [peerKey, peer] of deps.peers) {
79874
+ if (peer.state === "connected") {
79875
+ if (!oldestPeer || peer.connectedAt < oldestPeer.at) {
79876
+ oldestPeer = { id: peerKey, at: peer.connectedAt };
79877
+ }
79737
79878
  }
79738
79879
  }
79739
79880
  }
@@ -79751,19 +79892,12 @@ async function initiateConnection(deps, peerId, sharePermission) {
79751
79892
  }
79752
79893
  }
79753
79894
  disconnectPeer(deps.peers, oldestPeer.id, deps.notifyStateChange);
79895
+ } else {
79896
+ log(`P2P limit reached (${reservedCount}/${limits.maxP2PConnections}) with reserved connecting slot(s). Rejecting peer ${pid.slice(0, 12)}\u2026`);
79897
+ return;
79754
79898
  }
79755
79899
  }
79756
79900
  }
79757
- const pid = peerId || `legacy_${Date.now()}`;
79758
- const existing = deps.peers.get(pid);
79759
- if (existing?.state === "connected") {
79760
- log(`initiateconnection() ignored for peer ${pid} \u2014 already connected`);
79761
- return;
79762
- }
79763
- if (existing?.state === "connecting") {
79764
- log(`initiateconnection() ignored for peer ${pid} \u2014 connection already in progress`);
79765
- return;
79766
- }
79767
79901
  log(`initiateconnection() for peer ${pid}...`);
79768
79902
  const mod = deps.nodeDatachannel;
79769
79903
  const PeerConnectionCtor = mod.PeerConnection || mod.default?.PeerConnection || mod.default || mod;
@@ -80122,14 +80256,19 @@ var init_daemon_p2p = __esm({
80122
80256
  }
80123
80257
  return false;
80124
80258
  }
80125
- /** Get the target session for the currently active screenshot request */
80126
- get screenshotTargetSessionId() {
80259
+ /** Get all target sessions for active screenshot requests */
80260
+ get screenshotTargetSessionIds() {
80261
+ const targets = /* @__PURE__ */ new Set();
80127
80262
  for (const peer of this.peers.values()) {
80128
80263
  if (peer.screenshotActive && peer.state === "connected" && peer.screenshotTargetSessionId) {
80129
- return peer.screenshotTargetSessionId;
80264
+ targets.add(peer.screenshotTargetSessionId);
80130
80265
  }
80131
80266
  }
80132
- return void 0;
80267
+ return Array.from(targets);
80268
+ }
80269
+ /** Get the target session for the currently active screenshot request */
80270
+ get screenshotTargetSessionId() {
80271
+ return this.screenshotTargetSessionIds[0];
80133
80272
  }
80134
80273
  constructor(serverConn) {
80135
80274
  this.serverConn = serverConn;
@@ -80238,6 +80377,14 @@ ${e?.stack || ""}`);
80238
80377
  }
80239
80378
  return false;
80240
80379
  }
80380
+ hasAnyNeedingFirstFrameForTarget(targetSessionId) {
80381
+ for (const peer of this.peers.values()) {
80382
+ if (peer.needsFirstFrame && peer.screenshotActive && peer.state === "connected" && peer.screenshotTargetSessionId === targetSessionId) {
80383
+ return true;
80384
+ }
80385
+ }
80386
+ return false;
80387
+ }
80241
80388
  onStateChange(listener) {
80242
80389
  this.stateListeners.push(listener);
80243
80390
  }
@@ -80301,11 +80448,14 @@ ${e?.stack || ""}`);
80301
80448
  if (targetPeers.size === 0) return false;
80302
80449
  return this.screenshotSender.broadcastSessionOutput(targetPeers, sessionId, data);
80303
80450
  }
80304
- sendScreenshot(base64Data) {
80305
- return this.screenshotSender.sendScreenshot(this.peers, base64Data);
80451
+ sendScreenshot(base64Data, targetSessionId) {
80452
+ return this.screenshotSender.sendScreenshot(this.peers, base64Data, targetSessionId);
80453
+ }
80454
+ sendScreenshotBuffer(buffer, targetSessionId) {
80455
+ return this.screenshotSender.sendScreenshotBuffer(this.peers, buffer, targetSessionId);
80306
80456
  }
80307
- sendScreenshotBuffer(buffer) {
80308
- return this.screenshotSender.sendScreenshotBuffer(this.peers, buffer);
80457
+ sendScreenshotBufferForTarget(targetSessionId, buffer) {
80458
+ return this.sendScreenshotBuffer(buffer, targetSessionId);
80309
80459
  }
80310
80460
  // ─── Handler registration (unchanged API) ───────
80311
80461
  onFileRequest(handler) {
@@ -87029,6 +87179,7 @@ var init_screenshot_controller = __esm({
87029
87179
  lastSize = 0;
87030
87180
  lastHash = 0;
87031
87181
  staticFrameCount = 0;
87182
+ targetFrameState = /* @__PURE__ */ new Map();
87032
87183
  currentInterval;
87033
87184
  // Quality profiles
87034
87185
  profileDirect;
@@ -87092,14 +87243,13 @@ var init_screenshot_controller = __esm({
87092
87243
  async tick() {
87093
87244
  if (!this.deps.isRunning()) return;
87094
87245
  const active = this.deps.isScreenshotActive();
87095
- const targetSessionId = this.deps.getScreenshotTargetSessionId();
87096
- if (active && !targetSessionId) {
87246
+ const targetSessionIds = this.getActiveTargetSessionIds();
87247
+ const isRelay = this.deps.isUsingRelay();
87248
+ const profile = isRelay ? this.profileRelay : this.profileDirect;
87249
+ if (active && targetSessionIds.length === 0) {
87097
87250
  this.timer = setTimeout(() => this.tick(), 500);
87098
87251
  return;
87099
87252
  }
87100
- const cdp = targetSessionId ? this.deps.getCdp(targetSessionId) : null;
87101
- const isRelay = this.deps.isUsingRelay();
87102
- const profile = isRelay ? this.profileRelay : this.profileDirect;
87103
87253
  this.checkBudgetReset();
87104
87254
  if (this.dailyBudgetMs > 0) {
87105
87255
  const now = Date.now();
@@ -87114,49 +87264,84 @@ var init_screenshot_controller = __esm({
87114
87264
  }
87115
87265
  }
87116
87266
  const budgetBlocked = this.budgetExhausted && isRelay;
87117
- if (!active || !cdp || budgetBlocked) {
87267
+ if (!active || budgetBlocked) {
87118
87268
  this.staticFrameCount = 0;
87269
+ this.targetFrameState.clear();
87119
87270
  this.currentInterval = profile.maxInterval;
87120
87271
  if (!active) this.lastActiveTimestamp = 0;
87121
87272
  this.timer = setTimeout(() => this.tick(), budgetBlocked ? 3e4 : this.currentInterval);
87122
87273
  return;
87123
87274
  }
87124
87275
  this.debugCount++;
87125
- try {
87126
- const buf = await cdp.captureScreenshot({ quality: profile.quality });
87127
- if (buf) {
87276
+ let capturedAny = false;
87277
+ let sentAnyFrame = false;
87278
+ let anyFirstFrameAcrossTargets = false;
87279
+ for (const targetSessionId of targetSessionIds) {
87280
+ const cdp = this.deps.getCdp(targetSessionId);
87281
+ if (!cdp) continue;
87282
+ try {
87283
+ const buf = await cdp.captureScreenshot({ quality: profile.quality });
87284
+ if (!buf) {
87285
+ if (this.debugCount <= 5) LOG.debug("Screenshot", `captureScreenshot returned null for target=${targetSessionId}`);
87286
+ continue;
87287
+ }
87288
+ capturedAny = true;
87289
+ const state = this.getTargetFrameState(targetSessionId);
87128
87290
  const hash2 = _ScreenshotController.fnvHash(buf);
87129
- const sizeMatch = buf.length === this.lastSize;
87130
- const hashMatch = hash2 === this.lastHash;
87131
- const anyNeedsFirstFrame = this.deps.hasAnyNeedingFirstFrame();
87291
+ const sizeMatch = buf.length === state.lastSize;
87292
+ const hashMatch = hash2 === state.lastHash;
87293
+ const anyNeedsFirstFrame = this.deps.hasAnyNeedingFirstFrameForTarget?.(targetSessionId) ?? this.deps.hasAnyNeedingFirstFrame();
87132
87294
  const resizeTarget = anyNeedsFirstFrame ? profile.firstFrameLongEdge : profile.maxLongEdge;
87295
+ anyFirstFrameAcrossTargets = anyFirstFrameAcrossTargets || anyNeedsFirstFrame;
87133
87296
  if (sizeMatch && hashMatch && !anyNeedsFirstFrame) {
87134
- this.staticFrameCount++;
87135
- if (this.staticFrameCount >= this.STATIC_THRESHOLD) {
87297
+ state.staticFrameCount++;
87298
+ if (state.staticFrameCount >= this.STATIC_THRESHOLD) {
87136
87299
  this.currentInterval = Math.min(this.currentInterval + 200, profile.maxInterval);
87137
87300
  }
87138
87301
  if (this.debugCount <= 5 || this.debugCount % 50 === 0) {
87139
- LOG.debug("Screenshot", `skip (unchanged, static=${this.staticFrameCount}, interval=${this.currentInterval}ms, ${isRelay ? "RELAY" : "DIRECT"})`);
87140
- }
87141
- } else {
87142
- const normalizedBuf = await this.normalizeBuffer(buf, resizeTarget, profile.quality);
87143
- this.lastSize = buf.length;
87144
- this.lastHash = hash2;
87145
- this.staticFrameCount = 0;
87146
- this.currentInterval = profile.minInterval;
87147
- const sent = this.deps.sendScreenshotBuffer(normalizedBuf);
87148
- if (this.debugCount <= 3 || anyNeedsFirstFrame) {
87149
- LOG.debug("Screenshot", `sent: ${normalizedBuf.length} bytes, delivered=${sent}, interval=${this.currentInterval}ms, ${isRelay ? "RELAY" : "DIRECT"}${anyNeedsFirstFrame ? " (first-frame)" : ""}`);
87302
+ LOG.debug("Screenshot", `skip target=${targetSessionId} (unchanged, static=${state.staticFrameCount}, interval=${this.currentInterval}ms, ${isRelay ? "RELAY" : "DIRECT"})`);
87150
87303
  }
87304
+ continue;
87151
87305
  }
87152
- } else {
87153
- if (this.debugCount <= 5) LOG.debug("Screenshot", "captureScreenshot returned null");
87306
+ const normalizedBuf = await this.normalizeBuffer(buf, resizeTarget, profile.quality);
87307
+ state.lastSize = buf.length;
87308
+ state.lastHash = hash2;
87309
+ state.staticFrameCount = 0;
87310
+ this.lastSize = buf.length;
87311
+ this.lastHash = hash2;
87312
+ this.staticFrameCount = 0;
87313
+ this.currentInterval = profile.minInterval;
87314
+ const sent = this.deps.sendScreenshotBufferForTarget ? this.deps.sendScreenshotBufferForTarget(targetSessionId, normalizedBuf) : this.deps.sendScreenshotBuffer(normalizedBuf);
87315
+ sentAnyFrame = sentAnyFrame || sent;
87316
+ if (this.debugCount <= 3 || anyNeedsFirstFrame) {
87317
+ LOG.debug("Screenshot", `sent target=${targetSessionId}: ${normalizedBuf.length} bytes, delivered=${sent}, interval=${this.currentInterval}ms, ${isRelay ? "RELAY" : "DIRECT"}${anyNeedsFirstFrame ? " (first-frame)" : ""}`);
87318
+ }
87319
+ } catch (e) {
87320
+ if (this.debugCount <= 5) LOG.warn("Screenshot", `error target=${targetSessionId}: ${e?.message}`);
87154
87321
  }
87155
- } catch (e) {
87156
- if (this.debugCount <= 5) LOG.warn("Screenshot", `error: ${e?.message}`);
87322
+ }
87323
+ if (!capturedAny) {
87324
+ this.currentInterval = profile.maxInterval;
87325
+ } else if (!sentAnyFrame && !anyFirstFrameAcrossTargets) {
87326
+ this.currentInterval = Math.min(this.currentInterval + 200, profile.maxInterval);
87157
87327
  }
87158
87328
  this.timer = setTimeout(() => this.tick(), this.currentInterval);
87159
87329
  }
87330
+ getActiveTargetSessionIds() {
87331
+ const explicitTargets = this.deps.getScreenshotTargetSessionIds?.() || [];
87332
+ const normalized = explicitTargets.map((target) => typeof target === "string" ? target.trim() : "").filter((target) => target.length > 0);
87333
+ if (normalized.length > 0) return Array.from(new Set(normalized));
87334
+ const legacyTarget = this.deps.getScreenshotTargetSessionId();
87335
+ return legacyTarget ? [legacyTarget] : [];
87336
+ }
87337
+ getTargetFrameState(targetSessionId) {
87338
+ let state = this.targetFrameState.get(targetSessionId);
87339
+ if (!state) {
87340
+ state = { lastSize: 0, lastHash: 0, staticFrameCount: 0 };
87341
+ this.targetFrameState.set(targetSessionId, state);
87342
+ }
87343
+ return state;
87344
+ }
87160
87345
  // ─── Budget ───────────────────────────────────
87161
87346
  checkBudgetReset() {
87162
87347
  const today = (/* @__PURE__ */ new Date()).toISOString().slice(0, 10);
@@ -87373,7 +87558,7 @@ function killPid2(pid) {
87373
87558
  return false;
87374
87559
  }
87375
87560
  }
87376
- function getWindowsProcessCommandLine(pid) {
87561
+ function getWindowsProcessCommandLine2(pid) {
87377
87562
  const pidFilter = `ProcessId=${pid}`;
87378
87563
  try {
87379
87564
  const psOut = (0, import_child_process13.execFileSync)("powershell.exe", [
@@ -87402,13 +87587,32 @@ function getWindowsProcessCommandLine(pid) {
87402
87587
  }
87403
87588
  return null;
87404
87589
  }
87590
+ function getProcessCommandLine2(pid) {
87591
+ if (!Number.isFinite(pid) || pid <= 0) return null;
87592
+ if (process.platform === "win32") return getWindowsProcessCommandLine2(pid);
87593
+ try {
87594
+ const text = (0, import_child_process13.execFileSync)("ps", ["-o", "command=", "-p", String(pid)], {
87595
+ encoding: "utf8",
87596
+ timeout: 3e3,
87597
+ stdio: ["ignore", "pipe", "ignore"]
87598
+ }).trim();
87599
+ return text || null;
87600
+ } catch {
87601
+ return null;
87602
+ }
87603
+ }
87604
+ function isManagedSessionHostPid2(pid) {
87605
+ const commandLine = getProcessCommandLine2(pid);
87606
+ if (!commandLine) return false;
87607
+ return /session-host-daemon/i.test(commandLine);
87608
+ }
87405
87609
  function stopManagedSessionHostProcess() {
87406
87610
  let stopped = false;
87407
87611
  const pidFile = getSessionHostPidFile();
87408
87612
  try {
87409
87613
  if (fs22.existsSync(pidFile)) {
87410
87614
  const pid = Number.parseInt(fs22.readFileSync(pidFile, "utf8").trim(), 10);
87411
- if (Number.isFinite(pid) && pid !== process.pid) {
87615
+ if (Number.isFinite(pid) && pid !== process.pid && isManagedSessionHostPid2(pid)) {
87412
87616
  stopped = killPid2(pid) || stopped;
87413
87617
  }
87414
87618
  }
@@ -87422,40 +87626,7 @@ function stopManagedSessionHostProcess() {
87422
87626
  return stopped;
87423
87627
  }
87424
87628
  function stopSessionHost() {
87425
- let stopped = stopManagedSessionHostProcess();
87426
- if (process.platform === "win32") {
87427
- try {
87428
- const raw = (0, import_child_process13.execFileSync)("tasklist", ["/FO", "CSV", "/NH", "/FI", "IMAGENAME eq node.exe"], {
87429
- encoding: "utf8",
87430
- timeout: 5e3,
87431
- stdio: ["ignore", "pipe", "ignore"],
87432
- windowsHide: true
87433
- }).trim();
87434
- for (const line of raw.split(/\r?\n/)) {
87435
- const match = line.match(/^"node\.exe","(\d+)"/i);
87436
- if (!match) continue;
87437
- const candidatePid = Number.parseInt(match[1], 10);
87438
- if (!Number.isFinite(candidatePid) || candidatePid === process.pid) continue;
87439
- const commandLine = getWindowsProcessCommandLine(candidatePid);
87440
- if (commandLine?.includes("session-host-daemon")) {
87441
- stopped = killPid2(candidatePid) || stopped;
87442
- }
87443
- }
87444
- } catch {
87445
- }
87446
- } else {
87447
- try {
87448
- const raw = (0, import_child_process13.execFileSync)("pgrep", ["-f", "session-host-daemon"], { encoding: "utf8" }).trim();
87449
- for (const line of raw.split("\n")) {
87450
- const pid = Number.parseInt(line.trim(), 10);
87451
- if (Number.isFinite(pid) && pid !== process.pid && pid !== process.ppid) {
87452
- stopped = killPid2(pid) || stopped;
87453
- }
87454
- }
87455
- } catch {
87456
- }
87457
- }
87458
- return stopped;
87629
+ return stopManagedSessionHostProcess();
87459
87630
  }
87460
87631
  async function ensureSessionHostReady2() {
87461
87632
  const quarantine = quarantineLegacyStandaloneSessions({
@@ -87712,10 +87883,65 @@ var init_session_host_controller = __esm({
87712
87883
  var adhdev_daemon_exports = {};
87713
87884
  __export(adhdev_daemon_exports, {
87714
87885
  AdhdevDaemon: () => AdhdevDaemon,
87886
+ buildMandatoryUpdateInfoFromServerPayload: () => buildMandatoryUpdateInfoFromServerPayload,
87887
+ buildMandatoryUpdateRequiredPayload: () => buildMandatoryUpdateRequiredPayload,
87715
87888
  getDaemonPid: () => getDaemonPid,
87716
87889
  isDaemonRunning: () => isDaemonRunning,
87717
- stopDaemon: () => stopDaemon
87718
- });
87890
+ stopDaemon: () => stopDaemon,
87891
+ validateMandatoryUpdateTarget: () => validateMandatoryUpdateTarget,
87892
+ verifyPublishedMandatoryUpdateTarget: () => verifyPublishedMandatoryUpdateTarget
87893
+ });
87894
+ function validateMandatoryUpdateTarget(targetVersion) {
87895
+ if (typeof targetVersion !== "string") return { valid: false, error: "target version is required" };
87896
+ if (targetVersion !== targetVersion.trim()) return { valid: false, error: "target version must not contain whitespace" };
87897
+ const version2 = targetVersion;
87898
+ if (!version2) return { valid: false, error: "target version is required" };
87899
+ if (!/^\d+\.\d+\.\d+(?:-[0-9A-Za-z]+(?:\.[0-9A-Za-z]+)*)?(?:\+[0-9A-Za-z]+(?:\.[0-9A-Za-z]+)*)?$/.test(version2)) {
87900
+ return { valid: false, error: `invalid semver target: ${version2}` };
87901
+ }
87902
+ return { valid: true };
87903
+ }
87904
+ function verifyPublishedMandatoryUpdateTarget(packageName, targetVersion, deps = {}) {
87905
+ if (!/^(?:adhdev|@adhdev\/daemon-standalone)$/.test(packageName)) {
87906
+ throw new Error(`invalid mandatory update package: ${packageName}`);
87907
+ }
87908
+ const validation = validateMandatoryUpdateTarget(targetVersion);
87909
+ if (!validation.valid) throw new Error(validation.error || "invalid mandatory update target");
87910
+ const run = deps.execFileSync || import_child_process14.execFileSync;
87911
+ const npmExecutable = deps.npmExecutable || resolveCurrentGlobalInstallSurface({ packageName }).npmExecutable;
87912
+ const published = String(run(npmExecutable, ["view", `${packageName}@${targetVersion}`, "version"], {
87913
+ encoding: "utf-8",
87914
+ timeout: 1e4,
87915
+ stdio: ["pipe", "pipe", "pipe"],
87916
+ ...process.platform === "win32" ? { shell: true, windowsHide: true } : {}
87917
+ })).trim();
87918
+ if (published !== targetVersion) {
87919
+ throw new Error(`Published version mismatch: expected ${targetVersion}, got ${published || "unknown"}`);
87920
+ }
87921
+ return published;
87922
+ }
87923
+ function buildMandatoryUpdateInfoFromServerPayload(payload, fallbackVersion) {
87924
+ const targetVersion = typeof payload?.latest === "string" && payload.latest ? payload.latest : fallbackVersion;
87925
+ const validation = validateMandatoryUpdateTarget(targetVersion);
87926
+ if (!validation.valid) return { info: null, error: validation.error || targetVersion };
87927
+ return {
87928
+ info: {
87929
+ targetVersion,
87930
+ reason: typeof payload?.reason === "string" && payload.reason.trim() ? payload.reason.trim() : "major_minor_mismatch",
87931
+ minVersion: typeof payload?.minVersion === "string" && payload.minVersion.trim() ? payload.minVersion.trim() : void 0
87932
+ }
87933
+ };
87934
+ }
87935
+ function buildMandatoryUpdateRequiredPayload(pending, interactionId) {
87936
+ return {
87937
+ error: "A mandatory daemon update is pending. Finish current work and let the daemon update before starting a new session.",
87938
+ code: "DAEMON_UPDATE_REQUIRED",
87939
+ required: true,
87940
+ latest: pending.targetVersion,
87941
+ reason: pending.reason,
87942
+ interactionId
87943
+ };
87944
+ }
87719
87945
  function resolveDaemonPort(ref = {}) {
87720
87946
  return Number.isFinite(ref.port) && Number(ref.port) > 0 ? Number(ref.port) : DEFAULT_DAEMON_PORT;
87721
87947
  }
@@ -87742,7 +87968,7 @@ function removeDaemonPid(ref = {}) {
87742
87968
  function isDaemonRunning(ref = {}) {
87743
87969
  const port = resolveDaemonPort(ref);
87744
87970
  try {
87745
- const { execFileSync: execFileSync6 } = require("child_process");
87971
+ const { execFileSync: execFileSync7 } = require("child_process");
87746
87972
  const probe = `
87747
87973
  const http = require('http');
87748
87974
  const req = http.get('http://127.0.0.1:${port}/health', { timeout: 1500 }, (res) => {
@@ -87752,7 +87978,7 @@ function isDaemonRunning(ref = {}) {
87752
87978
  req.on('error', () => process.stdout.write('0'));
87753
87979
  req.on('timeout', () => { req.destroy(); process.stdout.write('0'); });
87754
87980
  `;
87755
- const result = execFileSync6(process.execPath, ["-e", probe], {
87981
+ const result = execFileSync7(process.execPath, ["-e", probe], {
87756
87982
  encoding: "utf-8",
87757
87983
  timeout: 3e3,
87758
87984
  stdio: ["ignore", "pipe", "ignore"]
@@ -87778,9 +88004,9 @@ function isDaemonRunning(ref = {}) {
87778
88004
  function isAdhdevProcess(pid) {
87779
88005
  try {
87780
88006
  if (process.platform === "win32") {
87781
- const { execFileSync: execFileSync6 } = require("child_process");
88007
+ const { execFileSync: execFileSync7 } = require("child_process");
87782
88008
  try {
87783
- const psOut = execFileSync6("powershell.exe", [
88009
+ const psOut = execFileSync7("powershell.exe", [
87784
88010
  "-NoProfile",
87785
88011
  "-NonInteractive",
87786
88012
  "-ExecutionPolicy",
@@ -87793,8 +88019,8 @@ function isAdhdevProcess(pid) {
87793
88019
  return true;
87794
88020
  }
87795
88021
  } else {
87796
- const { execFileSync: execFileSync6 } = require("child_process");
87797
- const cmdline = execFileSync6("ps", ["-o", "command=", "-p", String(pid)], {
88022
+ const { execFileSync: execFileSync7 } = require("child_process");
88023
+ const cmdline = execFileSync7("ps", ["-o", "command=", "-p", String(pid)], {
87798
88024
  encoding: "utf-8",
87799
88025
  timeout: 2e3,
87800
88026
  stdio: ["ignore", "pipe", "ignore"]
@@ -87808,7 +88034,7 @@ function isAdhdevProcess(pid) {
87808
88034
  function getDaemonHealthPid(ref = {}) {
87809
88035
  const port = resolveDaemonPort(ref);
87810
88036
  try {
87811
- const { execFileSync: execFileSync6 } = require("child_process");
88037
+ const { execFileSync: execFileSync7 } = require("child_process");
87812
88038
  const probe = `
87813
88039
  const http = require('http');
87814
88040
  const req = http.get('http://127.0.0.1:${port}/health', { timeout: 1500 }, (res) => {
@@ -87826,7 +88052,7 @@ function getDaemonHealthPid(ref = {}) {
87826
88052
  req.on('error', () => {});
87827
88053
  req.on('timeout', () => { req.destroy(); });
87828
88054
  `;
87829
- const result = execFileSync6(process.execPath, ["-e", probe], {
88055
+ const result = execFileSync7(process.execPath, ["-e", probe], {
87830
88056
  encoding: "utf-8",
87831
88057
  timeout: 3e3,
87832
88058
  stdio: ["ignore", "pipe", "ignore"]
@@ -87872,7 +88098,7 @@ function stopDaemon(ref = {}) {
87872
88098
  return false;
87873
88099
  }
87874
88100
  }
87875
- var os28, fs23, path29, import_http, import_ws3, pkgVersion, AdhdevDaemon;
88101
+ var os28, fs23, path29, import_http, import_child_process14, import_ws3, pkgVersion, AdhdevDaemon;
87876
88102
  var init_adhdev_daemon = __esm({
87877
88103
  "src/adhdev-daemon.ts"() {
87878
88104
  "use strict";
@@ -87888,12 +88114,13 @@ var init_adhdev_daemon = __esm({
87888
88114
  fs23 = __toESM(require("fs"));
87889
88115
  path29 = __toESM(require("path"));
87890
88116
  import_http = require("http");
88117
+ import_child_process14 = require("child_process");
87891
88118
  import_ws3 = require("ws");
87892
88119
  init_source();
87893
88120
  init_version();
87894
88121
  init_src();
87895
88122
  init_runtime_defaults();
87896
- pkgVersion = resolvePackageVersion({ injectedVersion: "0.9.33" });
88123
+ pkgVersion = resolvePackageVersion({ injectedVersion: "0.9.34" });
87897
88124
  AdhdevDaemon = class _AdhdevDaemon {
87898
88125
  localHttpServer = null;
87899
88126
  localWss = null;
@@ -87986,6 +88213,10 @@ var init_adhdev_daemon = __esm({
87986
88213
  getUpgradePackageName() {
87987
88214
  return process.argv[1]?.includes("daemon-standalone") ? "@adhdev/daemon-standalone" : "adhdev";
87988
88215
  }
88216
+ getMandatoryUpdateBlockPayload(cmd, interactionId) {
88217
+ if (!this.pendingMandatoryUpdate || !_AdhdevDaemon.MANDATORY_UPDATE_BLOCKED_COMMANDS.has(cmd)) return null;
88218
+ return buildMandatoryUpdateRequiredPayload(this.pendingMandatoryUpdate, interactionId);
88219
+ }
87989
88220
  hasBlockingSessionsForMandatoryUpdate() {
87990
88221
  if (!this.components) return false;
87991
88222
  const blocking = /* @__PURE__ */ new Set(["generating", "waiting_approval", "starting"]);
@@ -88011,15 +88242,7 @@ var init_adhdev_daemon = __esm({
88011
88242
  const pkgName = this.getUpgradePackageName();
88012
88243
  this.mandatoryUpgradeInFlight = true;
88013
88244
  try {
88014
- const { execSync: execSync8 } = await import("child_process");
88015
- const published = execSync8(`npm view ${pkgName}@${pending.targetVersion} version`, {
88016
- encoding: "utf-8",
88017
- timeout: 1e4,
88018
- stdio: ["pipe", "pipe", "pipe"]
88019
- }).trim();
88020
- if (published !== pending.targetVersion) {
88021
- throw new Error(`Published version mismatch: expected ${pending.targetVersion}, got ${published || "unknown"}`);
88022
- }
88245
+ verifyPublishedMandatoryUpdateTarget(pkgName, pending.targetVersion);
88023
88246
  LOG.warn("Upgrade", `Applying mandatory daemon update (${pending.reason}) \u2192 v${pending.targetVersion}`);
88024
88247
  spawnDetachedDaemonUpgradeHelper({
88025
88248
  packageName: pkgName,
@@ -88121,7 +88344,7 @@ var init_adhdev_daemon = __esm({
88121
88344
  const now = Date.now();
88122
88345
  const cached2 = this.hotChatSnapshotCache;
88123
88346
  const sessions = cached2 && now - cached2.builtAt < _AdhdevDaemon.HOT_CHAT_SNAPSHOT_CACHE_TTL_MS ? cached2.sessions : (() => {
88124
- const built = this.buildLiveStatusSnapshot().sessions || [];
88347
+ const built = this.components.instanceManager.collectHotChatSessionStates();
88125
88348
  this.hotChatSnapshotCache = { sessions: built, builtAt: now };
88126
88349
  return built;
88127
88350
  })();
@@ -88506,14 +88729,17 @@ ${err?.stack || ""}`);
88506
88729
  isRunning: () => this.running,
88507
88730
  isScreenshotActive: () => this.p2p?.screenshotActive ?? false,
88508
88731
  getScreenshotTargetSessionId: () => this.p2p?.screenshotTargetSessionId,
88732
+ getScreenshotTargetSessionIds: () => this.p2p?.screenshotTargetSessionIds ?? [],
88509
88733
  isUsingRelay: () => this.p2p?.isUsingRelay ?? false,
88510
88734
  hasAnyNeedingFirstFrame: () => this.p2p?.hasAnyNeedingFirstFrame() ?? false,
88735
+ hasAnyNeedingFirstFrameForTarget: (targetSessionId) => this.p2p?.hasAnyNeedingFirstFrameForTarget(targetSessionId) ?? false,
88511
88736
  getCdp: (targetSessionId) => {
88512
88737
  if (targetSessionId) return this.getCdpFor(targetSessionId);
88513
88738
  LOG.warn("P2P", "Screenshot requested without targetSessionId \u2014 cannot determine target session. Skipping frame.");
88514
88739
  return null;
88515
88740
  },
88516
- sendScreenshotBuffer: (buf) => this.p2p.sendScreenshotBuffer(buf)
88741
+ sendScreenshotBuffer: (buf) => this.p2p.sendScreenshotBuffer(buf),
88742
+ sendScreenshotBufferForTarget: (targetSessionId, buf) => this.p2p.sendScreenshotBufferForTarget(targetSessionId, buf)
88517
88743
  }, planLimits ?? void 0);
88518
88744
  this.screenshotController.start();
88519
88745
  this.p2p.onScreenshotStart(() => this.screenshotController?.triggerImmediate());
@@ -88542,11 +88768,12 @@ ${err?.stack || ""}`);
88542
88768
  });
88543
88769
  this.serverConn.on("force_update_required", (msg) => {
88544
88770
  const payload = msg.payload;
88545
- this.pendingMandatoryUpdate = {
88546
- targetVersion: typeof payload.latest === "string" && payload.latest.trim() ? payload.latest.trim() : pkgVersion,
88547
- reason: typeof payload.reason === "string" && payload.reason.trim() ? payload.reason.trim() : "major_minor_mismatch",
88548
- minVersion: typeof payload.minVersion === "string" && payload.minVersion.trim() ? payload.minVersion.trim() : void 0
88549
- };
88771
+ const parsedUpdate = buildMandatoryUpdateInfoFromServerPayload(payload, pkgVersion);
88772
+ if (!parsedUpdate.info) {
88773
+ LOG.error("Upgrade", `Ignoring invalid mandatory daemon update target from server: ${parsedUpdate.error || "unknown"}`);
88774
+ return;
88775
+ }
88776
+ this.pendingMandatoryUpdate = parsedUpdate.info;
88550
88777
  LOG.warn("Upgrade", `Mandatory daemon update required (${this.pendingMandatoryUpdate.reason})`);
88551
88778
  void this.maybeApplyMandatoryUpdate("server-force-update");
88552
88779
  });
@@ -88657,15 +88884,9 @@ ${err?.stack || ""}`);
88657
88884
  const cmdStart = Date.now();
88658
88885
  const source = msg.ipcWs ? "ext" : typeof msg.source === "string" && msg.source.trim() ? msg.source : "ws";
88659
88886
  try {
88660
- if (this.pendingMandatoryUpdate && _AdhdevDaemon.MANDATORY_UPDATE_BLOCKED_COMMANDS.has(cmd)) {
88661
- this.sendResult(msg, false, {
88662
- error: "A mandatory daemon update is pending. Finish current work and let the daemon update before starting a new session.",
88663
- code: "DAEMON_UPDATE_REQUIRED",
88664
- required: true,
88665
- latest: this.pendingMandatoryUpdate.targetVersion,
88666
- reason: this.pendingMandatoryUpdate.reason,
88667
- interactionId
88668
- });
88887
+ const mandatoryUpdateBlock = this.getMandatoryUpdateBlockPayload(cmd, interactionId);
88888
+ if (mandatoryUpdateBlock) {
88889
+ this.sendResult(msg, false, mandatoryUpdateBlock);
88669
88890
  return;
88670
88891
  }
88671
88892
  if (source === "api" && !loadConfig().allowServerApiProxy) {
@@ -88710,6 +88931,10 @@ ${err?.stack || ""}`);
88710
88931
  const interactionId = String(normalizedData._interactionId);
88711
88932
  const cmdStart = Date.now();
88712
88933
  try {
88934
+ const mandatoryUpdateBlock = this.getMandatoryUpdateBlockPayload(cmdType, interactionId);
88935
+ if (mandatoryUpdateBlock) {
88936
+ return { success: false, ...mandatoryUpdateBlock };
88937
+ }
88713
88938
  switch (cmdType) {
88714
88939
  case "get_runtime_snapshot": {
88715
88940
  const sessionId = typeof normalizedData.sessionId === "string" ? normalizedData.sessionId : "";
@@ -88888,8 +89113,22 @@ ${err?.stack || ""}`);
88888
89113
  }));
88889
89114
  return;
88890
89115
  }
89116
+ const normalizedArgs = this.ensureInteractionContext(args);
89117
+ const interactionId = String(normalizedArgs._interactionId);
89118
+ const mandatoryUpdateBlock = this.getMandatoryUpdateBlockPayload(command, interactionId);
89119
+ if (mandatoryUpdateBlock) {
89120
+ ws.send(JSON.stringify({
89121
+ type: "ext:command_result",
89122
+ payload: {
89123
+ requestId,
89124
+ success: false,
89125
+ ...mandatoryUpdateBlock
89126
+ }
89127
+ }));
89128
+ return;
89129
+ }
88891
89130
  try {
88892
- const result = await this.components.router.execute(command, args, "ipc");
89131
+ const result = await this.components.router.execute(command, normalizedArgs, "ipc");
88893
89132
  ws.send(JSON.stringify({
88894
89133
  type: "ext:command_result",
88895
89134
  payload: {
@@ -89059,9 +89298,9 @@ async function runWizard(options = {}) {
89059
89298
  }
89060
89299
  async function checkForUpdate() {
89061
89300
  try {
89062
- const { execFileSync: execFileSync6 } = await import("child_process");
89301
+ const { execFileSync: execFileSync7 } = await import("child_process");
89063
89302
  const currentVersion = resolvePackageVersion();
89064
- const latestVersion = readLatestPublishedCliVersion(execFileSync6);
89303
+ const latestVersion = readLatestPublishedCliVersion(execFileSync7);
89065
89304
  if (!latestVersion) return;
89066
89305
  if (!currentVersion || !latestVersion || currentVersion === latestVersion) return;
89067
89306
  console.log(source_default.yellow(` Update available: ${currentVersion} \u2192 ${latestVersion}`));
@@ -89078,7 +89317,7 @@ async function checkForUpdate() {
89078
89317
  const spinner = (await Promise.resolve().then(() => (init_ora(), ora_exports))).default("Updating adhdev CLI...").start();
89079
89318
  try {
89080
89319
  const installCommand = buildPinnedGlobalInstallCommand({ packageName: "adhdev", targetVersion: "latest" });
89081
- execFileSync6(installCommand.command, installCommand.args, {
89320
+ execFileSync7(installCommand.command, installCommand.args, {
89082
89321
  encoding: "utf-8",
89083
89322
  timeout: 6e4,
89084
89323
  stdio: ["pipe", "pipe", "pipe"]
@@ -92373,7 +92612,7 @@ function registerDaemonCommands(program2, pkgVersion3) {
92373
92612
 
92374
92613
  // src/cli/doctor-commands.ts
92375
92614
  init_source();
92376
- var import_child_process14 = require("child_process");
92615
+ var import_child_process15 = require("child_process");
92377
92616
  var fs26 = __toESM(require("fs"));
92378
92617
  var os30 = __toESM(require("os"));
92379
92618
  var path33 = __toESM(require("path"));
@@ -93015,7 +93254,7 @@ function findCommandPaths(command) {
93015
93254
  try {
93016
93255
  const bin = process.platform === "win32" ? "where.exe" : "which";
93017
93256
  const args = process.platform === "win32" ? [command] : ["-a", command];
93018
- const output = (0, import_child_process14.execFileSync)(bin, args, {
93257
+ const output = (0, import_child_process15.execFileSync)(bin, args, {
93019
93258
  encoding: "utf-8",
93020
93259
  stdio: ["ignore", "pipe", "ignore"]
93021
93260
  });
@@ -93030,7 +93269,7 @@ function probeCliBinary(commandPath, currentVersion) {
93030
93269
  currentVersion
93031
93270
  };
93032
93271
  try {
93033
- probe.version = (0, import_child_process14.execFileSync)(commandPath, ["--version"], {
93272
+ probe.version = (0, import_child_process15.execFileSync)(commandPath, ["--version"], {
93034
93273
  encoding: "utf-8",
93035
93274
  stdio: ["ignore", "pipe", "pipe"]
93036
93275
  }).trim();
@@ -93038,7 +93277,7 @@ function probeCliBinary(commandPath, currentVersion) {
93038
93277
  probe.versionError = error48?.stderr?.toString?.().trim() || error48?.message || "version probe failed";
93039
93278
  }
93040
93279
  try {
93041
- probe.helpText = (0, import_child_process14.execFileSync)(commandPath, ["--help"], {
93280
+ probe.helpText = (0, import_child_process15.execFileSync)(commandPath, ["--help"], {
93042
93281
  encoding: "utf-8",
93043
93282
  stdio: ["ignore", "pipe", "pipe"]
93044
93283
  });