@adhdev/daemon-core 0.8.63 → 0.8.65
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 +9 -0
- package/dist/index.d.ts +2 -1
- package/dist/index.js +251 -12
- package/dist/index.js.map +1 -1
- package/dist/index.mjs +244 -12
- package/dist/index.mjs.map +1 -1
- package/dist/providers/acp-provider-instance.d.ts +5 -0
- package/dist/providers/chat-message-normalization.d.ts +22 -0
- package/dist/status/chat-tail-hot-sessions.d.ts +15 -0
- package/dist/status/snapshot.d.ts +1 -0
- package/node_modules/@adhdev/session-host-core/package.json +2 -2
- package/package.json +2 -2
- package/src/agent-stream/provider-adapter.ts +2 -1
- package/src/agent-stream/types.ts +9 -0
- package/src/index.ts +9 -0
- package/src/providers/acp-provider-instance.ts +120 -4
- package/src/providers/chat-message-normalization.ts +109 -4
- package/src/providers/ide-provider-instance.ts +4 -2
- package/src/status/chat-tail-hot-sessions.ts +61 -0
- package/src/status/snapshot.ts +11 -5
|
@@ -39,6 +39,7 @@ export declare class AcpProviderInstance implements ProviderInstance {
|
|
|
39
39
|
private activeToolCalls;
|
|
40
40
|
private stopReason;
|
|
41
41
|
private partialContent;
|
|
42
|
+
private partialThoughtContent;
|
|
42
43
|
/** Rich content blocks accumulated during streaming */
|
|
43
44
|
private partialBlocks;
|
|
44
45
|
/** Tool calls collected during current turn */
|
|
@@ -94,6 +95,10 @@ export declare class AcpProviderInstance implements ProviderInstance {
|
|
|
94
95
|
private truncateContent;
|
|
95
96
|
/** Build ContentBlock[] from current partial state */
|
|
96
97
|
private buildPartialBlocks;
|
|
98
|
+
private buildPartialThoughtMessage;
|
|
99
|
+
private buildToolCallBubbleKind;
|
|
100
|
+
private summarizeToolCallBubbleContent;
|
|
101
|
+
private buildTurnToolCallMessages;
|
|
97
102
|
/** Finalize streaming content into an assistant message */
|
|
98
103
|
private finalizeAssistantMessage;
|
|
99
104
|
/** Convert ACP ToolCallContent[] to our ToolCallContent[] */
|
|
@@ -4,6 +4,7 @@ export type BuiltinChatMessageKind = typeof BUILTIN_CHAT_MESSAGE_KINDS[number];
|
|
|
4
4
|
export type ChatMessageKind = BuiltinChatMessageKind | (string & {});
|
|
5
5
|
export declare function isBuiltinChatMessageKind(kind: unknown): kind is BuiltinChatMessageKind;
|
|
6
6
|
export declare function normalizeChatMessageKind(kind: unknown, role: unknown): ChatMessageKind;
|
|
7
|
+
export declare function resolveChatMessageKind<T extends ChatMessage>(message: T): ChatMessageKind;
|
|
7
8
|
export declare function buildChatMessage<T extends Omit<ChatMessage, 'kind'> & {
|
|
8
9
|
kind?: ChatMessageKind;
|
|
9
10
|
}>(message: T): T & {
|
|
@@ -32,6 +33,27 @@ export declare function buildAssistantChatMessage<T extends Omit<ChatMessage, 'r
|
|
|
32
33
|
role: 'assistant';
|
|
33
34
|
kind: ChatMessageKind;
|
|
34
35
|
});
|
|
36
|
+
export declare function buildThoughtChatMessage<T extends Omit<ChatMessage, 'role' | 'kind'> & {
|
|
37
|
+
role?: 'assistant';
|
|
38
|
+
kind?: ChatMessageKind;
|
|
39
|
+
}>(message: T): (T & {
|
|
40
|
+
role: 'assistant';
|
|
41
|
+
kind: ChatMessageKind;
|
|
42
|
+
});
|
|
43
|
+
export declare function buildToolChatMessage<T extends Omit<ChatMessage, 'role' | 'kind'> & {
|
|
44
|
+
role?: 'assistant';
|
|
45
|
+
kind?: ChatMessageKind;
|
|
46
|
+
}>(message: T): (T & {
|
|
47
|
+
role: 'assistant';
|
|
48
|
+
kind: ChatMessageKind;
|
|
49
|
+
});
|
|
50
|
+
export declare function buildTerminalChatMessage<T extends Omit<ChatMessage, 'role' | 'kind'> & {
|
|
51
|
+
role?: 'assistant';
|
|
52
|
+
kind?: ChatMessageKind;
|
|
53
|
+
}>(message: T): (T & {
|
|
54
|
+
role: 'assistant';
|
|
55
|
+
kind: ChatMessageKind;
|
|
56
|
+
});
|
|
35
57
|
export declare function buildUserChatMessage<T extends Omit<ChatMessage, 'role' | 'kind'> & {
|
|
36
58
|
role?: 'user';
|
|
37
59
|
kind?: ChatMessageKind;
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
export declare const DEFAULT_ACTIVE_CHAT_POLL_STATUSES: Set<string>;
|
|
2
|
+
export declare const DEFAULT_CHAT_TAIL_RECENT_MESSAGE_GRACE_MS = 8000;
|
|
3
|
+
export interface HotChatSessionLike {
|
|
4
|
+
id?: string | null;
|
|
5
|
+
status?: unknown;
|
|
6
|
+
lastMessageAt?: unknown;
|
|
7
|
+
}
|
|
8
|
+
export declare function classifyHotChatSessionsForSubscriptionFlush(sessions: HotChatSessionLike[], previousHotSessionIds: ReadonlySet<string>, options?: {
|
|
9
|
+
now?: number;
|
|
10
|
+
recentMessageGraceMs?: number;
|
|
11
|
+
activeStatuses?: ReadonlySet<string>;
|
|
12
|
+
}): {
|
|
13
|
+
active: Set<string>;
|
|
14
|
+
finalizing: Set<string>;
|
|
15
|
+
};
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@adhdev/session-host-core",
|
|
3
|
-
"version": "0.8.
|
|
4
|
-
"description": "ADHDev local session host core
|
|
3
|
+
"version": "0.8.65",
|
|
4
|
+
"description": "ADHDev local session host core — session registry, protocol, buffers",
|
|
5
5
|
"main": "dist/index.js",
|
|
6
6
|
"types": "dist/index.d.ts",
|
|
7
7
|
"exports": {
|
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@adhdev/daemon-core",
|
|
3
|
-
"version": "0.8.
|
|
4
|
-
"description": "ADHDev daemon core
|
|
3
|
+
"version": "0.8.65",
|
|
4
|
+
"description": "ADHDev daemon core — CDP, IDE detection, providers, command execution",
|
|
5
5
|
"main": "dist/index.js",
|
|
6
6
|
"types": "dist/index.d.ts",
|
|
7
7
|
"exports": {
|
|
@@ -14,6 +14,7 @@ import type {
|
|
|
14
14
|
import type { ProviderModule, ProviderScripts } from '../providers/contracts.js';
|
|
15
15
|
import { extractProviderControlValues, normalizeProviderEffects } from '../providers/control-effects.js';
|
|
16
16
|
import { resolveProviderStateSurface } from '../providers/provider-patch-state.js';
|
|
17
|
+
import { normalizeChatMessages } from '../providers/chat-message-normalization.js';
|
|
17
18
|
|
|
18
19
|
export class ProviderStreamAdapter implements IAgentStreamAdapter {
|
|
19
20
|
readonly agentType: string;
|
|
@@ -153,7 +154,7 @@ export class ProviderStreamAdapter implements IAgentStreamAdapter {
|
|
|
153
154
|
agentName: this.agentName,
|
|
154
155
|
extensionId: this.extensionId,
|
|
155
156
|
status: data.status || 'idle',
|
|
156
|
-
messages: data.messages
|
|
157
|
+
messages: normalizeChatMessages(Array.isArray(data.messages) ? data.messages : []) as any,
|
|
157
158
|
inputContent: data.inputContent || '',
|
|
158
159
|
activeModal: data.activeModal,
|
|
159
160
|
};
|
|
@@ -7,12 +7,21 @@
|
|
|
7
7
|
|
|
8
8
|
import type { ProviderEffect } from '../providers/contracts.js';
|
|
9
9
|
import type { ProviderSummaryMetadata } from '../shared-types.js';
|
|
10
|
+
import type { ChatMessageKind } from '../providers/chat-message-normalization.js';
|
|
10
11
|
|
|
11
12
|
/** Agent chat message */
|
|
12
13
|
export interface AgentChatMessage {
|
|
13
14
|
role: 'user' | 'assistant' | 'system';
|
|
14
15
|
content: string;
|
|
16
|
+
kind?: ChatMessageKind;
|
|
15
17
|
timestamp?: number;
|
|
18
|
+
receivedAt?: number;
|
|
19
|
+
id?: string;
|
|
20
|
+
index?: number;
|
|
21
|
+
meta?: Record<string, unknown>;
|
|
22
|
+
senderName?: string;
|
|
23
|
+
_type?: string;
|
|
24
|
+
_sub?: string;
|
|
16
25
|
}
|
|
17
26
|
|
|
18
27
|
/** Agent chat history item */
|
package/src/index.ts
CHANGED
|
@@ -115,6 +115,11 @@ export type { IDEInfo } from './detection/ide-detector.js';
|
|
|
115
115
|
export { detectCLIs } from './detection/cli-detector.js';
|
|
116
116
|
export { getHostMemorySnapshot } from './system/host-memory.js';
|
|
117
117
|
export type { HostMemorySnapshot } from './system/host-memory.js';
|
|
118
|
+
export {
|
|
119
|
+
classifyHotChatSessionsForSubscriptionFlush,
|
|
120
|
+
DEFAULT_ACTIVE_CHAT_POLL_STATUSES,
|
|
121
|
+
DEFAULT_CHAT_TAIL_RECENT_MESSAGE_GRACE_MS,
|
|
122
|
+
} from './status/chat-tail-hot-sessions.js';
|
|
118
123
|
|
|
119
124
|
// ── CDP ──
|
|
120
125
|
export { DaemonCdpManager } from './cdp/manager.js';
|
|
@@ -204,10 +209,14 @@ export {
|
|
|
204
209
|
BUILTIN_CHAT_MESSAGE_KINDS,
|
|
205
210
|
isBuiltinChatMessageKind,
|
|
206
211
|
normalizeChatMessageKind,
|
|
212
|
+
resolveChatMessageKind,
|
|
207
213
|
buildChatMessage,
|
|
208
214
|
buildSystemChatMessage,
|
|
209
215
|
buildRuntimeSystemChatMessage,
|
|
210
216
|
buildAssistantChatMessage,
|
|
217
|
+
buildThoughtChatMessage,
|
|
218
|
+
buildToolChatMessage,
|
|
219
|
+
buildTerminalChatMessage,
|
|
211
220
|
buildUserChatMessage,
|
|
212
221
|
normalizeChatMessage,
|
|
213
222
|
normalizeChatMessages,
|
|
@@ -51,7 +51,16 @@ import { normalizeContent, flattenContent, normalizeInputEnvelope } from './cont
|
|
|
51
51
|
import type { ProviderInstance, ProviderState, AcpProviderState, ProviderErrorReason, ProviderEvent, InstanceContext } from './provider-instance.js';
|
|
52
52
|
import { StatusMonitor } from './status-monitor.js';
|
|
53
53
|
import { buildLegacyModelModeSummaryMetadata } from './summary-metadata.js';
|
|
54
|
-
import {
|
|
54
|
+
import {
|
|
55
|
+
buildAssistantChatMessage,
|
|
56
|
+
buildChatMessage,
|
|
57
|
+
buildRuntimeSystemChatMessage,
|
|
58
|
+
buildTerminalChatMessage,
|
|
59
|
+
buildThoughtChatMessage,
|
|
60
|
+
buildToolChatMessage,
|
|
61
|
+
buildUserChatMessage,
|
|
62
|
+
normalizeChatMessages,
|
|
63
|
+
} from './chat-message-normalization.js';
|
|
55
64
|
import { LOG } from '../logging/logger.js';
|
|
56
65
|
import type { ChatMessage } from '../types.js';
|
|
57
66
|
|
|
@@ -231,6 +240,7 @@ export class AcpProviderInstance implements ProviderInstance {
|
|
|
231
240
|
private activeToolCalls: AcpToolCall[] = [];
|
|
232
241
|
private stopReason: string | null = null;
|
|
233
242
|
private partialContent = '';
|
|
243
|
+
private partialThoughtContent = '';
|
|
234
244
|
/** Rich content blocks accumulated during streaming */
|
|
235
245
|
private partialBlocks: ContentBlock[] = [];
|
|
236
246
|
/** Tool calls collected during current turn */
|
|
@@ -300,7 +310,12 @@ export class AcpProviderInstance implements ProviderInstance {
|
|
|
300
310
|
...m,
|
|
301
311
|
content,
|
|
302
312
|
});
|
|
303
|
-
}));
|
|
313
|
+
})) as ChatMessage[];
|
|
314
|
+
|
|
315
|
+
if (this.currentStatus === 'generating') {
|
|
316
|
+
const partialThoughtMessage = this.buildPartialThoughtMessage(Date.now());
|
|
317
|
+
if (partialThoughtMessage) recentMessages.push(partialThoughtMessage as ChatMessage);
|
|
318
|
+
}
|
|
304
319
|
|
|
305
320
|
// generating during partial response add
|
|
306
321
|
if (this.currentStatus === 'generating' && (this.partialContent || this.partialBlocks.length > 0)) {
|
|
@@ -980,6 +995,7 @@ export class AcpProviderInstance implements ProviderInstance {
|
|
|
980
995
|
|
|
981
996
|
this.currentStatus = 'generating';
|
|
982
997
|
this.partialContent = '';
|
|
998
|
+
this.partialThoughtContent = '';
|
|
983
999
|
this.partialBlocks = [];
|
|
984
1000
|
this.turnToolCalls = [];
|
|
985
1001
|
this.detectStatusTransition();
|
|
@@ -1076,9 +1092,15 @@ export class AcpProviderInstance implements ProviderInstance {
|
|
|
1076
1092
|
this.currentStatus = 'generating';
|
|
1077
1093
|
break;
|
|
1078
1094
|
}
|
|
1079
|
-
case 'agent_thought_chunk':
|
|
1095
|
+
case 'agent_thought_chunk': {
|
|
1096
|
+
const content = update.content;
|
|
1097
|
+
if (content?.type === 'text' && typeof content.text === 'string') {
|
|
1098
|
+
this.partialThoughtContent += content.text;
|
|
1099
|
+
}
|
|
1100
|
+
this.currentStatus = 'generating';
|
|
1101
|
+
break;
|
|
1102
|
+
}
|
|
1080
1103
|
case 'user_message_chunk': {
|
|
1081
|
-
// Track but don't display thought chunks as main content
|
|
1082
1104
|
break;
|
|
1083
1105
|
}
|
|
1084
1106
|
case 'tool_call': {
|
|
@@ -1256,8 +1278,101 @@ export class AcpProviderInstance implements ProviderInstance {
|
|
|
1256
1278
|
return blocks;
|
|
1257
1279
|
}
|
|
1258
1280
|
|
|
1281
|
+
private buildPartialThoughtMessage(timestamp = Date.now()): AcpMessage | null {
|
|
1282
|
+
const content = this.partialThoughtContent.trim();
|
|
1283
|
+
if (!content) return null;
|
|
1284
|
+
return buildThoughtChatMessage({
|
|
1285
|
+
content,
|
|
1286
|
+
timestamp,
|
|
1287
|
+
meta: {
|
|
1288
|
+
label: 'Thought',
|
|
1289
|
+
isRunning: this.currentStatus === 'generating',
|
|
1290
|
+
},
|
|
1291
|
+
});
|
|
1292
|
+
}
|
|
1293
|
+
|
|
1294
|
+
private buildToolCallBubbleKind(toolCall: ToolCallInfo): 'thought' | 'tool' | 'terminal' {
|
|
1295
|
+
if (toolCall.kind === 'think') return 'thought';
|
|
1296
|
+
if (toolCall.kind === 'execute') return 'terminal';
|
|
1297
|
+
if (Array.isArray(toolCall.content) && toolCall.content.some((entry) => entry?.type === 'terminal')) return 'terminal';
|
|
1298
|
+
return 'tool';
|
|
1299
|
+
}
|
|
1300
|
+
|
|
1301
|
+
private summarizeToolCallBubbleContent(toolCall: ToolCallInfo): string {
|
|
1302
|
+
const rawOutput = typeof toolCall.rawOutput === 'string'
|
|
1303
|
+
? toolCall.rawOutput.trim()
|
|
1304
|
+
: (toolCall.rawOutput != null ? JSON.stringify(toolCall.rawOutput) : '');
|
|
1305
|
+
if (rawOutput) return rawOutput;
|
|
1306
|
+
|
|
1307
|
+
const contentText = Array.isArray(toolCall.content)
|
|
1308
|
+
? toolCall.content
|
|
1309
|
+
.map((entry) => {
|
|
1310
|
+
if (!entry || typeof entry !== 'object') return '';
|
|
1311
|
+
if (entry.type === 'content') return flattenContent([entry.content]).trim();
|
|
1312
|
+
if (entry.type === 'diff') return `${entry.path}\n${entry.newText || ''}`.trim();
|
|
1313
|
+
if (entry.type === 'terminal') return `Terminal: ${entry.terminalId || ''}`.trim();
|
|
1314
|
+
return '';
|
|
1315
|
+
})
|
|
1316
|
+
.filter(Boolean)
|
|
1317
|
+
.join('\n\n')
|
|
1318
|
+
.trim()
|
|
1319
|
+
: '';
|
|
1320
|
+
if (contentText) return contentText;
|
|
1321
|
+
|
|
1322
|
+
const rawInput = typeof toolCall.rawInput === 'string'
|
|
1323
|
+
? toolCall.rawInput.trim()
|
|
1324
|
+
: (toolCall.rawInput != null ? JSON.stringify(toolCall.rawInput) : '');
|
|
1325
|
+
if (rawInput) {
|
|
1326
|
+
return toolCall.title ? `${toolCall.title}\n${rawInput}` : rawInput;
|
|
1327
|
+
}
|
|
1328
|
+
|
|
1329
|
+
return toolCall.title || '';
|
|
1330
|
+
}
|
|
1331
|
+
|
|
1332
|
+
private buildTurnToolCallMessages(timestamp = Date.now()): AcpMessage[] {
|
|
1333
|
+
return this.turnToolCalls
|
|
1334
|
+
.map((toolCall) => {
|
|
1335
|
+
const content = this.summarizeToolCallBubbleContent(toolCall);
|
|
1336
|
+
if (!content) return null;
|
|
1337
|
+
const isRunning = toolCall.status === 'pending' || toolCall.status === 'in_progress';
|
|
1338
|
+
const label = toolCall.title || undefined;
|
|
1339
|
+
const kind = this.buildToolCallBubbleKind(toolCall);
|
|
1340
|
+
if (kind === 'thought') {
|
|
1341
|
+
return buildThoughtChatMessage({
|
|
1342
|
+
content,
|
|
1343
|
+
timestamp,
|
|
1344
|
+
meta: { label: label || 'Thought', isRunning },
|
|
1345
|
+
});
|
|
1346
|
+
}
|
|
1347
|
+
if (kind === 'terminal') {
|
|
1348
|
+
return buildTerminalChatMessage({
|
|
1349
|
+
content,
|
|
1350
|
+
timestamp,
|
|
1351
|
+
meta: { label: label || 'Ran command', isRunning },
|
|
1352
|
+
});
|
|
1353
|
+
}
|
|
1354
|
+
return buildToolChatMessage({
|
|
1355
|
+
content,
|
|
1356
|
+
timestamp,
|
|
1357
|
+
meta: { label: label || 'Tool call', isRunning },
|
|
1358
|
+
});
|
|
1359
|
+
})
|
|
1360
|
+
.filter(Boolean) as AcpMessage[];
|
|
1361
|
+
}
|
|
1362
|
+
|
|
1259
1363
|
/** Finalize streaming content into an assistant message */
|
|
1260
1364
|
private finalizeAssistantMessage(): void {
|
|
1365
|
+
const timestamp = Date.now();
|
|
1366
|
+
const thoughtMessage = this.buildPartialThoughtMessage(timestamp);
|
|
1367
|
+
if (thoughtMessage) {
|
|
1368
|
+
this.messages.push(thoughtMessage);
|
|
1369
|
+
}
|
|
1370
|
+
|
|
1371
|
+
const toolCallMessages = this.buildTurnToolCallMessages(timestamp);
|
|
1372
|
+
if (toolCallMessages.length > 0) {
|
|
1373
|
+
this.messages.push(...toolCallMessages);
|
|
1374
|
+
}
|
|
1375
|
+
|
|
1261
1376
|
const blocks = this.buildPartialBlocks();
|
|
1262
1377
|
// Remove trailing '...' from text blocks for final message
|
|
1263
1378
|
const finalBlocks = blocks.map(b => {
|
|
@@ -1277,6 +1392,7 @@ export class AcpProviderInstance implements ProviderInstance {
|
|
|
1277
1392
|
}));
|
|
1278
1393
|
}
|
|
1279
1394
|
this.partialContent = '';
|
|
1395
|
+
this.partialThoughtContent = '';
|
|
1280
1396
|
this.partialBlocks = [];
|
|
1281
1397
|
this.turnToolCalls = [];
|
|
1282
1398
|
}
|
|
@@ -6,23 +6,107 @@ export type BuiltinChatMessageKind = typeof BUILTIN_CHAT_MESSAGE_KINDS[number];
|
|
|
6
6
|
export type ChatMessageKind = BuiltinChatMessageKind | (string & {});
|
|
7
7
|
|
|
8
8
|
const KNOWN_CHAT_MESSAGE_KINDS = new Set<string>(BUILTIN_CHAT_MESSAGE_KINDS);
|
|
9
|
+
const CHAT_MESSAGE_KIND_ALIASES: Record<string, BuiltinChatMessageKind> = {
|
|
10
|
+
text: 'standard',
|
|
11
|
+
message: 'standard',
|
|
12
|
+
assistant: 'standard',
|
|
13
|
+
thinking: 'thought',
|
|
14
|
+
think: 'thought',
|
|
15
|
+
reasoning: 'thought',
|
|
16
|
+
reason: 'thought',
|
|
17
|
+
toolcall: 'tool',
|
|
18
|
+
tool_call: 'tool',
|
|
19
|
+
tooluse: 'tool',
|
|
20
|
+
tool_use: 'tool',
|
|
21
|
+
action: 'tool',
|
|
22
|
+
command: 'terminal',
|
|
23
|
+
cmd: 'terminal',
|
|
24
|
+
shell: 'terminal',
|
|
25
|
+
console: 'terminal',
|
|
26
|
+
};
|
|
27
|
+
|
|
28
|
+
function canonicalizeKindHint(value: string): string {
|
|
29
|
+
return value.trim().toLowerCase().replace(/[\s-]+/g, '_');
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
function resolveBuiltinOrAliasKind(kind: unknown): BuiltinChatMessageKind | null {
|
|
33
|
+
if (typeof kind !== 'string') return null;
|
|
34
|
+
const normalizedKind = canonicalizeKindHint(kind);
|
|
35
|
+
if (!normalizedKind) return null;
|
|
36
|
+
if (KNOWN_CHAT_MESSAGE_KINDS.has(normalizedKind)) return normalizedKind as BuiltinChatMessageKind;
|
|
37
|
+
return CHAT_MESSAGE_KIND_ALIASES[normalizedKind] || null;
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
function inferHintKind(value: unknown): BuiltinChatMessageKind | null {
|
|
41
|
+
const direct = resolveBuiltinOrAliasKind(value);
|
|
42
|
+
if (direct) return direct;
|
|
43
|
+
if (typeof value !== 'string') return null;
|
|
44
|
+
const normalized = canonicalizeKindHint(value);
|
|
45
|
+
if (!normalized) return null;
|
|
46
|
+
if (/thought|thinking|reasoning/.test(normalized)) return 'thought';
|
|
47
|
+
if (/tool/.test(normalized)) return 'tool';
|
|
48
|
+
if (/terminal|command|shell|console/.test(normalized)) return 'terminal';
|
|
49
|
+
return null;
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
function inferKindFromToolCalls(message: ChatMessage): BuiltinChatMessageKind | null {
|
|
53
|
+
const toolCalls = Array.isArray(message?.toolCalls) ? message.toolCalls : [];
|
|
54
|
+
if (toolCalls.length === 0) return null;
|
|
55
|
+
if (toolCalls.some((toolCall) => toolCall?.kind === 'think')) return 'thought';
|
|
56
|
+
if (toolCalls.some((toolCall) => toolCall?.kind === 'execute')) return 'terminal';
|
|
57
|
+
if (toolCalls.some((toolCall) => Array.isArray(toolCall?.content) && toolCall.content.some((entry) => entry?.type === 'terminal'))) {
|
|
58
|
+
return 'terminal';
|
|
59
|
+
}
|
|
60
|
+
return 'tool';
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
function inferMissingChatMessageKind(message: ChatMessage): BuiltinChatMessageKind | null {
|
|
64
|
+
const role = typeof message?.role === 'string' ? message.role.trim().toLowerCase() : '';
|
|
65
|
+
if (role === 'system') return 'system';
|
|
66
|
+
|
|
67
|
+
const meta = message?.meta && typeof message.meta === 'object' ? message.meta as Record<string, unknown> : undefined;
|
|
68
|
+
const hintCandidates: unknown[] = [
|
|
69
|
+
message?._sub,
|
|
70
|
+
message?._type,
|
|
71
|
+
meta?.label,
|
|
72
|
+
typeof message?.senderName === 'string' ? message.senderName : undefined,
|
|
73
|
+
];
|
|
74
|
+
|
|
75
|
+
for (const candidate of hintCandidates) {
|
|
76
|
+
const inferred = inferHintKind(candidate);
|
|
77
|
+
if (inferred) return inferred;
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
const inferredFromToolCalls = inferKindFromToolCalls(message);
|
|
81
|
+
if (inferredFromToolCalls) return inferredFromToolCalls;
|
|
82
|
+
return null;
|
|
83
|
+
}
|
|
9
84
|
|
|
10
85
|
export function isBuiltinChatMessageKind(kind: unknown): kind is BuiltinChatMessageKind {
|
|
11
|
-
return
|
|
86
|
+
return resolveBuiltinOrAliasKind(kind) !== null;
|
|
12
87
|
}
|
|
13
88
|
|
|
14
89
|
export function normalizeChatMessageKind(kind: unknown, role: unknown): ChatMessageKind {
|
|
15
|
-
const
|
|
16
|
-
if (
|
|
90
|
+
const resolvedKind = resolveBuiltinOrAliasKind(kind);
|
|
91
|
+
if (resolvedKind) return resolvedKind;
|
|
17
92
|
|
|
18
93
|
const normalizedRole = typeof role === 'string' ? role.trim().toLowerCase() : '';
|
|
19
94
|
return normalizedRole === 'system' ? 'system' : 'standard';
|
|
20
95
|
}
|
|
21
96
|
|
|
97
|
+
export function resolveChatMessageKind<T extends ChatMessage>(message: T): ChatMessageKind {
|
|
98
|
+
const explicitKind = resolveBuiltinOrAliasKind(message?.kind);
|
|
99
|
+
if (explicitKind) return explicitKind;
|
|
100
|
+
|
|
101
|
+
const inferredKind = inferMissingChatMessageKind(message);
|
|
102
|
+
if (inferredKind) return inferredKind;
|
|
103
|
+
return normalizeChatMessageKind(message?.kind, message?.role);
|
|
104
|
+
}
|
|
105
|
+
|
|
22
106
|
export function buildChatMessage<T extends Omit<ChatMessage, 'kind'> & { kind?: ChatMessageKind }>(message: T): T & { kind: ChatMessageKind } {
|
|
23
107
|
return {
|
|
24
108
|
...message,
|
|
25
|
-
kind:
|
|
109
|
+
kind: resolveChatMessageKind(message as unknown as ChatMessage),
|
|
26
110
|
};
|
|
27
111
|
}
|
|
28
112
|
|
|
@@ -51,6 +135,27 @@ export function buildAssistantChatMessage<T extends Omit<ChatMessage, 'role' | '
|
|
|
51
135
|
} as T & { role: 'assistant'; kind?: ChatMessageKind }) as T & { role: 'assistant'; kind: ChatMessageKind };
|
|
52
136
|
}
|
|
53
137
|
|
|
138
|
+
export function buildThoughtChatMessage<T extends Omit<ChatMessage, 'role' | 'kind'> & { role?: 'assistant'; kind?: ChatMessageKind }>(message: T): (T & { role: 'assistant'; kind: ChatMessageKind }) {
|
|
139
|
+
return buildAssistantChatMessage({
|
|
140
|
+
...message,
|
|
141
|
+
kind: message?.kind || 'thought',
|
|
142
|
+
} as T & { role?: 'assistant'; kind?: ChatMessageKind }) as T & { role: 'assistant'; kind: ChatMessageKind };
|
|
143
|
+
}
|
|
144
|
+
|
|
145
|
+
export function buildToolChatMessage<T extends Omit<ChatMessage, 'role' | 'kind'> & { role?: 'assistant'; kind?: ChatMessageKind }>(message: T): (T & { role: 'assistant'; kind: ChatMessageKind }) {
|
|
146
|
+
return buildAssistantChatMessage({
|
|
147
|
+
...message,
|
|
148
|
+
kind: message?.kind || 'tool',
|
|
149
|
+
} as T & { role?: 'assistant'; kind?: ChatMessageKind }) as T & { role: 'assistant'; kind: ChatMessageKind };
|
|
150
|
+
}
|
|
151
|
+
|
|
152
|
+
export function buildTerminalChatMessage<T extends Omit<ChatMessage, 'role' | 'kind'> & { role?: 'assistant'; kind?: ChatMessageKind }>(message: T): (T & { role: 'assistant'; kind: ChatMessageKind }) {
|
|
153
|
+
return buildAssistantChatMessage({
|
|
154
|
+
...message,
|
|
155
|
+
kind: message?.kind || 'terminal',
|
|
156
|
+
} as T & { role?: 'assistant'; kind?: ChatMessageKind }) as T & { role: 'assistant'; kind: ChatMessageKind };
|
|
157
|
+
}
|
|
158
|
+
|
|
54
159
|
export function buildUserChatMessage<T extends Omit<ChatMessage, 'role' | 'kind'> & { role?: 'user'; kind?: ChatMessageKind }>(message: T): (T & { role: 'user'; kind: ChatMessageKind }) {
|
|
55
160
|
return buildChatMessage({
|
|
56
161
|
...message,
|
|
@@ -336,11 +336,13 @@ export class IdeProviderInstance implements ProviderInstance {
|
|
|
336
336
|
if (pm.receivedAt) prevByHash.set(h, pm.receivedAt);
|
|
337
337
|
}
|
|
338
338
|
const now = Date.now();
|
|
339
|
-
const
|
|
340
|
-
for (const msg of
|
|
339
|
+
const rawMessages = chat.messages || [];
|
|
340
|
+
for (const msg of rawMessages) {
|
|
341
341
|
const h = `${msg.role}:${(msg.content || '').slice(0, 100)}`;
|
|
342
342
|
msg.receivedAt = prevByHash.get(h) || now;
|
|
343
343
|
}
|
|
344
|
+
chat.messages = normalizeChatMessages(rawMessages as ChatMessage[]) as any;
|
|
345
|
+
const messages = chat.messages || [];
|
|
344
346
|
|
|
345
347
|
// Filter messages by provider settings (showThinking, showToolCalls, showTerminal)
|
|
346
348
|
if (messages.length > 0) {
|
|
@@ -0,0 +1,61 @@
|
|
|
1
|
+
export const DEFAULT_ACTIVE_CHAT_POLL_STATUSES = new Set([
|
|
2
|
+
'generating',
|
|
3
|
+
'waiting_approval',
|
|
4
|
+
'starting',
|
|
5
|
+
]);
|
|
6
|
+
|
|
7
|
+
export const DEFAULT_CHAT_TAIL_RECENT_MESSAGE_GRACE_MS = 8_000;
|
|
8
|
+
|
|
9
|
+
export interface HotChatSessionLike {
|
|
10
|
+
id?: string | null;
|
|
11
|
+
status?: unknown;
|
|
12
|
+
lastMessageAt?: unknown;
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
function parseMessageTimestamp(value: unknown): number {
|
|
16
|
+
if (typeof value === 'number' && Number.isFinite(value)) return value;
|
|
17
|
+
if (typeof value === 'string') {
|
|
18
|
+
const parsed = Date.parse(value);
|
|
19
|
+
if (Number.isFinite(parsed)) return parsed;
|
|
20
|
+
}
|
|
21
|
+
return 0;
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
export function classifyHotChatSessionsForSubscriptionFlush(
|
|
25
|
+
sessions: HotChatSessionLike[],
|
|
26
|
+
previousHotSessionIds: ReadonlySet<string>,
|
|
27
|
+
options: {
|
|
28
|
+
now?: number;
|
|
29
|
+
recentMessageGraceMs?: number;
|
|
30
|
+
activeStatuses?: ReadonlySet<string>;
|
|
31
|
+
} = {},
|
|
32
|
+
): { active: Set<string>; finalizing: Set<string> } {
|
|
33
|
+
const now = options.now ?? Date.now();
|
|
34
|
+
const recentMessageGraceMs = Math.max(
|
|
35
|
+
0,
|
|
36
|
+
Number.isFinite(options.recentMessageGraceMs)
|
|
37
|
+
? Number(options.recentMessageGraceMs)
|
|
38
|
+
: DEFAULT_CHAT_TAIL_RECENT_MESSAGE_GRACE_MS,
|
|
39
|
+
);
|
|
40
|
+
const activeStatuses = options.activeStatuses ?? DEFAULT_ACTIVE_CHAT_POLL_STATUSES;
|
|
41
|
+
const active = new Set<string>();
|
|
42
|
+
|
|
43
|
+
for (const session of sessions) {
|
|
44
|
+
const sessionId = typeof session?.id === 'string' ? session.id : '';
|
|
45
|
+
if (!sessionId) continue;
|
|
46
|
+
|
|
47
|
+
const status = String(session?.status || '').toLowerCase();
|
|
48
|
+
const lastMessageAt = parseMessageTimestamp(session?.lastMessageAt);
|
|
49
|
+
const recentlyUpdated = lastMessageAt > 0 && (now - lastMessageAt) <= recentMessageGraceMs;
|
|
50
|
+
|
|
51
|
+
if (activeStatuses.has(status) || recentlyUpdated) {
|
|
52
|
+
active.add(sessionId);
|
|
53
|
+
}
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
const finalizing = new Set(
|
|
57
|
+
Array.from(previousHotSessionIds).filter((sessionId) => !active.has(sessionId)),
|
|
58
|
+
);
|
|
59
|
+
|
|
60
|
+
return { active, finalizing };
|
|
61
|
+
}
|
package/src/status/snapshot.ts
CHANGED
|
@@ -189,6 +189,10 @@ function parseMessageTime(value: unknown): number {
|
|
|
189
189
|
return 0;
|
|
190
190
|
}
|
|
191
191
|
|
|
192
|
+
function getMessageEventTime(message: { receivedAt?: unknown; timestamp?: unknown } | null | undefined): number {
|
|
193
|
+
return parseMessageTime(message?.receivedAt) || parseMessageTime(message?.timestamp) || 0;
|
|
194
|
+
}
|
|
195
|
+
|
|
192
196
|
function stringifyPreviewContent(content: unknown): string {
|
|
193
197
|
if (typeof content === 'string') return content;
|
|
194
198
|
if (Array.isArray(content)) {
|
|
@@ -233,6 +237,7 @@ function getLastDisplayMessage(session: {
|
|
|
233
237
|
role?: string;
|
|
234
238
|
content?: unknown;
|
|
235
239
|
receivedAt?: number | string;
|
|
240
|
+
timestamp?: number | string;
|
|
236
241
|
}> | null
|
|
237
242
|
} | null
|
|
238
243
|
}) {
|
|
@@ -247,7 +252,7 @@ function getLastDisplayMessage(session: {
|
|
|
247
252
|
return {
|
|
248
253
|
role,
|
|
249
254
|
preview,
|
|
250
|
-
receivedAt:
|
|
255
|
+
receivedAt: getMessageEventTime(candidate),
|
|
251
256
|
hash: simplePreviewHash(`${role}:${preview}`),
|
|
252
257
|
};
|
|
253
258
|
}
|
|
@@ -256,12 +261,12 @@ function getLastDisplayMessage(session: {
|
|
|
256
261
|
|
|
257
262
|
function getSessionMessageUpdatedAt(session: {
|
|
258
263
|
activeChat?: {
|
|
259
|
-
messages?: Array<{ receivedAt?: number | string }> | null
|
|
264
|
+
messages?: Array<{ receivedAt?: number | string; timestamp?: number | string }> | null
|
|
260
265
|
} | null
|
|
261
266
|
}) {
|
|
262
267
|
const lastMessage = session.activeChat?.messages?.at?.(-1);
|
|
263
268
|
if (!lastMessage) return 0;
|
|
264
|
-
return
|
|
269
|
+
return getMessageEventTime(lastMessage);
|
|
265
270
|
}
|
|
266
271
|
|
|
267
272
|
export function getSessionCompletionMarker(session: {
|
|
@@ -271,6 +276,7 @@ export function getSessionCompletionMarker(session: {
|
|
|
271
276
|
id?: string;
|
|
272
277
|
index?: number;
|
|
273
278
|
receivedAt?: number | string;
|
|
279
|
+
timestamp?: number | string;
|
|
274
280
|
_turnKey?: string;
|
|
275
281
|
}> | null
|
|
276
282
|
} | null
|
|
@@ -282,13 +288,13 @@ export function getSessionCompletionMarker(session: {
|
|
|
282
288
|
if (typeof lastMessage._turnKey === 'string' && lastMessage._turnKey) return `turn:${lastMessage._turnKey}`;
|
|
283
289
|
if (typeof lastMessage.id === 'string' && lastMessage.id) return `id:${lastMessage.id}`;
|
|
284
290
|
if (typeof lastMessage.index === 'number' && Number.isFinite(lastMessage.index)) return `idx:${lastMessage.index}`;
|
|
285
|
-
const timestamp =
|
|
291
|
+
const timestamp = getMessageEventTime(lastMessage);
|
|
286
292
|
return timestamp > 0 ? `ts:${timestamp}` : '';
|
|
287
293
|
}
|
|
288
294
|
|
|
289
295
|
function getSessionLastUsedAt(session: {
|
|
290
296
|
activeChat?: {
|
|
291
|
-
messages?: Array<{ receivedAt?: number | string }> | null
|
|
297
|
+
messages?: Array<{ receivedAt?: number | string; timestamp?: number | string }> | null
|
|
292
298
|
} | null
|
|
293
299
|
lastUpdated?: number
|
|
294
300
|
}) {
|