@adhdev/daemon-core 0.9.39 → 0.9.40

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.39",
3
+ "version": "0.9.40",
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.39",
3
+ "version": "0.9.40",
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",
@@ -173,10 +173,12 @@ export function hydrateCliParsedMessages(
173
173
  });
174
174
  }
175
175
 
176
- function chooseMoreComparableCliMessage(left: CliChatMessage, right: CliChatMessage): CliChatMessage {
177
- const leftComparable = normalizeComparableMessageContent(left.content || '');
178
- const rightComparable = normalizeComparableMessageContent(right.content || '');
179
-
176
+ function chooseMoreComparableCliMessage(
177
+ left: CliChatMessage,
178
+ right: CliChatMessage,
179
+ leftComparable = normalizeComparableMessageContent(left.content || ''),
180
+ rightComparable = normalizeComparableMessageContent(right.content || ''),
181
+ ): CliChatMessage {
180
182
  if (leftComparable && leftComparable === rightComparable) {
181
183
  const leftNewlines = String(left.content || '').split(/\r\n|\n|\r/g).length - 1;
182
184
  const rightNewlines = String(right.content || '').split(/\r\n|\n|\r/g).length - 1;
@@ -187,35 +189,43 @@ function chooseMoreComparableCliMessage(left: CliChatMessage, right: CliChatMess
187
189
  }
188
190
 
189
191
  function dedupeConsecutiveComparableCliMessages(messages: CliChatMessage[]): CliChatMessage[] {
190
- const deduped: CliChatMessage[] = [];
192
+ const deduped: Array<{ message: CliChatMessage; comparable: string }> = [];
191
193
 
192
194
  for (const message of messages) {
193
195
  const current = {
194
196
  ...message,
195
197
  content: typeof message.content === 'string' ? message.content : String(message.content || ''),
196
198
  } as CliChatMessage;
199
+ const currentComparable = normalizeComparableMessageContent(current.content || '');
197
200
  const previous = deduped[deduped.length - 1];
198
201
  if (!previous) {
199
- deduped.push(current);
202
+ deduped.push({ message: current, comparable: currentComparable });
200
203
  continue;
201
204
  }
202
205
 
203
- const previousComparable = normalizeComparableMessageContent(previous.content || '');
204
- const currentComparable = normalizeComparableMessageContent(current.content || '');
205
- const sameRole = previous.role === current.role;
206
- const sameKind = (previous.kind || 'standard') === (current.kind || 'standard');
207
- const sameSender = (previous.senderName || '') === (current.senderName || '');
208
- const comparableMatch = previousComparable && previousComparable === currentComparable;
206
+ const sameRole = previous.message.role === current.role;
207
+ const sameKind = (previous.message.kind || 'standard') === (current.kind || 'standard');
208
+ const sameSender = (previous.message.senderName || '') === (current.senderName || '');
209
+ const comparableMatch = previous.comparable && previous.comparable === currentComparable;
209
210
 
210
211
  if (sameRole && sameKind && sameSender && comparableMatch) {
211
- deduped[deduped.length - 1] = chooseMoreComparableCliMessage(previous, current);
212
+ const selected = chooseMoreComparableCliMessage(
213
+ previous.message,
214
+ current,
215
+ previous.comparable,
216
+ currentComparable,
217
+ );
218
+ deduped[deduped.length - 1] = {
219
+ message: selected,
220
+ comparable: selected === current ? currentComparable : previous.comparable,
221
+ };
212
222
  continue;
213
223
  }
214
224
 
215
- deduped.push(current);
225
+ deduped.push({ message: current, comparable: currentComparable });
216
226
  }
217
227
 
218
- return deduped;
228
+ return deduped.map((entry) => entry.message);
219
229
  }
220
230
 
221
231
  export function normalizeCliParsedMessages(
@@ -178,85 +178,90 @@ function normalizeReadChatMessages(payload: Record<string, any>): ChatMessage[]
178
178
  return normalizeChatMessages(messages);
179
179
  }
180
180
 
181
- function buildReadChatReplayCollapseSignature(message: ChatMessage | null | undefined): string {
182
- if (!message) return '';
183
- const role = typeof message.role === 'string' ? message.role.trim().toLowerCase() : '';
184
- const kind = typeof message.kind === 'string' ? message.kind.trim().toLowerCase() : 'standard';
185
- const senderName = typeof message.senderName === 'string' ? message.senderName.trim().toLowerCase() : '';
186
- const content = flattenContent(message.content || '').replace(/\s+/g, ' ').trim();
187
- return `${role}:${kind}:${senderName}:${content}`;
188
- }
189
-
190
- function shouldCollapseReadChatReplayDuplicate(message: ChatMessage | null | undefined): boolean {
191
- if (!message) return false;
192
- const role = typeof message.role === 'string' ? message.role.trim().toLowerCase() : '';
193
- return role === 'assistant' || role === 'system';
181
+ interface ReadChatReplayCollapseInfo {
182
+ role: string;
183
+ kind: string;
184
+ senderName: string;
185
+ content: string;
186
+ signature: string;
187
+ collapsible: boolean;
194
188
  }
195
189
 
196
- function normalizeReadChatReplayText(message: ChatMessage | null | undefined): string {
197
- return flattenContent(message?.content || '').replace(/\s+/g, ' ').trim();
190
+ function normalizeReadChatReplayTextContent(content: ChatMessage['content'] | undefined): string {
191
+ return flattenContent(content || '').replace(/\s+/g, ' ').trim();
198
192
  }
199
193
 
200
- function isStableReadChatAssistantAnswer(message: ChatMessage | null | undefined): boolean {
201
- if (!message) return false;
194
+ function getReadChatReplayCollapseInfo(message: ChatMessage | null | undefined): ReadChatReplayCollapseInfo | null {
195
+ if (!message) return null;
202
196
  const role = typeof message.role === 'string' ? message.role.trim().toLowerCase() : '';
203
197
  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;
198
+ const senderName = typeof message.senderName === 'string' ? message.senderName.trim().toLowerCase() : '';
199
+ const collapsible = role === 'assistant' || role === 'system';
200
+ if (!collapsible) return { role, kind, senderName, content: '', signature: '', collapsible };
201
+ const content = normalizeReadChatReplayTextContent(message.content);
202
+ return {
203
+ role,
204
+ kind,
205
+ senderName,
206
+ content,
207
+ signature: `${role}:${kind}:${senderName}:${content}`,
208
+ collapsible,
209
+ };
210
+ }
211
+
212
+ function isStableReadChatAssistantAnswerInfo(info: ReadChatReplayCollapseInfo | null): boolean {
213
+ if (!info) return false;
214
+ if (info.role !== 'assistant') return false;
215
+ if (info.kind && info.kind !== 'standard') return false;
216
+ if (info.content.length < 160) return false;
208
217
 
209
218
  // A provider may surface expanded command output as a standard assistant bubble
210
219
  // (for example Claude Code's "Bash command ..." block). That is live work output,
211
220
  // not a stable final answer. Treating it as a terminal answer would hide the
212
221
  // real final response and violate read_chat fidelity.
213
- if (/^(bash|shell|terminal) command\b/i.test(content)) return false;
222
+ if (/^(bash|shell|terminal) command\b/i.test(info.content)) return false;
214
223
  return true;
215
224
  }
216
225
 
217
- function isReplayedAssistantAnswerAfterStableAnswer(
218
- message: ChatMessage | null | undefined,
219
- stableAnswer: ChatMessage | null,
226
+ function isReplayedAssistantAnswerAfterStableAnswerInfo(
227
+ info: ReadChatReplayCollapseInfo | null,
228
+ stableContent: string,
220
229
  ): 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);
230
+ if (!info || !stableContent) return false;
231
+ if (info.role !== 'assistant') return false;
232
+ if (info.kind && info.kind !== 'standard') return false;
233
+ const content = info.content;
228
234
  if (content.length < 80 || stableContent.length < 80) return false;
229
235
  return content === stableContent || content.startsWith(stableContent) || stableContent.startsWith(content);
230
236
  }
231
237
 
232
- function collapseReplayDuplicatesFromReadChat(messages: ChatMessage[]): ChatMessage[] {
238
+ export function collapseReplayDuplicatesFromReadChat(messages: ChatMessage[]): ChatMessage[] {
233
239
  const collapsed: ChatMessage[] = [];
234
240
  const replaySignaturesInCurrentTurn = new Set<string>();
235
- let stableAssistantAnswerInCurrentTurn: ChatMessage | null = null;
241
+ let stableAssistantAnswerContentInCurrentTurn = '';
242
+ let previousReplaySignature = '';
236
243
 
237
244
  for (const message of messages) {
238
- const role = typeof message.role === 'string' ? message.role.trim().toLowerCase() : '';
239
- if (role === 'user') {
245
+ const info = getReadChatReplayCollapseInfo(message);
246
+ if (info?.role === 'user') {
240
247
  replaySignaturesInCurrentTurn.clear();
241
- stableAssistantAnswerInCurrentTurn = null;
248
+ stableAssistantAnswerContentInCurrentTurn = '';
249
+ previousReplaySignature = '';
242
250
  }
243
251
 
244
- const signature = buildReadChatReplayCollapseSignature(message);
245
- const previous = collapsed[collapsed.length - 1];
246
- const previousSignature = buildReadChatReplayCollapseSignature(previous);
247
-
248
- if (shouldCollapseReadChatReplayDuplicate(message) && signature) {
249
- if (previousSignature === signature) continue;
250
- if (replaySignaturesInCurrentTurn.has(signature)) continue;
251
- if (isReplayedAssistantAnswerAfterStableAnswer(message, stableAssistantAnswerInCurrentTurn)) continue;
252
+ if (info?.collapsible && info.signature) {
253
+ if (previousReplaySignature === info.signature) continue;
254
+ if (replaySignaturesInCurrentTurn.has(info.signature)) continue;
255
+ if (isReplayedAssistantAnswerAfterStableAnswerInfo(info, stableAssistantAnswerContentInCurrentTurn)) continue;
252
256
  }
253
257
 
254
258
  collapsed.push(message);
255
- if (shouldCollapseReadChatReplayDuplicate(message) && signature) {
256
- replaySignaturesInCurrentTurn.add(signature);
259
+ previousReplaySignature = info?.collapsible ? info.signature : '';
260
+ if (info?.collapsible && info.signature) {
261
+ replaySignaturesInCurrentTurn.add(info.signature);
257
262
  }
258
- if (isStableReadChatAssistantAnswer(message)) {
259
- stableAssistantAnswerInCurrentTurn = message;
263
+ if (isStableReadChatAssistantAnswerInfo(info)) {
264
+ stableAssistantAnswerContentInCurrentTurn = info?.content || '';
260
265
  }
261
266
  }
262
267
 
@@ -369,13 +374,17 @@ function computeReadChatSync(messages: ChatMessage[], cursor: Required<ReadChatC
369
374
  }
370
375
 
371
376
  if (cursor.tailLimit > 0 && knownSignature === lastMessageSignature) {
372
- return {
373
- syncMode: 'noop',
374
- replaceFrom: totalMessages,
375
- messages: [],
376
- totalMessages,
377
- lastMessageSignature,
378
- };
377
+ const requestedTailCount = Math.min(totalMessages, cursor.tailLimit);
378
+ if (knownMessageCount >= requestedTailCount) {
379
+ return {
380
+ syncMode: 'noop',
381
+ replaceFrom: totalMessages,
382
+ messages: [],
383
+ totalMessages,
384
+ lastMessageSignature,
385
+ };
386
+ }
387
+ return buildBoundedTailSync(messages, cursor);
379
388
  }
380
389
 
381
390
  if (knownMessageCount < totalMessages) {