adhdev 0.9.33 → 0.9.35

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
@@ -9954,7 +9954,14 @@ var init_handler = __esm({
9954
9954
  await this._ctx.providerLoader.fetchLatest().catch(() => {
9955
9955
  });
9956
9956
  this._ctx.providerLoader.reload();
9957
- return { success: true };
9957
+ this._ctx.providerLoader.registerToDetector();
9958
+ const refreshedInstances = this._ctx.instanceManager ? this._ctx.instanceManager.refreshProviderDefinitions((providerType) => this._ctx.providerLoader.resolve(providerType)) : 0;
9959
+ const providers = this._ctx.providerLoader.getAll().map((provider) => ({
9960
+ type: provider.type,
9961
+ name: provider.name,
9962
+ category: provider.category
9963
+ }));
9964
+ return { success: true, refreshedInstances, providers };
9958
9965
  }
9959
9966
  return { success: false, error: "ProviderLoader not initialized" };
9960
9967
  }
@@ -11413,13 +11420,14 @@ function sliceFromOffset(text, start) {
11413
11420
  function hydrateCliParsedMessages(parsedMessages, options) {
11414
11421
  const { committedMessages, scope, lastOutputAt } = options;
11415
11422
  const referenceMessages = [...committedMessages];
11423
+ const referenceComparables = referenceMessages.map((message) => normalizeComparableMessageContent(message?.content || ""));
11416
11424
  const usedReferenceIndexes = /* @__PURE__ */ new Set();
11417
11425
  const now = options.now ?? Date.now();
11418
11426
  const findReferenceTimestamp = (role, content, parsedIndex) => {
11419
11427
  const normalizedContent = normalizeComparableMessageContent(content);
11420
11428
  if (!normalizedContent) return void 0;
11421
11429
  const sameIndex = referenceMessages[parsedIndex];
11422
- if (sameIndex && !usedReferenceIndexes.has(parsedIndex) && sameIndex.role === role && normalizeComparableMessageContent(sameIndex.content) === normalizedContent && typeof sameIndex.timestamp === "number" && Number.isFinite(sameIndex.timestamp)) {
11430
+ if (sameIndex && !usedReferenceIndexes.has(parsedIndex) && sameIndex.role === role && referenceComparables[parsedIndex] === normalizedContent && typeof sameIndex.timestamp === "number" && Number.isFinite(sameIndex.timestamp)) {
11423
11431
  usedReferenceIndexes.add(parsedIndex);
11424
11432
  return sameIndex.timestamp;
11425
11433
  }
@@ -11427,7 +11435,7 @@ function hydrateCliParsedMessages(parsedMessages, options) {
11427
11435
  if (usedReferenceIndexes.has(i)) continue;
11428
11436
  const candidate = referenceMessages[i];
11429
11437
  if (!candidate || candidate.role !== role) continue;
11430
- const candidateContent = normalizeComparableMessageContent(candidate.content);
11438
+ const candidateContent = referenceComparables[i];
11431
11439
  if (!candidateContent) continue;
11432
11440
  const exactMatch = candidateContent === normalizedContent;
11433
11441
  const fuzzyMatch = candidateContent.includes(normalizedContent) || normalizedContent.includes(candidateContent);
@@ -11809,6 +11817,8 @@ var init_provider_cli_adapter = __esm({
11809
11817
  messages = [];
11810
11818
  committedMessages = [];
11811
11819
  structuredMessages = [];
11820
+ committedMessagesActivitySignature = "";
11821
+ committedMessagesChangedAt = 0;
11812
11822
  currentStatus = "starting";
11813
11823
  onStatusChange = null;
11814
11824
  responseBuffer = "";
@@ -11890,10 +11900,35 @@ var init_provider_cli_adapter = __esm({
11890
11900
  providerResolutionMeta;
11891
11901
  static FINISH_RETRY_DELAY_MS = 300;
11892
11902
  static MAX_FINISH_RETRIES = 2;
11903
+ buildCommittedMessagesActivitySignature() {
11904
+ const last = this.committedMessages[this.committedMessages.length - 1];
11905
+ return [
11906
+ String(this.committedMessages.length),
11907
+ String(last?.role || ""),
11908
+ String(last?.kind || ""),
11909
+ String(last?.senderName || ""),
11910
+ String(last?.timestamp || ""),
11911
+ String(last?.receivedAt || ""),
11912
+ normalizeComparableMessageContent(last?.content || "").slice(-240)
11913
+ ].join("|");
11914
+ }
11893
11915
  syncMessageViews() {
11916
+ const signature = this.buildCommittedMessagesActivitySignature();
11917
+ if (signature !== this.committedMessagesActivitySignature) {
11918
+ this.committedMessagesActivitySignature = signature;
11919
+ this.committedMessagesChangedAt = Date.now();
11920
+ }
11894
11921
  this.messages = [...this.committedMessages];
11895
11922
  this.structuredMessages = [...this.committedMessages];
11896
11923
  }
11924
+ getLastCommittedMessageActivityAt() {
11925
+ const last = this.committedMessages[this.committedMessages.length - 1];
11926
+ const messageTime = Math.max(
11927
+ typeof last?.receivedAt === "number" && Number.isFinite(last.receivedAt) ? last.receivedAt : 0,
11928
+ typeof last?.timestamp === "number" && Number.isFinite(last.timestamp) ? last.timestamp : 0
11929
+ );
11930
+ return Math.max(messageTime, this.committedMessagesChangedAt || 0);
11931
+ }
11897
11932
  readTerminalScreenText(now = Date.now()) {
11898
11933
  const screenText = this.terminalScreen.getText() || "";
11899
11934
  this.lastScreenText = screenText;
@@ -12033,9 +12068,16 @@ var init_provider_cli_adapter = __esm({
12033
12068
  /** Inject CLI scripts after construction (e.g. when resolved by ProviderLoader) */
12034
12069
  setCliScripts(scripts) {
12035
12070
  this.cliScripts = scripts;
12071
+ this.parsedStatusCache = null;
12072
+ this.parseErrorMessage = null;
12036
12073
  const scriptNames = listCliScriptNames(scripts);
12037
12074
  LOG.info("CLI", `[${this.cliType}] CLI scripts injected: [${scriptNames.join(", ")}]`);
12038
12075
  }
12076
+ /** Refresh provider scripts/config used by this adapter without restarting the PTY runtime. */
12077
+ refreshProviderDefinition(provider) {
12078
+ this.provider = provider;
12079
+ this.setCliScripts(provider.scripts || {});
12080
+ }
12039
12081
  updateRuntimeSettings(settings) {
12040
12082
  this.runtimeSettings = { ...settings };
12041
12083
  }
@@ -12519,7 +12561,7 @@ var init_provider_cli_adapter = __esm({
12519
12561
  return;
12520
12562
  }
12521
12563
  if (this.currentTurnScope && !lastParsedAssistant) {
12522
- LOG.info(
12564
+ LOG.debug(
12523
12565
  "CLI",
12524
12566
  `[${this.cliType}] Settled without assistant: prompt=${JSON.stringify(this.currentTurnScope.prompt).slice(0, 140)} responseBuffer=${JSON.stringify(summarizeCliTraceText(this.responseBuffer, 220)).slice(0, 260)} screen=${JSON.stringify(summarizeCliTraceText(screenText, 220)).slice(0, 260)} providerDir=${this.providerResolutionMeta.providerDir || "-"} scriptDir=${this.providerResolutionMeta.scriptDir || "-"}`
12525
12567
  );
@@ -13335,9 +13377,43 @@ var init_provider_cli_adapter = __esm({
13335
13377
  }
13336
13378
  armResponseTimeout() {
13337
13379
  if (this.responseTimeout) clearTimeout(this.responseTimeout);
13380
+ const timeoutMs = this.timeouts.maxResponse;
13381
+ if (!Number.isFinite(timeoutMs) || timeoutMs <= 0) {
13382
+ this.responseTimeout = null;
13383
+ return;
13384
+ }
13338
13385
  this.responseTimeout = setTimeout(() => {
13339
- if (this.isWaitingForResponse) this.finishResponse();
13340
- }, this.timeouts.maxResponse);
13386
+ this.responseTimeout = null;
13387
+ if (!this.isWaitingForResponse) return;
13388
+ const detectedStatusBeforeEval = this.runDetectStatus(this.recentOutputBuffer);
13389
+ this.recordTrace("response_timeout_check", {
13390
+ timeoutMs,
13391
+ detectedStatus: detectedStatusBeforeEval,
13392
+ currentStatus: this.currentStatus,
13393
+ isWaitingForResponse: this.isWaitingForResponse,
13394
+ hasActionableApproval: this.hasActionableApproval(),
13395
+ ...buildCliTraceParseSnapshot({
13396
+ accumulatedBuffer: this.accumulatedBuffer,
13397
+ accumulatedRawBuffer: this.accumulatedRawBuffer,
13398
+ responseBuffer: this.responseBuffer,
13399
+ partialResponse: this.responseBuffer,
13400
+ scope: this.currentTurnScope
13401
+ })
13402
+ });
13403
+ this.settledBuffer = this.recentOutputBuffer;
13404
+ this.evaluateSettled();
13405
+ if (this.isWaitingForResponse && !this.hasActionableApproval()) {
13406
+ const detectedStatusAfterEval = this.runDetectStatus(this.recentOutputBuffer);
13407
+ this.recordTrace("response_timeout_kept_open", {
13408
+ timeoutMs,
13409
+ detectedStatusBeforeEval,
13410
+ detectedStatusAfterEval,
13411
+ currentStatus: this.currentStatus,
13412
+ isWaitingForResponse: this.isWaitingForResponse
13413
+ });
13414
+ this.armResponseTimeout();
13415
+ }
13416
+ }, timeoutMs);
13341
13417
  }
13342
13418
  writeSubmitKeyForRetry(mode) {
13343
13419
  void this.writeToPty(this.sendKey).catch((error48) => {
@@ -14023,6 +14099,11 @@ var init_cli_provider_instance = __esm({
14023
14099
  launchMode;
14024
14100
  startedAt = Date.now();
14025
14101
  onProviderSessionResolved;
14102
+ refreshProviderDefinition(provider) {
14103
+ if (provider.type !== this.type || provider.category !== "cli") return;
14104
+ this.provider = provider;
14105
+ this.adapter.refreshProviderDefinition(provider);
14106
+ }
14026
14107
  // ─── Lifecycle ─────────────────────────────────
14027
14108
  async init(context) {
14028
14109
  this.context = context;
@@ -14228,6 +14309,22 @@ var init_cli_provider_instance = __esm({
14228
14309
  getPresentationMode() {
14229
14310
  return this.presentationMode;
14230
14311
  }
14312
+ getHotChatSessionState() {
14313
+ const adapterStatus = this.adapter.getStatus();
14314
+ const autoApproveActive = adapterStatus.status === "waiting_approval" && this.shouldAutoApprove();
14315
+ const visibleStatus = autoApproveActive ? "generating" : adapterStatus.status;
14316
+ const runtime = this.adapter.getRuntimeMetadata();
14317
+ const lastCommittedMessageActivityAt = typeof this.adapter.getLastCommittedMessageActivityAt === "function" ? this.adapter.getLastCommittedMessageActivityAt() : 0;
14318
+ return {
14319
+ id: this.instanceId,
14320
+ status: visibleStatus,
14321
+ lastMessageAt: lastCommittedMessageActivityAt || void 0,
14322
+ runtimeLifecycle: runtime?.lifecycle ?? null,
14323
+ runtimeSurfaceKind: runtime?.surfaceKind,
14324
+ runtimeRestoredFromStorage: runtime?.restoredFromStorage === true,
14325
+ runtimeRecoveryState: runtime?.recoveryState ?? null
14326
+ };
14327
+ }
14231
14328
  updateSettings(newSettings) {
14232
14329
  this.settings = { ...newSettings };
14233
14330
  this.adapter.updateRuntimeSettings?.(this.settings);
@@ -14383,6 +14480,15 @@ var init_cli_provider_instance = __esm({
14383
14480
  this.completedDebouncePending = { chatTitle, duration: duration3, timestamp: now };
14384
14481
  this.completedDebounceTimer = setTimeout(() => {
14385
14482
  if (this.completedDebouncePending) {
14483
+ const latestStatus = this.adapter.getStatus();
14484
+ const latestAutoApproveActive = latestStatus.status === "waiting_approval" && this.shouldAutoApprove();
14485
+ const latestVisibleStatus = latestAutoApproveActive ? "generating" : latestStatus.status;
14486
+ if (latestVisibleStatus !== "idle") {
14487
+ LOG.info("CLI", `[${this.type}] cancelled pending completed (resumed ${latestVisibleStatus})`);
14488
+ this.completedDebouncePending = null;
14489
+ this.completedDebounceTimer = null;
14490
+ return;
14491
+ }
14386
14492
  LOG.info("CLI", `[${this.type}] completed in ${this.completedDebouncePending.duration}s`);
14387
14493
  this.pushEvent({ event: "agent:generating_completed", ...this.completedDebouncePending });
14388
14494
  this.completedDebouncePending = null;
@@ -37502,6 +37608,51 @@ function killPid(pid) {
37502
37608
  return false;
37503
37609
  }
37504
37610
  }
37611
+ function getWindowsProcessCommandLine(pid) {
37612
+ const pidFilter = `ProcessId=${pid}`;
37613
+ try {
37614
+ const psOut = (0, import_child_process8.execFileSync)("powershell.exe", [
37615
+ "-NoProfile",
37616
+ "-NonInteractive",
37617
+ "-ExecutionPolicy",
37618
+ "Bypass",
37619
+ "-Command",
37620
+ `(Get-CimInstance Win32_Process -Filter "${pidFilter}").CommandLine`
37621
+ ], { encoding: "utf8", timeout: 5e3, stdio: ["ignore", "pipe", "ignore"] }).trim();
37622
+ if (psOut) return psOut;
37623
+ } catch {
37624
+ }
37625
+ try {
37626
+ const wmicOut = (0, import_child_process8.execFileSync)("wmic", [
37627
+ "process",
37628
+ "where",
37629
+ pidFilter,
37630
+ "get",
37631
+ "CommandLine"
37632
+ ], { encoding: "utf8", timeout: 3e3, stdio: ["ignore", "pipe", "ignore"] }).trim();
37633
+ if (wmicOut) return wmicOut;
37634
+ } catch {
37635
+ }
37636
+ return null;
37637
+ }
37638
+ function getProcessCommandLine(pid) {
37639
+ if (!Number.isFinite(pid) || pid <= 0) return null;
37640
+ if (process.platform === "win32") return getWindowsProcessCommandLine(pid);
37641
+ try {
37642
+ const text = (0, import_child_process8.execFileSync)("ps", ["-o", "command=", "-p", String(pid)], {
37643
+ encoding: "utf8",
37644
+ timeout: 3e3,
37645
+ stdio: ["ignore", "pipe", "ignore"]
37646
+ }).trim();
37647
+ return text || null;
37648
+ } catch {
37649
+ return null;
37650
+ }
37651
+ }
37652
+ function isManagedSessionHostPid(pid) {
37653
+ const commandLine = getProcessCommandLine(pid);
37654
+ return !!commandLine && /session-host-daemon/i.test(commandLine);
37655
+ }
37505
37656
  async function waitForPidExit(pid, timeoutMs) {
37506
37657
  const start = Date.now();
37507
37658
  while (Date.now() - start < timeoutMs) {
@@ -37518,7 +37669,7 @@ function stopSessionHostProcesses(appName) {
37518
37669
  try {
37519
37670
  if (fs8.existsSync(pidFile)) {
37520
37671
  const pid = Number.parseInt(fs8.readFileSync(pidFile, "utf8").trim(), 10);
37521
- if (Number.isFinite(pid)) {
37672
+ if (Number.isFinite(pid) && pid !== process.pid && isManagedSessionHostPid(pid)) {
37522
37673
  killPid(pid);
37523
37674
  }
37524
37675
  }
@@ -37529,18 +37680,6 @@ function stopSessionHostProcesses(appName) {
37529
37680
  } catch {
37530
37681
  }
37531
37682
  }
37532
- if (process.platform !== "win32") {
37533
- try {
37534
- const raw = (0, import_child_process8.execFileSync)("pgrep", ["-f", "session-host-daemon"], { encoding: "utf8" }).trim();
37535
- for (const line of raw.split("\n")) {
37536
- const pid = Number.parseInt(line.trim(), 10);
37537
- if (Number.isFinite(pid)) {
37538
- killPid(pid);
37539
- }
37540
- }
37541
- } catch {
37542
- }
37543
- }
37544
37683
  }
37545
37684
  function removeDaemonPidFile() {
37546
37685
  const pidFile = path16.join(os19.homedir(), ".adhdev", "daemon.pid");
@@ -39882,6 +40021,23 @@ var init_forward = __esm({
39882
40021
  });
39883
40022
 
39884
40023
  // ../../oss/packages/daemon-core/src/providers/provider-instance-manager.ts
40024
+ function projectHotChatSessionStatesFromProviderState(state) {
40025
+ const project = (item) => ({
40026
+ id: item.instanceId,
40027
+ status: item.activeChat?.status || item.status,
40028
+ unread: item.unread,
40029
+ inboxBucket: item.inboxBucket,
40030
+ lastMessageAt: item.lastMessageAt ?? item.activeChat?.lastMessageAt,
40031
+ runtimeLifecycle: item.runtime?.lifecycle ?? null,
40032
+ runtimeSurfaceKind: item.runtime?.surfaceKind,
40033
+ runtimeRestoredFromStorage: item.runtime?.restoredFromStorage === true,
40034
+ runtimeRecoveryState: item.runtime?.recoveryState ?? null
40035
+ });
40036
+ if (state.category === "ide") {
40037
+ return [project(state), ...state.extensions.map(project)];
40038
+ }
40039
+ return [project(state)];
40040
+ }
39885
40041
  var ProviderInstanceManager;
39886
40042
  var init_provider_instance_manager = __esm({
39887
40043
  "../../oss/packages/daemon-core/src/providers/provider-instance-manager.ts"() {
@@ -39982,6 +40138,27 @@ var init_provider_instance_manager = __esm({
39982
40138
  }
39983
40139
  return states;
39984
40140
  }
40141
+ collectHotChatSessionStates() {
40142
+ const sessions = [];
40143
+ for (const [id, instance] of this.instances) {
40144
+ try {
40145
+ const projected = instance.getHotChatSessionState?.();
40146
+ if (Array.isArray(projected)) {
40147
+ sessions.push(...projected.filter((session) => !!session?.id));
40148
+ continue;
40149
+ }
40150
+ if (projected?.id) {
40151
+ sessions.push(projected);
40152
+ continue;
40153
+ }
40154
+ const state = instance.getState();
40155
+ sessions.push(...projectHotChatSessionStatesFromProviderState(state));
40156
+ } catch (e) {
40157
+ LOG.warn("InstanceMgr", `[InstanceManager] Failed to collect hot chat metadata from ${id}: ${e.message}`);
40158
+ }
40159
+ }
40160
+ return sessions;
40161
+ }
39985
40162
  /**
39986
40163
  * Per-category status collect
39987
40164
  */
@@ -40063,6 +40240,17 @@ var init_provider_instance_manager = __esm({
40063
40240
  }
40064
40241
  return updated;
40065
40242
  }
40243
+ refreshProviderDefinitions(resolveProvider) {
40244
+ let refreshed = 0;
40245
+ for (const instance of this.instances.values()) {
40246
+ if (typeof instance.refreshProviderDefinition !== "function") continue;
40247
+ const provider = resolveProvider(instance.type);
40248
+ if (!provider || typeof provider !== "object") continue;
40249
+ instance.refreshProviderDefinition(provider);
40250
+ refreshed += 1;
40251
+ }
40252
+ return refreshed;
40253
+ }
40066
40254
  // ─── cleanup ──────────────────────────────────────
40067
40255
  /**
40068
40256
  * All terminate
@@ -44345,20 +44533,7 @@ var init_dev_server = __esm({
44345
44533
  async handleReload(_req, res) {
44346
44534
  try {
44347
44535
  this.providerLoader.reload();
44348
- let refreshedInstances = 0;
44349
- if (this.instanceManager) {
44350
- for (const id of this.instanceManager.listInstanceIds()) {
44351
- const instance = this.instanceManager.getInstance(id);
44352
- const providerType = typeof instance?.type === "string" ? instance.type : "";
44353
- if (!providerType) continue;
44354
- const resolved = this.providerLoader.resolve(providerType);
44355
- if (!resolved) continue;
44356
- if (instance && typeof instance === "object" && "provider" in instance) {
44357
- instance.provider = resolved;
44358
- refreshedInstances += 1;
44359
- }
44360
- }
44361
- }
44536
+ const refreshedInstances = this.instanceManager ? this.instanceManager.refreshProviderDefinitions((providerType) => this.providerLoader.resolve(providerType)) : 0;
44362
44537
  const providers = this.providerLoader.getAll().map((p) => ({
44363
44538
  type: p.type,
44364
44539
  name: p.name,
@@ -47464,19 +47639,20 @@ var init_screenshot_sender = __esm({
47464
47639
  }
47465
47640
  return sentAny;
47466
47641
  }
47467
- sendScreenshot(peers, base64Data) {
47642
+ sendScreenshot(peers, base64Data, targetSessionId) {
47468
47643
  const buffer = Buffer.from(base64Data, "base64");
47469
- return this.sendScreenshotBuffer(peers, buffer);
47644
+ return this.sendScreenshotBuffer(peers, buffer, targetSessionId);
47470
47645
  }
47471
47646
  /** Send screenshot as raw Buffer (no base64 conversion overhead) */
47472
- sendScreenshotBuffer(peers, buffer) {
47647
+ sendScreenshotBuffer(peers, buffer, targetSessionId) {
47473
47648
  let sentAny = false;
47474
47649
  let debugOnce = !this._ssDebugDone;
47475
47650
  for (const [pid, peer] of peers.entries()) {
47476
47651
  if (debugOnce) {
47477
- logDebug(`sendScreenshot peer=${pid}: state=${peer.state}, hasCh=${!!peer.dataChannel}, ssActive=${peer.screenshotActive}, chOpen=${peer.dataChannel?.isOpen?.() ?? "N/A"}, bufSize=${buffer.length}`);
47652
+ logDebug(`sendScreenshot peer=${pid}: state=${peer.state}, hasCh=${!!peer.dataChannel}, ssActive=${peer.screenshotActive}, target=${peer.screenshotTargetSessionId || "none"}, chOpen=${peer.dataChannel?.isOpen?.() ?? "N/A"}, bufSize=${buffer.length}`);
47478
47653
  }
47479
47654
  if (peer.state !== "connected" || !peer.dataChannel || !peer.screenshotActive) continue;
47655
+ if (targetSessionId && peer.screenshotTargetSessionId !== targetSessionId) continue;
47480
47656
  try {
47481
47657
  if (!peer.dataChannel.isOpen()) continue;
47482
47658
  const header = Buffer.alloc(4);
@@ -47508,18 +47684,32 @@ async function initiateConnection(deps, peerId, sharePermission) {
47508
47684
  log("Cannot initiate \u2014 node-datachannel not available");
47509
47685
  return;
47510
47686
  }
47687
+ const pid = peerId || `legacy_${Date.now()}`;
47688
+ const existing = deps.peers.get(pid);
47689
+ if (existing?.state === "connected") {
47690
+ log(`initiateconnection() ignored for peer ${pid} \u2014 already connected`);
47691
+ return;
47692
+ }
47693
+ if (existing?.state === "connecting") {
47694
+ log(`initiateconnection() ignored for peer ${pid} \u2014 connection already in progress`);
47695
+ return;
47696
+ }
47511
47697
  const limits = deps.serverConn.getPlanLimits();
47512
47698
  if (limits && limits.maxP2PConnections !== -1) {
47513
47699
  let connectedCount = 0;
47700
+ let reservedCount = 0;
47514
47701
  for (const peer of deps.peers.values()) {
47515
47702
  if (peer.state === "connected") connectedCount++;
47703
+ if (peer.state === "connected" || peer.state === "connecting") reservedCount++;
47516
47704
  }
47517
- if (connectedCount >= limits.maxP2PConnections) {
47705
+ if (reservedCount >= limits.maxP2PConnections) {
47518
47706
  let oldestPeer = null;
47519
- for (const [pid2, peer] of deps.peers) {
47520
- if (peer.state === "connected") {
47521
- if (!oldestPeer || peer.connectedAt < oldestPeer.at) {
47522
- oldestPeer = { id: pid2, at: peer.connectedAt };
47707
+ if (connectedCount >= limits.maxP2PConnections) {
47708
+ for (const [peerKey, peer] of deps.peers) {
47709
+ if (peer.state === "connected") {
47710
+ if (!oldestPeer || peer.connectedAt < oldestPeer.at) {
47711
+ oldestPeer = { id: peerKey, at: peer.connectedAt };
47712
+ }
47523
47713
  }
47524
47714
  }
47525
47715
  }
@@ -47537,19 +47727,12 @@ async function initiateConnection(deps, peerId, sharePermission) {
47537
47727
  }
47538
47728
  }
47539
47729
  disconnectPeer(deps.peers, oldestPeer.id, deps.notifyStateChange);
47730
+ } else {
47731
+ log(`P2P limit reached (${reservedCount}/${limits.maxP2PConnections}) with reserved connecting slot(s). Rejecting peer ${pid.slice(0, 12)}\u2026`);
47732
+ return;
47540
47733
  }
47541
47734
  }
47542
47735
  }
47543
- const pid = peerId || `legacy_${Date.now()}`;
47544
- const existing = deps.peers.get(pid);
47545
- if (existing?.state === "connected") {
47546
- log(`initiateconnection() ignored for peer ${pid} \u2014 already connected`);
47547
- return;
47548
- }
47549
- if (existing?.state === "connecting") {
47550
- log(`initiateconnection() ignored for peer ${pid} \u2014 connection already in progress`);
47551
- return;
47552
- }
47553
47736
  log(`initiateconnection() for peer ${pid}...`);
47554
47737
  const mod = deps.nodeDatachannel;
47555
47738
  const PeerConnectionCtor = mod.PeerConnection || mod.default?.PeerConnection || mod.default || mod;
@@ -47908,14 +48091,19 @@ var init_daemon_p2p = __esm({
47908
48091
  }
47909
48092
  return false;
47910
48093
  }
47911
- /** Get the target session for the currently active screenshot request */
47912
- get screenshotTargetSessionId() {
48094
+ /** Get all target sessions for active screenshot requests */
48095
+ get screenshotTargetSessionIds() {
48096
+ const targets = /* @__PURE__ */ new Set();
47913
48097
  for (const peer of this.peers.values()) {
47914
48098
  if (peer.screenshotActive && peer.state === "connected" && peer.screenshotTargetSessionId) {
47915
- return peer.screenshotTargetSessionId;
48099
+ targets.add(peer.screenshotTargetSessionId);
47916
48100
  }
47917
48101
  }
47918
- return void 0;
48102
+ return Array.from(targets);
48103
+ }
48104
+ /** Get the target session for the currently active screenshot request */
48105
+ get screenshotTargetSessionId() {
48106
+ return this.screenshotTargetSessionIds[0];
47919
48107
  }
47920
48108
  constructor(serverConn) {
47921
48109
  this.serverConn = serverConn;
@@ -48024,6 +48212,14 @@ ${e?.stack || ""}`);
48024
48212
  }
48025
48213
  return false;
48026
48214
  }
48215
+ hasAnyNeedingFirstFrameForTarget(targetSessionId) {
48216
+ for (const peer of this.peers.values()) {
48217
+ if (peer.needsFirstFrame && peer.screenshotActive && peer.state === "connected" && peer.screenshotTargetSessionId === targetSessionId) {
48218
+ return true;
48219
+ }
48220
+ }
48221
+ return false;
48222
+ }
48027
48223
  onStateChange(listener) {
48028
48224
  this.stateListeners.push(listener);
48029
48225
  }
@@ -48087,11 +48283,14 @@ ${e?.stack || ""}`);
48087
48283
  if (targetPeers.size === 0) return false;
48088
48284
  return this.screenshotSender.broadcastSessionOutput(targetPeers, sessionId, data);
48089
48285
  }
48090
- sendScreenshot(base64Data) {
48091
- return this.screenshotSender.sendScreenshot(this.peers, base64Data);
48286
+ sendScreenshot(base64Data, targetSessionId) {
48287
+ return this.screenshotSender.sendScreenshot(this.peers, base64Data, targetSessionId);
48092
48288
  }
48093
- sendScreenshotBuffer(buffer) {
48094
- return this.screenshotSender.sendScreenshotBuffer(this.peers, buffer);
48289
+ sendScreenshotBuffer(buffer, targetSessionId) {
48290
+ return this.screenshotSender.sendScreenshotBuffer(this.peers, buffer, targetSessionId);
48291
+ }
48292
+ sendScreenshotBufferForTarget(targetSessionId, buffer) {
48293
+ return this.sendScreenshotBuffer(buffer, targetSessionId);
48095
48294
  }
48096
48295
  // ─── Handler registration (unchanged API) ───────
48097
48296
  onFileRequest(handler) {
@@ -54815,6 +55014,7 @@ var init_screenshot_controller = __esm({
54815
55014
  lastSize = 0;
54816
55015
  lastHash = 0;
54817
55016
  staticFrameCount = 0;
55017
+ targetFrameState = /* @__PURE__ */ new Map();
54818
55018
  currentInterval;
54819
55019
  // Quality profiles
54820
55020
  profileDirect;
@@ -54878,14 +55078,13 @@ var init_screenshot_controller = __esm({
54878
55078
  async tick() {
54879
55079
  if (!this.deps.isRunning()) return;
54880
55080
  const active = this.deps.isScreenshotActive();
54881
- const targetSessionId = this.deps.getScreenshotTargetSessionId();
54882
- if (active && !targetSessionId) {
55081
+ const targetSessionIds = this.getActiveTargetSessionIds();
55082
+ const isRelay = this.deps.isUsingRelay();
55083
+ const profile = isRelay ? this.profileRelay : this.profileDirect;
55084
+ if (active && targetSessionIds.length === 0) {
54883
55085
  this.timer = setTimeout(() => this.tick(), 500);
54884
55086
  return;
54885
55087
  }
54886
- const cdp = targetSessionId ? this.deps.getCdp(targetSessionId) : null;
54887
- const isRelay = this.deps.isUsingRelay();
54888
- const profile = isRelay ? this.profileRelay : this.profileDirect;
54889
55088
  this.checkBudgetReset();
54890
55089
  if (this.dailyBudgetMs > 0) {
54891
55090
  const now = Date.now();
@@ -54900,49 +55099,84 @@ var init_screenshot_controller = __esm({
54900
55099
  }
54901
55100
  }
54902
55101
  const budgetBlocked = this.budgetExhausted && isRelay;
54903
- if (!active || !cdp || budgetBlocked) {
55102
+ if (!active || budgetBlocked) {
54904
55103
  this.staticFrameCount = 0;
55104
+ this.targetFrameState.clear();
54905
55105
  this.currentInterval = profile.maxInterval;
54906
55106
  if (!active) this.lastActiveTimestamp = 0;
54907
55107
  this.timer = setTimeout(() => this.tick(), budgetBlocked ? 3e4 : this.currentInterval);
54908
55108
  return;
54909
55109
  }
54910
55110
  this.debugCount++;
54911
- try {
54912
- const buf = await cdp.captureScreenshot({ quality: profile.quality });
54913
- if (buf) {
55111
+ let capturedAny = false;
55112
+ let sentAnyFrame = false;
55113
+ let anyFirstFrameAcrossTargets = false;
55114
+ for (const targetSessionId of targetSessionIds) {
55115
+ const cdp = this.deps.getCdp(targetSessionId);
55116
+ if (!cdp) continue;
55117
+ try {
55118
+ const buf = await cdp.captureScreenshot({ quality: profile.quality });
55119
+ if (!buf) {
55120
+ if (this.debugCount <= 5) LOG.debug("Screenshot", `captureScreenshot returned null for target=${targetSessionId}`);
55121
+ continue;
55122
+ }
55123
+ capturedAny = true;
55124
+ const state = this.getTargetFrameState(targetSessionId);
54914
55125
  const hash2 = _ScreenshotController.fnvHash(buf);
54915
- const sizeMatch = buf.length === this.lastSize;
54916
- const hashMatch = hash2 === this.lastHash;
54917
- const anyNeedsFirstFrame = this.deps.hasAnyNeedingFirstFrame();
55126
+ const sizeMatch = buf.length === state.lastSize;
55127
+ const hashMatch = hash2 === state.lastHash;
55128
+ const anyNeedsFirstFrame = this.deps.hasAnyNeedingFirstFrameForTarget?.(targetSessionId) ?? this.deps.hasAnyNeedingFirstFrame();
54918
55129
  const resizeTarget = anyNeedsFirstFrame ? profile.firstFrameLongEdge : profile.maxLongEdge;
55130
+ anyFirstFrameAcrossTargets = anyFirstFrameAcrossTargets || anyNeedsFirstFrame;
54919
55131
  if (sizeMatch && hashMatch && !anyNeedsFirstFrame) {
54920
- this.staticFrameCount++;
54921
- if (this.staticFrameCount >= this.STATIC_THRESHOLD) {
55132
+ state.staticFrameCount++;
55133
+ if (state.staticFrameCount >= this.STATIC_THRESHOLD) {
54922
55134
  this.currentInterval = Math.min(this.currentInterval + 200, profile.maxInterval);
54923
55135
  }
54924
55136
  if (this.debugCount <= 5 || this.debugCount % 50 === 0) {
54925
- LOG.debug("Screenshot", `skip (unchanged, static=${this.staticFrameCount}, interval=${this.currentInterval}ms, ${isRelay ? "RELAY" : "DIRECT"})`);
54926
- }
54927
- } else {
54928
- const normalizedBuf = await this.normalizeBuffer(buf, resizeTarget, profile.quality);
54929
- this.lastSize = buf.length;
54930
- this.lastHash = hash2;
54931
- this.staticFrameCount = 0;
54932
- this.currentInterval = profile.minInterval;
54933
- const sent = this.deps.sendScreenshotBuffer(normalizedBuf);
54934
- if (this.debugCount <= 3 || anyNeedsFirstFrame) {
54935
- LOG.debug("Screenshot", `sent: ${normalizedBuf.length} bytes, delivered=${sent}, interval=${this.currentInterval}ms, ${isRelay ? "RELAY" : "DIRECT"}${anyNeedsFirstFrame ? " (first-frame)" : ""}`);
55137
+ LOG.debug("Screenshot", `skip target=${targetSessionId} (unchanged, static=${state.staticFrameCount}, interval=${this.currentInterval}ms, ${isRelay ? "RELAY" : "DIRECT"})`);
54936
55138
  }
55139
+ continue;
54937
55140
  }
54938
- } else {
54939
- if (this.debugCount <= 5) LOG.debug("Screenshot", "captureScreenshot returned null");
55141
+ const normalizedBuf = await this.normalizeBuffer(buf, resizeTarget, profile.quality);
55142
+ state.lastSize = buf.length;
55143
+ state.lastHash = hash2;
55144
+ state.staticFrameCount = 0;
55145
+ this.lastSize = buf.length;
55146
+ this.lastHash = hash2;
55147
+ this.staticFrameCount = 0;
55148
+ this.currentInterval = profile.minInterval;
55149
+ const sent = this.deps.sendScreenshotBufferForTarget ? this.deps.sendScreenshotBufferForTarget(targetSessionId, normalizedBuf) : this.deps.sendScreenshotBuffer(normalizedBuf);
55150
+ sentAnyFrame = sentAnyFrame || sent;
55151
+ if (this.debugCount <= 3 || anyNeedsFirstFrame) {
55152
+ LOG.debug("Screenshot", `sent target=${targetSessionId}: ${normalizedBuf.length} bytes, delivered=${sent}, interval=${this.currentInterval}ms, ${isRelay ? "RELAY" : "DIRECT"}${anyNeedsFirstFrame ? " (first-frame)" : ""}`);
55153
+ }
55154
+ } catch (e) {
55155
+ if (this.debugCount <= 5) LOG.warn("Screenshot", `error target=${targetSessionId}: ${e?.message}`);
54940
55156
  }
54941
- } catch (e) {
54942
- if (this.debugCount <= 5) LOG.warn("Screenshot", `error: ${e?.message}`);
55157
+ }
55158
+ if (!capturedAny) {
55159
+ this.currentInterval = profile.maxInterval;
55160
+ } else if (!sentAnyFrame && !anyFirstFrameAcrossTargets) {
55161
+ this.currentInterval = Math.min(this.currentInterval + 200, profile.maxInterval);
54943
55162
  }
54944
55163
  this.timer = setTimeout(() => this.tick(), this.currentInterval);
54945
55164
  }
55165
+ getActiveTargetSessionIds() {
55166
+ const explicitTargets = this.deps.getScreenshotTargetSessionIds?.() || [];
55167
+ const normalized = explicitTargets.map((target) => typeof target === "string" ? target.trim() : "").filter((target) => target.length > 0);
55168
+ if (normalized.length > 0) return Array.from(new Set(normalized));
55169
+ const legacyTarget = this.deps.getScreenshotTargetSessionId();
55170
+ return legacyTarget ? [legacyTarget] : [];
55171
+ }
55172
+ getTargetFrameState(targetSessionId) {
55173
+ let state = this.targetFrameState.get(targetSessionId);
55174
+ if (!state) {
55175
+ state = { lastSize: 0, lastHash: 0, staticFrameCount: 0 };
55176
+ this.targetFrameState.set(targetSessionId, state);
55177
+ }
55178
+ return state;
55179
+ }
54946
55180
  // ─── Budget ───────────────────────────────────
54947
55181
  checkBudgetReset() {
54948
55182
  const today = (/* @__PURE__ */ new Date()).toISOString().slice(0, 10);
@@ -55133,7 +55367,7 @@ function killPid2(pid) {
55133
55367
  return false;
55134
55368
  }
55135
55369
  }
55136
- function getWindowsProcessCommandLine(pid) {
55370
+ function getWindowsProcessCommandLine2(pid) {
55137
55371
  const pidFilter = `ProcessId=${pid}`;
55138
55372
  try {
55139
55373
  const psOut = (0, import_child_process12.execFileSync)("powershell.exe", [
@@ -55162,13 +55396,32 @@ function getWindowsProcessCommandLine(pid) {
55162
55396
  }
55163
55397
  return null;
55164
55398
  }
55399
+ function getProcessCommandLine2(pid) {
55400
+ if (!Number.isFinite(pid) || pid <= 0) return null;
55401
+ if (process.platform === "win32") return getWindowsProcessCommandLine2(pid);
55402
+ try {
55403
+ const text = (0, import_child_process12.execFileSync)("ps", ["-o", "command=", "-p", String(pid)], {
55404
+ encoding: "utf8",
55405
+ timeout: 3e3,
55406
+ stdio: ["ignore", "pipe", "ignore"]
55407
+ }).trim();
55408
+ return text || null;
55409
+ } catch {
55410
+ return null;
55411
+ }
55412
+ }
55413
+ function isManagedSessionHostPid2(pid) {
55414
+ const commandLine = getProcessCommandLine2(pid);
55415
+ if (!commandLine) return false;
55416
+ return /session-host-daemon/i.test(commandLine);
55417
+ }
55165
55418
  function stopManagedSessionHostProcess() {
55166
55419
  let stopped = false;
55167
55420
  const pidFile = getSessionHostPidFile();
55168
55421
  try {
55169
55422
  if (fs17.existsSync(pidFile)) {
55170
55423
  const pid = Number.parseInt(fs17.readFileSync(pidFile, "utf8").trim(), 10);
55171
- if (Number.isFinite(pid) && pid !== process.pid) {
55424
+ if (Number.isFinite(pid) && pid !== process.pid && isManagedSessionHostPid2(pid)) {
55172
55425
  stopped = killPid2(pid) || stopped;
55173
55426
  }
55174
55427
  }
@@ -55182,40 +55435,7 @@ function stopManagedSessionHostProcess() {
55182
55435
  return stopped;
55183
55436
  }
55184
55437
  function stopSessionHost() {
55185
- let stopped = stopManagedSessionHostProcess();
55186
- if (process.platform === "win32") {
55187
- try {
55188
- const raw = (0, import_child_process12.execFileSync)("tasklist", ["/FO", "CSV", "/NH", "/FI", "IMAGENAME eq node.exe"], {
55189
- encoding: "utf8",
55190
- timeout: 5e3,
55191
- stdio: ["ignore", "pipe", "ignore"],
55192
- windowsHide: true
55193
- }).trim();
55194
- for (const line of raw.split(/\r?\n/)) {
55195
- const match = line.match(/^"node\.exe","(\d+)"/i);
55196
- if (!match) continue;
55197
- const candidatePid = Number.parseInt(match[1], 10);
55198
- if (!Number.isFinite(candidatePid) || candidatePid === process.pid) continue;
55199
- const commandLine = getWindowsProcessCommandLine(candidatePid);
55200
- if (commandLine?.includes("session-host-daemon")) {
55201
- stopped = killPid2(candidatePid) || stopped;
55202
- }
55203
- }
55204
- } catch {
55205
- }
55206
- } else {
55207
- try {
55208
- const raw = (0, import_child_process12.execFileSync)("pgrep", ["-f", "session-host-daemon"], { encoding: "utf8" }).trim();
55209
- for (const line of raw.split("\n")) {
55210
- const pid = Number.parseInt(line.trim(), 10);
55211
- if (Number.isFinite(pid) && pid !== process.pid && pid !== process.ppid) {
55212
- stopped = killPid2(pid) || stopped;
55213
- }
55214
- }
55215
- } catch {
55216
- }
55217
- }
55218
- return stopped;
55438
+ return stopManagedSessionHostProcess();
55219
55439
  }
55220
55440
  async function ensureSessionHostReady2() {
55221
55441
  const quarantine = quarantineLegacyStandaloneSessions({
@@ -55992,10 +56212,65 @@ var init_version = __esm({
55992
56212
  var adhdev_daemon_exports = {};
55993
56213
  __export(adhdev_daemon_exports, {
55994
56214
  AdhdevDaemon: () => AdhdevDaemon,
56215
+ buildMandatoryUpdateInfoFromServerPayload: () => buildMandatoryUpdateInfoFromServerPayload,
56216
+ buildMandatoryUpdateRequiredPayload: () => buildMandatoryUpdateRequiredPayload,
55995
56217
  getDaemonPid: () => getDaemonPid,
55996
56218
  isDaemonRunning: () => isDaemonRunning,
55997
- stopDaemon: () => stopDaemon
55998
- });
56219
+ stopDaemon: () => stopDaemon,
56220
+ validateMandatoryUpdateTarget: () => validateMandatoryUpdateTarget,
56221
+ verifyPublishedMandatoryUpdateTarget: () => verifyPublishedMandatoryUpdateTarget
56222
+ });
56223
+ function validateMandatoryUpdateTarget(targetVersion) {
56224
+ if (typeof targetVersion !== "string") return { valid: false, error: "target version is required" };
56225
+ if (targetVersion !== targetVersion.trim()) return { valid: false, error: "target version must not contain whitespace" };
56226
+ const version2 = targetVersion;
56227
+ if (!version2) return { valid: false, error: "target version is required" };
56228
+ if (!/^\d+\.\d+\.\d+(?:-[0-9A-Za-z]+(?:\.[0-9A-Za-z]+)*)?(?:\+[0-9A-Za-z]+(?:\.[0-9A-Za-z]+)*)?$/.test(version2)) {
56229
+ return { valid: false, error: `invalid semver target: ${version2}` };
56230
+ }
56231
+ return { valid: true };
56232
+ }
56233
+ function verifyPublishedMandatoryUpdateTarget(packageName, targetVersion, deps = {}) {
56234
+ if (!/^(?:adhdev|@adhdev\/daemon-standalone)$/.test(packageName)) {
56235
+ throw new Error(`invalid mandatory update package: ${packageName}`);
56236
+ }
56237
+ const validation = validateMandatoryUpdateTarget(targetVersion);
56238
+ if (!validation.valid) throw new Error(validation.error || "invalid mandatory update target");
56239
+ const run = deps.execFileSync || import_child_process13.execFileSync;
56240
+ const npmExecutable = deps.npmExecutable || resolveCurrentGlobalInstallSurface({ packageName }).npmExecutable;
56241
+ const published = String(run(npmExecutable, ["view", `${packageName}@${targetVersion}`, "version"], {
56242
+ encoding: "utf-8",
56243
+ timeout: 1e4,
56244
+ stdio: ["pipe", "pipe", "pipe"],
56245
+ ...process.platform === "win32" ? { shell: true, windowsHide: true } : {}
56246
+ })).trim();
56247
+ if (published !== targetVersion) {
56248
+ throw new Error(`Published version mismatch: expected ${targetVersion}, got ${published || "unknown"}`);
56249
+ }
56250
+ return published;
56251
+ }
56252
+ function buildMandatoryUpdateInfoFromServerPayload(payload, fallbackVersion) {
56253
+ const targetVersion = typeof payload?.latest === "string" && payload.latest ? payload.latest : fallbackVersion;
56254
+ const validation = validateMandatoryUpdateTarget(targetVersion);
56255
+ if (!validation.valid) return { info: null, error: validation.error || targetVersion };
56256
+ return {
56257
+ info: {
56258
+ targetVersion,
56259
+ reason: typeof payload?.reason === "string" && payload.reason.trim() ? payload.reason.trim() : "major_minor_mismatch",
56260
+ minVersion: typeof payload?.minVersion === "string" && payload.minVersion.trim() ? payload.minVersion.trim() : void 0
56261
+ }
56262
+ };
56263
+ }
56264
+ function buildMandatoryUpdateRequiredPayload(pending, interactionId) {
56265
+ return {
56266
+ error: "A mandatory daemon update is pending. Finish current work and let the daemon update before starting a new session.",
56267
+ code: "DAEMON_UPDATE_REQUIRED",
56268
+ required: true,
56269
+ latest: pending.targetVersion,
56270
+ reason: pending.reason,
56271
+ interactionId
56272
+ };
56273
+ }
55999
56274
  function resolveDaemonPort(ref = {}) {
56000
56275
  return Number.isFinite(ref.port) && Number(ref.port) > 0 ? Number(ref.port) : DEFAULT_DAEMON_PORT;
56001
56276
  }
@@ -56022,7 +56297,7 @@ function removeDaemonPid(ref = {}) {
56022
56297
  function isDaemonRunning(ref = {}) {
56023
56298
  const port = resolveDaemonPort(ref);
56024
56299
  try {
56025
- const { execFileSync: execFileSync5 } = require("child_process");
56300
+ const { execFileSync: execFileSync6 } = require("child_process");
56026
56301
  const probe = `
56027
56302
  const http = require('http');
56028
56303
  const req = http.get('http://127.0.0.1:${port}/health', { timeout: 1500 }, (res) => {
@@ -56032,7 +56307,7 @@ function isDaemonRunning(ref = {}) {
56032
56307
  req.on('error', () => process.stdout.write('0'));
56033
56308
  req.on('timeout', () => { req.destroy(); process.stdout.write('0'); });
56034
56309
  `;
56035
- const result = execFileSync5(process.execPath, ["-e", probe], {
56310
+ const result = execFileSync6(process.execPath, ["-e", probe], {
56036
56311
  encoding: "utf-8",
56037
56312
  timeout: 3e3,
56038
56313
  stdio: ["ignore", "pipe", "ignore"]
@@ -56058,9 +56333,9 @@ function isDaemonRunning(ref = {}) {
56058
56333
  function isAdhdevProcess(pid) {
56059
56334
  try {
56060
56335
  if (process.platform === "win32") {
56061
- const { execFileSync: execFileSync5 } = require("child_process");
56336
+ const { execFileSync: execFileSync6 } = require("child_process");
56062
56337
  try {
56063
- const psOut = execFileSync5("powershell.exe", [
56338
+ const psOut = execFileSync6("powershell.exe", [
56064
56339
  "-NoProfile",
56065
56340
  "-NonInteractive",
56066
56341
  "-ExecutionPolicy",
@@ -56073,8 +56348,8 @@ function isAdhdevProcess(pid) {
56073
56348
  return true;
56074
56349
  }
56075
56350
  } else {
56076
- const { execFileSync: execFileSync5 } = require("child_process");
56077
- const cmdline = execFileSync5("ps", ["-o", "command=", "-p", String(pid)], {
56351
+ const { execFileSync: execFileSync6 } = require("child_process");
56352
+ const cmdline = execFileSync6("ps", ["-o", "command=", "-p", String(pid)], {
56078
56353
  encoding: "utf-8",
56079
56354
  timeout: 2e3,
56080
56355
  stdio: ["ignore", "pipe", "ignore"]
@@ -56088,7 +56363,7 @@ function isAdhdevProcess(pid) {
56088
56363
  function getDaemonHealthPid(ref = {}) {
56089
56364
  const port = resolveDaemonPort(ref);
56090
56365
  try {
56091
- const { execFileSync: execFileSync5 } = require("child_process");
56366
+ const { execFileSync: execFileSync6 } = require("child_process");
56092
56367
  const probe = `
56093
56368
  const http = require('http');
56094
56369
  const req = http.get('http://127.0.0.1:${port}/health', { timeout: 1500 }, (res) => {
@@ -56106,7 +56381,7 @@ function getDaemonHealthPid(ref = {}) {
56106
56381
  req.on('error', () => {});
56107
56382
  req.on('timeout', () => { req.destroy(); });
56108
56383
  `;
56109
- const result = execFileSync5(process.execPath, ["-e", probe], {
56384
+ const result = execFileSync6(process.execPath, ["-e", probe], {
56110
56385
  encoding: "utf-8",
56111
56386
  timeout: 3e3,
56112
56387
  stdio: ["ignore", "pipe", "ignore"]
@@ -56152,7 +56427,7 @@ function stopDaemon(ref = {}) {
56152
56427
  return false;
56153
56428
  }
56154
56429
  }
56155
- var os26, fs18, path26, import_http, import_ws3, pkgVersion, AdhdevDaemon;
56430
+ var os26, fs18, path26, import_http, import_child_process13, import_ws3, pkgVersion, AdhdevDaemon;
56156
56431
  var init_adhdev_daemon = __esm({
56157
56432
  "src/adhdev-daemon.ts"() {
56158
56433
  "use strict";
@@ -56168,12 +56443,13 @@ var init_adhdev_daemon = __esm({
56168
56443
  fs18 = __toESM(require("fs"));
56169
56444
  path26 = __toESM(require("path"));
56170
56445
  import_http = require("http");
56446
+ import_child_process13 = require("child_process");
56171
56447
  import_ws3 = require("ws");
56172
56448
  init_source2();
56173
56449
  init_version();
56174
56450
  init_src();
56175
56451
  init_runtime_defaults();
56176
- pkgVersion = resolvePackageVersion({ injectedVersion: "0.9.33" });
56452
+ pkgVersion = resolvePackageVersion({ injectedVersion: "0.9.35" });
56177
56453
  AdhdevDaemon = class _AdhdevDaemon {
56178
56454
  localHttpServer = null;
56179
56455
  localWss = null;
@@ -56266,6 +56542,10 @@ var init_adhdev_daemon = __esm({
56266
56542
  getUpgradePackageName() {
56267
56543
  return process.argv[1]?.includes("daemon-standalone") ? "@adhdev/daemon-standalone" : "adhdev";
56268
56544
  }
56545
+ getMandatoryUpdateBlockPayload(cmd, interactionId) {
56546
+ if (!this.pendingMandatoryUpdate || !_AdhdevDaemon.MANDATORY_UPDATE_BLOCKED_COMMANDS.has(cmd)) return null;
56547
+ return buildMandatoryUpdateRequiredPayload(this.pendingMandatoryUpdate, interactionId);
56548
+ }
56269
56549
  hasBlockingSessionsForMandatoryUpdate() {
56270
56550
  if (!this.components) return false;
56271
56551
  const blocking = /* @__PURE__ */ new Set(["generating", "waiting_approval", "starting"]);
@@ -56291,15 +56571,7 @@ var init_adhdev_daemon = __esm({
56291
56571
  const pkgName = this.getUpgradePackageName();
56292
56572
  this.mandatoryUpgradeInFlight = true;
56293
56573
  try {
56294
- const { execSync: execSync7 } = await import("child_process");
56295
- const published = execSync7(`npm view ${pkgName}@${pending.targetVersion} version`, {
56296
- encoding: "utf-8",
56297
- timeout: 1e4,
56298
- stdio: ["pipe", "pipe", "pipe"]
56299
- }).trim();
56300
- if (published !== pending.targetVersion) {
56301
- throw new Error(`Published version mismatch: expected ${pending.targetVersion}, got ${published || "unknown"}`);
56302
- }
56574
+ verifyPublishedMandatoryUpdateTarget(pkgName, pending.targetVersion);
56303
56575
  LOG.warn("Upgrade", `Applying mandatory daemon update (${pending.reason}) \u2192 v${pending.targetVersion}`);
56304
56576
  spawnDetachedDaemonUpgradeHelper({
56305
56577
  packageName: pkgName,
@@ -56401,7 +56673,7 @@ var init_adhdev_daemon = __esm({
56401
56673
  const now = Date.now();
56402
56674
  const cached2 = this.hotChatSnapshotCache;
56403
56675
  const sessions = cached2 && now - cached2.builtAt < _AdhdevDaemon.HOT_CHAT_SNAPSHOT_CACHE_TTL_MS ? cached2.sessions : (() => {
56404
- const built = this.buildLiveStatusSnapshot().sessions || [];
56676
+ const built = this.components.instanceManager.collectHotChatSessionStates();
56405
56677
  this.hotChatSnapshotCache = { sessions: built, builtAt: now };
56406
56678
  return built;
56407
56679
  })();
@@ -56786,14 +57058,17 @@ ${err?.stack || ""}`);
56786
57058
  isRunning: () => this.running,
56787
57059
  isScreenshotActive: () => this.p2p?.screenshotActive ?? false,
56788
57060
  getScreenshotTargetSessionId: () => this.p2p?.screenshotTargetSessionId,
57061
+ getScreenshotTargetSessionIds: () => this.p2p?.screenshotTargetSessionIds ?? [],
56789
57062
  isUsingRelay: () => this.p2p?.isUsingRelay ?? false,
56790
57063
  hasAnyNeedingFirstFrame: () => this.p2p?.hasAnyNeedingFirstFrame() ?? false,
57064
+ hasAnyNeedingFirstFrameForTarget: (targetSessionId) => this.p2p?.hasAnyNeedingFirstFrameForTarget(targetSessionId) ?? false,
56791
57065
  getCdp: (targetSessionId) => {
56792
57066
  if (targetSessionId) return this.getCdpFor(targetSessionId);
56793
57067
  LOG.warn("P2P", "Screenshot requested without targetSessionId \u2014 cannot determine target session. Skipping frame.");
56794
57068
  return null;
56795
57069
  },
56796
- sendScreenshotBuffer: (buf) => this.p2p.sendScreenshotBuffer(buf)
57070
+ sendScreenshotBuffer: (buf) => this.p2p.sendScreenshotBuffer(buf),
57071
+ sendScreenshotBufferForTarget: (targetSessionId, buf) => this.p2p.sendScreenshotBufferForTarget(targetSessionId, buf)
56797
57072
  }, planLimits ?? void 0);
56798
57073
  this.screenshotController.start();
56799
57074
  this.p2p.onScreenshotStart(() => this.screenshotController?.triggerImmediate());
@@ -56822,11 +57097,12 @@ ${err?.stack || ""}`);
56822
57097
  });
56823
57098
  this.serverConn.on("force_update_required", (msg) => {
56824
57099
  const payload = msg.payload;
56825
- this.pendingMandatoryUpdate = {
56826
- targetVersion: typeof payload.latest === "string" && payload.latest.trim() ? payload.latest.trim() : pkgVersion,
56827
- reason: typeof payload.reason === "string" && payload.reason.trim() ? payload.reason.trim() : "major_minor_mismatch",
56828
- minVersion: typeof payload.minVersion === "string" && payload.minVersion.trim() ? payload.minVersion.trim() : void 0
56829
- };
57100
+ const parsedUpdate = buildMandatoryUpdateInfoFromServerPayload(payload, pkgVersion);
57101
+ if (!parsedUpdate.info) {
57102
+ LOG.error("Upgrade", `Ignoring invalid mandatory daemon update target from server: ${parsedUpdate.error || "unknown"}`);
57103
+ return;
57104
+ }
57105
+ this.pendingMandatoryUpdate = parsedUpdate.info;
56830
57106
  LOG.warn("Upgrade", `Mandatory daemon update required (${this.pendingMandatoryUpdate.reason})`);
56831
57107
  void this.maybeApplyMandatoryUpdate("server-force-update");
56832
57108
  });
@@ -56937,15 +57213,9 @@ ${err?.stack || ""}`);
56937
57213
  const cmdStart = Date.now();
56938
57214
  const source = msg.ipcWs ? "ext" : typeof msg.source === "string" && msg.source.trim() ? msg.source : "ws";
56939
57215
  try {
56940
- if (this.pendingMandatoryUpdate && _AdhdevDaemon.MANDATORY_UPDATE_BLOCKED_COMMANDS.has(cmd)) {
56941
- this.sendResult(msg, false, {
56942
- error: "A mandatory daemon update is pending. Finish current work and let the daemon update before starting a new session.",
56943
- code: "DAEMON_UPDATE_REQUIRED",
56944
- required: true,
56945
- latest: this.pendingMandatoryUpdate.targetVersion,
56946
- reason: this.pendingMandatoryUpdate.reason,
56947
- interactionId
56948
- });
57216
+ const mandatoryUpdateBlock = this.getMandatoryUpdateBlockPayload(cmd, interactionId);
57217
+ if (mandatoryUpdateBlock) {
57218
+ this.sendResult(msg, false, mandatoryUpdateBlock);
56949
57219
  return;
56950
57220
  }
56951
57221
  if (source === "api" && !loadConfig().allowServerApiProxy) {
@@ -56990,6 +57260,10 @@ ${err?.stack || ""}`);
56990
57260
  const interactionId = String(normalizedData._interactionId);
56991
57261
  const cmdStart = Date.now();
56992
57262
  try {
57263
+ const mandatoryUpdateBlock = this.getMandatoryUpdateBlockPayload(cmdType, interactionId);
57264
+ if (mandatoryUpdateBlock) {
57265
+ return { success: false, ...mandatoryUpdateBlock };
57266
+ }
56993
57267
  switch (cmdType) {
56994
57268
  case "get_runtime_snapshot": {
56995
57269
  const sessionId = typeof normalizedData.sessionId === "string" ? normalizedData.sessionId : "";
@@ -57168,8 +57442,22 @@ ${err?.stack || ""}`);
57168
57442
  }));
57169
57443
  return;
57170
57444
  }
57445
+ const normalizedArgs = this.ensureInteractionContext(args);
57446
+ const interactionId = String(normalizedArgs._interactionId);
57447
+ const mandatoryUpdateBlock = this.getMandatoryUpdateBlockPayload(command, interactionId);
57448
+ if (mandatoryUpdateBlock) {
57449
+ ws.send(JSON.stringify({
57450
+ type: "ext:command_result",
57451
+ payload: {
57452
+ requestId,
57453
+ success: false,
57454
+ ...mandatoryUpdateBlock
57455
+ }
57456
+ }));
57457
+ return;
57458
+ }
57171
57459
  try {
57172
- const result = await this.components.router.execute(command, args, "ipc");
57460
+ const result = await this.components.router.execute(command, normalizedArgs, "ipc");
57173
57461
  ws.send(JSON.stringify({
57174
57462
  type: "ext:command_result",
57175
57463
  payload: {
@@ -85994,12 +86282,12 @@ function splitStringBySpace(str) {
85994
86282
  }
85995
86283
  return pieces;
85996
86284
  }
85997
- var import_chardet, import_child_process13, import_fs7, import_node_path2, import_node_os4, import_node_crypto, import_iconv_lite, ExternalEditor;
86285
+ var import_chardet, import_child_process14, import_fs7, import_node_path2, import_node_os4, import_node_crypto, import_iconv_lite, ExternalEditor;
85998
86286
  var init_esm2 = __esm({
85999
86287
  "../../node_modules/@inquirer/external-editor/dist/esm/index.js"() {
86000
86288
  "use strict";
86001
86289
  import_chardet = __toESM(require_lib2(), 1);
86002
- import_child_process13 = require("child_process");
86290
+ import_child_process14 = require("child_process");
86003
86291
  import_fs7 = require("fs");
86004
86292
  import_node_path2 = __toESM(require("path"), 1);
86005
86293
  import_node_os4 = __toESM(require("os"), 1);
@@ -86106,7 +86394,7 @@ var init_esm2 = __esm({
86106
86394
  }
86107
86395
  launchEditor() {
86108
86396
  try {
86109
- const editorProcess = (0, import_child_process13.spawnSync)(this.editor.bin, this.editor.args.concat([this.tempFile]), { stdio: "inherit" });
86397
+ const editorProcess = (0, import_child_process14.spawnSync)(this.editor.bin, this.editor.args.concat([this.tempFile]), { stdio: "inherit" });
86110
86398
  this.lastExitStatus = editorProcess.status ?? 0;
86111
86399
  } catch (launchError) {
86112
86400
  throw new LaunchEditorError(launchError);
@@ -86114,7 +86402,7 @@ var init_esm2 = __esm({
86114
86402
  }
86115
86403
  launchEditorAsync(callback) {
86116
86404
  try {
86117
- const editorProcess = (0, import_child_process13.spawn)(this.editor.bin, this.editor.args.concat([this.tempFile]), { stdio: "inherit" });
86405
+ const editorProcess = (0, import_child_process14.spawn)(this.editor.bin, this.editor.args.concat([this.tempFile]), { stdio: "inherit" });
86118
86406
  editorProcess.on("exit", (code) => {
86119
86407
  this.lastExitStatus = code;
86120
86408
  setImmediate(callback);
@@ -88392,9 +88680,9 @@ async function runWizard(options = {}) {
88392
88680
  }
88393
88681
  async function checkForUpdate() {
88394
88682
  try {
88395
- const { execFileSync: execFileSync5 } = await import("child_process");
88683
+ const { execFileSync: execFileSync6 } = await import("child_process");
88396
88684
  const currentVersion = resolvePackageVersion();
88397
- const latestVersion = readLatestPublishedCliVersion(execFileSync5);
88685
+ const latestVersion = readLatestPublishedCliVersion(execFileSync6);
88398
88686
  if (!latestVersion) return;
88399
88687
  if (!currentVersion || !latestVersion || currentVersion === latestVersion) return;
88400
88688
  console.log(source_default2.yellow(` Update available: ${currentVersion} \u2192 ${latestVersion}`));
@@ -88411,7 +88699,7 @@ async function checkForUpdate() {
88411
88699
  const spinner = (await Promise.resolve().then(() => (init_ora(), ora_exports))).default("Updating adhdev CLI...").start();
88412
88700
  try {
88413
88701
  const installCommand = buildPinnedGlobalInstallCommand({ packageName: "adhdev", targetVersion: "latest" });
88414
- execFileSync5(installCommand.command, installCommand.args, {
88702
+ execFileSync6(installCommand.command, installCommand.args, {
88415
88703
  encoding: "utf-8",
88416
88704
  timeout: 6e4,
88417
88705
  stdio: ["pipe", "pipe", "pipe"]