@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.js CHANGED
@@ -1642,6 +1642,9 @@ var init_provider_cli_adapter = __esm({
1642
1642
  looksLikeVisibleIdlePrompt(screenText) {
1643
1643
  const text = String(screenText || "");
1644
1644
  if (!text.trim()) return false;
1645
+ 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)) {
1646
+ return true;
1647
+ }
1645
1648
  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);
1646
1649
  }
1647
1650
  findLastMatchingLineIndex(lines, predicate) {
@@ -4969,11 +4972,72 @@ var path7 = __toESM(require("path"));
4969
4972
  var os5 = __toESM(require("os"));
4970
4973
  var HISTORY_DIR = path7.join(os5.homedir(), ".adhdev", "history");
4971
4974
  var RETAIN_DAYS = 30;
4975
+ 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;
4976
+ function normalizeHistoryComparable(text) {
4977
+ return String(text || "").replace(/\s+/g, " ").trim();
4978
+ }
4979
+ function cleanupHistoryContent(agentType, role, content) {
4980
+ let value = String(content || "").replace(/\r\n/g, "\n").trim();
4981
+ if (!value) return "";
4982
+ if (agentType === "codex-cli" && role === "assistant") {
4983
+ const filtered = value.split("\n").filter((line) => !CODEX_STARTER_PROMPT_RE.test(line.trim())).join("\n").replace(/\n{3,}/g, "\n\n").trim();
4984
+ value = filtered;
4985
+ }
4986
+ return value;
4987
+ }
4988
+ function buildHistoryMessageHash(agentType, message) {
4989
+ if (message.historyDedupKey) return message.historyDedupKey;
4990
+ const cleaned = cleanupHistoryContent(agentType, message.role, message.content);
4991
+ return `${message.kind || "standard"}:${message.role}:${message.receivedAt || 0}:${normalizeHistoryComparable(cleaned)}`;
4992
+ }
4993
+ function buildHistoryMessageSignature(agentType, message) {
4994
+ const cleaned = cleanupHistoryContent(agentType, message.role, message.content);
4995
+ return `${message.kind || "standard"}:${message.role}:${normalizeHistoryComparable(cleaned)}`;
4996
+ }
4997
+ function isAdjacentHistoryDuplicate(agentType, previous, next) {
4998
+ if (!previous || !next) return false;
4999
+ return buildHistoryMessageSignature(agentType, previous) === buildHistoryMessageSignature(agentType, next);
5000
+ }
5001
+ function collapseReplayAssistantTurns(agentType, messages) {
5002
+ if (agentType !== "codex-cli") return messages;
5003
+ const collapsed = [];
5004
+ let sawAssistantSinceLastUser = false;
5005
+ for (const message of messages) {
5006
+ if (message.role === "user") {
5007
+ sawAssistantSinceLastUser = false;
5008
+ collapsed.push(message);
5009
+ continue;
5010
+ }
5011
+ if (message.role === "assistant") {
5012
+ if (sawAssistantSinceLastUser) continue;
5013
+ sawAssistantSinceLastUser = true;
5014
+ collapsed.push(message);
5015
+ continue;
5016
+ }
5017
+ collapsed.push(message);
5018
+ }
5019
+ return collapsed;
5020
+ }
5021
+ function sanitizeHistoryMessage(agentType, message) {
5022
+ if (!message || message.role !== "user" && message.role !== "assistant" && message.role !== "system") {
5023
+ return null;
5024
+ }
5025
+ const content = cleanupHistoryContent(agentType, message.role, message.content);
5026
+ if (!content) return null;
5027
+ return {
5028
+ ...message,
5029
+ content
5030
+ };
5031
+ }
4972
5032
  var ChatHistoryWriter = class {
4973
5033
  /** Last seen message count per agent (deduplication) */
4974
5034
  lastSeenCounts = /* @__PURE__ */ new Map();
4975
5035
  /** Last seen message hash per agent (deduplication) */
4976
5036
  lastSeenHashes = /* @__PURE__ */ new Map();
5037
+ /** Last appended normalized message signature per agent/session */
5038
+ lastSeenSignatures = /* @__PURE__ */ new Map();
5039
+ /** Last appended normalized non-system turn signature per agent/session */
5040
+ lastSeenTurnSignatures = /* @__PURE__ */ new Map();
4977
5041
  rotated = false;
4978
5042
  /**
4979
5043
  * Append new messages to history
@@ -4995,14 +5059,36 @@ var ChatHistoryWriter = class {
4995
5059
  }
4996
5060
  const newMessages = [];
4997
5061
  for (const msg of messages) {
4998
- const hash = msg.historyDedupKey || `${msg.kind || "standard"}:${msg.role}:${(msg.content || "").slice(0, 50)}`;
5062
+ const role = msg.role;
5063
+ if (role !== "user" && role !== "assistant" && role !== "system") continue;
5064
+ const content = cleanupHistoryContent(agentType, role, msg.content || "");
5065
+ if (!content) continue;
5066
+ const receivedAt = msg.receivedAt || Date.now();
5067
+ const hash = buildHistoryMessageHash(agentType, {
5068
+ role,
5069
+ content,
5070
+ receivedAt,
5071
+ kind: typeof msg.kind === "string" ? msg.kind : void 0,
5072
+ historyDedupKey: msg.historyDedupKey
5073
+ });
5074
+ const signature = buildHistoryMessageSignature(agentType, {
5075
+ role,
5076
+ content,
5077
+ kind: typeof msg.kind === "string" ? msg.kind : void 0
5078
+ });
4999
5079
  if (seenHashes.has(hash)) continue;
5080
+ if (this.lastSeenSignatures.get(dedupKey) === signature) continue;
5081
+ if (role !== "system" && this.lastSeenTurnSignatures.get(dedupKey) === signature) continue;
5000
5082
  seenHashes.add(hash);
5083
+ this.lastSeenSignatures.set(dedupKey, signature);
5084
+ if (role !== "system") {
5085
+ this.lastSeenTurnSignatures.set(dedupKey, signature);
5086
+ }
5001
5087
  newMessages.push({
5002
- ts: new Date(msg.receivedAt || Date.now()).toISOString(),
5003
- receivedAt: msg.receivedAt || Date.now(),
5004
- role: msg.role,
5005
- content: msg.content || "",
5088
+ ts: new Date(receivedAt).toISOString(),
5089
+ receivedAt,
5090
+ role,
5091
+ content,
5006
5092
  kind: typeof msg.kind === "string" ? msg.kind : void 0,
5007
5093
  senderName: typeof msg.senderName === "string" ? msg.senderName : void 0,
5008
5094
  agent: agentType,
@@ -5022,6 +5108,8 @@ var ChatHistoryWriter = class {
5022
5108
  const prevCount = this.lastSeenCounts.get(dedupKey) || 0;
5023
5109
  if (messages.length < prevCount * 0.5 && prevCount > 3) {
5024
5110
  seenHashes.clear();
5111
+ this.lastSeenSignatures.delete(dedupKey);
5112
+ this.lastSeenTurnSignatures.delete(dedupKey);
5025
5113
  for (const msg of messages) {
5026
5114
  seenHashes.add(msg.historyDedupKey || `${msg.kind || "standard"}:${msg.role}:${(msg.content || "").slice(0, 50)}`);
5027
5115
  }
@@ -5035,6 +5123,54 @@ var ChatHistoryWriter = class {
5035
5123
  } catch {
5036
5124
  }
5037
5125
  }
5126
+ seedSessionHistory(agentType, messages = [], historySessionId, instanceId) {
5127
+ const effectiveHistoryKey = historySessionId || instanceId;
5128
+ const dedupKey = effectiveHistoryKey ? `${agentType}:${effectiveHistoryKey}` : agentType;
5129
+ const seenHashes = /* @__PURE__ */ new Set();
5130
+ for (const raw of messages) {
5131
+ const role = raw?.role;
5132
+ if (role !== "user" && role !== "assistant" && role !== "system") continue;
5133
+ const content = cleanupHistoryContent(agentType, role, raw?.content || "");
5134
+ if (!content) continue;
5135
+ seenHashes.add(buildHistoryMessageHash(agentType, {
5136
+ role,
5137
+ content,
5138
+ receivedAt: raw?.receivedAt || 0,
5139
+ kind: typeof raw?.kind === "string" ? raw.kind : void 0,
5140
+ historyDedupKey: raw?.historyDedupKey
5141
+ }));
5142
+ }
5143
+ this.lastSeenHashes.set(dedupKey, seenHashes);
5144
+ this.lastSeenCounts.set(dedupKey, messages.length);
5145
+ const lastMessage = [...messages].reverse().find((raw) => {
5146
+ const role = raw?.role;
5147
+ if (role !== "user" && role !== "assistant" && role !== "system") return false;
5148
+ return !!cleanupHistoryContent(agentType, role, raw?.content || "");
5149
+ });
5150
+ const lastTurnMessage = [...messages].reverse().find((raw) => {
5151
+ const role = raw?.role;
5152
+ if (role !== "user" && role !== "assistant") return false;
5153
+ return !!cleanupHistoryContent(agentType, role, raw?.content || "");
5154
+ });
5155
+ if (lastMessage) {
5156
+ this.lastSeenSignatures.set(dedupKey, buildHistoryMessageSignature(agentType, {
5157
+ role: lastMessage.role,
5158
+ content: lastMessage.content,
5159
+ kind: typeof lastMessage.kind === "string" ? lastMessage.kind : void 0
5160
+ }));
5161
+ } else {
5162
+ this.lastSeenSignatures.delete(dedupKey);
5163
+ }
5164
+ if (lastTurnMessage) {
5165
+ this.lastSeenTurnSignatures.set(dedupKey, buildHistoryMessageSignature(agentType, {
5166
+ role: lastTurnMessage.role,
5167
+ content: lastTurnMessage.content,
5168
+ kind: typeof lastTurnMessage.kind === "string" ? lastTurnMessage.kind : void 0
5169
+ }));
5170
+ } else {
5171
+ this.lastSeenTurnSignatures.delete(dedupKey);
5172
+ }
5173
+ }
5038
5174
  appendSystemMarker(agentType, content, options = {}) {
5039
5175
  this.appendNewMessages(
5040
5176
  agentType,
@@ -5065,6 +5201,16 @@ var ChatHistoryWriter = class {
5065
5201
  this.lastSeenHashes.set(toDedupKey, nextHashes);
5066
5202
  this.lastSeenHashes.delete(fromDedupKey);
5067
5203
  }
5204
+ const fromSignature = this.lastSeenSignatures.get(fromDedupKey);
5205
+ if (fromSignature) {
5206
+ this.lastSeenSignatures.set(toDedupKey, fromSignature);
5207
+ this.lastSeenSignatures.delete(fromDedupKey);
5208
+ }
5209
+ const fromTurnSignature = this.lastSeenTurnSignatures.get(fromDedupKey);
5210
+ if (fromTurnSignature) {
5211
+ this.lastSeenTurnSignatures.set(toDedupKey, fromTurnSignature);
5212
+ this.lastSeenTurnSignatures.delete(fromDedupKey);
5213
+ }
5068
5214
  const fromCount = this.lastSeenCounts.get(fromDedupKey);
5069
5215
  if (typeof fromCount === "number") {
5070
5216
  this.lastSeenCounts.set(toDedupKey, Math.max(fromCount, this.lastSeenCounts.get(toDedupKey) || 0));
@@ -5106,10 +5252,61 @@ var ChatHistoryWriter = class {
5106
5252
  } catch {
5107
5253
  }
5108
5254
  }
5255
+ compactHistorySession(agentType, historySessionId) {
5256
+ const sessionId = String(historySessionId || "").trim();
5257
+ if (!sessionId) return;
5258
+ try {
5259
+ const dir = path7.join(HISTORY_DIR, this.sanitize(agentType));
5260
+ if (!fs3.existsSync(dir)) return;
5261
+ const prefix = `${this.sanitize(sessionId)}_`;
5262
+ const files = fs3.readdirSync(dir).filter((file) => file.startsWith(prefix) && file.endsWith(".jsonl")).sort();
5263
+ const seen = /* @__PURE__ */ new Set();
5264
+ for (const file of files) {
5265
+ const filePath = path7.join(dir, file);
5266
+ const lines = fs3.readFileSync(filePath, "utf-8").split("\n").filter(Boolean);
5267
+ const next = [];
5268
+ for (const line of lines) {
5269
+ let parsed = null;
5270
+ try {
5271
+ parsed = JSON.parse(line);
5272
+ } catch {
5273
+ parsed = null;
5274
+ }
5275
+ if (!parsed || parsed.historySessionId !== sessionId) continue;
5276
+ const sanitized = sanitizeHistoryMessage(agentType, parsed);
5277
+ if (!sanitized) continue;
5278
+ const hash = buildHistoryMessageHash(agentType, sanitized);
5279
+ if (seen.has(hash)) continue;
5280
+ seen.add(hash);
5281
+ next.push(sanitized);
5282
+ }
5283
+ next.sort((a, b) => a.receivedAt - b.receivedAt);
5284
+ const dedupedAdjacent = [];
5285
+ let lastTurn = null;
5286
+ for (const entry of next) {
5287
+ const previous = dedupedAdjacent[dedupedAdjacent.length - 1];
5288
+ if (isAdjacentHistoryDuplicate(agentType, previous, entry)) continue;
5289
+ if (entry.role !== "system" && isAdjacentHistoryDuplicate(agentType, lastTurn, entry)) continue;
5290
+ dedupedAdjacent.push(entry);
5291
+ if (entry.role !== "system") lastTurn = entry;
5292
+ }
5293
+ const collapsed = collapseReplayAssistantTurns(agentType, dedupedAdjacent);
5294
+ if (collapsed.length === 0) {
5295
+ fs3.unlinkSync(filePath);
5296
+ continue;
5297
+ }
5298
+ fs3.writeFileSync(filePath, `${collapsed.map((entry) => JSON.stringify(entry)).join("\n")}
5299
+ `, "utf-8");
5300
+ }
5301
+ } catch {
5302
+ }
5303
+ }
5109
5304
  /** Called when agent session is explicitly changed */
5110
5305
  onSessionChange(agentType) {
5111
5306
  this.lastSeenHashes.delete(agentType);
5112
5307
  this.lastSeenCounts.delete(agentType);
5308
+ this.lastSeenSignatures.delete(agentType);
5309
+ this.lastSeenTurnSignatures.delete(agentType);
5113
5310
  }
5114
5311
  /** Delete history files older than 30 days */
5115
5312
  async rotateOldFiles() {
@@ -5150,23 +5347,37 @@ function readChatHistory(agentType, offset = 0, limit = 30, historySessionId) {
5150
5347
  return true;
5151
5348
  }).sort().reverse();
5152
5349
  const allMessages = [];
5153
- const needed = offset + limit + 1;
5350
+ const seen = /* @__PURE__ */ new Set();
5154
5351
  for (const file of files) {
5155
- if (allMessages.length >= needed) break;
5156
5352
  const filePath = path7.join(dir, file);
5157
5353
  const content = fs3.readFileSync(filePath, "utf-8");
5158
5354
  const lines = content.trim().split("\n").filter(Boolean);
5159
- for (let i = lines.length - 1; i >= 0; i--) {
5160
- if (allMessages.length >= needed) break;
5355
+ for (let i = 0; i < lines.length; i++) {
5161
5356
  try {
5162
- allMessages.push(JSON.parse(lines[i]));
5357
+ const parsed = JSON.parse(lines[i]);
5358
+ const sanitizedMessage = sanitizeHistoryMessage(agentType, parsed);
5359
+ if (!sanitizedMessage) continue;
5360
+ const hash = buildHistoryMessageHash(agentType, sanitizedMessage);
5361
+ if (seen.has(hash)) continue;
5362
+ seen.add(hash);
5363
+ allMessages.push(sanitizedMessage);
5163
5364
  } catch {
5164
5365
  }
5165
5366
  }
5166
5367
  }
5167
- const sliced = allMessages.slice(offset, offset + limit);
5168
- const hasMore = allMessages.length > offset + limit;
5169
- sliced.reverse();
5368
+ allMessages.sort((a, b) => a.receivedAt - b.receivedAt);
5369
+ const chronological = [];
5370
+ let lastTurn = null;
5371
+ for (const message of allMessages) {
5372
+ const previous = chronological[chronological.length - 1];
5373
+ if (isAdjacentHistoryDuplicate(agentType, previous, message)) continue;
5374
+ if (message.role !== "system" && isAdjacentHistoryDuplicate(agentType, lastTurn, message)) continue;
5375
+ chronological.push(message);
5376
+ if (message.role !== "system") lastTurn = message;
5377
+ }
5378
+ const collapsed = collapseReplayAssistantTurns(agentType, chronological);
5379
+ const sliced = collapsed.slice(offset, offset + limit);
5380
+ const hasMore = collapsed.length > offset + limit;
5170
5381
  return { messages: sliced, hasMore };
5171
5382
  } catch {
5172
5383
  return { messages: [], hasMore: false };
@@ -5588,6 +5799,55 @@ ${effect.notification.body || ""}`.trim();
5588
5799
 
5589
5800
  // src/providers/ide-provider-instance.ts
5590
5801
  init_logger();
5802
+
5803
+ // src/providers/approval-utils.ts
5804
+ var DEFAULT_APPROVAL_POSITIVE_HINTS = [
5805
+ "run",
5806
+ "approve",
5807
+ "accept",
5808
+ "allow once",
5809
+ "always allow",
5810
+ "allow",
5811
+ "yes",
5812
+ "proceed",
5813
+ "continue",
5814
+ "confirm",
5815
+ "save",
5816
+ "ok",
5817
+ "trust"
5818
+ ];
5819
+ function normalizeApprovalLabel(value) {
5820
+ return String(value || "").toLowerCase().replace(/[^\p{L}\p{N}]+/gu, " ").trim();
5821
+ }
5822
+ function getApprovalPositiveHints(provider) {
5823
+ const customHints = Array.isArray(provider?.approvalPositiveHints) ? provider.approvalPositiveHints.map((hint) => normalizeApprovalLabel(String(hint || ""))).filter(Boolean) : [];
5824
+ return customHints.length > 0 ? customHints : DEFAULT_APPROVAL_POSITIVE_HINTS;
5825
+ }
5826
+ function pickApprovalButton(buttons, provider) {
5827
+ const labels = (buttons || []).map((button) => String(button || "").trim()).filter(Boolean);
5828
+ if (labels.length === 0) {
5829
+ return { index: 0, label: "Approve" };
5830
+ }
5831
+ const normalizedButtons = labels.map((label) => normalizeApprovalLabel(label));
5832
+ const hints = getApprovalPositiveHints(provider);
5833
+ for (const hint of hints) {
5834
+ const exactIndex = normalizedButtons.findIndex((label) => label === hint);
5835
+ if (exactIndex >= 0) return { index: exactIndex, label: labels[exactIndex] };
5836
+ const prefixIndex = normalizedButtons.findIndex((label) => label.startsWith(hint));
5837
+ if (prefixIndex >= 0) return { index: prefixIndex, label: labels[prefixIndex] };
5838
+ const includeIndex = normalizedButtons.findIndex((label) => label.includes(hint));
5839
+ if (includeIndex >= 0) return { index: includeIndex, label: labels[includeIndex] };
5840
+ }
5841
+ return { index: 0, label: labels[0] };
5842
+ }
5843
+ function formatAutoApprovalMessage(modalMessage, buttonLabel) {
5844
+ const lines = [`Auto-approved${buttonLabel ? `: ${buttonLabel}` : ""}`];
5845
+ const cleanMessage = String(modalMessage || "").trim();
5846
+ if (cleanMessage) lines.push(cleanMessage);
5847
+ return lines.join("\n");
5848
+ }
5849
+
5850
+ // src/providers/ide-provider-instance.ts
5591
5851
  var IdeProviderInstance = class {
5592
5852
  type;
5593
5853
  category = "ide";
@@ -5654,6 +5914,8 @@ var IdeProviderInstance = class {
5654
5914
  }
5655
5915
  getState() {
5656
5916
  const cdp = this.context?.cdp;
5917
+ const autoApproveActive = (this.currentStatus === "waiting_approval" || this.cachedChat?.status === "waiting_approval") && this.canAutoApprove();
5918
+ const visibleStatus = autoApproveActive ? "generating" : this.currentStatus;
5657
5919
  const extensionStates = [];
5658
5920
  for (const ext of this.extensions.values()) {
5659
5921
  extensionStates.push(ext.getState());
@@ -5662,13 +5924,13 @@ var IdeProviderInstance = class {
5662
5924
  type: this.type,
5663
5925
  name: this.provider.name,
5664
5926
  category: "ide",
5665
- status: this.currentStatus,
5927
+ status: visibleStatus,
5666
5928
  activeChat: this.cachedChat ? {
5667
5929
  id: this.cachedChat.id || "active_session",
5668
5930
  title: this.cachedChat.title || this.type,
5669
- status: this.cachedChat.status || this.currentStatus,
5931
+ status: autoApproveActive && this.cachedChat.status === "waiting_approval" ? "generating" : this.cachedChat.status || visibleStatus,
5670
5932
  messages: this.mergeConversationMessages(this.cachedChat.messages || []),
5671
- activeModal: this.cachedChat.activeModal || null,
5933
+ activeModal: autoApproveActive ? null : this.cachedChat.activeModal || null,
5672
5934
  inputContent: this.cachedChat.inputContent || ""
5673
5935
  } : null,
5674
5936
  workspace: this.workspace || null,
@@ -5887,7 +6149,9 @@ var IdeProviderInstance = class {
5887
6149
  const chatStatus = chatData?.status;
5888
6150
  if (!chatStatus) return;
5889
6151
  const agentKey = `${this.type}:native`;
5890
- const agentStatus = chatStatus === "streaming" || chatStatus === "generating" ? "generating" : chatStatus === "waiting_approval" ? "waiting_approval" : "idle";
6152
+ const rawAgentStatus = chatStatus === "streaming" || chatStatus === "generating" ? "generating" : chatStatus === "waiting_approval" ? "waiting_approval" : "idle";
6153
+ const autoApproveActive = rawAgentStatus === "waiting_approval" && this.canAutoApprove();
6154
+ const agentStatus = autoApproveActive ? "generating" : rawAgentStatus;
5891
6155
  const lastMsg = Array.isArray(chatData?.messages) && chatData.messages.length > 0 ? chatData.messages[chatData.messages.length - 1] : null;
5892
6156
  const progressFingerprint = agentStatus === "generating" ? `${lastMsg?.role || ""}:${typeof lastMsg?.content === "string" ? lastMsg.content : JSON.stringify(lastMsg?.content || "")}`.slice(-2e3) : void 0;
5893
6157
  this.currentStatus = agentStatus;
@@ -5919,7 +6183,7 @@ var IdeProviderInstance = class {
5919
6183
  this.applyProviderResponse(chatData, {
5920
6184
  phase: agentStatus === "idle" && (lastStatus === "generating" || lastStatus === "waiting_approval") ? "turn_completed" : "immediate"
5921
6185
  });
5922
- if (agentStatus === "waiting_approval" && this.settings.autoApprove && !this.autoApproveBusy) {
6186
+ if (rawAgentStatus === "waiting_approval" && autoApproveActive && !this.autoApproveBusy) {
5923
6187
  this.autoApproveViaScript(chatData);
5924
6188
  }
5925
6189
  const monitorEvents = this.monitor.check(agentKey, agentStatus, now, progressFingerprint);
@@ -6070,6 +6334,9 @@ ${effect.notification.body || ""}`.trim();
6070
6334
  updateCdp(cdp) {
6071
6335
  if (this.context) this.context.cdp = cdp;
6072
6336
  }
6337
+ canAutoApprove() {
6338
+ return this.settings.autoApprove !== false && typeof this.provider.scripts?.resolveAction === "function" && !!this.context?.cdp?.isConnected;
6339
+ }
6073
6340
  // ─── Auto-approve via CDP script ────────────────────
6074
6341
  async autoApproveViaScript(_chatData) {
6075
6342
  const cdp = this.context?.cdp;
@@ -6081,17 +6348,15 @@ ${effect.notification.body || ""}`.trim();
6081
6348
  }
6082
6349
  this.autoApproveBusy = true;
6083
6350
  try {
6084
- let targetButton = _chatData?.activeModal?.buttons?.[0] || "Run";
6085
- const buttons = _chatData?.activeModal?.buttons || [];
6086
- for (const b of buttons) {
6087
- const lower = String(b).toLowerCase().replace(/[^\w]/g, "");
6088
- if (/^(run|approve|accept|yes|allow|always|proceed|save)/.test(lower)) {
6089
- targetButton = b;
6090
- break;
6091
- }
6092
- }
6351
+ const { label: targetButton } = pickApprovalButton(_chatData?.activeModal?.buttons, this.provider);
6093
6352
  const script = scriptFn({ action: "approve", button: targetButton, buttonText: targetButton });
6094
6353
  if (!script) return;
6354
+ const now = Date.now();
6355
+ this.appendRuntimeSystemMessage(
6356
+ formatAutoApprovalMessage(_chatData?.activeModal?.message, targetButton),
6357
+ `auto_approval:${now}:${targetButton}`,
6358
+ now
6359
+ );
6095
6360
  LOG.info("IdeInstance", `[IdeInstance:${this.type}] autoApprove: executing resolveAction for "${targetButton}"`);
6096
6361
  let rawResult = await cdp.evaluate(script, 1e4);
6097
6362
  if (typeof rawResult === "string") {
@@ -6113,12 +6378,6 @@ ${effect.notification.body || ""}`.trim();
6113
6378
  LOG.warn("IdeInstance", `[IdeInstance:${this.type}] autoApprove: cdp.send() not available for coordinate click`);
6114
6379
  }
6115
6380
  }
6116
- this.pushEvent({
6117
- event: "agent:auto_approved",
6118
- chatTitle: _chatData?.title || this.provider.name,
6119
- timestamp: Date.now(),
6120
- ideType: this.type
6121
- });
6122
6381
  } catch (e) {
6123
6382
  LOG.warn("IdeInstance", `[IdeInstance:${this.type}] autoApprove error: ${e?.message}`);
6124
6383
  } finally {
@@ -7020,6 +7279,46 @@ function didProviderConfirmSend(result) {
7020
7279
  if (!parsed || typeof parsed !== "object") return false;
7021
7280
  return parsed.sent === true || parsed.success === true || parsed.ok === true || parsed.submitted === true || parsed.dispatched === true;
7022
7281
  }
7282
+ async function readExtensionChatState(h) {
7283
+ try {
7284
+ const evalResult = await h.evaluateProviderScript("readChat", void 0, 5e4);
7285
+ if (!evalResult?.result) return null;
7286
+ const parsed = parseMaybeJson(evalResult.result);
7287
+ return parsed && typeof parsed === "object" ? parsed : null;
7288
+ } catch {
7289
+ return null;
7290
+ }
7291
+ }
7292
+ function getStateMessageCount(state) {
7293
+ return Array.isArray(state?.messages) ? state.messages.length : 0;
7294
+ }
7295
+ function getStateLastSignature(state) {
7296
+ const messages = Array.isArray(state?.messages) ? state.messages : [];
7297
+ const last = messages[messages.length - 1];
7298
+ if (!last) return "";
7299
+ return `${last.role || ""}:${String(last.content || "").replace(/\s+/g, " ").trim()}`;
7300
+ }
7301
+ async function getStableExtensionBaseline(h) {
7302
+ const first = await readExtensionChatState(h);
7303
+ if (getStateMessageCount(first) > 0 || getStateLastSignature(first)) return first;
7304
+ await new Promise((resolve12) => setTimeout(resolve12, 150));
7305
+ const second = await readExtensionChatState(h);
7306
+ return getStateMessageCount(second) >= getStateMessageCount(first) ? second : first;
7307
+ }
7308
+ async function verifyExtensionSendObserved(h, before) {
7309
+ const beforeCount = getStateMessageCount(before);
7310
+ const beforeSignature = getStateLastSignature(before);
7311
+ for (let attempt = 0; attempt < 12; attempt += 1) {
7312
+ await new Promise((resolve12) => setTimeout(resolve12, 250));
7313
+ const state = await readExtensionChatState(h);
7314
+ if (state?.status === "waiting_approval") return true;
7315
+ const afterCount = getStateMessageCount(state);
7316
+ const afterSignature = getStateLastSignature(state);
7317
+ if (afterCount > beforeCount) return true;
7318
+ if (afterSignature && afterSignature !== beforeSignature) return true;
7319
+ }
7320
+ return false;
7321
+ }
7023
7322
  async function handleChatHistory(h, args) {
7024
7323
  const { agentType, offset, limit } = args;
7025
7324
  const historySessionId = getHistorySessionId(h, args);
@@ -7200,12 +7499,17 @@ async function handleSendChat(h, args) {
7200
7499
  if (isExtensionTransport(transport)) {
7201
7500
  _log(`Extension: ${provider?.type || "unknown_extension"}`);
7202
7501
  try {
7502
+ const beforeState = await getStableExtensionBaseline(h);
7203
7503
  const evalResult = await h.evaluateProviderScript("sendMessage", { message: text }, 3e4);
7204
7504
  if (evalResult?.result) {
7205
7505
  const parsed = parseMaybeJson(evalResult.result);
7206
7506
  if (didProviderConfirmSend(parsed)) {
7207
- _log(`Extension script sent OK`);
7208
- return _logSendSuccess("extension-script");
7507
+ const observed = await verifyExtensionSendObserved(h, beforeState);
7508
+ if (observed) {
7509
+ _log(`Extension script sent OK`);
7510
+ return _logSendSuccess("extension-script");
7511
+ }
7512
+ _log(`Extension script reported send but no chat-state change was observed`);
7209
7513
  }
7210
7514
  if (parsed?.needsTypeAndSend) {
7211
7515
  _log(`Extension needsTypeAndSend \u2192 AgentStreamManager`);
@@ -7710,7 +8014,7 @@ async function handleResolveAction(h, args) {
7710
8014
  return { success: true, buttonIndex, button: buttons[buttonIndex] ?? button };
7711
8015
  }
7712
8016
  if (isExtensionTransport(transport) && h.agentStream && h.getCdp() && h.currentSession?.sessionId) {
7713
- const ok = await h.agentStream.resolveSessionAction(h.getCdp(), h.currentSession.sessionId, action);
8017
+ const ok = await h.agentStream.resolveSessionAction(h.getCdp(), h.currentSession.sessionId, action, button);
7714
8018
  return { success: ok };
7715
8019
  }
7716
8020
  if (transport === "acp") {
@@ -9117,6 +9421,7 @@ var CliProviderInstance = class {
9117
9421
  historyWriter;
9118
9422
  runtimeMessages = [];
9119
9423
  instanceId;
9424
+ suppressIdleHistoryReplay = false;
9120
9425
  presentationMode;
9121
9426
  providerSessionId;
9122
9427
  launchMode;
@@ -9144,7 +9449,15 @@ var CliProviderInstance = class {
9144
9449
  await this.adapter.spawn();
9145
9450
  this.maybeAppendRuntimeRecoveryMessage(this.adapter.getRuntimeMetadata());
9146
9451
  if (this.providerSessionId) {
9452
+ this.historyWriter.compactHistorySession(this.type, this.providerSessionId);
9147
9453
  const restoredHistory = readChatHistory(this.type, 0, 200, this.providerSessionId);
9454
+ this.historyWriter.seedSessionHistory(
9455
+ this.type,
9456
+ restoredHistory.messages,
9457
+ this.providerSessionId,
9458
+ this.instanceId
9459
+ );
9460
+ this.suppressIdleHistoryReplay = restoredHistory.messages.length > 0;
9148
9461
  if (restoredHistory.messages.length > 0) {
9149
9462
  this.adapter.seedCommittedMessages(
9150
9463
  restoredHistory.messages.map((message) => ({
@@ -9188,7 +9501,7 @@ var CliProviderInstance = class {
9188
9501
  } else if (this.type === "codex-cli") {
9189
9502
  probedSessionId = this.probeSessionIdFromConfig({
9190
9503
  dbPath: "~/.codex/state_5.sqlite",
9191
- query: "select id from threads where cwd in ({dirs}) and created_at >= ? and archived = 0 order by created_at desc limit 1",
9504
+ query: "select id from threads where cwd in ({dirs}) and updated_at >= ? and archived = 0 order by updated_at desc limit 1",
9192
9505
  timestampFormat: "unix_s"
9193
9506
  });
9194
9507
  } else if (this.type === "goose-cli") {
@@ -9232,6 +9545,8 @@ var CliProviderInstance = class {
9232
9545
  getState() {
9233
9546
  const adapterStatus = this.adapter.getStatus();
9234
9547
  const parsedStatus = this.adapter.getScriptParsedStatus?.() || null;
9548
+ const autoApproveActive = adapterStatus.status === "waiting_approval" && this.shouldAutoApprove();
9549
+ const visibleStatus = autoApproveActive ? "generating" : adapterStatus.status;
9235
9550
  const parsedProviderSessionId = typeof parsedStatus?.providerSessionId === "string" ? parsedStatus.providerSessionId.trim() : "";
9236
9551
  if (parsedProviderSessionId) {
9237
9552
  this.promoteProviderSessionId(parsedProviderSessionId);
@@ -9248,6 +9563,7 @@ var CliProviderInstance = class {
9248
9563
  const mergedMessages = this.mergeConversationMessages(parsedMessages);
9249
9564
  const dirName = this.workingDir.split("/").filter(Boolean).pop() || "session";
9250
9565
  if (parsedMessages.length > 0) {
9566
+ const shouldSkipReplayPersist = this.suppressIdleHistoryReplay && adapterStatus.status === "idle" && parsedStatus?.status === "idle";
9251
9567
  let messagesToSave = parsedMessages;
9252
9568
  if (parsedStatus?.status === "generating" || parsedStatus?.status === "long_generating") {
9253
9569
  const lastIdx = messagesToSave.length - 1;
@@ -9255,7 +9571,7 @@ var CliProviderInstance = class {
9255
9571
  messagesToSave = messagesToSave.slice(0, lastIdx);
9256
9572
  }
9257
9573
  }
9258
- if (messagesToSave.length > 0) {
9574
+ if (!shouldSkipReplayPersist && messagesToSave.length > 0) {
9259
9575
  this.historyWriter.appendNewMessages(
9260
9576
  this.type,
9261
9577
  messagesToSave,
@@ -9270,14 +9586,14 @@ var CliProviderInstance = class {
9270
9586
  type: this.type,
9271
9587
  name: this.provider.name,
9272
9588
  category: "cli",
9273
- status: adapterStatus.status,
9589
+ status: visibleStatus,
9274
9590
  mode: this.presentationMode,
9275
9591
  activeChat: {
9276
9592
  id: `${this.type}_${this.workingDir}`,
9277
9593
  title: parsedStatus?.title || dirName,
9278
- status: parsedStatus?.status || adapterStatus.status,
9594
+ status: autoApproveActive && parsedStatus?.status === "waiting_approval" ? "generating" : parsedStatus?.status || visibleStatus,
9279
9595
  messages: mergedMessages,
9280
- activeModal: parsedStatus?.activeModal ?? adapterStatus.activeModal,
9596
+ activeModal: autoApproveActive ? null : parsedStatus?.activeModal ?? adapterStatus.activeModal,
9281
9597
  inputContent: ""
9282
9598
  },
9283
9599
  workspace: this.workingDir,
@@ -9341,7 +9657,16 @@ var CliProviderInstance = class {
9341
9657
  const now = Date.now();
9342
9658
  const adapterStatus = this.adapter.getStatus();
9343
9659
  const parsedStatus = this.adapter.getScriptParsedStatus?.() || null;
9344
- const newStatus = adapterStatus.status;
9660
+ const rawStatus = adapterStatus.status;
9661
+ const autoApproveActive = rawStatus === "waiting_approval" && this.shouldAutoApprove();
9662
+ if (autoApproveActive) {
9663
+ const { index: buttonIndex, label: buttonLabel } = pickApprovalButton(adapterStatus.activeModal?.buttons, this.provider);
9664
+ this.recordAutoApproval(adapterStatus.activeModal?.message, buttonLabel, now);
9665
+ setTimeout(() => {
9666
+ this.adapter.resolveModal(buttonIndex);
9667
+ }, 0);
9668
+ }
9669
+ const newStatus = autoApproveActive ? "generating" : rawStatus;
9345
9670
  const dirName = this.workingDir.split("/").filter(Boolean).pop() || "session";
9346
9671
  const chatTitle = `${this.provider.name} \xB7 ${dirName}`;
9347
9672
  const partial = this.adapter.getPartialResponse();
@@ -9350,6 +9675,7 @@ var CliProviderInstance = class {
9350
9675
  if (newStatus !== this.lastStatus) {
9351
9676
  LOG.info("CLI", `[${this.type}] status: ${this.lastStatus} \u2192 ${newStatus}`);
9352
9677
  if (this.lastStatus === "idle" && newStatus === "generating") {
9678
+ this.suppressIdleHistoryReplay = false;
9353
9679
  if (this.completedDebouncePending) {
9354
9680
  LOG.info("CLI", `[${this.type}] cancelled pending completed (resumed generating)`);
9355
9681
  if (this.completedDebounceTimer) {
@@ -9369,6 +9695,7 @@ var CliProviderInstance = class {
9369
9695
  this.generatingDebounceTimer = null;
9370
9696
  }, 1e3);
9371
9697
  } else if (newStatus === "waiting_approval") {
9698
+ this.suppressIdleHistoryReplay = false;
9372
9699
  if (this.generatingDebouncePending) {
9373
9700
  if (this.generatingDebounceTimer) {
9374
9701
  clearTimeout(this.generatingDebounceTimer);
@@ -9549,6 +9876,16 @@ ${effect.notification.body || ""}`.trim();
9549
9876
  get cliName() {
9550
9877
  return this.provider.name;
9551
9878
  }
9879
+ shouldAutoApprove() {
9880
+ return this.settings.autoApprove !== false;
9881
+ }
9882
+ recordAutoApproval(modalMessage, buttonLabel, now = Date.now()) {
9883
+ this.appendRuntimeSystemMessage(
9884
+ formatAutoApprovalMessage(modalMessage, buttonLabel),
9885
+ `auto_approval:${now}:${buttonLabel || "approve"}`,
9886
+ now
9887
+ );
9888
+ }
9552
9889
  recordApprovalSelection(buttonText) {
9553
9890
  const cleanButton = String(buttonText || "").trim();
9554
9891
  if (!cleanButton) return;
@@ -10122,8 +10459,10 @@ var AcpProviderInstance = class {
10122
10459
  input: tc.rawInput ? typeof tc.rawInput === "string" ? tc.rawInput : JSON.stringify(tc.rawInput) : void 0
10123
10460
  });
10124
10461
  }
10125
- if (this.settings.autoApprove) {
10126
- this.log.info(`[${this.type}] Auto-approving: ${tc.title || tc.toolCallId}`);
10462
+ if (this.settings.autoApprove !== false) {
10463
+ const toolTitle = tc.title || tc.toolCallId || "tool call";
10464
+ this.log.info(`[${this.type}] Auto-approving: ${toolTitle}`);
10465
+ this.appendSystemMessage(`Auto-approved: ${toolTitle}`);
10127
10466
  const allowOption = params.options.find((o) => o.kind === "allow_once") || params.options.find((o) => o.kind === "allow_always");
10128
10467
  if (allowOption) {
10129
10468
  return { outcome: { outcome: "selected", optionId: allowOption.optionId } };
@@ -10575,6 +10914,18 @@ var AcpProviderInstance = class {
10575
10914
  this.events.push(event);
10576
10915
  if (this.events.length > 50) this.events = this.events.slice(-50);
10577
10916
  }
10917
+ appendSystemMessage(content, timestamp = Date.now()) {
10918
+ const normalizedContent = String(content || "").trim();
10919
+ if (!normalizedContent) return;
10920
+ this.messages.push({
10921
+ role: "system",
10922
+ content: normalizedContent,
10923
+ timestamp
10924
+ });
10925
+ if (this.messages.length > 200) {
10926
+ this.messages = this.messages.slice(-100);
10927
+ }
10928
+ }
10578
10929
  flushEvents() {
10579
10930
  const events = [...this.events];
10580
10931
  this.events = [];
@@ -11081,6 +11432,7 @@ Run 'adhdev doctor' for detailed diagnostics.`
11081
11432
  if (!instanceManager) return 0;
11082
11433
  const sessions = records || await this.deps.listHostedCliRuntimes?.() || [];
11083
11434
  let restored = 0;
11435
+ const restoredBindings = /* @__PURE__ */ new Set();
11084
11436
  for (const record of sessions) {
11085
11437
  if (!record?.runtimeId || !record?.cliType || !record?.workspace) continue;
11086
11438
  if (this.adapters.has(record.runtimeId) || instanceManager.getInstance(record.runtimeId)) continue;
@@ -11094,6 +11446,18 @@ Run 'adhdev doctor' for detailed diagnostics.`
11094
11446
  record.cliArgs,
11095
11447
  record.providerSessionId
11096
11448
  );
11449
+ const bindingKey = [
11450
+ normalizedType,
11451
+ record.workspace,
11452
+ sessionBinding.providerSessionId || record.runtimeId
11453
+ ].join("::");
11454
+ if (restoredBindings.has(bindingKey)) {
11455
+ LOG.info(
11456
+ "CLI",
11457
+ `\u21B7 Skipping duplicate hosted runtime restore: ${record.runtimeKey || record.runtimeId} (${normalizedType} @ ${record.workspace}) binding=${sessionBinding.providerSessionId || "runtime"}`
11458
+ );
11459
+ continue;
11460
+ }
11097
11461
  try {
11098
11462
  await this.registerCliInstance(
11099
11463
  record.runtimeId,
@@ -11109,6 +11473,7 @@ Run 'adhdev doctor' for detailed diagnostics.`
11109
11473
  launchMode: "manual"
11110
11474
  }
11111
11475
  );
11476
+ restoredBindings.add(bindingKey);
11112
11477
  restored += 1;
11113
11478
  LOG.info("CLI", `\u267B Restored hosted runtime: ${record.runtimeKey || record.runtimeId} (${record.displayName || record.workspace})`);
11114
11479
  } catch (error) {
@@ -12135,7 +12500,7 @@ var ProviderLoader = class _ProviderLoader {
12135
12500
  */
12136
12501
  getSettingValue(type, key) {
12137
12502
  const schemaDef = this.getSettingsSchema(type)[key];
12138
- const defaultVal = schemaDef ? schemaDef.default : void 0;
12503
+ const defaultVal = schemaDef ? key === "autoApprove" && schemaDef.type === "boolean" ? true : schemaDef.default : void 0;
12139
12504
  try {
12140
12505
  const { loadConfig: loadConfig2 } = (init_config(), __toCommonJS(config_exports));
12141
12506
  const config = loadConfig2();
@@ -12194,13 +12559,32 @@ var ProviderLoader = class _ProviderLoader {
12194
12559
  getSettingsSchema(type) {
12195
12560
  const provider = this.providers.get(type);
12196
12561
  if (!provider) return {};
12197
- return {
12562
+ const result = {
12198
12563
  ...this.getSyntheticSettings(type, provider),
12199
12564
  ...provider.settings || {}
12200
12565
  };
12566
+ if (result.autoApprove?.type === "boolean") {
12567
+ result.autoApprove = {
12568
+ ...result.autoApprove,
12569
+ default: true,
12570
+ public: true,
12571
+ label: result.autoApprove.label || "Auto Approve",
12572
+ description: result.autoApprove.description || "Automatically approve actionable prompts without sending approval alerts."
12573
+ };
12574
+ }
12575
+ return result;
12201
12576
  }
12202
12577
  getSyntheticSettings(type, provider) {
12203
12578
  const result = {};
12579
+ if (!provider.settings?.autoApprove) {
12580
+ result.autoApprove = {
12581
+ type: "boolean",
12582
+ default: true,
12583
+ public: true,
12584
+ label: "Auto Approve",
12585
+ description: "Automatically approve actionable prompts without sending approval alerts."
12586
+ };
12587
+ }
12204
12588
  if ((provider.category === "cli" || provider.category === "acp") && provider.spawn?.command && !provider.settings?.executablePath) {
12205
12589
  result.executablePath = {
12206
12590
  type: "string",
@@ -13460,6 +13844,15 @@ var DaemonCommandRouter = class {
13460
13844
  const record = await this.deps.sessionHostControl.forceDetachClient(sessionId, clientId);
13461
13845
  return { success: true, record };
13462
13846
  }
13847
+ case "session_host_prune_duplicate_sessions": {
13848
+ if (!this.deps.sessionHostControl) return { success: false, error: "Session host control unavailable" };
13849
+ const result = await this.deps.sessionHostControl.pruneDuplicateSessions({
13850
+ providerType: typeof args?.providerType === "string" ? args.providerType : void 0,
13851
+ workspace: typeof args?.workspace === "string" ? args.workspace : void 0,
13852
+ dryRun: args?.dryRun === true
13853
+ });
13854
+ return { success: true, result };
13855
+ }
13463
13856
  case "session_host_acquire_write": {
13464
13857
  if (!this.deps.sessionHostControl) return { success: false, error: "Session host control unavailable" };
13465
13858
  const sessionId = typeof args?.sessionId === "string" ? args.sessionId : "";
@@ -14045,6 +14438,51 @@ var ProviderStreamAdapter = class {
14045
14438
  isTransportError(reason) {
14046
14439
  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);
14047
14440
  }
14441
+ titlesMatch(actual, expected) {
14442
+ const lhs = actual.trim().toLowerCase();
14443
+ const rhs = expected.trim().toLowerCase();
14444
+ if (!lhs || !rhs) return false;
14445
+ return lhs === rhs || lhs.includes(rhs) || rhs.includes(lhs);
14446
+ }
14447
+ messageCount(state) {
14448
+ return Array.isArray(state?.messages) ? state.messages.length : 0;
14449
+ }
14450
+ lastMessageSignature(state) {
14451
+ const messages = Array.isArray(state?.messages) ? state.messages : [];
14452
+ const last = messages[messages.length - 1];
14453
+ if (!last) return "";
14454
+ return `${last.role || ""}:${String(last.content || "").replace(/\s+/g, " ").trim()}`;
14455
+ }
14456
+ async verifySendOutcome(evaluate, before) {
14457
+ const beforeCount = this.messageCount(before);
14458
+ const beforeSignature = this.lastMessageSignature(before);
14459
+ for (let attempt = 0; attempt < 12; attempt += 1) {
14460
+ await new Promise((resolve12) => setTimeout(resolve12, 250));
14461
+ let state;
14462
+ try {
14463
+ state = await this.readChat(evaluate);
14464
+ } catch {
14465
+ continue;
14466
+ }
14467
+ if (state.status === "waiting_approval") {
14468
+ return true;
14469
+ }
14470
+ const afterCount = this.messageCount(state);
14471
+ const afterSignature = this.lastMessageSignature(state);
14472
+ if (afterCount > beforeCount) return true;
14473
+ if (afterSignature && afterSignature !== beforeSignature) return true;
14474
+ }
14475
+ return false;
14476
+ }
14477
+ async readStableBaselineState(evaluate) {
14478
+ const first = await this.readChat(evaluate);
14479
+ if (this.messageCount(first) > 0 || this.lastMessageSignature(first)) {
14480
+ return first;
14481
+ }
14482
+ await new Promise((resolve12) => setTimeout(resolve12, 150));
14483
+ const second = await this.readChat(evaluate);
14484
+ return this.messageCount(second) >= this.messageCount(first) ? second : first;
14485
+ }
14048
14486
  async readChat(evaluate) {
14049
14487
  const script = this.callScript("readChat");
14050
14488
  if (!script) return this.errorState("readChat script not available");
@@ -14070,6 +14508,9 @@ var ProviderStreamAdapter = class {
14070
14508
  mode: data.mode,
14071
14509
  activeModal: data.activeModal
14072
14510
  };
14511
+ if (typeof data.title === "string" && data.title.trim()) {
14512
+ state.title = data.title.trim();
14513
+ }
14073
14514
  const controlValues = extractProviderControlValues(this.provider.controls, data);
14074
14515
  if (controlValues) state.controlValues = controlValues;
14075
14516
  const effects = normalizeProviderEffects(data);
@@ -14093,6 +14534,12 @@ var ProviderStreamAdapter = class {
14093
14534
  }
14094
14535
  }
14095
14536
  async sendMessage(evaluate, text) {
14537
+ let beforeState = null;
14538
+ try {
14539
+ beforeState = await this.readStableBaselineState(evaluate);
14540
+ } catch {
14541
+ beforeState = null;
14542
+ }
14096
14543
  const params = { message: text };
14097
14544
  const script = this.callScript("sendMessage", params) || this.callScript("sendMessage", text);
14098
14545
  if (!script) throw new Error(`[${this.agentName}] sendMessage script not available`);
@@ -14110,7 +14557,9 @@ var ProviderStreamAdapter = class {
14110
14557
  }
14111
14558
  if (parsed && typeof parsed === "object") {
14112
14559
  if (parsed.sent === true || parsed.success === true || parsed.ok === true || parsed.submitted === true || parsed.dispatched === true) {
14113
- return;
14560
+ const verified = await this.verifySendOutcome(evaluate, beforeState);
14561
+ if (verified) return;
14562
+ throw new Error(`[${this.agentName}] sendMessage was not observed in chat state`);
14114
14563
  }
14115
14564
  if (typeof parsed.error === "string" && parsed.error.trim()) {
14116
14565
  throw new Error(`[${this.agentName}] sendMessage failed: ${parsed.error}`);
@@ -14121,7 +14570,15 @@ var ProviderStreamAdapter = class {
14121
14570
  async resolveAction(evaluate, action, button) {
14122
14571
  const script = this.callScript("resolveAction", { action, button });
14123
14572
  if (!script) return false;
14124
- return await evaluate(script) === true;
14573
+ const result = await evaluate(script);
14574
+ const parsed = this.parseMaybeJson(result);
14575
+ if (parsed === true) return true;
14576
+ if (typeof parsed === "string") {
14577
+ const normalized = parsed.trim().toLowerCase();
14578
+ return normalized === "ok" || normalized === "success" || normalized === "true" || normalized === "resolved" || normalized === "approved" || normalized === "rejected";
14579
+ }
14580
+ if (!parsed || typeof parsed !== "object") return false;
14581
+ return parsed.resolved === true || parsed.success === true || parsed.ok === true || parsed.found === true;
14125
14582
  }
14126
14583
  async newSession(evaluate) {
14127
14584
  const script = this.callScript("newSession");
@@ -14158,7 +14615,14 @@ var ProviderStreamAdapter = class {
14158
14615
  return normalized === "true" || normalized === "ok" || normalized === "switched" || normalized === "success";
14159
14616
  }
14160
14617
  if (data && typeof data === "object") {
14161
- return data.switched === true || data.success === true || data.ok === true;
14618
+ if (data.switched === true || data.success === true || data.ok === true) return true;
14619
+ if (typeof data.error === "string" && data.error.trim()) return false;
14620
+ }
14621
+ for (let attempt = 0; attempt < 6; attempt += 1) {
14622
+ await new Promise((resolve12) => setTimeout(resolve12, 250));
14623
+ const state = await this.readChat(evaluate);
14624
+ const title = typeof state.title === "string" ? state.title : "";
14625
+ if (this.titlesMatch(title, sessionId)) return true;
14162
14626
  }
14163
14627
  return false;
14164
14628
  }
@@ -14366,7 +14830,7 @@ var DaemonAgentStreamManager = class {
14366
14830
  return false;
14367
14831
  }
14368
14832
  }
14369
- async resolveSessionAction(cdp, sessionId, action) {
14833
+ async resolveSessionAction(cdp, sessionId, action, button) {
14370
14834
  await this.ensureSessionPanelOpen(sessionId);
14371
14835
  const target = this.getSessionTarget(sessionId);
14372
14836
  if (!target?.parentSessionId) return false;
@@ -14376,7 +14840,7 @@ var DaemonAgentStreamManager = class {
14376
14840
  if (!agent) return false;
14377
14841
  try {
14378
14842
  const evaluate = (expr, timeout) => cdp.evaluateInSessionFrame(agent.cdpSessionId, expr, timeout);
14379
- return await agent.adapter.resolveAction(evaluate, action);
14843
+ return await agent.adapter.resolveAction(evaluate, action, button);
14380
14844
  } catch (e) {
14381
14845
  this.logFn(`[AgentStream] resolveAction(${sessionId}) error: ${e.message}`);
14382
14846
  return false;
@@ -14606,7 +15070,43 @@ var AgentStreamPoller = class {
14606
15070
  }
14607
15071
  try {
14608
15072
  await agentStreamManager.syncActiveSession(cdp, parentSessionId);
14609
- const stream = await agentStreamManager.collectActiveSession(cdp, parentSessionId);
15073
+ let stream = await agentStreamManager.collectActiveSession(cdp, parentSessionId);
15074
+ if (stream?.status === "waiting_approval") {
15075
+ const autoApprove = providerLoader.getSettings(stream.agentType).autoApprove !== false;
15076
+ if (autoApprove && resolvedActiveSessionId) {
15077
+ const provider = providerLoader.getMeta(stream.agentType);
15078
+ const { label: buttonLabel } = pickApprovalButton(stream.activeModal?.buttons, provider);
15079
+ const approved = await agentStreamManager.resolveSessionAction(cdp, resolvedActiveSessionId, "approve", buttonLabel);
15080
+ if (approved) {
15081
+ const effectId = [
15082
+ "auto_approval",
15083
+ resolvedActiveSessionId,
15084
+ String(stream.messages?.length || 0),
15085
+ buttonLabel,
15086
+ String(stream.activeModal?.message || "").trim()
15087
+ ].join(":");
15088
+ stream = {
15089
+ ...stream,
15090
+ status: "streaming",
15091
+ activeModal: void 0,
15092
+ effects: [
15093
+ ...stream.effects || [],
15094
+ {
15095
+ type: "message",
15096
+ id: effectId,
15097
+ persist: true,
15098
+ message: {
15099
+ role: "system",
15100
+ senderName: "System",
15101
+ kind: "system",
15102
+ content: formatAutoApprovalMessage(stream.activeModal?.message, buttonLabel)
15103
+ }
15104
+ }
15105
+ ]
15106
+ };
15107
+ }
15108
+ }
15109
+ }
14610
15110
  this.deps.onStreamsUpdated?.(ideType, stream ? [stream] : []);
14611
15111
  } catch {
14612
15112
  }