adhdev 0.9.32 → 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
@@ -7981,6 +7981,26 @@ function toHistoryPersistedMessages(messages) {
7981
7981
  historyDedupKey: deriveHistoryDedupKey(message)
7982
7982
  }));
7983
7983
  }
7984
+ function findLastMessageIndexBySignature(messages, signature) {
7985
+ if (!signature) return -1;
7986
+ for (let index = messages.length - 1; index >= 0; index -= 1) {
7987
+ if (getChatMessageSignature(messages[index]) === signature) {
7988
+ return index;
7989
+ }
7990
+ }
7991
+ return -1;
7992
+ }
7993
+ function buildBoundedTailSync(messages, cursor) {
7994
+ const totalMessages = messages.length;
7995
+ const tailMessages = cursor.tailLimit > 0 && totalMessages > cursor.tailLimit ? messages.slice(-cursor.tailLimit) : messages;
7996
+ return {
7997
+ syncMode: "full",
7998
+ replaceFrom: 0,
7999
+ messages: tailMessages,
8000
+ totalMessages,
8001
+ lastMessageSignature: getChatMessageSignature(messages[totalMessages - 1])
8002
+ };
8003
+ }
7984
8004
  function computeReadChatSync(messages, cursor) {
7985
8005
  const totalMessages = messages.length;
7986
8006
  const lastMessageSignature = getChatMessageSignature(messages[totalMessages - 1]);
@@ -8012,6 +8032,15 @@ function computeReadChatSync(messages, cursor) {
8012
8032
  lastMessageSignature
8013
8033
  };
8014
8034
  }
8035
+ if (cursor.tailLimit > 0 && knownSignature === lastMessageSignature) {
8036
+ return {
8037
+ syncMode: "noop",
8038
+ replaceFrom: totalMessages,
8039
+ messages: [],
8040
+ totalMessages,
8041
+ lastMessageSignature
8042
+ };
8043
+ }
8015
8044
  if (knownMessageCount < totalMessages) {
8016
8045
  const anchorSignature = getChatMessageSignature(messages[knownMessageCount - 1]);
8017
8046
  if (anchorSignature === knownSignature) {
@@ -8023,6 +8052,19 @@ function computeReadChatSync(messages, cursor) {
8023
8052
  lastMessageSignature
8024
8053
  };
8025
8054
  }
8055
+ if (cursor.tailLimit > 0) {
8056
+ const signatureIndex = findLastMessageIndexBySignature(messages, knownSignature);
8057
+ if (signatureIndex >= 0) {
8058
+ return {
8059
+ syncMode: "append",
8060
+ replaceFrom: knownMessageCount,
8061
+ messages: messages.slice(signatureIndex + 1),
8062
+ totalMessages,
8063
+ lastMessageSignature
8064
+ };
8065
+ }
8066
+ return buildBoundedTailSync(messages, cursor);
8067
+ }
8026
8068
  }
8027
8069
  const replaceFrom = Math.max(0, Math.min(knownMessageCount - 1, totalMessages));
8028
8070
  return {
@@ -12327,13 +12369,14 @@ function sliceFromOffset(text, start) {
12327
12369
  function hydrateCliParsedMessages(parsedMessages, options) {
12328
12370
  const { committedMessages, scope, lastOutputAt } = options;
12329
12371
  const referenceMessages = [...committedMessages];
12372
+ const referenceComparables = referenceMessages.map((message) => normalizeComparableMessageContent(message?.content || ""));
12330
12373
  const usedReferenceIndexes = /* @__PURE__ */ new Set();
12331
12374
  const now = options.now ?? Date.now();
12332
12375
  const findReferenceTimestamp = (role, content, parsedIndex) => {
12333
12376
  const normalizedContent = normalizeComparableMessageContent(content);
12334
12377
  if (!normalizedContent) return void 0;
12335
12378
  const sameIndex = referenceMessages[parsedIndex];
12336
- 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)) {
12337
12380
  usedReferenceIndexes.add(parsedIndex);
12338
12381
  return sameIndex.timestamp;
12339
12382
  }
@@ -12341,7 +12384,7 @@ function hydrateCliParsedMessages(parsedMessages, options) {
12341
12384
  if (usedReferenceIndexes.has(i)) continue;
12342
12385
  const candidate = referenceMessages[i];
12343
12386
  if (!candidate || candidate.role !== role) continue;
12344
- const candidateContent = normalizeComparableMessageContent(candidate.content);
12387
+ const candidateContent = referenceComparables[i];
12345
12388
  if (!candidateContent) continue;
12346
12389
  const exactMatch = candidateContent === normalizedContent;
12347
12390
  const fuzzyMatch = candidateContent.includes(normalizedContent) || normalizedContent.includes(candidateContent);
@@ -13433,7 +13476,7 @@ var init_provider_cli_adapter = __esm({
13433
13476
  return;
13434
13477
  }
13435
13478
  if (this.currentTurnScope && !lastParsedAssistant) {
13436
- LOG.info(
13479
+ LOG.debug(
13437
13480
  "CLI",
13438
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 || "-"}`
13439
13482
  );
@@ -14249,9 +14292,43 @@ var init_provider_cli_adapter = __esm({
14249
14292
  }
14250
14293
  armResponseTimeout() {
14251
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
+ }
14252
14300
  this.responseTimeout = setTimeout(() => {
14253
- if (this.isWaitingForResponse) this.finishResponse();
14254
- }, 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);
14255
14332
  }
14256
14333
  writeSubmitKeyForRetry(mode) {
14257
14334
  void this.writeToPty(this.sendKey).catch((error48) => {
@@ -15142,6 +15219,20 @@ var init_cli_provider_instance = __esm({
15142
15219
  getPresentationMode() {
15143
15220
  return this.presentationMode;
15144
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
+ }
15145
15236
  updateSettings(newSettings) {
15146
15237
  this.settings = { ...newSettings };
15147
15238
  this.adapter.updateRuntimeSettings?.(this.settings);
@@ -15297,6 +15388,15 @@ var init_cli_provider_instance = __esm({
15297
15388
  this.completedDebouncePending = { chatTitle, duration: duration3, timestamp: now };
15298
15389
  this.completedDebounceTimer = setTimeout(() => {
15299
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
+ }
15300
15400
  LOG.info("CLI", `[${this.type}] completed in ${this.completedDebouncePending.duration}s`);
15301
15401
  this.pushEvent({ event: "agent:generating_completed", ...this.completedDebouncePending });
15302
15402
  this.completedDebouncePending = null;
@@ -36425,10 +36525,22 @@ var init_provider_loader = __esm({
36425
36525
  setMachineProviderEnabled(type, enabled) {
36426
36526
  return this.setMachineProviderConfig(type, { enabled });
36427
36527
  }
36528
+ getEffectiveProviderAvailability(type) {
36529
+ const providerType = this.resolveAlias(type);
36530
+ const availability = this.providerAvailability.get(providerType);
36531
+ if (availability) return availability;
36532
+ const machineConfig = this.getMachineProviderConfig(providerType);
36533
+ const lastDetection = machineConfig.lastDetection;
36534
+ if (!lastDetection) return void 0;
36535
+ return {
36536
+ installed: lastDetection.ok === true,
36537
+ detectedPath: typeof lastDetection.path === "string" && lastDetection.path.trim() ? lastDetection.path.trim() : null
36538
+ };
36539
+ }
36428
36540
  getMachineProviderStatus(type) {
36429
36541
  const providerType = this.resolveAlias(type);
36430
36542
  if (!this.isMachineProviderEnabled(providerType)) return "disabled";
36431
- const availability = this.providerAvailability.get(providerType);
36543
+ const availability = this.getEffectiveProviderAvailability(providerType);
36432
36544
  if (!availability) return "enabled_unchecked";
36433
36545
  return availability.installed ? "detected" : "not_detected";
36434
36546
  }
@@ -36556,7 +36668,7 @@ var init_provider_loader = __esm({
36556
36668
  }
36557
36669
  getAvailableProviderInfos() {
36558
36670
  return this.getAll().map((provider) => {
36559
- const availability = this.providerAvailability.get(provider.type);
36671
+ const availability = this.getEffectiveProviderAvailability(provider.type);
36560
36672
  const enabled = this.isMachineProviderEnabled(provider.type);
36561
36673
  const machineConfig = this.getMachineProviderConfig(provider.type);
36562
36674
  return {
@@ -38404,6 +38516,51 @@ function killPid(pid) {
38404
38516
  return false;
38405
38517
  }
38406
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
+ }
38407
38564
  async function waitForPidExit(pid, timeoutMs) {
38408
38565
  const start = Date.now();
38409
38566
  while (Date.now() - start < timeoutMs) {
@@ -38420,7 +38577,7 @@ function stopSessionHostProcesses(appName) {
38420
38577
  try {
38421
38578
  if (fs8.existsSync(pidFile)) {
38422
38579
  const pid = Number.parseInt(fs8.readFileSync(pidFile, "utf8").trim(), 10);
38423
- if (Number.isFinite(pid)) {
38580
+ if (Number.isFinite(pid) && pid !== process.pid && isManagedSessionHostPid(pid)) {
38424
38581
  killPid(pid);
38425
38582
  }
38426
38583
  }
@@ -38431,18 +38588,6 @@ function stopSessionHostProcesses(appName) {
38431
38588
  } catch {
38432
38589
  }
38433
38590
  }
38434
- if (process.platform !== "win32") {
38435
- try {
38436
- const raw = (0, import_child_process8.execFileSync)("pgrep", ["-f", "session-host-daemon"], { encoding: "utf8" }).trim();
38437
- for (const line of raw.split("\n")) {
38438
- const pid = Number.parseInt(line.trim(), 10);
38439
- if (Number.isFinite(pid)) {
38440
- killPid(pid);
38441
- }
38442
- }
38443
- } catch {
38444
- }
38445
- }
38446
38591
  }
38447
38592
  function removeDaemonPidFile() {
38448
38593
  const pidFile = path17.join(os20.homedir(), ".adhdev", "daemon.pid");
@@ -40784,6 +40929,20 @@ var init_forward = __esm({
40784
40929
  });
40785
40930
 
40786
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
+ }
40787
40946
  var ProviderInstanceManager;
40788
40947
  var init_provider_instance_manager = __esm({
40789
40948
  "../../oss/packages/daemon-core/src/providers/provider-instance-manager.ts"() {
@@ -40884,6 +41043,27 @@ var init_provider_instance_manager = __esm({
40884
41043
  }
40885
41044
  return states;
40886
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
+ }
40887
41067
  /**
40888
41068
  * Per-category status collect
40889
41069
  */
@@ -79624,19 +79804,20 @@ var init_screenshot_sender = __esm({
79624
79804
  }
79625
79805
  return sentAny;
79626
79806
  }
79627
- sendScreenshot(peers, base64Data) {
79807
+ sendScreenshot(peers, base64Data, targetSessionId) {
79628
79808
  const buffer = Buffer.from(base64Data, "base64");
79629
- return this.sendScreenshotBuffer(peers, buffer);
79809
+ return this.sendScreenshotBuffer(peers, buffer, targetSessionId);
79630
79810
  }
79631
79811
  /** Send screenshot as raw Buffer (no base64 conversion overhead) */
79632
- sendScreenshotBuffer(peers, buffer) {
79812
+ sendScreenshotBuffer(peers, buffer, targetSessionId) {
79633
79813
  let sentAny = false;
79634
79814
  let debugOnce = !this._ssDebugDone;
79635
79815
  for (const [pid, peer] of peers.entries()) {
79636
79816
  if (debugOnce) {
79637
- 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}`);
79638
79818
  }
79639
79819
  if (peer.state !== "connected" || !peer.dataChannel || !peer.screenshotActive) continue;
79820
+ if (targetSessionId && peer.screenshotTargetSessionId !== targetSessionId) continue;
79640
79821
  try {
79641
79822
  if (!peer.dataChannel.isOpen()) continue;
79642
79823
  const header = Buffer.alloc(4);
@@ -79668,18 +79849,32 @@ async function initiateConnection(deps, peerId, sharePermission) {
79668
79849
  log("Cannot initiate \u2014 node-datachannel not available");
79669
79850
  return;
79670
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
+ }
79671
79862
  const limits = deps.serverConn.getPlanLimits();
79672
79863
  if (limits && limits.maxP2PConnections !== -1) {
79673
79864
  let connectedCount = 0;
79865
+ let reservedCount = 0;
79674
79866
  for (const peer of deps.peers.values()) {
79675
79867
  if (peer.state === "connected") connectedCount++;
79868
+ if (peer.state === "connected" || peer.state === "connecting") reservedCount++;
79676
79869
  }
79677
- if (connectedCount >= limits.maxP2PConnections) {
79870
+ if (reservedCount >= limits.maxP2PConnections) {
79678
79871
  let oldestPeer = null;
79679
- for (const [pid2, peer] of deps.peers) {
79680
- if (peer.state === "connected") {
79681
- if (!oldestPeer || peer.connectedAt < oldestPeer.at) {
79682
- 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
+ }
79683
79878
  }
79684
79879
  }
79685
79880
  }
@@ -79697,19 +79892,12 @@ async function initiateConnection(deps, peerId, sharePermission) {
79697
79892
  }
79698
79893
  }
79699
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;
79700
79898
  }
79701
79899
  }
79702
79900
  }
79703
- const pid = peerId || `legacy_${Date.now()}`;
79704
- const existing = deps.peers.get(pid);
79705
- if (existing?.state === "connected") {
79706
- log(`initiateconnection() ignored for peer ${pid} \u2014 already connected`);
79707
- return;
79708
- }
79709
- if (existing?.state === "connecting") {
79710
- log(`initiateconnection() ignored for peer ${pid} \u2014 connection already in progress`);
79711
- return;
79712
- }
79713
79901
  log(`initiateconnection() for peer ${pid}...`);
79714
79902
  const mod = deps.nodeDatachannel;
79715
79903
  const PeerConnectionCtor = mod.PeerConnection || mod.default?.PeerConnection || mod.default || mod;
@@ -80068,14 +80256,19 @@ var init_daemon_p2p = __esm({
80068
80256
  }
80069
80257
  return false;
80070
80258
  }
80071
- /** Get the target session for the currently active screenshot request */
80072
- get screenshotTargetSessionId() {
80259
+ /** Get all target sessions for active screenshot requests */
80260
+ get screenshotTargetSessionIds() {
80261
+ const targets = /* @__PURE__ */ new Set();
80073
80262
  for (const peer of this.peers.values()) {
80074
80263
  if (peer.screenshotActive && peer.state === "connected" && peer.screenshotTargetSessionId) {
80075
- return peer.screenshotTargetSessionId;
80264
+ targets.add(peer.screenshotTargetSessionId);
80076
80265
  }
80077
80266
  }
80078
- 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];
80079
80272
  }
80080
80273
  constructor(serverConn) {
80081
80274
  this.serverConn = serverConn;
@@ -80184,6 +80377,14 @@ ${e?.stack || ""}`);
80184
80377
  }
80185
80378
  return false;
80186
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
+ }
80187
80388
  onStateChange(listener) {
80188
80389
  this.stateListeners.push(listener);
80189
80390
  }
@@ -80247,11 +80448,14 @@ ${e?.stack || ""}`);
80247
80448
  if (targetPeers.size === 0) return false;
80248
80449
  return this.screenshotSender.broadcastSessionOutput(targetPeers, sessionId, data);
80249
80450
  }
80250
- sendScreenshot(base64Data) {
80251
- 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);
80252
80456
  }
80253
- sendScreenshotBuffer(buffer) {
80254
- return this.screenshotSender.sendScreenshotBuffer(this.peers, buffer);
80457
+ sendScreenshotBufferForTarget(targetSessionId, buffer) {
80458
+ return this.sendScreenshotBuffer(buffer, targetSessionId);
80255
80459
  }
80256
80460
  // ─── Handler registration (unchanged API) ───────
80257
80461
  onFileRequest(handler) {
@@ -86975,6 +87179,7 @@ var init_screenshot_controller = __esm({
86975
87179
  lastSize = 0;
86976
87180
  lastHash = 0;
86977
87181
  staticFrameCount = 0;
87182
+ targetFrameState = /* @__PURE__ */ new Map();
86978
87183
  currentInterval;
86979
87184
  // Quality profiles
86980
87185
  profileDirect;
@@ -87038,14 +87243,13 @@ var init_screenshot_controller = __esm({
87038
87243
  async tick() {
87039
87244
  if (!this.deps.isRunning()) return;
87040
87245
  const active = this.deps.isScreenshotActive();
87041
- const targetSessionId = this.deps.getScreenshotTargetSessionId();
87042
- 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) {
87043
87250
  this.timer = setTimeout(() => this.tick(), 500);
87044
87251
  return;
87045
87252
  }
87046
- const cdp = targetSessionId ? this.deps.getCdp(targetSessionId) : null;
87047
- const isRelay = this.deps.isUsingRelay();
87048
- const profile = isRelay ? this.profileRelay : this.profileDirect;
87049
87253
  this.checkBudgetReset();
87050
87254
  if (this.dailyBudgetMs > 0) {
87051
87255
  const now = Date.now();
@@ -87060,49 +87264,84 @@ var init_screenshot_controller = __esm({
87060
87264
  }
87061
87265
  }
87062
87266
  const budgetBlocked = this.budgetExhausted && isRelay;
87063
- if (!active || !cdp || budgetBlocked) {
87267
+ if (!active || budgetBlocked) {
87064
87268
  this.staticFrameCount = 0;
87269
+ this.targetFrameState.clear();
87065
87270
  this.currentInterval = profile.maxInterval;
87066
87271
  if (!active) this.lastActiveTimestamp = 0;
87067
87272
  this.timer = setTimeout(() => this.tick(), budgetBlocked ? 3e4 : this.currentInterval);
87068
87273
  return;
87069
87274
  }
87070
87275
  this.debugCount++;
87071
- try {
87072
- const buf = await cdp.captureScreenshot({ quality: profile.quality });
87073
- 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);
87074
87290
  const hash2 = _ScreenshotController.fnvHash(buf);
87075
- const sizeMatch = buf.length === this.lastSize;
87076
- const hashMatch = hash2 === this.lastHash;
87077
- 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();
87078
87294
  const resizeTarget = anyNeedsFirstFrame ? profile.firstFrameLongEdge : profile.maxLongEdge;
87295
+ anyFirstFrameAcrossTargets = anyFirstFrameAcrossTargets || anyNeedsFirstFrame;
87079
87296
  if (sizeMatch && hashMatch && !anyNeedsFirstFrame) {
87080
- this.staticFrameCount++;
87081
- if (this.staticFrameCount >= this.STATIC_THRESHOLD) {
87297
+ state.staticFrameCount++;
87298
+ if (state.staticFrameCount >= this.STATIC_THRESHOLD) {
87082
87299
  this.currentInterval = Math.min(this.currentInterval + 200, profile.maxInterval);
87083
87300
  }
87084
87301
  if (this.debugCount <= 5 || this.debugCount % 50 === 0) {
87085
- LOG.debug("Screenshot", `skip (unchanged, static=${this.staticFrameCount}, interval=${this.currentInterval}ms, ${isRelay ? "RELAY" : "DIRECT"})`);
87086
- }
87087
- } else {
87088
- const normalizedBuf = await this.normalizeBuffer(buf, resizeTarget, profile.quality);
87089
- this.lastSize = buf.length;
87090
- this.lastHash = hash2;
87091
- this.staticFrameCount = 0;
87092
- this.currentInterval = profile.minInterval;
87093
- const sent = this.deps.sendScreenshotBuffer(normalizedBuf);
87094
- if (this.debugCount <= 3 || anyNeedsFirstFrame) {
87095
- 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"})`);
87096
87303
  }
87304
+ continue;
87097
87305
  }
87098
- } else {
87099
- 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}`);
87100
87321
  }
87101
- } catch (e) {
87102
- 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);
87103
87327
  }
87104
87328
  this.timer = setTimeout(() => this.tick(), this.currentInterval);
87105
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
+ }
87106
87345
  // ─── Budget ───────────────────────────────────
87107
87346
  checkBudgetReset() {
87108
87347
  const today = (/* @__PURE__ */ new Date()).toISOString().slice(0, 10);
@@ -87319,7 +87558,7 @@ function killPid2(pid) {
87319
87558
  return false;
87320
87559
  }
87321
87560
  }
87322
- function getWindowsProcessCommandLine(pid) {
87561
+ function getWindowsProcessCommandLine2(pid) {
87323
87562
  const pidFilter = `ProcessId=${pid}`;
87324
87563
  try {
87325
87564
  const psOut = (0, import_child_process13.execFileSync)("powershell.exe", [
@@ -87348,13 +87587,32 @@ function getWindowsProcessCommandLine(pid) {
87348
87587
  }
87349
87588
  return null;
87350
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
+ }
87351
87609
  function stopManagedSessionHostProcess() {
87352
87610
  let stopped = false;
87353
87611
  const pidFile = getSessionHostPidFile();
87354
87612
  try {
87355
87613
  if (fs22.existsSync(pidFile)) {
87356
87614
  const pid = Number.parseInt(fs22.readFileSync(pidFile, "utf8").trim(), 10);
87357
- if (Number.isFinite(pid) && pid !== process.pid) {
87615
+ if (Number.isFinite(pid) && pid !== process.pid && isManagedSessionHostPid2(pid)) {
87358
87616
  stopped = killPid2(pid) || stopped;
87359
87617
  }
87360
87618
  }
@@ -87368,40 +87626,7 @@ function stopManagedSessionHostProcess() {
87368
87626
  return stopped;
87369
87627
  }
87370
87628
  function stopSessionHost() {
87371
- let stopped = stopManagedSessionHostProcess();
87372
- if (process.platform === "win32") {
87373
- try {
87374
- const raw = (0, import_child_process13.execFileSync)("tasklist", ["/FO", "CSV", "/NH", "/FI", "IMAGENAME eq node.exe"], {
87375
- encoding: "utf8",
87376
- timeout: 5e3,
87377
- stdio: ["ignore", "pipe", "ignore"],
87378
- windowsHide: true
87379
- }).trim();
87380
- for (const line of raw.split(/\r?\n/)) {
87381
- const match = line.match(/^"node\.exe","(\d+)"/i);
87382
- if (!match) continue;
87383
- const candidatePid = Number.parseInt(match[1], 10);
87384
- if (!Number.isFinite(candidatePid) || candidatePid === process.pid) continue;
87385
- const commandLine = getWindowsProcessCommandLine(candidatePid);
87386
- if (commandLine?.includes("session-host-daemon")) {
87387
- stopped = killPid2(candidatePid) || stopped;
87388
- }
87389
- }
87390
- } catch {
87391
- }
87392
- } else {
87393
- try {
87394
- const raw = (0, import_child_process13.execFileSync)("pgrep", ["-f", "session-host-daemon"], { encoding: "utf8" }).trim();
87395
- for (const line of raw.split("\n")) {
87396
- const pid = Number.parseInt(line.trim(), 10);
87397
- if (Number.isFinite(pid) && pid !== process.pid && pid !== process.ppid) {
87398
- stopped = killPid2(pid) || stopped;
87399
- }
87400
- }
87401
- } catch {
87402
- }
87403
- }
87404
- return stopped;
87629
+ return stopManagedSessionHostProcess();
87405
87630
  }
87406
87631
  async function ensureSessionHostReady2() {
87407
87632
  const quarantine = quarantineLegacyStandaloneSessions({
@@ -87658,10 +87883,65 @@ var init_session_host_controller = __esm({
87658
87883
  var adhdev_daemon_exports = {};
87659
87884
  __export(adhdev_daemon_exports, {
87660
87885
  AdhdevDaemon: () => AdhdevDaemon,
87886
+ buildMandatoryUpdateInfoFromServerPayload: () => buildMandatoryUpdateInfoFromServerPayload,
87887
+ buildMandatoryUpdateRequiredPayload: () => buildMandatoryUpdateRequiredPayload,
87661
87888
  getDaemonPid: () => getDaemonPid,
87662
87889
  isDaemonRunning: () => isDaemonRunning,
87663
- stopDaemon: () => stopDaemon
87664
- });
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
+ }
87665
87945
  function resolveDaemonPort(ref = {}) {
87666
87946
  return Number.isFinite(ref.port) && Number(ref.port) > 0 ? Number(ref.port) : DEFAULT_DAEMON_PORT;
87667
87947
  }
@@ -87688,7 +87968,7 @@ function removeDaemonPid(ref = {}) {
87688
87968
  function isDaemonRunning(ref = {}) {
87689
87969
  const port = resolveDaemonPort(ref);
87690
87970
  try {
87691
- const { execFileSync: execFileSync6 } = require("child_process");
87971
+ const { execFileSync: execFileSync7 } = require("child_process");
87692
87972
  const probe = `
87693
87973
  const http = require('http');
87694
87974
  const req = http.get('http://127.0.0.1:${port}/health', { timeout: 1500 }, (res) => {
@@ -87698,7 +87978,7 @@ function isDaemonRunning(ref = {}) {
87698
87978
  req.on('error', () => process.stdout.write('0'));
87699
87979
  req.on('timeout', () => { req.destroy(); process.stdout.write('0'); });
87700
87980
  `;
87701
- const result = execFileSync6(process.execPath, ["-e", probe], {
87981
+ const result = execFileSync7(process.execPath, ["-e", probe], {
87702
87982
  encoding: "utf-8",
87703
87983
  timeout: 3e3,
87704
87984
  stdio: ["ignore", "pipe", "ignore"]
@@ -87724,9 +88004,9 @@ function isDaemonRunning(ref = {}) {
87724
88004
  function isAdhdevProcess(pid) {
87725
88005
  try {
87726
88006
  if (process.platform === "win32") {
87727
- const { execFileSync: execFileSync6 } = require("child_process");
88007
+ const { execFileSync: execFileSync7 } = require("child_process");
87728
88008
  try {
87729
- const psOut = execFileSync6("powershell.exe", [
88009
+ const psOut = execFileSync7("powershell.exe", [
87730
88010
  "-NoProfile",
87731
88011
  "-NonInteractive",
87732
88012
  "-ExecutionPolicy",
@@ -87739,8 +88019,8 @@ function isAdhdevProcess(pid) {
87739
88019
  return true;
87740
88020
  }
87741
88021
  } else {
87742
- const { execFileSync: execFileSync6 } = require("child_process");
87743
- 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)], {
87744
88024
  encoding: "utf-8",
87745
88025
  timeout: 2e3,
87746
88026
  stdio: ["ignore", "pipe", "ignore"]
@@ -87754,7 +88034,7 @@ function isAdhdevProcess(pid) {
87754
88034
  function getDaemonHealthPid(ref = {}) {
87755
88035
  const port = resolveDaemonPort(ref);
87756
88036
  try {
87757
- const { execFileSync: execFileSync6 } = require("child_process");
88037
+ const { execFileSync: execFileSync7 } = require("child_process");
87758
88038
  const probe = `
87759
88039
  const http = require('http');
87760
88040
  const req = http.get('http://127.0.0.1:${port}/health', { timeout: 1500 }, (res) => {
@@ -87772,7 +88052,7 @@ function getDaemonHealthPid(ref = {}) {
87772
88052
  req.on('error', () => {});
87773
88053
  req.on('timeout', () => { req.destroy(); });
87774
88054
  `;
87775
- const result = execFileSync6(process.execPath, ["-e", probe], {
88055
+ const result = execFileSync7(process.execPath, ["-e", probe], {
87776
88056
  encoding: "utf-8",
87777
88057
  timeout: 3e3,
87778
88058
  stdio: ["ignore", "pipe", "ignore"]
@@ -87818,7 +88098,7 @@ function stopDaemon(ref = {}) {
87818
88098
  return false;
87819
88099
  }
87820
88100
  }
87821
- var os28, fs23, path29, import_http, import_ws3, pkgVersion, AdhdevDaemon;
88101
+ var os28, fs23, path29, import_http, import_child_process14, import_ws3, pkgVersion, AdhdevDaemon;
87822
88102
  var init_adhdev_daemon = __esm({
87823
88103
  "src/adhdev-daemon.ts"() {
87824
88104
  "use strict";
@@ -87834,12 +88114,13 @@ var init_adhdev_daemon = __esm({
87834
88114
  fs23 = __toESM(require("fs"));
87835
88115
  path29 = __toESM(require("path"));
87836
88116
  import_http = require("http");
88117
+ import_child_process14 = require("child_process");
87837
88118
  import_ws3 = require("ws");
87838
88119
  init_source();
87839
88120
  init_version();
87840
88121
  init_src();
87841
88122
  init_runtime_defaults();
87842
- pkgVersion = resolvePackageVersion({ injectedVersion: "0.9.32" });
88123
+ pkgVersion = resolvePackageVersion({ injectedVersion: "0.9.34" });
87843
88124
  AdhdevDaemon = class _AdhdevDaemon {
87844
88125
  localHttpServer = null;
87845
88126
  localWss = null;
@@ -87932,6 +88213,10 @@ var init_adhdev_daemon = __esm({
87932
88213
  getUpgradePackageName() {
87933
88214
  return process.argv[1]?.includes("daemon-standalone") ? "@adhdev/daemon-standalone" : "adhdev";
87934
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
+ }
87935
88220
  hasBlockingSessionsForMandatoryUpdate() {
87936
88221
  if (!this.components) return false;
87937
88222
  const blocking = /* @__PURE__ */ new Set(["generating", "waiting_approval", "starting"]);
@@ -87957,15 +88242,7 @@ var init_adhdev_daemon = __esm({
87957
88242
  const pkgName = this.getUpgradePackageName();
87958
88243
  this.mandatoryUpgradeInFlight = true;
87959
88244
  try {
87960
- const { execSync: execSync8 } = await import("child_process");
87961
- const published = execSync8(`npm view ${pkgName}@${pending.targetVersion} version`, {
87962
- encoding: "utf-8",
87963
- timeout: 1e4,
87964
- stdio: ["pipe", "pipe", "pipe"]
87965
- }).trim();
87966
- if (published !== pending.targetVersion) {
87967
- throw new Error(`Published version mismatch: expected ${pending.targetVersion}, got ${published || "unknown"}`);
87968
- }
88245
+ verifyPublishedMandatoryUpdateTarget(pkgName, pending.targetVersion);
87969
88246
  LOG.warn("Upgrade", `Applying mandatory daemon update (${pending.reason}) \u2192 v${pending.targetVersion}`);
87970
88247
  spawnDetachedDaemonUpgradeHelper({
87971
88248
  packageName: pkgName,
@@ -88067,7 +88344,7 @@ var init_adhdev_daemon = __esm({
88067
88344
  const now = Date.now();
88068
88345
  const cached2 = this.hotChatSnapshotCache;
88069
88346
  const sessions = cached2 && now - cached2.builtAt < _AdhdevDaemon.HOT_CHAT_SNAPSHOT_CACHE_TTL_MS ? cached2.sessions : (() => {
88070
- const built = this.buildLiveStatusSnapshot().sessions || [];
88347
+ const built = this.components.instanceManager.collectHotChatSessionStates();
88071
88348
  this.hotChatSnapshotCache = { sessions: built, builtAt: now };
88072
88349
  return built;
88073
88350
  })();
@@ -88452,14 +88729,17 @@ ${err?.stack || ""}`);
88452
88729
  isRunning: () => this.running,
88453
88730
  isScreenshotActive: () => this.p2p?.screenshotActive ?? false,
88454
88731
  getScreenshotTargetSessionId: () => this.p2p?.screenshotTargetSessionId,
88732
+ getScreenshotTargetSessionIds: () => this.p2p?.screenshotTargetSessionIds ?? [],
88455
88733
  isUsingRelay: () => this.p2p?.isUsingRelay ?? false,
88456
88734
  hasAnyNeedingFirstFrame: () => this.p2p?.hasAnyNeedingFirstFrame() ?? false,
88735
+ hasAnyNeedingFirstFrameForTarget: (targetSessionId) => this.p2p?.hasAnyNeedingFirstFrameForTarget(targetSessionId) ?? false,
88457
88736
  getCdp: (targetSessionId) => {
88458
88737
  if (targetSessionId) return this.getCdpFor(targetSessionId);
88459
88738
  LOG.warn("P2P", "Screenshot requested without targetSessionId \u2014 cannot determine target session. Skipping frame.");
88460
88739
  return null;
88461
88740
  },
88462
- sendScreenshotBuffer: (buf) => this.p2p.sendScreenshotBuffer(buf)
88741
+ sendScreenshotBuffer: (buf) => this.p2p.sendScreenshotBuffer(buf),
88742
+ sendScreenshotBufferForTarget: (targetSessionId, buf) => this.p2p.sendScreenshotBufferForTarget(targetSessionId, buf)
88463
88743
  }, planLimits ?? void 0);
88464
88744
  this.screenshotController.start();
88465
88745
  this.p2p.onScreenshotStart(() => this.screenshotController?.triggerImmediate());
@@ -88488,11 +88768,12 @@ ${err?.stack || ""}`);
88488
88768
  });
88489
88769
  this.serverConn.on("force_update_required", (msg) => {
88490
88770
  const payload = msg.payload;
88491
- this.pendingMandatoryUpdate = {
88492
- targetVersion: typeof payload.latest === "string" && payload.latest.trim() ? payload.latest.trim() : pkgVersion,
88493
- reason: typeof payload.reason === "string" && payload.reason.trim() ? payload.reason.trim() : "major_minor_mismatch",
88494
- minVersion: typeof payload.minVersion === "string" && payload.minVersion.trim() ? payload.minVersion.trim() : void 0
88495
- };
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;
88496
88777
  LOG.warn("Upgrade", `Mandatory daemon update required (${this.pendingMandatoryUpdate.reason})`);
88497
88778
  void this.maybeApplyMandatoryUpdate("server-force-update");
88498
88779
  });
@@ -88603,15 +88884,9 @@ ${err?.stack || ""}`);
88603
88884
  const cmdStart = Date.now();
88604
88885
  const source = msg.ipcWs ? "ext" : typeof msg.source === "string" && msg.source.trim() ? msg.source : "ws";
88605
88886
  try {
88606
- if (this.pendingMandatoryUpdate && _AdhdevDaemon.MANDATORY_UPDATE_BLOCKED_COMMANDS.has(cmd)) {
88607
- this.sendResult(msg, false, {
88608
- error: "A mandatory daemon update is pending. Finish current work and let the daemon update before starting a new session.",
88609
- code: "DAEMON_UPDATE_REQUIRED",
88610
- required: true,
88611
- latest: this.pendingMandatoryUpdate.targetVersion,
88612
- reason: this.pendingMandatoryUpdate.reason,
88613
- interactionId
88614
- });
88887
+ const mandatoryUpdateBlock = this.getMandatoryUpdateBlockPayload(cmd, interactionId);
88888
+ if (mandatoryUpdateBlock) {
88889
+ this.sendResult(msg, false, mandatoryUpdateBlock);
88615
88890
  return;
88616
88891
  }
88617
88892
  if (source === "api" && !loadConfig().allowServerApiProxy) {
@@ -88656,6 +88931,10 @@ ${err?.stack || ""}`);
88656
88931
  const interactionId = String(normalizedData._interactionId);
88657
88932
  const cmdStart = Date.now();
88658
88933
  try {
88934
+ const mandatoryUpdateBlock = this.getMandatoryUpdateBlockPayload(cmdType, interactionId);
88935
+ if (mandatoryUpdateBlock) {
88936
+ return { success: false, ...mandatoryUpdateBlock };
88937
+ }
88659
88938
  switch (cmdType) {
88660
88939
  case "get_runtime_snapshot": {
88661
88940
  const sessionId = typeof normalizedData.sessionId === "string" ? normalizedData.sessionId : "";
@@ -88834,8 +89113,22 @@ ${err?.stack || ""}`);
88834
89113
  }));
88835
89114
  return;
88836
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
+ }
88837
89130
  try {
88838
- const result = await this.components.router.execute(command, args, "ipc");
89131
+ const result = await this.components.router.execute(command, normalizedArgs, "ipc");
88839
89132
  ws.send(JSON.stringify({
88840
89133
  type: "ext:command_result",
88841
89134
  payload: {
@@ -89005,9 +89298,9 @@ async function runWizard(options = {}) {
89005
89298
  }
89006
89299
  async function checkForUpdate() {
89007
89300
  try {
89008
- const { execFileSync: execFileSync6 } = await import("child_process");
89301
+ const { execFileSync: execFileSync7 } = await import("child_process");
89009
89302
  const currentVersion = resolvePackageVersion();
89010
- const latestVersion = readLatestPublishedCliVersion(execFileSync6);
89303
+ const latestVersion = readLatestPublishedCliVersion(execFileSync7);
89011
89304
  if (!latestVersion) return;
89012
89305
  if (!currentVersion || !latestVersion || currentVersion === latestVersion) return;
89013
89306
  console.log(source_default.yellow(` Update available: ${currentVersion} \u2192 ${latestVersion}`));
@@ -89024,7 +89317,7 @@ async function checkForUpdate() {
89024
89317
  const spinner = (await Promise.resolve().then(() => (init_ora(), ora_exports))).default("Updating adhdev CLI...").start();
89025
89318
  try {
89026
89319
  const installCommand = buildPinnedGlobalInstallCommand({ packageName: "adhdev", targetVersion: "latest" });
89027
- execFileSync6(installCommand.command, installCommand.args, {
89320
+ execFileSync7(installCommand.command, installCommand.args, {
89028
89321
  encoding: "utf-8",
89029
89322
  timeout: 6e4,
89030
89323
  stdio: ["pipe", "pipe", "pipe"]
@@ -92319,7 +92612,7 @@ function registerDaemonCommands(program2, pkgVersion3) {
92319
92612
 
92320
92613
  // src/cli/doctor-commands.ts
92321
92614
  init_source();
92322
- var import_child_process14 = require("child_process");
92615
+ var import_child_process15 = require("child_process");
92323
92616
  var fs26 = __toESM(require("fs"));
92324
92617
  var os30 = __toESM(require("os"));
92325
92618
  var path33 = __toESM(require("path"));
@@ -92961,7 +93254,7 @@ function findCommandPaths(command) {
92961
93254
  try {
92962
93255
  const bin = process.platform === "win32" ? "where.exe" : "which";
92963
93256
  const args = process.platform === "win32" ? [command] : ["-a", command];
92964
- const output = (0, import_child_process14.execFileSync)(bin, args, {
93257
+ const output = (0, import_child_process15.execFileSync)(bin, args, {
92965
93258
  encoding: "utf-8",
92966
93259
  stdio: ["ignore", "pipe", "ignore"]
92967
93260
  });
@@ -92976,7 +93269,7 @@ function probeCliBinary(commandPath, currentVersion) {
92976
93269
  currentVersion
92977
93270
  };
92978
93271
  try {
92979
- probe.version = (0, import_child_process14.execFileSync)(commandPath, ["--version"], {
93272
+ probe.version = (0, import_child_process15.execFileSync)(commandPath, ["--version"], {
92980
93273
  encoding: "utf-8",
92981
93274
  stdio: ["ignore", "pipe", "pipe"]
92982
93275
  }).trim();
@@ -92984,7 +93277,7 @@ function probeCliBinary(commandPath, currentVersion) {
92984
93277
  probe.versionError = error48?.stderr?.toString?.().trim() || error48?.message || "version probe failed";
92985
93278
  }
92986
93279
  try {
92987
- probe.helpText = (0, import_child_process14.execFileSync)(commandPath, ["--help"], {
93280
+ probe.helpText = (0, import_child_process15.execFileSync)(commandPath, ["--help"], {
92988
93281
  encoding: "utf-8",
92989
93282
  stdio: ["ignore", "pipe", "pipe"]
92990
93283
  });