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/index.js CHANGED
@@ -7461,6 +7461,26 @@ function toHistoryPersistedMessages(messages) {
7461
7461
  historyDedupKey: deriveHistoryDedupKey(message)
7462
7462
  }));
7463
7463
  }
7464
+ function findLastMessageIndexBySignature(messages, signature) {
7465
+ if (!signature) return -1;
7466
+ for (let index = messages.length - 1; index >= 0; index -= 1) {
7467
+ if (getChatMessageSignature(messages[index]) === signature) {
7468
+ return index;
7469
+ }
7470
+ }
7471
+ return -1;
7472
+ }
7473
+ function buildBoundedTailSync(messages, cursor) {
7474
+ const totalMessages = messages.length;
7475
+ const tailMessages = cursor.tailLimit > 0 && totalMessages > cursor.tailLimit ? messages.slice(-cursor.tailLimit) : messages;
7476
+ return {
7477
+ syncMode: "full",
7478
+ replaceFrom: 0,
7479
+ messages: tailMessages,
7480
+ totalMessages,
7481
+ lastMessageSignature: getChatMessageSignature(messages[totalMessages - 1])
7482
+ };
7483
+ }
7464
7484
  function computeReadChatSync(messages, cursor) {
7465
7485
  const totalMessages = messages.length;
7466
7486
  const lastMessageSignature = getChatMessageSignature(messages[totalMessages - 1]);
@@ -7492,6 +7512,15 @@ function computeReadChatSync(messages, cursor) {
7492
7512
  lastMessageSignature
7493
7513
  };
7494
7514
  }
7515
+ if (cursor.tailLimit > 0 && knownSignature === lastMessageSignature) {
7516
+ return {
7517
+ syncMode: "noop",
7518
+ replaceFrom: totalMessages,
7519
+ messages: [],
7520
+ totalMessages,
7521
+ lastMessageSignature
7522
+ };
7523
+ }
7495
7524
  if (knownMessageCount < totalMessages) {
7496
7525
  const anchorSignature = getChatMessageSignature(messages[knownMessageCount - 1]);
7497
7526
  if (anchorSignature === knownSignature) {
@@ -7503,6 +7532,19 @@ function computeReadChatSync(messages, cursor) {
7503
7532
  lastMessageSignature
7504
7533
  };
7505
7534
  }
7535
+ if (cursor.tailLimit > 0) {
7536
+ const signatureIndex = findLastMessageIndexBySignature(messages, knownSignature);
7537
+ if (signatureIndex >= 0) {
7538
+ return {
7539
+ syncMode: "append",
7540
+ replaceFrom: knownMessageCount,
7541
+ messages: messages.slice(signatureIndex + 1),
7542
+ totalMessages,
7543
+ lastMessageSignature
7544
+ };
7545
+ }
7546
+ return buildBoundedTailSync(messages, cursor);
7547
+ }
7506
7548
  }
7507
7549
  const replaceFrom = Math.max(0, Math.min(knownMessageCount - 1, totalMessages));
7508
7550
  return {
@@ -11371,13 +11413,14 @@ function sliceFromOffset(text, start) {
11371
11413
  function hydrateCliParsedMessages(parsedMessages, options) {
11372
11414
  const { committedMessages, scope, lastOutputAt } = options;
11373
11415
  const referenceMessages = [...committedMessages];
11416
+ const referenceComparables = referenceMessages.map((message) => normalizeComparableMessageContent(message?.content || ""));
11374
11417
  const usedReferenceIndexes = /* @__PURE__ */ new Set();
11375
11418
  const now = options.now ?? Date.now();
11376
11419
  const findReferenceTimestamp = (role, content, parsedIndex) => {
11377
11420
  const normalizedContent = normalizeComparableMessageContent(content);
11378
11421
  if (!normalizedContent) return void 0;
11379
11422
  const sameIndex = referenceMessages[parsedIndex];
11380
- 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)) {
11381
11424
  usedReferenceIndexes.add(parsedIndex);
11382
11425
  return sameIndex.timestamp;
11383
11426
  }
@@ -11385,7 +11428,7 @@ function hydrateCliParsedMessages(parsedMessages, options) {
11385
11428
  if (usedReferenceIndexes.has(i)) continue;
11386
11429
  const candidate = referenceMessages[i];
11387
11430
  if (!candidate || candidate.role !== role) continue;
11388
- const candidateContent = normalizeComparableMessageContent(candidate.content);
11431
+ const candidateContent = referenceComparables[i];
11389
11432
  if (!candidateContent) continue;
11390
11433
  const exactMatch = candidateContent === normalizedContent;
11391
11434
  const fuzzyMatch = candidateContent.includes(normalizedContent) || normalizedContent.includes(candidateContent);
@@ -12477,7 +12520,7 @@ var init_provider_cli_adapter = __esm({
12477
12520
  return;
12478
12521
  }
12479
12522
  if (this.currentTurnScope && !lastParsedAssistant) {
12480
- LOG.info(
12523
+ LOG.debug(
12481
12524
  "CLI",
12482
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 || "-"}`
12483
12526
  );
@@ -13293,9 +13336,43 @@ var init_provider_cli_adapter = __esm({
13293
13336
  }
13294
13337
  armResponseTimeout() {
13295
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
+ }
13296
13344
  this.responseTimeout = setTimeout(() => {
13297
- if (this.isWaitingForResponse) this.finishResponse();
13298
- }, 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);
13299
13376
  }
13300
13377
  writeSubmitKeyForRetry(mode) {
13301
13378
  void this.writeToPty(this.sendKey).catch((error48) => {
@@ -14186,6 +14263,20 @@ var init_cli_provider_instance = __esm({
14186
14263
  getPresentationMode() {
14187
14264
  return this.presentationMode;
14188
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
+ }
14189
14280
  updateSettings(newSettings) {
14190
14281
  this.settings = { ...newSettings };
14191
14282
  this.adapter.updateRuntimeSettings?.(this.settings);
@@ -14341,6 +14432,15 @@ var init_cli_provider_instance = __esm({
14341
14432
  this.completedDebouncePending = { chatTitle, duration: duration3, timestamp: now };
14342
14433
  this.completedDebounceTimer = setTimeout(() => {
14343
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
+ }
14344
14444
  LOG.info("CLI", `[${this.type}] completed in ${this.completedDebouncePending.duration}s`);
14345
14445
  this.pushEvent({ event: "agent:generating_completed", ...this.completedDebouncePending });
14346
14446
  this.completedDebouncePending = null;
@@ -35469,10 +35569,22 @@ var init_provider_loader = __esm({
35469
35569
  setMachineProviderEnabled(type, enabled) {
35470
35570
  return this.setMachineProviderConfig(type, { enabled });
35471
35571
  }
35572
+ getEffectiveProviderAvailability(type) {
35573
+ const providerType = this.resolveAlias(type);
35574
+ const availability = this.providerAvailability.get(providerType);
35575
+ if (availability) return availability;
35576
+ const machineConfig = this.getMachineProviderConfig(providerType);
35577
+ const lastDetection = machineConfig.lastDetection;
35578
+ if (!lastDetection) return void 0;
35579
+ return {
35580
+ installed: lastDetection.ok === true,
35581
+ detectedPath: typeof lastDetection.path === "string" && lastDetection.path.trim() ? lastDetection.path.trim() : null
35582
+ };
35583
+ }
35472
35584
  getMachineProviderStatus(type) {
35473
35585
  const providerType = this.resolveAlias(type);
35474
35586
  if (!this.isMachineProviderEnabled(providerType)) return "disabled";
35475
- const availability = this.providerAvailability.get(providerType);
35587
+ const availability = this.getEffectiveProviderAvailability(providerType);
35476
35588
  if (!availability) return "enabled_unchecked";
35477
35589
  return availability.installed ? "detected" : "not_detected";
35478
35590
  }
@@ -35600,7 +35712,7 @@ var init_provider_loader = __esm({
35600
35712
  }
35601
35713
  getAvailableProviderInfos() {
35602
35714
  return this.getAll().map((provider) => {
35603
- const availability = this.providerAvailability.get(provider.type);
35715
+ const availability = this.getEffectiveProviderAvailability(provider.type);
35604
35716
  const enabled = this.isMachineProviderEnabled(provider.type);
35605
35717
  const machineConfig = this.getMachineProviderConfig(provider.type);
35606
35718
  return {
@@ -37448,6 +37560,51 @@ function killPid(pid) {
37448
37560
  return false;
37449
37561
  }
37450
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
+ }
37451
37608
  async function waitForPidExit(pid, timeoutMs) {
37452
37609
  const start = Date.now();
37453
37610
  while (Date.now() - start < timeoutMs) {
@@ -37464,7 +37621,7 @@ function stopSessionHostProcesses(appName) {
37464
37621
  try {
37465
37622
  if (fs8.existsSync(pidFile)) {
37466
37623
  const pid = Number.parseInt(fs8.readFileSync(pidFile, "utf8").trim(), 10);
37467
- if (Number.isFinite(pid)) {
37624
+ if (Number.isFinite(pid) && pid !== process.pid && isManagedSessionHostPid(pid)) {
37468
37625
  killPid(pid);
37469
37626
  }
37470
37627
  }
@@ -37475,18 +37632,6 @@ function stopSessionHostProcesses(appName) {
37475
37632
  } catch {
37476
37633
  }
37477
37634
  }
37478
- if (process.platform !== "win32") {
37479
- try {
37480
- const raw = (0, import_child_process8.execFileSync)("pgrep", ["-f", "session-host-daemon"], { encoding: "utf8" }).trim();
37481
- for (const line of raw.split("\n")) {
37482
- const pid = Number.parseInt(line.trim(), 10);
37483
- if (Number.isFinite(pid)) {
37484
- killPid(pid);
37485
- }
37486
- }
37487
- } catch {
37488
- }
37489
- }
37490
37635
  }
37491
37636
  function removeDaemonPidFile() {
37492
37637
  const pidFile = path16.join(os19.homedir(), ".adhdev", "daemon.pid");
@@ -39828,6 +39973,20 @@ var init_forward = __esm({
39828
39973
  });
39829
39974
 
39830
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
+ }
39831
39990
  var ProviderInstanceManager;
39832
39991
  var init_provider_instance_manager = __esm({
39833
39992
  "../../oss/packages/daemon-core/src/providers/provider-instance-manager.ts"() {
@@ -39928,6 +40087,27 @@ var init_provider_instance_manager = __esm({
39928
40087
  }
39929
40088
  return states;
39930
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
+ }
39931
40111
  /**
39932
40112
  * Per-category status collect
39933
40113
  */
@@ -47410,19 +47590,20 @@ var init_screenshot_sender = __esm({
47410
47590
  }
47411
47591
  return sentAny;
47412
47592
  }
47413
- sendScreenshot(peers, base64Data) {
47593
+ sendScreenshot(peers, base64Data, targetSessionId) {
47414
47594
  const buffer = Buffer.from(base64Data, "base64");
47415
- return this.sendScreenshotBuffer(peers, buffer);
47595
+ return this.sendScreenshotBuffer(peers, buffer, targetSessionId);
47416
47596
  }
47417
47597
  /** Send screenshot as raw Buffer (no base64 conversion overhead) */
47418
- sendScreenshotBuffer(peers, buffer) {
47598
+ sendScreenshotBuffer(peers, buffer, targetSessionId) {
47419
47599
  let sentAny = false;
47420
47600
  let debugOnce = !this._ssDebugDone;
47421
47601
  for (const [pid, peer] of peers.entries()) {
47422
47602
  if (debugOnce) {
47423
- 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}`);
47424
47604
  }
47425
47605
  if (peer.state !== "connected" || !peer.dataChannel || !peer.screenshotActive) continue;
47606
+ if (targetSessionId && peer.screenshotTargetSessionId !== targetSessionId) continue;
47426
47607
  try {
47427
47608
  if (!peer.dataChannel.isOpen()) continue;
47428
47609
  const header = Buffer.alloc(4);
@@ -47454,18 +47635,32 @@ async function initiateConnection(deps, peerId, sharePermission) {
47454
47635
  log("Cannot initiate \u2014 node-datachannel not available");
47455
47636
  return;
47456
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
+ }
47457
47648
  const limits = deps.serverConn.getPlanLimits();
47458
47649
  if (limits && limits.maxP2PConnections !== -1) {
47459
47650
  let connectedCount = 0;
47651
+ let reservedCount = 0;
47460
47652
  for (const peer of deps.peers.values()) {
47461
47653
  if (peer.state === "connected") connectedCount++;
47654
+ if (peer.state === "connected" || peer.state === "connecting") reservedCount++;
47462
47655
  }
47463
- if (connectedCount >= limits.maxP2PConnections) {
47656
+ if (reservedCount >= limits.maxP2PConnections) {
47464
47657
  let oldestPeer = null;
47465
- for (const [pid2, peer] of deps.peers) {
47466
- if (peer.state === "connected") {
47467
- if (!oldestPeer || peer.connectedAt < oldestPeer.at) {
47468
- 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
+ }
47469
47664
  }
47470
47665
  }
47471
47666
  }
@@ -47483,19 +47678,12 @@ async function initiateConnection(deps, peerId, sharePermission) {
47483
47678
  }
47484
47679
  }
47485
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;
47486
47684
  }
47487
47685
  }
47488
47686
  }
47489
- const pid = peerId || `legacy_${Date.now()}`;
47490
- const existing = deps.peers.get(pid);
47491
- if (existing?.state === "connected") {
47492
- log(`initiateconnection() ignored for peer ${pid} \u2014 already connected`);
47493
- return;
47494
- }
47495
- if (existing?.state === "connecting") {
47496
- log(`initiateconnection() ignored for peer ${pid} \u2014 connection already in progress`);
47497
- return;
47498
- }
47499
47687
  log(`initiateconnection() for peer ${pid}...`);
47500
47688
  const mod = deps.nodeDatachannel;
47501
47689
  const PeerConnectionCtor = mod.PeerConnection || mod.default?.PeerConnection || mod.default || mod;
@@ -47854,14 +48042,19 @@ var init_daemon_p2p = __esm({
47854
48042
  }
47855
48043
  return false;
47856
48044
  }
47857
- /** Get the target session for the currently active screenshot request */
47858
- get screenshotTargetSessionId() {
48045
+ /** Get all target sessions for active screenshot requests */
48046
+ get screenshotTargetSessionIds() {
48047
+ const targets = /* @__PURE__ */ new Set();
47859
48048
  for (const peer of this.peers.values()) {
47860
48049
  if (peer.screenshotActive && peer.state === "connected" && peer.screenshotTargetSessionId) {
47861
- return peer.screenshotTargetSessionId;
48050
+ targets.add(peer.screenshotTargetSessionId);
47862
48051
  }
47863
48052
  }
47864
- 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];
47865
48058
  }
47866
48059
  constructor(serverConn) {
47867
48060
  this.serverConn = serverConn;
@@ -47970,6 +48163,14 @@ ${e?.stack || ""}`);
47970
48163
  }
47971
48164
  return false;
47972
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
+ }
47973
48174
  onStateChange(listener) {
47974
48175
  this.stateListeners.push(listener);
47975
48176
  }
@@ -48033,11 +48234,14 @@ ${e?.stack || ""}`);
48033
48234
  if (targetPeers.size === 0) return false;
48034
48235
  return this.screenshotSender.broadcastSessionOutput(targetPeers, sessionId, data);
48035
48236
  }
48036
- sendScreenshot(base64Data) {
48037
- return this.screenshotSender.sendScreenshot(this.peers, base64Data);
48237
+ sendScreenshot(base64Data, targetSessionId) {
48238
+ return this.screenshotSender.sendScreenshot(this.peers, base64Data, targetSessionId);
48038
48239
  }
48039
- sendScreenshotBuffer(buffer) {
48040
- 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);
48041
48245
  }
48042
48246
  // ─── Handler registration (unchanged API) ───────
48043
48247
  onFileRequest(handler) {
@@ -54761,6 +54965,7 @@ var init_screenshot_controller = __esm({
54761
54965
  lastSize = 0;
54762
54966
  lastHash = 0;
54763
54967
  staticFrameCount = 0;
54968
+ targetFrameState = /* @__PURE__ */ new Map();
54764
54969
  currentInterval;
54765
54970
  // Quality profiles
54766
54971
  profileDirect;
@@ -54824,14 +55029,13 @@ var init_screenshot_controller = __esm({
54824
55029
  async tick() {
54825
55030
  if (!this.deps.isRunning()) return;
54826
55031
  const active = this.deps.isScreenshotActive();
54827
- const targetSessionId = this.deps.getScreenshotTargetSessionId();
54828
- 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) {
54829
55036
  this.timer = setTimeout(() => this.tick(), 500);
54830
55037
  return;
54831
55038
  }
54832
- const cdp = targetSessionId ? this.deps.getCdp(targetSessionId) : null;
54833
- const isRelay = this.deps.isUsingRelay();
54834
- const profile = isRelay ? this.profileRelay : this.profileDirect;
54835
55039
  this.checkBudgetReset();
54836
55040
  if (this.dailyBudgetMs > 0) {
54837
55041
  const now = Date.now();
@@ -54846,49 +55050,84 @@ var init_screenshot_controller = __esm({
54846
55050
  }
54847
55051
  }
54848
55052
  const budgetBlocked = this.budgetExhausted && isRelay;
54849
- if (!active || !cdp || budgetBlocked) {
55053
+ if (!active || budgetBlocked) {
54850
55054
  this.staticFrameCount = 0;
55055
+ this.targetFrameState.clear();
54851
55056
  this.currentInterval = profile.maxInterval;
54852
55057
  if (!active) this.lastActiveTimestamp = 0;
54853
55058
  this.timer = setTimeout(() => this.tick(), budgetBlocked ? 3e4 : this.currentInterval);
54854
55059
  return;
54855
55060
  }
54856
55061
  this.debugCount++;
54857
- try {
54858
- const buf = await cdp.captureScreenshot({ quality: profile.quality });
54859
- 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);
54860
55076
  const hash2 = _ScreenshotController.fnvHash(buf);
54861
- const sizeMatch = buf.length === this.lastSize;
54862
- const hashMatch = hash2 === this.lastHash;
54863
- 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();
54864
55080
  const resizeTarget = anyNeedsFirstFrame ? profile.firstFrameLongEdge : profile.maxLongEdge;
55081
+ anyFirstFrameAcrossTargets = anyFirstFrameAcrossTargets || anyNeedsFirstFrame;
54865
55082
  if (sizeMatch && hashMatch && !anyNeedsFirstFrame) {
54866
- this.staticFrameCount++;
54867
- if (this.staticFrameCount >= this.STATIC_THRESHOLD) {
55083
+ state.staticFrameCount++;
55084
+ if (state.staticFrameCount >= this.STATIC_THRESHOLD) {
54868
55085
  this.currentInterval = Math.min(this.currentInterval + 200, profile.maxInterval);
54869
55086
  }
54870
55087
  if (this.debugCount <= 5 || this.debugCount % 50 === 0) {
54871
- LOG.debug("Screenshot", `skip (unchanged, static=${this.staticFrameCount}, interval=${this.currentInterval}ms, ${isRelay ? "RELAY" : "DIRECT"})`);
54872
- }
54873
- } else {
54874
- const normalizedBuf = await this.normalizeBuffer(buf, resizeTarget, profile.quality);
54875
- this.lastSize = buf.length;
54876
- this.lastHash = hash2;
54877
- this.staticFrameCount = 0;
54878
- this.currentInterval = profile.minInterval;
54879
- const sent = this.deps.sendScreenshotBuffer(normalizedBuf);
54880
- if (this.debugCount <= 3 || anyNeedsFirstFrame) {
54881
- 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"})`);
54882
55089
  }
55090
+ continue;
54883
55091
  }
54884
- } else {
54885
- 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}`);
54886
55107
  }
54887
- } catch (e) {
54888
- 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);
54889
55113
  }
54890
55114
  this.timer = setTimeout(() => this.tick(), this.currentInterval);
54891
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
+ }
54892
55131
  // ─── Budget ───────────────────────────────────
54893
55132
  checkBudgetReset() {
54894
55133
  const today = (/* @__PURE__ */ new Date()).toISOString().slice(0, 10);
@@ -55079,7 +55318,7 @@ function killPid2(pid) {
55079
55318
  return false;
55080
55319
  }
55081
55320
  }
55082
- function getWindowsProcessCommandLine(pid) {
55321
+ function getWindowsProcessCommandLine2(pid) {
55083
55322
  const pidFilter = `ProcessId=${pid}`;
55084
55323
  try {
55085
55324
  const psOut = (0, import_child_process12.execFileSync)("powershell.exe", [
@@ -55108,13 +55347,32 @@ function getWindowsProcessCommandLine(pid) {
55108
55347
  }
55109
55348
  return null;
55110
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
+ }
55111
55369
  function stopManagedSessionHostProcess() {
55112
55370
  let stopped = false;
55113
55371
  const pidFile = getSessionHostPidFile();
55114
55372
  try {
55115
55373
  if (fs17.existsSync(pidFile)) {
55116
55374
  const pid = Number.parseInt(fs17.readFileSync(pidFile, "utf8").trim(), 10);
55117
- if (Number.isFinite(pid) && pid !== process.pid) {
55375
+ if (Number.isFinite(pid) && pid !== process.pid && isManagedSessionHostPid2(pid)) {
55118
55376
  stopped = killPid2(pid) || stopped;
55119
55377
  }
55120
55378
  }
@@ -55128,40 +55386,7 @@ function stopManagedSessionHostProcess() {
55128
55386
  return stopped;
55129
55387
  }
55130
55388
  function stopSessionHost() {
55131
- let stopped = stopManagedSessionHostProcess();
55132
- if (process.platform === "win32") {
55133
- try {
55134
- const raw = (0, import_child_process12.execFileSync)("tasklist", ["/FO", "CSV", "/NH", "/FI", "IMAGENAME eq node.exe"], {
55135
- encoding: "utf8",
55136
- timeout: 5e3,
55137
- stdio: ["ignore", "pipe", "ignore"],
55138
- windowsHide: true
55139
- }).trim();
55140
- for (const line of raw.split(/\r?\n/)) {
55141
- const match = line.match(/^"node\.exe","(\d+)"/i);
55142
- if (!match) continue;
55143
- const candidatePid = Number.parseInt(match[1], 10);
55144
- if (!Number.isFinite(candidatePid) || candidatePid === process.pid) continue;
55145
- const commandLine = getWindowsProcessCommandLine(candidatePid);
55146
- if (commandLine?.includes("session-host-daemon")) {
55147
- stopped = killPid2(candidatePid) || stopped;
55148
- }
55149
- }
55150
- } catch {
55151
- }
55152
- } else {
55153
- try {
55154
- const raw = (0, import_child_process12.execFileSync)("pgrep", ["-f", "session-host-daemon"], { encoding: "utf8" }).trim();
55155
- for (const line of raw.split("\n")) {
55156
- const pid = Number.parseInt(line.trim(), 10);
55157
- if (Number.isFinite(pid) && pid !== process.pid && pid !== process.ppid) {
55158
- stopped = killPid2(pid) || stopped;
55159
- }
55160
- }
55161
- } catch {
55162
- }
55163
- }
55164
- return stopped;
55389
+ return stopManagedSessionHostProcess();
55165
55390
  }
55166
55391
  async function ensureSessionHostReady2() {
55167
55392
  const quarantine = quarantineLegacyStandaloneSessions({
@@ -55938,10 +56163,65 @@ var init_version = __esm({
55938
56163
  var adhdev_daemon_exports = {};
55939
56164
  __export(adhdev_daemon_exports, {
55940
56165
  AdhdevDaemon: () => AdhdevDaemon,
56166
+ buildMandatoryUpdateInfoFromServerPayload: () => buildMandatoryUpdateInfoFromServerPayload,
56167
+ buildMandatoryUpdateRequiredPayload: () => buildMandatoryUpdateRequiredPayload,
55941
56168
  getDaemonPid: () => getDaemonPid,
55942
56169
  isDaemonRunning: () => isDaemonRunning,
55943
- stopDaemon: () => stopDaemon
55944
- });
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
+ }
55945
56225
  function resolveDaemonPort(ref = {}) {
55946
56226
  return Number.isFinite(ref.port) && Number(ref.port) > 0 ? Number(ref.port) : DEFAULT_DAEMON_PORT;
55947
56227
  }
@@ -55968,7 +56248,7 @@ function removeDaemonPid(ref = {}) {
55968
56248
  function isDaemonRunning(ref = {}) {
55969
56249
  const port = resolveDaemonPort(ref);
55970
56250
  try {
55971
- const { execFileSync: execFileSync5 } = require("child_process");
56251
+ const { execFileSync: execFileSync6 } = require("child_process");
55972
56252
  const probe = `
55973
56253
  const http = require('http');
55974
56254
  const req = http.get('http://127.0.0.1:${port}/health', { timeout: 1500 }, (res) => {
@@ -55978,7 +56258,7 @@ function isDaemonRunning(ref = {}) {
55978
56258
  req.on('error', () => process.stdout.write('0'));
55979
56259
  req.on('timeout', () => { req.destroy(); process.stdout.write('0'); });
55980
56260
  `;
55981
- const result = execFileSync5(process.execPath, ["-e", probe], {
56261
+ const result = execFileSync6(process.execPath, ["-e", probe], {
55982
56262
  encoding: "utf-8",
55983
56263
  timeout: 3e3,
55984
56264
  stdio: ["ignore", "pipe", "ignore"]
@@ -56004,9 +56284,9 @@ function isDaemonRunning(ref = {}) {
56004
56284
  function isAdhdevProcess(pid) {
56005
56285
  try {
56006
56286
  if (process.platform === "win32") {
56007
- const { execFileSync: execFileSync5 } = require("child_process");
56287
+ const { execFileSync: execFileSync6 } = require("child_process");
56008
56288
  try {
56009
- const psOut = execFileSync5("powershell.exe", [
56289
+ const psOut = execFileSync6("powershell.exe", [
56010
56290
  "-NoProfile",
56011
56291
  "-NonInteractive",
56012
56292
  "-ExecutionPolicy",
@@ -56019,8 +56299,8 @@ function isAdhdevProcess(pid) {
56019
56299
  return true;
56020
56300
  }
56021
56301
  } else {
56022
- const { execFileSync: execFileSync5 } = require("child_process");
56023
- 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)], {
56024
56304
  encoding: "utf-8",
56025
56305
  timeout: 2e3,
56026
56306
  stdio: ["ignore", "pipe", "ignore"]
@@ -56034,7 +56314,7 @@ function isAdhdevProcess(pid) {
56034
56314
  function getDaemonHealthPid(ref = {}) {
56035
56315
  const port = resolveDaemonPort(ref);
56036
56316
  try {
56037
- const { execFileSync: execFileSync5 } = require("child_process");
56317
+ const { execFileSync: execFileSync6 } = require("child_process");
56038
56318
  const probe = `
56039
56319
  const http = require('http');
56040
56320
  const req = http.get('http://127.0.0.1:${port}/health', { timeout: 1500 }, (res) => {
@@ -56052,7 +56332,7 @@ function getDaemonHealthPid(ref = {}) {
56052
56332
  req.on('error', () => {});
56053
56333
  req.on('timeout', () => { req.destroy(); });
56054
56334
  `;
56055
- const result = execFileSync5(process.execPath, ["-e", probe], {
56335
+ const result = execFileSync6(process.execPath, ["-e", probe], {
56056
56336
  encoding: "utf-8",
56057
56337
  timeout: 3e3,
56058
56338
  stdio: ["ignore", "pipe", "ignore"]
@@ -56098,7 +56378,7 @@ function stopDaemon(ref = {}) {
56098
56378
  return false;
56099
56379
  }
56100
56380
  }
56101
- var os26, fs18, path26, import_http, import_ws3, pkgVersion, AdhdevDaemon;
56381
+ var os26, fs18, path26, import_http, import_child_process13, import_ws3, pkgVersion, AdhdevDaemon;
56102
56382
  var init_adhdev_daemon = __esm({
56103
56383
  "src/adhdev-daemon.ts"() {
56104
56384
  "use strict";
@@ -56114,12 +56394,13 @@ var init_adhdev_daemon = __esm({
56114
56394
  fs18 = __toESM(require("fs"));
56115
56395
  path26 = __toESM(require("path"));
56116
56396
  import_http = require("http");
56397
+ import_child_process13 = require("child_process");
56117
56398
  import_ws3 = require("ws");
56118
56399
  init_source2();
56119
56400
  init_version();
56120
56401
  init_src();
56121
56402
  init_runtime_defaults();
56122
- pkgVersion = resolvePackageVersion({ injectedVersion: "0.9.32" });
56403
+ pkgVersion = resolvePackageVersion({ injectedVersion: "0.9.34" });
56123
56404
  AdhdevDaemon = class _AdhdevDaemon {
56124
56405
  localHttpServer = null;
56125
56406
  localWss = null;
@@ -56212,6 +56493,10 @@ var init_adhdev_daemon = __esm({
56212
56493
  getUpgradePackageName() {
56213
56494
  return process.argv[1]?.includes("daemon-standalone") ? "@adhdev/daemon-standalone" : "adhdev";
56214
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
+ }
56215
56500
  hasBlockingSessionsForMandatoryUpdate() {
56216
56501
  if (!this.components) return false;
56217
56502
  const blocking = /* @__PURE__ */ new Set(["generating", "waiting_approval", "starting"]);
@@ -56237,15 +56522,7 @@ var init_adhdev_daemon = __esm({
56237
56522
  const pkgName = this.getUpgradePackageName();
56238
56523
  this.mandatoryUpgradeInFlight = true;
56239
56524
  try {
56240
- const { execSync: execSync7 } = await import("child_process");
56241
- const published = execSync7(`npm view ${pkgName}@${pending.targetVersion} version`, {
56242
- encoding: "utf-8",
56243
- timeout: 1e4,
56244
- stdio: ["pipe", "pipe", "pipe"]
56245
- }).trim();
56246
- if (published !== pending.targetVersion) {
56247
- throw new Error(`Published version mismatch: expected ${pending.targetVersion}, got ${published || "unknown"}`);
56248
- }
56525
+ verifyPublishedMandatoryUpdateTarget(pkgName, pending.targetVersion);
56249
56526
  LOG.warn("Upgrade", `Applying mandatory daemon update (${pending.reason}) \u2192 v${pending.targetVersion}`);
56250
56527
  spawnDetachedDaemonUpgradeHelper({
56251
56528
  packageName: pkgName,
@@ -56347,7 +56624,7 @@ var init_adhdev_daemon = __esm({
56347
56624
  const now = Date.now();
56348
56625
  const cached2 = this.hotChatSnapshotCache;
56349
56626
  const sessions = cached2 && now - cached2.builtAt < _AdhdevDaemon.HOT_CHAT_SNAPSHOT_CACHE_TTL_MS ? cached2.sessions : (() => {
56350
- const built = this.buildLiveStatusSnapshot().sessions || [];
56627
+ const built = this.components.instanceManager.collectHotChatSessionStates();
56351
56628
  this.hotChatSnapshotCache = { sessions: built, builtAt: now };
56352
56629
  return built;
56353
56630
  })();
@@ -56732,14 +57009,17 @@ ${err?.stack || ""}`);
56732
57009
  isRunning: () => this.running,
56733
57010
  isScreenshotActive: () => this.p2p?.screenshotActive ?? false,
56734
57011
  getScreenshotTargetSessionId: () => this.p2p?.screenshotTargetSessionId,
57012
+ getScreenshotTargetSessionIds: () => this.p2p?.screenshotTargetSessionIds ?? [],
56735
57013
  isUsingRelay: () => this.p2p?.isUsingRelay ?? false,
56736
57014
  hasAnyNeedingFirstFrame: () => this.p2p?.hasAnyNeedingFirstFrame() ?? false,
57015
+ hasAnyNeedingFirstFrameForTarget: (targetSessionId) => this.p2p?.hasAnyNeedingFirstFrameForTarget(targetSessionId) ?? false,
56737
57016
  getCdp: (targetSessionId) => {
56738
57017
  if (targetSessionId) return this.getCdpFor(targetSessionId);
56739
57018
  LOG.warn("P2P", "Screenshot requested without targetSessionId \u2014 cannot determine target session. Skipping frame.");
56740
57019
  return null;
56741
57020
  },
56742
- sendScreenshotBuffer: (buf) => this.p2p.sendScreenshotBuffer(buf)
57021
+ sendScreenshotBuffer: (buf) => this.p2p.sendScreenshotBuffer(buf),
57022
+ sendScreenshotBufferForTarget: (targetSessionId, buf) => this.p2p.sendScreenshotBufferForTarget(targetSessionId, buf)
56743
57023
  }, planLimits ?? void 0);
56744
57024
  this.screenshotController.start();
56745
57025
  this.p2p.onScreenshotStart(() => this.screenshotController?.triggerImmediate());
@@ -56768,11 +57048,12 @@ ${err?.stack || ""}`);
56768
57048
  });
56769
57049
  this.serverConn.on("force_update_required", (msg) => {
56770
57050
  const payload = msg.payload;
56771
- this.pendingMandatoryUpdate = {
56772
- targetVersion: typeof payload.latest === "string" && payload.latest.trim() ? payload.latest.trim() : pkgVersion,
56773
- reason: typeof payload.reason === "string" && payload.reason.trim() ? payload.reason.trim() : "major_minor_mismatch",
56774
- minVersion: typeof payload.minVersion === "string" && payload.minVersion.trim() ? payload.minVersion.trim() : void 0
56775
- };
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;
56776
57057
  LOG.warn("Upgrade", `Mandatory daemon update required (${this.pendingMandatoryUpdate.reason})`);
56777
57058
  void this.maybeApplyMandatoryUpdate("server-force-update");
56778
57059
  });
@@ -56883,15 +57164,9 @@ ${err?.stack || ""}`);
56883
57164
  const cmdStart = Date.now();
56884
57165
  const source = msg.ipcWs ? "ext" : typeof msg.source === "string" && msg.source.trim() ? msg.source : "ws";
56885
57166
  try {
56886
- if (this.pendingMandatoryUpdate && _AdhdevDaemon.MANDATORY_UPDATE_BLOCKED_COMMANDS.has(cmd)) {
56887
- this.sendResult(msg, false, {
56888
- error: "A mandatory daemon update is pending. Finish current work and let the daemon update before starting a new session.",
56889
- code: "DAEMON_UPDATE_REQUIRED",
56890
- required: true,
56891
- latest: this.pendingMandatoryUpdate.targetVersion,
56892
- reason: this.pendingMandatoryUpdate.reason,
56893
- interactionId
56894
- });
57167
+ const mandatoryUpdateBlock = this.getMandatoryUpdateBlockPayload(cmd, interactionId);
57168
+ if (mandatoryUpdateBlock) {
57169
+ this.sendResult(msg, false, mandatoryUpdateBlock);
56895
57170
  return;
56896
57171
  }
56897
57172
  if (source === "api" && !loadConfig().allowServerApiProxy) {
@@ -56936,6 +57211,10 @@ ${err?.stack || ""}`);
56936
57211
  const interactionId = String(normalizedData._interactionId);
56937
57212
  const cmdStart = Date.now();
56938
57213
  try {
57214
+ const mandatoryUpdateBlock = this.getMandatoryUpdateBlockPayload(cmdType, interactionId);
57215
+ if (mandatoryUpdateBlock) {
57216
+ return { success: false, ...mandatoryUpdateBlock };
57217
+ }
56939
57218
  switch (cmdType) {
56940
57219
  case "get_runtime_snapshot": {
56941
57220
  const sessionId = typeof normalizedData.sessionId === "string" ? normalizedData.sessionId : "";
@@ -57114,8 +57393,22 @@ ${err?.stack || ""}`);
57114
57393
  }));
57115
57394
  return;
57116
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
+ }
57117
57410
  try {
57118
- const result = await this.components.router.execute(command, args, "ipc");
57411
+ const result = await this.components.router.execute(command, normalizedArgs, "ipc");
57119
57412
  ws.send(JSON.stringify({
57120
57413
  type: "ext:command_result",
57121
57414
  payload: {
@@ -85940,12 +86233,12 @@ function splitStringBySpace(str) {
85940
86233
  }
85941
86234
  return pieces;
85942
86235
  }
85943
- 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;
85944
86237
  var init_esm2 = __esm({
85945
86238
  "../../node_modules/@inquirer/external-editor/dist/esm/index.js"() {
85946
86239
  "use strict";
85947
86240
  import_chardet = __toESM(require_lib2(), 1);
85948
- import_child_process13 = require("child_process");
86241
+ import_child_process14 = require("child_process");
85949
86242
  import_fs7 = require("fs");
85950
86243
  import_node_path2 = __toESM(require("path"), 1);
85951
86244
  import_node_os4 = __toESM(require("os"), 1);
@@ -86052,7 +86345,7 @@ var init_esm2 = __esm({
86052
86345
  }
86053
86346
  launchEditor() {
86054
86347
  try {
86055
- 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" });
86056
86349
  this.lastExitStatus = editorProcess.status ?? 0;
86057
86350
  } catch (launchError) {
86058
86351
  throw new LaunchEditorError(launchError);
@@ -86060,7 +86353,7 @@ var init_esm2 = __esm({
86060
86353
  }
86061
86354
  launchEditorAsync(callback) {
86062
86355
  try {
86063
- 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" });
86064
86357
  editorProcess.on("exit", (code) => {
86065
86358
  this.lastExitStatus = code;
86066
86359
  setImmediate(callback);
@@ -88338,9 +88631,9 @@ async function runWizard(options = {}) {
88338
88631
  }
88339
88632
  async function checkForUpdate() {
88340
88633
  try {
88341
- const { execFileSync: execFileSync5 } = await import("child_process");
88634
+ const { execFileSync: execFileSync6 } = await import("child_process");
88342
88635
  const currentVersion = resolvePackageVersion();
88343
- const latestVersion = readLatestPublishedCliVersion(execFileSync5);
88636
+ const latestVersion = readLatestPublishedCliVersion(execFileSync6);
88344
88637
  if (!latestVersion) return;
88345
88638
  if (!currentVersion || !latestVersion || currentVersion === latestVersion) return;
88346
88639
  console.log(source_default2.yellow(` Update available: ${currentVersion} \u2192 ${latestVersion}`));
@@ -88357,7 +88650,7 @@ async function checkForUpdate() {
88357
88650
  const spinner = (await Promise.resolve().then(() => (init_ora(), ora_exports))).default("Updating adhdev CLI...").start();
88358
88651
  try {
88359
88652
  const installCommand = buildPinnedGlobalInstallCommand({ packageName: "adhdev", targetVersion: "latest" });
88360
- execFileSync5(installCommand.command, installCommand.args, {
88653
+ execFileSync6(installCommand.command, installCommand.args, {
88361
88654
  encoding: "utf-8",
88362
88655
  timeout: 6e4,
88363
88656
  stdio: ["pipe", "pipe", "pipe"]