@adhdev/daemon-core 0.8.28 → 0.8.30

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.
Files changed (36) hide show
  1. package/dist/agent-stream/manager.d.ts +1 -1
  2. package/dist/agent-stream/provider-adapter.d.ts +5 -0
  3. package/dist/commands/router.d.ts +5 -0
  4. package/dist/config/chat-history.d.ts +12 -0
  5. package/dist/index.js +552 -52
  6. package/dist/index.js.map +1 -1
  7. package/dist/index.mjs +552 -52
  8. package/dist/index.mjs.map +1 -1
  9. package/dist/providers/acp-provider-instance.d.ts +1 -0
  10. package/dist/providers/approval-utils.d.ts +7 -0
  11. package/dist/providers/cli-provider-instance.d.ts +3 -0
  12. package/dist/providers/contracts.d.ts +2 -0
  13. package/dist/providers/ide-provider-instance.d.ts +1 -0
  14. package/node_modules/@adhdev/session-host-core/dist/index.d.mts +24 -1
  15. package/node_modules/@adhdev/session-host-core/dist/index.d.ts +24 -1
  16. package/node_modules/@adhdev/session-host-core/dist/index.js +6 -1
  17. package/node_modules/@adhdev/session-host-core/dist/index.js.map +1 -1
  18. package/node_modules/@adhdev/session-host-core/dist/index.mjs +6 -1
  19. package/node_modules/@adhdev/session-host-core/dist/index.mjs.map +1 -1
  20. package/node_modules/@adhdev/session-host-core/package.json +1 -1
  21. package/package.json +1 -1
  22. package/src/agent-stream/manager.ts +2 -2
  23. package/src/agent-stream/poller.ts +38 -1
  24. package/src/agent-stream/provider-adapter.ts +97 -3
  25. package/src/cli-adapters/provider-cli-adapter.ts +3 -0
  26. package/src/commands/chat-commands.ts +53 -3
  27. package/src/commands/cli-manager.ts +14 -0
  28. package/src/commands/router.ts +11 -0
  29. package/src/config/chat-history.ts +269 -18
  30. package/src/providers/acp-provider-instance.ts +17 -2
  31. package/src/providers/approval-utils.ts +66 -0
  32. package/src/providers/cli-provider-instance.ts +47 -6
  33. package/src/providers/contracts.d.ts +1 -0
  34. package/src/providers/contracts.ts +3 -1
  35. package/src/providers/ide-provider-instance.ts +28 -23
  36. package/src/providers/provider-loader.ts +26 -2
package/dist/index.mjs CHANGED
@@ -1640,6 +1640,9 @@ var init_provider_cli_adapter = __esm({
1640
1640
  looksLikeVisibleIdlePrompt(screenText) {
1641
1641
  const text = String(screenText || "");
1642
1642
  if (!text.trim()) return false;
1643
+ if (this.cliType === "codex-cli" && /(^|\n)\s*[❯›>]\s+(?:Find and fix a bug in @filename|Improve documentation in @filename|Use \/skills|Write tests for @filename|Explain this codebase|Summarize recent commits|Implement \{feature\}|Run \/review on my current changes)(?:\n|$)/im.test(text)) {
1644
+ return true;
1645
+ }
1643
1646
  return /(^|\n)\s*[❯›>]\s*(?:\n|$)/m.test(text) || /⏎\s+send/i.test(text) || /\?\s*for\s*shortcuts/i.test(text) || /Type your message(?:\s+or\s+@path\/to\/file)?/i.test(text) || /workspace\s*\(\/directory\)/i.test(text) || /for\s*shortcuts/i.test(text);
1644
1647
  }
1645
1648
  findLastMatchingLineIndex(lines, predicate) {
@@ -4884,11 +4887,72 @@ import * as path7 from "path";
4884
4887
  import * as os5 from "os";
4885
4888
  var HISTORY_DIR = path7.join(os5.homedir(), ".adhdev", "history");
4886
4889
  var RETAIN_DAYS = 30;
4890
+ var CODEX_STARTER_PROMPT_RE = /^(?:[›❯]\s*)?(?:Find and fix a bug in @filename|Improve documentation in @filename|Write tests for @filename|Explain this codebase|Summarize recent commits|Implement \{feature\}|Use \/skills(?: to list available skills)?|Run \/review on my current changes)$/i;
4891
+ function normalizeHistoryComparable(text) {
4892
+ return String(text || "").replace(/\s+/g, " ").trim();
4893
+ }
4894
+ function cleanupHistoryContent(agentType, role, content) {
4895
+ let value = String(content || "").replace(/\r\n/g, "\n").trim();
4896
+ if (!value) return "";
4897
+ if (agentType === "codex-cli" && role === "assistant") {
4898
+ const filtered = value.split("\n").filter((line) => !CODEX_STARTER_PROMPT_RE.test(line.trim())).join("\n").replace(/\n{3,}/g, "\n\n").trim();
4899
+ value = filtered;
4900
+ }
4901
+ return value;
4902
+ }
4903
+ function buildHistoryMessageHash(agentType, message) {
4904
+ if (message.historyDedupKey) return message.historyDedupKey;
4905
+ const cleaned = cleanupHistoryContent(agentType, message.role, message.content);
4906
+ return `${message.kind || "standard"}:${message.role}:${message.receivedAt || 0}:${normalizeHistoryComparable(cleaned)}`;
4907
+ }
4908
+ function buildHistoryMessageSignature(agentType, message) {
4909
+ const cleaned = cleanupHistoryContent(agentType, message.role, message.content);
4910
+ return `${message.kind || "standard"}:${message.role}:${normalizeHistoryComparable(cleaned)}`;
4911
+ }
4912
+ function isAdjacentHistoryDuplicate(agentType, previous, next) {
4913
+ if (!previous || !next) return false;
4914
+ return buildHistoryMessageSignature(agentType, previous) === buildHistoryMessageSignature(agentType, next);
4915
+ }
4916
+ function collapseReplayAssistantTurns(agentType, messages) {
4917
+ if (agentType !== "codex-cli") return messages;
4918
+ const collapsed = [];
4919
+ let sawAssistantSinceLastUser = false;
4920
+ for (const message of messages) {
4921
+ if (message.role === "user") {
4922
+ sawAssistantSinceLastUser = false;
4923
+ collapsed.push(message);
4924
+ continue;
4925
+ }
4926
+ if (message.role === "assistant") {
4927
+ if (sawAssistantSinceLastUser) continue;
4928
+ sawAssistantSinceLastUser = true;
4929
+ collapsed.push(message);
4930
+ continue;
4931
+ }
4932
+ collapsed.push(message);
4933
+ }
4934
+ return collapsed;
4935
+ }
4936
+ function sanitizeHistoryMessage(agentType, message) {
4937
+ if (!message || message.role !== "user" && message.role !== "assistant" && message.role !== "system") {
4938
+ return null;
4939
+ }
4940
+ const content = cleanupHistoryContent(agentType, message.role, message.content);
4941
+ if (!content) return null;
4942
+ return {
4943
+ ...message,
4944
+ content
4945
+ };
4946
+ }
4887
4947
  var ChatHistoryWriter = class {
4888
4948
  /** Last seen message count per agent (deduplication) */
4889
4949
  lastSeenCounts = /* @__PURE__ */ new Map();
4890
4950
  /** Last seen message hash per agent (deduplication) */
4891
4951
  lastSeenHashes = /* @__PURE__ */ new Map();
4952
+ /** Last appended normalized message signature per agent/session */
4953
+ lastSeenSignatures = /* @__PURE__ */ new Map();
4954
+ /** Last appended normalized non-system turn signature per agent/session */
4955
+ lastSeenTurnSignatures = /* @__PURE__ */ new Map();
4892
4956
  rotated = false;
4893
4957
  /**
4894
4958
  * Append new messages to history
@@ -4910,14 +4974,36 @@ var ChatHistoryWriter = class {
4910
4974
  }
4911
4975
  const newMessages = [];
4912
4976
  for (const msg of messages) {
4913
- const hash = msg.historyDedupKey || `${msg.kind || "standard"}:${msg.role}:${(msg.content || "").slice(0, 50)}`;
4977
+ const role = msg.role;
4978
+ if (role !== "user" && role !== "assistant" && role !== "system") continue;
4979
+ const content = cleanupHistoryContent(agentType, role, msg.content || "");
4980
+ if (!content) continue;
4981
+ const receivedAt = msg.receivedAt || Date.now();
4982
+ const hash = buildHistoryMessageHash(agentType, {
4983
+ role,
4984
+ content,
4985
+ receivedAt,
4986
+ kind: typeof msg.kind === "string" ? msg.kind : void 0,
4987
+ historyDedupKey: msg.historyDedupKey
4988
+ });
4989
+ const signature = buildHistoryMessageSignature(agentType, {
4990
+ role,
4991
+ content,
4992
+ kind: typeof msg.kind === "string" ? msg.kind : void 0
4993
+ });
4914
4994
  if (seenHashes.has(hash)) continue;
4995
+ if (this.lastSeenSignatures.get(dedupKey) === signature) continue;
4996
+ if (role !== "system" && this.lastSeenTurnSignatures.get(dedupKey) === signature) continue;
4915
4997
  seenHashes.add(hash);
4998
+ this.lastSeenSignatures.set(dedupKey, signature);
4999
+ if (role !== "system") {
5000
+ this.lastSeenTurnSignatures.set(dedupKey, signature);
5001
+ }
4916
5002
  newMessages.push({
4917
- ts: new Date(msg.receivedAt || Date.now()).toISOString(),
4918
- receivedAt: msg.receivedAt || Date.now(),
4919
- role: msg.role,
4920
- content: msg.content || "",
5003
+ ts: new Date(receivedAt).toISOString(),
5004
+ receivedAt,
5005
+ role,
5006
+ content,
4921
5007
  kind: typeof msg.kind === "string" ? msg.kind : void 0,
4922
5008
  senderName: typeof msg.senderName === "string" ? msg.senderName : void 0,
4923
5009
  agent: agentType,
@@ -4937,6 +5023,8 @@ var ChatHistoryWriter = class {
4937
5023
  const prevCount = this.lastSeenCounts.get(dedupKey) || 0;
4938
5024
  if (messages.length < prevCount * 0.5 && prevCount > 3) {
4939
5025
  seenHashes.clear();
5026
+ this.lastSeenSignatures.delete(dedupKey);
5027
+ this.lastSeenTurnSignatures.delete(dedupKey);
4940
5028
  for (const msg of messages) {
4941
5029
  seenHashes.add(msg.historyDedupKey || `${msg.kind || "standard"}:${msg.role}:${(msg.content || "").slice(0, 50)}`);
4942
5030
  }
@@ -4950,6 +5038,54 @@ var ChatHistoryWriter = class {
4950
5038
  } catch {
4951
5039
  }
4952
5040
  }
5041
+ seedSessionHistory(agentType, messages = [], historySessionId, instanceId) {
5042
+ const effectiveHistoryKey = historySessionId || instanceId;
5043
+ const dedupKey = effectiveHistoryKey ? `${agentType}:${effectiveHistoryKey}` : agentType;
5044
+ const seenHashes = /* @__PURE__ */ new Set();
5045
+ for (const raw of messages) {
5046
+ const role = raw?.role;
5047
+ if (role !== "user" && role !== "assistant" && role !== "system") continue;
5048
+ const content = cleanupHistoryContent(agentType, role, raw?.content || "");
5049
+ if (!content) continue;
5050
+ seenHashes.add(buildHistoryMessageHash(agentType, {
5051
+ role,
5052
+ content,
5053
+ receivedAt: raw?.receivedAt || 0,
5054
+ kind: typeof raw?.kind === "string" ? raw.kind : void 0,
5055
+ historyDedupKey: raw?.historyDedupKey
5056
+ }));
5057
+ }
5058
+ this.lastSeenHashes.set(dedupKey, seenHashes);
5059
+ this.lastSeenCounts.set(dedupKey, messages.length);
5060
+ const lastMessage = [...messages].reverse().find((raw) => {
5061
+ const role = raw?.role;
5062
+ if (role !== "user" && role !== "assistant" && role !== "system") return false;
5063
+ return !!cleanupHistoryContent(agentType, role, raw?.content || "");
5064
+ });
5065
+ const lastTurnMessage = [...messages].reverse().find((raw) => {
5066
+ const role = raw?.role;
5067
+ if (role !== "user" && role !== "assistant") return false;
5068
+ return !!cleanupHistoryContent(agentType, role, raw?.content || "");
5069
+ });
5070
+ if (lastMessage) {
5071
+ this.lastSeenSignatures.set(dedupKey, buildHistoryMessageSignature(agentType, {
5072
+ role: lastMessage.role,
5073
+ content: lastMessage.content,
5074
+ kind: typeof lastMessage.kind === "string" ? lastMessage.kind : void 0
5075
+ }));
5076
+ } else {
5077
+ this.lastSeenSignatures.delete(dedupKey);
5078
+ }
5079
+ if (lastTurnMessage) {
5080
+ this.lastSeenTurnSignatures.set(dedupKey, buildHistoryMessageSignature(agentType, {
5081
+ role: lastTurnMessage.role,
5082
+ content: lastTurnMessage.content,
5083
+ kind: typeof lastTurnMessage.kind === "string" ? lastTurnMessage.kind : void 0
5084
+ }));
5085
+ } else {
5086
+ this.lastSeenTurnSignatures.delete(dedupKey);
5087
+ }
5088
+ }
4953
5089
  appendSystemMarker(agentType, content, options = {}) {
4954
5090
  this.appendNewMessages(
4955
5091
  agentType,
@@ -4980,6 +5116,16 @@ var ChatHistoryWriter = class {
4980
5116
  this.lastSeenHashes.set(toDedupKey, nextHashes);
4981
5117
  this.lastSeenHashes.delete(fromDedupKey);
4982
5118
  }
5119
+ const fromSignature = this.lastSeenSignatures.get(fromDedupKey);
5120
+ if (fromSignature) {
5121
+ this.lastSeenSignatures.set(toDedupKey, fromSignature);
5122
+ this.lastSeenSignatures.delete(fromDedupKey);
5123
+ }
5124
+ const fromTurnSignature = this.lastSeenTurnSignatures.get(fromDedupKey);
5125
+ if (fromTurnSignature) {
5126
+ this.lastSeenTurnSignatures.set(toDedupKey, fromTurnSignature);
5127
+ this.lastSeenTurnSignatures.delete(fromDedupKey);
5128
+ }
4983
5129
  const fromCount = this.lastSeenCounts.get(fromDedupKey);
4984
5130
  if (typeof fromCount === "number") {
4985
5131
  this.lastSeenCounts.set(toDedupKey, Math.max(fromCount, this.lastSeenCounts.get(toDedupKey) || 0));
@@ -5021,10 +5167,61 @@ var ChatHistoryWriter = class {
5021
5167
  } catch {
5022
5168
  }
5023
5169
  }
5170
+ compactHistorySession(agentType, historySessionId) {
5171
+ const sessionId = String(historySessionId || "").trim();
5172
+ if (!sessionId) return;
5173
+ try {
5174
+ const dir = path7.join(HISTORY_DIR, this.sanitize(agentType));
5175
+ if (!fs3.existsSync(dir)) return;
5176
+ const prefix = `${this.sanitize(sessionId)}_`;
5177
+ const files = fs3.readdirSync(dir).filter((file) => file.startsWith(prefix) && file.endsWith(".jsonl")).sort();
5178
+ const seen = /* @__PURE__ */ new Set();
5179
+ for (const file of files) {
5180
+ const filePath = path7.join(dir, file);
5181
+ const lines = fs3.readFileSync(filePath, "utf-8").split("\n").filter(Boolean);
5182
+ const next = [];
5183
+ for (const line of lines) {
5184
+ let parsed = null;
5185
+ try {
5186
+ parsed = JSON.parse(line);
5187
+ } catch {
5188
+ parsed = null;
5189
+ }
5190
+ if (!parsed || parsed.historySessionId !== sessionId) continue;
5191
+ const sanitized = sanitizeHistoryMessage(agentType, parsed);
5192
+ if (!sanitized) continue;
5193
+ const hash = buildHistoryMessageHash(agentType, sanitized);
5194
+ if (seen.has(hash)) continue;
5195
+ seen.add(hash);
5196
+ next.push(sanitized);
5197
+ }
5198
+ next.sort((a, b) => a.receivedAt - b.receivedAt);
5199
+ const dedupedAdjacent = [];
5200
+ let lastTurn = null;
5201
+ for (const entry of next) {
5202
+ const previous = dedupedAdjacent[dedupedAdjacent.length - 1];
5203
+ if (isAdjacentHistoryDuplicate(agentType, previous, entry)) continue;
5204
+ if (entry.role !== "system" && isAdjacentHistoryDuplicate(agentType, lastTurn, entry)) continue;
5205
+ dedupedAdjacent.push(entry);
5206
+ if (entry.role !== "system") lastTurn = entry;
5207
+ }
5208
+ const collapsed = collapseReplayAssistantTurns(agentType, dedupedAdjacent);
5209
+ if (collapsed.length === 0) {
5210
+ fs3.unlinkSync(filePath);
5211
+ continue;
5212
+ }
5213
+ fs3.writeFileSync(filePath, `${collapsed.map((entry) => JSON.stringify(entry)).join("\n")}
5214
+ `, "utf-8");
5215
+ }
5216
+ } catch {
5217
+ }
5218
+ }
5024
5219
  /** Called when agent session is explicitly changed */
5025
5220
  onSessionChange(agentType) {
5026
5221
  this.lastSeenHashes.delete(agentType);
5027
5222
  this.lastSeenCounts.delete(agentType);
5223
+ this.lastSeenSignatures.delete(agentType);
5224
+ this.lastSeenTurnSignatures.delete(agentType);
5028
5225
  }
5029
5226
  /** Delete history files older than 30 days */
5030
5227
  async rotateOldFiles() {
@@ -5065,23 +5262,37 @@ function readChatHistory(agentType, offset = 0, limit = 30, historySessionId) {
5065
5262
  return true;
5066
5263
  }).sort().reverse();
5067
5264
  const allMessages = [];
5068
- const needed = offset + limit + 1;
5265
+ const seen = /* @__PURE__ */ new Set();
5069
5266
  for (const file of files) {
5070
- if (allMessages.length >= needed) break;
5071
5267
  const filePath = path7.join(dir, file);
5072
5268
  const content = fs3.readFileSync(filePath, "utf-8");
5073
5269
  const lines = content.trim().split("\n").filter(Boolean);
5074
- for (let i = lines.length - 1; i >= 0; i--) {
5075
- if (allMessages.length >= needed) break;
5270
+ for (let i = 0; i < lines.length; i++) {
5076
5271
  try {
5077
- allMessages.push(JSON.parse(lines[i]));
5272
+ const parsed = JSON.parse(lines[i]);
5273
+ const sanitizedMessage = sanitizeHistoryMessage(agentType, parsed);
5274
+ if (!sanitizedMessage) continue;
5275
+ const hash = buildHistoryMessageHash(agentType, sanitizedMessage);
5276
+ if (seen.has(hash)) continue;
5277
+ seen.add(hash);
5278
+ allMessages.push(sanitizedMessage);
5078
5279
  } catch {
5079
5280
  }
5080
5281
  }
5081
5282
  }
5082
- const sliced = allMessages.slice(offset, offset + limit);
5083
- const hasMore = allMessages.length > offset + limit;
5084
- sliced.reverse();
5283
+ allMessages.sort((a, b) => a.receivedAt - b.receivedAt);
5284
+ const chronological = [];
5285
+ let lastTurn = null;
5286
+ for (const message of allMessages) {
5287
+ const previous = chronological[chronological.length - 1];
5288
+ if (isAdjacentHistoryDuplicate(agentType, previous, message)) continue;
5289
+ if (message.role !== "system" && isAdjacentHistoryDuplicate(agentType, lastTurn, message)) continue;
5290
+ chronological.push(message);
5291
+ if (message.role !== "system") lastTurn = message;
5292
+ }
5293
+ const collapsed = collapseReplayAssistantTurns(agentType, chronological);
5294
+ const sliced = collapsed.slice(offset, offset + limit);
5295
+ const hasMore = collapsed.length > offset + limit;
5085
5296
  return { messages: sliced, hasMore };
5086
5297
  } catch {
5087
5298
  return { messages: [], hasMore: false };
@@ -5503,6 +5714,55 @@ ${effect.notification.body || ""}`.trim();
5503
5714
 
5504
5715
  // src/providers/ide-provider-instance.ts
5505
5716
  init_logger();
5717
+
5718
+ // src/providers/approval-utils.ts
5719
+ var DEFAULT_APPROVAL_POSITIVE_HINTS = [
5720
+ "run",
5721
+ "approve",
5722
+ "accept",
5723
+ "allow once",
5724
+ "always allow",
5725
+ "allow",
5726
+ "yes",
5727
+ "proceed",
5728
+ "continue",
5729
+ "confirm",
5730
+ "save",
5731
+ "ok",
5732
+ "trust"
5733
+ ];
5734
+ function normalizeApprovalLabel(value) {
5735
+ return String(value || "").toLowerCase().replace(/[^\p{L}\p{N}]+/gu, " ").trim();
5736
+ }
5737
+ function getApprovalPositiveHints(provider) {
5738
+ const customHints = Array.isArray(provider?.approvalPositiveHints) ? provider.approvalPositiveHints.map((hint) => normalizeApprovalLabel(String(hint || ""))).filter(Boolean) : [];
5739
+ return customHints.length > 0 ? customHints : DEFAULT_APPROVAL_POSITIVE_HINTS;
5740
+ }
5741
+ function pickApprovalButton(buttons, provider) {
5742
+ const labels = (buttons || []).map((button) => String(button || "").trim()).filter(Boolean);
5743
+ if (labels.length === 0) {
5744
+ return { index: 0, label: "Approve" };
5745
+ }
5746
+ const normalizedButtons = labels.map((label) => normalizeApprovalLabel(label));
5747
+ const hints = getApprovalPositiveHints(provider);
5748
+ for (const hint of hints) {
5749
+ const exactIndex = normalizedButtons.findIndex((label) => label === hint);
5750
+ if (exactIndex >= 0) return { index: exactIndex, label: labels[exactIndex] };
5751
+ const prefixIndex = normalizedButtons.findIndex((label) => label.startsWith(hint));
5752
+ if (prefixIndex >= 0) return { index: prefixIndex, label: labels[prefixIndex] };
5753
+ const includeIndex = normalizedButtons.findIndex((label) => label.includes(hint));
5754
+ if (includeIndex >= 0) return { index: includeIndex, label: labels[includeIndex] };
5755
+ }
5756
+ return { index: 0, label: labels[0] };
5757
+ }
5758
+ function formatAutoApprovalMessage(modalMessage, buttonLabel) {
5759
+ const lines = [`Auto-approved${buttonLabel ? `: ${buttonLabel}` : ""}`];
5760
+ const cleanMessage = String(modalMessage || "").trim();
5761
+ if (cleanMessage) lines.push(cleanMessage);
5762
+ return lines.join("\n");
5763
+ }
5764
+
5765
+ // src/providers/ide-provider-instance.ts
5506
5766
  var IdeProviderInstance = class {
5507
5767
  type;
5508
5768
  category = "ide";
@@ -5569,6 +5829,8 @@ var IdeProviderInstance = class {
5569
5829
  }
5570
5830
  getState() {
5571
5831
  const cdp = this.context?.cdp;
5832
+ const autoApproveActive = (this.currentStatus === "waiting_approval" || this.cachedChat?.status === "waiting_approval") && this.canAutoApprove();
5833
+ const visibleStatus = autoApproveActive ? "generating" : this.currentStatus;
5572
5834
  const extensionStates = [];
5573
5835
  for (const ext of this.extensions.values()) {
5574
5836
  extensionStates.push(ext.getState());
@@ -5577,13 +5839,13 @@ var IdeProviderInstance = class {
5577
5839
  type: this.type,
5578
5840
  name: this.provider.name,
5579
5841
  category: "ide",
5580
- status: this.currentStatus,
5842
+ status: visibleStatus,
5581
5843
  activeChat: this.cachedChat ? {
5582
5844
  id: this.cachedChat.id || "active_session",
5583
5845
  title: this.cachedChat.title || this.type,
5584
- status: this.cachedChat.status || this.currentStatus,
5846
+ status: autoApproveActive && this.cachedChat.status === "waiting_approval" ? "generating" : this.cachedChat.status || visibleStatus,
5585
5847
  messages: this.mergeConversationMessages(this.cachedChat.messages || []),
5586
- activeModal: this.cachedChat.activeModal || null,
5848
+ activeModal: autoApproveActive ? null : this.cachedChat.activeModal || null,
5587
5849
  inputContent: this.cachedChat.inputContent || ""
5588
5850
  } : null,
5589
5851
  workspace: this.workspace || null,
@@ -5802,7 +6064,9 @@ var IdeProviderInstance = class {
5802
6064
  const chatStatus = chatData?.status;
5803
6065
  if (!chatStatus) return;
5804
6066
  const agentKey = `${this.type}:native`;
5805
- const agentStatus = chatStatus === "streaming" || chatStatus === "generating" ? "generating" : chatStatus === "waiting_approval" ? "waiting_approval" : "idle";
6067
+ const rawAgentStatus = chatStatus === "streaming" || chatStatus === "generating" ? "generating" : chatStatus === "waiting_approval" ? "waiting_approval" : "idle";
6068
+ const autoApproveActive = rawAgentStatus === "waiting_approval" && this.canAutoApprove();
6069
+ const agentStatus = autoApproveActive ? "generating" : rawAgentStatus;
5806
6070
  const lastMsg = Array.isArray(chatData?.messages) && chatData.messages.length > 0 ? chatData.messages[chatData.messages.length - 1] : null;
5807
6071
  const progressFingerprint = agentStatus === "generating" ? `${lastMsg?.role || ""}:${typeof lastMsg?.content === "string" ? lastMsg.content : JSON.stringify(lastMsg?.content || "")}`.slice(-2e3) : void 0;
5808
6072
  this.currentStatus = agentStatus;
@@ -5834,7 +6098,7 @@ var IdeProviderInstance = class {
5834
6098
  this.applyProviderResponse(chatData, {
5835
6099
  phase: agentStatus === "idle" && (lastStatus === "generating" || lastStatus === "waiting_approval") ? "turn_completed" : "immediate"
5836
6100
  });
5837
- if (agentStatus === "waiting_approval" && this.settings.autoApprove && !this.autoApproveBusy) {
6101
+ if (rawAgentStatus === "waiting_approval" && autoApproveActive && !this.autoApproveBusy) {
5838
6102
  this.autoApproveViaScript(chatData);
5839
6103
  }
5840
6104
  const monitorEvents = this.monitor.check(agentKey, agentStatus, now, progressFingerprint);
@@ -5985,6 +6249,9 @@ ${effect.notification.body || ""}`.trim();
5985
6249
  updateCdp(cdp) {
5986
6250
  if (this.context) this.context.cdp = cdp;
5987
6251
  }
6252
+ canAutoApprove() {
6253
+ return this.settings.autoApprove !== false && typeof this.provider.scripts?.resolveAction === "function" && !!this.context?.cdp?.isConnected;
6254
+ }
5988
6255
  // ─── Auto-approve via CDP script ────────────────────
5989
6256
  async autoApproveViaScript(_chatData) {
5990
6257
  const cdp = this.context?.cdp;
@@ -5996,17 +6263,15 @@ ${effect.notification.body || ""}`.trim();
5996
6263
  }
5997
6264
  this.autoApproveBusy = true;
5998
6265
  try {
5999
- let targetButton = _chatData?.activeModal?.buttons?.[0] || "Run";
6000
- const buttons = _chatData?.activeModal?.buttons || [];
6001
- for (const b of buttons) {
6002
- const lower = String(b).toLowerCase().replace(/[^\w]/g, "");
6003
- if (/^(run|approve|accept|yes|allow|always|proceed|save)/.test(lower)) {
6004
- targetButton = b;
6005
- break;
6006
- }
6007
- }
6266
+ const { label: targetButton } = pickApprovalButton(_chatData?.activeModal?.buttons, this.provider);
6008
6267
  const script = scriptFn({ action: "approve", button: targetButton, buttonText: targetButton });
6009
6268
  if (!script) return;
6269
+ const now = Date.now();
6270
+ this.appendRuntimeSystemMessage(
6271
+ formatAutoApprovalMessage(_chatData?.activeModal?.message, targetButton),
6272
+ `auto_approval:${now}:${targetButton}`,
6273
+ now
6274
+ );
6010
6275
  LOG.info("IdeInstance", `[IdeInstance:${this.type}] autoApprove: executing resolveAction for "${targetButton}"`);
6011
6276
  let rawResult = await cdp.evaluate(script, 1e4);
6012
6277
  if (typeof rawResult === "string") {
@@ -6028,12 +6293,6 @@ ${effect.notification.body || ""}`.trim();
6028
6293
  LOG.warn("IdeInstance", `[IdeInstance:${this.type}] autoApprove: cdp.send() not available for coordinate click`);
6029
6294
  }
6030
6295
  }
6031
- this.pushEvent({
6032
- event: "agent:auto_approved",
6033
- chatTitle: _chatData?.title || this.provider.name,
6034
- timestamp: Date.now(),
6035
- ideType: this.type
6036
- });
6037
6296
  } catch (e) {
6038
6297
  LOG.warn("IdeInstance", `[IdeInstance:${this.type}] autoApprove error: ${e?.message}`);
6039
6298
  } finally {
@@ -6935,6 +7194,46 @@ function didProviderConfirmSend(result) {
6935
7194
  if (!parsed || typeof parsed !== "object") return false;
6936
7195
  return parsed.sent === true || parsed.success === true || parsed.ok === true || parsed.submitted === true || parsed.dispatched === true;
6937
7196
  }
7197
+ async function readExtensionChatState(h) {
7198
+ try {
7199
+ const evalResult = await h.evaluateProviderScript("readChat", void 0, 5e4);
7200
+ if (!evalResult?.result) return null;
7201
+ const parsed = parseMaybeJson(evalResult.result);
7202
+ return parsed && typeof parsed === "object" ? parsed : null;
7203
+ } catch {
7204
+ return null;
7205
+ }
7206
+ }
7207
+ function getStateMessageCount(state) {
7208
+ return Array.isArray(state?.messages) ? state.messages.length : 0;
7209
+ }
7210
+ function getStateLastSignature(state) {
7211
+ const messages = Array.isArray(state?.messages) ? state.messages : [];
7212
+ const last = messages[messages.length - 1];
7213
+ if (!last) return "";
7214
+ return `${last.role || ""}:${String(last.content || "").replace(/\s+/g, " ").trim()}`;
7215
+ }
7216
+ async function getStableExtensionBaseline(h) {
7217
+ const first = await readExtensionChatState(h);
7218
+ if (getStateMessageCount(first) > 0 || getStateLastSignature(first)) return first;
7219
+ await new Promise((resolve12) => setTimeout(resolve12, 150));
7220
+ const second = await readExtensionChatState(h);
7221
+ return getStateMessageCount(second) >= getStateMessageCount(first) ? second : first;
7222
+ }
7223
+ async function verifyExtensionSendObserved(h, before) {
7224
+ const beforeCount = getStateMessageCount(before);
7225
+ const beforeSignature = getStateLastSignature(before);
7226
+ for (let attempt = 0; attempt < 12; attempt += 1) {
7227
+ await new Promise((resolve12) => setTimeout(resolve12, 250));
7228
+ const state = await readExtensionChatState(h);
7229
+ if (state?.status === "waiting_approval") return true;
7230
+ const afterCount = getStateMessageCount(state);
7231
+ const afterSignature = getStateLastSignature(state);
7232
+ if (afterCount > beforeCount) return true;
7233
+ if (afterSignature && afterSignature !== beforeSignature) return true;
7234
+ }
7235
+ return false;
7236
+ }
6938
7237
  async function handleChatHistory(h, args) {
6939
7238
  const { agentType, offset, limit } = args;
6940
7239
  const historySessionId = getHistorySessionId(h, args);
@@ -7115,12 +7414,17 @@ async function handleSendChat(h, args) {
7115
7414
  if (isExtensionTransport(transport)) {
7116
7415
  _log(`Extension: ${provider?.type || "unknown_extension"}`);
7117
7416
  try {
7417
+ const beforeState = await getStableExtensionBaseline(h);
7118
7418
  const evalResult = await h.evaluateProviderScript("sendMessage", { message: text }, 3e4);
7119
7419
  if (evalResult?.result) {
7120
7420
  const parsed = parseMaybeJson(evalResult.result);
7121
7421
  if (didProviderConfirmSend(parsed)) {
7122
- _log(`Extension script sent OK`);
7123
- return _logSendSuccess("extension-script");
7422
+ const observed = await verifyExtensionSendObserved(h, beforeState);
7423
+ if (observed) {
7424
+ _log(`Extension script sent OK`);
7425
+ return _logSendSuccess("extension-script");
7426
+ }
7427
+ _log(`Extension script reported send but no chat-state change was observed`);
7124
7428
  }
7125
7429
  if (parsed?.needsTypeAndSend) {
7126
7430
  _log(`Extension needsTypeAndSend \u2192 AgentStreamManager`);
@@ -7625,7 +7929,7 @@ async function handleResolveAction(h, args) {
7625
7929
  return { success: true, buttonIndex, button: buttons[buttonIndex] ?? button };
7626
7930
  }
7627
7931
  if (isExtensionTransport(transport) && h.agentStream && h.getCdp() && h.currentSession?.sessionId) {
7628
- const ok = await h.agentStream.resolveSessionAction(h.getCdp(), h.currentSession.sessionId, action);
7932
+ const ok = await h.agentStream.resolveSessionAction(h.getCdp(), h.currentSession.sessionId, action, button);
7629
7933
  return { success: ok };
7630
7934
  }
7631
7935
  if (transport === "acp") {
@@ -9032,6 +9336,7 @@ var CliProviderInstance = class {
9032
9336
  historyWriter;
9033
9337
  runtimeMessages = [];
9034
9338
  instanceId;
9339
+ suppressIdleHistoryReplay = false;
9035
9340
  presentationMode;
9036
9341
  providerSessionId;
9037
9342
  launchMode;
@@ -9059,7 +9364,15 @@ var CliProviderInstance = class {
9059
9364
  await this.adapter.spawn();
9060
9365
  this.maybeAppendRuntimeRecoveryMessage(this.adapter.getRuntimeMetadata());
9061
9366
  if (this.providerSessionId) {
9367
+ this.historyWriter.compactHistorySession(this.type, this.providerSessionId);
9062
9368
  const restoredHistory = readChatHistory(this.type, 0, 200, this.providerSessionId);
9369
+ this.historyWriter.seedSessionHistory(
9370
+ this.type,
9371
+ restoredHistory.messages,
9372
+ this.providerSessionId,
9373
+ this.instanceId
9374
+ );
9375
+ this.suppressIdleHistoryReplay = restoredHistory.messages.length > 0;
9063
9376
  if (restoredHistory.messages.length > 0) {
9064
9377
  this.adapter.seedCommittedMessages(
9065
9378
  restoredHistory.messages.map((message) => ({
@@ -9103,7 +9416,7 @@ var CliProviderInstance = class {
9103
9416
  } else if (this.type === "codex-cli") {
9104
9417
  probedSessionId = this.probeSessionIdFromConfig({
9105
9418
  dbPath: "~/.codex/state_5.sqlite",
9106
- query: "select id from threads where cwd in ({dirs}) and created_at >= ? and archived = 0 order by created_at desc limit 1",
9419
+ query: "select id from threads where cwd in ({dirs}) and updated_at >= ? and archived = 0 order by updated_at desc limit 1",
9107
9420
  timestampFormat: "unix_s"
9108
9421
  });
9109
9422
  } else if (this.type === "goose-cli") {
@@ -9147,6 +9460,8 @@ var CliProviderInstance = class {
9147
9460
  getState() {
9148
9461
  const adapterStatus = this.adapter.getStatus();
9149
9462
  const parsedStatus = this.adapter.getScriptParsedStatus?.() || null;
9463
+ const autoApproveActive = adapterStatus.status === "waiting_approval" && this.shouldAutoApprove();
9464
+ const visibleStatus = autoApproveActive ? "generating" : adapterStatus.status;
9150
9465
  const parsedProviderSessionId = typeof parsedStatus?.providerSessionId === "string" ? parsedStatus.providerSessionId.trim() : "";
9151
9466
  if (parsedProviderSessionId) {
9152
9467
  this.promoteProviderSessionId(parsedProviderSessionId);
@@ -9163,6 +9478,7 @@ var CliProviderInstance = class {
9163
9478
  const mergedMessages = this.mergeConversationMessages(parsedMessages);
9164
9479
  const dirName = this.workingDir.split("/").filter(Boolean).pop() || "session";
9165
9480
  if (parsedMessages.length > 0) {
9481
+ const shouldSkipReplayPersist = this.suppressIdleHistoryReplay && adapterStatus.status === "idle" && parsedStatus?.status === "idle";
9166
9482
  let messagesToSave = parsedMessages;
9167
9483
  if (parsedStatus?.status === "generating" || parsedStatus?.status === "long_generating") {
9168
9484
  const lastIdx = messagesToSave.length - 1;
@@ -9170,7 +9486,7 @@ var CliProviderInstance = class {
9170
9486
  messagesToSave = messagesToSave.slice(0, lastIdx);
9171
9487
  }
9172
9488
  }
9173
- if (messagesToSave.length > 0) {
9489
+ if (!shouldSkipReplayPersist && messagesToSave.length > 0) {
9174
9490
  this.historyWriter.appendNewMessages(
9175
9491
  this.type,
9176
9492
  messagesToSave,
@@ -9185,14 +9501,14 @@ var CliProviderInstance = class {
9185
9501
  type: this.type,
9186
9502
  name: this.provider.name,
9187
9503
  category: "cli",
9188
- status: adapterStatus.status,
9504
+ status: visibleStatus,
9189
9505
  mode: this.presentationMode,
9190
9506
  activeChat: {
9191
9507
  id: `${this.type}_${this.workingDir}`,
9192
9508
  title: parsedStatus?.title || dirName,
9193
- status: parsedStatus?.status || adapterStatus.status,
9509
+ status: autoApproveActive && parsedStatus?.status === "waiting_approval" ? "generating" : parsedStatus?.status || visibleStatus,
9194
9510
  messages: mergedMessages,
9195
- activeModal: parsedStatus?.activeModal ?? adapterStatus.activeModal,
9511
+ activeModal: autoApproveActive ? null : parsedStatus?.activeModal ?? adapterStatus.activeModal,
9196
9512
  inputContent: ""
9197
9513
  },
9198
9514
  workspace: this.workingDir,
@@ -9256,7 +9572,16 @@ var CliProviderInstance = class {
9256
9572
  const now = Date.now();
9257
9573
  const adapterStatus = this.adapter.getStatus();
9258
9574
  const parsedStatus = this.adapter.getScriptParsedStatus?.() || null;
9259
- const newStatus = adapterStatus.status;
9575
+ const rawStatus = adapterStatus.status;
9576
+ const autoApproveActive = rawStatus === "waiting_approval" && this.shouldAutoApprove();
9577
+ if (autoApproveActive) {
9578
+ const { index: buttonIndex, label: buttonLabel } = pickApprovalButton(adapterStatus.activeModal?.buttons, this.provider);
9579
+ this.recordAutoApproval(adapterStatus.activeModal?.message, buttonLabel, now);
9580
+ setTimeout(() => {
9581
+ this.adapter.resolveModal(buttonIndex);
9582
+ }, 0);
9583
+ }
9584
+ const newStatus = autoApproveActive ? "generating" : rawStatus;
9260
9585
  const dirName = this.workingDir.split("/").filter(Boolean).pop() || "session";
9261
9586
  const chatTitle = `${this.provider.name} \xB7 ${dirName}`;
9262
9587
  const partial = this.adapter.getPartialResponse();
@@ -9265,6 +9590,7 @@ var CliProviderInstance = class {
9265
9590
  if (newStatus !== this.lastStatus) {
9266
9591
  LOG.info("CLI", `[${this.type}] status: ${this.lastStatus} \u2192 ${newStatus}`);
9267
9592
  if (this.lastStatus === "idle" && newStatus === "generating") {
9593
+ this.suppressIdleHistoryReplay = false;
9268
9594
  if (this.completedDebouncePending) {
9269
9595
  LOG.info("CLI", `[${this.type}] cancelled pending completed (resumed generating)`);
9270
9596
  if (this.completedDebounceTimer) {
@@ -9284,6 +9610,7 @@ var CliProviderInstance = class {
9284
9610
  this.generatingDebounceTimer = null;
9285
9611
  }, 1e3);
9286
9612
  } else if (newStatus === "waiting_approval") {
9613
+ this.suppressIdleHistoryReplay = false;
9287
9614
  if (this.generatingDebouncePending) {
9288
9615
  if (this.generatingDebounceTimer) {
9289
9616
  clearTimeout(this.generatingDebounceTimer);
@@ -9464,6 +9791,16 @@ ${effect.notification.body || ""}`.trim();
9464
9791
  get cliName() {
9465
9792
  return this.provider.name;
9466
9793
  }
9794
+ shouldAutoApprove() {
9795
+ return this.settings.autoApprove !== false;
9796
+ }
9797
+ recordAutoApproval(modalMessage, buttonLabel, now = Date.now()) {
9798
+ this.appendRuntimeSystemMessage(
9799
+ formatAutoApprovalMessage(modalMessage, buttonLabel),
9800
+ `auto_approval:${now}:${buttonLabel || "approve"}`,
9801
+ now
9802
+ );
9803
+ }
9467
9804
  recordApprovalSelection(buttonText) {
9468
9805
  const cleanButton = String(buttonText || "").trim();
9469
9806
  if (!cleanButton) return;
@@ -10042,8 +10379,10 @@ var AcpProviderInstance = class {
10042
10379
  input: tc.rawInput ? typeof tc.rawInput === "string" ? tc.rawInput : JSON.stringify(tc.rawInput) : void 0
10043
10380
  });
10044
10381
  }
10045
- if (this.settings.autoApprove) {
10046
- this.log.info(`[${this.type}] Auto-approving: ${tc.title || tc.toolCallId}`);
10382
+ if (this.settings.autoApprove !== false) {
10383
+ const toolTitle = tc.title || tc.toolCallId || "tool call";
10384
+ this.log.info(`[${this.type}] Auto-approving: ${toolTitle}`);
10385
+ this.appendSystemMessage(`Auto-approved: ${toolTitle}`);
10047
10386
  const allowOption = params.options.find((o) => o.kind === "allow_once") || params.options.find((o) => o.kind === "allow_always");
10048
10387
  if (allowOption) {
10049
10388
  return { outcome: { outcome: "selected", optionId: allowOption.optionId } };
@@ -10495,6 +10834,18 @@ var AcpProviderInstance = class {
10495
10834
  this.events.push(event);
10496
10835
  if (this.events.length > 50) this.events = this.events.slice(-50);
10497
10836
  }
10837
+ appendSystemMessage(content, timestamp = Date.now()) {
10838
+ const normalizedContent = String(content || "").trim();
10839
+ if (!normalizedContent) return;
10840
+ this.messages.push({
10841
+ role: "system",
10842
+ content: normalizedContent,
10843
+ timestamp
10844
+ });
10845
+ if (this.messages.length > 200) {
10846
+ this.messages = this.messages.slice(-100);
10847
+ }
10848
+ }
10498
10849
  flushEvents() {
10499
10850
  const events = [...this.events];
10500
10851
  this.events = [];
@@ -11001,6 +11352,7 @@ Run 'adhdev doctor' for detailed diagnostics.`
11001
11352
  if (!instanceManager) return 0;
11002
11353
  const sessions = records || await this.deps.listHostedCliRuntimes?.() || [];
11003
11354
  let restored = 0;
11355
+ const restoredBindings = /* @__PURE__ */ new Set();
11004
11356
  for (const record of sessions) {
11005
11357
  if (!record?.runtimeId || !record?.cliType || !record?.workspace) continue;
11006
11358
  if (this.adapters.has(record.runtimeId) || instanceManager.getInstance(record.runtimeId)) continue;
@@ -11014,6 +11366,18 @@ Run 'adhdev doctor' for detailed diagnostics.`
11014
11366
  record.cliArgs,
11015
11367
  record.providerSessionId
11016
11368
  );
11369
+ const bindingKey = [
11370
+ normalizedType,
11371
+ record.workspace,
11372
+ sessionBinding.providerSessionId || record.runtimeId
11373
+ ].join("::");
11374
+ if (restoredBindings.has(bindingKey)) {
11375
+ LOG.info(
11376
+ "CLI",
11377
+ `\u21B7 Skipping duplicate hosted runtime restore: ${record.runtimeKey || record.runtimeId} (${normalizedType} @ ${record.workspace}) binding=${sessionBinding.providerSessionId || "runtime"}`
11378
+ );
11379
+ continue;
11380
+ }
11017
11381
  try {
11018
11382
  await this.registerCliInstance(
11019
11383
  record.runtimeId,
@@ -11029,6 +11393,7 @@ Run 'adhdev doctor' for detailed diagnostics.`
11029
11393
  launchMode: "manual"
11030
11394
  }
11031
11395
  );
11396
+ restoredBindings.add(bindingKey);
11032
11397
  restored += 1;
11033
11398
  LOG.info("CLI", `\u267B Restored hosted runtime: ${record.runtimeKey || record.runtimeId} (${record.displayName || record.workspace})`);
11034
11399
  } catch (error) {
@@ -12055,7 +12420,7 @@ var ProviderLoader = class _ProviderLoader {
12055
12420
  */
12056
12421
  getSettingValue(type, key) {
12057
12422
  const schemaDef = this.getSettingsSchema(type)[key];
12058
- const defaultVal = schemaDef ? schemaDef.default : void 0;
12423
+ const defaultVal = schemaDef ? key === "autoApprove" && schemaDef.type === "boolean" ? true : schemaDef.default : void 0;
12059
12424
  try {
12060
12425
  const { loadConfig: loadConfig2 } = (init_config(), __toCommonJS(config_exports));
12061
12426
  const config = loadConfig2();
@@ -12114,13 +12479,32 @@ var ProviderLoader = class _ProviderLoader {
12114
12479
  getSettingsSchema(type) {
12115
12480
  const provider = this.providers.get(type);
12116
12481
  if (!provider) return {};
12117
- return {
12482
+ const result = {
12118
12483
  ...this.getSyntheticSettings(type, provider),
12119
12484
  ...provider.settings || {}
12120
12485
  };
12486
+ if (result.autoApprove?.type === "boolean") {
12487
+ result.autoApprove = {
12488
+ ...result.autoApprove,
12489
+ default: true,
12490
+ public: true,
12491
+ label: result.autoApprove.label || "Auto Approve",
12492
+ description: result.autoApprove.description || "Automatically approve actionable prompts without sending approval alerts."
12493
+ };
12494
+ }
12495
+ return result;
12121
12496
  }
12122
12497
  getSyntheticSettings(type, provider) {
12123
12498
  const result = {};
12499
+ if (!provider.settings?.autoApprove) {
12500
+ result.autoApprove = {
12501
+ type: "boolean",
12502
+ default: true,
12503
+ public: true,
12504
+ label: "Auto Approve",
12505
+ description: "Automatically approve actionable prompts without sending approval alerts."
12506
+ };
12507
+ }
12124
12508
  if ((provider.category === "cli" || provider.category === "acp") && provider.spawn?.command && !provider.settings?.executablePath) {
12125
12509
  result.executablePath = {
12126
12510
  type: "string",
@@ -13380,6 +13764,15 @@ var DaemonCommandRouter = class {
13380
13764
  const record = await this.deps.sessionHostControl.forceDetachClient(sessionId, clientId);
13381
13765
  return { success: true, record };
13382
13766
  }
13767
+ case "session_host_prune_duplicate_sessions": {
13768
+ if (!this.deps.sessionHostControl) return { success: false, error: "Session host control unavailable" };
13769
+ const result = await this.deps.sessionHostControl.pruneDuplicateSessions({
13770
+ providerType: typeof args?.providerType === "string" ? args.providerType : void 0,
13771
+ workspace: typeof args?.workspace === "string" ? args.workspace : void 0,
13772
+ dryRun: args?.dryRun === true
13773
+ });
13774
+ return { success: true, result };
13775
+ }
13383
13776
  case "session_host_acquire_write": {
13384
13777
  if (!this.deps.sessionHostControl) return { success: false, error: "Session host control unavailable" };
13385
13778
  const sessionId = typeof args?.sessionId === "string" ? args.sessionId : "";
@@ -13965,6 +14358,51 @@ var ProviderStreamAdapter = class {
13965
14358
  isTransportError(reason) {
13966
14359
  return /Session with given id not found/i.test(reason) || /CDP not connected/i.test(reason) || /Target closed/i.test(reason) || /WebSocket not open/i.test(reason) || /not connected/i.test(reason) || /execution context/i.test(reason) || /Cannot find context with specified id/i.test(reason);
13967
14360
  }
14361
+ titlesMatch(actual, expected) {
14362
+ const lhs = actual.trim().toLowerCase();
14363
+ const rhs = expected.trim().toLowerCase();
14364
+ if (!lhs || !rhs) return false;
14365
+ return lhs === rhs || lhs.includes(rhs) || rhs.includes(lhs);
14366
+ }
14367
+ messageCount(state) {
14368
+ return Array.isArray(state?.messages) ? state.messages.length : 0;
14369
+ }
14370
+ lastMessageSignature(state) {
14371
+ const messages = Array.isArray(state?.messages) ? state.messages : [];
14372
+ const last = messages[messages.length - 1];
14373
+ if (!last) return "";
14374
+ return `${last.role || ""}:${String(last.content || "").replace(/\s+/g, " ").trim()}`;
14375
+ }
14376
+ async verifySendOutcome(evaluate, before) {
14377
+ const beforeCount = this.messageCount(before);
14378
+ const beforeSignature = this.lastMessageSignature(before);
14379
+ for (let attempt = 0; attempt < 12; attempt += 1) {
14380
+ await new Promise((resolve12) => setTimeout(resolve12, 250));
14381
+ let state;
14382
+ try {
14383
+ state = await this.readChat(evaluate);
14384
+ } catch {
14385
+ continue;
14386
+ }
14387
+ if (state.status === "waiting_approval") {
14388
+ return true;
14389
+ }
14390
+ const afterCount = this.messageCount(state);
14391
+ const afterSignature = this.lastMessageSignature(state);
14392
+ if (afterCount > beforeCount) return true;
14393
+ if (afterSignature && afterSignature !== beforeSignature) return true;
14394
+ }
14395
+ return false;
14396
+ }
14397
+ async readStableBaselineState(evaluate) {
14398
+ const first = await this.readChat(evaluate);
14399
+ if (this.messageCount(first) > 0 || this.lastMessageSignature(first)) {
14400
+ return first;
14401
+ }
14402
+ await new Promise((resolve12) => setTimeout(resolve12, 150));
14403
+ const second = await this.readChat(evaluate);
14404
+ return this.messageCount(second) >= this.messageCount(first) ? second : first;
14405
+ }
13968
14406
  async readChat(evaluate) {
13969
14407
  const script = this.callScript("readChat");
13970
14408
  if (!script) return this.errorState("readChat script not available");
@@ -13990,6 +14428,9 @@ var ProviderStreamAdapter = class {
13990
14428
  mode: data.mode,
13991
14429
  activeModal: data.activeModal
13992
14430
  };
14431
+ if (typeof data.title === "string" && data.title.trim()) {
14432
+ state.title = data.title.trim();
14433
+ }
13993
14434
  const controlValues = extractProviderControlValues(this.provider.controls, data);
13994
14435
  if (controlValues) state.controlValues = controlValues;
13995
14436
  const effects = normalizeProviderEffects(data);
@@ -14013,6 +14454,12 @@ var ProviderStreamAdapter = class {
14013
14454
  }
14014
14455
  }
14015
14456
  async sendMessage(evaluate, text) {
14457
+ let beforeState = null;
14458
+ try {
14459
+ beforeState = await this.readStableBaselineState(evaluate);
14460
+ } catch {
14461
+ beforeState = null;
14462
+ }
14016
14463
  const params = { message: text };
14017
14464
  const script = this.callScript("sendMessage", params) || this.callScript("sendMessage", text);
14018
14465
  if (!script) throw new Error(`[${this.agentName}] sendMessage script not available`);
@@ -14030,7 +14477,9 @@ var ProviderStreamAdapter = class {
14030
14477
  }
14031
14478
  if (parsed && typeof parsed === "object") {
14032
14479
  if (parsed.sent === true || parsed.success === true || parsed.ok === true || parsed.submitted === true || parsed.dispatched === true) {
14033
- return;
14480
+ const verified = await this.verifySendOutcome(evaluate, beforeState);
14481
+ if (verified) return;
14482
+ throw new Error(`[${this.agentName}] sendMessage was not observed in chat state`);
14034
14483
  }
14035
14484
  if (typeof parsed.error === "string" && parsed.error.trim()) {
14036
14485
  throw new Error(`[${this.agentName}] sendMessage failed: ${parsed.error}`);
@@ -14041,7 +14490,15 @@ var ProviderStreamAdapter = class {
14041
14490
  async resolveAction(evaluate, action, button) {
14042
14491
  const script = this.callScript("resolveAction", { action, button });
14043
14492
  if (!script) return false;
14044
- return await evaluate(script) === true;
14493
+ const result = await evaluate(script);
14494
+ const parsed = this.parseMaybeJson(result);
14495
+ if (parsed === true) return true;
14496
+ if (typeof parsed === "string") {
14497
+ const normalized = parsed.trim().toLowerCase();
14498
+ return normalized === "ok" || normalized === "success" || normalized === "true" || normalized === "resolved" || normalized === "approved" || normalized === "rejected";
14499
+ }
14500
+ if (!parsed || typeof parsed !== "object") return false;
14501
+ return parsed.resolved === true || parsed.success === true || parsed.ok === true || parsed.found === true;
14045
14502
  }
14046
14503
  async newSession(evaluate) {
14047
14504
  const script = this.callScript("newSession");
@@ -14078,7 +14535,14 @@ var ProviderStreamAdapter = class {
14078
14535
  return normalized === "true" || normalized === "ok" || normalized === "switched" || normalized === "success";
14079
14536
  }
14080
14537
  if (data && typeof data === "object") {
14081
- return data.switched === true || data.success === true || data.ok === true;
14538
+ if (data.switched === true || data.success === true || data.ok === true) return true;
14539
+ if (typeof data.error === "string" && data.error.trim()) return false;
14540
+ }
14541
+ for (let attempt = 0; attempt < 6; attempt += 1) {
14542
+ await new Promise((resolve12) => setTimeout(resolve12, 250));
14543
+ const state = await this.readChat(evaluate);
14544
+ const title = typeof state.title === "string" ? state.title : "";
14545
+ if (this.titlesMatch(title, sessionId)) return true;
14082
14546
  }
14083
14547
  return false;
14084
14548
  }
@@ -14286,7 +14750,7 @@ var DaemonAgentStreamManager = class {
14286
14750
  return false;
14287
14751
  }
14288
14752
  }
14289
- async resolveSessionAction(cdp, sessionId, action) {
14753
+ async resolveSessionAction(cdp, sessionId, action, button) {
14290
14754
  await this.ensureSessionPanelOpen(sessionId);
14291
14755
  const target = this.getSessionTarget(sessionId);
14292
14756
  if (!target?.parentSessionId) return false;
@@ -14296,7 +14760,7 @@ var DaemonAgentStreamManager = class {
14296
14760
  if (!agent) return false;
14297
14761
  try {
14298
14762
  const evaluate = (expr, timeout) => cdp.evaluateInSessionFrame(agent.cdpSessionId, expr, timeout);
14299
- return await agent.adapter.resolveAction(evaluate, action);
14763
+ return await agent.adapter.resolveAction(evaluate, action, button);
14300
14764
  } catch (e) {
14301
14765
  this.logFn(`[AgentStream] resolveAction(${sessionId}) error: ${e.message}`);
14302
14766
  return false;
@@ -14526,7 +14990,43 @@ var AgentStreamPoller = class {
14526
14990
  }
14527
14991
  try {
14528
14992
  await agentStreamManager.syncActiveSession(cdp, parentSessionId);
14529
- const stream = await agentStreamManager.collectActiveSession(cdp, parentSessionId);
14993
+ let stream = await agentStreamManager.collectActiveSession(cdp, parentSessionId);
14994
+ if (stream?.status === "waiting_approval") {
14995
+ const autoApprove = providerLoader.getSettings(stream.agentType).autoApprove !== false;
14996
+ if (autoApprove && resolvedActiveSessionId) {
14997
+ const provider = providerLoader.getMeta(stream.agentType);
14998
+ const { label: buttonLabel } = pickApprovalButton(stream.activeModal?.buttons, provider);
14999
+ const approved = await agentStreamManager.resolveSessionAction(cdp, resolvedActiveSessionId, "approve", buttonLabel);
15000
+ if (approved) {
15001
+ const effectId = [
15002
+ "auto_approval",
15003
+ resolvedActiveSessionId,
15004
+ String(stream.messages?.length || 0),
15005
+ buttonLabel,
15006
+ String(stream.activeModal?.message || "").trim()
15007
+ ].join(":");
15008
+ stream = {
15009
+ ...stream,
15010
+ status: "streaming",
15011
+ activeModal: void 0,
15012
+ effects: [
15013
+ ...stream.effects || [],
15014
+ {
15015
+ type: "message",
15016
+ id: effectId,
15017
+ persist: true,
15018
+ message: {
15019
+ role: "system",
15020
+ senderName: "System",
15021
+ kind: "system",
15022
+ content: formatAutoApprovalMessage(stream.activeModal?.message, buttonLabel)
15023
+ }
15024
+ }
15025
+ ]
15026
+ };
15027
+ }
15028
+ }
15029
+ }
14530
15030
  this.deps.onStreamsUpdated?.(ideType, stream ? [stream] : []);
14531
15031
  } catch {
14532
15032
  }