@adhdev/daemon-core 0.8.28 → 0.8.29

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.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 };
@@ -6935,6 +7146,46 @@ function didProviderConfirmSend(result) {
6935
7146
  if (!parsed || typeof parsed !== "object") return false;
6936
7147
  return parsed.sent === true || parsed.success === true || parsed.ok === true || parsed.submitted === true || parsed.dispatched === true;
6937
7148
  }
7149
+ async function readExtensionChatState(h) {
7150
+ try {
7151
+ const evalResult = await h.evaluateProviderScript("readChat", void 0, 5e4);
7152
+ if (!evalResult?.result) return null;
7153
+ const parsed = parseMaybeJson(evalResult.result);
7154
+ return parsed && typeof parsed === "object" ? parsed : null;
7155
+ } catch {
7156
+ return null;
7157
+ }
7158
+ }
7159
+ function getStateMessageCount(state) {
7160
+ return Array.isArray(state?.messages) ? state.messages.length : 0;
7161
+ }
7162
+ function getStateLastSignature(state) {
7163
+ const messages = Array.isArray(state?.messages) ? state.messages : [];
7164
+ const last = messages[messages.length - 1];
7165
+ if (!last) return "";
7166
+ return `${last.role || ""}:${String(last.content || "").replace(/\s+/g, " ").trim()}`;
7167
+ }
7168
+ async function getStableExtensionBaseline(h) {
7169
+ const first = await readExtensionChatState(h);
7170
+ if (getStateMessageCount(first) > 0 || getStateLastSignature(first)) return first;
7171
+ await new Promise((resolve12) => setTimeout(resolve12, 150));
7172
+ const second = await readExtensionChatState(h);
7173
+ return getStateMessageCount(second) >= getStateMessageCount(first) ? second : first;
7174
+ }
7175
+ async function verifyExtensionSendObserved(h, before) {
7176
+ const beforeCount = getStateMessageCount(before);
7177
+ const beforeSignature = getStateLastSignature(before);
7178
+ for (let attempt = 0; attempt < 12; attempt += 1) {
7179
+ await new Promise((resolve12) => setTimeout(resolve12, 250));
7180
+ const state = await readExtensionChatState(h);
7181
+ if (state?.status === "waiting_approval") return true;
7182
+ const afterCount = getStateMessageCount(state);
7183
+ const afterSignature = getStateLastSignature(state);
7184
+ if (afterCount > beforeCount) return true;
7185
+ if (afterSignature && afterSignature !== beforeSignature) return true;
7186
+ }
7187
+ return false;
7188
+ }
6938
7189
  async function handleChatHistory(h, args) {
6939
7190
  const { agentType, offset, limit } = args;
6940
7191
  const historySessionId = getHistorySessionId(h, args);
@@ -7115,12 +7366,17 @@ async function handleSendChat(h, args) {
7115
7366
  if (isExtensionTransport(transport)) {
7116
7367
  _log(`Extension: ${provider?.type || "unknown_extension"}`);
7117
7368
  try {
7369
+ const beforeState = await getStableExtensionBaseline(h);
7118
7370
  const evalResult = await h.evaluateProviderScript("sendMessage", { message: text }, 3e4);
7119
7371
  if (evalResult?.result) {
7120
7372
  const parsed = parseMaybeJson(evalResult.result);
7121
7373
  if (didProviderConfirmSend(parsed)) {
7122
- _log(`Extension script sent OK`);
7123
- return _logSendSuccess("extension-script");
7374
+ const observed = await verifyExtensionSendObserved(h, beforeState);
7375
+ if (observed) {
7376
+ _log(`Extension script sent OK`);
7377
+ return _logSendSuccess("extension-script");
7378
+ }
7379
+ _log(`Extension script reported send but no chat-state change was observed`);
7124
7380
  }
7125
7381
  if (parsed?.needsTypeAndSend) {
7126
7382
  _log(`Extension needsTypeAndSend \u2192 AgentStreamManager`);
@@ -7625,7 +7881,7 @@ async function handleResolveAction(h, args) {
7625
7881
  return { success: true, buttonIndex, button: buttons[buttonIndex] ?? button };
7626
7882
  }
7627
7883
  if (isExtensionTransport(transport) && h.agentStream && h.getCdp() && h.currentSession?.sessionId) {
7628
- const ok = await h.agentStream.resolveSessionAction(h.getCdp(), h.currentSession.sessionId, action);
7884
+ const ok = await h.agentStream.resolveSessionAction(h.getCdp(), h.currentSession.sessionId, action, button);
7629
7885
  return { success: ok };
7630
7886
  }
7631
7887
  if (transport === "acp") {
@@ -9032,6 +9288,7 @@ var CliProviderInstance = class {
9032
9288
  historyWriter;
9033
9289
  runtimeMessages = [];
9034
9290
  instanceId;
9291
+ suppressIdleHistoryReplay = false;
9035
9292
  presentationMode;
9036
9293
  providerSessionId;
9037
9294
  launchMode;
@@ -9059,7 +9316,15 @@ var CliProviderInstance = class {
9059
9316
  await this.adapter.spawn();
9060
9317
  this.maybeAppendRuntimeRecoveryMessage(this.adapter.getRuntimeMetadata());
9061
9318
  if (this.providerSessionId) {
9319
+ this.historyWriter.compactHistorySession(this.type, this.providerSessionId);
9062
9320
  const restoredHistory = readChatHistory(this.type, 0, 200, this.providerSessionId);
9321
+ this.historyWriter.seedSessionHistory(
9322
+ this.type,
9323
+ restoredHistory.messages,
9324
+ this.providerSessionId,
9325
+ this.instanceId
9326
+ );
9327
+ this.suppressIdleHistoryReplay = restoredHistory.messages.length > 0;
9063
9328
  if (restoredHistory.messages.length > 0) {
9064
9329
  this.adapter.seedCommittedMessages(
9065
9330
  restoredHistory.messages.map((message) => ({
@@ -9103,7 +9368,7 @@ var CliProviderInstance = class {
9103
9368
  } else if (this.type === "codex-cli") {
9104
9369
  probedSessionId = this.probeSessionIdFromConfig({
9105
9370
  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",
9371
+ query: "select id from threads where cwd in ({dirs}) and updated_at >= ? and archived = 0 order by updated_at desc limit 1",
9107
9372
  timestampFormat: "unix_s"
9108
9373
  });
9109
9374
  } else if (this.type === "goose-cli") {
@@ -9163,6 +9428,7 @@ var CliProviderInstance = class {
9163
9428
  const mergedMessages = this.mergeConversationMessages(parsedMessages);
9164
9429
  const dirName = this.workingDir.split("/").filter(Boolean).pop() || "session";
9165
9430
  if (parsedMessages.length > 0) {
9431
+ const shouldSkipReplayPersist = this.suppressIdleHistoryReplay && adapterStatus.status === "idle" && parsedStatus?.status === "idle";
9166
9432
  let messagesToSave = parsedMessages;
9167
9433
  if (parsedStatus?.status === "generating" || parsedStatus?.status === "long_generating") {
9168
9434
  const lastIdx = messagesToSave.length - 1;
@@ -9170,7 +9436,7 @@ var CliProviderInstance = class {
9170
9436
  messagesToSave = messagesToSave.slice(0, lastIdx);
9171
9437
  }
9172
9438
  }
9173
- if (messagesToSave.length > 0) {
9439
+ if (!shouldSkipReplayPersist && messagesToSave.length > 0) {
9174
9440
  this.historyWriter.appendNewMessages(
9175
9441
  this.type,
9176
9442
  messagesToSave,
@@ -9265,6 +9531,7 @@ var CliProviderInstance = class {
9265
9531
  if (newStatus !== this.lastStatus) {
9266
9532
  LOG.info("CLI", `[${this.type}] status: ${this.lastStatus} \u2192 ${newStatus}`);
9267
9533
  if (this.lastStatus === "idle" && newStatus === "generating") {
9534
+ this.suppressIdleHistoryReplay = false;
9268
9535
  if (this.completedDebouncePending) {
9269
9536
  LOG.info("CLI", `[${this.type}] cancelled pending completed (resumed generating)`);
9270
9537
  if (this.completedDebounceTimer) {
@@ -9284,6 +9551,7 @@ var CliProviderInstance = class {
9284
9551
  this.generatingDebounceTimer = null;
9285
9552
  }, 1e3);
9286
9553
  } else if (newStatus === "waiting_approval") {
9554
+ this.suppressIdleHistoryReplay = false;
9287
9555
  if (this.generatingDebouncePending) {
9288
9556
  if (this.generatingDebounceTimer) {
9289
9557
  clearTimeout(this.generatingDebounceTimer);
@@ -11001,6 +11269,7 @@ Run 'adhdev doctor' for detailed diagnostics.`
11001
11269
  if (!instanceManager) return 0;
11002
11270
  const sessions = records || await this.deps.listHostedCliRuntimes?.() || [];
11003
11271
  let restored = 0;
11272
+ const restoredBindings = /* @__PURE__ */ new Set();
11004
11273
  for (const record of sessions) {
11005
11274
  if (!record?.runtimeId || !record?.cliType || !record?.workspace) continue;
11006
11275
  if (this.adapters.has(record.runtimeId) || instanceManager.getInstance(record.runtimeId)) continue;
@@ -11014,6 +11283,18 @@ Run 'adhdev doctor' for detailed diagnostics.`
11014
11283
  record.cliArgs,
11015
11284
  record.providerSessionId
11016
11285
  );
11286
+ const bindingKey = [
11287
+ normalizedType,
11288
+ record.workspace,
11289
+ sessionBinding.providerSessionId || record.runtimeId
11290
+ ].join("::");
11291
+ if (restoredBindings.has(bindingKey)) {
11292
+ LOG.info(
11293
+ "CLI",
11294
+ `\u21B7 Skipping duplicate hosted runtime restore: ${record.runtimeKey || record.runtimeId} (${normalizedType} @ ${record.workspace}) binding=${sessionBinding.providerSessionId || "runtime"}`
11295
+ );
11296
+ continue;
11297
+ }
11017
11298
  try {
11018
11299
  await this.registerCliInstance(
11019
11300
  record.runtimeId,
@@ -11029,6 +11310,7 @@ Run 'adhdev doctor' for detailed diagnostics.`
11029
11310
  launchMode: "manual"
11030
11311
  }
11031
11312
  );
11313
+ restoredBindings.add(bindingKey);
11032
11314
  restored += 1;
11033
11315
  LOG.info("CLI", `\u267B Restored hosted runtime: ${record.runtimeKey || record.runtimeId} (${record.displayName || record.workspace})`);
11034
11316
  } catch (error) {
@@ -13380,6 +13662,15 @@ var DaemonCommandRouter = class {
13380
13662
  const record = await this.deps.sessionHostControl.forceDetachClient(sessionId, clientId);
13381
13663
  return { success: true, record };
13382
13664
  }
13665
+ case "session_host_prune_duplicate_sessions": {
13666
+ if (!this.deps.sessionHostControl) return { success: false, error: "Session host control unavailable" };
13667
+ const result = await this.deps.sessionHostControl.pruneDuplicateSessions({
13668
+ providerType: typeof args?.providerType === "string" ? args.providerType : void 0,
13669
+ workspace: typeof args?.workspace === "string" ? args.workspace : void 0,
13670
+ dryRun: args?.dryRun === true
13671
+ });
13672
+ return { success: true, result };
13673
+ }
13383
13674
  case "session_host_acquire_write": {
13384
13675
  if (!this.deps.sessionHostControl) return { success: false, error: "Session host control unavailable" };
13385
13676
  const sessionId = typeof args?.sessionId === "string" ? args.sessionId : "";
@@ -13965,6 +14256,51 @@ var ProviderStreamAdapter = class {
13965
14256
  isTransportError(reason) {
13966
14257
  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
14258
  }
14259
+ titlesMatch(actual, expected) {
14260
+ const lhs = actual.trim().toLowerCase();
14261
+ const rhs = expected.trim().toLowerCase();
14262
+ if (!lhs || !rhs) return false;
14263
+ return lhs === rhs || lhs.includes(rhs) || rhs.includes(lhs);
14264
+ }
14265
+ messageCount(state) {
14266
+ return Array.isArray(state?.messages) ? state.messages.length : 0;
14267
+ }
14268
+ lastMessageSignature(state) {
14269
+ const messages = Array.isArray(state?.messages) ? state.messages : [];
14270
+ const last = messages[messages.length - 1];
14271
+ if (!last) return "";
14272
+ return `${last.role || ""}:${String(last.content || "").replace(/\s+/g, " ").trim()}`;
14273
+ }
14274
+ async verifySendOutcome(evaluate, before) {
14275
+ const beforeCount = this.messageCount(before);
14276
+ const beforeSignature = this.lastMessageSignature(before);
14277
+ for (let attempt = 0; attempt < 12; attempt += 1) {
14278
+ await new Promise((resolve12) => setTimeout(resolve12, 250));
14279
+ let state;
14280
+ try {
14281
+ state = await this.readChat(evaluate);
14282
+ } catch {
14283
+ continue;
14284
+ }
14285
+ if (state.status === "waiting_approval") {
14286
+ return true;
14287
+ }
14288
+ const afterCount = this.messageCount(state);
14289
+ const afterSignature = this.lastMessageSignature(state);
14290
+ if (afterCount > beforeCount) return true;
14291
+ if (afterSignature && afterSignature !== beforeSignature) return true;
14292
+ }
14293
+ return false;
14294
+ }
14295
+ async readStableBaselineState(evaluate) {
14296
+ const first = await this.readChat(evaluate);
14297
+ if (this.messageCount(first) > 0 || this.lastMessageSignature(first)) {
14298
+ return first;
14299
+ }
14300
+ await new Promise((resolve12) => setTimeout(resolve12, 150));
14301
+ const second = await this.readChat(evaluate);
14302
+ return this.messageCount(second) >= this.messageCount(first) ? second : first;
14303
+ }
13968
14304
  async readChat(evaluate) {
13969
14305
  const script = this.callScript("readChat");
13970
14306
  if (!script) return this.errorState("readChat script not available");
@@ -13990,6 +14326,9 @@ var ProviderStreamAdapter = class {
13990
14326
  mode: data.mode,
13991
14327
  activeModal: data.activeModal
13992
14328
  };
14329
+ if (typeof data.title === "string" && data.title.trim()) {
14330
+ state.title = data.title.trim();
14331
+ }
13993
14332
  const controlValues = extractProviderControlValues(this.provider.controls, data);
13994
14333
  if (controlValues) state.controlValues = controlValues;
13995
14334
  const effects = normalizeProviderEffects(data);
@@ -14013,6 +14352,12 @@ var ProviderStreamAdapter = class {
14013
14352
  }
14014
14353
  }
14015
14354
  async sendMessage(evaluate, text) {
14355
+ let beforeState = null;
14356
+ try {
14357
+ beforeState = await this.readStableBaselineState(evaluate);
14358
+ } catch {
14359
+ beforeState = null;
14360
+ }
14016
14361
  const params = { message: text };
14017
14362
  const script = this.callScript("sendMessage", params) || this.callScript("sendMessage", text);
14018
14363
  if (!script) throw new Error(`[${this.agentName}] sendMessage script not available`);
@@ -14030,7 +14375,9 @@ var ProviderStreamAdapter = class {
14030
14375
  }
14031
14376
  if (parsed && typeof parsed === "object") {
14032
14377
  if (parsed.sent === true || parsed.success === true || parsed.ok === true || parsed.submitted === true || parsed.dispatched === true) {
14033
- return;
14378
+ const verified = await this.verifySendOutcome(evaluate, beforeState);
14379
+ if (verified) return;
14380
+ throw new Error(`[${this.agentName}] sendMessage was not observed in chat state`);
14034
14381
  }
14035
14382
  if (typeof parsed.error === "string" && parsed.error.trim()) {
14036
14383
  throw new Error(`[${this.agentName}] sendMessage failed: ${parsed.error}`);
@@ -14041,7 +14388,15 @@ var ProviderStreamAdapter = class {
14041
14388
  async resolveAction(evaluate, action, button) {
14042
14389
  const script = this.callScript("resolveAction", { action, button });
14043
14390
  if (!script) return false;
14044
- return await evaluate(script) === true;
14391
+ const result = await evaluate(script);
14392
+ const parsed = this.parseMaybeJson(result);
14393
+ if (parsed === true) return true;
14394
+ if (typeof parsed === "string") {
14395
+ const normalized = parsed.trim().toLowerCase();
14396
+ return normalized === "ok" || normalized === "success" || normalized === "true" || normalized === "resolved" || normalized === "approved" || normalized === "rejected";
14397
+ }
14398
+ if (!parsed || typeof parsed !== "object") return false;
14399
+ return parsed.resolved === true || parsed.success === true || parsed.ok === true || parsed.found === true;
14045
14400
  }
14046
14401
  async newSession(evaluate) {
14047
14402
  const script = this.callScript("newSession");
@@ -14078,7 +14433,14 @@ var ProviderStreamAdapter = class {
14078
14433
  return normalized === "true" || normalized === "ok" || normalized === "switched" || normalized === "success";
14079
14434
  }
14080
14435
  if (data && typeof data === "object") {
14081
- return data.switched === true || data.success === true || data.ok === true;
14436
+ if (data.switched === true || data.success === true || data.ok === true) return true;
14437
+ if (typeof data.error === "string" && data.error.trim()) return false;
14438
+ }
14439
+ for (let attempt = 0; attempt < 6; attempt += 1) {
14440
+ await new Promise((resolve12) => setTimeout(resolve12, 250));
14441
+ const state = await this.readChat(evaluate);
14442
+ const title = typeof state.title === "string" ? state.title : "";
14443
+ if (this.titlesMatch(title, sessionId)) return true;
14082
14444
  }
14083
14445
  return false;
14084
14446
  }
@@ -14286,7 +14648,7 @@ var DaemonAgentStreamManager = class {
14286
14648
  return false;
14287
14649
  }
14288
14650
  }
14289
- async resolveSessionAction(cdp, sessionId, action) {
14651
+ async resolveSessionAction(cdp, sessionId, action, button) {
14290
14652
  await this.ensureSessionPanelOpen(sessionId);
14291
14653
  const target = this.getSessionTarget(sessionId);
14292
14654
  if (!target?.parentSessionId) return false;
@@ -14296,7 +14658,7 @@ var DaemonAgentStreamManager = class {
14296
14658
  if (!agent) return false;
14297
14659
  try {
14298
14660
  const evaluate = (expr, timeout) => cdp.evaluateInSessionFrame(agent.cdpSessionId, expr, timeout);
14299
- return await agent.adapter.resolveAction(evaluate, action);
14661
+ return await agent.adapter.resolveAction(evaluate, action, button);
14300
14662
  } catch (e) {
14301
14663
  this.logFn(`[AgentStream] resolveAction(${sessionId}) error: ${e.message}`);
14302
14664
  return false;