@adhdev/daemon-core 0.8.21 → 0.8.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.
- package/dist/agent-stream/types.d.ts +3 -0
- package/dist/cli-adapter-types.d.ts +3 -0
- package/dist/cli-adapters/provider-cli-adapter.d.ts +71 -11
- package/dist/commands/stream-commands.d.ts +1 -0
- package/dist/config/config.d.ts +6 -0
- package/dist/index.js +1163 -314
- package/dist/index.js.map +1 -1
- package/dist/index.mjs +1163 -314
- package/dist/index.mjs.map +1 -1
- package/dist/providers/cli-provider-instance.d.ts +6 -0
- package/dist/providers/contracts.d.ts +59 -1
- package/dist/providers/control-effects.d.ts +4 -0
- package/dist/providers/extension-provider-instance.d.ts +9 -0
- package/dist/providers/ide-provider-instance.d.ts +8 -0
- package/dist/shared-types.d.ts +2 -1
- package/node_modules/@adhdev/session-host-core/package.json +1 -1
- package/package.json +3 -2
- package/src/agent-stream/forward.ts +2 -0
- package/src/agent-stream/provider-adapter.ts +5 -15
- package/src/agent-stream/types.ts +4 -0
- package/src/cli-adapter-types.ts +3 -0
- package/src/cli-adapters/provider-cli-adapter.ts +399 -49
- package/src/commands/chat-commands.ts +33 -12
- package/src/commands/handler.ts +1 -0
- package/src/commands/stream-commands.ts +99 -8
- package/src/config/config.d.ts +1 -0
- package/src/config/config.ts +9 -0
- package/src/launch.ts +57 -11
- package/src/providers/cli-provider-instance.ts +148 -2
- package/src/providers/contracts.ts +65 -2
- package/src/providers/control-effects.ts +114 -0
- package/src/providers/extension-provider-instance.ts +163 -3
- package/src/providers/ide-provider-instance.ts +181 -2
- package/src/shared-types.d.ts +1 -0
- package/src/shared-types.ts +2 -1
- package/src/status/snapshot.ts +1 -0
|
@@ -26,6 +26,10 @@ export interface ReadChatResult {
|
|
|
26
26
|
inputContent?: string;
|
|
27
27
|
model?: string;
|
|
28
28
|
autoApprove?: string;
|
|
29
|
+
/** Explicit dynamic control values returned by the provider */
|
|
30
|
+
controlValues?: Record<string, string | number | boolean>;
|
|
31
|
+
/** Provider-driven UI effects derived from chat state */
|
|
32
|
+
effects?: ProviderEffect[];
|
|
29
33
|
}
|
|
30
34
|
|
|
31
35
|
import type { ChatMessage } from '../types.js';
|
|
@@ -46,6 +50,43 @@ export interface ModalInfo {
|
|
|
46
50
|
height?: number;
|
|
47
51
|
}
|
|
48
52
|
|
|
53
|
+
export interface ProviderEffectMessage {
|
|
54
|
+
role?: 'system' | 'assistant' | 'user';
|
|
55
|
+
content: string | ContentBlock[];
|
|
56
|
+
kind?: string;
|
|
57
|
+
senderName?: string;
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
export interface ProviderEffectToast {
|
|
61
|
+
level?: 'info' | 'success' | 'warning';
|
|
62
|
+
message: string;
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
export type ProviderNotificationPreferenceKey = 'disconnect' | 'completion' | 'approval' | 'browser';
|
|
66
|
+
export type ProviderNotificationChannel = 'bubble' | 'toast' | 'browser';
|
|
67
|
+
|
|
68
|
+
export interface ProviderEffectNotification {
|
|
69
|
+
title?: string;
|
|
70
|
+
body: string;
|
|
71
|
+
level?: 'info' | 'success' | 'warning';
|
|
72
|
+
channels?: ProviderNotificationChannel[];
|
|
73
|
+
preferenceKey?: ProviderNotificationPreferenceKey;
|
|
74
|
+
bubbleContent?: string | ContentBlock[];
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
export interface ProviderEffect {
|
|
78
|
+
type: 'message' | 'toast' | 'notification';
|
|
79
|
+
/** Stable dedup key; falls back to a content hash when omitted */
|
|
80
|
+
id?: string;
|
|
81
|
+
/** Default immediate. turn_completed fires only on generating/waiting -> idle transitions. */
|
|
82
|
+
when?: 'immediate' | 'turn_completed';
|
|
83
|
+
/** Default true. False keeps the effect UI-only. */
|
|
84
|
+
persist?: boolean;
|
|
85
|
+
message?: ProviderEffectMessage;
|
|
86
|
+
toast?: ProviderEffectToast;
|
|
87
|
+
notification?: ProviderEffectNotification;
|
|
88
|
+
}
|
|
89
|
+
|
|
49
90
|
// ─── Rich Content Types (ACP Standard) ─────────────────
|
|
50
91
|
// Based on ACP SDK v0.16.1 schema types.
|
|
51
92
|
// All provider categories (ACP, IDE, Extension, CLI) use these as output standard.
|
|
@@ -290,13 +331,35 @@ export interface ProviderModule {
|
|
|
290
331
|
versionCommand?: string;
|
|
291
332
|
/** Versions tested by provider maintainer (informational) */
|
|
292
333
|
testedVersions?: string[];
|
|
293
|
-
|
|
334
|
+
/** Per-OS process names — used by launch.ts to detect/kill IDE processes */
|
|
294
335
|
processNames?: {
|
|
295
336
|
darwin?: string;
|
|
296
337
|
win32?: string[];
|
|
297
338
|
linux?: string[];
|
|
298
339
|
[key: string]: string | string[] | undefined;
|
|
299
340
|
};
|
|
341
|
+
/**
|
|
342
|
+
* IDE launch preferences.
|
|
343
|
+
* Lets each provider choose how its GUI app should be started per platform.
|
|
344
|
+
*/
|
|
345
|
+
launch?: {
|
|
346
|
+
/**
|
|
347
|
+
* Preferred launch method by platform.
|
|
348
|
+
* - 'cli': use the IDE CLI wrapper/binary
|
|
349
|
+
* - 'app': use platform app launcher (e.g. `open -a` on macOS)
|
|
350
|
+
* - 'auto': let core choose a sensible default
|
|
351
|
+
*/
|
|
352
|
+
prefer?: {
|
|
353
|
+
darwin?: 'auto' | 'cli' | 'app';
|
|
354
|
+
win32?: 'auto' | 'cli' | 'app';
|
|
355
|
+
linux?: 'auto' | 'cli' | 'app';
|
|
356
|
+
[key: string]: 'auto' | 'cli' | 'app' | undefined;
|
|
357
|
+
};
|
|
358
|
+
/**
|
|
359
|
+
* Override how long core waits for CDP to come up after launch.
|
|
360
|
+
*/
|
|
361
|
+
cdpStartupTimeoutMs?: number;
|
|
362
|
+
};
|
|
300
363
|
/** Per-OS install paths — used by detector.ts to detect IDE installation */
|
|
301
364
|
paths?: {
|
|
302
365
|
darwin?: string[];
|
|
@@ -580,7 +643,7 @@ export interface ProviderSettingSchema extends ProviderSettingDef {
|
|
|
580
643
|
* - 'slider' — numeric range (temperature: 0–2)
|
|
581
644
|
* - 'action' — one-shot button (show usage, restart, clear context)
|
|
582
645
|
*/
|
|
583
|
-
export type ProviderControlType = 'select' | 'toggle' | 'cycle' | 'slider' | 'action';
|
|
646
|
+
export type ProviderControlType = 'select' | 'toggle' | 'cycle' | 'slider' | 'action' | 'display';
|
|
584
647
|
|
|
585
648
|
/**
|
|
586
649
|
* Where the control appears in the chat UI:
|
|
@@ -0,0 +1,114 @@
|
|
|
1
|
+
import type { ProviderControlDef, ProviderEffect } from './contracts.js';
|
|
2
|
+
|
|
3
|
+
export type ProviderControlValue = string | number | boolean;
|
|
4
|
+
|
|
5
|
+
export function extractProviderControlValues(
|
|
6
|
+
controls: ProviderControlDef[] | undefined,
|
|
7
|
+
data: any,
|
|
8
|
+
): Record<string, ProviderControlValue> | undefined {
|
|
9
|
+
if (!data || typeof data !== 'object') return undefined;
|
|
10
|
+
|
|
11
|
+
const values: Record<string, ProviderControlValue> = {};
|
|
12
|
+
const explicit = data.controlValues;
|
|
13
|
+
if (explicit && typeof explicit === 'object') {
|
|
14
|
+
for (const [key, value] of Object.entries(explicit)) {
|
|
15
|
+
if (typeof value === 'string' || typeof value === 'number' || typeof value === 'boolean') {
|
|
16
|
+
values[key] = value;
|
|
17
|
+
}
|
|
18
|
+
}
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
for (const ctrl of controls || []) {
|
|
22
|
+
if (!ctrl.readFrom) continue;
|
|
23
|
+
const rawValue = data[ctrl.readFrom];
|
|
24
|
+
if (rawValue === undefined || rawValue === null) continue;
|
|
25
|
+
values[ctrl.id] = normalizeControlValue(rawValue);
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
if (data.model !== undefined && values.model === undefined) values.model = normalizeControlValue(data.model);
|
|
29
|
+
if (data.mode !== undefined && values.mode === undefined) values.mode = normalizeControlValue(data.mode);
|
|
30
|
+
|
|
31
|
+
return Object.keys(values).length > 0 ? values : undefined;
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
export function normalizeProviderEffects(data: any): ProviderEffect[] {
|
|
35
|
+
const rawEffects = Array.isArray(data?.effects) ? data.effects : [];
|
|
36
|
+
const effects: ProviderEffect[] = [];
|
|
37
|
+
|
|
38
|
+
for (const raw of rawEffects) {
|
|
39
|
+
if (!raw || typeof raw !== 'object') continue;
|
|
40
|
+
const type = raw.type;
|
|
41
|
+
if (type === 'message' && raw.message && typeof raw.message === 'object') {
|
|
42
|
+
const content = raw.message.content;
|
|
43
|
+
if (typeof content !== 'string' && !Array.isArray(content)) continue;
|
|
44
|
+
effects.push({
|
|
45
|
+
type: 'message',
|
|
46
|
+
id: typeof raw.id === 'string' ? raw.id : undefined,
|
|
47
|
+
when: raw.when === 'turn_completed' ? 'turn_completed' : 'immediate',
|
|
48
|
+
persist: raw.persist !== false,
|
|
49
|
+
message: {
|
|
50
|
+
role: raw.message.role === 'assistant' || raw.message.role === 'user' ? raw.message.role : 'system',
|
|
51
|
+
content,
|
|
52
|
+
kind: typeof raw.message.kind === 'string' ? raw.message.kind : undefined,
|
|
53
|
+
senderName: typeof raw.message.senderName === 'string' ? raw.message.senderName : undefined,
|
|
54
|
+
},
|
|
55
|
+
});
|
|
56
|
+
continue;
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
if (type === 'toast' && raw.toast && typeof raw.toast.message === 'string') {
|
|
60
|
+
effects.push({
|
|
61
|
+
type: 'toast',
|
|
62
|
+
id: typeof raw.id === 'string' ? raw.id : undefined,
|
|
63
|
+
when: raw.when === 'turn_completed' ? 'turn_completed' : 'immediate',
|
|
64
|
+
persist: raw.persist !== false,
|
|
65
|
+
toast: {
|
|
66
|
+
level: raw.toast.level === 'success' || raw.toast.level === 'warning' ? raw.toast.level : 'info',
|
|
67
|
+
message: raw.toast.message,
|
|
68
|
+
},
|
|
69
|
+
});
|
|
70
|
+
continue;
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
if (type === 'notification' && raw.notification && typeof raw.notification.body === 'string') {
|
|
74
|
+
effects.push({
|
|
75
|
+
type: 'notification',
|
|
76
|
+
id: typeof raw.id === 'string' ? raw.id : undefined,
|
|
77
|
+
when: raw.when === 'turn_completed' ? 'turn_completed' : 'immediate',
|
|
78
|
+
persist: raw.persist !== false,
|
|
79
|
+
notification: {
|
|
80
|
+
title: typeof raw.notification.title === 'string' ? raw.notification.title : undefined,
|
|
81
|
+
body: raw.notification.body,
|
|
82
|
+
level: raw.notification.level === 'success' || raw.notification.level === 'warning' ? raw.notification.level : 'info',
|
|
83
|
+
channels: Array.isArray(raw.notification.channels)
|
|
84
|
+
? raw.notification.channels.filter((channel: unknown) =>
|
|
85
|
+
channel === 'bubble' || channel === 'toast' || channel === 'browser')
|
|
86
|
+
: undefined,
|
|
87
|
+
preferenceKey: raw.notification.preferenceKey === 'disconnect'
|
|
88
|
+
|| raw.notification.preferenceKey === 'completion'
|
|
89
|
+
|| raw.notification.preferenceKey === 'approval'
|
|
90
|
+
|| raw.notification.preferenceKey === 'browser'
|
|
91
|
+
? raw.notification.preferenceKey
|
|
92
|
+
: undefined,
|
|
93
|
+
bubbleContent: typeof raw.notification.bubbleContent === 'string' || Array.isArray(raw.notification.bubbleContent)
|
|
94
|
+
? raw.notification.bubbleContent
|
|
95
|
+
: undefined,
|
|
96
|
+
},
|
|
97
|
+
});
|
|
98
|
+
}
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
return effects;
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
function normalizeControlValue(value: any): ProviderControlValue {
|
|
105
|
+
if (typeof value === 'string' || typeof value === 'number' || typeof value === 'boolean') {
|
|
106
|
+
return value;
|
|
107
|
+
}
|
|
108
|
+
if (value && typeof value === 'object') {
|
|
109
|
+
if (typeof value.label === 'string') return value.label;
|
|
110
|
+
if (typeof value.name === 'string') return value.name;
|
|
111
|
+
if (typeof value.id === 'string') return value.id;
|
|
112
|
+
}
|
|
113
|
+
return String(value);
|
|
114
|
+
}
|
|
@@ -8,6 +8,9 @@
|
|
|
8
8
|
import 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 { extractProviderControlValues, normalizeProviderEffects } from './control-effects.js';
|
|
12
|
+
import { ChatHistoryWriter } from '../config/chat-history.js';
|
|
13
|
+
import type { ChatMessage } from '../types.js';
|
|
11
14
|
|
|
12
15
|
export class ExtensionProviderInstance implements ProviderInstance {
|
|
13
16
|
readonly type: string;
|
|
@@ -26,9 +29,12 @@ export class ExtensionProviderInstance implements ProviderInstance {
|
|
|
26
29
|
private currentModel: string = '';
|
|
27
30
|
private currentMode: string = '';
|
|
28
31
|
private controlValues: Record<string, string | number | boolean> = {};
|
|
32
|
+
private appliedEffectKeys = new Set<string>();
|
|
33
|
+
private runtimeMessages: Array<{ key: string; message: ChatMessage }> = [];
|
|
29
34
|
private lastAgentStatus: string = 'idle';
|
|
30
35
|
private generatingStartedAt: number = 0;
|
|
31
36
|
private monitor: StatusMonitor;
|
|
37
|
+
private historyWriter: ChatHistoryWriter;
|
|
32
38
|
|
|
33
39
|
// meta
|
|
34
40
|
private instanceId: string;
|
|
@@ -43,6 +49,7 @@ export class ExtensionProviderInstance implements ProviderInstance {
|
|
|
43
49
|
this.provider = provider;
|
|
44
50
|
this.instanceId = crypto.randomUUID();
|
|
45
51
|
this.monitor = new StatusMonitor();
|
|
52
|
+
this.historyWriter = new ChatHistoryWriter();
|
|
46
53
|
}
|
|
47
54
|
|
|
48
55
|
// ─── Lifecycle ──────────────────────────────────
|
|
@@ -73,11 +80,11 @@ export class ExtensionProviderInstance implements ProviderInstance {
|
|
|
73
80
|
name: this.provider.name,
|
|
74
81
|
category: 'extension',
|
|
75
82
|
status: this.currentStatus as ProviderState['status'],
|
|
76
|
-
activeChat: this.messages.length > 0 ? {
|
|
83
|
+
activeChat: (this.messages.length > 0 || this.runtimeMessages.length > 0) ? {
|
|
77
84
|
id: this.chatId || this.instanceId,
|
|
78
85
|
title: this.chatTitle || this.agentName || this.provider.name,
|
|
79
86
|
status: this.currentStatus,
|
|
80
|
-
messages: this.messages,
|
|
87
|
+
messages: this.mergeConversationMessages(this.messages),
|
|
81
88
|
activeModal: this.activeModal,
|
|
82
89
|
inputContent: '',
|
|
83
90
|
} : null,
|
|
@@ -101,7 +108,8 @@ export class ExtensionProviderInstance implements ProviderInstance {
|
|
|
101
108
|
if (data?.activeModal !== undefined) this.activeModal = data.activeModal;
|
|
102
109
|
if (data?.model) this.currentModel = data.model;
|
|
103
110
|
if (data?.mode) this.currentMode = data.mode;
|
|
104
|
-
|
|
111
|
+
const controlValues = extractProviderControlValues(this.provider.controls, data) || data?.controlValues;
|
|
112
|
+
if (controlValues) this.controlValues = controlValues;
|
|
105
113
|
if (typeof data?.sessionId === 'string' && data.sessionId.trim()) this.chatId = data.sessionId;
|
|
106
114
|
if (typeof data?.title === 'string' && data.title.trim()) this.chatTitle = data.title;
|
|
107
115
|
if (typeof data?.agentName === 'string' && data.agentName.trim()) this.agentName = data.agentName;
|
|
@@ -115,6 +123,8 @@ export class ExtensionProviderInstance implements ProviderInstance {
|
|
|
115
123
|
this.resetStreamState();
|
|
116
124
|
} else if (event === 'extension_connected') {
|
|
117
125
|
this.ideType = data?.ideType || '';
|
|
126
|
+
} else if (event === 'provider_state_patch' && data && typeof data === 'object') {
|
|
127
|
+
this.applyProviderResponse(data, { phase: 'immediate' });
|
|
118
128
|
// Maintain instanceId UUID — do not overwrite
|
|
119
129
|
}
|
|
120
130
|
}
|
|
@@ -123,6 +133,17 @@ export class ExtensionProviderInstance implements ProviderInstance {
|
|
|
123
133
|
this.agentStreams = [];
|
|
124
134
|
this.messages = [];
|
|
125
135
|
this.monitor.reset();
|
|
136
|
+
this.appliedEffectKeys.clear();
|
|
137
|
+
this.runtimeMessages = [];
|
|
138
|
+
}
|
|
139
|
+
|
|
140
|
+
updateSettings(newSettings: Record<string, any>): void {
|
|
141
|
+
this.settings = { ...newSettings };
|
|
142
|
+
this.monitor.updateConfig({
|
|
143
|
+
approvalAlert: this.settings.approvalAlert !== false,
|
|
144
|
+
longGeneratingAlert: this.settings.longGeneratingAlert !== false,
|
|
145
|
+
longGeneratingThresholdSec: this.settings.longGeneratingThresholdSec || 180,
|
|
146
|
+
});
|
|
126
147
|
}
|
|
127
148
|
|
|
128
149
|
/** Query UUID instanceId */
|
|
@@ -143,6 +164,7 @@ export class ExtensionProviderInstance implements ProviderInstance {
|
|
|
143
164
|
? `${lastMsg?.role || ''}:${typeof lastMsg?.content === 'string' ? lastMsg.content : JSON.stringify(lastMsg?.content || '')}`.slice(-2000)
|
|
144
165
|
: undefined;
|
|
145
166
|
|
|
167
|
+
const previousStatus = this.lastAgentStatus;
|
|
146
168
|
if (agentStatus !== this.lastAgentStatus) {
|
|
147
169
|
if (this.lastAgentStatus === 'idle' && agentStatus === 'generating') {
|
|
148
170
|
this.generatingStartedAt = now;
|
|
@@ -185,6 +207,12 @@ export class ExtensionProviderInstance implements ProviderInstance {
|
|
|
185
207
|
this.lastAgentStatus = agentStatus;
|
|
186
208
|
}
|
|
187
209
|
|
|
210
|
+
this.applyProviderResponse(data, {
|
|
211
|
+
phase: (agentStatus === 'idle' && (previousStatus === 'generating' || previousStatus === 'waiting_approval'))
|
|
212
|
+
? 'turn_completed'
|
|
213
|
+
: 'immediate',
|
|
214
|
+
});
|
|
215
|
+
|
|
188
216
|
// Monitor check (cooldown based notification) — keep monitor events (long_generating etc)
|
|
189
217
|
const agentKey = `${this.type}:ext`;
|
|
190
218
|
const monitorEvents = this.monitor.check(agentKey, agentStatus, now, progressFingerprint);
|
|
@@ -198,6 +226,138 @@ export class ExtensionProviderInstance implements ProviderInstance {
|
|
|
198
226
|
if (this.events.length > 50) this.events = this.events.slice(-50);
|
|
199
227
|
}
|
|
200
228
|
|
|
229
|
+
private applyProviderResponse(data: any, options: { phase: 'immediate' | 'turn_completed' }): void {
|
|
230
|
+
if (!data || typeof data !== 'object') return;
|
|
231
|
+
|
|
232
|
+
const controlValues = extractProviderControlValues(this.provider.controls, data);
|
|
233
|
+
if (controlValues) this.controlValues = { ...this.controlValues, ...controlValues };
|
|
234
|
+
|
|
235
|
+
const effects = normalizeProviderEffects(data);
|
|
236
|
+
for (const effect of effects) {
|
|
237
|
+
const effectWhen = effect.when || 'immediate';
|
|
238
|
+
if (effectWhen === 'turn_completed' && options.phase !== 'turn_completed') continue;
|
|
239
|
+
if (effectWhen === 'immediate' && options.phase === 'turn_completed') continue;
|
|
240
|
+
|
|
241
|
+
const effectKey = this.getEffectDedupKey(effect);
|
|
242
|
+
if (this.appliedEffectKeys.has(effectKey)) continue;
|
|
243
|
+
this.appliedEffectKeys.add(effectKey);
|
|
244
|
+
|
|
245
|
+
if (effect.persist !== false) {
|
|
246
|
+
const persisted = this.getPersistedEffectContent(effect);
|
|
247
|
+
if (persisted) this.appendRuntimeSystemMessage(persisted, effectKey);
|
|
248
|
+
}
|
|
249
|
+
|
|
250
|
+
if (effect.type === 'message' && effect.message) {
|
|
251
|
+
this.pushEvent({
|
|
252
|
+
event: 'provider:message',
|
|
253
|
+
timestamp: Date.now(),
|
|
254
|
+
content: typeof effect.message.content === 'string' ? effect.message.content : JSON.stringify(effect.message.content),
|
|
255
|
+
role: effect.message.role || 'system',
|
|
256
|
+
kind: effect.message.kind,
|
|
257
|
+
senderName: effect.message.senderName,
|
|
258
|
+
});
|
|
259
|
+
} else if (effect.type === 'toast' && effect.toast) {
|
|
260
|
+
this.pushEvent({
|
|
261
|
+
event: 'provider:toast',
|
|
262
|
+
effectId: effect.id || effectKey,
|
|
263
|
+
timestamp: Date.now(),
|
|
264
|
+
message: effect.toast.message,
|
|
265
|
+
level: effect.toast.level || 'info',
|
|
266
|
+
});
|
|
267
|
+
} else if (effect.type === 'notification' && effect.notification) {
|
|
268
|
+
this.pushEvent({
|
|
269
|
+
event: 'provider:notification',
|
|
270
|
+
effectId: effect.id || effectKey,
|
|
271
|
+
timestamp: Date.now(),
|
|
272
|
+
title: effect.notification.title,
|
|
273
|
+
message: effect.notification.body,
|
|
274
|
+
content: typeof effect.notification.bubbleContent === 'string'
|
|
275
|
+
? effect.notification.bubbleContent
|
|
276
|
+
: effect.notification.body,
|
|
277
|
+
level: effect.notification.level || 'info',
|
|
278
|
+
channels: effect.notification.channels || ['toast'],
|
|
279
|
+
preferenceKey: effect.notification.preferenceKey,
|
|
280
|
+
});
|
|
281
|
+
}
|
|
282
|
+
}
|
|
283
|
+
}
|
|
284
|
+
|
|
285
|
+
private appendRuntimeSystemMessage(content: string, dedupKey: string, receivedAt = Date.now()): void {
|
|
286
|
+
const normalizedContent = String(content || '').trim();
|
|
287
|
+
if (!normalizedContent) return;
|
|
288
|
+
if (this.runtimeMessages.some((entry) => entry.key === dedupKey)) return;
|
|
289
|
+
|
|
290
|
+
this.runtimeMessages.push({
|
|
291
|
+
key: dedupKey,
|
|
292
|
+
message: {
|
|
293
|
+
role: 'system',
|
|
294
|
+
senderName: 'System',
|
|
295
|
+
content: normalizedContent,
|
|
296
|
+
receivedAt,
|
|
297
|
+
timestamp: receivedAt,
|
|
298
|
+
},
|
|
299
|
+
});
|
|
300
|
+
if (this.runtimeMessages.length > 50) this.runtimeMessages = this.runtimeMessages.slice(-50);
|
|
301
|
+
|
|
302
|
+
this.historyWriter.appendNewMessages(
|
|
303
|
+
this.type,
|
|
304
|
+
[{
|
|
305
|
+
role: 'system',
|
|
306
|
+
senderName: 'System',
|
|
307
|
+
content: normalizedContent,
|
|
308
|
+
kind: 'system',
|
|
309
|
+
receivedAt,
|
|
310
|
+
historyDedupKey: dedupKey,
|
|
311
|
+
}],
|
|
312
|
+
this.chatTitle || this.agentName || this.provider.name,
|
|
313
|
+
this.instanceId,
|
|
314
|
+
this.chatId || this.instanceId,
|
|
315
|
+
);
|
|
316
|
+
}
|
|
317
|
+
|
|
318
|
+
private mergeConversationMessages(messages: any[]): ChatMessage[] {
|
|
319
|
+
if (this.runtimeMessages.length === 0) return messages;
|
|
320
|
+
return [...messages, ...this.runtimeMessages.map((entry) => entry.message)]
|
|
321
|
+
.map((message, index) => ({ message, index }))
|
|
322
|
+
.sort((a, b) => {
|
|
323
|
+
const aTime = a.message.receivedAt || a.message.timestamp || 0;
|
|
324
|
+
const bTime = b.message.receivedAt || b.message.timestamp || 0;
|
|
325
|
+
if (aTime !== bTime) return aTime - bTime;
|
|
326
|
+
return a.index - b.index;
|
|
327
|
+
})
|
|
328
|
+
.map((entry) => entry.message);
|
|
329
|
+
}
|
|
330
|
+
|
|
331
|
+
private getPersistedEffectContent(effect: { type: string; message?: { content?: unknown }; toast?: { message?: string }; notification?: { title?: string; body?: string; bubbleContent?: unknown } }): string | null {
|
|
332
|
+
if (effect.type === 'message') {
|
|
333
|
+
return typeof effect.message?.content === 'string'
|
|
334
|
+
? effect.message.content
|
|
335
|
+
: JSON.stringify(effect.message?.content || '');
|
|
336
|
+
}
|
|
337
|
+
if (effect.type === 'toast') {
|
|
338
|
+
return effect.toast?.message || null;
|
|
339
|
+
}
|
|
340
|
+
if (effect.type === 'notification') {
|
|
341
|
+
if (typeof effect.notification?.bubbleContent === 'string') return effect.notification.bubbleContent;
|
|
342
|
+
if (typeof effect.notification?.title === 'string' && effect.notification.title.trim()) {
|
|
343
|
+
return `${effect.notification.title}\n${effect.notification.body || ''}`.trim();
|
|
344
|
+
}
|
|
345
|
+
return effect.notification?.body || null;
|
|
346
|
+
}
|
|
347
|
+
return null;
|
|
348
|
+
}
|
|
349
|
+
|
|
350
|
+
private getEffectDedupKey(effect: { id?: string; type: string; message?: { content?: unknown }; toast?: { message?: string }; notification?: { title?: string; body?: string } }): string {
|
|
351
|
+
if (effect.id) return `provider_effect:${effect.id}`;
|
|
352
|
+
if (effect.type === 'message') {
|
|
353
|
+
return `provider_effect:message:${typeof effect.message?.content === 'string' ? effect.message.content : JSON.stringify(effect.message?.content || '')}`;
|
|
354
|
+
}
|
|
355
|
+
if (effect.type === 'notification') {
|
|
356
|
+
return `provider_effect:notification:${effect.notification?.title || ''}:${effect.notification?.body || ''}`;
|
|
357
|
+
}
|
|
358
|
+
return `provider_effect:toast:${effect.toast?.message || ''}`;
|
|
359
|
+
}
|
|
360
|
+
|
|
201
361
|
private flushEvents(): ProviderEvent[] {
|
|
202
362
|
const events = [...this.events];
|
|
203
363
|
this.events = [];
|