@adhdev/daemon-core 0.8.59 → 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,7 +10,7 @@ import * as path from 'path';
10
10
  import * as crypto from 'crypto';
11
11
  import * as fs from 'fs';
12
12
  import { createRequire } from 'node:module';
13
- import { normalizeInputEnvelope, type ProviderModule } from './contracts.js';
13
+ import { normalizeInputEnvelope, type ProviderModule, flattenContent } from './contracts.js';
14
14
  import type { ProviderInstance, ProviderState, ProviderEvent, InstanceContext } from './provider-instance.js';
15
15
  import { ProviderCliAdapter } from '../cli-adapters/provider-cli-adapter.js';
16
16
  import type { CliProviderModule } from '../cli-adapters/provider-cli-adapter.js';
@@ -19,10 +19,11 @@ import { StatusMonitor } from './status-monitor.js';
19
19
  import { ChatHistoryWriter, readChatHistory } from '../config/chat-history.js';
20
20
  import { LOG } from '../logging/logger.js';
21
21
  import type { ChatMessage } from '../types.js';
22
- import { normalizeProviderEffects } from './control-effects.js';
22
+ import { buildPersistedProviderEffectMessage, normalizeProviderEffects } from './control-effects.js';
23
23
  import { formatAutoApprovalMessage, pickApprovalButton } from './approval-utils.js';
24
24
  import { getCliScriptCommand, parseCliScriptResult } from './cli-script-results.js';
25
25
  import { mergeProviderPatchState, resolveProviderStateSurface } from './provider-patch-state.js';
26
+ import { buildChatMessage, buildRuntimeSystemChatMessage, normalizeChatMessages } from './chat-message-normalization.js';
26
27
 
27
28
  let CachedDatabaseSync: (new (path: string, options?: { readOnly?: boolean }) => {
28
29
  prepare(sql: string): { get(...params: Array<string | number>): unknown };
@@ -628,8 +629,8 @@ export class CliProviderInstance implements ProviderInstance {
628
629
  this.appliedEffectKeys.add(effectKey);
629
630
 
630
631
  if (effect.persist !== false) {
631
- const persisted = this.getPersistedEffectContent(effect);
632
- if (persisted) this.appendRuntimeSystemMessage(persisted, effectKey);
632
+ const persistedMessage = buildPersistedProviderEffectMessage(effect);
633
+ if (persistedMessage) this.appendRuntimeMessage(persistedMessage, effectKey);
633
634
  }
634
635
 
635
636
  if (effect.type === 'message' && effect.message) {
@@ -772,43 +773,55 @@ export class CliProviderInstance implements ProviderInstance {
772
773
  }
773
774
 
774
775
  private appendRuntimeSystemMessage(content: string, dedupKey: string, receivedAt = Date.now()): void {
775
- const normalizedContent = String(content || '').trim();
776
- if (!normalizedContent) return;
776
+ this.appendRuntimeMessage(buildRuntimeSystemChatMessage({
777
+ content,
778
+ receivedAt,
779
+ timestamp: receivedAt,
780
+ }), dedupKey);
781
+ }
782
+
783
+ private appendRuntimeMessage(message: ChatMessage, dedupKey: string): void {
784
+ const normalizedMessage = buildChatMessage({
785
+ ...message,
786
+ receivedAt: typeof message.receivedAt === 'number' ? message.receivedAt : (message.timestamp || Date.now()),
787
+ timestamp: typeof message.timestamp === 'number' ? message.timestamp : (message.receivedAt || Date.now()),
788
+ } as ChatMessage);
789
+ const normalizedContent = typeof normalizedMessage.content === 'string'
790
+ ? normalizedMessage.content.trim()
791
+ : flattenContent(normalizedMessage.content).trim();
792
+ if (!normalizedContent && (!Array.isArray(normalizedMessage.content) || normalizedMessage.content.length === 0)) return;
777
793
  if (this.runtimeMessages.some((entry) => entry.key === dedupKey)) return;
778
794
 
779
795
  this.runtimeMessages.push({
780
796
  key: dedupKey,
781
- message: {
782
- role: 'system',
783
- senderName: 'System',
784
- content: normalizedContent,
785
- receivedAt,
786
- timestamp: receivedAt,
787
- },
797
+ message: normalizedMessage,
788
798
  });
789
799
  if (this.runtimeMessages.length > 50) {
790
800
  this.runtimeMessages = this.runtimeMessages.slice(-50);
791
801
  }
792
802
 
793
- this.historyWriter.appendNewMessages(
794
- this.type,
795
- [{
796
- role: 'system',
797
- senderName: 'System',
798
- content: normalizedContent,
799
- receivedAt,
800
- historyDedupKey: dedupKey,
801
- }],
802
- this.adapter.getScriptParsedStatus?.()?.title || this.workingDir.split('/').filter(Boolean).pop() || 'session',
803
- this.instanceId,
804
- this.providerSessionId,
805
- );
803
+ if (normalizedContent) {
804
+ this.historyWriter.appendNewMessages(
805
+ this.type,
806
+ [{
807
+ role: normalizedMessage.role,
808
+ senderName: normalizedMessage.senderName,
809
+ kind: normalizedMessage.kind,
810
+ content: normalizedContent,
811
+ receivedAt: normalizedMessage.receivedAt || normalizedMessage.timestamp,
812
+ historyDedupKey: dedupKey,
813
+ }],
814
+ this.adapter.getScriptParsedStatus?.()?.title || this.workingDir.split('/').filter(Boolean).pop() || 'session',
815
+ this.instanceId,
816
+ this.providerSessionId,
817
+ );
818
+ }
806
819
  }
807
820
 
808
821
  private mergeConversationMessages(parsedMessages: any[]): ChatMessage[] {
809
- if (this.runtimeMessages.length === 0) return parsedMessages;
822
+ if (this.runtimeMessages.length === 0) return normalizeChatMessages(parsedMessages);
810
823
 
811
- return [...parsedMessages, ...this.runtimeMessages.map((entry) => entry.message)]
824
+ return normalizeChatMessages([...parsedMessages, ...this.runtimeMessages.map((entry) => entry.message)]
812
825
  .map((message, index) => ({ message, index }))
813
826
  .sort((a, b) => {
814
827
  const aTime = a.message.receivedAt || a.message.timestamp || 0;
@@ -816,7 +829,7 @@ export class CliProviderInstance implements ProviderInstance {
816
829
  if (aTime !== bTime) return aTime - bTime;
817
830
  return a.index - b.index;
818
831
  })
819
- .map((entry) => entry.message);
832
+ .map((entry) => entry.message));
820
833
  }
821
834
 
822
835
  private formatApprovalRequestMessage(modalMessage?: string, buttons?: string[]): string {
@@ -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;