@adhdev/daemon-core 0.9.21 → 0.9.23

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.
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@adhdev/session-host-core",
3
- "version": "0.9.21",
3
+ "version": "0.9.23",
4
4
  "description": "ADHDev local session host core \u2014 session registry, protocol, buffers",
5
5
  "main": "dist/index.js",
6
6
  "types": "dist/index.d.ts",
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@adhdev/daemon-core",
3
- "version": "0.9.21",
3
+ "version": "0.9.23",
4
4
  "description": "ADHDev daemon core \u2014 CDP, IDE detection, providers, command execution",
5
5
  "main": "dist/index.js",
6
6
  "types": "dist/index.d.ts",
@@ -1171,11 +1171,7 @@ export class ProviderCliAdapter implements CliAdapter {
1171
1171
  const screenText = this.terminalScreen.getText() || '';
1172
1172
  const effectiveScreenText = screenText || this.accumulatedBuffer;
1173
1173
  const noActiveTurn = !this.currentTurnScope;
1174
- const looksIdleChrome = /(^|\n)\s*[❯›>]\s*(?:\n|$)/m.test(effectiveScreenText)
1175
- || (/accept edits on/i.test(effectiveScreenText)
1176
- && (/Update available!/i.test(screenText)
1177
- || /\/effort/i.test(screenText)
1178
- || /^.*➜\s+\S+/m.test(effectiveScreenText)));
1174
+ const looksIdleChrome = /(^|\n)\s*[❯›>]\s*(?:\n|$)/m.test(effectiveScreenText);
1179
1175
  const parsedShowsLiveAssistantProgress = parsedStatus === 'generating'
1180
1176
  && !!lastParsedAssistant
1181
1177
  && parsedMessages.length > this.committedMessages.length;
@@ -193,14 +193,52 @@ function shouldCollapseReadChatReplayDuplicate(message: ChatMessage | null | und
193
193
  return role === 'assistant' || role === 'system';
194
194
  }
195
195
 
196
+ function normalizeReadChatReplayText(message: ChatMessage | null | undefined): string {
197
+ return flattenContent(message?.content || '').replace(/\s+/g, ' ').trim();
198
+ }
199
+
200
+ function isStableReadChatAssistantAnswer(message: ChatMessage | null | undefined): boolean {
201
+ if (!message) return false;
202
+ const role = typeof message.role === 'string' ? message.role.trim().toLowerCase() : '';
203
+ const kind = typeof message.kind === 'string' ? message.kind.trim().toLowerCase() : 'standard';
204
+ if (role !== 'assistant') return false;
205
+ if (kind && kind !== 'standard') return false;
206
+ const content = normalizeReadChatReplayText(message);
207
+ if (content.length < 160) return false;
208
+
209
+ // A provider may surface expanded command output as a standard assistant bubble
210
+ // (for example Claude Code's "Bash command ..." block). That is live work output,
211
+ // not a stable final answer. Treating it as a terminal answer would hide the
212
+ // real final response and violate read_chat fidelity.
213
+ if (/^(bash|shell|terminal) command\b/i.test(content)) return false;
214
+ return true;
215
+ }
216
+
217
+ function isReplayedAssistantAnswerAfterStableAnswer(
218
+ message: ChatMessage | null | undefined,
219
+ stableAnswer: ChatMessage | null,
220
+ ): boolean {
221
+ if (!message || !stableAnswer) return false;
222
+ const role = typeof message.role === 'string' ? message.role.trim().toLowerCase() : '';
223
+ const kind = typeof message.kind === 'string' ? message.kind.trim().toLowerCase() : 'standard';
224
+ if (role !== 'assistant') return false;
225
+ if (kind && kind !== 'standard') return false;
226
+ const content = normalizeReadChatReplayText(message);
227
+ const stableContent = normalizeReadChatReplayText(stableAnswer);
228
+ if (content.length < 80 || stableContent.length < 80) return false;
229
+ return content === stableContent || content.startsWith(stableContent) || stableContent.startsWith(content);
230
+ }
231
+
196
232
  function collapseReplayDuplicatesFromReadChat(messages: ChatMessage[]): ChatMessage[] {
197
233
  const collapsed: ChatMessage[] = [];
198
234
  const replaySignaturesInCurrentTurn = new Set<string>();
235
+ let stableAssistantAnswerInCurrentTurn: ChatMessage | null = null;
199
236
 
200
237
  for (const message of messages) {
201
238
  const role = typeof message.role === 'string' ? message.role.trim().toLowerCase() : '';
202
239
  if (role === 'user') {
203
240
  replaySignaturesInCurrentTurn.clear();
241
+ stableAssistantAnswerInCurrentTurn = null;
204
242
  }
205
243
 
206
244
  const signature = buildReadChatReplayCollapseSignature(message);
@@ -210,12 +248,16 @@ function collapseReplayDuplicatesFromReadChat(messages: ChatMessage[]): ChatMess
210
248
  if (shouldCollapseReadChatReplayDuplicate(message) && signature) {
211
249
  if (previousSignature === signature) continue;
212
250
  if (replaySignaturesInCurrentTurn.has(signature)) continue;
251
+ if (isReplayedAssistantAnswerAfterStableAnswer(message, stableAssistantAnswerInCurrentTurn)) continue;
213
252
  }
214
253
 
215
254
  collapsed.push(message);
216
255
  if (shouldCollapseReadChatReplayDuplicate(message) && signature) {
217
256
  replaySignaturesInCurrentTurn.add(signature);
218
257
  }
258
+ if (isStableReadChatAssistantAnswer(message)) {
259
+ stableAssistantAnswerInCurrentTurn = message;
260
+ }
219
261
  }
220
262
 
221
263
  return collapsed;
@@ -367,6 +367,25 @@ export class CliProviderInstance implements ProviderInstance {
367
367
  ? parsedMessages.slice(-historyMessageCount)
368
368
  : [];
369
369
  }
370
+ // committedMessages (adapterStatus.messages) is the adapter's accumulated
371
+ // conversation history — use it as a floor to prevent history from disappearing.
372
+ //
373
+ // waiting_approval: always override — the approval dialog fills the terminal,
374
+ // pushing prior conversation out of view; parsedMessages will be partial or empty
375
+ // regardless of historyMessageCount.
376
+ //
377
+ // Other active states (generating, long_generating, error): apply floor only
378
+ // when the script has not explicitly windowed via historyMessageCount, so
379
+ // intentional windowing is preserved. Excludes idle — scripts may legitimately
380
+ // return messages:[] when the CLI exits or a new session begins.
381
+ const committedMessages = Array.isArray(adapterStatus.messages) ? adapterStatus.messages : [];
382
+ const isActiveNonIdle = adapterStatus.status !== 'idle';
383
+ const shouldApplyCommittedFloor = parsedMessages.length < committedMessages.length
384
+ && (adapterStatus.status === 'waiting_approval'
385
+ || (isActiveNonIdle && historyMessageCount === null));
386
+ if (shouldApplyCommittedFloor) {
387
+ parsedMessages = normalizeChatMessages(committedMessages as any);
388
+ }
370
389
  const mergedMessages = this.mergeConversationMessages(parsedMessages);
371
390
  const canonicalBackedHistory = this.syncCanonicalSavedHistoryIfNeeded();
372
391