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/index.js CHANGED
@@ -11413,13 +11413,14 @@ function sliceFromOffset(text, start) {
11413
11413
  function hydrateCliParsedMessages(parsedMessages, options) {
11414
11414
  const { committedMessages, scope, lastOutputAt } = options;
11415
11415
  const referenceMessages = [...committedMessages];
11416
+ const referenceComparables = referenceMessages.map((message) => normalizeComparableMessageContent(message?.content || ""));
11416
11417
  const usedReferenceIndexes = /* @__PURE__ */ new Set();
11417
11418
  const now = options.now ?? Date.now();
11418
11419
  const findReferenceTimestamp = (role, content, parsedIndex) => {
11419
11420
  const normalizedContent = normalizeComparableMessageContent(content);
11420
11421
  if (!normalizedContent) return void 0;
11421
11422
  const sameIndex = referenceMessages[parsedIndex];
11422
- if (sameIndex && !usedReferenceIndexes.has(parsedIndex) && sameIndex.role === role && normalizeComparableMessageContent(sameIndex.content) === normalizedContent && typeof sameIndex.timestamp === "number" && Number.isFinite(sameIndex.timestamp)) {
11423
+ if (sameIndex && !usedReferenceIndexes.has(parsedIndex) && sameIndex.role === role && referenceComparables[parsedIndex] === normalizedContent && typeof sameIndex.timestamp === "number" && Number.isFinite(sameIndex.timestamp)) {
11423
11424
  usedReferenceIndexes.add(parsedIndex);
11424
11425
  return sameIndex.timestamp;
11425
11426
  }
@@ -11427,7 +11428,7 @@ function hydrateCliParsedMessages(parsedMessages, options) {
11427
11428
  if (usedReferenceIndexes.has(i)) continue;
11428
11429
  const candidate = referenceMessages[i];
11429
11430
  if (!candidate || candidate.role !== role) continue;
11430
- const candidateContent = normalizeComparableMessageContent(candidate.content);
11431
+ const candidateContent = referenceComparables[i];
11431
11432
  if (!candidateContent) continue;
11432
11433
  const exactMatch = candidateContent === normalizedContent;
11433
11434
  const fuzzyMatch = candidateContent.includes(normalizedContent) || normalizedContent.includes(candidateContent);
@@ -12519,7 +12520,7 @@ var init_provider_cli_adapter = __esm({
12519
12520
  return;
12520
12521
  }
12521
12522
  if (this.currentTurnScope && !lastParsedAssistant) {
12522
- LOG.info(
12523
+ LOG.debug(
12523
12524
  "CLI",
12524
12525
  `[${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 || "-"}`
12525
12526
  );
@@ -13335,9 +13336,43 @@ var init_provider_cli_adapter = __esm({
13335
13336
  }
13336
13337
  armResponseTimeout() {
13337
13338
  if (this.responseTimeout) clearTimeout(this.responseTimeout);
13339
+ const timeoutMs = this.timeouts.maxResponse;
13340
+ if (!Number.isFinite(timeoutMs) || timeoutMs <= 0) {
13341
+ this.responseTimeout = null;
13342
+ return;
13343
+ }
13338
13344
  this.responseTimeout = setTimeout(() => {
13339
- if (this.isWaitingForResponse) this.finishResponse();
13340
- }, this.timeouts.maxResponse);
13345
+ this.responseTimeout = null;
13346
+ if (!this.isWaitingForResponse) return;
13347
+ const detectedStatusBeforeEval = this.runDetectStatus(this.recentOutputBuffer);
13348
+ this.recordTrace("response_timeout_check", {
13349
+ timeoutMs,
13350
+ detectedStatus: detectedStatusBeforeEval,
13351
+ currentStatus: this.currentStatus,
13352
+ isWaitingForResponse: this.isWaitingForResponse,
13353
+ hasActionableApproval: this.hasActionableApproval(),
13354
+ ...buildCliTraceParseSnapshot({
13355
+ accumulatedBuffer: this.accumulatedBuffer,
13356
+ accumulatedRawBuffer: this.accumulatedRawBuffer,
13357
+ responseBuffer: this.responseBuffer,
13358
+ partialResponse: this.responseBuffer,
13359
+ scope: this.currentTurnScope
13360
+ })
13361
+ });
13362
+ this.settledBuffer = this.recentOutputBuffer;
13363
+ this.evaluateSettled();
13364
+ if (this.isWaitingForResponse && !this.hasActionableApproval()) {
13365
+ const detectedStatusAfterEval = this.runDetectStatus(this.recentOutputBuffer);
13366
+ this.recordTrace("response_timeout_kept_open", {
13367
+ timeoutMs,
13368
+ detectedStatusBeforeEval,
13369
+ detectedStatusAfterEval,
13370
+ currentStatus: this.currentStatus,
13371
+ isWaitingForResponse: this.isWaitingForResponse
13372
+ });
13373
+ this.armResponseTimeout();
13374
+ }
13375
+ }, timeoutMs);
13341
13376
  }
13342
13377
  writeSubmitKeyForRetry(mode) {
13343
13378
  void this.writeToPty(this.sendKey).catch((error48) => {
@@ -14228,6 +14263,20 @@ var init_cli_provider_instance = __esm({
14228
14263
  getPresentationMode() {
14229
14264
  return this.presentationMode;
14230
14265
  }
14266
+ getHotChatSessionState() {
14267
+ const adapterStatus = this.adapter.getStatus();
14268
+ const autoApproveActive = adapterStatus.status === "waiting_approval" && this.shouldAutoApprove();
14269
+ const visibleStatus = autoApproveActive ? "generating" : adapterStatus.status;
14270
+ const runtime = this.adapter.getRuntimeMetadata();
14271
+ return {
14272
+ id: this.instanceId,
14273
+ status: visibleStatus,
14274
+ runtimeLifecycle: runtime?.lifecycle ?? null,
14275
+ runtimeSurfaceKind: runtime?.surfaceKind,
14276
+ runtimeRestoredFromStorage: runtime?.restoredFromStorage === true,
14277
+ runtimeRecoveryState: runtime?.recoveryState ?? null
14278
+ };
14279
+ }
14231
14280
  updateSettings(newSettings) {
14232
14281
  this.settings = { ...newSettings };
14233
14282
  this.adapter.updateRuntimeSettings?.(this.settings);
@@ -14383,6 +14432,15 @@ var init_cli_provider_instance = __esm({
14383
14432
  this.completedDebouncePending = { chatTitle, duration: duration3, timestamp: now };
14384
14433
  this.completedDebounceTimer = setTimeout(() => {
14385
14434
  if (this.completedDebouncePending) {
14435
+ const latestStatus = this.adapter.getStatus();
14436
+ const latestAutoApproveActive = latestStatus.status === "waiting_approval" && this.shouldAutoApprove();
14437
+ const latestVisibleStatus = latestAutoApproveActive ? "generating" : latestStatus.status;
14438
+ if (latestVisibleStatus !== "idle") {
14439
+ LOG.info("CLI", `[${this.type}] cancelled pending completed (resumed ${latestVisibleStatus})`);
14440
+ this.completedDebouncePending = null;
14441
+ this.completedDebounceTimer = null;
14442
+ return;
14443
+ }
14386
14444
  LOG.info("CLI", `[${this.type}] completed in ${this.completedDebouncePending.duration}s`);
14387
14445
  this.pushEvent({ event: "agent:generating_completed", ...this.completedDebouncePending });
14388
14446
  this.completedDebouncePending = null;
@@ -37502,6 +37560,51 @@ function killPid(pid) {
37502
37560
  return false;
37503
37561
  }
37504
37562
  }
37563
+ function getWindowsProcessCommandLine(pid) {
37564
+ const pidFilter = `ProcessId=${pid}`;
37565
+ try {
37566
+ const psOut = (0, import_child_process8.execFileSync)("powershell.exe", [
37567
+ "-NoProfile",
37568
+ "-NonInteractive",
37569
+ "-ExecutionPolicy",
37570
+ "Bypass",
37571
+ "-Command",
37572
+ `(Get-CimInstance Win32_Process -Filter "${pidFilter}").CommandLine`
37573
+ ], { encoding: "utf8", timeout: 5e3, stdio: ["ignore", "pipe", "ignore"] }).trim();
37574
+ if (psOut) return psOut;
37575
+ } catch {
37576
+ }
37577
+ try {
37578
+ const wmicOut = (0, import_child_process8.execFileSync)("wmic", [
37579
+ "process",
37580
+ "where",
37581
+ pidFilter,
37582
+ "get",
37583
+ "CommandLine"
37584
+ ], { encoding: "utf8", timeout: 3e3, stdio: ["ignore", "pipe", "ignore"] }).trim();
37585
+ if (wmicOut) return wmicOut;
37586
+ } catch {
37587
+ }
37588
+ return null;
37589
+ }
37590
+ function getProcessCommandLine(pid) {
37591
+ if (!Number.isFinite(pid) || pid <= 0) return null;
37592
+ if (process.platform === "win32") return getWindowsProcessCommandLine(pid);
37593
+ try {
37594
+ const text = (0, import_child_process8.execFileSync)("ps", ["-o", "command=", "-p", String(pid)], {
37595
+ encoding: "utf8",
37596
+ timeout: 3e3,
37597
+ stdio: ["ignore", "pipe", "ignore"]
37598
+ }).trim();
37599
+ return text || null;
37600
+ } catch {
37601
+ return null;
37602
+ }
37603
+ }
37604
+ function isManagedSessionHostPid(pid) {
37605
+ const commandLine = getProcessCommandLine(pid);
37606
+ return !!commandLine && /session-host-daemon/i.test(commandLine);
37607
+ }
37505
37608
  async function waitForPidExit(pid, timeoutMs) {
37506
37609
  const start = Date.now();
37507
37610
  while (Date.now() - start < timeoutMs) {
@@ -37518,7 +37621,7 @@ function stopSessionHostProcesses(appName) {
37518
37621
  try {
37519
37622
  if (fs8.existsSync(pidFile)) {
37520
37623
  const pid = Number.parseInt(fs8.readFileSync(pidFile, "utf8").trim(), 10);
37521
- if (Number.isFinite(pid)) {
37624
+ if (Number.isFinite(pid) && pid !== process.pid && isManagedSessionHostPid(pid)) {
37522
37625
  killPid(pid);
37523
37626
  }
37524
37627
  }
@@ -37529,18 +37632,6 @@ function stopSessionHostProcesses(appName) {
37529
37632
  } catch {
37530
37633
  }
37531
37634
  }
37532
- if (process.platform !== "win32") {
37533
- try {
37534
- const raw = (0, import_child_process8.execFileSync)("pgrep", ["-f", "session-host-daemon"], { encoding: "utf8" }).trim();
37535
- for (const line of raw.split("\n")) {
37536
- const pid = Number.parseInt(line.trim(), 10);
37537
- if (Number.isFinite(pid)) {
37538
- killPid(pid);
37539
- }
37540
- }
37541
- } catch {
37542
- }
37543
- }
37544
37635
  }
37545
37636
  function removeDaemonPidFile() {
37546
37637
  const pidFile = path16.join(os19.homedir(), ".adhdev", "daemon.pid");
@@ -39882,6 +39973,20 @@ var init_forward = __esm({
39882
39973
  });
39883
39974
 
39884
39975
  // ../../oss/packages/daemon-core/src/providers/provider-instance-manager.ts
39976
+ function projectHotChatSessionStatesFromProviderState(state) {
39977
+ const project = (item) => ({
39978
+ id: item.instanceId,
39979
+ status: item.activeChat?.status || item.status,
39980
+ runtimeLifecycle: item.runtime?.lifecycle ?? null,
39981
+ runtimeSurfaceKind: item.runtime?.surfaceKind,
39982
+ runtimeRestoredFromStorage: item.runtime?.restoredFromStorage === true,
39983
+ runtimeRecoveryState: item.runtime?.recoveryState ?? null
39984
+ });
39985
+ if (state.category === "ide") {
39986
+ return [project(state), ...state.extensions.map(project)];
39987
+ }
39988
+ return [project(state)];
39989
+ }
39885
39990
  var ProviderInstanceManager;
39886
39991
  var init_provider_instance_manager = __esm({
39887
39992
  "../../oss/packages/daemon-core/src/providers/provider-instance-manager.ts"() {
@@ -39982,6 +40087,27 @@ var init_provider_instance_manager = __esm({
39982
40087
  }
39983
40088
  return states;
39984
40089
  }
40090
+ collectHotChatSessionStates() {
40091
+ const sessions = [];
40092
+ for (const [id, instance] of this.instances) {
40093
+ try {
40094
+ const projected = instance.getHotChatSessionState?.();
40095
+ if (Array.isArray(projected)) {
40096
+ sessions.push(...projected.filter((session) => !!session?.id));
40097
+ continue;
40098
+ }
40099
+ if (projected?.id) {
40100
+ sessions.push(projected);
40101
+ continue;
40102
+ }
40103
+ const state = instance.getState();
40104
+ sessions.push(...projectHotChatSessionStatesFromProviderState(state));
40105
+ } catch (e) {
40106
+ LOG.warn("InstanceMgr", `[InstanceManager] Failed to collect hot chat metadata from ${id}: ${e.message}`);
40107
+ }
40108
+ }
40109
+ return sessions;
40110
+ }
39985
40111
  /**
39986
40112
  * Per-category status collect
39987
40113
  */
@@ -47464,19 +47590,20 @@ var init_screenshot_sender = __esm({
47464
47590
  }
47465
47591
  return sentAny;
47466
47592
  }
47467
- sendScreenshot(peers, base64Data) {
47593
+ sendScreenshot(peers, base64Data, targetSessionId) {
47468
47594
  const buffer = Buffer.from(base64Data, "base64");
47469
- return this.sendScreenshotBuffer(peers, buffer);
47595
+ return this.sendScreenshotBuffer(peers, buffer, targetSessionId);
47470
47596
  }
47471
47597
  /** Send screenshot as raw Buffer (no base64 conversion overhead) */
47472
- sendScreenshotBuffer(peers, buffer) {
47598
+ sendScreenshotBuffer(peers, buffer, targetSessionId) {
47473
47599
  let sentAny = false;
47474
47600
  let debugOnce = !this._ssDebugDone;
47475
47601
  for (const [pid, peer] of peers.entries()) {
47476
47602
  if (debugOnce) {
47477
- logDebug(`sendScreenshot peer=${pid}: state=${peer.state}, hasCh=${!!peer.dataChannel}, ssActive=${peer.screenshotActive}, chOpen=${peer.dataChannel?.isOpen?.() ?? "N/A"}, bufSize=${buffer.length}`);
47603
+ 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}`);
47478
47604
  }
47479
47605
  if (peer.state !== "connected" || !peer.dataChannel || !peer.screenshotActive) continue;
47606
+ if (targetSessionId && peer.screenshotTargetSessionId !== targetSessionId) continue;
47480
47607
  try {
47481
47608
  if (!peer.dataChannel.isOpen()) continue;
47482
47609
  const header = Buffer.alloc(4);
@@ -47508,18 +47635,32 @@ async function initiateConnection(deps, peerId, sharePermission) {
47508
47635
  log("Cannot initiate \u2014 node-datachannel not available");
47509
47636
  return;
47510
47637
  }
47638
+ const pid = peerId || `legacy_${Date.now()}`;
47639
+ const existing = deps.peers.get(pid);
47640
+ if (existing?.state === "connected") {
47641
+ log(`initiateconnection() ignored for peer ${pid} \u2014 already connected`);
47642
+ return;
47643
+ }
47644
+ if (existing?.state === "connecting") {
47645
+ log(`initiateconnection() ignored for peer ${pid} \u2014 connection already in progress`);
47646
+ return;
47647
+ }
47511
47648
  const limits = deps.serverConn.getPlanLimits();
47512
47649
  if (limits && limits.maxP2PConnections !== -1) {
47513
47650
  let connectedCount = 0;
47651
+ let reservedCount = 0;
47514
47652
  for (const peer of deps.peers.values()) {
47515
47653
  if (peer.state === "connected") connectedCount++;
47654
+ if (peer.state === "connected" || peer.state === "connecting") reservedCount++;
47516
47655
  }
47517
- if (connectedCount >= limits.maxP2PConnections) {
47656
+ if (reservedCount >= limits.maxP2PConnections) {
47518
47657
  let oldestPeer = null;
47519
- for (const [pid2, peer] of deps.peers) {
47520
- if (peer.state === "connected") {
47521
- if (!oldestPeer || peer.connectedAt < oldestPeer.at) {
47522
- oldestPeer = { id: pid2, at: peer.connectedAt };
47658
+ if (connectedCount >= limits.maxP2PConnections) {
47659
+ for (const [peerKey, peer] of deps.peers) {
47660
+ if (peer.state === "connected") {
47661
+ if (!oldestPeer || peer.connectedAt < oldestPeer.at) {
47662
+ oldestPeer = { id: peerKey, at: peer.connectedAt };
47663
+ }
47523
47664
  }
47524
47665
  }
47525
47666
  }
@@ -47537,19 +47678,12 @@ async function initiateConnection(deps, peerId, sharePermission) {
47537
47678
  }
47538
47679
  }
47539
47680
  disconnectPeer(deps.peers, oldestPeer.id, deps.notifyStateChange);
47681
+ } else {
47682
+ log(`P2P limit reached (${reservedCount}/${limits.maxP2PConnections}) with reserved connecting slot(s). Rejecting peer ${pid.slice(0, 12)}\u2026`);
47683
+ return;
47540
47684
  }
47541
47685
  }
47542
47686
  }
47543
- const pid = peerId || `legacy_${Date.now()}`;
47544
- const existing = deps.peers.get(pid);
47545
- if (existing?.state === "connected") {
47546
- log(`initiateconnection() ignored for peer ${pid} \u2014 already connected`);
47547
- return;
47548
- }
47549
- if (existing?.state === "connecting") {
47550
- log(`initiateconnection() ignored for peer ${pid} \u2014 connection already in progress`);
47551
- return;
47552
- }
47553
47687
  log(`initiateconnection() for peer ${pid}...`);
47554
47688
  const mod = deps.nodeDatachannel;
47555
47689
  const PeerConnectionCtor = mod.PeerConnection || mod.default?.PeerConnection || mod.default || mod;
@@ -47908,14 +48042,19 @@ var init_daemon_p2p = __esm({
47908
48042
  }
47909
48043
  return false;
47910
48044
  }
47911
- /** Get the target session for the currently active screenshot request */
47912
- get screenshotTargetSessionId() {
48045
+ /** Get all target sessions for active screenshot requests */
48046
+ get screenshotTargetSessionIds() {
48047
+ const targets = /* @__PURE__ */ new Set();
47913
48048
  for (const peer of this.peers.values()) {
47914
48049
  if (peer.screenshotActive && peer.state === "connected" && peer.screenshotTargetSessionId) {
47915
- return peer.screenshotTargetSessionId;
48050
+ targets.add(peer.screenshotTargetSessionId);
47916
48051
  }
47917
48052
  }
47918
- return void 0;
48053
+ return Array.from(targets);
48054
+ }
48055
+ /** Get the target session for the currently active screenshot request */
48056
+ get screenshotTargetSessionId() {
48057
+ return this.screenshotTargetSessionIds[0];
47919
48058
  }
47920
48059
  constructor(serverConn) {
47921
48060
  this.serverConn = serverConn;
@@ -48024,6 +48163,14 @@ ${e?.stack || ""}`);
48024
48163
  }
48025
48164
  return false;
48026
48165
  }
48166
+ hasAnyNeedingFirstFrameForTarget(targetSessionId) {
48167
+ for (const peer of this.peers.values()) {
48168
+ if (peer.needsFirstFrame && peer.screenshotActive && peer.state === "connected" && peer.screenshotTargetSessionId === targetSessionId) {
48169
+ return true;
48170
+ }
48171
+ }
48172
+ return false;
48173
+ }
48027
48174
  onStateChange(listener) {
48028
48175
  this.stateListeners.push(listener);
48029
48176
  }
@@ -48087,11 +48234,14 @@ ${e?.stack || ""}`);
48087
48234
  if (targetPeers.size === 0) return false;
48088
48235
  return this.screenshotSender.broadcastSessionOutput(targetPeers, sessionId, data);
48089
48236
  }
48090
- sendScreenshot(base64Data) {
48091
- return this.screenshotSender.sendScreenshot(this.peers, base64Data);
48237
+ sendScreenshot(base64Data, targetSessionId) {
48238
+ return this.screenshotSender.sendScreenshot(this.peers, base64Data, targetSessionId);
48092
48239
  }
48093
- sendScreenshotBuffer(buffer) {
48094
- return this.screenshotSender.sendScreenshotBuffer(this.peers, buffer);
48240
+ sendScreenshotBuffer(buffer, targetSessionId) {
48241
+ return this.screenshotSender.sendScreenshotBuffer(this.peers, buffer, targetSessionId);
48242
+ }
48243
+ sendScreenshotBufferForTarget(targetSessionId, buffer) {
48244
+ return this.sendScreenshotBuffer(buffer, targetSessionId);
48095
48245
  }
48096
48246
  // ─── Handler registration (unchanged API) ───────
48097
48247
  onFileRequest(handler) {
@@ -54815,6 +54965,7 @@ var init_screenshot_controller = __esm({
54815
54965
  lastSize = 0;
54816
54966
  lastHash = 0;
54817
54967
  staticFrameCount = 0;
54968
+ targetFrameState = /* @__PURE__ */ new Map();
54818
54969
  currentInterval;
54819
54970
  // Quality profiles
54820
54971
  profileDirect;
@@ -54878,14 +55029,13 @@ var init_screenshot_controller = __esm({
54878
55029
  async tick() {
54879
55030
  if (!this.deps.isRunning()) return;
54880
55031
  const active = this.deps.isScreenshotActive();
54881
- const targetSessionId = this.deps.getScreenshotTargetSessionId();
54882
- if (active && !targetSessionId) {
55032
+ const targetSessionIds = this.getActiveTargetSessionIds();
55033
+ const isRelay = this.deps.isUsingRelay();
55034
+ const profile = isRelay ? this.profileRelay : this.profileDirect;
55035
+ if (active && targetSessionIds.length === 0) {
54883
55036
  this.timer = setTimeout(() => this.tick(), 500);
54884
55037
  return;
54885
55038
  }
54886
- const cdp = targetSessionId ? this.deps.getCdp(targetSessionId) : null;
54887
- const isRelay = this.deps.isUsingRelay();
54888
- const profile = isRelay ? this.profileRelay : this.profileDirect;
54889
55039
  this.checkBudgetReset();
54890
55040
  if (this.dailyBudgetMs > 0) {
54891
55041
  const now = Date.now();
@@ -54900,49 +55050,84 @@ var init_screenshot_controller = __esm({
54900
55050
  }
54901
55051
  }
54902
55052
  const budgetBlocked = this.budgetExhausted && isRelay;
54903
- if (!active || !cdp || budgetBlocked) {
55053
+ if (!active || budgetBlocked) {
54904
55054
  this.staticFrameCount = 0;
55055
+ this.targetFrameState.clear();
54905
55056
  this.currentInterval = profile.maxInterval;
54906
55057
  if (!active) this.lastActiveTimestamp = 0;
54907
55058
  this.timer = setTimeout(() => this.tick(), budgetBlocked ? 3e4 : this.currentInterval);
54908
55059
  return;
54909
55060
  }
54910
55061
  this.debugCount++;
54911
- try {
54912
- const buf = await cdp.captureScreenshot({ quality: profile.quality });
54913
- if (buf) {
55062
+ let capturedAny = false;
55063
+ let sentAnyFrame = false;
55064
+ let anyFirstFrameAcrossTargets = false;
55065
+ for (const targetSessionId of targetSessionIds) {
55066
+ const cdp = this.deps.getCdp(targetSessionId);
55067
+ if (!cdp) continue;
55068
+ try {
55069
+ const buf = await cdp.captureScreenshot({ quality: profile.quality });
55070
+ if (!buf) {
55071
+ if (this.debugCount <= 5) LOG.debug("Screenshot", `captureScreenshot returned null for target=${targetSessionId}`);
55072
+ continue;
55073
+ }
55074
+ capturedAny = true;
55075
+ const state = this.getTargetFrameState(targetSessionId);
54914
55076
  const hash2 = _ScreenshotController.fnvHash(buf);
54915
- const sizeMatch = buf.length === this.lastSize;
54916
- const hashMatch = hash2 === this.lastHash;
54917
- const anyNeedsFirstFrame = this.deps.hasAnyNeedingFirstFrame();
55077
+ const sizeMatch = buf.length === state.lastSize;
55078
+ const hashMatch = hash2 === state.lastHash;
55079
+ const anyNeedsFirstFrame = this.deps.hasAnyNeedingFirstFrameForTarget?.(targetSessionId) ?? this.deps.hasAnyNeedingFirstFrame();
54918
55080
  const resizeTarget = anyNeedsFirstFrame ? profile.firstFrameLongEdge : profile.maxLongEdge;
55081
+ anyFirstFrameAcrossTargets = anyFirstFrameAcrossTargets || anyNeedsFirstFrame;
54919
55082
  if (sizeMatch && hashMatch && !anyNeedsFirstFrame) {
54920
- this.staticFrameCount++;
54921
- if (this.staticFrameCount >= this.STATIC_THRESHOLD) {
55083
+ state.staticFrameCount++;
55084
+ if (state.staticFrameCount >= this.STATIC_THRESHOLD) {
54922
55085
  this.currentInterval = Math.min(this.currentInterval + 200, profile.maxInterval);
54923
55086
  }
54924
55087
  if (this.debugCount <= 5 || this.debugCount % 50 === 0) {
54925
- LOG.debug("Screenshot", `skip (unchanged, static=${this.staticFrameCount}, interval=${this.currentInterval}ms, ${isRelay ? "RELAY" : "DIRECT"})`);
54926
- }
54927
- } else {
54928
- const normalizedBuf = await this.normalizeBuffer(buf, resizeTarget, profile.quality);
54929
- this.lastSize = buf.length;
54930
- this.lastHash = hash2;
54931
- this.staticFrameCount = 0;
54932
- this.currentInterval = profile.minInterval;
54933
- const sent = this.deps.sendScreenshotBuffer(normalizedBuf);
54934
- if (this.debugCount <= 3 || anyNeedsFirstFrame) {
54935
- LOG.debug("Screenshot", `sent: ${normalizedBuf.length} bytes, delivered=${sent}, interval=${this.currentInterval}ms, ${isRelay ? "RELAY" : "DIRECT"}${anyNeedsFirstFrame ? " (first-frame)" : ""}`);
55088
+ LOG.debug("Screenshot", `skip target=${targetSessionId} (unchanged, static=${state.staticFrameCount}, interval=${this.currentInterval}ms, ${isRelay ? "RELAY" : "DIRECT"})`);
54936
55089
  }
55090
+ continue;
54937
55091
  }
54938
- } else {
54939
- if (this.debugCount <= 5) LOG.debug("Screenshot", "captureScreenshot returned null");
55092
+ const normalizedBuf = await this.normalizeBuffer(buf, resizeTarget, profile.quality);
55093
+ state.lastSize = buf.length;
55094
+ state.lastHash = hash2;
55095
+ state.staticFrameCount = 0;
55096
+ this.lastSize = buf.length;
55097
+ this.lastHash = hash2;
55098
+ this.staticFrameCount = 0;
55099
+ this.currentInterval = profile.minInterval;
55100
+ const sent = this.deps.sendScreenshotBufferForTarget ? this.deps.sendScreenshotBufferForTarget(targetSessionId, normalizedBuf) : this.deps.sendScreenshotBuffer(normalizedBuf);
55101
+ sentAnyFrame = sentAnyFrame || sent;
55102
+ if (this.debugCount <= 3 || anyNeedsFirstFrame) {
55103
+ LOG.debug("Screenshot", `sent target=${targetSessionId}: ${normalizedBuf.length} bytes, delivered=${sent}, interval=${this.currentInterval}ms, ${isRelay ? "RELAY" : "DIRECT"}${anyNeedsFirstFrame ? " (first-frame)" : ""}`);
55104
+ }
55105
+ } catch (e) {
55106
+ if (this.debugCount <= 5) LOG.warn("Screenshot", `error target=${targetSessionId}: ${e?.message}`);
54940
55107
  }
54941
- } catch (e) {
54942
- if (this.debugCount <= 5) LOG.warn("Screenshot", `error: ${e?.message}`);
55108
+ }
55109
+ if (!capturedAny) {
55110
+ this.currentInterval = profile.maxInterval;
55111
+ } else if (!sentAnyFrame && !anyFirstFrameAcrossTargets) {
55112
+ this.currentInterval = Math.min(this.currentInterval + 200, profile.maxInterval);
54943
55113
  }
54944
55114
  this.timer = setTimeout(() => this.tick(), this.currentInterval);
54945
55115
  }
55116
+ getActiveTargetSessionIds() {
55117
+ const explicitTargets = this.deps.getScreenshotTargetSessionIds?.() || [];
55118
+ const normalized = explicitTargets.map((target) => typeof target === "string" ? target.trim() : "").filter((target) => target.length > 0);
55119
+ if (normalized.length > 0) return Array.from(new Set(normalized));
55120
+ const legacyTarget = this.deps.getScreenshotTargetSessionId();
55121
+ return legacyTarget ? [legacyTarget] : [];
55122
+ }
55123
+ getTargetFrameState(targetSessionId) {
55124
+ let state = this.targetFrameState.get(targetSessionId);
55125
+ if (!state) {
55126
+ state = { lastSize: 0, lastHash: 0, staticFrameCount: 0 };
55127
+ this.targetFrameState.set(targetSessionId, state);
55128
+ }
55129
+ return state;
55130
+ }
54946
55131
  // ─── Budget ───────────────────────────────────
54947
55132
  checkBudgetReset() {
54948
55133
  const today = (/* @__PURE__ */ new Date()).toISOString().slice(0, 10);
@@ -55133,7 +55318,7 @@ function killPid2(pid) {
55133
55318
  return false;
55134
55319
  }
55135
55320
  }
55136
- function getWindowsProcessCommandLine(pid) {
55321
+ function getWindowsProcessCommandLine2(pid) {
55137
55322
  const pidFilter = `ProcessId=${pid}`;
55138
55323
  try {
55139
55324
  const psOut = (0, import_child_process12.execFileSync)("powershell.exe", [
@@ -55162,13 +55347,32 @@ function getWindowsProcessCommandLine(pid) {
55162
55347
  }
55163
55348
  return null;
55164
55349
  }
55350
+ function getProcessCommandLine2(pid) {
55351
+ if (!Number.isFinite(pid) || pid <= 0) return null;
55352
+ if (process.platform === "win32") return getWindowsProcessCommandLine2(pid);
55353
+ try {
55354
+ const text = (0, import_child_process12.execFileSync)("ps", ["-o", "command=", "-p", String(pid)], {
55355
+ encoding: "utf8",
55356
+ timeout: 3e3,
55357
+ stdio: ["ignore", "pipe", "ignore"]
55358
+ }).trim();
55359
+ return text || null;
55360
+ } catch {
55361
+ return null;
55362
+ }
55363
+ }
55364
+ function isManagedSessionHostPid2(pid) {
55365
+ const commandLine = getProcessCommandLine2(pid);
55366
+ if (!commandLine) return false;
55367
+ return /session-host-daemon/i.test(commandLine);
55368
+ }
55165
55369
  function stopManagedSessionHostProcess() {
55166
55370
  let stopped = false;
55167
55371
  const pidFile = getSessionHostPidFile();
55168
55372
  try {
55169
55373
  if (fs17.existsSync(pidFile)) {
55170
55374
  const pid = Number.parseInt(fs17.readFileSync(pidFile, "utf8").trim(), 10);
55171
- if (Number.isFinite(pid) && pid !== process.pid) {
55375
+ if (Number.isFinite(pid) && pid !== process.pid && isManagedSessionHostPid2(pid)) {
55172
55376
  stopped = killPid2(pid) || stopped;
55173
55377
  }
55174
55378
  }
@@ -55182,40 +55386,7 @@ function stopManagedSessionHostProcess() {
55182
55386
  return stopped;
55183
55387
  }
55184
55388
  function stopSessionHost() {
55185
- let stopped = stopManagedSessionHostProcess();
55186
- if (process.platform === "win32") {
55187
- try {
55188
- const raw = (0, import_child_process12.execFileSync)("tasklist", ["/FO", "CSV", "/NH", "/FI", "IMAGENAME eq node.exe"], {
55189
- encoding: "utf8",
55190
- timeout: 5e3,
55191
- stdio: ["ignore", "pipe", "ignore"],
55192
- windowsHide: true
55193
- }).trim();
55194
- for (const line of raw.split(/\r?\n/)) {
55195
- const match = line.match(/^"node\.exe","(\d+)"/i);
55196
- if (!match) continue;
55197
- const candidatePid = Number.parseInt(match[1], 10);
55198
- if (!Number.isFinite(candidatePid) || candidatePid === process.pid) continue;
55199
- const commandLine = getWindowsProcessCommandLine(candidatePid);
55200
- if (commandLine?.includes("session-host-daemon")) {
55201
- stopped = killPid2(candidatePid) || stopped;
55202
- }
55203
- }
55204
- } catch {
55205
- }
55206
- } else {
55207
- try {
55208
- const raw = (0, import_child_process12.execFileSync)("pgrep", ["-f", "session-host-daemon"], { encoding: "utf8" }).trim();
55209
- for (const line of raw.split("\n")) {
55210
- const pid = Number.parseInt(line.trim(), 10);
55211
- if (Number.isFinite(pid) && pid !== process.pid && pid !== process.ppid) {
55212
- stopped = killPid2(pid) || stopped;
55213
- }
55214
- }
55215
- } catch {
55216
- }
55217
- }
55218
- return stopped;
55389
+ return stopManagedSessionHostProcess();
55219
55390
  }
55220
55391
  async function ensureSessionHostReady2() {
55221
55392
  const quarantine = quarantineLegacyStandaloneSessions({
@@ -55992,10 +56163,65 @@ var init_version = __esm({
55992
56163
  var adhdev_daemon_exports = {};
55993
56164
  __export(adhdev_daemon_exports, {
55994
56165
  AdhdevDaemon: () => AdhdevDaemon,
56166
+ buildMandatoryUpdateInfoFromServerPayload: () => buildMandatoryUpdateInfoFromServerPayload,
56167
+ buildMandatoryUpdateRequiredPayload: () => buildMandatoryUpdateRequiredPayload,
55995
56168
  getDaemonPid: () => getDaemonPid,
55996
56169
  isDaemonRunning: () => isDaemonRunning,
55997
- stopDaemon: () => stopDaemon
55998
- });
56170
+ stopDaemon: () => stopDaemon,
56171
+ validateMandatoryUpdateTarget: () => validateMandatoryUpdateTarget,
56172
+ verifyPublishedMandatoryUpdateTarget: () => verifyPublishedMandatoryUpdateTarget
56173
+ });
56174
+ function validateMandatoryUpdateTarget(targetVersion) {
56175
+ if (typeof targetVersion !== "string") return { valid: false, error: "target version is required" };
56176
+ if (targetVersion !== targetVersion.trim()) return { valid: false, error: "target version must not contain whitespace" };
56177
+ const version2 = targetVersion;
56178
+ if (!version2) return { valid: false, error: "target version is required" };
56179
+ if (!/^\d+\.\d+\.\d+(?:-[0-9A-Za-z]+(?:\.[0-9A-Za-z]+)*)?(?:\+[0-9A-Za-z]+(?:\.[0-9A-Za-z]+)*)?$/.test(version2)) {
56180
+ return { valid: false, error: `invalid semver target: ${version2}` };
56181
+ }
56182
+ return { valid: true };
56183
+ }
56184
+ function verifyPublishedMandatoryUpdateTarget(packageName, targetVersion, deps = {}) {
56185
+ if (!/^(?:adhdev|@adhdev\/daemon-standalone)$/.test(packageName)) {
56186
+ throw new Error(`invalid mandatory update package: ${packageName}`);
56187
+ }
56188
+ const validation = validateMandatoryUpdateTarget(targetVersion);
56189
+ if (!validation.valid) throw new Error(validation.error || "invalid mandatory update target");
56190
+ const run = deps.execFileSync || import_child_process13.execFileSync;
56191
+ const npmExecutable = deps.npmExecutable || resolveCurrentGlobalInstallSurface({ packageName }).npmExecutable;
56192
+ const published = String(run(npmExecutable, ["view", `${packageName}@${targetVersion}`, "version"], {
56193
+ encoding: "utf-8",
56194
+ timeout: 1e4,
56195
+ stdio: ["pipe", "pipe", "pipe"],
56196
+ ...process.platform === "win32" ? { shell: true, windowsHide: true } : {}
56197
+ })).trim();
56198
+ if (published !== targetVersion) {
56199
+ throw new Error(`Published version mismatch: expected ${targetVersion}, got ${published || "unknown"}`);
56200
+ }
56201
+ return published;
56202
+ }
56203
+ function buildMandatoryUpdateInfoFromServerPayload(payload, fallbackVersion) {
56204
+ const targetVersion = typeof payload?.latest === "string" && payload.latest ? payload.latest : fallbackVersion;
56205
+ const validation = validateMandatoryUpdateTarget(targetVersion);
56206
+ if (!validation.valid) return { info: null, error: validation.error || targetVersion };
56207
+ return {
56208
+ info: {
56209
+ targetVersion,
56210
+ reason: typeof payload?.reason === "string" && payload.reason.trim() ? payload.reason.trim() : "major_minor_mismatch",
56211
+ minVersion: typeof payload?.minVersion === "string" && payload.minVersion.trim() ? payload.minVersion.trim() : void 0
56212
+ }
56213
+ };
56214
+ }
56215
+ function buildMandatoryUpdateRequiredPayload(pending, interactionId) {
56216
+ return {
56217
+ error: "A mandatory daemon update is pending. Finish current work and let the daemon update before starting a new session.",
56218
+ code: "DAEMON_UPDATE_REQUIRED",
56219
+ required: true,
56220
+ latest: pending.targetVersion,
56221
+ reason: pending.reason,
56222
+ interactionId
56223
+ };
56224
+ }
55999
56225
  function resolveDaemonPort(ref = {}) {
56000
56226
  return Number.isFinite(ref.port) && Number(ref.port) > 0 ? Number(ref.port) : DEFAULT_DAEMON_PORT;
56001
56227
  }
@@ -56022,7 +56248,7 @@ function removeDaemonPid(ref = {}) {
56022
56248
  function isDaemonRunning(ref = {}) {
56023
56249
  const port = resolveDaemonPort(ref);
56024
56250
  try {
56025
- const { execFileSync: execFileSync5 } = require("child_process");
56251
+ const { execFileSync: execFileSync6 } = require("child_process");
56026
56252
  const probe = `
56027
56253
  const http = require('http');
56028
56254
  const req = http.get('http://127.0.0.1:${port}/health', { timeout: 1500 }, (res) => {
@@ -56032,7 +56258,7 @@ function isDaemonRunning(ref = {}) {
56032
56258
  req.on('error', () => process.stdout.write('0'));
56033
56259
  req.on('timeout', () => { req.destroy(); process.stdout.write('0'); });
56034
56260
  `;
56035
- const result = execFileSync5(process.execPath, ["-e", probe], {
56261
+ const result = execFileSync6(process.execPath, ["-e", probe], {
56036
56262
  encoding: "utf-8",
56037
56263
  timeout: 3e3,
56038
56264
  stdio: ["ignore", "pipe", "ignore"]
@@ -56058,9 +56284,9 @@ function isDaemonRunning(ref = {}) {
56058
56284
  function isAdhdevProcess(pid) {
56059
56285
  try {
56060
56286
  if (process.platform === "win32") {
56061
- const { execFileSync: execFileSync5 } = require("child_process");
56287
+ const { execFileSync: execFileSync6 } = require("child_process");
56062
56288
  try {
56063
- const psOut = execFileSync5("powershell.exe", [
56289
+ const psOut = execFileSync6("powershell.exe", [
56064
56290
  "-NoProfile",
56065
56291
  "-NonInteractive",
56066
56292
  "-ExecutionPolicy",
@@ -56073,8 +56299,8 @@ function isAdhdevProcess(pid) {
56073
56299
  return true;
56074
56300
  }
56075
56301
  } else {
56076
- const { execFileSync: execFileSync5 } = require("child_process");
56077
- const cmdline = execFileSync5("ps", ["-o", "command=", "-p", String(pid)], {
56302
+ const { execFileSync: execFileSync6 } = require("child_process");
56303
+ const cmdline = execFileSync6("ps", ["-o", "command=", "-p", String(pid)], {
56078
56304
  encoding: "utf-8",
56079
56305
  timeout: 2e3,
56080
56306
  stdio: ["ignore", "pipe", "ignore"]
@@ -56088,7 +56314,7 @@ function isAdhdevProcess(pid) {
56088
56314
  function getDaemonHealthPid(ref = {}) {
56089
56315
  const port = resolveDaemonPort(ref);
56090
56316
  try {
56091
- const { execFileSync: execFileSync5 } = require("child_process");
56317
+ const { execFileSync: execFileSync6 } = require("child_process");
56092
56318
  const probe = `
56093
56319
  const http = require('http');
56094
56320
  const req = http.get('http://127.0.0.1:${port}/health', { timeout: 1500 }, (res) => {
@@ -56106,7 +56332,7 @@ function getDaemonHealthPid(ref = {}) {
56106
56332
  req.on('error', () => {});
56107
56333
  req.on('timeout', () => { req.destroy(); });
56108
56334
  `;
56109
- const result = execFileSync5(process.execPath, ["-e", probe], {
56335
+ const result = execFileSync6(process.execPath, ["-e", probe], {
56110
56336
  encoding: "utf-8",
56111
56337
  timeout: 3e3,
56112
56338
  stdio: ["ignore", "pipe", "ignore"]
@@ -56152,7 +56378,7 @@ function stopDaemon(ref = {}) {
56152
56378
  return false;
56153
56379
  }
56154
56380
  }
56155
- var os26, fs18, path26, import_http, import_ws3, pkgVersion, AdhdevDaemon;
56381
+ var os26, fs18, path26, import_http, import_child_process13, import_ws3, pkgVersion, AdhdevDaemon;
56156
56382
  var init_adhdev_daemon = __esm({
56157
56383
  "src/adhdev-daemon.ts"() {
56158
56384
  "use strict";
@@ -56168,12 +56394,13 @@ var init_adhdev_daemon = __esm({
56168
56394
  fs18 = __toESM(require("fs"));
56169
56395
  path26 = __toESM(require("path"));
56170
56396
  import_http = require("http");
56397
+ import_child_process13 = require("child_process");
56171
56398
  import_ws3 = require("ws");
56172
56399
  init_source2();
56173
56400
  init_version();
56174
56401
  init_src();
56175
56402
  init_runtime_defaults();
56176
- pkgVersion = resolvePackageVersion({ injectedVersion: "0.9.33" });
56403
+ pkgVersion = resolvePackageVersion({ injectedVersion: "0.9.34" });
56177
56404
  AdhdevDaemon = class _AdhdevDaemon {
56178
56405
  localHttpServer = null;
56179
56406
  localWss = null;
@@ -56266,6 +56493,10 @@ var init_adhdev_daemon = __esm({
56266
56493
  getUpgradePackageName() {
56267
56494
  return process.argv[1]?.includes("daemon-standalone") ? "@adhdev/daemon-standalone" : "adhdev";
56268
56495
  }
56496
+ getMandatoryUpdateBlockPayload(cmd, interactionId) {
56497
+ if (!this.pendingMandatoryUpdate || !_AdhdevDaemon.MANDATORY_UPDATE_BLOCKED_COMMANDS.has(cmd)) return null;
56498
+ return buildMandatoryUpdateRequiredPayload(this.pendingMandatoryUpdate, interactionId);
56499
+ }
56269
56500
  hasBlockingSessionsForMandatoryUpdate() {
56270
56501
  if (!this.components) return false;
56271
56502
  const blocking = /* @__PURE__ */ new Set(["generating", "waiting_approval", "starting"]);
@@ -56291,15 +56522,7 @@ var init_adhdev_daemon = __esm({
56291
56522
  const pkgName = this.getUpgradePackageName();
56292
56523
  this.mandatoryUpgradeInFlight = true;
56293
56524
  try {
56294
- const { execSync: execSync7 } = await import("child_process");
56295
- const published = execSync7(`npm view ${pkgName}@${pending.targetVersion} version`, {
56296
- encoding: "utf-8",
56297
- timeout: 1e4,
56298
- stdio: ["pipe", "pipe", "pipe"]
56299
- }).trim();
56300
- if (published !== pending.targetVersion) {
56301
- throw new Error(`Published version mismatch: expected ${pending.targetVersion}, got ${published || "unknown"}`);
56302
- }
56525
+ verifyPublishedMandatoryUpdateTarget(pkgName, pending.targetVersion);
56303
56526
  LOG.warn("Upgrade", `Applying mandatory daemon update (${pending.reason}) \u2192 v${pending.targetVersion}`);
56304
56527
  spawnDetachedDaemonUpgradeHelper({
56305
56528
  packageName: pkgName,
@@ -56401,7 +56624,7 @@ var init_adhdev_daemon = __esm({
56401
56624
  const now = Date.now();
56402
56625
  const cached2 = this.hotChatSnapshotCache;
56403
56626
  const sessions = cached2 && now - cached2.builtAt < _AdhdevDaemon.HOT_CHAT_SNAPSHOT_CACHE_TTL_MS ? cached2.sessions : (() => {
56404
- const built = this.buildLiveStatusSnapshot().sessions || [];
56627
+ const built = this.components.instanceManager.collectHotChatSessionStates();
56405
56628
  this.hotChatSnapshotCache = { sessions: built, builtAt: now };
56406
56629
  return built;
56407
56630
  })();
@@ -56786,14 +57009,17 @@ ${err?.stack || ""}`);
56786
57009
  isRunning: () => this.running,
56787
57010
  isScreenshotActive: () => this.p2p?.screenshotActive ?? false,
56788
57011
  getScreenshotTargetSessionId: () => this.p2p?.screenshotTargetSessionId,
57012
+ getScreenshotTargetSessionIds: () => this.p2p?.screenshotTargetSessionIds ?? [],
56789
57013
  isUsingRelay: () => this.p2p?.isUsingRelay ?? false,
56790
57014
  hasAnyNeedingFirstFrame: () => this.p2p?.hasAnyNeedingFirstFrame() ?? false,
57015
+ hasAnyNeedingFirstFrameForTarget: (targetSessionId) => this.p2p?.hasAnyNeedingFirstFrameForTarget(targetSessionId) ?? false,
56791
57016
  getCdp: (targetSessionId) => {
56792
57017
  if (targetSessionId) return this.getCdpFor(targetSessionId);
56793
57018
  LOG.warn("P2P", "Screenshot requested without targetSessionId \u2014 cannot determine target session. Skipping frame.");
56794
57019
  return null;
56795
57020
  },
56796
- sendScreenshotBuffer: (buf) => this.p2p.sendScreenshotBuffer(buf)
57021
+ sendScreenshotBuffer: (buf) => this.p2p.sendScreenshotBuffer(buf),
57022
+ sendScreenshotBufferForTarget: (targetSessionId, buf) => this.p2p.sendScreenshotBufferForTarget(targetSessionId, buf)
56797
57023
  }, planLimits ?? void 0);
56798
57024
  this.screenshotController.start();
56799
57025
  this.p2p.onScreenshotStart(() => this.screenshotController?.triggerImmediate());
@@ -56822,11 +57048,12 @@ ${err?.stack || ""}`);
56822
57048
  });
56823
57049
  this.serverConn.on("force_update_required", (msg) => {
56824
57050
  const payload = msg.payload;
56825
- this.pendingMandatoryUpdate = {
56826
- targetVersion: typeof payload.latest === "string" && payload.latest.trim() ? payload.latest.trim() : pkgVersion,
56827
- reason: typeof payload.reason === "string" && payload.reason.trim() ? payload.reason.trim() : "major_minor_mismatch",
56828
- minVersion: typeof payload.minVersion === "string" && payload.minVersion.trim() ? payload.minVersion.trim() : void 0
56829
- };
57051
+ const parsedUpdate = buildMandatoryUpdateInfoFromServerPayload(payload, pkgVersion);
57052
+ if (!parsedUpdate.info) {
57053
+ LOG.error("Upgrade", `Ignoring invalid mandatory daemon update target from server: ${parsedUpdate.error || "unknown"}`);
57054
+ return;
57055
+ }
57056
+ this.pendingMandatoryUpdate = parsedUpdate.info;
56830
57057
  LOG.warn("Upgrade", `Mandatory daemon update required (${this.pendingMandatoryUpdate.reason})`);
56831
57058
  void this.maybeApplyMandatoryUpdate("server-force-update");
56832
57059
  });
@@ -56937,15 +57164,9 @@ ${err?.stack || ""}`);
56937
57164
  const cmdStart = Date.now();
56938
57165
  const source = msg.ipcWs ? "ext" : typeof msg.source === "string" && msg.source.trim() ? msg.source : "ws";
56939
57166
  try {
56940
- if (this.pendingMandatoryUpdate && _AdhdevDaemon.MANDATORY_UPDATE_BLOCKED_COMMANDS.has(cmd)) {
56941
- this.sendResult(msg, false, {
56942
- error: "A mandatory daemon update is pending. Finish current work and let the daemon update before starting a new session.",
56943
- code: "DAEMON_UPDATE_REQUIRED",
56944
- required: true,
56945
- latest: this.pendingMandatoryUpdate.targetVersion,
56946
- reason: this.pendingMandatoryUpdate.reason,
56947
- interactionId
56948
- });
57167
+ const mandatoryUpdateBlock = this.getMandatoryUpdateBlockPayload(cmd, interactionId);
57168
+ if (mandatoryUpdateBlock) {
57169
+ this.sendResult(msg, false, mandatoryUpdateBlock);
56949
57170
  return;
56950
57171
  }
56951
57172
  if (source === "api" && !loadConfig().allowServerApiProxy) {
@@ -56990,6 +57211,10 @@ ${err?.stack || ""}`);
56990
57211
  const interactionId = String(normalizedData._interactionId);
56991
57212
  const cmdStart = Date.now();
56992
57213
  try {
57214
+ const mandatoryUpdateBlock = this.getMandatoryUpdateBlockPayload(cmdType, interactionId);
57215
+ if (mandatoryUpdateBlock) {
57216
+ return { success: false, ...mandatoryUpdateBlock };
57217
+ }
56993
57218
  switch (cmdType) {
56994
57219
  case "get_runtime_snapshot": {
56995
57220
  const sessionId = typeof normalizedData.sessionId === "string" ? normalizedData.sessionId : "";
@@ -57168,8 +57393,22 @@ ${err?.stack || ""}`);
57168
57393
  }));
57169
57394
  return;
57170
57395
  }
57396
+ const normalizedArgs = this.ensureInteractionContext(args);
57397
+ const interactionId = String(normalizedArgs._interactionId);
57398
+ const mandatoryUpdateBlock = this.getMandatoryUpdateBlockPayload(command, interactionId);
57399
+ if (mandatoryUpdateBlock) {
57400
+ ws.send(JSON.stringify({
57401
+ type: "ext:command_result",
57402
+ payload: {
57403
+ requestId,
57404
+ success: false,
57405
+ ...mandatoryUpdateBlock
57406
+ }
57407
+ }));
57408
+ return;
57409
+ }
57171
57410
  try {
57172
- const result = await this.components.router.execute(command, args, "ipc");
57411
+ const result = await this.components.router.execute(command, normalizedArgs, "ipc");
57173
57412
  ws.send(JSON.stringify({
57174
57413
  type: "ext:command_result",
57175
57414
  payload: {
@@ -85994,12 +86233,12 @@ function splitStringBySpace(str) {
85994
86233
  }
85995
86234
  return pieces;
85996
86235
  }
85997
- var import_chardet, import_child_process13, import_fs7, import_node_path2, import_node_os4, import_node_crypto, import_iconv_lite, ExternalEditor;
86236
+ var import_chardet, import_child_process14, import_fs7, import_node_path2, import_node_os4, import_node_crypto, import_iconv_lite, ExternalEditor;
85998
86237
  var init_esm2 = __esm({
85999
86238
  "../../node_modules/@inquirer/external-editor/dist/esm/index.js"() {
86000
86239
  "use strict";
86001
86240
  import_chardet = __toESM(require_lib2(), 1);
86002
- import_child_process13 = require("child_process");
86241
+ import_child_process14 = require("child_process");
86003
86242
  import_fs7 = require("fs");
86004
86243
  import_node_path2 = __toESM(require("path"), 1);
86005
86244
  import_node_os4 = __toESM(require("os"), 1);
@@ -86106,7 +86345,7 @@ var init_esm2 = __esm({
86106
86345
  }
86107
86346
  launchEditor() {
86108
86347
  try {
86109
- const editorProcess = (0, import_child_process13.spawnSync)(this.editor.bin, this.editor.args.concat([this.tempFile]), { stdio: "inherit" });
86348
+ const editorProcess = (0, import_child_process14.spawnSync)(this.editor.bin, this.editor.args.concat([this.tempFile]), { stdio: "inherit" });
86110
86349
  this.lastExitStatus = editorProcess.status ?? 0;
86111
86350
  } catch (launchError) {
86112
86351
  throw new LaunchEditorError(launchError);
@@ -86114,7 +86353,7 @@ var init_esm2 = __esm({
86114
86353
  }
86115
86354
  launchEditorAsync(callback) {
86116
86355
  try {
86117
- const editorProcess = (0, import_child_process13.spawn)(this.editor.bin, this.editor.args.concat([this.tempFile]), { stdio: "inherit" });
86356
+ const editorProcess = (0, import_child_process14.spawn)(this.editor.bin, this.editor.args.concat([this.tempFile]), { stdio: "inherit" });
86118
86357
  editorProcess.on("exit", (code) => {
86119
86358
  this.lastExitStatus = code;
86120
86359
  setImmediate(callback);
@@ -88392,9 +88631,9 @@ async function runWizard(options = {}) {
88392
88631
  }
88393
88632
  async function checkForUpdate() {
88394
88633
  try {
88395
- const { execFileSync: execFileSync5 } = await import("child_process");
88634
+ const { execFileSync: execFileSync6 } = await import("child_process");
88396
88635
  const currentVersion = resolvePackageVersion();
88397
- const latestVersion = readLatestPublishedCliVersion(execFileSync5);
88636
+ const latestVersion = readLatestPublishedCliVersion(execFileSync6);
88398
88637
  if (!latestVersion) return;
88399
88638
  if (!currentVersion || !latestVersion || currentVersion === latestVersion) return;
88400
88639
  console.log(source_default2.yellow(` Update available: ${currentVersion} \u2192 ${latestVersion}`));
@@ -88411,7 +88650,7 @@ async function checkForUpdate() {
88411
88650
  const spinner = (await Promise.resolve().then(() => (init_ora(), ora_exports))).default("Updating adhdev CLI...").start();
88412
88651
  try {
88413
88652
  const installCommand = buildPinnedGlobalInstallCommand({ packageName: "adhdev", targetVersion: "latest" });
88414
- execFileSync5(installCommand.command, installCommand.args, {
88653
+ execFileSync6(installCommand.command, installCommand.args, {
88415
88654
  encoding: "utf-8",
88416
88655
  timeout: 6e4,
88417
88656
  stdio: ["pipe", "pipe", "pipe"]