@adhdev/daemon-core 0.8.60 → 0.8.61

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.
@@ -10,6 +10,7 @@
10
10
  // ─── readChat() return value ───────────────────────────
11
11
 
12
12
  import type { ProviderSummaryMetadata } from '../shared-types.js';
13
+ import type { ChatMessageKind } from './chat-message-normalization.js';
13
14
 
14
15
  export interface ReadChatResult {
15
16
  messages: ChatMessage[];
@@ -70,7 +71,7 @@ export interface ModalInfo {
70
71
  export interface ProviderEffectMessage {
71
72
  role?: 'system' | 'assistant' | 'user';
72
73
  content: string | MessagePart[];
73
- kind?: string;
74
+ kind?: ChatMessageKind;
74
75
  senderName?: string;
75
76
  }
76
77
 
@@ -89,6 +90,9 @@ export interface ProviderEffectNotification {
89
90
  channels?: ProviderNotificationChannel[];
90
91
  preferenceKey?: ProviderNotificationPreferenceKey;
91
92
  bubbleContent?: string | MessagePart[];
93
+ bubbleKind?: ChatMessageKind;
94
+ bubbleRole?: 'system' | 'assistant' | 'user';
95
+ bubbleSenderName?: string;
92
96
  }
93
97
 
94
98
  export interface ProviderEffect {
@@ -6,6 +6,9 @@ import type {
6
6
  ProviderControlOption,
7
7
  ProviderEffect,
8
8
  } from './contracts.js';
9
+ import { flattenContent } from './contracts.js';
10
+ import type { ChatMessage } from '../types.js';
11
+ import { buildChatMessage, buildRuntimeSystemChatMessage } from './chat-message-normalization.js';
9
12
 
10
13
  export type ProviderControlValue = string | number | boolean;
11
14
 
@@ -97,6 +100,15 @@ export function normalizeProviderEffects(data: any): ProviderEffect[] {
97
100
  bubbleContent: typeof raw.notification.bubbleContent === 'string' || Array.isArray(raw.notification.bubbleContent)
98
101
  ? raw.notification.bubbleContent
99
102
  : undefined,
103
+ bubbleKind: typeof raw.notification.bubbleKind === 'string'
104
+ ? raw.notification.bubbleKind
105
+ : undefined,
106
+ bubbleRole: raw.notification.bubbleRole === 'assistant' || raw.notification.bubbleRole === 'user'
107
+ ? raw.notification.bubbleRole
108
+ : (raw.notification.bubbleRole === 'system' ? 'system' : undefined),
109
+ bubbleSenderName: typeof raw.notification.bubbleSenderName === 'string'
110
+ ? raw.notification.bubbleSenderName
111
+ : undefined,
100
112
  },
101
113
  });
102
114
  }
@@ -105,6 +117,59 @@ export function normalizeProviderEffects(data: any): ProviderEffect[] {
105
117
  return effects;
106
118
  }
107
119
 
120
+ export function buildPersistedProviderEffectMessage(effect: ProviderEffect | null | undefined): ChatMessage | null {
121
+ if (!effect) return null;
122
+
123
+ if (effect.type === 'message' && effect.message) {
124
+ const role = effect.message.role === 'assistant' || effect.message.role === 'user'
125
+ ? effect.message.role
126
+ : 'system';
127
+ if (role === 'system') {
128
+ return buildRuntimeSystemChatMessage({
129
+ content: effect.message.content,
130
+ kind: effect.message.kind,
131
+ senderName: effect.message.senderName,
132
+ });
133
+ }
134
+ return buildChatMessage({
135
+ role,
136
+ content: effect.message.content,
137
+ kind: effect.message.kind,
138
+ senderName: effect.message.senderName,
139
+ } as ChatMessage);
140
+ }
141
+
142
+ if (effect.type === 'notification' && effect.notification) {
143
+ const bubbleContent = effect.notification.bubbleContent
144
+ ?? formatNotificationBubbleFallback(effect.notification.title, effect.notification.body);
145
+ const flattened = typeof bubbleContent === 'string' ? bubbleContent.trim() : flattenContent(bubbleContent).trim();
146
+ if (!flattened && (!Array.isArray(bubbleContent) || bubbleContent.length === 0)) return null;
147
+
148
+ const role = effect.notification.bubbleRole === 'assistant' || effect.notification.bubbleRole === 'user'
149
+ ? effect.notification.bubbleRole
150
+ : 'system';
151
+ if (role === 'system') {
152
+ return buildRuntimeSystemChatMessage({
153
+ content: bubbleContent,
154
+ kind: effect.notification.bubbleKind,
155
+ senderName: effect.notification.bubbleSenderName,
156
+ });
157
+ }
158
+ return buildChatMessage({
159
+ role,
160
+ content: bubbleContent,
161
+ kind: effect.notification.bubbleKind,
162
+ senderName: effect.notification.bubbleSenderName,
163
+ } as ChatMessage);
164
+ }
165
+
166
+ if (effect.type === 'toast' && effect.toast?.message) {
167
+ return buildRuntimeSystemChatMessage({ content: effect.toast.message });
168
+ }
169
+
170
+ return null;
171
+ }
172
+
108
173
  export function normalizeControlListResult(data: any): ControlListResult {
109
174
  if (data && typeof data === 'object' && Array.isArray(data.options)) {
110
175
  return {
@@ -196,3 +261,10 @@ function normalizeControlValue(value: any): ProviderControlValue {
196
261
  }
197
262
  return String(value);
198
263
  }
264
+
265
+ function formatNotificationBubbleFallback(title: string | undefined, body: string): string {
266
+ const cleanTitle = typeof title === 'string' ? title.trim() : '';
267
+ const cleanBody = String(body || '').trim();
268
+ if (cleanTitle && cleanBody) return `${cleanTitle}\n${cleanBody}`;
269
+ return cleanTitle || cleanBody;
270
+ }
@@ -5,13 +5,14 @@
5
5
  * CDP webview discovery + agent stream collection moved here.
6
6
  */
7
7
 
8
- import type { ProviderModule } from './contracts.js';
8
+ import { flattenContent, type ProviderModule } from './contracts.js';
9
9
  import type { ProviderInstance, ProviderState, ProviderEvent, InstanceContext } from './provider-instance.js';
10
10
  import { StatusMonitor } from './status-monitor.js';
11
- import { normalizeProviderEffects } from './control-effects.js';
11
+ import { buildPersistedProviderEffectMessage, normalizeProviderEffects } from './control-effects.js';
12
12
  import { ChatHistoryWriter } from '../config/chat-history.js';
13
13
  import type { ChatMessage } from '../types.js';
14
14
  import { mergeProviderPatchState, resolveProviderStateSurface } from './provider-patch-state.js';
15
+ import { buildChatMessage, buildRuntimeSystemChatMessage, normalizeChatMessages } from './chat-message-normalization.js';
15
16
 
16
17
  export class ExtensionProviderInstance implements ProviderInstance {
17
18
  readonly type: string;
@@ -259,8 +260,8 @@ export class ExtensionProviderInstance implements ProviderInstance {
259
260
  this.appliedEffectKeys.add(effectKey);
260
261
 
261
262
  if (effect.persist !== false) {
262
- const persisted = this.getPersistedEffectContent(effect);
263
- if (persisted) this.appendRuntimeSystemMessage(persisted, effectKey);
263
+ const persistedMessage = buildPersistedProviderEffectMessage(effect);
264
+ if (persistedMessage) this.appendRuntimeMessage(persistedMessage, effectKey);
264
265
  }
265
266
 
266
267
  if (effect.type === 'message' && effect.message) {
@@ -299,36 +300,47 @@ export class ExtensionProviderInstance implements ProviderInstance {
299
300
  }
300
301
 
301
302
  private appendRuntimeSystemMessage(content: string, dedupKey: string, receivedAt = Date.now()): void {
302
- const normalizedContent = String(content || '').trim();
303
- if (!normalizedContent) return;
303
+ this.appendRuntimeMessage(buildRuntimeSystemChatMessage({
304
+ content,
305
+ receivedAt,
306
+ timestamp: receivedAt,
307
+ }), dedupKey);
308
+ }
309
+
310
+ private appendRuntimeMessage(message: ChatMessage, dedupKey: string): void {
311
+ const normalizedMessage = buildChatMessage({
312
+ ...message,
313
+ receivedAt: typeof message.receivedAt === 'number' ? message.receivedAt : (message.timestamp || Date.now()),
314
+ timestamp: typeof message.timestamp === 'number' ? message.timestamp : (message.receivedAt || Date.now()),
315
+ } as ChatMessage);
316
+ const normalizedContent = typeof normalizedMessage.content === 'string'
317
+ ? normalizedMessage.content.trim()
318
+ : flattenContent(normalizedMessage.content).trim();
319
+ if (!normalizedContent && (!Array.isArray(normalizedMessage.content) || normalizedMessage.content.length === 0)) return;
304
320
  if (this.runtimeMessages.some((entry) => entry.key === dedupKey)) return;
305
321
 
306
322
  this.runtimeMessages.push({
307
323
  key: dedupKey,
308
- message: {
309
- role: 'system',
310
- senderName: 'System',
311
- content: normalizedContent,
312
- receivedAt,
313
- timestamp: receivedAt,
314
- },
324
+ message: normalizedMessage,
315
325
  });
316
326
  if (this.runtimeMessages.length > 50) this.runtimeMessages = this.runtimeMessages.slice(-50);
317
327
 
318
- this.historyWriter.appendNewMessages(
319
- this.type,
320
- [{
321
- role: 'system',
322
- senderName: 'System',
323
- content: normalizedContent,
324
- kind: 'system',
325
- receivedAt,
326
- historyDedupKey: dedupKey,
327
- }],
328
- this.chatTitle || this.agentName || this.provider.name,
329
- this.instanceId,
330
- this.chatId || this.instanceId,
331
- );
328
+ if (normalizedContent) {
329
+ this.historyWriter.appendNewMessages(
330
+ this.type,
331
+ [{
332
+ role: normalizedMessage.role,
333
+ senderName: normalizedMessage.senderName,
334
+ kind: normalizedMessage.kind,
335
+ content: normalizedContent,
336
+ receivedAt: normalizedMessage.receivedAt || normalizedMessage.timestamp,
337
+ historyDedupKey: dedupKey,
338
+ }],
339
+ this.chatTitle || this.agentName || this.provider.name,
340
+ this.instanceId,
341
+ this.chatId || this.instanceId,
342
+ );
343
+ }
332
344
  }
333
345
 
334
346
  /**
@@ -348,12 +360,12 @@ export class ExtensionProviderInstance implements ProviderInstance {
348
360
  }
349
361
 
350
362
  this.prevMessageHashes = nextHashes;
351
- return messages;
363
+ return normalizeChatMessages(messages);
352
364
  }
353
365
 
354
366
  private mergeConversationMessages(messages: any[]): ChatMessage[] {
355
- if (this.runtimeMessages.length === 0) return messages;
356
- return [...messages, ...this.runtimeMessages.map((entry) => entry.message)]
367
+ if (this.runtimeMessages.length === 0) return normalizeChatMessages(messages);
368
+ return normalizeChatMessages([...messages, ...this.runtimeMessages.map((entry) => entry.message)]
357
369
  .map((message, index) => ({ message, index }))
358
370
  .sort((a, b) => {
359
371
  const aTime = a.message.receivedAt || a.message.timestamp || 0;
@@ -361,7 +373,7 @@ export class ExtensionProviderInstance implements ProviderInstance {
361
373
  if (aTime !== bTime) return aTime - bTime;
362
374
  return a.index - b.index;
363
375
  })
364
- .map((entry) => entry.message);
376
+ .map((entry) => entry.message));
365
377
  }
366
378
 
367
379
  private getPersistedEffectContent(effect: { type: string; message?: { content?: unknown }; toast?: { message?: string }; notification?: { title?: string; body?: string; bubbleContent?: unknown } }): string | null {
@@ -11,16 +11,17 @@
11
11
 
12
12
  import * as os from 'os';
13
13
  import * as crypto from 'crypto';
14
- import type { ProviderModule } from './contracts.js';
14
+ import { flattenContent, type ProviderModule } from './contracts.js';
15
15
  import type { ProviderInstance, ProviderState, ProviderEvent, InstanceContext } from './provider-instance.js';
16
16
  import { ExtensionProviderInstance } from './extension-provider-instance.js';
17
17
  import { StatusMonitor } from './status-monitor.js';
18
18
  import { ChatHistoryWriter } from '../config/chat-history.js';
19
19
  import { LOG } from '../logging/logger.js';
20
- import { normalizeProviderEffects } from './control-effects.js';
20
+ import { buildPersistedProviderEffectMessage, normalizeProviderEffects } from './control-effects.js';
21
21
  import type { ChatMessage } from '../types.js';
22
22
  import { formatAutoApprovalMessage, pickApprovalButton } from './approval-utils.js';
23
23
  import { mergeProviderPatchState, resolveProviderStateSurface } from './provider-patch-state.js';
24
+ import { buildChatMessage, buildRuntimeSystemChatMessage, normalizeChatMessages } from './chat-message-normalization.js';
24
25
 
25
26
  type ReadChatModal = {
26
27
  message?: string;
@@ -497,8 +498,8 @@ export class IdeProviderInstance implements ProviderInstance {
497
498
  this.appliedEffectKeys.add(effectKey);
498
499
 
499
500
  if (effect.persist !== false) {
500
- const persisted = this.getPersistedEffectContent(effect);
501
- if (persisted) this.appendRuntimeSystemMessage(persisted, effectKey);
501
+ const persistedMessage = buildPersistedProviderEffectMessage(effect);
502
+ if (persistedMessage) this.appendRuntimeMessage(persistedMessage, effectKey);
502
503
  }
503
504
 
504
505
  if (effect.type === 'message' && effect.message) {
@@ -537,8 +538,23 @@ export class IdeProviderInstance implements ProviderInstance {
537
538
  }
538
539
 
539
540
  private appendRuntimeSystemMessage(content: string, dedupKey: string, receivedAt = Date.now()): void {
540
- const normalizedContent = String(content || '').trim();
541
- if (!normalizedContent) return;
541
+ this.appendRuntimeMessage(buildRuntimeSystemChatMessage({
542
+ content,
543
+ receivedAt,
544
+ timestamp: receivedAt,
545
+ }), dedupKey);
546
+ }
547
+
548
+ private appendRuntimeMessage(message: ChatMessage, dedupKey: string): void {
549
+ const normalizedMessage = buildChatMessage({
550
+ ...message,
551
+ receivedAt: typeof message.receivedAt === 'number' ? message.receivedAt : (message.timestamp || Date.now()),
552
+ timestamp: typeof message.timestamp === 'number' ? message.timestamp : (message.receivedAt || Date.now()),
553
+ } as ChatMessage);
554
+ const normalizedContent = typeof normalizedMessage.content === 'string'
555
+ ? normalizedMessage.content.trim()
556
+ : flattenContent(normalizedMessage.content).trim();
557
+ if (!normalizedContent && (!Array.isArray(normalizedMessage.content) || normalizedMessage.content.length === 0)) return;
542
558
  if (this.runtimeMessages.some((entry) => entry.key === dedupKey)) return;
543
559
  if (!this.cachedChat) {
544
560
  this.cachedChat = {
@@ -553,35 +569,31 @@ export class IdeProviderInstance implements ProviderInstance {
553
569
 
554
570
  this.runtimeMessages.push({
555
571
  key: dedupKey,
556
- message: {
557
- role: 'system',
558
- senderName: 'System',
559
- content: normalizedContent,
560
- receivedAt,
561
- timestamp: receivedAt,
562
- },
572
+ message: normalizedMessage,
563
573
  });
564
574
  if (this.runtimeMessages.length > 50) this.runtimeMessages = this.runtimeMessages.slice(-50);
565
575
 
566
- this.historyWriter.appendNewMessages(
567
- this.type,
568
- [{
569
- role: 'system',
570
- senderName: 'System',
571
- content: normalizedContent,
572
- kind: 'system',
573
- receivedAt,
574
- historyDedupKey: dedupKey,
575
- }],
576
- this.cachedChat?.title || this.provider.name,
577
- this.instanceId,
578
- this.cachedChat?.id || this.instanceId,
579
- );
576
+ if (normalizedContent) {
577
+ this.historyWriter.appendNewMessages(
578
+ this.type,
579
+ [{
580
+ role: normalizedMessage.role,
581
+ senderName: normalizedMessage.senderName,
582
+ kind: normalizedMessage.kind,
583
+ content: normalizedContent,
584
+ receivedAt: normalizedMessage.receivedAt || normalizedMessage.timestamp,
585
+ historyDedupKey: dedupKey,
586
+ }],
587
+ this.cachedChat?.title || this.provider.name,
588
+ this.instanceId,
589
+ this.cachedChat?.id || this.instanceId,
590
+ );
591
+ }
580
592
  }
581
593
 
582
594
  private mergeConversationMessages(messages: any[]): ChatMessage[] {
583
- if (this.runtimeMessages.length === 0) return messages;
584
- return [...messages, ...this.runtimeMessages.map((entry) => entry.message)]
595
+ if (this.runtimeMessages.length === 0) return normalizeChatMessages(messages);
596
+ return normalizeChatMessages([...messages, ...this.runtimeMessages.map((entry) => entry.message)]
585
597
  .map((message, index) => ({ message, index }))
586
598
  .sort((a, b) => {
587
599
  const aTime = a.message.receivedAt || a.message.timestamp || 0;
@@ -589,7 +601,7 @@ export class IdeProviderInstance implements ProviderInstance {
589
601
  if (aTime !== bTime) return aTime - bTime;
590
602
  return a.index - b.index;
591
603
  })
592
- .map((entry) => entry.message);
604
+ .map((entry) => entry.message));
593
605
  }
594
606
 
595
607
  private getPersistedEffectContent(effect: { type: string; message?: { content?: unknown }; toast?: { message?: string }; notification?: { title?: string; body?: string; bubbleContent?: unknown } }): string | null {
package/src/types.ts CHANGED
@@ -5,6 +5,7 @@
5
5
  * When modifying this file, also update interface contracts in AGENT_PROTOCOL.md.
6
6
  */
7
7
  import type { StatusReportPayload, AvailableProviderInfo } from './shared-types.js';
8
+ import type { ChatMessageKind } from './providers/chat-message-normalization.js';
8
9
 
9
10
  // ── Daemon Status ──
10
11
 
@@ -29,7 +30,7 @@ export interface ChatMessage {
29
30
  role: string; // 'user' | 'assistant' | 'system' | 'human'
30
31
  /** Plain text (legacy) or canonical message parts */
31
32
  content: string | MessagePart[];
32
- kind?: string; // 'standard' | 'thought' | 'tool' | 'terminal' | 'system'
33
+ kind?: ChatMessageKind; // built-ins: standard | thought | tool | terminal | system; custom kinds allowed
33
34
  id?: string;
34
35
  index?: number;
35
36
  timestamp?: number;