@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.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 };
@@ -7020,6 +7231,46 @@ function didProviderConfirmSend(result) {
7020
7231
  if (!parsed || typeof parsed !== "object") return false;
7021
7232
  return parsed.sent === true || parsed.success === true || parsed.ok === true || parsed.submitted === true || parsed.dispatched === true;
7022
7233
  }
7234
+ async function readExtensionChatState(h) {
7235
+ try {
7236
+ const evalResult = await h.evaluateProviderScript("readChat", void 0, 5e4);
7237
+ if (!evalResult?.result) return null;
7238
+ const parsed = parseMaybeJson(evalResult.result);
7239
+ return parsed && typeof parsed === "object" ? parsed : null;
7240
+ } catch {
7241
+ return null;
7242
+ }
7243
+ }
7244
+ function getStateMessageCount(state) {
7245
+ return Array.isArray(state?.messages) ? state.messages.length : 0;
7246
+ }
7247
+ function getStateLastSignature(state) {
7248
+ const messages = Array.isArray(state?.messages) ? state.messages : [];
7249
+ const last = messages[messages.length - 1];
7250
+ if (!last) return "";
7251
+ return `${last.role || ""}:${String(last.content || "").replace(/\s+/g, " ").trim()}`;
7252
+ }
7253
+ async function getStableExtensionBaseline(h) {
7254
+ const first = await readExtensionChatState(h);
7255
+ if (getStateMessageCount(first) > 0 || getStateLastSignature(first)) return first;
7256
+ await new Promise((resolve12) => setTimeout(resolve12, 150));
7257
+ const second = await readExtensionChatState(h);
7258
+ return getStateMessageCount(second) >= getStateMessageCount(first) ? second : first;
7259
+ }
7260
+ async function verifyExtensionSendObserved(h, before) {
7261
+ const beforeCount = getStateMessageCount(before);
7262
+ const beforeSignature = getStateLastSignature(before);
7263
+ for (let attempt = 0; attempt < 12; attempt += 1) {
7264
+ await new Promise((resolve12) => setTimeout(resolve12, 250));
7265
+ const state = await readExtensionChatState(h);
7266
+ if (state?.status === "waiting_approval") return true;
7267
+ const afterCount = getStateMessageCount(state);
7268
+ const afterSignature = getStateLastSignature(state);
7269
+ if (afterCount > beforeCount) return true;
7270
+ if (afterSignature && afterSignature !== beforeSignature) return true;
7271
+ }
7272
+ return false;
7273
+ }
7023
7274
  async function handleChatHistory(h, args) {
7024
7275
  const { agentType, offset, limit } = args;
7025
7276
  const historySessionId = getHistorySessionId(h, args);
@@ -7200,12 +7451,17 @@ async function handleSendChat(h, args) {
7200
7451
  if (isExtensionTransport(transport)) {
7201
7452
  _log(`Extension: ${provider?.type || "unknown_extension"}`);
7202
7453
  try {
7454
+ const beforeState = await getStableExtensionBaseline(h);
7203
7455
  const evalResult = await h.evaluateProviderScript("sendMessage", { message: text }, 3e4);
7204
7456
  if (evalResult?.result) {
7205
7457
  const parsed = parseMaybeJson(evalResult.result);
7206
7458
  if (didProviderConfirmSend(parsed)) {
7207
- _log(`Extension script sent OK`);
7208
- return _logSendSuccess("extension-script");
7459
+ const observed = await verifyExtensionSendObserved(h, beforeState);
7460
+ if (observed) {
7461
+ _log(`Extension script sent OK`);
7462
+ return _logSendSuccess("extension-script");
7463
+ }
7464
+ _log(`Extension script reported send but no chat-state change was observed`);
7209
7465
  }
7210
7466
  if (parsed?.needsTypeAndSend) {
7211
7467
  _log(`Extension needsTypeAndSend \u2192 AgentStreamManager`);
@@ -7710,7 +7966,7 @@ async function handleResolveAction(h, args) {
7710
7966
  return { success: true, buttonIndex, button: buttons[buttonIndex] ?? button };
7711
7967
  }
7712
7968
  if (isExtensionTransport(transport) && h.agentStream && h.getCdp() && h.currentSession?.sessionId) {
7713
- const ok = await h.agentStream.resolveSessionAction(h.getCdp(), h.currentSession.sessionId, action);
7969
+ const ok = await h.agentStream.resolveSessionAction(h.getCdp(), h.currentSession.sessionId, action, button);
7714
7970
  return { success: ok };
7715
7971
  }
7716
7972
  if (transport === "acp") {
@@ -9117,6 +9373,7 @@ var CliProviderInstance = class {
9117
9373
  historyWriter;
9118
9374
  runtimeMessages = [];
9119
9375
  instanceId;
9376
+ suppressIdleHistoryReplay = false;
9120
9377
  presentationMode;
9121
9378
  providerSessionId;
9122
9379
  launchMode;
@@ -9144,7 +9401,15 @@ var CliProviderInstance = class {
9144
9401
  await this.adapter.spawn();
9145
9402
  this.maybeAppendRuntimeRecoveryMessage(this.adapter.getRuntimeMetadata());
9146
9403
  if (this.providerSessionId) {
9404
+ this.historyWriter.compactHistorySession(this.type, this.providerSessionId);
9147
9405
  const restoredHistory = readChatHistory(this.type, 0, 200, this.providerSessionId);
9406
+ this.historyWriter.seedSessionHistory(
9407
+ this.type,
9408
+ restoredHistory.messages,
9409
+ this.providerSessionId,
9410
+ this.instanceId
9411
+ );
9412
+ this.suppressIdleHistoryReplay = restoredHistory.messages.length > 0;
9148
9413
  if (restoredHistory.messages.length > 0) {
9149
9414
  this.adapter.seedCommittedMessages(
9150
9415
  restoredHistory.messages.map((message) => ({
@@ -9188,7 +9453,7 @@ var CliProviderInstance = class {
9188
9453
  } else if (this.type === "codex-cli") {
9189
9454
  probedSessionId = this.probeSessionIdFromConfig({
9190
9455
  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",
9456
+ query: "select id from threads where cwd in ({dirs}) and updated_at >= ? and archived = 0 order by updated_at desc limit 1",
9192
9457
  timestampFormat: "unix_s"
9193
9458
  });
9194
9459
  } else if (this.type === "goose-cli") {
@@ -9248,6 +9513,7 @@ var CliProviderInstance = class {
9248
9513
  const mergedMessages = this.mergeConversationMessages(parsedMessages);
9249
9514
  const dirName = this.workingDir.split("/").filter(Boolean).pop() || "session";
9250
9515
  if (parsedMessages.length > 0) {
9516
+ const shouldSkipReplayPersist = this.suppressIdleHistoryReplay && adapterStatus.status === "idle" && parsedStatus?.status === "idle";
9251
9517
  let messagesToSave = parsedMessages;
9252
9518
  if (parsedStatus?.status === "generating" || parsedStatus?.status === "long_generating") {
9253
9519
  const lastIdx = messagesToSave.length - 1;
@@ -9255,7 +9521,7 @@ var CliProviderInstance = class {
9255
9521
  messagesToSave = messagesToSave.slice(0, lastIdx);
9256
9522
  }
9257
9523
  }
9258
- if (messagesToSave.length > 0) {
9524
+ if (!shouldSkipReplayPersist && messagesToSave.length > 0) {
9259
9525
  this.historyWriter.appendNewMessages(
9260
9526
  this.type,
9261
9527
  messagesToSave,
@@ -9350,6 +9616,7 @@ var CliProviderInstance = class {
9350
9616
  if (newStatus !== this.lastStatus) {
9351
9617
  LOG.info("CLI", `[${this.type}] status: ${this.lastStatus} \u2192 ${newStatus}`);
9352
9618
  if (this.lastStatus === "idle" && newStatus === "generating") {
9619
+ this.suppressIdleHistoryReplay = false;
9353
9620
  if (this.completedDebouncePending) {
9354
9621
  LOG.info("CLI", `[${this.type}] cancelled pending completed (resumed generating)`);
9355
9622
  if (this.completedDebounceTimer) {
@@ -9369,6 +9636,7 @@ var CliProviderInstance = class {
9369
9636
  this.generatingDebounceTimer = null;
9370
9637
  }, 1e3);
9371
9638
  } else if (newStatus === "waiting_approval") {
9639
+ this.suppressIdleHistoryReplay = false;
9372
9640
  if (this.generatingDebouncePending) {
9373
9641
  if (this.generatingDebounceTimer) {
9374
9642
  clearTimeout(this.generatingDebounceTimer);
@@ -11081,6 +11349,7 @@ Run 'adhdev doctor' for detailed diagnostics.`
11081
11349
  if (!instanceManager) return 0;
11082
11350
  const sessions = records || await this.deps.listHostedCliRuntimes?.() || [];
11083
11351
  let restored = 0;
11352
+ const restoredBindings = /* @__PURE__ */ new Set();
11084
11353
  for (const record of sessions) {
11085
11354
  if (!record?.runtimeId || !record?.cliType || !record?.workspace) continue;
11086
11355
  if (this.adapters.has(record.runtimeId) || instanceManager.getInstance(record.runtimeId)) continue;
@@ -11094,6 +11363,18 @@ Run 'adhdev doctor' for detailed diagnostics.`
11094
11363
  record.cliArgs,
11095
11364
  record.providerSessionId
11096
11365
  );
11366
+ const bindingKey = [
11367
+ normalizedType,
11368
+ record.workspace,
11369
+ sessionBinding.providerSessionId || record.runtimeId
11370
+ ].join("::");
11371
+ if (restoredBindings.has(bindingKey)) {
11372
+ LOG.info(
11373
+ "CLI",
11374
+ `\u21B7 Skipping duplicate hosted runtime restore: ${record.runtimeKey || record.runtimeId} (${normalizedType} @ ${record.workspace}) binding=${sessionBinding.providerSessionId || "runtime"}`
11375
+ );
11376
+ continue;
11377
+ }
11097
11378
  try {
11098
11379
  await this.registerCliInstance(
11099
11380
  record.runtimeId,
@@ -11109,6 +11390,7 @@ Run 'adhdev doctor' for detailed diagnostics.`
11109
11390
  launchMode: "manual"
11110
11391
  }
11111
11392
  );
11393
+ restoredBindings.add(bindingKey);
11112
11394
  restored += 1;
11113
11395
  LOG.info("CLI", `\u267B Restored hosted runtime: ${record.runtimeKey || record.runtimeId} (${record.displayName || record.workspace})`);
11114
11396
  } catch (error) {
@@ -13460,6 +13742,15 @@ var DaemonCommandRouter = class {
13460
13742
  const record = await this.deps.sessionHostControl.forceDetachClient(sessionId, clientId);
13461
13743
  return { success: true, record };
13462
13744
  }
13745
+ case "session_host_prune_duplicate_sessions": {
13746
+ if (!this.deps.sessionHostControl) return { success: false, error: "Session host control unavailable" };
13747
+ const result = await this.deps.sessionHostControl.pruneDuplicateSessions({
13748
+ providerType: typeof args?.providerType === "string" ? args.providerType : void 0,
13749
+ workspace: typeof args?.workspace === "string" ? args.workspace : void 0,
13750
+ dryRun: args?.dryRun === true
13751
+ });
13752
+ return { success: true, result };
13753
+ }
13463
13754
  case "session_host_acquire_write": {
13464
13755
  if (!this.deps.sessionHostControl) return { success: false, error: "Session host control unavailable" };
13465
13756
  const sessionId = typeof args?.sessionId === "string" ? args.sessionId : "";
@@ -14045,6 +14336,51 @@ var ProviderStreamAdapter = class {
14045
14336
  isTransportError(reason) {
14046
14337
  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
14338
  }
14339
+ titlesMatch(actual, expected) {
14340
+ const lhs = actual.trim().toLowerCase();
14341
+ const rhs = expected.trim().toLowerCase();
14342
+ if (!lhs || !rhs) return false;
14343
+ return lhs === rhs || lhs.includes(rhs) || rhs.includes(lhs);
14344
+ }
14345
+ messageCount(state) {
14346
+ return Array.isArray(state?.messages) ? state.messages.length : 0;
14347
+ }
14348
+ lastMessageSignature(state) {
14349
+ const messages = Array.isArray(state?.messages) ? state.messages : [];
14350
+ const last = messages[messages.length - 1];
14351
+ if (!last) return "";
14352
+ return `${last.role || ""}:${String(last.content || "").replace(/\s+/g, " ").trim()}`;
14353
+ }
14354
+ async verifySendOutcome(evaluate, before) {
14355
+ const beforeCount = this.messageCount(before);
14356
+ const beforeSignature = this.lastMessageSignature(before);
14357
+ for (let attempt = 0; attempt < 12; attempt += 1) {
14358
+ await new Promise((resolve12) => setTimeout(resolve12, 250));
14359
+ let state;
14360
+ try {
14361
+ state = await this.readChat(evaluate);
14362
+ } catch {
14363
+ continue;
14364
+ }
14365
+ if (state.status === "waiting_approval") {
14366
+ return true;
14367
+ }
14368
+ const afterCount = this.messageCount(state);
14369
+ const afterSignature = this.lastMessageSignature(state);
14370
+ if (afterCount > beforeCount) return true;
14371
+ if (afterSignature && afterSignature !== beforeSignature) return true;
14372
+ }
14373
+ return false;
14374
+ }
14375
+ async readStableBaselineState(evaluate) {
14376
+ const first = await this.readChat(evaluate);
14377
+ if (this.messageCount(first) > 0 || this.lastMessageSignature(first)) {
14378
+ return first;
14379
+ }
14380
+ await new Promise((resolve12) => setTimeout(resolve12, 150));
14381
+ const second = await this.readChat(evaluate);
14382
+ return this.messageCount(second) >= this.messageCount(first) ? second : first;
14383
+ }
14048
14384
  async readChat(evaluate) {
14049
14385
  const script = this.callScript("readChat");
14050
14386
  if (!script) return this.errorState("readChat script not available");
@@ -14070,6 +14406,9 @@ var ProviderStreamAdapter = class {
14070
14406
  mode: data.mode,
14071
14407
  activeModal: data.activeModal
14072
14408
  };
14409
+ if (typeof data.title === "string" && data.title.trim()) {
14410
+ state.title = data.title.trim();
14411
+ }
14073
14412
  const controlValues = extractProviderControlValues(this.provider.controls, data);
14074
14413
  if (controlValues) state.controlValues = controlValues;
14075
14414
  const effects = normalizeProviderEffects(data);
@@ -14093,6 +14432,12 @@ var ProviderStreamAdapter = class {
14093
14432
  }
14094
14433
  }
14095
14434
  async sendMessage(evaluate, text) {
14435
+ let beforeState = null;
14436
+ try {
14437
+ beforeState = await this.readStableBaselineState(evaluate);
14438
+ } catch {
14439
+ beforeState = null;
14440
+ }
14096
14441
  const params = { message: text };
14097
14442
  const script = this.callScript("sendMessage", params) || this.callScript("sendMessage", text);
14098
14443
  if (!script) throw new Error(`[${this.agentName}] sendMessage script not available`);
@@ -14110,7 +14455,9 @@ var ProviderStreamAdapter = class {
14110
14455
  }
14111
14456
  if (parsed && typeof parsed === "object") {
14112
14457
  if (parsed.sent === true || parsed.success === true || parsed.ok === true || parsed.submitted === true || parsed.dispatched === true) {
14113
- return;
14458
+ const verified = await this.verifySendOutcome(evaluate, beforeState);
14459
+ if (verified) return;
14460
+ throw new Error(`[${this.agentName}] sendMessage was not observed in chat state`);
14114
14461
  }
14115
14462
  if (typeof parsed.error === "string" && parsed.error.trim()) {
14116
14463
  throw new Error(`[${this.agentName}] sendMessage failed: ${parsed.error}`);
@@ -14121,7 +14468,15 @@ var ProviderStreamAdapter = class {
14121
14468
  async resolveAction(evaluate, action, button) {
14122
14469
  const script = this.callScript("resolveAction", { action, button });
14123
14470
  if (!script) return false;
14124
- return await evaluate(script) === true;
14471
+ const result = await evaluate(script);
14472
+ const parsed = this.parseMaybeJson(result);
14473
+ if (parsed === true) return true;
14474
+ if (typeof parsed === "string") {
14475
+ const normalized = parsed.trim().toLowerCase();
14476
+ return normalized === "ok" || normalized === "success" || normalized === "true" || normalized === "resolved" || normalized === "approved" || normalized === "rejected";
14477
+ }
14478
+ if (!parsed || typeof parsed !== "object") return false;
14479
+ return parsed.resolved === true || parsed.success === true || parsed.ok === true || parsed.found === true;
14125
14480
  }
14126
14481
  async newSession(evaluate) {
14127
14482
  const script = this.callScript("newSession");
@@ -14158,7 +14513,14 @@ var ProviderStreamAdapter = class {
14158
14513
  return normalized === "true" || normalized === "ok" || normalized === "switched" || normalized === "success";
14159
14514
  }
14160
14515
  if (data && typeof data === "object") {
14161
- return data.switched === true || data.success === true || data.ok === true;
14516
+ if (data.switched === true || data.success === true || data.ok === true) return true;
14517
+ if (typeof data.error === "string" && data.error.trim()) return false;
14518
+ }
14519
+ for (let attempt = 0; attempt < 6; attempt += 1) {
14520
+ await new Promise((resolve12) => setTimeout(resolve12, 250));
14521
+ const state = await this.readChat(evaluate);
14522
+ const title = typeof state.title === "string" ? state.title : "";
14523
+ if (this.titlesMatch(title, sessionId)) return true;
14162
14524
  }
14163
14525
  return false;
14164
14526
  }
@@ -14366,7 +14728,7 @@ var DaemonAgentStreamManager = class {
14366
14728
  return false;
14367
14729
  }
14368
14730
  }
14369
- async resolveSessionAction(cdp, sessionId, action) {
14731
+ async resolveSessionAction(cdp, sessionId, action, button) {
14370
14732
  await this.ensureSessionPanelOpen(sessionId);
14371
14733
  const target = this.getSessionTarget(sessionId);
14372
14734
  if (!target?.parentSessionId) return false;
@@ -14376,7 +14738,7 @@ var DaemonAgentStreamManager = class {
14376
14738
  if (!agent) return false;
14377
14739
  try {
14378
14740
  const evaluate = (expr, timeout) => cdp.evaluateInSessionFrame(agent.cdpSessionId, expr, timeout);
14379
- return await agent.adapter.resolveAction(evaluate, action);
14741
+ return await agent.adapter.resolveAction(evaluate, action, button);
14380
14742
  } catch (e) {
14381
14743
  this.logFn(`[AgentStream] resolveAction(${sessionId}) error: ${e.message}`);
14382
14744
  return false;