adhdev 0.8.34 → 0.8.36

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/cli/index.js CHANGED
@@ -67,6 +67,7 @@ function normalizeConfig(raw) {
67
67
  const parsed = isPlainObject(raw) ? raw : {};
68
68
  return {
69
69
  serverUrl: typeof parsed.serverUrl === "string" && parsed.serverUrl.trim() ? parsed.serverUrl : DEFAULT_CONFIG.serverUrl,
70
+ allowServerApiProxy: asBoolean(parsed.allowServerApiProxy, DEFAULT_CONFIG.allowServerApiProxy ?? false),
70
71
  selectedIde: asNullableString(parsed.selectedIde),
71
72
  configuredIdes: asStringArray(parsed.configuredIdes),
72
73
  installedExtensions: asStringArray(parsed.installedExtensions),
@@ -215,6 +216,7 @@ var init_config = __esm({
215
216
  import_crypto = require("crypto");
216
217
  DEFAULT_CONFIG = {
217
218
  serverUrl: "https://api.adhf.dev",
219
+ allowServerApiProxy: false,
218
220
  selectedIde: null,
219
221
  configuredIdes: [],
220
222
  installedExtensions: [],
@@ -4500,19 +4502,20 @@ function normalizeMessageTime(message) {
4500
4502
  }
4501
4503
  return msg;
4502
4504
  }
4503
- function trimMessagesForStatus(messages) {
4505
+ function trimMessagesForStatus(messages, options) {
4506
+ if (!options.includeMessages || options.messageLimit <= 0 || options.totalBytesLimit <= 0) return [];
4504
4507
  if (!Array.isArray(messages) || messages.length === 0) return [];
4505
- const recent = messages.slice(-STATUS_ACTIVE_CHAT_MESSAGE_LIMIT);
4508
+ const recent = messages.slice(-options.messageLimit);
4506
4509
  const kept = [];
4507
4510
  let totalBytes = 0;
4508
4511
  for (let i = recent.length - 1; i >= 0; i -= 1) {
4509
- let normalized = normalizeMessageTime(trimMessageForStatus(recent[i], STATUS_ACTIVE_CHAT_STRING_LIMIT));
4512
+ let normalized = normalizeMessageTime(trimMessageForStatus(recent[i], options.stringLimit));
4510
4513
  let size = estimateBytes(normalized);
4511
- if (size > STATUS_ACTIVE_CHAT_TOTAL_BYTES_LIMIT) {
4512
- normalized = normalizeMessageTime(trimMessageForStatus(recent[i], STATUS_ACTIVE_CHAT_FALLBACK_STRING_LIMIT));
4514
+ if (size > options.totalBytesLimit) {
4515
+ normalized = normalizeMessageTime(trimMessageForStatus(recent[i], options.fallbackStringLimit));
4513
4516
  size = estimateBytes(normalized);
4514
4517
  }
4515
- if (kept.length > 0 && totalBytes + size > STATUS_ACTIVE_CHAT_TOTAL_BYTES_LIMIT) {
4518
+ if (kept.length > 0 && totalBytes + size > options.totalBytesLimit) {
4516
4519
  continue;
4517
4520
  }
4518
4521
  kept.push(normalized);
@@ -4542,22 +4545,26 @@ function isManagedStatusWorking(status) {
4542
4545
  function isManagedStatusWaiting(status, opts) {
4543
4546
  return normalizeManagedStatus(status, opts) === "waiting_approval";
4544
4547
  }
4545
- function normalizeActiveChatData(activeChat) {
4548
+ function normalizeActiveChatData(activeChat, options = FULL_STATUS_ACTIVE_CHAT_OPTIONS) {
4546
4549
  if (!activeChat) return activeChat;
4550
+ const resolvedOptions = {
4551
+ ...FULL_STATUS_ACTIVE_CHAT_OPTIONS,
4552
+ ...options
4553
+ };
4547
4554
  return {
4548
4555
  ...activeChat,
4549
4556
  status: normalizeManagedStatus(activeChat.status, { activeModal: activeChat.activeModal }),
4550
- messages: trimMessagesForStatus(activeChat.messages),
4551
- activeModal: activeChat.activeModal ? {
4557
+ messages: trimMessagesForStatus(activeChat.messages, resolvedOptions),
4558
+ activeModal: resolvedOptions.includeActiveModal && activeChat.activeModal ? {
4552
4559
  message: truncateString(activeChat.activeModal.message || "", STATUS_MODAL_MESSAGE_LIMIT),
4553
4560
  buttons: (activeChat.activeModal.buttons || []).map(
4554
4561
  (button) => truncateString(String(button || ""), STATUS_MODAL_BUTTON_LIMIT)
4555
4562
  )
4556
- } : activeChat.activeModal,
4557
- inputContent: activeChat.inputContent ? truncateString(activeChat.inputContent, STATUS_INPUT_CONTENT_LIMIT) : activeChat.inputContent
4563
+ } : null,
4564
+ inputContent: resolvedOptions.includeInputContent && activeChat.inputContent ? truncateString(activeChat.inputContent, 2 * 1024) : void 0
4558
4565
  };
4559
4566
  }
4560
- var WORKING_STATUSES, STATUS_ACTIVE_CHAT_MESSAGE_LIMIT, STATUS_ACTIVE_CHAT_TOTAL_BYTES_LIMIT, STATUS_ACTIVE_CHAT_STRING_LIMIT, STATUS_ACTIVE_CHAT_FALLBACK_STRING_LIMIT, STATUS_INPUT_CONTENT_LIMIT, STATUS_MODAL_MESSAGE_LIMIT, STATUS_MODAL_BUTTON_LIMIT;
4567
+ var WORKING_STATUSES, FULL_STATUS_ACTIVE_CHAT_OPTIONS, LIVE_STATUS_ACTIVE_CHAT_OPTIONS, STATUS_MODAL_MESSAGE_LIMIT, STATUS_MODAL_BUTTON_LIMIT;
4561
4568
  var init_normalize = __esm({
4562
4569
  "../../oss/packages/daemon-core/src/status/normalize.ts"() {
4563
4570
  "use strict";
@@ -4569,17 +4576,43 @@ var init_normalize = __esm({
4569
4576
  "thinking",
4570
4577
  "active"
4571
4578
  ]);
4572
- STATUS_ACTIVE_CHAT_MESSAGE_LIMIT = 60;
4573
- STATUS_ACTIVE_CHAT_TOTAL_BYTES_LIMIT = 96 * 1024;
4574
- STATUS_ACTIVE_CHAT_STRING_LIMIT = 4 * 1024;
4575
- STATUS_ACTIVE_CHAT_FALLBACK_STRING_LIMIT = 1024;
4576
- STATUS_INPUT_CONTENT_LIMIT = 2 * 1024;
4579
+ FULL_STATUS_ACTIVE_CHAT_OPTIONS = {
4580
+ includeMessages: true,
4581
+ includeInputContent: true,
4582
+ includeActiveModal: true,
4583
+ messageLimit: 60,
4584
+ totalBytesLimit: 96 * 1024,
4585
+ stringLimit: 4 * 1024,
4586
+ fallbackStringLimit: 1024
4587
+ };
4588
+ LIVE_STATUS_ACTIVE_CHAT_OPTIONS = {
4589
+ includeMessages: false,
4590
+ includeInputContent: false,
4591
+ includeActiveModal: false,
4592
+ messageLimit: 0,
4593
+ totalBytesLimit: 0,
4594
+ stringLimit: 512,
4595
+ fallbackStringLimit: 256
4596
+ };
4577
4597
  STATUS_MODAL_MESSAGE_LIMIT = 2 * 1024;
4578
4598
  STATUS_MODAL_BUTTON_LIMIT = 120;
4579
4599
  }
4580
4600
  });
4581
4601
 
4582
4602
  // ../../oss/packages/daemon-core/src/status/builders.ts
4603
+ function getActiveChatOptions(profile) {
4604
+ if (profile === "full") return {};
4605
+ return LIVE_STATUS_ACTIVE_CHAT_OPTIONS;
4606
+ }
4607
+ function shouldIncludeSessionControls(profile) {
4608
+ return profile !== "live";
4609
+ }
4610
+ function shouldIncludeSessionMetadata(profile) {
4611
+ return profile !== "live";
4612
+ }
4613
+ function shouldIncludeRuntimeMetadata(profile) {
4614
+ return profile !== "live";
4615
+ }
4583
4616
  function findCdpManager(cdpManagers, key) {
4584
4617
  const exact = cdpManagers.get(key);
4585
4618
  if (exact) return exact;
@@ -4645,74 +4678,88 @@ function buildFallbackControls(providerControls, serverModel, serverMode, acpCon
4645
4678
  }
4646
4679
  return controls;
4647
4680
  }
4648
- function buildIdeWorkspaceSession(state, cdpManagers) {
4649
- const activeChat = normalizeActiveChatData(state.activeChat);
4681
+ function buildIdeWorkspaceSession(state, cdpManagers, options) {
4682
+ const profile = options.profile || "full";
4683
+ const activeChat = normalizeActiveChatData(state.activeChat, getActiveChatOptions(profile));
4684
+ const includeSessionMetadata = shouldIncludeSessionMetadata(profile);
4685
+ const includeSessionControls = shouldIncludeSessionControls(profile);
4650
4686
  const title = activeChat?.title || state.name;
4651
4687
  return {
4652
4688
  id: state.instanceId || state.type,
4653
4689
  parentId: null,
4654
4690
  providerType: state.type,
4655
- providerName: state.name,
4691
+ ...includeSessionMetadata && { providerName: state.name },
4656
4692
  kind: "workspace",
4657
4693
  transport: "cdp-page",
4658
4694
  status: normalizeManagedStatus(activeChat?.status || state.status, {
4659
4695
  activeModal: activeChat?.activeModal || null
4660
4696
  }),
4661
4697
  title,
4662
- workspace: state.workspace || null,
4698
+ ...includeSessionMetadata && { workspace: state.workspace || null },
4663
4699
  activeChat,
4664
- capabilities: IDE_SESSION_CAPABILITIES,
4700
+ ...includeSessionMetadata && { capabilities: IDE_SESSION_CAPABILITIES },
4665
4701
  cdpConnected: state.cdpConnected ?? isCdpConnected(cdpManagers, state.type),
4666
4702
  currentModel: state.currentModel,
4667
4703
  currentPlan: state.currentPlan,
4668
4704
  currentAutoApprove: state.currentAutoApprove,
4669
- controlValues: state.controlValues,
4670
- providerControls: buildFallbackControls(
4671
- state.providerControls,
4672
- state.currentModel,
4673
- state.currentPlan
4674
- ),
4705
+ ...includeSessionControls && {
4706
+ controlValues: state.controlValues,
4707
+ providerControls: buildFallbackControls(
4708
+ state.providerControls,
4709
+ state.currentModel,
4710
+ state.currentPlan
4711
+ )
4712
+ },
4675
4713
  errorMessage: state.errorMessage,
4676
4714
  errorReason: state.errorReason,
4677
4715
  lastUpdated: state.lastUpdated
4678
4716
  };
4679
4717
  }
4680
- function buildExtensionAgentSession(parent, ext) {
4681
- const activeChat = normalizeActiveChatData(ext.activeChat);
4718
+ function buildExtensionAgentSession(parent, ext, options) {
4719
+ const profile = options.profile || "full";
4720
+ const activeChat = normalizeActiveChatData(ext.activeChat, getActiveChatOptions(profile));
4721
+ const includeSessionMetadata = shouldIncludeSessionMetadata(profile);
4722
+ const includeSessionControls = shouldIncludeSessionControls(profile);
4682
4723
  return {
4683
4724
  id: ext.instanceId || `${parent.instanceId}:${ext.type}`,
4684
4725
  parentId: parent.instanceId || parent.type,
4685
4726
  providerType: ext.type,
4686
- providerName: ext.name,
4727
+ ...includeSessionMetadata && { providerName: ext.name },
4687
4728
  kind: "agent",
4688
4729
  transport: "cdp-webview",
4689
4730
  status: normalizeManagedStatus(activeChat?.status || ext.status, {
4690
4731
  activeModal: activeChat?.activeModal || null
4691
4732
  }),
4692
4733
  title: activeChat?.title || ext.name,
4693
- workspace: parent.workspace || null,
4734
+ ...includeSessionMetadata && { workspace: parent.workspace || null },
4694
4735
  activeChat,
4695
- capabilities: EXTENSION_SESSION_CAPABILITIES,
4736
+ ...includeSessionMetadata && { capabilities: EXTENSION_SESSION_CAPABILITIES },
4696
4737
  currentModel: ext.currentModel,
4697
4738
  currentPlan: ext.currentPlan,
4698
- controlValues: ext.controlValues,
4699
- providerControls: buildFallbackControls(
4700
- ext.providerControls,
4701
- ext.currentModel,
4702
- ext.currentPlan
4703
- ),
4739
+ ...includeSessionControls && {
4740
+ controlValues: ext.controlValues,
4741
+ providerControls: buildFallbackControls(
4742
+ ext.providerControls,
4743
+ ext.currentModel,
4744
+ ext.currentPlan
4745
+ )
4746
+ },
4704
4747
  errorMessage: ext.errorMessage,
4705
4748
  errorReason: ext.errorReason,
4706
4749
  lastUpdated: ext.lastUpdated
4707
4750
  };
4708
4751
  }
4709
- function buildCliSession(state) {
4710
- const activeChat = normalizeActiveChatData(state.activeChat);
4752
+ function buildCliSession(state, options) {
4753
+ const profile = options.profile || "full";
4754
+ const activeChat = normalizeActiveChatData(state.activeChat, getActiveChatOptions(profile));
4755
+ const includeSessionMetadata = shouldIncludeSessionMetadata(profile);
4756
+ const includeRuntimeMetadata = shouldIncludeRuntimeMetadata(profile);
4757
+ const includeSessionControls = shouldIncludeSessionControls(profile);
4711
4758
  return {
4712
4759
  id: state.instanceId,
4713
4760
  parentId: null,
4714
4761
  providerType: state.type,
4715
- providerName: state.name,
4762
+ ...includeSessionMetadata && { providerName: state.name },
4716
4763
  providerSessionId: state.providerSessionId,
4717
4764
  kind: "agent",
4718
4765
  transport: "pty",
@@ -4720,74 +4767,85 @@ function buildCliSession(state) {
4720
4767
  activeModal: activeChat?.activeModal || null
4721
4768
  }),
4722
4769
  title: activeChat?.title || state.name,
4723
- workspace: state.workspace || null,
4724
- runtimeKey: state.runtime?.runtimeKey,
4725
- runtimeDisplayName: state.runtime?.displayName,
4726
- runtimeWorkspaceLabel: state.runtime?.workspaceLabel,
4727
- runtimeWriteOwner: state.runtime?.writeOwner || null,
4728
- runtimeAttachedClients: state.runtime?.attachedClients || [],
4770
+ ...includeSessionMetadata && { workspace: state.workspace || null },
4771
+ ...includeRuntimeMetadata && {
4772
+ runtimeKey: state.runtime?.runtimeKey,
4773
+ runtimeDisplayName: state.runtime?.displayName,
4774
+ runtimeWorkspaceLabel: state.runtime?.workspaceLabel,
4775
+ runtimeWriteOwner: state.runtime?.writeOwner || null,
4776
+ runtimeAttachedClients: state.runtime?.attachedClients || []
4777
+ },
4729
4778
  mode: state.mode,
4730
4779
  resume: state.resume,
4731
4780
  activeChat,
4732
- capabilities: state.mode === "terminal" ? PTY_SESSION_CAPABILITIES : CLI_CHAT_SESSION_CAPABILITIES,
4733
- controlValues: state.controlValues,
4734
- providerControls: buildFallbackControls(
4735
- state.providerControls
4736
- ),
4781
+ ...includeSessionMetadata && {
4782
+ capabilities: state.mode === "terminal" ? PTY_SESSION_CAPABILITIES : CLI_CHAT_SESSION_CAPABILITIES
4783
+ },
4784
+ ...includeSessionControls && {
4785
+ controlValues: state.controlValues,
4786
+ providerControls: buildFallbackControls(
4787
+ state.providerControls
4788
+ )
4789
+ },
4737
4790
  errorMessage: state.errorMessage,
4738
4791
  errorReason: state.errorReason,
4739
4792
  lastUpdated: state.lastUpdated
4740
4793
  };
4741
4794
  }
4742
- function buildAcpSession(state) {
4743
- const activeChat = normalizeActiveChatData(state.activeChat);
4795
+ function buildAcpSession(state, options) {
4796
+ const profile = options.profile || "full";
4797
+ const activeChat = normalizeActiveChatData(state.activeChat, getActiveChatOptions(profile));
4798
+ const includeSessionMetadata = shouldIncludeSessionMetadata(profile);
4799
+ const includeSessionControls = shouldIncludeSessionControls(profile);
4744
4800
  return {
4745
4801
  id: state.instanceId,
4746
4802
  parentId: null,
4747
4803
  providerType: state.type,
4748
- providerName: state.name,
4804
+ ...includeSessionMetadata && { providerName: state.name },
4749
4805
  kind: "agent",
4750
4806
  transport: "acp",
4751
4807
  status: normalizeManagedStatus(activeChat?.status || state.status, {
4752
4808
  activeModal: activeChat?.activeModal || null
4753
4809
  }),
4754
4810
  title: activeChat?.title || state.name,
4755
- workspace: state.workspace || null,
4811
+ ...includeSessionMetadata && { workspace: state.workspace || null },
4756
4812
  activeChat,
4757
- capabilities: ACP_SESSION_CAPABILITIES,
4813
+ ...includeSessionMetadata && { capabilities: ACP_SESSION_CAPABILITIES },
4758
4814
  currentModel: state.currentModel,
4759
4815
  currentPlan: state.currentPlan,
4760
- acpConfigOptions: state.acpConfigOptions,
4761
- acpModes: state.acpModes,
4762
- controlValues: state.controlValues,
4763
- providerControls: buildFallbackControls(
4764
- state.providerControls,
4765
- state.currentModel,
4766
- state.currentPlan,
4767
- state.acpConfigOptions,
4768
- state.acpModes
4769
- ),
4816
+ ...includeSessionControls && {
4817
+ acpConfigOptions: state.acpConfigOptions,
4818
+ acpModes: state.acpModes,
4819
+ controlValues: state.controlValues,
4820
+ providerControls: buildFallbackControls(
4821
+ state.providerControls,
4822
+ state.currentModel,
4823
+ state.currentPlan,
4824
+ state.acpConfigOptions,
4825
+ state.acpModes
4826
+ )
4827
+ },
4770
4828
  errorMessage: state.errorMessage,
4771
4829
  errorReason: state.errorReason,
4772
4830
  lastUpdated: state.lastUpdated
4773
4831
  };
4774
4832
  }
4775
- function buildSessionEntries(allStates, cdpManagers) {
4833
+ function buildSessionEntries(allStates, cdpManagers, options = {}) {
4776
4834
  const sessions = [];
4777
4835
  const ideStates = allStates.filter((s) => s.category === "ide");
4778
4836
  const cliStates = allStates.filter((s) => s.category === "cli");
4779
4837
  const acpStates = allStates.filter((s) => s.category === "acp");
4780
4838
  for (const state of ideStates) {
4781
- sessions.push(buildIdeWorkspaceSession(state, cdpManagers));
4839
+ sessions.push(buildIdeWorkspaceSession(state, cdpManagers, options));
4782
4840
  for (const ext of state.extensions) {
4783
- sessions.push(buildExtensionAgentSession(state, ext));
4841
+ sessions.push(buildExtensionAgentSession(state, ext, options));
4784
4842
  }
4785
4843
  }
4786
4844
  for (const state of cliStates) {
4787
- sessions.push(buildCliSession(state));
4845
+ sessions.push(buildCliSession(state, options));
4788
4846
  }
4789
4847
  for (const state of acpStates) {
4790
- sessions.push(buildAcpSession(state));
4848
+ sessions.push(buildAcpSession(state, options));
4791
4849
  }
4792
4850
  const extensionParentIds = new Set(
4793
4851
  sessions.filter((session) => session.transport === "cdp-webview" && !!session.parentId).map((session) => session.parentId)
@@ -4899,7 +4957,31 @@ var init_reconcile = __esm({
4899
4957
  }
4900
4958
  });
4901
4959
 
4960
+ // ../../oss/packages/daemon-core/src/providers/contracts.ts
4961
+ function flattenContent(content) {
4962
+ if (typeof content === "string") return content;
4963
+ return content.filter((b) => b.type === "text").map((b) => b.text).join("\n");
4964
+ }
4965
+ var init_contracts = __esm({
4966
+ "../../oss/packages/daemon-core/src/providers/contracts.ts"() {
4967
+ "use strict";
4968
+ }
4969
+ });
4970
+
4902
4971
  // ../../oss/packages/daemon-core/src/commands/chat-commands.ts
4972
+ function hashSignatureParts(parts) {
4973
+ let hash2 = 2166136261;
4974
+ for (const part of parts) {
4975
+ const text = String(part || "");
4976
+ for (let i = 0; i < text.length; i += 1) {
4977
+ hash2 ^= text.charCodeAt(i);
4978
+ hash2 = Math.imul(hash2, 16777619) >>> 0;
4979
+ }
4980
+ hash2 ^= 255;
4981
+ hash2 = Math.imul(hash2, 16777619) >>> 0;
4982
+ }
4983
+ return hash2.toString(16).padStart(8, "0");
4984
+ }
4903
4985
  function getCurrentProviderType(h, fallback = "") {
4904
4986
  return h.currentSession?.providerType || h.currentProviderType || fallback;
4905
4987
  }
@@ -4973,6 +5055,134 @@ function parseMaybeJson(value) {
4973
5055
  return value;
4974
5056
  }
4975
5057
  }
5058
+ function getChatMessageSignature(message) {
5059
+ if (!message) return "";
5060
+ let content = "";
5061
+ try {
5062
+ content = JSON.stringify(message.content ?? "");
5063
+ } catch {
5064
+ content = String(message.content ?? "");
5065
+ }
5066
+ return hashSignatureParts([
5067
+ String(message.id || ""),
5068
+ String(message.index ?? ""),
5069
+ String(message.role || ""),
5070
+ String(message.receivedAt ?? message.timestamp ?? ""),
5071
+ content
5072
+ ]);
5073
+ }
5074
+ function normalizeReadChatCursor(args) {
5075
+ const knownMessageCount = Math.max(0, Number(args?.knownMessageCount || 0));
5076
+ const lastMessageSignature = typeof args?.lastMessageSignature === "string" ? args.lastMessageSignature : "";
5077
+ const tailLimit = Math.max(0, Number(args?.tailLimit || 0));
5078
+ return { knownMessageCount, lastMessageSignature, tailLimit };
5079
+ }
5080
+ function normalizeReadChatMessages(payload) {
5081
+ const messages = Array.isArray(payload.messages) ? payload.messages : [];
5082
+ return messages;
5083
+ }
5084
+ function deriveHistoryDedupKey(message) {
5085
+ const unitKey = typeof message._unitKey === "string" ? message._unitKey.trim() : "";
5086
+ if (unitKey) return `read_chat:${unitKey}`;
5087
+ const turnKey = typeof message._turnKey === "string" ? message._turnKey.trim() : "";
5088
+ if (!turnKey) return void 0;
5089
+ let content = "";
5090
+ try {
5091
+ content = JSON.stringify(message.content ?? "");
5092
+ } catch {
5093
+ content = String(message.content ?? "");
5094
+ }
5095
+ return `read_chat:${turnKey}:${String(message.role || "").toLowerCase()}:${content}`;
5096
+ }
5097
+ function toHistoryPersistedMessages(messages) {
5098
+ return messages.map((message) => ({
5099
+ role: message.role,
5100
+ content: flattenContent(message.content),
5101
+ receivedAt: typeof message.receivedAt === "number" ? message.receivedAt : void 0,
5102
+ kind: typeof message.kind === "string" ? message.kind : void 0,
5103
+ senderName: typeof message.senderName === "string" ? message.senderName : void 0,
5104
+ historyDedupKey: deriveHistoryDedupKey(message)
5105
+ }));
5106
+ }
5107
+ function computeReadChatSync(messages, cursor) {
5108
+ const totalMessages = messages.length;
5109
+ const lastMessageSignature = getChatMessageSignature(messages[totalMessages - 1]);
5110
+ const { knownMessageCount, lastMessageSignature: knownSignature } = cursor;
5111
+ if (!knownMessageCount || !knownSignature) {
5112
+ return {
5113
+ syncMode: "full",
5114
+ replaceFrom: 0,
5115
+ messages,
5116
+ totalMessages,
5117
+ lastMessageSignature
5118
+ };
5119
+ }
5120
+ if (knownMessageCount > totalMessages) {
5121
+ return {
5122
+ syncMode: "full",
5123
+ replaceFrom: 0,
5124
+ messages,
5125
+ totalMessages,
5126
+ lastMessageSignature
5127
+ };
5128
+ }
5129
+ if (knownMessageCount === totalMessages && knownSignature === lastMessageSignature) {
5130
+ return {
5131
+ syncMode: "noop",
5132
+ replaceFrom: totalMessages,
5133
+ messages: [],
5134
+ totalMessages,
5135
+ lastMessageSignature
5136
+ };
5137
+ }
5138
+ if (knownMessageCount < totalMessages) {
5139
+ const anchorSignature = getChatMessageSignature(messages[knownMessageCount - 1]);
5140
+ if (anchorSignature === knownSignature) {
5141
+ return {
5142
+ syncMode: "append",
5143
+ replaceFrom: knownMessageCount,
5144
+ messages: messages.slice(knownMessageCount),
5145
+ totalMessages,
5146
+ lastMessageSignature
5147
+ };
5148
+ }
5149
+ }
5150
+ const replaceFrom = Math.max(0, Math.min(knownMessageCount - 1, totalMessages));
5151
+ return {
5152
+ syncMode: replaceFrom === 0 ? "full" : "replace_tail",
5153
+ replaceFrom,
5154
+ messages: replaceFrom === 0 ? messages : messages.slice(replaceFrom),
5155
+ totalMessages,
5156
+ lastMessageSignature
5157
+ };
5158
+ }
5159
+ function buildReadChatCommandResult(payload, args) {
5160
+ const messages = normalizeReadChatMessages(payload);
5161
+ const cursor = normalizeReadChatCursor(args);
5162
+ if (!cursor.knownMessageCount && !cursor.lastMessageSignature && cursor.tailLimit > 0 && messages.length > cursor.tailLimit) {
5163
+ const tailMessages = messages.slice(-cursor.tailLimit);
5164
+ const lastMessageSignature = getChatMessageSignature(tailMessages[tailMessages.length - 1]);
5165
+ return {
5166
+ success: true,
5167
+ ...payload,
5168
+ messages: tailMessages,
5169
+ syncMode: "full",
5170
+ replaceFrom: 0,
5171
+ totalMessages: messages.length,
5172
+ lastMessageSignature
5173
+ };
5174
+ }
5175
+ const sync = computeReadChatSync(messages, cursor);
5176
+ return {
5177
+ success: true,
5178
+ ...payload,
5179
+ messages: sync.messages,
5180
+ syncMode: sync.syncMode,
5181
+ replaceFrom: sync.replaceFrom,
5182
+ totalMessages: sync.totalMessages,
5183
+ lastMessageSignature: sync.lastMessageSignature
5184
+ };
5185
+ }
4976
5186
  function didProviderConfirmSend(result) {
4977
5187
  const parsed = parseMaybeJson(result);
4978
5188
  if (parsed === true) return true;
@@ -5046,12 +5256,11 @@ async function handleReadChat(h, args) {
5046
5256
  _log(`${transport} adapter: ${adapter.cliType}`);
5047
5257
  const status = adapter.getStatus();
5048
5258
  if (status) {
5049
- return {
5050
- success: true,
5259
+ return buildReadChatCommandResult({
5051
5260
  messages: status.messages || [],
5052
5261
  status: status.status,
5053
5262
  activeModal: status.activeModal
5054
- };
5263
+ }, args);
5055
5264
  }
5056
5265
  }
5057
5266
  return { success: false, error: `${transport} adapter not found` };
@@ -5071,12 +5280,12 @@ async function handleReadChat(h, args) {
5071
5280
  _log(`Extension OK: ${parsed.messages?.length || 0} msgs`);
5072
5281
  h.historyWriter.appendNewMessages(
5073
5282
  provider?.type || "unknown_extension",
5074
- parsed.messages || [],
5283
+ toHistoryPersistedMessages(normalizeReadChatMessages(parsed)),
5075
5284
  parsed.title,
5076
5285
  args?.targetSessionId,
5077
5286
  historySessionId
5078
5287
  );
5079
- return { success: true, ...parsed };
5288
+ return buildReadChatCommandResult(parsed, args);
5080
5289
  }
5081
5290
  }
5082
5291
  } catch (e) {
@@ -5088,21 +5297,25 @@ async function handleReadChat(h, args) {
5088
5297
  if (cdp2 && parentSessionId) {
5089
5298
  const stream = await h.agentStream.collectActiveSession(cdp2, parentSessionId);
5090
5299
  if (stream?.agentType !== provider?.type) {
5091
- return { success: true, messages: [], status: "idle" };
5300
+ return buildReadChatCommandResult({ messages: [], status: "idle" }, args);
5092
5301
  }
5093
5302
  if (stream) {
5094
5303
  h.historyWriter.appendNewMessages(
5095
5304
  stream.agentType,
5096
- stream.messages || [],
5305
+ toHistoryPersistedMessages(stream.messages || []),
5097
5306
  void 0,
5098
5307
  args?.targetSessionId,
5099
5308
  historySessionId
5100
5309
  );
5101
- return { success: true, messages: stream.messages || [], status: stream.status, agentType: stream.agentType };
5310
+ return buildReadChatCommandResult({
5311
+ messages: stream.messages || [],
5312
+ status: stream.status,
5313
+ agentType: stream.agentType
5314
+ }, args);
5102
5315
  }
5103
5316
  }
5104
5317
  }
5105
- return { success: true, messages: [], status: "idle" };
5318
+ return buildReadChatCommandResult({ messages: [], status: "idle" }, args);
5106
5319
  }
5107
5320
  const cdp = h.getCdp();
5108
5321
  if (!cdp?.isConnected) return { success: false, error: "CDP not connected" };
@@ -5124,18 +5337,18 @@ async function handleReadChat(h, args) {
5124
5337
  _log(`Webview OK: ${parsed.messages?.length || 0} msgs`);
5125
5338
  h.historyWriter.appendNewMessages(
5126
5339
  provider?.type || getCurrentProviderType(h, "unknown_webview"),
5127
- parsed.messages || [],
5340
+ toHistoryPersistedMessages(normalizeReadChatMessages(parsed)),
5128
5341
  parsed.title,
5129
5342
  args?.targetSessionId,
5130
5343
  historySessionId
5131
5344
  );
5132
- return { success: true, ...parsed };
5345
+ return buildReadChatCommandResult(parsed, args);
5133
5346
  }
5134
5347
  }
5135
5348
  } catch (e) {
5136
5349
  _log(`Webview readChat error: ${e.message}`);
5137
5350
  }
5138
- return { success: true, messages: [], status: "idle" };
5351
+ return buildReadChatCommandResult({ messages: [], status: "idle" }, args);
5139
5352
  }
5140
5353
  const script = h.getProviderScript("readChat") || h.getProviderScript("read_chat");
5141
5354
  if (script) {
@@ -5152,18 +5365,18 @@ async function handleReadChat(h, args) {
5152
5365
  _log(`OK: ${parsed.messages?.length} msgs`);
5153
5366
  h.historyWriter.appendNewMessages(
5154
5367
  provider?.type || getCurrentProviderType(h, "unknown_ide"),
5155
- parsed.messages || [],
5368
+ toHistoryPersistedMessages(normalizeReadChatMessages(parsed)),
5156
5369
  parsed.title,
5157
5370
  args?.targetSessionId,
5158
5371
  historySessionId
5159
5372
  );
5160
- return { success: true, ...parsed };
5373
+ return buildReadChatCommandResult(parsed, args);
5161
5374
  }
5162
5375
  } catch (e) {
5163
5376
  LOG.info("Command", `[read_chat] Script error: ${e.message}`);
5164
5377
  }
5165
5378
  }
5166
- return { success: true, messages: [], status: "idle" };
5379
+ return buildReadChatCommandResult({ messages: [], status: "idle" }, args);
5167
5380
  }
5168
5381
  async function handleSendChat(h, args) {
5169
5382
  const text = args?.text || args?.message;
@@ -5814,6 +6027,7 @@ var RECENT_SEND_WINDOW_MS, recentSendByTarget;
5814
6027
  var init_chat_commands = __esm({
5815
6028
  "../../oss/packages/daemon-core/src/commands/chat-commands.ts"() {
5816
6029
  "use strict";
6030
+ init_contracts();
5817
6031
  init_chat_history();
5818
6032
  init_logger();
5819
6033
  RECENT_SEND_WINDOW_MS = 1200;
@@ -6299,21 +6513,21 @@ function applyProviderPatch(h, args, payload) {
6299
6513
  });
6300
6514
  }
6301
6515
  async function executeProviderScript(h, args, scriptName) {
6302
- const { agentType, ideType } = args || {};
6303
- if (!agentType) return { success: false, error: "agentType is required" };
6516
+ const resolvedProviderType = h.currentSession?.providerType || h.currentProviderType || args?.agentType || args?.providerType;
6517
+ if (!resolvedProviderType) return { success: false, error: "targetSessionId or providerType is required" };
6304
6518
  const loader = h.ctx.providerLoader;
6305
6519
  if (!loader) return { success: false, error: "ProviderLoader not initialized" };
6306
- const provider = loader.resolve(agentType);
6307
- if (!provider) return { success: false, error: `Provider not found: ${agentType}` };
6520
+ const provider = loader.resolve(resolvedProviderType);
6521
+ if (!provider) return { success: false, error: `Provider not found: ${resolvedProviderType}` };
6308
6522
  const webviewScriptName = `webview${scriptName.charAt(0).toUpperCase() + scriptName.slice(1)}`;
6309
6523
  const hasWebviewScript = provider.category === "ide" && !!provider.scripts?.[webviewScriptName];
6310
6524
  const actualScriptName = hasWebviewScript ? webviewScriptName : scriptName;
6311
6525
  if (!provider.scripts?.[actualScriptName]) {
6312
- return { success: false, error: `Script '${actualScriptName}' not available for ${agentType}` };
6526
+ return { success: false, error: `Script '${actualScriptName}' not available for ${resolvedProviderType}` };
6313
6527
  }
6314
6528
  const normalizedArgs = normalizeProviderScriptArgs(args);
6315
6529
  if (provider.category === "cli") {
6316
- const adapter = h.getCliAdapter(args?.targetSessionId || agentType);
6530
+ const adapter = h.getCliAdapter(args?.targetSessionId || resolvedProviderType);
6317
6531
  if (!adapter?.invokeScript) {
6318
6532
  return { success: false, error: `CLI adapter does not support script '${actualScriptName}'` };
6319
6533
  }
@@ -6338,7 +6552,7 @@ async function executeProviderScript(h, args, scriptName) {
6338
6552
  const scriptFn = provider.scripts[actualScriptName];
6339
6553
  const scriptCode = scriptFn(normalizedArgs);
6340
6554
  if (!scriptCode) return { success: false, error: `Script '${actualScriptName}' returned null` };
6341
- const cdpKey = provider.category === "ide" ? h.currentSession?.cdpManagerKey || h.currentManagerKey || agentType : h.currentSession?.cdpManagerKey || h.currentManagerKey || ideType;
6555
+ const cdpKey = provider.category === "ide" ? h.currentSession?.cdpManagerKey || h.currentManagerKey || resolvedProviderType : h.currentSession?.cdpManagerKey || h.currentManagerKey;
6342
6556
  LOG.info("Command", `[ExtScript] provider=${provider.type} category=${provider.category} cdpKey=${cdpKey}`);
6343
6557
  const cdp = h.getCdp(cdpKey);
6344
6558
  if (!cdp?.isConnected) return { success: false, error: `No CDP connection for ${cdpKey || "any"}` };
@@ -6346,7 +6560,7 @@ async function executeProviderScript(h, args, scriptName) {
6346
6560
  let result;
6347
6561
  if (provider.category === "extension") {
6348
6562
  const runtimeSessionId = h.currentSession?.sessionId || args?.targetSessionId;
6349
- if (!runtimeSessionId) return { success: false, error: `No target session found for ${agentType}` };
6563
+ if (!runtimeSessionId) return { success: false, error: `No target session found for ${resolvedProviderType}` };
6350
6564
  const parentSessionId = h.currentSession?.parentSessionId;
6351
6565
  if (parentSessionId) {
6352
6566
  await h.agentStream?.setActiveSession(cdp, parentSessionId, runtimeSessionId);
@@ -6375,7 +6589,7 @@ async function executeProviderScript(h, args, scriptName) {
6375
6589
  }
6376
6590
  } else {
6377
6591
  if (!targetSessionId) {
6378
- return { success: false, error: `No active session found for ${agentType}` };
6592
+ return { success: false, error: `No active session found for ${resolvedProviderType}` };
6379
6593
  }
6380
6594
  result = await cdp.evaluateInSessionFrame(targetSessionId, scriptCode);
6381
6595
  }
@@ -9724,8 +9938,6 @@ ${data.message || ""}`.trim();
9724
9938
  if (blockingModal || this.currentStatus === "waiting_approval") {
9725
9939
  throw new Error(`${this.cliName} is awaiting confirmation before it can accept a prompt`);
9726
9940
  }
9727
- this.committedMessages.push({ role: "user", content: text, timestamp: Date.now() });
9728
- this.syncMessageViews();
9729
9941
  this.isWaitingForResponse = true;
9730
9942
  this.responseBuffer = "";
9731
9943
  this.finishRetryCount = 0;
@@ -9757,6 +9969,13 @@ ${data.message || ""}`.trim();
9757
9969
  const submitDelayMs = this.sendDelayMs + Math.min(2e3, Math.max(0, estimatedLines - 1) * 350);
9758
9970
  const maxEchoWaitMs = submitDelayMs + Math.max(1500, Math.min(5e3, estimatedLines * 500));
9759
9971
  const retryDelayMs = Math.max(350, Math.min(1500, Math.max(this.sendDelayMs, submitDelayMs)));
9972
+ let didCommitUserTurn = false;
9973
+ const commitUserTurn = () => {
9974
+ if (didCommitUserTurn) return;
9975
+ didCommitUserTurn = true;
9976
+ this.committedMessages.push({ role: "user", content: text, timestamp: Date.now() });
9977
+ this.syncMessageViews();
9978
+ };
9760
9979
  if (this.settleTimer) {
9761
9980
  clearTimeout(this.settleTimer);
9762
9981
  this.settleTimer = null;
@@ -9769,109 +9988,128 @@ ${data.message || ""}`.trim();
9769
9988
  if (this.isWaitingForResponse) this.finishResponse();
9770
9989
  }, this.timeouts.maxResponse);
9771
9990
  };
9772
- const submit = () => {
9773
- if (!this.ptyProcess) return;
9774
- this.submitPendingUntil = 0;
9775
- this.recordTrace("submit_write", {
9776
- mode: "submit_key",
9777
- sendKey: this.sendKey,
9778
- screenText: summarizeCliTraceText(this.terminalScreen.getText(), 500)
9779
- });
9780
- this.ptyProcess.write(this.sendKey);
9781
- const retrySubmitIfStuck = (attempt) => {
9782
- this.submitRetryTimer = null;
9783
- if (!this.ptyProcess || !this.isWaitingForResponse || this.submitRetryUsed) return;
9784
- if (this.currentStatus === "waiting_approval") return;
9785
- if ((this.responseBuffer || "").trim()) return;
9991
+ await new Promise((resolve17) => {
9992
+ let resolved = false;
9993
+ const resolveOnce = () => {
9994
+ if (resolved) return;
9995
+ resolved = true;
9996
+ resolve17();
9997
+ };
9998
+ const submit = () => {
9999
+ if (!this.ptyProcess) {
10000
+ resolveOnce();
10001
+ return;
10002
+ }
10003
+ commitUserTurn();
10004
+ this.submitPendingUntil = 0;
9786
10005
  const screenText = this.terminalScreen.getText();
9787
- if (!promptLikelyVisible(screenText, normalizedPromptSnippet)) return;
9788
- if (/Esc to interrupt|Do you want to proceed|This command requires approval|Allow Codex to|Approve and run now|Always approve this session|Running…|Running\.\.\./i.test(screenText)) return;
9789
- this.responseSettleIgnoreUntil = Date.now() + this.timeouts.outputSettle + 400;
9790
- LOG.info("CLI", `[${this.cliType}] Retrying submit key for stuck prompt (attempt ${attempt})`);
9791
10006
  this.recordTrace("submit_write", {
9792
- mode: "submit_retry",
9793
- attempt,
10007
+ mode: "submit_key",
9794
10008
  sendKey: this.sendKey,
9795
10009
  screenText: summarizeCliTraceText(screenText, 500)
9796
10010
  });
9797
10011
  this.ptyProcess.write(this.sendKey);
9798
- if (attempt >= 3) {
9799
- this.submitRetryUsed = true;
9800
- return;
9801
- }
9802
- this.submitRetryTimer = setTimeout(() => retrySubmitIfStuck(attempt + 1), retryDelayMs);
10012
+ const retrySubmitIfStuck = (attempt) => {
10013
+ this.submitRetryTimer = null;
10014
+ if (!this.ptyProcess || !this.isWaitingForResponse || this.submitRetryUsed) return;
10015
+ if (this.currentStatus === "waiting_approval") return;
10016
+ if ((this.responseBuffer || "").trim()) return;
10017
+ const screenText2 = this.terminalScreen.getText();
10018
+ if (!promptLikelyVisible(screenText2, normalizedPromptSnippet)) return;
10019
+ if (/Esc to interrupt|Do you want to proceed|This command requires approval|Allow Codex to|Approve and run now|Always approve this session|Running…|Running\.\.\./i.test(screenText2)) return;
10020
+ this.responseSettleIgnoreUntil = Date.now() + this.timeouts.outputSettle + 400;
10021
+ LOG.info("CLI", `[${this.cliType}] Retrying submit key for stuck prompt (attempt ${attempt})`);
10022
+ this.recordTrace("submit_write", {
10023
+ mode: "submit_retry",
10024
+ attempt,
10025
+ sendKey: this.sendKey,
10026
+ screenText: summarizeCliTraceText(screenText2, 500)
10027
+ });
10028
+ this.ptyProcess.write(this.sendKey);
10029
+ if (attempt >= 3) {
10030
+ this.submitRetryUsed = true;
10031
+ return;
10032
+ }
10033
+ this.submitRetryTimer = setTimeout(() => retrySubmitIfStuck(attempt + 1), retryDelayMs);
10034
+ };
10035
+ this.submitRetryTimer = setTimeout(() => retrySubmitIfStuck(1), retryDelayMs);
10036
+ startResponseTimeout();
10037
+ resolveOnce();
9803
10038
  };
9804
- this.submitRetryTimer = setTimeout(() => retrySubmitIfStuck(1), retryDelayMs);
9805
- startResponseTimeout();
9806
- };
9807
- if (this.submitStrategy === "immediate") {
9808
- this.submitPendingUntil = 0;
10039
+ if (this.submitStrategy === "immediate") {
10040
+ commitUserTurn();
10041
+ this.submitPendingUntil = 0;
10042
+ this.recordTrace("submit_write", {
10043
+ mode: "immediate",
10044
+ text: summarizeCliTraceText(text, 500),
10045
+ sendKey: this.sendKey,
10046
+ screenText: summarizeCliTraceText(this.terminalScreen.getText(), 500)
10047
+ });
10048
+ this.ptyProcess.write(text + this.sendKey);
10049
+ this.submitRetryTimer = setTimeout(() => {
10050
+ this.submitRetryTimer = null;
10051
+ if (!this.ptyProcess || !this.isWaitingForResponse || this.submitRetryUsed) return;
10052
+ if (this.currentStatus === "waiting_approval") return;
10053
+ if ((this.responseBuffer || "").trim()) return;
10054
+ const screenText = this.terminalScreen.getText();
10055
+ if (!promptLikelyVisible(screenText, normalizedPromptSnippet)) return;
10056
+ LOG.info("CLI", `[${this.cliType}] Retrying submit key for stuck prompt (attempt 1)`);
10057
+ this.responseSettleIgnoreUntil = Date.now() + this.timeouts.outputSettle + 400;
10058
+ this.recordTrace("submit_write", {
10059
+ mode: "immediate_retry",
10060
+ attempt: 1,
10061
+ sendKey: this.sendKey,
10062
+ screenText: summarizeCliTraceText(screenText, 500)
10063
+ });
10064
+ this.ptyProcess.write(this.sendKey);
10065
+ this.submitRetryUsed = true;
10066
+ }, retryDelayMs);
10067
+ startResponseTimeout();
10068
+ resolveOnce();
10069
+ return;
10070
+ }
10071
+ if (submitDelayMs > 0) {
10072
+ this.submitPendingUntil = Date.now() + submitDelayMs;
10073
+ }
10074
+ this.ptyProcess.write(text);
9809
10075
  this.recordTrace("submit_write", {
9810
- mode: "immediate",
10076
+ mode: "type_then_submit",
9811
10077
  text: summarizeCliTraceText(text, 500),
9812
10078
  sendKey: this.sendKey,
9813
10079
  screenText: summarizeCliTraceText(this.terminalScreen.getText(), 500)
9814
10080
  });
9815
- this.ptyProcess.write(text + this.sendKey);
9816
- this.submitRetryTimer = setTimeout(() => {
9817
- this.submitRetryTimer = null;
9818
- if (!this.ptyProcess || !this.isWaitingForResponse || this.submitRetryUsed) return;
9819
- if (this.currentStatus === "waiting_approval") return;
9820
- if ((this.responseBuffer || "").trim()) return;
10081
+ const submitStartedAt = Date.now();
10082
+ let lastNormalizedScreen = "";
10083
+ let lastScreenChangeAt = submitStartedAt;
10084
+ const waitForEchoAndSubmit = () => {
10085
+ if (!this.ptyProcess) {
10086
+ resolveOnce();
10087
+ return;
10088
+ }
10089
+ const now = Date.now();
10090
+ const elapsed = now - submitStartedAt;
9821
10091
  const screenText = this.terminalScreen.getText();
9822
- if (!promptLikelyVisible(screenText, normalizedPromptSnippet)) return;
9823
- LOG.info("CLI", `[${this.cliType}] Retrying submit key for stuck prompt (attempt 1)`);
9824
- this.responseSettleIgnoreUntil = Date.now() + this.timeouts.outputSettle + 400;
9825
- this.recordTrace("submit_write", {
9826
- mode: "immediate_retry",
9827
- attempt: 1,
9828
- sendKey: this.sendKey,
9829
- screenText: summarizeCliTraceText(screenText, 500)
9830
- });
9831
- this.ptyProcess.write(this.sendKey);
9832
- this.submitRetryUsed = true;
9833
- }, retryDelayMs);
9834
- startResponseTimeout();
9835
- return;
9836
- }
9837
- if (submitDelayMs > 0) {
9838
- this.submitPendingUntil = Date.now() + submitDelayMs;
9839
- }
9840
- this.ptyProcess.write(text);
9841
- this.recordTrace("submit_write", {
9842
- mode: "type_then_submit",
9843
- text: summarizeCliTraceText(text, 500),
9844
- sendKey: this.sendKey,
9845
- screenText: summarizeCliTraceText(this.terminalScreen.getText(), 500)
9846
- });
9847
- const submitStartedAt = Date.now();
9848
- let lastNormalizedScreen = "";
9849
- let lastScreenChangeAt = submitStartedAt;
9850
- const waitForEchoAndSubmit = () => {
9851
- if (!this.ptyProcess) return;
9852
- const now = Date.now();
9853
- const elapsed = now - submitStartedAt;
9854
- const screenText = this.terminalScreen.getText();
9855
- const normalizedScreen = normalizePromptText(screenText);
9856
- if (normalizedScreen !== lastNormalizedScreen) {
9857
- lastNormalizedScreen = normalizedScreen;
9858
- lastScreenChangeAt = now;
9859
- }
9860
- const echoVisible = !normalizedPromptSnippet || promptLikelyVisible(screenText, normalizedPromptSnippet);
9861
- if (echoVisible) {
9862
- const screenSettled = now - lastScreenChangeAt >= 500;
9863
- if (elapsed >= submitDelayMs && screenSettled) {
10092
+ const normalizedScreen = normalizePromptText(screenText);
10093
+ if (normalizedScreen !== lastNormalizedScreen) {
10094
+ lastNormalizedScreen = normalizedScreen;
10095
+ lastScreenChangeAt = now;
10096
+ }
10097
+ const echoVisible = !normalizedPromptSnippet || promptLikelyVisible(screenText, normalizedPromptSnippet);
10098
+ if (echoVisible) {
10099
+ const screenSettled = now - lastScreenChangeAt >= 500;
10100
+ if (elapsed >= submitDelayMs && screenSettled) {
10101
+ submit();
10102
+ return;
10103
+ }
10104
+ }
10105
+ if (elapsed >= maxEchoWaitMs) {
9864
10106
  submit();
9865
10107
  return;
9866
10108
  }
9867
- }
9868
- if (elapsed >= maxEchoWaitMs) {
9869
- submit();
9870
- return;
9871
- }
9872
- setTimeout(waitForEchoAndSubmit, 50);
9873
- };
9874
- waitForEchoAndSubmit();
10109
+ setTimeout(waitForEchoAndSubmit, 50);
10110
+ };
10111
+ waitForEchoAndSubmit();
10112
+ });
9875
10113
  }
9876
10114
  getPartialResponse() {
9877
10115
  if (!this.isWaitingForResponse) return "";
@@ -27177,17 +27415,6 @@ var init_acp = __esm({
27177
27415
  }
27178
27416
  });
27179
27417
 
27180
- // ../../oss/packages/daemon-core/src/providers/contracts.ts
27181
- function flattenContent(content) {
27182
- if (typeof content === "string") return content;
27183
- return content.filter((b) => b.type === "text").map((b) => b.text).join("\n");
27184
- }
27185
- var init_contracts = __esm({
27186
- "../../oss/packages/daemon-core/src/providers/contracts.ts"() {
27187
- "use strict";
27188
- }
27189
- });
27190
-
27191
27418
  // ../../oss/packages/daemon-core/src/providers/acp-provider-instance.ts
27192
27419
  var import_stream, import_child_process5, AcpProviderInstance;
27193
27420
  var init_acp_provider_instance = __esm({
@@ -32340,6 +32567,37 @@ function buildAvailableProviders(providerLoader) {
32340
32567
  ...provider.detectedPath !== void 0 ? { detectedPath: provider.detectedPath } : {}
32341
32568
  }));
32342
32569
  }
32570
+ function buildMachineInfo(profile = "full") {
32571
+ const base = {
32572
+ hostname: os17.hostname(),
32573
+ platform: os17.platform()
32574
+ };
32575
+ if (profile === "live") {
32576
+ return base;
32577
+ }
32578
+ if (profile === "metadata") {
32579
+ const memSnap2 = getHostMemorySnapshot();
32580
+ return {
32581
+ ...base,
32582
+ arch: os17.arch(),
32583
+ cpus: os17.cpus().length,
32584
+ totalMem: memSnap2.totalMem,
32585
+ release: os17.release()
32586
+ };
32587
+ }
32588
+ const memSnap = getHostMemorySnapshot();
32589
+ return {
32590
+ ...base,
32591
+ arch: os17.arch(),
32592
+ cpus: os17.cpus().length,
32593
+ totalMem: memSnap.totalMem,
32594
+ freeMem: memSnap.freeMem,
32595
+ availableMem: memSnap.availableMem,
32596
+ loadavg: os17.loadavg(),
32597
+ uptime: os17.uptime(),
32598
+ release: os17.release()
32599
+ };
32600
+ }
32343
32601
  function parseMessageTime(value) {
32344
32602
  if (typeof value === "number" && Number.isFinite(value)) return value;
32345
32603
  if (typeof value === "string") {
@@ -32395,26 +32653,35 @@ function buildRecentLaunches(recentActivity) {
32395
32653
  })).sort((a, b) => b.lastLaunchedAt - a.lastLaunchedAt).slice(0, 12);
32396
32654
  }
32397
32655
  function buildStatusSnapshot(options) {
32656
+ const profile = options.profile || "full";
32398
32657
  const cfg = loadConfig();
32399
32658
  const state = loadState();
32400
32659
  const wsState = getWorkspaceState(cfg);
32401
- const memSnap = getHostMemorySnapshot();
32402
32660
  const recentActivity = getRecentActivity(state, 20);
32403
- const sessions = buildSessionEntries(
32661
+ const unreadSourceSessions = buildSessionEntries(
32404
32662
  options.allStates,
32405
- options.cdpManagers
32663
+ options.cdpManagers,
32664
+ { profile: "full" }
32406
32665
  );
32407
- for (const session of sessions) {
32408
- const lastSeenAt = getSessionSeenAt(state, session.id);
32409
- const seenCompletionMarker = getSessionSeenMarker(state, session.id);
32410
- const lastUsedAt = getSessionLastUsedAt(session);
32411
- const completionMarker = getSessionCompletionMarker(session);
32412
- const { unread, inboxBucket } = session.surfaceHidden ? { unread: false, inboxBucket: "idle" } : getUnreadState(
32413
- getSessionMessageUpdatedAt(session) > 0,
32414
- session.status,
32666
+ const sessions = profile === "full" ? unreadSourceSessions : buildSessionEntries(
32667
+ options.allStates,
32668
+ options.cdpManagers,
32669
+ { profile }
32670
+ );
32671
+ const sessionsById = new Map(sessions.map((session) => [session.id, session]));
32672
+ for (const sourceSession of unreadSourceSessions) {
32673
+ const session = sessionsById.get(sourceSession.id);
32674
+ if (!session) continue;
32675
+ const lastSeenAt = getSessionSeenAt(state, sourceSession.id);
32676
+ const seenCompletionMarker = getSessionSeenMarker(state, sourceSession.id);
32677
+ const lastUsedAt = getSessionLastUsedAt(sourceSession);
32678
+ const completionMarker = getSessionCompletionMarker(sourceSession);
32679
+ const { unread, inboxBucket } = sourceSession.surfaceHidden ? { unread: false, inboxBucket: "idle" } : getUnreadState(
32680
+ getSessionMessageUpdatedAt(sourceSession) > 0,
32681
+ sourceSession.status,
32415
32682
  lastUsedAt,
32416
32683
  lastSeenAt,
32417
- getLastMessageRole(session),
32684
+ getLastMessageRole(sourceSession),
32418
32685
  completionMarker,
32419
32686
  seenCompletionMarker
32420
32687
  );
@@ -32424,39 +32691,30 @@ function buildStatusSnapshot(options) {
32424
32691
  if (READ_DEBUG_ENABLED && (session.unread || session.inboxBucket !== "idle" || session.providerType.includes("codex"))) {
32425
32692
  LOG.info(
32426
32693
  "RecentRead",
32427
- `snapshot session id=${session.id} provider=${session.providerType} status=${String(session.status || "")} bucket=${inboxBucket} unread=${String(unread)} lastSeenAt=${lastSeenAt} completionMarker=${completionMarker || "-"} seenMarker=${seenCompletionMarker || "-"} lastUpdated=${String(session.lastUpdated || 0)} lastUsedAt=${lastUsedAt} lastRole=${getLastMessageRole(session)} msgUpdatedAt=${getSessionMessageUpdatedAt(session)}`
32694
+ `snapshot session id=${session.id} provider=${session.providerType} status=${String(session.status || "")} bucket=${inboxBucket} unread=${String(unread)} lastSeenAt=${lastSeenAt} completionMarker=${completionMarker || "-"} seenMarker=${seenCompletionMarker || "-"} lastUpdated=${String(session.lastUpdated || 0)} lastUsedAt=${lastUsedAt} lastRole=${getLastMessageRole(sourceSession)} msgUpdatedAt=${getSessionMessageUpdatedAt(sourceSession)}`
32428
32695
  );
32429
32696
  }
32430
32697
  }
32431
- const terminalBackend = getTerminalBackendRuntimeStatus();
32698
+ const includeMachineMetadata = profile !== "live";
32699
+ const terminalBackend = includeMachineMetadata ? getTerminalBackendRuntimeStatus() : void 0;
32432
32700
  return {
32433
32701
  instanceId: options.instanceId,
32434
- version: options.version,
32435
- daemonMode: options.daemonMode,
32436
- machine: {
32437
- hostname: os17.hostname(),
32438
- platform: os17.platform(),
32439
- arch: os17.arch(),
32440
- cpus: os17.cpus().length,
32441
- totalMem: memSnap.totalMem,
32442
- freeMem: memSnap.freeMem,
32443
- availableMem: memSnap.availableMem,
32444
- loadavg: os17.loadavg(),
32445
- uptime: os17.uptime(),
32446
- release: os17.release()
32447
- },
32448
- machineNickname: options.machineNickname ?? cfg.machineNickname ?? null,
32702
+ ...includeMachineMetadata ? { version: options.version } : {},
32703
+ machine: buildMachineInfo(profile),
32704
+ ...includeMachineMetadata ? { machineNickname: options.machineNickname ?? cfg.machineNickname ?? null } : {},
32449
32705
  timestamp: options.timestamp ?? Date.now(),
32450
- detectedIdes: buildDetectedIdeInfos(options.detectedIdes, options.cdpManagers),
32451
32706
  ...options.p2p ? { p2p: options.p2p } : {},
32452
32707
  sessions,
32453
- workspaces: wsState.workspaces,
32454
- defaultWorkspaceId: wsState.defaultWorkspaceId,
32455
- defaultWorkspacePath: wsState.defaultWorkspacePath,
32456
- terminalSizingMode: cfg.terminalSizingMode || "measured",
32457
- recentLaunches: buildRecentLaunches(recentActivity),
32458
- terminalBackend,
32459
- availableProviders: buildAvailableProviders(options.providerLoader)
32708
+ ...terminalBackend ? { terminalBackend } : {},
32709
+ ...includeMachineMetadata && {
32710
+ detectedIdes: buildDetectedIdeInfos(options.detectedIdes, options.cdpManagers),
32711
+ workspaces: wsState.workspaces,
32712
+ defaultWorkspaceId: wsState.defaultWorkspaceId,
32713
+ defaultWorkspacePath: wsState.defaultWorkspacePath,
32714
+ terminalSizingMode: cfg.terminalSizingMode || "measured",
32715
+ recentLaunches: buildRecentLaunches(recentActivity),
32716
+ availableProviders: buildAvailableProviders(options.providerLoader)
32717
+ }
32460
32718
  };
32461
32719
  }
32462
32720
  var os17, READ_DEBUG_ENABLED;
@@ -32727,6 +32985,7 @@ var init_router = __esm({
32727
32985
  init_logger();
32728
32986
  init_builders();
32729
32987
  init_snapshot();
32988
+ init_snapshot();
32730
32989
  init_upgrade_helper();
32731
32990
  fs9 = __toESM(require("fs"));
32732
32991
  CHAT_COMMANDS = [
@@ -33055,6 +33314,25 @@ var init_router = __esm({
33055
33314
  updateConfig({ userName: name });
33056
33315
  return { success: true, userName: name };
33057
33316
  }
33317
+ case "get_status_metadata": {
33318
+ const snapshot = buildStatusSnapshot({
33319
+ allStates: this.deps.instanceManager.collectAllStates(),
33320
+ cdpManagers: this.deps.cdpManagers,
33321
+ providerLoader: this.deps.providerLoader,
33322
+ detectedIdes: this.deps.detectedIdes.value,
33323
+ instanceId: this.deps.statusInstanceId || loadConfig().machineId || "daemon",
33324
+ version: this.deps.statusVersion || "unknown",
33325
+ profile: "metadata"
33326
+ });
33327
+ return { success: true, status: snapshot };
33328
+ }
33329
+ case "get_machine_runtime_stats": {
33330
+ return {
33331
+ success: true,
33332
+ machine: buildMachineInfo("full"),
33333
+ timestamp: Date.now()
33334
+ };
33335
+ }
33058
33336
  case "mark_session_seen": {
33059
33337
  const sessionId = args?.sessionId;
33060
33338
  if (!sessionId || typeof sessionId !== "string") {
@@ -33216,6 +33494,7 @@ var init_reporter = __esm({
33216
33494
  lastStatusSentAt = 0;
33217
33495
  statusPendingThrottle = false;
33218
33496
  lastP2PStatusHash = "";
33497
+ lastServerStatusHash = "";
33219
33498
  lastStatusSummary = "";
33220
33499
  statusTimer = null;
33221
33500
  p2pTimer = null;
@@ -33226,11 +33505,11 @@ var init_reporter = __esm({
33226
33505
  // ─── Lifecycle ───────────────────────────────────
33227
33506
  startReporting() {
33228
33507
  setTimeout(() => {
33229
- this.sendUnifiedStatusReport().catch((e) => LOG.warn("Status", `Initial report failed: ${e?.message}`));
33508
+ this.sendUnifiedStatusReport({ forceServer: true, reason: "initial" }).catch((e) => LOG.warn("Status", `Initial report failed: ${e?.message}`));
33230
33509
  }, 2e3);
33231
33510
  const scheduleServerReport = () => {
33232
33511
  this.statusTimer = setTimeout(() => {
33233
- this.sendUnifiedStatusReport().catch((e) => LOG.warn("Status", `Periodic report failed: ${e?.message}`));
33512
+ this.sendUnifiedStatusReport({ forceServer: true, reason: "periodic" }).catch((e) => LOG.warn("Status", `Periodic report failed: ${e?.message}`));
33234
33513
  scheduleServerReport();
33235
33514
  }, 3e4);
33236
33515
  };
@@ -33267,9 +33546,58 @@ var init_reporter = __esm({
33267
33546
  }, 5e3 - elapsed);
33268
33547
  }
33269
33548
  }
33549
+ toDaemonStatusEventName(value) {
33550
+ switch (value) {
33551
+ case "agent:generating_started":
33552
+ case "agent:waiting_approval":
33553
+ case "agent:generating_completed":
33554
+ case "agent:stopped":
33555
+ case "monitor:long_generating":
33556
+ return value;
33557
+ default:
33558
+ return null;
33559
+ }
33560
+ }
33561
+ buildServerStatusEvent(event) {
33562
+ const eventName = this.toDaemonStatusEventName(event.event);
33563
+ if (!eventName) return null;
33564
+ if (eventName.startsWith("provider:")) {
33565
+ return null;
33566
+ }
33567
+ const payload = {
33568
+ event: eventName,
33569
+ timestamp: typeof event.timestamp === "number" && Number.isFinite(event.timestamp) ? event.timestamp : Date.now()
33570
+ };
33571
+ if (typeof event.targetSessionId === "string" && event.targetSessionId.trim()) {
33572
+ payload.targetSessionId = event.targetSessionId.trim();
33573
+ }
33574
+ const providerType = typeof event.providerType === "string" && event.providerType.trim() ? event.providerType.trim() : typeof event.ideType === "string" && event.ideType.trim() ? event.ideType.trim() : "";
33575
+ if (providerType) {
33576
+ payload.providerType = providerType;
33577
+ }
33578
+ if (typeof event.duration === "number" && Number.isFinite(event.duration)) {
33579
+ payload.duration = event.duration;
33580
+ }
33581
+ if (typeof event.elapsedSec === "number" && Number.isFinite(event.elapsedSec)) {
33582
+ payload.elapsedSec = event.elapsedSec;
33583
+ }
33584
+ if (typeof event.modalMessage === "string" && event.modalMessage.trim()) {
33585
+ payload.modalMessage = event.modalMessage;
33586
+ }
33587
+ if (Array.isArray(event.modalButtons)) {
33588
+ const modalButtons = event.modalButtons.filter((button) => typeof button === "string" && button.trim().length > 0);
33589
+ if (modalButtons.length > 0) {
33590
+ payload.modalButtons = modalButtons;
33591
+ }
33592
+ }
33593
+ return payload;
33594
+ }
33270
33595
  emitStatusEvent(event) {
33271
33596
  LOG.info("StatusEvent", `${event.event} (${event.providerType || event.ideType || ""})`);
33272
- this.deps.serverConn?.sendMessage("status_event", event);
33597
+ const serverEvent = this.buildServerStatusEvent(event);
33598
+ if (!serverEvent) return;
33599
+ this.deps.p2p?.sendStatusEvent(serverEvent);
33600
+ this.deps.serverConn?.sendMessage("status_event", serverEvent);
33273
33601
  }
33274
33602
  removeAgentTracking(_key) {
33275
33603
  }
@@ -33338,17 +33666,16 @@ var init_reporter = __esm({
33338
33666
  detectedIdes: this.deps.detectedIdes || [],
33339
33667
  instanceId: this.deps.instanceId,
33340
33668
  version: this.deps.daemonVersion || "unknown",
33341
- daemonMode: true,
33342
33669
  timestamp: now,
33343
33670
  p2p: {
33344
33671
  available: p2p?.isAvailable || false,
33345
33672
  state: p2p?.connectionState || "unavailable",
33346
33673
  peers: p2p?.connectedPeerCount || 0,
33347
33674
  screenshotActive: p2p?.screenshotActive || false
33348
- }
33675
+ },
33676
+ profile: "live"
33349
33677
  }),
33350
- screenshotUsage: this.deps.getScreenshotUsage?.() || null,
33351
- connectedExtensions: []
33678
+ screenshotUsage: this.deps.getScreenshotUsage?.() || null
33352
33679
  };
33353
33680
  const payloadBytes = JSON.stringify(payload).length;
33354
33681
  const p2pSent = this.sendP2PPayload(payload);
@@ -33363,46 +33690,48 @@ var init_reporter = __esm({
33363
33690
  }
33364
33691
  if (opts?.p2pOnly) return;
33365
33692
  const wsPayload = {
33366
- daemonMode: true,
33367
33693
  sessions: sessions.map((session) => ({
33368
33694
  id: session.id,
33369
33695
  parentId: session.parentId,
33370
33696
  providerType: session.providerType,
33371
- providerName: session.providerName,
33697
+ providerName: session.providerName || session.providerType,
33372
33698
  kind: session.kind,
33373
33699
  transport: session.transport,
33374
33700
  status: session.status,
33375
- workspace: session.workspace,
33701
+ workspace: session.workspace ?? null,
33376
33702
  title: session.title,
33377
33703
  cdpConnected: session.cdpConnected,
33378
33704
  currentModel: session.currentModel,
33379
33705
  currentPlan: session.currentPlan,
33380
- currentAutoApprove: session.currentAutoApprove,
33381
- lastUpdated: session.lastUpdated,
33382
- unread: session.unread,
33383
- lastSeenAt: session.lastSeenAt,
33384
- inboxBucket: session.inboxBucket,
33385
- surfaceHidden: session.surfaceHidden,
33386
- controlValues: session.controlValues,
33387
- providerControls: session.providerControls,
33388
- acpConfigOptions: session.acpConfigOptions,
33389
- acpModes: session.acpModes
33706
+ currentAutoApprove: session.currentAutoApprove
33390
33707
  })),
33391
33708
  p2p: payload.p2p,
33392
- timestamp: now,
33393
- detectedIdes: payload.detectedIdes,
33394
- availableProviders: payload.availableProviders
33709
+ timestamp: now
33395
33710
  };
33711
+ const wsHash = this.simpleHash(JSON.stringify({
33712
+ ...wsPayload,
33713
+ timestamp: void 0
33714
+ }));
33715
+ if (!opts?.forceServer && wsHash === this.lastServerStatusHash) {
33716
+ LOG.debug("Server", `skip duplicate status_report${opts?.reason ? ` (${opts.reason})` : ""}`);
33717
+ return;
33718
+ }
33719
+ this.lastServerStatusHash = wsHash;
33396
33720
  serverConn.sendMessage("status_report", wsPayload);
33397
- LOG.debug("Server", `sent status_report (${JSON.stringify(wsPayload).length} bytes)`);
33721
+ LOG.debug("Server", `sent status_report (${JSON.stringify(wsPayload).length} bytes)${opts?.reason ? ` [${opts.reason}]` : ""}`);
33398
33722
  }
33399
33723
  // ─── P2P ─────────────────────────────────────────
33400
33724
  sendP2PPayload(payload) {
33401
33725
  const { timestamp: _ts, system: _sys, ...hashTarget } = payload;
33726
+ const sessions = Array.isArray(hashTarget.sessions) ? hashTarget.sessions.map((session) => {
33727
+ if (!session || typeof session !== "object") return session;
33728
+ const { lastUpdated: _lu, ...stableSession } = session;
33729
+ return stableSession;
33730
+ }) : hashTarget.sessions;
33402
33731
  const hashPayload = hashTarget.machine ? (() => {
33403
33732
  const { freeMem: _f, availableMem: _a2, loadavg: _l, uptime: _u, ...stableMachine } = hashTarget.machine;
33404
- return { ...hashTarget, machine: stableMachine };
33405
- })() : hashTarget;
33733
+ return { ...hashTarget, sessions, machine: stableMachine };
33734
+ })() : { ...hashTarget, sessions };
33406
33735
  const h = this.simpleHash(JSON.stringify(hashPayload));
33407
33736
  if (h !== this.lastP2PStatusHash) {
33408
33737
  this.lastP2PStatusHash = h;
@@ -40613,6 +40942,8 @@ async function initDaemonComponents(config2) {
40613
40942
  onStatusChange: config2.onStatusChange,
40614
40943
  onPostChatCommand: config2.onPostChatCommand,
40615
40944
  sessionHostControl: config2.sessionHostControl,
40945
+ statusInstanceId: config2.statusInstanceId,
40946
+ statusVersion: config2.statusVersion,
40616
40947
  getCdpLogFn: config2.getCdpLogFn || ((ideType) => LOG.forComponent(`CDP:${ideType}`).asLogFn())
40617
40948
  });
40618
40949
  poller = new AgentStreamPoller({
@@ -40738,6 +41069,7 @@ __export(src_exports, {
40738
41069
  SessionHostPtyTransportFactory: () => SessionHostPtyTransportFactory,
40739
41070
  VersionArchive: () => VersionArchive,
40740
41071
  appendRecentActivity: () => appendRecentActivity,
41072
+ buildMachineInfo: () => buildMachineInfo,
40741
41073
  buildSessionEntries: () => buildSessionEntries,
40742
41074
  buildStatusSnapshot: () => buildStatusSnapshot,
40743
41075
  connectCdpManager: () => connectCdpManager,
@@ -40990,6 +41322,7 @@ var init_server_connection = __esm({
40990
41322
  reconnectTimer = null;
40991
41323
  pingTimer = null;
40992
41324
  pongTimeout = null;
41325
+ missedPongCount = 0;
40993
41326
  messageHandlers = /* @__PURE__ */ new Map();
40994
41327
  stateChangeCallbacks = [];
40995
41328
  /** Fallback handler for message types without specific on() registration */
@@ -41010,6 +41343,7 @@ var init_server_connection = __esm({
41010
41343
  this.ws = new import_ws2.default(fullUrl, {
41011
41344
  headers: {
41012
41345
  "X-ADHDev-Token": this.options.token,
41346
+ "X-ADHDev-Daemon": JSON.stringify(this.options.cliInfo),
41013
41347
  "X-ADHDev-IDE": JSON.stringify(this.options.cliInfo),
41014
41348
  "X-ADHDev-Version": this.options.daemonVersion || "unknown"
41015
41349
  }
@@ -41088,18 +41422,21 @@ var init_server_connection = __esm({
41088
41422
  // --- Private ---
41089
41423
  onOpen() {
41090
41424
  LOG.info("Server", `[ServerConn] WebSocket open, sending auth...`);
41425
+ this.missedPongCount = 0;
41091
41426
  this.setState("authenticating");
41092
41427
  this.send({
41093
41428
  type: "auth",
41094
41429
  payload: {
41095
41430
  token: this.options.token,
41096
- ide: this.options.cliInfo
41431
+ daemon: this.options.cliInfo
41097
41432
  },
41098
41433
  timestamp: Date.now()
41099
41434
  });
41100
41435
  }
41101
41436
  onMessage(text) {
41102
41437
  try {
41438
+ this.clearPongTimeout();
41439
+ this.missedPongCount = 0;
41103
41440
  const message = JSON.parse(text);
41104
41441
  if (message.type === "auth_ok") {
41105
41442
  this.reconnectAttempts = 0;
@@ -41200,6 +41537,7 @@ var init_server_connection = __esm({
41200
41537
  this.reconnectTimer = null;
41201
41538
  }
41202
41539
  this.stopHeartbeat();
41540
+ this.missedPongCount = 0;
41203
41541
  }
41204
41542
  // ─── WS Heartbeat (ping/pong) ─────────────────────
41205
41543
  startHeartbeat() {
@@ -41209,9 +41547,14 @@ var init_server_connection = __esm({
41209
41547
  try {
41210
41548
  this.ws.ping();
41211
41549
  this.pongTimeout = setTimeout(() => {
41212
- LOG.info("Server", "[ServerConn] \u26A0 Pong timeout (15s) \u2014 closing zombie WS");
41213
- if (this.ws) {
41550
+ this.pongTimeout = null;
41551
+ this.missedPongCount += 1;
41552
+ const misses = this.missedPongCount;
41553
+ if (misses >= 3 && this.ws) {
41554
+ LOG.warn("Server", `[ServerConn] Pong timeout (${misses}x) \u2014 closing stale WS`);
41214
41555
  this.ws.terminate();
41556
+ } else {
41557
+ LOG.warn("Server", `[ServerConn] Pong timeout (${misses}x) \u2014 keeping WS open and retrying`);
41215
41558
  }
41216
41559
  }, 15e3);
41217
41560
  } catch {
@@ -41223,12 +41566,13 @@ var init_server_connection = __esm({
41223
41566
  clearInterval(this.pingTimer);
41224
41567
  this.pingTimer = null;
41225
41568
  }
41226
- if (this.pongTimeout) {
41227
- clearTimeout(this.pongTimeout);
41228
- this.pongTimeout = null;
41229
- }
41569
+ this.clearPongTimeout();
41230
41570
  }
41231
41571
  onPong() {
41572
+ this.clearPongTimeout();
41573
+ this.missedPongCount = 0;
41574
+ }
41575
+ clearPongTimeout() {
41232
41576
  if (this.pongTimeout) {
41233
41577
  clearTimeout(this.pongTimeout);
41234
41578
  this.pongTimeout = null;
@@ -41324,11 +41668,19 @@ function routeDataChannelMessage(peerId, msg, peers, handlers) {
41324
41668
  if (peer) peer.lastPongAt = Date.now();
41325
41669
  return;
41326
41670
  }
41671
+ if (parsed.type === "subscribe") {
41672
+ handleSubscribe(peerId, parsed, peers, handlers);
41673
+ return;
41674
+ }
41675
+ if (parsed.type === "unsubscribe") {
41676
+ handleUnsubscribe(peerId, parsed, peers);
41677
+ return;
41678
+ }
41327
41679
  if (parsed.type === "screenshot_start") {
41328
41680
  const peer = peers.get(peerId);
41329
41681
  const permission = peer?.sharePermission;
41330
- if (!parsed.ideType) {
41331
- log(`screenshot_start: REJECTED \u2014 no ideType from peer ${peerId}`);
41682
+ if (!parsed.targetSessionId) {
41683
+ log(`screenshot_start: REJECTED \u2014 no targetSessionId from peer ${peerId}`);
41332
41684
  return;
41333
41685
  }
41334
41686
  if (!canPeerStartScreenshots(permission)) {
@@ -41337,9 +41689,9 @@ function routeDataChannelMessage(peerId, msg, peers, handlers) {
41337
41689
  }
41338
41690
  if (peer) {
41339
41691
  peer.screenshotActive = true;
41340
- peer.screenshotIdeType = parsed.ideType;
41692
+ peer.screenshotTargetSessionId = parsed.targetSessionId;
41341
41693
  peer.needsFirstFrame = true;
41342
- log(`screenshot_start: peer=${peerId}, ideType=${parsed.ideType}, channelOpen=${!!peer.dataChannel}, state=${peer.state}`);
41694
+ log(`screenshot_start: peer=${peerId}, targetSessionId=${parsed.targetSessionId}, channelOpen=${!!peer.dataChannel}, state=${peer.state}`);
41343
41695
  handlers.screenshotStartHandler?.();
41344
41696
  } else {
41345
41697
  log(`screenshot_start: peer ${peerId} NOT FOUND in peers map!`);
@@ -41391,6 +41743,87 @@ function routeDataChannelMessage(peerId, msg, peers, handlers) {
41391
41743
  log(`Parse error from peer ${peerId}: ${e?.message}`);
41392
41744
  }
41393
41745
  }
41746
+ function handleSubscribe(peerId, msg, peers, handlers) {
41747
+ const peer = peers.get(peerId);
41748
+ if (!peer) return;
41749
+ if (msg.topic === "session.chat_tail") {
41750
+ const targetSessionId = typeof msg.params?.targetSessionId === "string" ? msg.params.targetSessionId.trim() : "";
41751
+ if (!targetSessionId) return;
41752
+ if (!peer.chatSubscriptions) peer.chatSubscriptions = /* @__PURE__ */ new Map();
41753
+ peer.chatSubscriptions.set(msg.key, {
41754
+ key: msg.key,
41755
+ params: msg.params,
41756
+ seq: 0,
41757
+ cursor: {
41758
+ knownMessageCount: Math.max(0, Number(msg.params.knownMessageCount || 0)),
41759
+ lastMessageSignature: typeof msg.params.lastMessageSignature === "string" ? msg.params.lastMessageSignature : "",
41760
+ tailLimit: Math.max(0, Number(msg.params.tailLimit || 0))
41761
+ },
41762
+ lastDeliveredSignature: ""
41763
+ });
41764
+ } else if (msg.topic === "machine.runtime") {
41765
+ if (!peer.machineRuntimeSubscriptions) peer.machineRuntimeSubscriptions = /* @__PURE__ */ new Map();
41766
+ peer.machineRuntimeSubscriptions.set(msg.key, {
41767
+ key: msg.key,
41768
+ params: msg.params,
41769
+ seq: 0,
41770
+ lastSentAt: 0
41771
+ });
41772
+ } else if (msg.topic === "session_host.diagnostics") {
41773
+ if (!peer.sessionHostDiagnosticsSubscriptions) peer.sessionHostDiagnosticsSubscriptions = /* @__PURE__ */ new Map();
41774
+ peer.sessionHostDiagnosticsSubscriptions.set(msg.key, {
41775
+ key: msg.key,
41776
+ params: msg.params,
41777
+ seq: 0,
41778
+ lastSentAt: 0
41779
+ });
41780
+ } else if (msg.topic === "session.modal") {
41781
+ const targetSessionId = typeof msg.params?.targetSessionId === "string" ? msg.params.targetSessionId.trim() : "";
41782
+ if (!targetSessionId) return;
41783
+ if (!peer.sessionModalSubscriptions) peer.sessionModalSubscriptions = /* @__PURE__ */ new Map();
41784
+ peer.sessionModalSubscriptions.set(msg.key, {
41785
+ key: msg.key,
41786
+ params: msg.params,
41787
+ seq: 0,
41788
+ lastSentAt: 0,
41789
+ lastDeliveredSignature: ""
41790
+ });
41791
+ } else if (msg.topic === "daemon.metadata") {
41792
+ if (!peer.daemonMetadataSubscriptions) peer.daemonMetadataSubscriptions = /* @__PURE__ */ new Map();
41793
+ peer.daemonMetadataSubscriptions.set(msg.key, {
41794
+ key: msg.key,
41795
+ params: msg.params,
41796
+ seq: 0,
41797
+ lastSentAt: 0
41798
+ });
41799
+ } else {
41800
+ return;
41801
+ }
41802
+ handlers.subscriptionChangeHandler?.();
41803
+ }
41804
+ function handleUnsubscribe(peerId, msg, peers) {
41805
+ const peer = peers.get(peerId);
41806
+ if (!peer) return;
41807
+ if (msg.topic === "session.chat_tail") {
41808
+ peer.chatSubscriptions?.delete(msg.key);
41809
+ return;
41810
+ }
41811
+ if (msg.topic === "machine.runtime") {
41812
+ peer.machineRuntimeSubscriptions?.delete(msg.key);
41813
+ return;
41814
+ }
41815
+ if (msg.topic === "session_host.diagnostics") {
41816
+ peer.sessionHostDiagnosticsSubscriptions?.delete(msg.key);
41817
+ return;
41818
+ }
41819
+ if (msg.topic === "session.modal") {
41820
+ peer.sessionModalSubscriptions?.delete(msg.key);
41821
+ return;
41822
+ }
41823
+ if (msg.topic === "daemon.metadata") {
41824
+ peer.daemonMetadataSubscriptions?.delete(msg.key);
41825
+ }
41826
+ }
41394
41827
  async function handleP2PCommand(peerId, msg, peers, handlers) {
41395
41828
  const { id, commandType, data } = msg;
41396
41829
  const peer = peers.get(peerId);
@@ -41411,7 +41844,7 @@ async function handleP2PCommand(peerId, msg, peers, handlers) {
41411
41844
  }
41412
41845
  }
41413
41846
  async function handleInputEvent(peerId, msg, peers, handlers) {
41414
- const { id, action, params, instanceId } = msg;
41847
+ const { id, action, params, targetSessionId: explicitTargetSessionId } = msg;
41415
41848
  const peer = peers.get(peerId);
41416
41849
  const permission = peer?.sharePermission;
41417
41850
  if (!canPeerUseRemoteInput(permission)) {
@@ -41423,11 +41856,11 @@ async function handleInputEvent(peerId, msg, peers, handlers) {
41423
41856
  return;
41424
41857
  }
41425
41858
  try {
41426
- const ideType = instanceId || peer?.screenshotIdeType;
41427
- if (!ideType) {
41428
- log(`[Input] WARNING: No instanceId or screenshotIdeType for peer ${peerId}`);
41859
+ const targetSessionId = explicitTargetSessionId || peer?.screenshotTargetSessionId;
41860
+ if (!targetSessionId) {
41861
+ log(`[Input] WARNING: No targetSessionId for peer ${peerId}`);
41429
41862
  }
41430
- const result = await handlers.inputHandler({ action, params, ideType });
41863
+ const result = await handlers.inputHandler({ action, params, targetSessionId });
41431
41864
  sendToPeer(peer, { id, type: "response", success: true, result });
41432
41865
  } catch (e) {
41433
41866
  sendToPeer(peer, { id, type: "response", success: false, error: e?.message });
@@ -41486,6 +41919,35 @@ var init_screenshot_sender = __esm({
41486
41919
  }
41487
41920
  return sentAny;
41488
41921
  }
41922
+ sendStatusEvent(peers, event) {
41923
+ const payload = JSON.stringify({
41924
+ type: "status_event",
41925
+ payload: event,
41926
+ timestamp: Date.now()
41927
+ });
41928
+ let sentAny = false;
41929
+ for (const peer of peers.values()) {
41930
+ if (peer.state !== "connected" || !peer.dataChannel) continue;
41931
+ try {
41932
+ peer.dataChannel.sendMessage(payload);
41933
+ sentAny = true;
41934
+ } catch {
41935
+ }
41936
+ }
41937
+ return sentAny;
41938
+ }
41939
+ sendTopicUpdateToPeer(peer, update) {
41940
+ if (!peer?.dataChannel || peer.state !== "connected") return false;
41941
+ try {
41942
+ peer.dataChannel.sendMessage(JSON.stringify({
41943
+ type: "topic_update",
41944
+ update
41945
+ }));
41946
+ return true;
41947
+ } catch {
41948
+ return false;
41949
+ }
41950
+ }
41489
41951
  /** Broadcast runtime session output to all connected peers */
41490
41952
  broadcastSessionOutput(peers, sessionId, data) {
41491
41953
  const msg = JSON.stringify({ type: "session_output", sessionId, data });
@@ -41578,8 +42040,14 @@ async function initiateConnection(deps, peerId, sharePermission) {
41578
42040
  }
41579
42041
  const pid = peerId || `legacy_${Date.now()}`;
41580
42042
  const existing = deps.peers.get(pid);
41581
- if (existing?.state === "connected") return;
41582
- if (existing?.state === "connecting") disconnectPeer(deps.peers, pid, deps.notifyStateChange);
42043
+ if (existing?.state === "connected") {
42044
+ log(`initiateconnection() ignored for peer ${pid} \u2014 already connected`);
42045
+ return;
42046
+ }
42047
+ if (existing?.state === "connecting") {
42048
+ log(`initiateconnection() ignored for peer ${pid} \u2014 connection already in progress`);
42049
+ return;
42050
+ }
41583
42051
  log(`initiateconnection() for peer ${pid}...`);
41584
42052
  const mod = deps.nodeDatachannel;
41585
42053
  const PeerConnectionCtor = mod.PeerConnection || mod.default?.PeerConnection || mod.default || mod;
@@ -41641,7 +42109,12 @@ async function initiateConnection(deps, peerId, sharePermission) {
41641
42109
  pendingCandidates: [],
41642
42110
  remoteDescriptionSet: false,
41643
42111
  isRelay: false,
41644
- lastPongAt: Date.now()
42112
+ lastPongAt: Date.now(),
42113
+ chatSubscriptions: /* @__PURE__ */ new Map(),
42114
+ machineRuntimeSubscriptions: /* @__PURE__ */ new Map(),
42115
+ sessionHostDiagnosticsSubscriptions: /* @__PURE__ */ new Map(),
42116
+ sessionModalSubscriptions: /* @__PURE__ */ new Map(),
42117
+ daemonMetadataSubscriptions: /* @__PURE__ */ new Map()
41645
42118
  };
41646
42119
  deps.peers.set(pid, entry);
41647
42120
  deps.notifyStateChange();
@@ -41879,6 +42352,7 @@ var init_daemon_p2p = __esm({
41879
42352
  peers = /* @__PURE__ */ new Map();
41880
42353
  nodeDatachannel = null;
41881
42354
  stateListeners = [];
42355
+ lastNotifiedState = null;
41882
42356
  screenshotSender = new ScreenshotSender();
41883
42357
  // Handler storage — exposed to router via DataChannelHandlers interface
41884
42358
  handlers = {
@@ -41887,7 +42361,8 @@ var init_daemon_p2p = __esm({
41887
42361
  commandHandler: null,
41888
42362
  ptyInputHandler: null,
41889
42363
  ptyResizeHandler: null,
41890
- screenshotStartHandler: null
42364
+ screenshotStartHandler: null,
42365
+ subscriptionChangeHandler: null
41891
42366
  };
41892
42367
  get screenshotActive() {
41893
42368
  for (const peer of this.peers.values()) {
@@ -41895,11 +42370,11 @@ var init_daemon_p2p = __esm({
41895
42370
  }
41896
42371
  return false;
41897
42372
  }
41898
- /** Get the ideType for the currently active screenshot request */
41899
- get screenshotIdeType() {
42373
+ /** Get the target session for the currently active screenshot request */
42374
+ get screenshotTargetSessionId() {
41900
42375
  for (const peer of this.peers.values()) {
41901
- if (peer.screenshotActive && peer.state === "connected" && peer.screenshotIdeType) {
41902
- return peer.screenshotIdeType;
42376
+ if (peer.screenshotActive && peer.state === "connected" && peer.screenshotTargetSessionId) {
42377
+ return peer.screenshotTargetSessionId;
41903
42378
  }
41904
42379
  }
41905
42380
  return void 0;
@@ -42016,6 +42491,8 @@ ${e?.stack || ""}`);
42016
42491
  }
42017
42492
  notifyStateChange = () => {
42018
42493
  const state = this.connectionState;
42494
+ if (state === this.lastNotifiedState) return;
42495
+ this.lastNotifiedState = state;
42019
42496
  this.stateListeners.forEach((fn) => fn(state));
42020
42497
  };
42021
42498
  get connectionManagerDeps() {
@@ -42043,11 +42520,15 @@ ${e?.stack || ""}`);
42043
42520
  for (const peerId of Array.from(this.peers.keys())) {
42044
42521
  this.disconnectPeer(peerId);
42045
42522
  }
42523
+ this.lastNotifiedState = null;
42046
42524
  }
42047
42525
  // ─── Delegated data sending ─────────────────────
42048
42526
  sendStatus(status) {
42049
42527
  return this.screenshotSender.sendStatus(this.peers, status);
42050
42528
  }
42529
+ sendStatusEvent(event) {
42530
+ return this.screenshotSender.sendStatusEvent(this.peers, event);
42531
+ }
42051
42532
  broadcastSessionOutput(sessionId, data) {
42052
42533
  return this.screenshotSender.broadcastSessionOutput(this.peers, sessionId, data);
42053
42534
  }
@@ -42076,6 +42557,89 @@ ${e?.stack || ""}`);
42076
42557
  onScreenshotStart(handler) {
42077
42558
  this.handlers.screenshotStartHandler = handler;
42078
42559
  }
42560
+ onSubscriptionChange(handler) {
42561
+ this.handlers.subscriptionChangeHandler = handler;
42562
+ }
42563
+ hasChatSubscriptions() {
42564
+ for (const peer of this.peers.values()) {
42565
+ if (peer.chatSubscriptions && peer.chatSubscriptions.size > 0) return true;
42566
+ }
42567
+ return false;
42568
+ }
42569
+ hasMachineRuntimeSubscriptions() {
42570
+ for (const peer of this.peers.values()) {
42571
+ if (peer.machineRuntimeSubscriptions && peer.machineRuntimeSubscriptions.size > 0) return true;
42572
+ }
42573
+ return false;
42574
+ }
42575
+ hasSessionHostDiagnosticsSubscriptions() {
42576
+ for (const peer of this.peers.values()) {
42577
+ if (peer.sessionHostDiagnosticsSubscriptions && peer.sessionHostDiagnosticsSubscriptions.size > 0) return true;
42578
+ }
42579
+ return false;
42580
+ }
42581
+ hasSessionModalSubscriptions() {
42582
+ for (const peer of this.peers.values()) {
42583
+ if (peer.sessionModalSubscriptions && peer.sessionModalSubscriptions.size > 0) return true;
42584
+ }
42585
+ return false;
42586
+ }
42587
+ hasDaemonMetadataSubscriptions() {
42588
+ for (const peer of this.peers.values()) {
42589
+ if (peer.daemonMetadataSubscriptions && peer.daemonMetadataSubscriptions.size > 0) return true;
42590
+ }
42591
+ return false;
42592
+ }
42593
+ async flushChatSubscriptions(builder) {
42594
+ for (const peer of this.peers.values()) {
42595
+ if (peer.state !== "connected" || !peer.chatSubscriptions || peer.chatSubscriptions.size === 0) continue;
42596
+ for (const subscription of peer.chatSubscriptions.values()) {
42597
+ const update = await builder(subscription);
42598
+ if (!update) continue;
42599
+ this.screenshotSender.sendTopicUpdateToPeer(peer, update);
42600
+ }
42601
+ }
42602
+ }
42603
+ async flushMachineRuntimeSubscriptions(builder) {
42604
+ for (const peer of this.peers.values()) {
42605
+ if (peer.state !== "connected" || !peer.machineRuntimeSubscriptions || peer.machineRuntimeSubscriptions.size === 0) continue;
42606
+ for (const subscription of peer.machineRuntimeSubscriptions.values()) {
42607
+ const update = await builder(subscription);
42608
+ if (!update) continue;
42609
+ this.screenshotSender.sendTopicUpdateToPeer(peer, update);
42610
+ }
42611
+ }
42612
+ }
42613
+ async flushSessionHostDiagnosticsSubscriptions(builder) {
42614
+ for (const peer of this.peers.values()) {
42615
+ if (peer.state !== "connected" || !peer.sessionHostDiagnosticsSubscriptions || peer.sessionHostDiagnosticsSubscriptions.size === 0) continue;
42616
+ for (const subscription of peer.sessionHostDiagnosticsSubscriptions.values()) {
42617
+ const update = await builder(subscription);
42618
+ if (!update) continue;
42619
+ this.screenshotSender.sendTopicUpdateToPeer(peer, update);
42620
+ }
42621
+ }
42622
+ }
42623
+ async flushSessionModalSubscriptions(builder) {
42624
+ for (const peer of this.peers.values()) {
42625
+ if (peer.state !== "connected" || !peer.sessionModalSubscriptions || peer.sessionModalSubscriptions.size === 0) continue;
42626
+ for (const subscription of peer.sessionModalSubscriptions.values()) {
42627
+ const update = await builder(subscription);
42628
+ if (!update) continue;
42629
+ this.screenshotSender.sendTopicUpdateToPeer(peer, update);
42630
+ }
42631
+ }
42632
+ }
42633
+ async flushDaemonMetadataSubscriptions(builder) {
42634
+ for (const peer of this.peers.values()) {
42635
+ if (peer.state !== "connected" || !peer.daemonMetadataSubscriptions || peer.daemonMetadataSubscriptions.size === 0) continue;
42636
+ for (const subscription of peer.daemonMetadataSubscriptions.values()) {
42637
+ const update = await builder(subscription);
42638
+ if (!update) continue;
42639
+ this.screenshotSender.sendTopicUpdateToPeer(peer, update);
42640
+ }
42641
+ }
42642
+ }
42079
42643
  };
42080
42644
  }
42081
42645
  });
@@ -48736,12 +49300,12 @@ var init_screenshot_controller = __esm({
48736
49300
  async tick() {
48737
49301
  if (!this.deps.isRunning()) return;
48738
49302
  const active = this.deps.isScreenshotActive();
48739
- const ssIdeType = this.deps.getScreenshotIdeType();
48740
- if (active && !ssIdeType) {
49303
+ const targetSessionId = this.deps.getScreenshotTargetSessionId();
49304
+ if (active && !targetSessionId) {
48741
49305
  this.timer = setTimeout(() => this.tick(), 500);
48742
49306
  return;
48743
49307
  }
48744
- const cdp = ssIdeType ? this.deps.getCdp(ssIdeType) : null;
49308
+ const cdp = targetSessionId ? this.deps.getCdp(targetSessionId) : null;
48745
49309
  const isRelay = this.deps.isUsingRelay();
48746
49310
  const profile = isRelay ? this.profileRelay : this.profileDirect;
48747
49311
  this.checkBudgetReset();
@@ -49272,6 +49836,48 @@ __export(adhdev_daemon_exports, {
49272
49836
  isDaemonRunning: () => isDaemonRunning,
49273
49837
  stopDaemon: () => stopDaemon
49274
49838
  });
49839
+ function hashSignatureParts2(parts) {
49840
+ let hash2 = 2166136261;
49841
+ for (const part of parts) {
49842
+ const text = String(part || "");
49843
+ for (let i = 0; i < text.length; i += 1) {
49844
+ hash2 ^= text.charCodeAt(i);
49845
+ hash2 = Math.imul(hash2, 16777619) >>> 0;
49846
+ }
49847
+ hash2 ^= 255;
49848
+ hash2 = Math.imul(hash2, 16777619) >>> 0;
49849
+ }
49850
+ return hash2.toString(16).padStart(8, "0");
49851
+ }
49852
+ function buildChatTailDeliverySignature(payload) {
49853
+ let messages = "";
49854
+ try {
49855
+ messages = JSON.stringify(payload.messages);
49856
+ } catch {
49857
+ messages = String(payload.messages.length);
49858
+ }
49859
+ return hashSignatureParts2([
49860
+ payload.sessionId,
49861
+ payload.historySessionId || "",
49862
+ payload.status,
49863
+ payload.title || "",
49864
+ payload.syncMode,
49865
+ String(payload.replaceFrom),
49866
+ String(payload.totalMessages),
49867
+ payload.lastMessageSignature,
49868
+ payload.activeModal ? `${payload.activeModal.message}|${payload.activeModal.buttons.join("")}` : "",
49869
+ messages
49870
+ ]);
49871
+ }
49872
+ function buildSessionModalDeliverySignature(payload) {
49873
+ return hashSignatureParts2([
49874
+ payload.sessionId,
49875
+ payload.status,
49876
+ payload.title || "",
49877
+ payload.modalMessage || "",
49878
+ Array.isArray(payload.modalButtons) ? payload.modalButtons.join("") : ""
49879
+ ]);
49880
+ }
49275
49881
  function getDaemonPidFile() {
49276
49882
  const dir = path26.join(os23.homedir(), ".adhdev");
49277
49883
  if (!fs17.existsSync(dir)) fs17.mkdirSync(dir, { recursive: true });
@@ -49335,7 +49941,7 @@ function stopDaemon() {
49335
49941
  return false;
49336
49942
  }
49337
49943
  }
49338
- var os23, fs17, path26, import_http, import_ws3, import_chalk2, pkgVersion, AdhdevDaemon;
49944
+ var os23, fs17, path26, import_http, import_ws3, import_chalk2, pkgVersion, ACTIVE_CHAT_POLL_STATUSES, AdhdevDaemon;
49339
49945
  var init_adhdev_daemon = __esm({
49340
49946
  "src/adhdev-daemon.ts"() {
49341
49947
  "use strict";
@@ -49353,7 +49959,12 @@ var init_adhdev_daemon = __esm({
49353
49959
  import_ws3 = require("ws");
49354
49960
  import_chalk2 = __toESM(require("chalk"));
49355
49961
  init_version();
49356
- pkgVersion = resolvePackageVersion({ injectedVersion: "0.8.34" });
49962
+ pkgVersion = resolvePackageVersion({ injectedVersion: "0.8.36" });
49963
+ ACTIVE_CHAT_POLL_STATUSES = /* @__PURE__ */ new Set([
49964
+ "generating",
49965
+ "waiting_approval",
49966
+ "starting"
49967
+ ]);
49357
49968
  AdhdevDaemon = class {
49358
49969
  localHttpServer = null;
49359
49970
  localWss = null;
@@ -49362,6 +49973,11 @@ var init_adhdev_daemon = __esm({
49362
49973
  p2p = null;
49363
49974
  screenshotController = null;
49364
49975
  statusReporter = null;
49976
+ topicSubscriptionTimer = null;
49977
+ p2pChatFlushInFlight = false;
49978
+ pendingP2PChatFlush = false;
49979
+ pendingP2PChatFlushOnlyActive = true;
49980
+ hotP2PChatSessionIds = /* @__PURE__ */ new Set();
49365
49981
  components = null;
49366
49982
  sessionHostEndpoint = null;
49367
49983
  sessionHostController = null;
@@ -49385,6 +50001,252 @@ var init_adhdev_daemon = __esm({
49385
50001
  const mode = this.getCliPresentationMode(sessionId);
49386
50002
  return mode === "chat" || mode === "terminal";
49387
50003
  }
50004
+ async buildChatTailUpdateForSubscription(subscription) {
50005
+ const result = await this.components.router.execute("read_chat", {
50006
+ targetSessionId: subscription.params.targetSessionId,
50007
+ ...subscription.params.historySessionId ? { historySessionId: subscription.params.historySessionId } : {},
50008
+ knownMessageCount: subscription.cursor.knownMessageCount,
50009
+ lastMessageSignature: subscription.cursor.lastMessageSignature,
50010
+ ...subscription.cursor.tailLimit > 0 ? { tailLimit: subscription.cursor.tailLimit } : {}
50011
+ }, "p2p");
50012
+ if (!result?.success || result.syncMode === "noop") {
50013
+ if (result?.success) {
50014
+ subscription.cursor = {
50015
+ knownMessageCount: Math.max(0, Number(result.totalMessages || subscription.cursor.knownMessageCount)),
50016
+ lastMessageSignature: typeof result.lastMessageSignature === "string" ? result.lastMessageSignature : subscription.cursor.lastMessageSignature,
50017
+ tailLimit: subscription.cursor.tailLimit
50018
+ };
50019
+ }
50020
+ return null;
50021
+ }
50022
+ subscription.seq += 1;
50023
+ const syncMode = result.syncMode === "append" || result.syncMode === "replace_tail" || result.syncMode === "noop" || result.syncMode === "full" ? result.syncMode : "full";
50024
+ subscription.cursor = {
50025
+ knownMessageCount: Math.max(0, Number(result.totalMessages || 0)),
50026
+ lastMessageSignature: typeof result.lastMessageSignature === "string" ? result.lastMessageSignature : "",
50027
+ tailLimit: subscription.cursor.tailLimit
50028
+ };
50029
+ const activeModal = result.activeModal && typeof result.activeModal === "object" && typeof result.activeModal.message === "string" && Array.isArray(result.activeModal.buttons) ? {
50030
+ message: result.activeModal.message,
50031
+ buttons: result.activeModal.buttons.filter((button) => typeof button === "string")
50032
+ } : null;
50033
+ const deliverySignature = buildChatTailDeliverySignature({
50034
+ sessionId: subscription.params.targetSessionId,
50035
+ ...subscription.params.historySessionId ? { historySessionId: subscription.params.historySessionId } : {},
50036
+ messages: Array.isArray(result.messages) ? result.messages : [],
50037
+ status: typeof result.status === "string" ? result.status : "idle",
50038
+ ...typeof result.title === "string" ? { title: result.title } : {},
50039
+ ...activeModal ? { activeModal } : {},
50040
+ syncMode,
50041
+ replaceFrom: Number(result.replaceFrom || 0),
50042
+ totalMessages: Number(result.totalMessages || 0),
50043
+ lastMessageSignature: typeof result.lastMessageSignature === "string" ? result.lastMessageSignature : ""
50044
+ });
50045
+ if (deliverySignature === subscription.lastDeliveredSignature) {
50046
+ return null;
50047
+ }
50048
+ subscription.lastDeliveredSignature = deliverySignature;
50049
+ return {
50050
+ topic: "session.chat_tail",
50051
+ key: subscription.key,
50052
+ sessionId: subscription.params.targetSessionId,
50053
+ ...subscription.params.historySessionId ? { historySessionId: subscription.params.historySessionId } : {},
50054
+ seq: subscription.seq,
50055
+ timestamp: Date.now(),
50056
+ messages: Array.isArray(result.messages) ? result.messages : [],
50057
+ status: typeof result.status === "string" ? result.status : "idle",
50058
+ ...typeof result.title === "string" ? { title: result.title } : {},
50059
+ ...activeModal ? { activeModal } : {},
50060
+ syncMode,
50061
+ replaceFrom: Number(result.replaceFrom || 0),
50062
+ totalMessages: Number(result.totalMessages || 0),
50063
+ lastMessageSignature: typeof result.lastMessageSignature === "string" ? result.lastMessageSignature : ""
50064
+ };
50065
+ }
50066
+ buildLiveStatusSnapshot() {
50067
+ return buildStatusSnapshot({
50068
+ allStates: this.components.instanceManager.collectAllStates(),
50069
+ cdpManagers: this.components.cdpManagers,
50070
+ providerLoader: this.components.providerLoader,
50071
+ detectedIdes: this.components.detectedIdes.value.map((ide) => ({
50072
+ ...ide,
50073
+ path: ide.path ?? void 0
50074
+ })),
50075
+ instanceId: `daemon_${loadConfig().machineId || "daemon"}`,
50076
+ version: pkgVersion,
50077
+ profile: "live"
50078
+ });
50079
+ }
50080
+ getHotChatSessionIdsForP2PFlush() {
50081
+ const snapshot = this.buildLiveStatusSnapshot();
50082
+ const active = new Set(
50083
+ snapshot.sessions.filter((session) => ACTIVE_CHAT_POLL_STATUSES.has(String(session.status || "").toLowerCase())).map((session) => session.id)
50084
+ );
50085
+ const finalizing = new Set(
50086
+ Array.from(this.hotP2PChatSessionIds).filter((sessionId) => !active.has(sessionId))
50087
+ );
50088
+ this.hotP2PChatSessionIds = active;
50089
+ return { active, finalizing };
50090
+ }
50091
+ async flushP2PChatSubscriptions(options = {}) {
50092
+ if (!this.p2p?.isConnected || !this.p2p.hasChatSubscriptions()) return;
50093
+ const onlyActive = options.onlyActive === true;
50094
+ if (this.p2pChatFlushInFlight) {
50095
+ this.pendingP2PChatFlush = true;
50096
+ this.pendingP2PChatFlushOnlyActive = this.pendingP2PChatFlushOnlyActive && onlyActive;
50097
+ return;
50098
+ }
50099
+ this.p2pChatFlushInFlight = true;
50100
+ try {
50101
+ const hotSessionIds = onlyActive ? this.getHotChatSessionIdsForP2PFlush() : null;
50102
+ await this.p2p.flushChatSubscriptions(async (subscription) => {
50103
+ if (hotSessionIds && !hotSessionIds.active.has(subscription.params.targetSessionId) && !hotSessionIds.finalizing.has(subscription.params.targetSessionId)) {
50104
+ return null;
50105
+ }
50106
+ return this.buildChatTailUpdateForSubscription(subscription);
50107
+ });
50108
+ } finally {
50109
+ this.p2pChatFlushInFlight = false;
50110
+ if (this.pendingP2PChatFlush) {
50111
+ const pendingOnlyActive = this.pendingP2PChatFlushOnlyActive;
50112
+ this.pendingP2PChatFlush = false;
50113
+ this.pendingP2PChatFlushOnlyActive = true;
50114
+ void this.flushP2PChatSubscriptions({ onlyActive: pendingOnlyActive });
50115
+ }
50116
+ }
50117
+ }
50118
+ buildMachineRuntimeUpdateForSubscription(subscription) {
50119
+ const intervalMs = Math.max(5e3, Number(subscription.params.intervalMs || 15e3));
50120
+ const now = Date.now();
50121
+ if (subscription.lastSentAt > 0 && now - subscription.lastSentAt < intervalMs) {
50122
+ return null;
50123
+ }
50124
+ subscription.seq += 1;
50125
+ subscription.lastSentAt = now;
50126
+ return {
50127
+ topic: "machine.runtime",
50128
+ key: subscription.key,
50129
+ machine: buildMachineInfo("full"),
50130
+ seq: subscription.seq,
50131
+ timestamp: now
50132
+ };
50133
+ }
50134
+ async flushP2PMachineRuntimeSubscriptions() {
50135
+ if (!this.p2p?.isConnected || !this.p2p.hasMachineRuntimeSubscriptions()) return;
50136
+ await this.p2p.flushMachineRuntimeSubscriptions(async (subscription) => this.buildMachineRuntimeUpdateForSubscription(subscription));
50137
+ }
50138
+ async buildSessionHostDiagnosticsUpdateForSubscription(subscription) {
50139
+ if (!this.sessionHostController) return null;
50140
+ const intervalMs = Math.max(5e3, Number(subscription.params.intervalMs || 1e4));
50141
+ const now = Date.now();
50142
+ if (subscription.lastSentAt > 0 && now - subscription.lastSentAt < intervalMs) {
50143
+ return null;
50144
+ }
50145
+ const diagnostics = await this.sessionHostController.getDiagnostics({
50146
+ includeSessions: subscription.params.includeSessions !== false,
50147
+ limit: Number(subscription.params.limit) || void 0
50148
+ });
50149
+ subscription.seq += 1;
50150
+ subscription.lastSentAt = now;
50151
+ return {
50152
+ topic: "session_host.diagnostics",
50153
+ key: subscription.key,
50154
+ diagnostics,
50155
+ seq: subscription.seq,
50156
+ timestamp: now
50157
+ };
50158
+ }
50159
+ async flushP2PSessionHostDiagnosticsSubscriptions() {
50160
+ if (!this.p2p?.isConnected || !this.p2p.hasSessionHostDiagnosticsSubscriptions()) return;
50161
+ await this.p2p.flushSessionHostDiagnosticsSubscriptions(
50162
+ async (subscription) => this.buildSessionHostDiagnosticsUpdateForSubscription(subscription)
50163
+ );
50164
+ }
50165
+ findProviderStateBySessionId(sessionId) {
50166
+ if (!this.components || !sessionId) return null;
50167
+ const states = this.components.instanceManager.collectAllStates();
50168
+ for (const state of states) {
50169
+ if (state.instanceId === sessionId) return state;
50170
+ if (state.category === "ide") {
50171
+ const child = state.extensions.find((entry) => entry.instanceId === sessionId);
50172
+ if (child) return child;
50173
+ }
50174
+ }
50175
+ return null;
50176
+ }
50177
+ buildSessionModalUpdateForSubscription(subscription) {
50178
+ const state = this.findProviderStateBySessionId(subscription.params.targetSessionId);
50179
+ if (!state) return null;
50180
+ const now = Date.now();
50181
+ const activeModal = state.activeChat?.activeModal;
50182
+ const modalButtons = Array.isArray(activeModal?.buttons) ? activeModal.buttons.filter((button) => typeof button === "string") : [];
50183
+ const status = String(state.activeChat?.status || state.status || "idle");
50184
+ const title = typeof state.activeChat?.title === "string" ? state.activeChat.title : void 0;
50185
+ const modalMessage = typeof activeModal?.message === "string" ? activeModal.message : void 0;
50186
+ const deliverySignature = buildSessionModalDeliverySignature({
50187
+ sessionId: subscription.params.targetSessionId,
50188
+ status,
50189
+ ...title ? { title } : {},
50190
+ ...modalMessage ? { modalMessage } : {},
50191
+ ...modalButtons.length > 0 ? { modalButtons } : {}
50192
+ });
50193
+ if (deliverySignature === subscription.lastDeliveredSignature) {
50194
+ return null;
50195
+ }
50196
+ subscription.lastDeliveredSignature = deliverySignature;
50197
+ subscription.seq += 1;
50198
+ subscription.lastSentAt = now;
50199
+ return {
50200
+ topic: "session.modal",
50201
+ key: subscription.key,
50202
+ sessionId: subscription.params.targetSessionId,
50203
+ status,
50204
+ ...title ? { title } : {},
50205
+ ...modalMessage ? { modalMessage } : {},
50206
+ ...modalButtons.length > 0 ? { modalButtons } : {},
50207
+ seq: subscription.seq,
50208
+ timestamp: now
50209
+ };
50210
+ }
50211
+ async flushP2PSessionModalSubscriptions() {
50212
+ if (!this.p2p?.isConnected || !this.p2p.hasSessionModalSubscriptions()) return;
50213
+ await this.p2p.flushSessionModalSubscriptions(
50214
+ async (subscription) => this.buildSessionModalUpdateForSubscription(subscription)
50215
+ );
50216
+ }
50217
+ buildDaemonMetadataSnapshot() {
50218
+ return buildStatusSnapshot({
50219
+ allStates: this.components.instanceManager.collectAllStates(),
50220
+ cdpManagers: this.components.cdpManagers,
50221
+ providerLoader: this.components.providerLoader,
50222
+ detectedIdes: this.components.detectedIdes.value.map((ide) => ({
50223
+ ...ide,
50224
+ path: ide.path ?? void 0
50225
+ })),
50226
+ instanceId: `daemon_${loadConfig().machineId || "daemon"}`,
50227
+ version: pkgVersion,
50228
+ profile: "metadata"
50229
+ });
50230
+ }
50231
+ buildDaemonMetadataUpdateForSubscription(subscription) {
50232
+ const now = Date.now();
50233
+ subscription.seq += 1;
50234
+ subscription.lastSentAt = now;
50235
+ return {
50236
+ topic: "daemon.metadata",
50237
+ key: subscription.key,
50238
+ daemonId: `daemon_${loadConfig().machineId || "daemon"}`,
50239
+ status: this.buildDaemonMetadataSnapshot(),
50240
+ seq: subscription.seq,
50241
+ timestamp: now
50242
+ };
50243
+ }
50244
+ async flushP2PDaemonMetadataSubscriptions() {
50245
+ if (!this.p2p?.isConnected || !this.p2p.hasDaemonMetadataSubscriptions()) return;
50246
+ await this.p2p.flushDaemonMetadataSubscriptions(
50247
+ async (subscription) => this.buildDaemonMetadataUpdateForSubscription(subscription)
50248
+ );
50249
+ }
49388
50250
  async start(options = {}) {
49389
50251
  installGlobalInterceptor();
49390
50252
  process.on("uncaughtException", (err) => {
@@ -49417,9 +50279,14 @@ ${err?.stack || ""}`);
49417
50279
  this.sessionHostEndpoint = sessionHostEndpoint;
49418
50280
  this.sessionHostController = new SessionHostController(
49419
50281
  sessionHostEndpoint,
49420
- (event) => this.broadcastLocalIpcMessage("daemon:session_host_event", event)
50282
+ (event) => {
50283
+ this.broadcastLocalIpcMessage("daemon:session_host_event", event);
50284
+ void this.flushP2PSessionHostDiagnosticsSubscriptions();
50285
+ void this.flushP2PSessionModalSubscriptions();
50286
+ }
49421
50287
  );
49422
50288
  await this.sessionHostController.start();
50289
+ const instanceId = `daemon_${config2.machineId}`;
49423
50290
  this.components = await initDaemonComponents({
49424
50291
  providerLogFn: LOG.forComponent("Provider").asLogFn(),
49425
50292
  cliManagerDeps: {
@@ -49452,6 +50319,8 @@ ${err?.stack || ""}`);
49452
50319
  listHostedCliRuntimes: async () => listHostedCliRuntimes2(sessionHostEndpoint)
49453
50320
  },
49454
50321
  enabledIdes: config2.enabledIdes,
50322
+ statusInstanceId: instanceId,
50323
+ statusVersion: pkgVersion,
49455
50324
  onStatusChange: () => this.statusReporter?.onStatusChange(),
49456
50325
  onPostChatCommand: () => {
49457
50326
  setTimeout(() => this.statusReporter?.throttledReport(), 1e3);
@@ -49477,7 +50346,6 @@ ${err?.stack || ""}`);
49477
50346
  }
49478
50347
  }).catch(() => {
49479
50348
  });
49480
- const instanceId = `daemon_${config2.machineId}`;
49481
50349
  this.serverConn = new ServerConnection({
49482
50350
  serverUrl: options.serverUrl || config2.serverUrl,
49483
50351
  token: authToken,
@@ -49527,14 +50395,21 @@ ${err?.stack || ""}`);
49527
50395
  found.adapter.resize(cols, rows);
49528
50396
  }
49529
50397
  });
50398
+ this.p2p.onSubscriptionChange(() => {
50399
+ void this.flushP2PChatSubscriptions();
50400
+ void this.flushP2PMachineRuntimeSubscriptions();
50401
+ void this.flushP2PSessionHostDiagnosticsSubscriptions();
50402
+ void this.flushP2PSessionModalSubscriptions();
50403
+ void this.flushP2PDaemonMetadataSubscriptions();
50404
+ });
49530
50405
  this.p2p.onStateChange((state) => {
49531
50406
  if (state === "connected") {
49532
50407
  LOG.info("P2P", "Peer connected \u2192 sending immediate full status report");
49533
50408
  this.statusReporter?.resetP2PHash();
49534
- this.statusReporter?.sendUnifiedStatusReport().catch((e) => LOG.warn("P2P", `Immediate status report failed: ${e?.message}`));
50409
+ this.statusReporter?.sendUnifiedStatusReport({ reason: "p2p-connect" }).catch((e) => LOG.warn("P2P", `Immediate status report failed: ${e?.message}`));
49535
50410
  setTimeout(() => {
49536
50411
  this.statusReporter?.resetP2PHash();
49537
- this.statusReporter?.sendUnifiedStatusReport().catch(() => {
50412
+ this.statusReporter?.sendUnifiedStatusReport({ reason: "p2p-connect-followup" }).catch(() => {
49538
50413
  });
49539
50414
  }, 2e3);
49540
50415
  }
@@ -49543,18 +50418,24 @@ ${err?.stack || ""}`);
49543
50418
  this.screenshotController = new ScreenshotController({
49544
50419
  isRunning: () => this.running,
49545
50420
  isScreenshotActive: () => this.p2p?.screenshotActive ?? false,
49546
- getScreenshotIdeType: () => this.p2p?.screenshotIdeType,
50421
+ getScreenshotTargetSessionId: () => this.p2p?.screenshotTargetSessionId,
49547
50422
  isUsingRelay: () => this.p2p?.isUsingRelay ?? false,
49548
50423
  hasAnyNeedingFirstFrame: () => this.p2p?.hasAnyNeedingFirstFrame() ?? false,
49549
- getCdp: (ideType) => {
49550
- if (ideType) return this.getCdpFor(ideType);
49551
- LOG.warn("P2P", "Screenshot requested without ideType \u2014 cannot determine target IDE. Skipping frame.");
50424
+ getCdp: (targetSessionId) => {
50425
+ if (targetSessionId) return this.getCdpFor(targetSessionId);
50426
+ LOG.warn("P2P", "Screenshot requested without targetSessionId \u2014 cannot determine target session. Skipping frame.");
49552
50427
  return null;
49553
50428
  },
49554
50429
  sendScreenshotBuffer: (buf) => this.p2p.sendScreenshotBuffer(buf)
49555
50430
  }, planLimits ?? void 0);
49556
50431
  this.screenshotController.start();
49557
50432
  this.p2p.onScreenshotStart(() => this.screenshotController?.triggerImmediate());
50433
+ this.topicSubscriptionTimer = setInterval(() => {
50434
+ void this.flushP2PChatSubscriptions({ onlyActive: true });
50435
+ void this.flushP2PMachineRuntimeSubscriptions();
50436
+ void this.flushP2PSessionHostDiagnosticsSubscriptions();
50437
+ void this.flushP2PSessionModalSubscriptions();
50438
+ }, 2500);
49558
50439
  } else {
49559
50440
  console.log(import_chalk2.default.gray(" \u26A0 P2P unavailable \u2014 using server relay"));
49560
50441
  }
@@ -49573,7 +50454,7 @@ ${err?.stack || ""}`);
49573
50454
  this.serverConn.onStateChange((state) => {
49574
50455
  if (state === "connected") {
49575
50456
  console.log(import_chalk2.default.green(" \u{1F4E1} Connected to ADHDev server"));
49576
- this.statusReporter?.sendUnifiedStatusReport();
50457
+ this.statusReporter?.sendUnifiedStatusReport({ forceServer: true, reason: "ws-connected" });
49577
50458
  } else if (state === "disconnected") {
49578
50459
  console.log(import_chalk2.default.yellow(" \u26A0 Server disconnected, will reconnect..."));
49579
50460
  }
@@ -49659,10 +50540,26 @@ ${err?.stack || ""}`);
49659
50540
  async handleCommand(msg, cmd, args) {
49660
50541
  LOG.info("Command", `${cmd}${args?.targetSessionId ? ` \u2192 session:${String(args.targetSessionId).split("_")[0]}` : ""}`);
49661
50542
  const cmdStart = Date.now();
49662
- const source = msg.ipcWs ? "ext" : "ws";
50543
+ const source = msg.ipcWs ? "ext" : typeof msg.source === "string" && msg.source.trim() ? msg.source : "ws";
49663
50544
  try {
50545
+ if (source === "api" && !loadConfig().allowServerApiProxy) {
50546
+ this.sendResult(msg, false, {
50547
+ error: "Server API relay is disabled on this daemon. Enable it explicitly with `adhdev daemon:api enable`.",
50548
+ code: "SERVER_API_DISABLED"
50549
+ });
50550
+ return;
50551
+ }
49664
50552
  const result = await this.components.router.execute(cmd, args, source);
49665
50553
  if (cmd.startsWith("workspace_")) this.statusReporter?.throttledReport();
50554
+ if (cmd === "get_status_metadata" || cmd.startsWith("workspace_") || cmd.startsWith("session_host_")) {
50555
+ void this.flushP2PDaemonMetadataSubscriptions();
50556
+ }
50557
+ if (cmd.startsWith("session_host_")) {
50558
+ void this.flushP2PSessionHostDiagnosticsSubscriptions();
50559
+ }
50560
+ if (cmd === "resolve_action" || cmd === "send_chat" || cmd === "read_chat") {
50561
+ void this.flushP2PSessionModalSubscriptions();
50562
+ }
49666
50563
  this.sendResult(msg, result.success, result);
49667
50564
  } catch (e) {
49668
50565
  console.error(import_chalk2.default.red(` \u2717 Command failed: ${e.message}`));
@@ -49702,6 +50599,7 @@ ${err?.stack || ""}`);
49702
50599
  const config2 = loadConfig();
49703
50600
  config2.machineNickname = nickname;
49704
50601
  saveConfig(config2);
50602
+ void this.flushP2PDaemonMetadataSubscriptions();
49705
50603
  logCommand({ ts: (/* @__PURE__ */ new Date()).toISOString(), cmd: cmdType, source: "p2p", args: { nickname }, success: true, durationMs: Date.now() - cmdStart });
49706
50604
  return { success: true, nickname };
49707
50605
  }
@@ -49714,6 +50612,15 @@ ${err?.stack || ""}`);
49714
50612
  }
49715
50613
  const routed = await this.components.router.execute(cmdType, data, "p2p");
49716
50614
  if (cmdType.startsWith("workspace_")) this.statusReporter?.throttledReport();
50615
+ if (cmdType === "get_status_metadata" || cmdType.startsWith("workspace_") || cmdType.startsWith("session_host_")) {
50616
+ void this.flushP2PDaemonMetadataSubscriptions();
50617
+ }
50618
+ if (cmdType.startsWith("session_host_")) {
50619
+ void this.flushP2PSessionHostDiagnosticsSubscriptions();
50620
+ }
50621
+ if (cmdType === "resolve_action" || cmdType === "send_chat" || cmdType === "read_chat") {
50622
+ void this.flushP2PSessionModalSubscriptions();
50623
+ }
49717
50624
  return routed;
49718
50625
  } catch (e) {
49719
50626
  logCommand({ ts: (/* @__PURE__ */ new Date()).toISOString(), cmd: cmdType, source: "p2p", success: false, error: e.message, durationMs: Date.now() - cmdStart });
@@ -49886,6 +50793,10 @@ ${err?.stack || ""}`);
49886
50793
  this.statusReporter.stopReporting();
49887
50794
  this.statusReporter = null;
49888
50795
  }
50796
+ if (this.topicSubscriptionTimer) {
50797
+ clearInterval(this.topicSubscriptionTimer);
50798
+ this.topicSubscriptionTimer = null;
50799
+ }
49889
50800
  if (this.components) {
49890
50801
  await shutdownDaemonComponents(this.components);
49891
50802
  }
@@ -51617,6 +52528,51 @@ function registerSetupCommands(program2, providerLoader) {
51617
52528
  console.log(import_chalk4.default.gray(" Auth credentials cleared. IDE and workspace settings preserved."));
51618
52529
  console.log(import_chalk4.default.gray(" Run `adhdev setup` to log in again.\n"));
51619
52530
  });
52531
+ program2.command("uninstall").description("Completely wipe all setting, configuration, stop daemon and uninstall service").option("-f, --force", "Skip confirmation prompt").action(async (options) => {
52532
+ const inquirer2 = await import("inquirer");
52533
+ const fs20 = await import("fs");
52534
+ const path29 = await import("path");
52535
+ const os26 = await import("os");
52536
+ const { spawnSync: spawnSync2 } = await import("child_process");
52537
+ if (!options.force) {
52538
+ const { confirm } = await inquirer2.default.prompt([
52539
+ {
52540
+ type: "confirm",
52541
+ name: "confirm",
52542
+ message: "Are you sure you want to completely wipe ADHDev settings, uninstall the daemon service, and remove all downloaded data? This cannot be undone.",
52543
+ default: false
52544
+ }
52545
+ ]);
52546
+ if (!confirm) return;
52547
+ }
52548
+ console.log(import_chalk4.default.bold("\n\u{1F5D1}\uFE0F Uninstalling ADHDev\n"));
52549
+ try {
52550
+ const { isDaemonRunning: isDaemonRunning2, stopDaemon: stopDaemon2 } = await Promise.resolve().then(() => (init_adhdev_daemon(), adhdev_daemon_exports));
52551
+ if (isDaemonRunning2()) {
52552
+ console.log(import_chalk4.default.gray(" Stopping daemon..."));
52553
+ stopDaemon2();
52554
+ console.log(import_chalk4.default.green(" \u2713 Daemon stopped."));
52555
+ }
52556
+ } catch {
52557
+ }
52558
+ console.log(import_chalk4.default.gray(" Removing OS background service..."));
52559
+ spawnSync2(process.execPath, [process.argv[1], "service", "uninstall"], { stdio: "inherit" });
52560
+ const adhdevDir = path29.join(os26.homedir(), ".adhdev");
52561
+ if (fs20.existsSync(adhdevDir)) {
52562
+ console.log(import_chalk4.default.gray(` Deleting ${adhdevDir}...`));
52563
+ try {
52564
+ fs20.rmSync(adhdevDir, { recursive: true, force: true });
52565
+ console.log(import_chalk4.default.green(" \u2713 Data wiped."));
52566
+ } catch (e) {
52567
+ console.log(import_chalk4.default.red(` \u2717 Failed to delete ~/.adhdev: ${e.message}`));
52568
+ }
52569
+ } else {
52570
+ console.log(import_chalk4.default.green(" \u2713 ~/.adhdev not found or already deleted."));
52571
+ }
52572
+ console.log(import_chalk4.default.green("\n\u2713 Uninstall complete."));
52573
+ console.log(import_chalk4.default.whiteBright(" To finish uninstalling the CLI tool, please run:"));
52574
+ console.log(import_chalk4.default.cyan(" npm uninstall -g adhdev\n"));
52575
+ });
51620
52576
  }
51621
52577
 
51622
52578
  // src/cli/daemon-commands.ts
@@ -51703,17 +52659,50 @@ function registerDaemonCommands(program2, pkgVersion3) {
51703
52659
  \u2717 Failed to start standalone server: ${err.message}`));
51704
52660
  });
51705
52661
  });
52662
+ hideCommand(program2.command("daemon:api [mode]").description("Manage server API relay access (disabled by default)").action(async (modeArg) => {
52663
+ const mode = typeof modeArg === "string" && modeArg.trim() ? modeArg.trim().toLowerCase() : "status";
52664
+ const { loadConfig: loadConfig2, saveConfig: saveConfig3 } = await Promise.resolve().then(() => (init_src(), src_exports));
52665
+ const config2 = loadConfig2();
52666
+ if (mode === "status") {
52667
+ const enabled2 = config2.allowServerApiProxy === true;
52668
+ console.log();
52669
+ console.log(import_chalk5.default.bold(" Server API Relay\n"));
52670
+ console.log(` ${import_chalk5.default.bold("Status:")} ${enabled2 ? import_chalk5.default.green("enabled") : import_chalk5.default.yellow("disabled")}`);
52671
+ console.log(import_chalk5.default.gray(" Applies only to server REST/API proxy routes."));
52672
+ console.log(import_chalk5.default.gray(" Dashboard traffic remains P2P/local IPC based.\n"));
52673
+ return;
52674
+ }
52675
+ if (mode !== "enable" && mode !== "disable") {
52676
+ console.log(import_chalk5.default.red(`
52677
+ \u2717 Unknown mode: ${mode}
52678
+ `));
52679
+ console.log(import_chalk5.default.gray(" Use: adhdev daemon:api [status|enable|disable]\n"));
52680
+ process.exitCode = 1;
52681
+ return;
52682
+ }
52683
+ const enabled = mode === "enable";
52684
+ config2.allowServerApiProxy = enabled;
52685
+ saveConfig3(config2);
52686
+ console.log();
52687
+ console.log(import_chalk5.default.bold(" Server API Relay\n"));
52688
+ console.log(` ${import_chalk5.default.bold("Status:")} ${enabled ? import_chalk5.default.green("enabled") : import_chalk5.default.yellow("disabled")}`);
52689
+ console.log(import_chalk5.default.gray(` Set with: adhdev daemon:api ${enabled ? "disable" : "enable"}
52690
+ `));
52691
+ }));
51706
52692
  hideCommand(program2.command("daemon:status").description("Check ADHDev Daemon status").option("-p, --port <port>", "Local WS server port", "19222").action(async (options) => {
51707
52693
  const { isDaemonRunning: isDaemonRunning2, getDaemonPid: getDaemonPid2 } = await Promise.resolve().then(() => (init_adhdev_daemon(), adhdev_daemon_exports));
51708
52694
  const { getCurrentDaemonLogPath: getCurrentDaemonLogPath2 } = await Promise.resolve().then(() => (init_src(), src_exports));
51709
52695
  const { probeSessionHostStatus: probeSessionHostStatus2 } = await Promise.resolve().then(() => (init_session_host(), session_host_exports));
51710
52696
  const port = parseInt(options.port, 10) || 19222;
52697
+ const { loadConfig: loadConfig2 } = await Promise.resolve().then(() => (init_src(), src_exports));
52698
+ const config2 = loadConfig2();
51711
52699
  if (isDaemonRunning2()) {
51712
52700
  console.log(import_chalk5.default.green(`
51713
52701
  \u2713 ADHDev Daemon is running.
51714
52702
  `));
51715
52703
  console.log(import_chalk5.default.gray(` PID: ${getDaemonPid2() ?? "unknown"}`));
51716
52704
  console.log(import_chalk5.default.gray(` Logs: ${getCurrentDaemonLogPath2()}`));
52705
+ console.log(import_chalk5.default.gray(` API Relay: ${config2.allowServerApiProxy ? "enabled" : "disabled"}`));
51717
52706
  const health = await fetchLocalDaemonHealth(port);
51718
52707
  if (health) {
51719
52708
  console.log(import_chalk5.default.gray(` Local IPC: reachable (ws://127.0.0.1:${health.port}${health.wsPath})`));
@@ -52159,6 +53148,36 @@ function probeNodeDatachannel(packageRoot) {
52159
53148
  };
52160
53149
  }
52161
53150
  }
53151
+ function probeSharpRuntime(packageRoot, nativeSharpPackage) {
53152
+ const resolved = resolveModuleFromPackage("sharp", packageRoot);
53153
+ if (!resolved) {
53154
+ return {
53155
+ label: "sharp",
53156
+ ok: false,
53157
+ detail: "module not found",
53158
+ fatal: false
53159
+ };
53160
+ }
53161
+ try {
53162
+ const sharp = require(resolved);
53163
+ const nativeResolved = resolveModuleFromPackage(nativeSharpPackage, packageRoot);
53164
+ const runtimeVersion = sharp?.versions?.sharp || sharp?.version || "unknown";
53165
+ const nativeDetail = nativeResolved ? `${nativeSharpPackage} resolved` : `${nativeSharpPackage} not separately resolvable`;
53166
+ return {
53167
+ label: "sharp",
53168
+ ok: typeof sharp === "function" || typeof sharp?.default === "function",
53169
+ detail: `${resolved} (runtime=${runtimeVersion}; ${nativeDetail})`,
53170
+ fatal: false
53171
+ };
53172
+ } catch (error48) {
53173
+ return {
53174
+ label: "sharp",
53175
+ ok: false,
53176
+ detail: `${resolved} (${error48?.message || "load failed"})`,
53177
+ fatal: false
53178
+ };
53179
+ }
53180
+ }
52162
53181
  function probeConfigAccess() {
52163
53182
  const configDir = path27.join(os24.homedir(), ".adhdev");
52164
53183
  const configPath = path27.join(configDir, "config.json");
@@ -52307,18 +53326,7 @@ function registerDoctorCommands(program2, pkgVersion3) {
52307
53326
  fatal: true
52308
53327
  },
52309
53328
  probeNodeDatachannel(packageRoot),
52310
- {
52311
- label: "sharp",
52312
- ok: Boolean(resolveModuleFromPackage("sharp", packageRoot)),
52313
- detail: resolveModuleFromPackage("sharp", packageRoot) || "module not found",
52314
- fatal: false
52315
- },
52316
- {
52317
- label: nativeSharpPackage,
52318
- ok: Boolean(resolveModuleFromPackage(nativeSharpPackage, packageRoot)),
52319
- detail: resolveModuleFromPackage(nativeSharpPackage, packageRoot) || "module not found",
52320
- fatal: false
52321
- },
53329
+ probeSharpRuntime(packageRoot, nativeSharpPackage),
52322
53330
  ...probeConfigAccess(),
52323
53331
  ...buildBrowseProbeChecks()
52324
53332
  ];
@@ -52333,7 +53341,7 @@ function registerDoctorCommands(program2, pkgVersion3) {
52333
53341
  checks.push({
52334
53342
  label: "Session host IPC",
52335
53343
  ok: probe.reachable,
52336
- detail: probe.reachable ? `reachable (${probe.runtimeCount} runtime(s), ${probe.endpoint.kind}:${probe.endpoint.path})` : `unreachable (${probe.endpoint.kind}:${probe.endpoint.path})`
53344
+ detail: probe.reachable ? `reachable (${probe.runtimeCount} runtime(s), ${probe.endpoint.kind}:${probe.endpoint.path})` : `${sessionHostPid ? "unreachable (stale PID or lazy host)" : "unreachable (host not running)"} (${probe.endpoint.kind}:${probe.endpoint.path})`
52337
53345
  });
52338
53346
  } catch (e) {
52339
53347
  checks.push({