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/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
  }
@@ -9357,8 +9571,6 @@ ${data.message || ""}`.trim();
9357
9571
  if (blockingModal || this.currentStatus === "waiting_approval") {
9358
9572
  throw new Error(`${this.cliName} is awaiting confirmation before it can accept a prompt`);
9359
9573
  }
9360
- this.committedMessages.push({ role: "user", content: text, timestamp: Date.now() });
9361
- this.syncMessageViews();
9362
9574
  this.isWaitingForResponse = true;
9363
9575
  this.responseBuffer = "";
9364
9576
  this.finishRetryCount = 0;
@@ -9390,6 +9602,13 @@ ${data.message || ""}`.trim();
9390
9602
  const submitDelayMs = this.sendDelayMs + Math.min(2e3, Math.max(0, estimatedLines - 1) * 350);
9391
9603
  const maxEchoWaitMs = submitDelayMs + Math.max(1500, Math.min(5e3, estimatedLines * 500));
9392
9604
  const retryDelayMs = Math.max(350, Math.min(1500, Math.max(this.sendDelayMs, submitDelayMs)));
9605
+ let didCommitUserTurn = false;
9606
+ const commitUserTurn = () => {
9607
+ if (didCommitUserTurn) return;
9608
+ didCommitUserTurn = true;
9609
+ this.committedMessages.push({ role: "user", content: text, timestamp: Date.now() });
9610
+ this.syncMessageViews();
9611
+ };
9393
9612
  if (this.settleTimer) {
9394
9613
  clearTimeout(this.settleTimer);
9395
9614
  this.settleTimer = null;
@@ -9402,109 +9621,128 @@ ${data.message || ""}`.trim();
9402
9621
  if (this.isWaitingForResponse) this.finishResponse();
9403
9622
  }, this.timeouts.maxResponse);
9404
9623
  };
9405
- const submit = () => {
9406
- if (!this.ptyProcess) return;
9407
- this.submitPendingUntil = 0;
9408
- this.recordTrace("submit_write", {
9409
- mode: "submit_key",
9410
- sendKey: this.sendKey,
9411
- screenText: summarizeCliTraceText(this.terminalScreen.getText(), 500)
9412
- });
9413
- this.ptyProcess.write(this.sendKey);
9414
- const retrySubmitIfStuck = (attempt) => {
9415
- this.submitRetryTimer = null;
9416
- if (!this.ptyProcess || !this.isWaitingForResponse || this.submitRetryUsed) return;
9417
- if (this.currentStatus === "waiting_approval") return;
9418
- if ((this.responseBuffer || "").trim()) return;
9624
+ await new Promise((resolve16) => {
9625
+ let resolved = false;
9626
+ const resolveOnce = () => {
9627
+ if (resolved) return;
9628
+ resolved = true;
9629
+ resolve16();
9630
+ };
9631
+ const submit = () => {
9632
+ if (!this.ptyProcess) {
9633
+ resolveOnce();
9634
+ return;
9635
+ }
9636
+ commitUserTurn();
9637
+ this.submitPendingUntil = 0;
9419
9638
  const screenText = this.terminalScreen.getText();
9420
- if (!promptLikelyVisible(screenText, normalizedPromptSnippet)) return;
9421
- 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;
9422
- this.responseSettleIgnoreUntil = Date.now() + this.timeouts.outputSettle + 400;
9423
- LOG.info("CLI", `[${this.cliType}] Retrying submit key for stuck prompt (attempt ${attempt})`);
9424
9639
  this.recordTrace("submit_write", {
9425
- mode: "submit_retry",
9426
- attempt,
9640
+ mode: "submit_key",
9427
9641
  sendKey: this.sendKey,
9428
9642
  screenText: summarizeCliTraceText(screenText, 500)
9429
9643
  });
9430
9644
  this.ptyProcess.write(this.sendKey);
9431
- if (attempt >= 3) {
9432
- this.submitRetryUsed = true;
9433
- return;
9434
- }
9435
- this.submitRetryTimer = setTimeout(() => retrySubmitIfStuck(attempt + 1), retryDelayMs);
9645
+ const retrySubmitIfStuck = (attempt) => {
9646
+ this.submitRetryTimer = null;
9647
+ if (!this.ptyProcess || !this.isWaitingForResponse || this.submitRetryUsed) return;
9648
+ if (this.currentStatus === "waiting_approval") return;
9649
+ if ((this.responseBuffer || "").trim()) return;
9650
+ const screenText2 = this.terminalScreen.getText();
9651
+ if (!promptLikelyVisible(screenText2, normalizedPromptSnippet)) return;
9652
+ 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;
9653
+ this.responseSettleIgnoreUntil = Date.now() + this.timeouts.outputSettle + 400;
9654
+ LOG.info("CLI", `[${this.cliType}] Retrying submit key for stuck prompt (attempt ${attempt})`);
9655
+ this.recordTrace("submit_write", {
9656
+ mode: "submit_retry",
9657
+ attempt,
9658
+ sendKey: this.sendKey,
9659
+ screenText: summarizeCliTraceText(screenText2, 500)
9660
+ });
9661
+ this.ptyProcess.write(this.sendKey);
9662
+ if (attempt >= 3) {
9663
+ this.submitRetryUsed = true;
9664
+ return;
9665
+ }
9666
+ this.submitRetryTimer = setTimeout(() => retrySubmitIfStuck(attempt + 1), retryDelayMs);
9667
+ };
9668
+ this.submitRetryTimer = setTimeout(() => retrySubmitIfStuck(1), retryDelayMs);
9669
+ startResponseTimeout();
9670
+ resolveOnce();
9436
9671
  };
9437
- this.submitRetryTimer = setTimeout(() => retrySubmitIfStuck(1), retryDelayMs);
9438
- startResponseTimeout();
9439
- };
9440
- if (this.submitStrategy === "immediate") {
9441
- this.submitPendingUntil = 0;
9672
+ if (this.submitStrategy === "immediate") {
9673
+ commitUserTurn();
9674
+ this.submitPendingUntil = 0;
9675
+ this.recordTrace("submit_write", {
9676
+ mode: "immediate",
9677
+ text: summarizeCliTraceText(text, 500),
9678
+ sendKey: this.sendKey,
9679
+ screenText: summarizeCliTraceText(this.terminalScreen.getText(), 500)
9680
+ });
9681
+ this.ptyProcess.write(text + this.sendKey);
9682
+ this.submitRetryTimer = setTimeout(() => {
9683
+ this.submitRetryTimer = null;
9684
+ if (!this.ptyProcess || !this.isWaitingForResponse || this.submitRetryUsed) return;
9685
+ if (this.currentStatus === "waiting_approval") return;
9686
+ if ((this.responseBuffer || "").trim()) return;
9687
+ const screenText = this.terminalScreen.getText();
9688
+ if (!promptLikelyVisible(screenText, normalizedPromptSnippet)) return;
9689
+ LOG.info("CLI", `[${this.cliType}] Retrying submit key for stuck prompt (attempt 1)`);
9690
+ this.responseSettleIgnoreUntil = Date.now() + this.timeouts.outputSettle + 400;
9691
+ this.recordTrace("submit_write", {
9692
+ mode: "immediate_retry",
9693
+ attempt: 1,
9694
+ sendKey: this.sendKey,
9695
+ screenText: summarizeCliTraceText(screenText, 500)
9696
+ });
9697
+ this.ptyProcess.write(this.sendKey);
9698
+ this.submitRetryUsed = true;
9699
+ }, retryDelayMs);
9700
+ startResponseTimeout();
9701
+ resolveOnce();
9702
+ return;
9703
+ }
9704
+ if (submitDelayMs > 0) {
9705
+ this.submitPendingUntil = Date.now() + submitDelayMs;
9706
+ }
9707
+ this.ptyProcess.write(text);
9442
9708
  this.recordTrace("submit_write", {
9443
- mode: "immediate",
9709
+ mode: "type_then_submit",
9444
9710
  text: summarizeCliTraceText(text, 500),
9445
9711
  sendKey: this.sendKey,
9446
9712
  screenText: summarizeCliTraceText(this.terminalScreen.getText(), 500)
9447
9713
  });
9448
- this.ptyProcess.write(text + this.sendKey);
9449
- this.submitRetryTimer = setTimeout(() => {
9450
- this.submitRetryTimer = null;
9451
- if (!this.ptyProcess || !this.isWaitingForResponse || this.submitRetryUsed) return;
9452
- if (this.currentStatus === "waiting_approval") return;
9453
- if ((this.responseBuffer || "").trim()) return;
9714
+ const submitStartedAt = Date.now();
9715
+ let lastNormalizedScreen = "";
9716
+ let lastScreenChangeAt = submitStartedAt;
9717
+ const waitForEchoAndSubmit = () => {
9718
+ if (!this.ptyProcess) {
9719
+ resolveOnce();
9720
+ return;
9721
+ }
9722
+ const now = Date.now();
9723
+ const elapsed = now - submitStartedAt;
9454
9724
  const screenText = this.terminalScreen.getText();
9455
- if (!promptLikelyVisible(screenText, normalizedPromptSnippet)) return;
9456
- LOG.info("CLI", `[${this.cliType}] Retrying submit key for stuck prompt (attempt 1)`);
9457
- this.responseSettleIgnoreUntil = Date.now() + this.timeouts.outputSettle + 400;
9458
- this.recordTrace("submit_write", {
9459
- mode: "immediate_retry",
9460
- attempt: 1,
9461
- sendKey: this.sendKey,
9462
- screenText: summarizeCliTraceText(screenText, 500)
9463
- });
9464
- this.ptyProcess.write(this.sendKey);
9465
- this.submitRetryUsed = true;
9466
- }, retryDelayMs);
9467
- startResponseTimeout();
9468
- return;
9469
- }
9470
- if (submitDelayMs > 0) {
9471
- this.submitPendingUntil = Date.now() + submitDelayMs;
9472
- }
9473
- this.ptyProcess.write(text);
9474
- this.recordTrace("submit_write", {
9475
- mode: "type_then_submit",
9476
- text: summarizeCliTraceText(text, 500),
9477
- sendKey: this.sendKey,
9478
- screenText: summarizeCliTraceText(this.terminalScreen.getText(), 500)
9479
- });
9480
- const submitStartedAt = Date.now();
9481
- let lastNormalizedScreen = "";
9482
- let lastScreenChangeAt = submitStartedAt;
9483
- const waitForEchoAndSubmit = () => {
9484
- if (!this.ptyProcess) return;
9485
- const now = Date.now();
9486
- const elapsed = now - submitStartedAt;
9487
- const screenText = this.terminalScreen.getText();
9488
- const normalizedScreen = normalizePromptText(screenText);
9489
- if (normalizedScreen !== lastNormalizedScreen) {
9490
- lastNormalizedScreen = normalizedScreen;
9491
- lastScreenChangeAt = now;
9492
- }
9493
- const echoVisible = !normalizedPromptSnippet || promptLikelyVisible(screenText, normalizedPromptSnippet);
9494
- if (echoVisible) {
9495
- const screenSettled = now - lastScreenChangeAt >= 500;
9496
- if (elapsed >= submitDelayMs && screenSettled) {
9725
+ const normalizedScreen = normalizePromptText(screenText);
9726
+ if (normalizedScreen !== lastNormalizedScreen) {
9727
+ lastNormalizedScreen = normalizedScreen;
9728
+ lastScreenChangeAt = now;
9729
+ }
9730
+ const echoVisible = !normalizedPromptSnippet || promptLikelyVisible(screenText, normalizedPromptSnippet);
9731
+ if (echoVisible) {
9732
+ const screenSettled = now - lastScreenChangeAt >= 500;
9733
+ if (elapsed >= submitDelayMs && screenSettled) {
9734
+ submit();
9735
+ return;
9736
+ }
9737
+ }
9738
+ if (elapsed >= maxEchoWaitMs) {
9497
9739
  submit();
9498
9740
  return;
9499
9741
  }
9500
- }
9501
- if (elapsed >= maxEchoWaitMs) {
9502
- submit();
9503
- return;
9504
- }
9505
- setTimeout(waitForEchoAndSubmit, 50);
9506
- };
9507
- waitForEchoAndSubmit();
9742
+ setTimeout(waitForEchoAndSubmit, 50);
9743
+ };
9744
+ waitForEchoAndSubmit();
9745
+ });
9508
9746
  }
9509
9747
  getPartialResponse() {
9510
9748
  if (!this.isWaitingForResponse) return "";
@@ -26810,17 +27048,6 @@ var init_acp = __esm({
26810
27048
  }
26811
27049
  });
26812
27050
 
26813
- // ../../oss/packages/daemon-core/src/providers/contracts.ts
26814
- function flattenContent(content) {
26815
- if (typeof content === "string") return content;
26816
- return content.filter((b) => b.type === "text").map((b) => b.text).join("\n");
26817
- }
26818
- var init_contracts = __esm({
26819
- "../../oss/packages/daemon-core/src/providers/contracts.ts"() {
26820
- "use strict";
26821
- }
26822
- });
26823
-
26824
27051
  // ../../oss/packages/daemon-core/src/providers/acp-provider-instance.ts
26825
27052
  var import_stream, import_child_process5, AcpProviderInstance;
26826
27053
  var init_acp_provider_instance = __esm({
@@ -31973,6 +32200,37 @@ function buildAvailableProviders(providerLoader) {
31973
32200
  ...provider.detectedPath !== void 0 ? { detectedPath: provider.detectedPath } : {}
31974
32201
  }));
31975
32202
  }
32203
+ function buildMachineInfo(profile = "full") {
32204
+ const base = {
32205
+ hostname: os17.hostname(),
32206
+ platform: os17.platform()
32207
+ };
32208
+ if (profile === "live") {
32209
+ return base;
32210
+ }
32211
+ if (profile === "metadata") {
32212
+ const memSnap2 = getHostMemorySnapshot();
32213
+ return {
32214
+ ...base,
32215
+ arch: os17.arch(),
32216
+ cpus: os17.cpus().length,
32217
+ totalMem: memSnap2.totalMem,
32218
+ release: os17.release()
32219
+ };
32220
+ }
32221
+ const memSnap = getHostMemorySnapshot();
32222
+ return {
32223
+ ...base,
32224
+ arch: os17.arch(),
32225
+ cpus: os17.cpus().length,
32226
+ totalMem: memSnap.totalMem,
32227
+ freeMem: memSnap.freeMem,
32228
+ availableMem: memSnap.availableMem,
32229
+ loadavg: os17.loadavg(),
32230
+ uptime: os17.uptime(),
32231
+ release: os17.release()
32232
+ };
32233
+ }
31976
32234
  function parseMessageTime(value) {
31977
32235
  if (typeof value === "number" && Number.isFinite(value)) return value;
31978
32236
  if (typeof value === "string") {
@@ -32028,26 +32286,35 @@ function buildRecentLaunches(recentActivity) {
32028
32286
  })).sort((a, b) => b.lastLaunchedAt - a.lastLaunchedAt).slice(0, 12);
32029
32287
  }
32030
32288
  function buildStatusSnapshot(options) {
32289
+ const profile = options.profile || "full";
32031
32290
  const cfg = loadConfig();
32032
32291
  const state = loadState();
32033
32292
  const wsState = getWorkspaceState(cfg);
32034
- const memSnap = getHostMemorySnapshot();
32035
32293
  const recentActivity = getRecentActivity(state, 20);
32036
- const sessions = buildSessionEntries(
32294
+ const unreadSourceSessions = buildSessionEntries(
32037
32295
  options.allStates,
32038
- options.cdpManagers
32296
+ options.cdpManagers,
32297
+ { profile: "full" }
32039
32298
  );
32040
- for (const session of sessions) {
32041
- const lastSeenAt = getSessionSeenAt(state, session.id);
32042
- const seenCompletionMarker = getSessionSeenMarker(state, session.id);
32043
- const lastUsedAt = getSessionLastUsedAt(session);
32044
- const completionMarker = getSessionCompletionMarker(session);
32045
- const { unread, inboxBucket } = session.surfaceHidden ? { unread: false, inboxBucket: "idle" } : getUnreadState(
32046
- getSessionMessageUpdatedAt(session) > 0,
32047
- session.status,
32299
+ const sessions = profile === "full" ? unreadSourceSessions : buildSessionEntries(
32300
+ options.allStates,
32301
+ options.cdpManagers,
32302
+ { profile }
32303
+ );
32304
+ const sessionsById = new Map(sessions.map((session) => [session.id, session]));
32305
+ for (const sourceSession of unreadSourceSessions) {
32306
+ const session = sessionsById.get(sourceSession.id);
32307
+ if (!session) continue;
32308
+ const lastSeenAt = getSessionSeenAt(state, sourceSession.id);
32309
+ const seenCompletionMarker = getSessionSeenMarker(state, sourceSession.id);
32310
+ const lastUsedAt = getSessionLastUsedAt(sourceSession);
32311
+ const completionMarker = getSessionCompletionMarker(sourceSession);
32312
+ const { unread, inboxBucket } = sourceSession.surfaceHidden ? { unread: false, inboxBucket: "idle" } : getUnreadState(
32313
+ getSessionMessageUpdatedAt(sourceSession) > 0,
32314
+ sourceSession.status,
32048
32315
  lastUsedAt,
32049
32316
  lastSeenAt,
32050
- getLastMessageRole(session),
32317
+ getLastMessageRole(sourceSession),
32051
32318
  completionMarker,
32052
32319
  seenCompletionMarker
32053
32320
  );
@@ -32057,39 +32324,30 @@ function buildStatusSnapshot(options) {
32057
32324
  if (READ_DEBUG_ENABLED && (session.unread || session.inboxBucket !== "idle" || session.providerType.includes("codex"))) {
32058
32325
  LOG.info(
32059
32326
  "RecentRead",
32060
- `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)}`
32327
+ `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)}`
32061
32328
  );
32062
32329
  }
32063
32330
  }
32064
- const terminalBackend = getTerminalBackendRuntimeStatus();
32331
+ const includeMachineMetadata = profile !== "live";
32332
+ const terminalBackend = includeMachineMetadata ? getTerminalBackendRuntimeStatus() : void 0;
32065
32333
  return {
32066
32334
  instanceId: options.instanceId,
32067
- version: options.version,
32068
- daemonMode: options.daemonMode,
32069
- machine: {
32070
- hostname: os17.hostname(),
32071
- platform: os17.platform(),
32072
- arch: os17.arch(),
32073
- cpus: os17.cpus().length,
32074
- totalMem: memSnap.totalMem,
32075
- freeMem: memSnap.freeMem,
32076
- availableMem: memSnap.availableMem,
32077
- loadavg: os17.loadavg(),
32078
- uptime: os17.uptime(),
32079
- release: os17.release()
32080
- },
32081
- machineNickname: options.machineNickname ?? cfg.machineNickname ?? null,
32335
+ ...includeMachineMetadata ? { version: options.version } : {},
32336
+ machine: buildMachineInfo(profile),
32337
+ ...includeMachineMetadata ? { machineNickname: options.machineNickname ?? cfg.machineNickname ?? null } : {},
32082
32338
  timestamp: options.timestamp ?? Date.now(),
32083
- detectedIdes: buildDetectedIdeInfos(options.detectedIdes, options.cdpManagers),
32084
32339
  ...options.p2p ? { p2p: options.p2p } : {},
32085
32340
  sessions,
32086
- workspaces: wsState.workspaces,
32087
- defaultWorkspaceId: wsState.defaultWorkspaceId,
32088
- defaultWorkspacePath: wsState.defaultWorkspacePath,
32089
- terminalSizingMode: cfg.terminalSizingMode || "measured",
32090
- recentLaunches: buildRecentLaunches(recentActivity),
32091
- terminalBackend,
32092
- availableProviders: buildAvailableProviders(options.providerLoader)
32341
+ ...terminalBackend ? { terminalBackend } : {},
32342
+ ...includeMachineMetadata && {
32343
+ detectedIdes: buildDetectedIdeInfos(options.detectedIdes, options.cdpManagers),
32344
+ workspaces: wsState.workspaces,
32345
+ defaultWorkspaceId: wsState.defaultWorkspaceId,
32346
+ defaultWorkspacePath: wsState.defaultWorkspacePath,
32347
+ terminalSizingMode: cfg.terminalSizingMode || "measured",
32348
+ recentLaunches: buildRecentLaunches(recentActivity),
32349
+ availableProviders: buildAvailableProviders(options.providerLoader)
32350
+ }
32093
32351
  };
32094
32352
  }
32095
32353
  var os17, READ_DEBUG_ENABLED;
@@ -32360,6 +32618,7 @@ var init_router = __esm({
32360
32618
  init_logger();
32361
32619
  init_builders();
32362
32620
  init_snapshot();
32621
+ init_snapshot();
32363
32622
  init_upgrade_helper();
32364
32623
  fs9 = __toESM(require("fs"));
32365
32624
  CHAT_COMMANDS = [
@@ -32688,6 +32947,25 @@ var init_router = __esm({
32688
32947
  updateConfig({ userName: name });
32689
32948
  return { success: true, userName: name };
32690
32949
  }
32950
+ case "get_status_metadata": {
32951
+ const snapshot = buildStatusSnapshot({
32952
+ allStates: this.deps.instanceManager.collectAllStates(),
32953
+ cdpManagers: this.deps.cdpManagers,
32954
+ providerLoader: this.deps.providerLoader,
32955
+ detectedIdes: this.deps.detectedIdes.value,
32956
+ instanceId: this.deps.statusInstanceId || loadConfig().machineId || "daemon",
32957
+ version: this.deps.statusVersion || "unknown",
32958
+ profile: "metadata"
32959
+ });
32960
+ return { success: true, status: snapshot };
32961
+ }
32962
+ case "get_machine_runtime_stats": {
32963
+ return {
32964
+ success: true,
32965
+ machine: buildMachineInfo("full"),
32966
+ timestamp: Date.now()
32967
+ };
32968
+ }
32691
32969
  case "mark_session_seen": {
32692
32970
  const sessionId = args?.sessionId;
32693
32971
  if (!sessionId || typeof sessionId !== "string") {
@@ -32849,6 +33127,7 @@ var init_reporter = __esm({
32849
33127
  lastStatusSentAt = 0;
32850
33128
  statusPendingThrottle = false;
32851
33129
  lastP2PStatusHash = "";
33130
+ lastServerStatusHash = "";
32852
33131
  lastStatusSummary = "";
32853
33132
  statusTimer = null;
32854
33133
  p2pTimer = null;
@@ -32859,11 +33138,11 @@ var init_reporter = __esm({
32859
33138
  // ─── Lifecycle ───────────────────────────────────
32860
33139
  startReporting() {
32861
33140
  setTimeout(() => {
32862
- this.sendUnifiedStatusReport().catch((e) => LOG.warn("Status", `Initial report failed: ${e?.message}`));
33141
+ this.sendUnifiedStatusReport({ forceServer: true, reason: "initial" }).catch((e) => LOG.warn("Status", `Initial report failed: ${e?.message}`));
32863
33142
  }, 2e3);
32864
33143
  const scheduleServerReport = () => {
32865
33144
  this.statusTimer = setTimeout(() => {
32866
- this.sendUnifiedStatusReport().catch((e) => LOG.warn("Status", `Periodic report failed: ${e?.message}`));
33145
+ this.sendUnifiedStatusReport({ forceServer: true, reason: "periodic" }).catch((e) => LOG.warn("Status", `Periodic report failed: ${e?.message}`));
32867
33146
  scheduleServerReport();
32868
33147
  }, 3e4);
32869
33148
  };
@@ -32900,9 +33179,58 @@ var init_reporter = __esm({
32900
33179
  }, 5e3 - elapsed);
32901
33180
  }
32902
33181
  }
33182
+ toDaemonStatusEventName(value) {
33183
+ switch (value) {
33184
+ case "agent:generating_started":
33185
+ case "agent:waiting_approval":
33186
+ case "agent:generating_completed":
33187
+ case "agent:stopped":
33188
+ case "monitor:long_generating":
33189
+ return value;
33190
+ default:
33191
+ return null;
33192
+ }
33193
+ }
33194
+ buildServerStatusEvent(event) {
33195
+ const eventName = this.toDaemonStatusEventName(event.event);
33196
+ if (!eventName) return null;
33197
+ if (eventName.startsWith("provider:")) {
33198
+ return null;
33199
+ }
33200
+ const payload = {
33201
+ event: eventName,
33202
+ timestamp: typeof event.timestamp === "number" && Number.isFinite(event.timestamp) ? event.timestamp : Date.now()
33203
+ };
33204
+ if (typeof event.targetSessionId === "string" && event.targetSessionId.trim()) {
33205
+ payload.targetSessionId = event.targetSessionId.trim();
33206
+ }
33207
+ const providerType = typeof event.providerType === "string" && event.providerType.trim() ? event.providerType.trim() : typeof event.ideType === "string" && event.ideType.trim() ? event.ideType.trim() : "";
33208
+ if (providerType) {
33209
+ payload.providerType = providerType;
33210
+ }
33211
+ if (typeof event.duration === "number" && Number.isFinite(event.duration)) {
33212
+ payload.duration = event.duration;
33213
+ }
33214
+ if (typeof event.elapsedSec === "number" && Number.isFinite(event.elapsedSec)) {
33215
+ payload.elapsedSec = event.elapsedSec;
33216
+ }
33217
+ if (typeof event.modalMessage === "string" && event.modalMessage.trim()) {
33218
+ payload.modalMessage = event.modalMessage;
33219
+ }
33220
+ if (Array.isArray(event.modalButtons)) {
33221
+ const modalButtons = event.modalButtons.filter((button) => typeof button === "string" && button.trim().length > 0);
33222
+ if (modalButtons.length > 0) {
33223
+ payload.modalButtons = modalButtons;
33224
+ }
33225
+ }
33226
+ return payload;
33227
+ }
32903
33228
  emitStatusEvent(event) {
32904
33229
  LOG.info("StatusEvent", `${event.event} (${event.providerType || event.ideType || ""})`);
32905
- this.deps.serverConn?.sendMessage("status_event", event);
33230
+ const serverEvent = this.buildServerStatusEvent(event);
33231
+ if (!serverEvent) return;
33232
+ this.deps.p2p?.sendStatusEvent(serverEvent);
33233
+ this.deps.serverConn?.sendMessage("status_event", serverEvent);
32906
33234
  }
32907
33235
  removeAgentTracking(_key) {
32908
33236
  }
@@ -32971,17 +33299,16 @@ var init_reporter = __esm({
32971
33299
  detectedIdes: this.deps.detectedIdes || [],
32972
33300
  instanceId: this.deps.instanceId,
32973
33301
  version: this.deps.daemonVersion || "unknown",
32974
- daemonMode: true,
32975
33302
  timestamp: now,
32976
33303
  p2p: {
32977
33304
  available: p2p?.isAvailable || false,
32978
33305
  state: p2p?.connectionState || "unavailable",
32979
33306
  peers: p2p?.connectedPeerCount || 0,
32980
33307
  screenshotActive: p2p?.screenshotActive || false
32981
- }
33308
+ },
33309
+ profile: "live"
32982
33310
  }),
32983
- screenshotUsage: this.deps.getScreenshotUsage?.() || null,
32984
- connectedExtensions: []
33311
+ screenshotUsage: this.deps.getScreenshotUsage?.() || null
32985
33312
  };
32986
33313
  const payloadBytes = JSON.stringify(payload).length;
32987
33314
  const p2pSent = this.sendP2PPayload(payload);
@@ -32996,46 +33323,48 @@ var init_reporter = __esm({
32996
33323
  }
32997
33324
  if (opts?.p2pOnly) return;
32998
33325
  const wsPayload = {
32999
- daemonMode: true,
33000
33326
  sessions: sessions.map((session) => ({
33001
33327
  id: session.id,
33002
33328
  parentId: session.parentId,
33003
33329
  providerType: session.providerType,
33004
- providerName: session.providerName,
33330
+ providerName: session.providerName || session.providerType,
33005
33331
  kind: session.kind,
33006
33332
  transport: session.transport,
33007
33333
  status: session.status,
33008
- workspace: session.workspace,
33334
+ workspace: session.workspace ?? null,
33009
33335
  title: session.title,
33010
33336
  cdpConnected: session.cdpConnected,
33011
33337
  currentModel: session.currentModel,
33012
33338
  currentPlan: session.currentPlan,
33013
- currentAutoApprove: session.currentAutoApprove,
33014
- lastUpdated: session.lastUpdated,
33015
- unread: session.unread,
33016
- lastSeenAt: session.lastSeenAt,
33017
- inboxBucket: session.inboxBucket,
33018
- surfaceHidden: session.surfaceHidden,
33019
- controlValues: session.controlValues,
33020
- providerControls: session.providerControls,
33021
- acpConfigOptions: session.acpConfigOptions,
33022
- acpModes: session.acpModes
33339
+ currentAutoApprove: session.currentAutoApprove
33023
33340
  })),
33024
33341
  p2p: payload.p2p,
33025
- timestamp: now,
33026
- detectedIdes: payload.detectedIdes,
33027
- availableProviders: payload.availableProviders
33342
+ timestamp: now
33028
33343
  };
33344
+ const wsHash = this.simpleHash(JSON.stringify({
33345
+ ...wsPayload,
33346
+ timestamp: void 0
33347
+ }));
33348
+ if (!opts?.forceServer && wsHash === this.lastServerStatusHash) {
33349
+ LOG.debug("Server", `skip duplicate status_report${opts?.reason ? ` (${opts.reason})` : ""}`);
33350
+ return;
33351
+ }
33352
+ this.lastServerStatusHash = wsHash;
33029
33353
  serverConn.sendMessage("status_report", wsPayload);
33030
- LOG.debug("Server", `sent status_report (${JSON.stringify(wsPayload).length} bytes)`);
33354
+ LOG.debug("Server", `sent status_report (${JSON.stringify(wsPayload).length} bytes)${opts?.reason ? ` [${opts.reason}]` : ""}`);
33031
33355
  }
33032
33356
  // ─── P2P ─────────────────────────────────────────
33033
33357
  sendP2PPayload(payload) {
33034
33358
  const { timestamp: _ts, system: _sys, ...hashTarget } = payload;
33359
+ const sessions = Array.isArray(hashTarget.sessions) ? hashTarget.sessions.map((session) => {
33360
+ if (!session || typeof session !== "object") return session;
33361
+ const { lastUpdated: _lu, ...stableSession } = session;
33362
+ return stableSession;
33363
+ }) : hashTarget.sessions;
33035
33364
  const hashPayload = hashTarget.machine ? (() => {
33036
33365
  const { freeMem: _f, availableMem: _a2, loadavg: _l, uptime: _u, ...stableMachine } = hashTarget.machine;
33037
- return { ...hashTarget, machine: stableMachine };
33038
- })() : hashTarget;
33366
+ return { ...hashTarget, sessions, machine: stableMachine };
33367
+ })() : { ...hashTarget, sessions };
33039
33368
  const h = this.simpleHash(JSON.stringify(hashPayload));
33040
33369
  if (h !== this.lastP2PStatusHash) {
33041
33370
  this.lastP2PStatusHash = h;
@@ -40246,6 +40575,8 @@ async function initDaemonComponents(config2) {
40246
40575
  onStatusChange: config2.onStatusChange,
40247
40576
  onPostChatCommand: config2.onPostChatCommand,
40248
40577
  sessionHostControl: config2.sessionHostControl,
40578
+ statusInstanceId: config2.statusInstanceId,
40579
+ statusVersion: config2.statusVersion,
40249
40580
  getCdpLogFn: config2.getCdpLogFn || ((ideType) => LOG.forComponent(`CDP:${ideType}`).asLogFn())
40250
40581
  });
40251
40582
  poller = new AgentStreamPoller({
@@ -40371,6 +40702,7 @@ __export(src_exports, {
40371
40702
  SessionHostPtyTransportFactory: () => SessionHostPtyTransportFactory,
40372
40703
  VersionArchive: () => VersionArchive,
40373
40704
  appendRecentActivity: () => appendRecentActivity,
40705
+ buildMachineInfo: () => buildMachineInfo,
40374
40706
  buildSessionEntries: () => buildSessionEntries,
40375
40707
  buildStatusSnapshot: () => buildStatusSnapshot,
40376
40708
  connectCdpManager: () => connectCdpManager,
@@ -40493,6 +40825,7 @@ var init_server_connection = __esm({
40493
40825
  reconnectTimer = null;
40494
40826
  pingTimer = null;
40495
40827
  pongTimeout = null;
40828
+ missedPongCount = 0;
40496
40829
  messageHandlers = /* @__PURE__ */ new Map();
40497
40830
  stateChangeCallbacks = [];
40498
40831
  /** Fallback handler for message types without specific on() registration */
@@ -40513,6 +40846,7 @@ var init_server_connection = __esm({
40513
40846
  this.ws = new import_ws2.default(fullUrl, {
40514
40847
  headers: {
40515
40848
  "X-ADHDev-Token": this.options.token,
40849
+ "X-ADHDev-Daemon": JSON.stringify(this.options.cliInfo),
40516
40850
  "X-ADHDev-IDE": JSON.stringify(this.options.cliInfo),
40517
40851
  "X-ADHDev-Version": this.options.daemonVersion || "unknown"
40518
40852
  }
@@ -40591,18 +40925,21 @@ var init_server_connection = __esm({
40591
40925
  // --- Private ---
40592
40926
  onOpen() {
40593
40927
  LOG.info("Server", `[ServerConn] WebSocket open, sending auth...`);
40928
+ this.missedPongCount = 0;
40594
40929
  this.setState("authenticating");
40595
40930
  this.send({
40596
40931
  type: "auth",
40597
40932
  payload: {
40598
40933
  token: this.options.token,
40599
- ide: this.options.cliInfo
40934
+ daemon: this.options.cliInfo
40600
40935
  },
40601
40936
  timestamp: Date.now()
40602
40937
  });
40603
40938
  }
40604
40939
  onMessage(text) {
40605
40940
  try {
40941
+ this.clearPongTimeout();
40942
+ this.missedPongCount = 0;
40606
40943
  const message = JSON.parse(text);
40607
40944
  if (message.type === "auth_ok") {
40608
40945
  this.reconnectAttempts = 0;
@@ -40703,6 +41040,7 @@ var init_server_connection = __esm({
40703
41040
  this.reconnectTimer = null;
40704
41041
  }
40705
41042
  this.stopHeartbeat();
41043
+ this.missedPongCount = 0;
40706
41044
  }
40707
41045
  // ─── WS Heartbeat (ping/pong) ─────────────────────
40708
41046
  startHeartbeat() {
@@ -40712,9 +41050,14 @@ var init_server_connection = __esm({
40712
41050
  try {
40713
41051
  this.ws.ping();
40714
41052
  this.pongTimeout = setTimeout(() => {
40715
- LOG.info("Server", "[ServerConn] \u26A0 Pong timeout (15s) \u2014 closing zombie WS");
40716
- if (this.ws) {
41053
+ this.pongTimeout = null;
41054
+ this.missedPongCount += 1;
41055
+ const misses = this.missedPongCount;
41056
+ if (misses >= 3 && this.ws) {
41057
+ LOG.warn("Server", `[ServerConn] Pong timeout (${misses}x) \u2014 closing stale WS`);
40717
41058
  this.ws.terminate();
41059
+ } else {
41060
+ LOG.warn("Server", `[ServerConn] Pong timeout (${misses}x) \u2014 keeping WS open and retrying`);
40718
41061
  }
40719
41062
  }, 15e3);
40720
41063
  } catch {
@@ -40726,12 +41069,13 @@ var init_server_connection = __esm({
40726
41069
  clearInterval(this.pingTimer);
40727
41070
  this.pingTimer = null;
40728
41071
  }
40729
- if (this.pongTimeout) {
40730
- clearTimeout(this.pongTimeout);
40731
- this.pongTimeout = null;
40732
- }
41072
+ this.clearPongTimeout();
40733
41073
  }
40734
41074
  onPong() {
41075
+ this.clearPongTimeout();
41076
+ this.missedPongCount = 0;
41077
+ }
41078
+ clearPongTimeout() {
40735
41079
  if (this.pongTimeout) {
40736
41080
  clearTimeout(this.pongTimeout);
40737
41081
  this.pongTimeout = null;
@@ -40827,11 +41171,19 @@ function routeDataChannelMessage(peerId, msg, peers, handlers) {
40827
41171
  if (peer) peer.lastPongAt = Date.now();
40828
41172
  return;
40829
41173
  }
41174
+ if (parsed.type === "subscribe") {
41175
+ handleSubscribe(peerId, parsed, peers, handlers);
41176
+ return;
41177
+ }
41178
+ if (parsed.type === "unsubscribe") {
41179
+ handleUnsubscribe(peerId, parsed, peers);
41180
+ return;
41181
+ }
40830
41182
  if (parsed.type === "screenshot_start") {
40831
41183
  const peer = peers.get(peerId);
40832
41184
  const permission = peer?.sharePermission;
40833
- if (!parsed.ideType) {
40834
- log(`screenshot_start: REJECTED \u2014 no ideType from peer ${peerId}`);
41185
+ if (!parsed.targetSessionId) {
41186
+ log(`screenshot_start: REJECTED \u2014 no targetSessionId from peer ${peerId}`);
40835
41187
  return;
40836
41188
  }
40837
41189
  if (!canPeerStartScreenshots(permission)) {
@@ -40840,9 +41192,9 @@ function routeDataChannelMessage(peerId, msg, peers, handlers) {
40840
41192
  }
40841
41193
  if (peer) {
40842
41194
  peer.screenshotActive = true;
40843
- peer.screenshotIdeType = parsed.ideType;
41195
+ peer.screenshotTargetSessionId = parsed.targetSessionId;
40844
41196
  peer.needsFirstFrame = true;
40845
- log(`screenshot_start: peer=${peerId}, ideType=${parsed.ideType}, channelOpen=${!!peer.dataChannel}, state=${peer.state}`);
41197
+ log(`screenshot_start: peer=${peerId}, targetSessionId=${parsed.targetSessionId}, channelOpen=${!!peer.dataChannel}, state=${peer.state}`);
40846
41198
  handlers.screenshotStartHandler?.();
40847
41199
  } else {
40848
41200
  log(`screenshot_start: peer ${peerId} NOT FOUND in peers map!`);
@@ -40894,6 +41246,87 @@ function routeDataChannelMessage(peerId, msg, peers, handlers) {
40894
41246
  log(`Parse error from peer ${peerId}: ${e?.message}`);
40895
41247
  }
40896
41248
  }
41249
+ function handleSubscribe(peerId, msg, peers, handlers) {
41250
+ const peer = peers.get(peerId);
41251
+ if (!peer) return;
41252
+ if (msg.topic === "session.chat_tail") {
41253
+ const targetSessionId = typeof msg.params?.targetSessionId === "string" ? msg.params.targetSessionId.trim() : "";
41254
+ if (!targetSessionId) return;
41255
+ if (!peer.chatSubscriptions) peer.chatSubscriptions = /* @__PURE__ */ new Map();
41256
+ peer.chatSubscriptions.set(msg.key, {
41257
+ key: msg.key,
41258
+ params: msg.params,
41259
+ seq: 0,
41260
+ cursor: {
41261
+ knownMessageCount: Math.max(0, Number(msg.params.knownMessageCount || 0)),
41262
+ lastMessageSignature: typeof msg.params.lastMessageSignature === "string" ? msg.params.lastMessageSignature : "",
41263
+ tailLimit: Math.max(0, Number(msg.params.tailLimit || 0))
41264
+ },
41265
+ lastDeliveredSignature: ""
41266
+ });
41267
+ } else if (msg.topic === "machine.runtime") {
41268
+ if (!peer.machineRuntimeSubscriptions) peer.machineRuntimeSubscriptions = /* @__PURE__ */ new Map();
41269
+ peer.machineRuntimeSubscriptions.set(msg.key, {
41270
+ key: msg.key,
41271
+ params: msg.params,
41272
+ seq: 0,
41273
+ lastSentAt: 0
41274
+ });
41275
+ } else if (msg.topic === "session_host.diagnostics") {
41276
+ if (!peer.sessionHostDiagnosticsSubscriptions) peer.sessionHostDiagnosticsSubscriptions = /* @__PURE__ */ new Map();
41277
+ peer.sessionHostDiagnosticsSubscriptions.set(msg.key, {
41278
+ key: msg.key,
41279
+ params: msg.params,
41280
+ seq: 0,
41281
+ lastSentAt: 0
41282
+ });
41283
+ } else if (msg.topic === "session.modal") {
41284
+ const targetSessionId = typeof msg.params?.targetSessionId === "string" ? msg.params.targetSessionId.trim() : "";
41285
+ if (!targetSessionId) return;
41286
+ if (!peer.sessionModalSubscriptions) peer.sessionModalSubscriptions = /* @__PURE__ */ new Map();
41287
+ peer.sessionModalSubscriptions.set(msg.key, {
41288
+ key: msg.key,
41289
+ params: msg.params,
41290
+ seq: 0,
41291
+ lastSentAt: 0,
41292
+ lastDeliveredSignature: ""
41293
+ });
41294
+ } else if (msg.topic === "daemon.metadata") {
41295
+ if (!peer.daemonMetadataSubscriptions) peer.daemonMetadataSubscriptions = /* @__PURE__ */ new Map();
41296
+ peer.daemonMetadataSubscriptions.set(msg.key, {
41297
+ key: msg.key,
41298
+ params: msg.params,
41299
+ seq: 0,
41300
+ lastSentAt: 0
41301
+ });
41302
+ } else {
41303
+ return;
41304
+ }
41305
+ handlers.subscriptionChangeHandler?.();
41306
+ }
41307
+ function handleUnsubscribe(peerId, msg, peers) {
41308
+ const peer = peers.get(peerId);
41309
+ if (!peer) return;
41310
+ if (msg.topic === "session.chat_tail") {
41311
+ peer.chatSubscriptions?.delete(msg.key);
41312
+ return;
41313
+ }
41314
+ if (msg.topic === "machine.runtime") {
41315
+ peer.machineRuntimeSubscriptions?.delete(msg.key);
41316
+ return;
41317
+ }
41318
+ if (msg.topic === "session_host.diagnostics") {
41319
+ peer.sessionHostDiagnosticsSubscriptions?.delete(msg.key);
41320
+ return;
41321
+ }
41322
+ if (msg.topic === "session.modal") {
41323
+ peer.sessionModalSubscriptions?.delete(msg.key);
41324
+ return;
41325
+ }
41326
+ if (msg.topic === "daemon.metadata") {
41327
+ peer.daemonMetadataSubscriptions?.delete(msg.key);
41328
+ }
41329
+ }
40897
41330
  async function handleP2PCommand(peerId, msg, peers, handlers) {
40898
41331
  const { id, commandType, data } = msg;
40899
41332
  const peer = peers.get(peerId);
@@ -40914,7 +41347,7 @@ async function handleP2PCommand(peerId, msg, peers, handlers) {
40914
41347
  }
40915
41348
  }
40916
41349
  async function handleInputEvent(peerId, msg, peers, handlers) {
40917
- const { id, action, params, instanceId } = msg;
41350
+ const { id, action, params, targetSessionId: explicitTargetSessionId } = msg;
40918
41351
  const peer = peers.get(peerId);
40919
41352
  const permission = peer?.sharePermission;
40920
41353
  if (!canPeerUseRemoteInput(permission)) {
@@ -40926,11 +41359,11 @@ async function handleInputEvent(peerId, msg, peers, handlers) {
40926
41359
  return;
40927
41360
  }
40928
41361
  try {
40929
- const ideType = instanceId || peer?.screenshotIdeType;
40930
- if (!ideType) {
40931
- log(`[Input] WARNING: No instanceId or screenshotIdeType for peer ${peerId}`);
41362
+ const targetSessionId = explicitTargetSessionId || peer?.screenshotTargetSessionId;
41363
+ if (!targetSessionId) {
41364
+ log(`[Input] WARNING: No targetSessionId for peer ${peerId}`);
40932
41365
  }
40933
- const result = await handlers.inputHandler({ action, params, ideType });
41366
+ const result = await handlers.inputHandler({ action, params, targetSessionId });
40934
41367
  sendToPeer(peer, { id, type: "response", success: true, result });
40935
41368
  } catch (e) {
40936
41369
  sendToPeer(peer, { id, type: "response", success: false, error: e?.message });
@@ -40989,6 +41422,35 @@ var init_screenshot_sender = __esm({
40989
41422
  }
40990
41423
  return sentAny;
40991
41424
  }
41425
+ sendStatusEvent(peers, event) {
41426
+ const payload = JSON.stringify({
41427
+ type: "status_event",
41428
+ payload: event,
41429
+ timestamp: Date.now()
41430
+ });
41431
+ let sentAny = false;
41432
+ for (const peer of peers.values()) {
41433
+ if (peer.state !== "connected" || !peer.dataChannel) continue;
41434
+ try {
41435
+ peer.dataChannel.sendMessage(payload);
41436
+ sentAny = true;
41437
+ } catch {
41438
+ }
41439
+ }
41440
+ return sentAny;
41441
+ }
41442
+ sendTopicUpdateToPeer(peer, update) {
41443
+ if (!peer?.dataChannel || peer.state !== "connected") return false;
41444
+ try {
41445
+ peer.dataChannel.sendMessage(JSON.stringify({
41446
+ type: "topic_update",
41447
+ update
41448
+ }));
41449
+ return true;
41450
+ } catch {
41451
+ return false;
41452
+ }
41453
+ }
40992
41454
  /** Broadcast runtime session output to all connected peers */
40993
41455
  broadcastSessionOutput(peers, sessionId, data) {
40994
41456
  const msg = JSON.stringify({ type: "session_output", sessionId, data });
@@ -41081,8 +41543,14 @@ async function initiateConnection(deps, peerId, sharePermission) {
41081
41543
  }
41082
41544
  const pid = peerId || `legacy_${Date.now()}`;
41083
41545
  const existing = deps.peers.get(pid);
41084
- if (existing?.state === "connected") return;
41085
- if (existing?.state === "connecting") disconnectPeer(deps.peers, pid, deps.notifyStateChange);
41546
+ if (existing?.state === "connected") {
41547
+ log(`initiateconnection() ignored for peer ${pid} \u2014 already connected`);
41548
+ return;
41549
+ }
41550
+ if (existing?.state === "connecting") {
41551
+ log(`initiateconnection() ignored for peer ${pid} \u2014 connection already in progress`);
41552
+ return;
41553
+ }
41086
41554
  log(`initiateconnection() for peer ${pid}...`);
41087
41555
  const mod = deps.nodeDatachannel;
41088
41556
  const PeerConnectionCtor = mod.PeerConnection || mod.default?.PeerConnection || mod.default || mod;
@@ -41144,7 +41612,12 @@ async function initiateConnection(deps, peerId, sharePermission) {
41144
41612
  pendingCandidates: [],
41145
41613
  remoteDescriptionSet: false,
41146
41614
  isRelay: false,
41147
- lastPongAt: Date.now()
41615
+ lastPongAt: Date.now(),
41616
+ chatSubscriptions: /* @__PURE__ */ new Map(),
41617
+ machineRuntimeSubscriptions: /* @__PURE__ */ new Map(),
41618
+ sessionHostDiagnosticsSubscriptions: /* @__PURE__ */ new Map(),
41619
+ sessionModalSubscriptions: /* @__PURE__ */ new Map(),
41620
+ daemonMetadataSubscriptions: /* @__PURE__ */ new Map()
41148
41621
  };
41149
41622
  deps.peers.set(pid, entry);
41150
41623
  deps.notifyStateChange();
@@ -41382,6 +41855,7 @@ var init_daemon_p2p = __esm({
41382
41855
  peers = /* @__PURE__ */ new Map();
41383
41856
  nodeDatachannel = null;
41384
41857
  stateListeners = [];
41858
+ lastNotifiedState = null;
41385
41859
  screenshotSender = new ScreenshotSender();
41386
41860
  // Handler storage — exposed to router via DataChannelHandlers interface
41387
41861
  handlers = {
@@ -41390,7 +41864,8 @@ var init_daemon_p2p = __esm({
41390
41864
  commandHandler: null,
41391
41865
  ptyInputHandler: null,
41392
41866
  ptyResizeHandler: null,
41393
- screenshotStartHandler: null
41867
+ screenshotStartHandler: null,
41868
+ subscriptionChangeHandler: null
41394
41869
  };
41395
41870
  get screenshotActive() {
41396
41871
  for (const peer of this.peers.values()) {
@@ -41398,11 +41873,11 @@ var init_daemon_p2p = __esm({
41398
41873
  }
41399
41874
  return false;
41400
41875
  }
41401
- /** Get the ideType for the currently active screenshot request */
41402
- get screenshotIdeType() {
41876
+ /** Get the target session for the currently active screenshot request */
41877
+ get screenshotTargetSessionId() {
41403
41878
  for (const peer of this.peers.values()) {
41404
- if (peer.screenshotActive && peer.state === "connected" && peer.screenshotIdeType) {
41405
- return peer.screenshotIdeType;
41879
+ if (peer.screenshotActive && peer.state === "connected" && peer.screenshotTargetSessionId) {
41880
+ return peer.screenshotTargetSessionId;
41406
41881
  }
41407
41882
  }
41408
41883
  return void 0;
@@ -41519,6 +41994,8 @@ ${e?.stack || ""}`);
41519
41994
  }
41520
41995
  notifyStateChange = () => {
41521
41996
  const state = this.connectionState;
41997
+ if (state === this.lastNotifiedState) return;
41998
+ this.lastNotifiedState = state;
41522
41999
  this.stateListeners.forEach((fn) => fn(state));
41523
42000
  };
41524
42001
  get connectionManagerDeps() {
@@ -41546,11 +42023,15 @@ ${e?.stack || ""}`);
41546
42023
  for (const peerId of Array.from(this.peers.keys())) {
41547
42024
  this.disconnectPeer(peerId);
41548
42025
  }
42026
+ this.lastNotifiedState = null;
41549
42027
  }
41550
42028
  // ─── Delegated data sending ─────────────────────
41551
42029
  sendStatus(status) {
41552
42030
  return this.screenshotSender.sendStatus(this.peers, status);
41553
42031
  }
42032
+ sendStatusEvent(event) {
42033
+ return this.screenshotSender.sendStatusEvent(this.peers, event);
42034
+ }
41554
42035
  broadcastSessionOutput(sessionId, data) {
41555
42036
  return this.screenshotSender.broadcastSessionOutput(this.peers, sessionId, data);
41556
42037
  }
@@ -41579,6 +42060,89 @@ ${e?.stack || ""}`);
41579
42060
  onScreenshotStart(handler) {
41580
42061
  this.handlers.screenshotStartHandler = handler;
41581
42062
  }
42063
+ onSubscriptionChange(handler) {
42064
+ this.handlers.subscriptionChangeHandler = handler;
42065
+ }
42066
+ hasChatSubscriptions() {
42067
+ for (const peer of this.peers.values()) {
42068
+ if (peer.chatSubscriptions && peer.chatSubscriptions.size > 0) return true;
42069
+ }
42070
+ return false;
42071
+ }
42072
+ hasMachineRuntimeSubscriptions() {
42073
+ for (const peer of this.peers.values()) {
42074
+ if (peer.machineRuntimeSubscriptions && peer.machineRuntimeSubscriptions.size > 0) return true;
42075
+ }
42076
+ return false;
42077
+ }
42078
+ hasSessionHostDiagnosticsSubscriptions() {
42079
+ for (const peer of this.peers.values()) {
42080
+ if (peer.sessionHostDiagnosticsSubscriptions && peer.sessionHostDiagnosticsSubscriptions.size > 0) return true;
42081
+ }
42082
+ return false;
42083
+ }
42084
+ hasSessionModalSubscriptions() {
42085
+ for (const peer of this.peers.values()) {
42086
+ if (peer.sessionModalSubscriptions && peer.sessionModalSubscriptions.size > 0) return true;
42087
+ }
42088
+ return false;
42089
+ }
42090
+ hasDaemonMetadataSubscriptions() {
42091
+ for (const peer of this.peers.values()) {
42092
+ if (peer.daemonMetadataSubscriptions && peer.daemonMetadataSubscriptions.size > 0) return true;
42093
+ }
42094
+ return false;
42095
+ }
42096
+ async flushChatSubscriptions(builder) {
42097
+ for (const peer of this.peers.values()) {
42098
+ if (peer.state !== "connected" || !peer.chatSubscriptions || peer.chatSubscriptions.size === 0) continue;
42099
+ for (const subscription of peer.chatSubscriptions.values()) {
42100
+ const update = await builder(subscription);
42101
+ if (!update) continue;
42102
+ this.screenshotSender.sendTopicUpdateToPeer(peer, update);
42103
+ }
42104
+ }
42105
+ }
42106
+ async flushMachineRuntimeSubscriptions(builder) {
42107
+ for (const peer of this.peers.values()) {
42108
+ if (peer.state !== "connected" || !peer.machineRuntimeSubscriptions || peer.machineRuntimeSubscriptions.size === 0) continue;
42109
+ for (const subscription of peer.machineRuntimeSubscriptions.values()) {
42110
+ const update = await builder(subscription);
42111
+ if (!update) continue;
42112
+ this.screenshotSender.sendTopicUpdateToPeer(peer, update);
42113
+ }
42114
+ }
42115
+ }
42116
+ async flushSessionHostDiagnosticsSubscriptions(builder) {
42117
+ for (const peer of this.peers.values()) {
42118
+ if (peer.state !== "connected" || !peer.sessionHostDiagnosticsSubscriptions || peer.sessionHostDiagnosticsSubscriptions.size === 0) continue;
42119
+ for (const subscription of peer.sessionHostDiagnosticsSubscriptions.values()) {
42120
+ const update = await builder(subscription);
42121
+ if (!update) continue;
42122
+ this.screenshotSender.sendTopicUpdateToPeer(peer, update);
42123
+ }
42124
+ }
42125
+ }
42126
+ async flushSessionModalSubscriptions(builder) {
42127
+ for (const peer of this.peers.values()) {
42128
+ if (peer.state !== "connected" || !peer.sessionModalSubscriptions || peer.sessionModalSubscriptions.size === 0) continue;
42129
+ for (const subscription of peer.sessionModalSubscriptions.values()) {
42130
+ const update = await builder(subscription);
42131
+ if (!update) continue;
42132
+ this.screenshotSender.sendTopicUpdateToPeer(peer, update);
42133
+ }
42134
+ }
42135
+ }
42136
+ async flushDaemonMetadataSubscriptions(builder) {
42137
+ for (const peer of this.peers.values()) {
42138
+ if (peer.state !== "connected" || !peer.daemonMetadataSubscriptions || peer.daemonMetadataSubscriptions.size === 0) continue;
42139
+ for (const subscription of peer.daemonMetadataSubscriptions.values()) {
42140
+ const update = await builder(subscription);
42141
+ if (!update) continue;
42142
+ this.screenshotSender.sendTopicUpdateToPeer(peer, update);
42143
+ }
42144
+ }
42145
+ }
41582
42146
  };
41583
42147
  }
41584
42148
  });
@@ -48239,12 +48803,12 @@ var init_screenshot_controller = __esm({
48239
48803
  async tick() {
48240
48804
  if (!this.deps.isRunning()) return;
48241
48805
  const active = this.deps.isScreenshotActive();
48242
- const ssIdeType = this.deps.getScreenshotIdeType();
48243
- if (active && !ssIdeType) {
48806
+ const targetSessionId = this.deps.getScreenshotTargetSessionId();
48807
+ if (active && !targetSessionId) {
48244
48808
  this.timer = setTimeout(() => this.tick(), 500);
48245
48809
  return;
48246
48810
  }
48247
- const cdp = ssIdeType ? this.deps.getCdp(ssIdeType) : null;
48811
+ const cdp = targetSessionId ? this.deps.getCdp(targetSessionId) : null;
48248
48812
  const isRelay = this.deps.isUsingRelay();
48249
48813
  const profile = isRelay ? this.profileRelay : this.profileDirect;
48250
48814
  this.checkBudgetReset();
@@ -48723,6 +49287,48 @@ __export(adhdev_daemon_exports, {
48723
49287
  isDaemonRunning: () => isDaemonRunning,
48724
49288
  stopDaemon: () => stopDaemon
48725
49289
  });
49290
+ function hashSignatureParts2(parts) {
49291
+ let hash2 = 2166136261;
49292
+ for (const part of parts) {
49293
+ const text = String(part || "");
49294
+ for (let i = 0; i < text.length; i += 1) {
49295
+ hash2 ^= text.charCodeAt(i);
49296
+ hash2 = Math.imul(hash2, 16777619) >>> 0;
49297
+ }
49298
+ hash2 ^= 255;
49299
+ hash2 = Math.imul(hash2, 16777619) >>> 0;
49300
+ }
49301
+ return hash2.toString(16).padStart(8, "0");
49302
+ }
49303
+ function buildChatTailDeliverySignature(payload) {
49304
+ let messages = "";
49305
+ try {
49306
+ messages = JSON.stringify(payload.messages);
49307
+ } catch {
49308
+ messages = String(payload.messages.length);
49309
+ }
49310
+ return hashSignatureParts2([
49311
+ payload.sessionId,
49312
+ payload.historySessionId || "",
49313
+ payload.status,
49314
+ payload.title || "",
49315
+ payload.syncMode,
49316
+ String(payload.replaceFrom),
49317
+ String(payload.totalMessages),
49318
+ payload.lastMessageSignature,
49319
+ payload.activeModal ? `${payload.activeModal.message}|${payload.activeModal.buttons.join("")}` : "",
49320
+ messages
49321
+ ]);
49322
+ }
49323
+ function buildSessionModalDeliverySignature(payload) {
49324
+ return hashSignatureParts2([
49325
+ payload.sessionId,
49326
+ payload.status,
49327
+ payload.title || "",
49328
+ payload.modalMessage || "",
49329
+ Array.isArray(payload.modalButtons) ? payload.modalButtons.join("") : ""
49330
+ ]);
49331
+ }
48726
49332
  function getDaemonPidFile() {
48727
49333
  const dir = path25.join(os23.homedir(), ".adhdev");
48728
49334
  if (!fs17.existsSync(dir)) fs17.mkdirSync(dir, { recursive: true });
@@ -48786,7 +49392,7 @@ function stopDaemon() {
48786
49392
  return false;
48787
49393
  }
48788
49394
  }
48789
- var os23, fs17, path25, import_http, import_ws3, import_chalk2, pkgVersion, AdhdevDaemon;
49395
+ var os23, fs17, path25, import_http, import_ws3, import_chalk2, pkgVersion, ACTIVE_CHAT_POLL_STATUSES, AdhdevDaemon;
48790
49396
  var init_adhdev_daemon = __esm({
48791
49397
  "src/adhdev-daemon.ts"() {
48792
49398
  "use strict";
@@ -48804,7 +49410,12 @@ var init_adhdev_daemon = __esm({
48804
49410
  import_ws3 = require("ws");
48805
49411
  import_chalk2 = __toESM(require("chalk"));
48806
49412
  init_version();
48807
- pkgVersion = resolvePackageVersion({ injectedVersion: "0.8.34" });
49413
+ pkgVersion = resolvePackageVersion({ injectedVersion: "0.8.36" });
49414
+ ACTIVE_CHAT_POLL_STATUSES = /* @__PURE__ */ new Set([
49415
+ "generating",
49416
+ "waiting_approval",
49417
+ "starting"
49418
+ ]);
48808
49419
  AdhdevDaemon = class {
48809
49420
  localHttpServer = null;
48810
49421
  localWss = null;
@@ -48813,6 +49424,11 @@ var init_adhdev_daemon = __esm({
48813
49424
  p2p = null;
48814
49425
  screenshotController = null;
48815
49426
  statusReporter = null;
49427
+ topicSubscriptionTimer = null;
49428
+ p2pChatFlushInFlight = false;
49429
+ pendingP2PChatFlush = false;
49430
+ pendingP2PChatFlushOnlyActive = true;
49431
+ hotP2PChatSessionIds = /* @__PURE__ */ new Set();
48816
49432
  components = null;
48817
49433
  sessionHostEndpoint = null;
48818
49434
  sessionHostController = null;
@@ -48836,6 +49452,252 @@ var init_adhdev_daemon = __esm({
48836
49452
  const mode = this.getCliPresentationMode(sessionId);
48837
49453
  return mode === "chat" || mode === "terminal";
48838
49454
  }
49455
+ async buildChatTailUpdateForSubscription(subscription) {
49456
+ const result = await this.components.router.execute("read_chat", {
49457
+ targetSessionId: subscription.params.targetSessionId,
49458
+ ...subscription.params.historySessionId ? { historySessionId: subscription.params.historySessionId } : {},
49459
+ knownMessageCount: subscription.cursor.knownMessageCount,
49460
+ lastMessageSignature: subscription.cursor.lastMessageSignature,
49461
+ ...subscription.cursor.tailLimit > 0 ? { tailLimit: subscription.cursor.tailLimit } : {}
49462
+ }, "p2p");
49463
+ if (!result?.success || result.syncMode === "noop") {
49464
+ if (result?.success) {
49465
+ subscription.cursor = {
49466
+ knownMessageCount: Math.max(0, Number(result.totalMessages || subscription.cursor.knownMessageCount)),
49467
+ lastMessageSignature: typeof result.lastMessageSignature === "string" ? result.lastMessageSignature : subscription.cursor.lastMessageSignature,
49468
+ tailLimit: subscription.cursor.tailLimit
49469
+ };
49470
+ }
49471
+ return null;
49472
+ }
49473
+ subscription.seq += 1;
49474
+ const syncMode = result.syncMode === "append" || result.syncMode === "replace_tail" || result.syncMode === "noop" || result.syncMode === "full" ? result.syncMode : "full";
49475
+ subscription.cursor = {
49476
+ knownMessageCount: Math.max(0, Number(result.totalMessages || 0)),
49477
+ lastMessageSignature: typeof result.lastMessageSignature === "string" ? result.lastMessageSignature : "",
49478
+ tailLimit: subscription.cursor.tailLimit
49479
+ };
49480
+ const activeModal = result.activeModal && typeof result.activeModal === "object" && typeof result.activeModal.message === "string" && Array.isArray(result.activeModal.buttons) ? {
49481
+ message: result.activeModal.message,
49482
+ buttons: result.activeModal.buttons.filter((button) => typeof button === "string")
49483
+ } : null;
49484
+ const deliverySignature = buildChatTailDeliverySignature({
49485
+ sessionId: subscription.params.targetSessionId,
49486
+ ...subscription.params.historySessionId ? { historySessionId: subscription.params.historySessionId } : {},
49487
+ messages: Array.isArray(result.messages) ? result.messages : [],
49488
+ status: typeof result.status === "string" ? result.status : "idle",
49489
+ ...typeof result.title === "string" ? { title: result.title } : {},
49490
+ ...activeModal ? { activeModal } : {},
49491
+ syncMode,
49492
+ replaceFrom: Number(result.replaceFrom || 0),
49493
+ totalMessages: Number(result.totalMessages || 0),
49494
+ lastMessageSignature: typeof result.lastMessageSignature === "string" ? result.lastMessageSignature : ""
49495
+ });
49496
+ if (deliverySignature === subscription.lastDeliveredSignature) {
49497
+ return null;
49498
+ }
49499
+ subscription.lastDeliveredSignature = deliverySignature;
49500
+ return {
49501
+ topic: "session.chat_tail",
49502
+ key: subscription.key,
49503
+ sessionId: subscription.params.targetSessionId,
49504
+ ...subscription.params.historySessionId ? { historySessionId: subscription.params.historySessionId } : {},
49505
+ seq: subscription.seq,
49506
+ timestamp: Date.now(),
49507
+ messages: Array.isArray(result.messages) ? result.messages : [],
49508
+ status: typeof result.status === "string" ? result.status : "idle",
49509
+ ...typeof result.title === "string" ? { title: result.title } : {},
49510
+ ...activeModal ? { activeModal } : {},
49511
+ syncMode,
49512
+ replaceFrom: Number(result.replaceFrom || 0),
49513
+ totalMessages: Number(result.totalMessages || 0),
49514
+ lastMessageSignature: typeof result.lastMessageSignature === "string" ? result.lastMessageSignature : ""
49515
+ };
49516
+ }
49517
+ buildLiveStatusSnapshot() {
49518
+ return buildStatusSnapshot({
49519
+ allStates: this.components.instanceManager.collectAllStates(),
49520
+ cdpManagers: this.components.cdpManagers,
49521
+ providerLoader: this.components.providerLoader,
49522
+ detectedIdes: this.components.detectedIdes.value.map((ide) => ({
49523
+ ...ide,
49524
+ path: ide.path ?? void 0
49525
+ })),
49526
+ instanceId: `daemon_${loadConfig().machineId || "daemon"}`,
49527
+ version: pkgVersion,
49528
+ profile: "live"
49529
+ });
49530
+ }
49531
+ getHotChatSessionIdsForP2PFlush() {
49532
+ const snapshot = this.buildLiveStatusSnapshot();
49533
+ const active = new Set(
49534
+ snapshot.sessions.filter((session) => ACTIVE_CHAT_POLL_STATUSES.has(String(session.status || "").toLowerCase())).map((session) => session.id)
49535
+ );
49536
+ const finalizing = new Set(
49537
+ Array.from(this.hotP2PChatSessionIds).filter((sessionId) => !active.has(sessionId))
49538
+ );
49539
+ this.hotP2PChatSessionIds = active;
49540
+ return { active, finalizing };
49541
+ }
49542
+ async flushP2PChatSubscriptions(options = {}) {
49543
+ if (!this.p2p?.isConnected || !this.p2p.hasChatSubscriptions()) return;
49544
+ const onlyActive = options.onlyActive === true;
49545
+ if (this.p2pChatFlushInFlight) {
49546
+ this.pendingP2PChatFlush = true;
49547
+ this.pendingP2PChatFlushOnlyActive = this.pendingP2PChatFlushOnlyActive && onlyActive;
49548
+ return;
49549
+ }
49550
+ this.p2pChatFlushInFlight = true;
49551
+ try {
49552
+ const hotSessionIds = onlyActive ? this.getHotChatSessionIdsForP2PFlush() : null;
49553
+ await this.p2p.flushChatSubscriptions(async (subscription) => {
49554
+ if (hotSessionIds && !hotSessionIds.active.has(subscription.params.targetSessionId) && !hotSessionIds.finalizing.has(subscription.params.targetSessionId)) {
49555
+ return null;
49556
+ }
49557
+ return this.buildChatTailUpdateForSubscription(subscription);
49558
+ });
49559
+ } finally {
49560
+ this.p2pChatFlushInFlight = false;
49561
+ if (this.pendingP2PChatFlush) {
49562
+ const pendingOnlyActive = this.pendingP2PChatFlushOnlyActive;
49563
+ this.pendingP2PChatFlush = false;
49564
+ this.pendingP2PChatFlushOnlyActive = true;
49565
+ void this.flushP2PChatSubscriptions({ onlyActive: pendingOnlyActive });
49566
+ }
49567
+ }
49568
+ }
49569
+ buildMachineRuntimeUpdateForSubscription(subscription) {
49570
+ const intervalMs = Math.max(5e3, Number(subscription.params.intervalMs || 15e3));
49571
+ const now = Date.now();
49572
+ if (subscription.lastSentAt > 0 && now - subscription.lastSentAt < intervalMs) {
49573
+ return null;
49574
+ }
49575
+ subscription.seq += 1;
49576
+ subscription.lastSentAt = now;
49577
+ return {
49578
+ topic: "machine.runtime",
49579
+ key: subscription.key,
49580
+ machine: buildMachineInfo("full"),
49581
+ seq: subscription.seq,
49582
+ timestamp: now
49583
+ };
49584
+ }
49585
+ async flushP2PMachineRuntimeSubscriptions() {
49586
+ if (!this.p2p?.isConnected || !this.p2p.hasMachineRuntimeSubscriptions()) return;
49587
+ await this.p2p.flushMachineRuntimeSubscriptions(async (subscription) => this.buildMachineRuntimeUpdateForSubscription(subscription));
49588
+ }
49589
+ async buildSessionHostDiagnosticsUpdateForSubscription(subscription) {
49590
+ if (!this.sessionHostController) return null;
49591
+ const intervalMs = Math.max(5e3, Number(subscription.params.intervalMs || 1e4));
49592
+ const now = Date.now();
49593
+ if (subscription.lastSentAt > 0 && now - subscription.lastSentAt < intervalMs) {
49594
+ return null;
49595
+ }
49596
+ const diagnostics = await this.sessionHostController.getDiagnostics({
49597
+ includeSessions: subscription.params.includeSessions !== false,
49598
+ limit: Number(subscription.params.limit) || void 0
49599
+ });
49600
+ subscription.seq += 1;
49601
+ subscription.lastSentAt = now;
49602
+ return {
49603
+ topic: "session_host.diagnostics",
49604
+ key: subscription.key,
49605
+ diagnostics,
49606
+ seq: subscription.seq,
49607
+ timestamp: now
49608
+ };
49609
+ }
49610
+ async flushP2PSessionHostDiagnosticsSubscriptions() {
49611
+ if (!this.p2p?.isConnected || !this.p2p.hasSessionHostDiagnosticsSubscriptions()) return;
49612
+ await this.p2p.flushSessionHostDiagnosticsSubscriptions(
49613
+ async (subscription) => this.buildSessionHostDiagnosticsUpdateForSubscription(subscription)
49614
+ );
49615
+ }
49616
+ findProviderStateBySessionId(sessionId) {
49617
+ if (!this.components || !sessionId) return null;
49618
+ const states = this.components.instanceManager.collectAllStates();
49619
+ for (const state of states) {
49620
+ if (state.instanceId === sessionId) return state;
49621
+ if (state.category === "ide") {
49622
+ const child = state.extensions.find((entry) => entry.instanceId === sessionId);
49623
+ if (child) return child;
49624
+ }
49625
+ }
49626
+ return null;
49627
+ }
49628
+ buildSessionModalUpdateForSubscription(subscription) {
49629
+ const state = this.findProviderStateBySessionId(subscription.params.targetSessionId);
49630
+ if (!state) return null;
49631
+ const now = Date.now();
49632
+ const activeModal = state.activeChat?.activeModal;
49633
+ const modalButtons = Array.isArray(activeModal?.buttons) ? activeModal.buttons.filter((button) => typeof button === "string") : [];
49634
+ const status = String(state.activeChat?.status || state.status || "idle");
49635
+ const title = typeof state.activeChat?.title === "string" ? state.activeChat.title : void 0;
49636
+ const modalMessage = typeof activeModal?.message === "string" ? activeModal.message : void 0;
49637
+ const deliverySignature = buildSessionModalDeliverySignature({
49638
+ sessionId: subscription.params.targetSessionId,
49639
+ status,
49640
+ ...title ? { title } : {},
49641
+ ...modalMessage ? { modalMessage } : {},
49642
+ ...modalButtons.length > 0 ? { modalButtons } : {}
49643
+ });
49644
+ if (deliverySignature === subscription.lastDeliveredSignature) {
49645
+ return null;
49646
+ }
49647
+ subscription.lastDeliveredSignature = deliverySignature;
49648
+ subscription.seq += 1;
49649
+ subscription.lastSentAt = now;
49650
+ return {
49651
+ topic: "session.modal",
49652
+ key: subscription.key,
49653
+ sessionId: subscription.params.targetSessionId,
49654
+ status,
49655
+ ...title ? { title } : {},
49656
+ ...modalMessage ? { modalMessage } : {},
49657
+ ...modalButtons.length > 0 ? { modalButtons } : {},
49658
+ seq: subscription.seq,
49659
+ timestamp: now
49660
+ };
49661
+ }
49662
+ async flushP2PSessionModalSubscriptions() {
49663
+ if (!this.p2p?.isConnected || !this.p2p.hasSessionModalSubscriptions()) return;
49664
+ await this.p2p.flushSessionModalSubscriptions(
49665
+ async (subscription) => this.buildSessionModalUpdateForSubscription(subscription)
49666
+ );
49667
+ }
49668
+ buildDaemonMetadataSnapshot() {
49669
+ return buildStatusSnapshot({
49670
+ allStates: this.components.instanceManager.collectAllStates(),
49671
+ cdpManagers: this.components.cdpManagers,
49672
+ providerLoader: this.components.providerLoader,
49673
+ detectedIdes: this.components.detectedIdes.value.map((ide) => ({
49674
+ ...ide,
49675
+ path: ide.path ?? void 0
49676
+ })),
49677
+ instanceId: `daemon_${loadConfig().machineId || "daemon"}`,
49678
+ version: pkgVersion,
49679
+ profile: "metadata"
49680
+ });
49681
+ }
49682
+ buildDaemonMetadataUpdateForSubscription(subscription) {
49683
+ const now = Date.now();
49684
+ subscription.seq += 1;
49685
+ subscription.lastSentAt = now;
49686
+ return {
49687
+ topic: "daemon.metadata",
49688
+ key: subscription.key,
49689
+ daemonId: `daemon_${loadConfig().machineId || "daemon"}`,
49690
+ status: this.buildDaemonMetadataSnapshot(),
49691
+ seq: subscription.seq,
49692
+ timestamp: now
49693
+ };
49694
+ }
49695
+ async flushP2PDaemonMetadataSubscriptions() {
49696
+ if (!this.p2p?.isConnected || !this.p2p.hasDaemonMetadataSubscriptions()) return;
49697
+ await this.p2p.flushDaemonMetadataSubscriptions(
49698
+ async (subscription) => this.buildDaemonMetadataUpdateForSubscription(subscription)
49699
+ );
49700
+ }
48839
49701
  async start(options = {}) {
48840
49702
  installGlobalInterceptor();
48841
49703
  process.on("uncaughtException", (err) => {
@@ -48868,9 +49730,14 @@ ${err?.stack || ""}`);
48868
49730
  this.sessionHostEndpoint = sessionHostEndpoint;
48869
49731
  this.sessionHostController = new SessionHostController(
48870
49732
  sessionHostEndpoint,
48871
- (event) => this.broadcastLocalIpcMessage("daemon:session_host_event", event)
49733
+ (event) => {
49734
+ this.broadcastLocalIpcMessage("daemon:session_host_event", event);
49735
+ void this.flushP2PSessionHostDiagnosticsSubscriptions();
49736
+ void this.flushP2PSessionModalSubscriptions();
49737
+ }
48872
49738
  );
48873
49739
  await this.sessionHostController.start();
49740
+ const instanceId = `daemon_${config2.machineId}`;
48874
49741
  this.components = await initDaemonComponents({
48875
49742
  providerLogFn: LOG.forComponent("Provider").asLogFn(),
48876
49743
  cliManagerDeps: {
@@ -48903,6 +49770,8 @@ ${err?.stack || ""}`);
48903
49770
  listHostedCliRuntimes: async () => listHostedCliRuntimes2(sessionHostEndpoint)
48904
49771
  },
48905
49772
  enabledIdes: config2.enabledIdes,
49773
+ statusInstanceId: instanceId,
49774
+ statusVersion: pkgVersion,
48906
49775
  onStatusChange: () => this.statusReporter?.onStatusChange(),
48907
49776
  onPostChatCommand: () => {
48908
49777
  setTimeout(() => this.statusReporter?.throttledReport(), 1e3);
@@ -48928,7 +49797,6 @@ ${err?.stack || ""}`);
48928
49797
  }
48929
49798
  }).catch(() => {
48930
49799
  });
48931
- const instanceId = `daemon_${config2.machineId}`;
48932
49800
  this.serverConn = new ServerConnection({
48933
49801
  serverUrl: options.serverUrl || config2.serverUrl,
48934
49802
  token: authToken,
@@ -48978,14 +49846,21 @@ ${err?.stack || ""}`);
48978
49846
  found.adapter.resize(cols, rows);
48979
49847
  }
48980
49848
  });
49849
+ this.p2p.onSubscriptionChange(() => {
49850
+ void this.flushP2PChatSubscriptions();
49851
+ void this.flushP2PMachineRuntimeSubscriptions();
49852
+ void this.flushP2PSessionHostDiagnosticsSubscriptions();
49853
+ void this.flushP2PSessionModalSubscriptions();
49854
+ void this.flushP2PDaemonMetadataSubscriptions();
49855
+ });
48981
49856
  this.p2p.onStateChange((state) => {
48982
49857
  if (state === "connected") {
48983
49858
  LOG.info("P2P", "Peer connected \u2192 sending immediate full status report");
48984
49859
  this.statusReporter?.resetP2PHash();
48985
- this.statusReporter?.sendUnifiedStatusReport().catch((e) => LOG.warn("P2P", `Immediate status report failed: ${e?.message}`));
49860
+ this.statusReporter?.sendUnifiedStatusReport({ reason: "p2p-connect" }).catch((e) => LOG.warn("P2P", `Immediate status report failed: ${e?.message}`));
48986
49861
  setTimeout(() => {
48987
49862
  this.statusReporter?.resetP2PHash();
48988
- this.statusReporter?.sendUnifiedStatusReport().catch(() => {
49863
+ this.statusReporter?.sendUnifiedStatusReport({ reason: "p2p-connect-followup" }).catch(() => {
48989
49864
  });
48990
49865
  }, 2e3);
48991
49866
  }
@@ -48994,18 +49869,24 @@ ${err?.stack || ""}`);
48994
49869
  this.screenshotController = new ScreenshotController({
48995
49870
  isRunning: () => this.running,
48996
49871
  isScreenshotActive: () => this.p2p?.screenshotActive ?? false,
48997
- getScreenshotIdeType: () => this.p2p?.screenshotIdeType,
49872
+ getScreenshotTargetSessionId: () => this.p2p?.screenshotTargetSessionId,
48998
49873
  isUsingRelay: () => this.p2p?.isUsingRelay ?? false,
48999
49874
  hasAnyNeedingFirstFrame: () => this.p2p?.hasAnyNeedingFirstFrame() ?? false,
49000
- getCdp: (ideType) => {
49001
- if (ideType) return this.getCdpFor(ideType);
49002
- LOG.warn("P2P", "Screenshot requested without ideType \u2014 cannot determine target IDE. Skipping frame.");
49875
+ getCdp: (targetSessionId) => {
49876
+ if (targetSessionId) return this.getCdpFor(targetSessionId);
49877
+ LOG.warn("P2P", "Screenshot requested without targetSessionId \u2014 cannot determine target session. Skipping frame.");
49003
49878
  return null;
49004
49879
  },
49005
49880
  sendScreenshotBuffer: (buf) => this.p2p.sendScreenshotBuffer(buf)
49006
49881
  }, planLimits ?? void 0);
49007
49882
  this.screenshotController.start();
49008
49883
  this.p2p.onScreenshotStart(() => this.screenshotController?.triggerImmediate());
49884
+ this.topicSubscriptionTimer = setInterval(() => {
49885
+ void this.flushP2PChatSubscriptions({ onlyActive: true });
49886
+ void this.flushP2PMachineRuntimeSubscriptions();
49887
+ void this.flushP2PSessionHostDiagnosticsSubscriptions();
49888
+ void this.flushP2PSessionModalSubscriptions();
49889
+ }, 2500);
49009
49890
  } else {
49010
49891
  console.log(import_chalk2.default.gray(" \u26A0 P2P unavailable \u2014 using server relay"));
49011
49892
  }
@@ -49024,7 +49905,7 @@ ${err?.stack || ""}`);
49024
49905
  this.serverConn.onStateChange((state) => {
49025
49906
  if (state === "connected") {
49026
49907
  console.log(import_chalk2.default.green(" \u{1F4E1} Connected to ADHDev server"));
49027
- this.statusReporter?.sendUnifiedStatusReport();
49908
+ this.statusReporter?.sendUnifiedStatusReport({ forceServer: true, reason: "ws-connected" });
49028
49909
  } else if (state === "disconnected") {
49029
49910
  console.log(import_chalk2.default.yellow(" \u26A0 Server disconnected, will reconnect..."));
49030
49911
  }
@@ -49110,10 +49991,26 @@ ${err?.stack || ""}`);
49110
49991
  async handleCommand(msg, cmd, args) {
49111
49992
  LOG.info("Command", `${cmd}${args?.targetSessionId ? ` \u2192 session:${String(args.targetSessionId).split("_")[0]}` : ""}`);
49112
49993
  const cmdStart = Date.now();
49113
- const source = msg.ipcWs ? "ext" : "ws";
49994
+ const source = msg.ipcWs ? "ext" : typeof msg.source === "string" && msg.source.trim() ? msg.source : "ws";
49114
49995
  try {
49996
+ if (source === "api" && !loadConfig().allowServerApiProxy) {
49997
+ this.sendResult(msg, false, {
49998
+ error: "Server API relay is disabled on this daemon. Enable it explicitly with `adhdev daemon:api enable`.",
49999
+ code: "SERVER_API_DISABLED"
50000
+ });
50001
+ return;
50002
+ }
49115
50003
  const result = await this.components.router.execute(cmd, args, source);
49116
50004
  if (cmd.startsWith("workspace_")) this.statusReporter?.throttledReport();
50005
+ if (cmd === "get_status_metadata" || cmd.startsWith("workspace_") || cmd.startsWith("session_host_")) {
50006
+ void this.flushP2PDaemonMetadataSubscriptions();
50007
+ }
50008
+ if (cmd.startsWith("session_host_")) {
50009
+ void this.flushP2PSessionHostDiagnosticsSubscriptions();
50010
+ }
50011
+ if (cmd === "resolve_action" || cmd === "send_chat" || cmd === "read_chat") {
50012
+ void this.flushP2PSessionModalSubscriptions();
50013
+ }
49117
50014
  this.sendResult(msg, result.success, result);
49118
50015
  } catch (e) {
49119
50016
  console.error(import_chalk2.default.red(` \u2717 Command failed: ${e.message}`));
@@ -49153,6 +50050,7 @@ ${err?.stack || ""}`);
49153
50050
  const config2 = loadConfig();
49154
50051
  config2.machineNickname = nickname;
49155
50052
  saveConfig(config2);
50053
+ void this.flushP2PDaemonMetadataSubscriptions();
49156
50054
  logCommand({ ts: (/* @__PURE__ */ new Date()).toISOString(), cmd: cmdType, source: "p2p", args: { nickname }, success: true, durationMs: Date.now() - cmdStart });
49157
50055
  return { success: true, nickname };
49158
50056
  }
@@ -49165,6 +50063,15 @@ ${err?.stack || ""}`);
49165
50063
  }
49166
50064
  const routed = await this.components.router.execute(cmdType, data, "p2p");
49167
50065
  if (cmdType.startsWith("workspace_")) this.statusReporter?.throttledReport();
50066
+ if (cmdType === "get_status_metadata" || cmdType.startsWith("workspace_") || cmdType.startsWith("session_host_")) {
50067
+ void this.flushP2PDaemonMetadataSubscriptions();
50068
+ }
50069
+ if (cmdType.startsWith("session_host_")) {
50070
+ void this.flushP2PSessionHostDiagnosticsSubscriptions();
50071
+ }
50072
+ if (cmdType === "resolve_action" || cmdType === "send_chat" || cmdType === "read_chat") {
50073
+ void this.flushP2PSessionModalSubscriptions();
50074
+ }
49168
50075
  return routed;
49169
50076
  } catch (e) {
49170
50077
  logCommand({ ts: (/* @__PURE__ */ new Date()).toISOString(), cmd: cmdType, source: "p2p", success: false, error: e.message, durationMs: Date.now() - cmdStart });
@@ -49337,6 +50244,10 @@ ${err?.stack || ""}`);
49337
50244
  this.statusReporter.stopReporting();
49338
50245
  this.statusReporter = null;
49339
50246
  }
50247
+ if (this.topicSubscriptionTimer) {
50248
+ clearInterval(this.topicSubscriptionTimer);
50249
+ this.topicSubscriptionTimer = null;
50250
+ }
49340
50251
  if (this.components) {
49341
50252
  await shutdownDaemonComponents(this.components);
49342
50253
  }