@adhdev/daemon-core 0.8.25 → 0.8.28
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/provider-adapter.d.ts +1 -0
- package/dist/boot/daemon-lifecycle.d.ts +2 -0
- package/dist/cli-adapters/pty-transport.d.ts +3 -0
- package/dist/commands/handler.d.ts +1 -0
- package/dist/commands/router.d.ts +24 -0
- package/dist/commands/stream-commands.d.ts +1 -1
- package/dist/detection/cli-detector.d.ts +6 -2
- package/dist/detection/ide-detector.d.ts +2 -1
- package/dist/index.js +824 -369
- package/dist/index.js.map +1 -1
- package/dist/index.mjs +822 -367
- package/dist/index.mjs.map +1 -1
- package/dist/providers/acp-provider-instance.d.ts +1 -1
- package/dist/providers/cli-provider-instance.d.ts +1 -0
- package/dist/providers/extension-provider-instance.d.ts +7 -0
- package/dist/providers/provider-loader.d.ts +26 -0
- package/dist/shared-types.d.ts +2 -0
- package/dist/status/normalize.js +14 -2
- package/dist/status/normalize.js.map +1 -1
- package/dist/status/normalize.mjs +14 -2
- package/dist/status/normalize.mjs.map +1 -1
- package/dist/status/snapshot.d.ts +8 -2
- package/node_modules/@adhdev/session-host-core/dist/index.d.mts +72 -1
- package/node_modules/@adhdev/session-host-core/dist/index.d.ts +72 -1
- package/node_modules/@adhdev/session-host-core/dist/index.js.map +1 -1
- package/node_modules/@adhdev/session-host-core/package.json +1 -1
- package/package.json +1 -1
- package/src/agent-stream/provider-adapter.ts +45 -3
- package/src/boot/daemon-lifecycle.ts +31 -1
- package/src/cli-adapters/provider-cli-adapter.ts +14 -3
- package/src/cli-adapters/pty-transport.ts +3 -0
- package/src/cli-adapters/session-host-transport.ts +8 -0
- package/src/commands/chat-commands.ts +38 -9
- package/src/commands/cli-manager.ts +2 -2
- package/src/commands/handler.ts +26 -3
- package/src/commands/router.ts +144 -1
- package/src/commands/stream-commands.ts +6 -3
- package/src/detection/cli-detector.ts +72 -29
- package/src/detection/ide-detector.ts +24 -8
- package/src/launch.ts +1 -1
- package/src/providers/acp-provider-instance.ts +19 -10
- package/src/providers/cli-provider-instance.ts +29 -1
- package/src/providers/extension-provider-instance.ts +24 -1
- package/src/providers/provider-loader.ts +144 -11
- package/src/shared-types.ts +2 -0
- package/src/status/normalize.ts +19 -2
- package/src/status/snapshot.ts +25 -14
|
@@ -25,6 +25,7 @@ export class ExtensionProviderInstance implements ProviderInstance {
|
|
|
25
25
|
private currentStatus: string = 'idle';
|
|
26
26
|
private agentStreams: any[] = [];
|
|
27
27
|
private messages: any[] = [];
|
|
28
|
+
private prevMessageHashes = new Map<string, number>();
|
|
28
29
|
private activeModal: any = null;
|
|
29
30
|
private currentModel: string = '';
|
|
30
31
|
private currentMode: string = '';
|
|
@@ -104,7 +105,7 @@ export class ExtensionProviderInstance implements ProviderInstance {
|
|
|
104
105
|
if (event === 'stream_update') {
|
|
105
106
|
// Reflect data collected from agent-stream-manager
|
|
106
107
|
if (data?.streams) this.agentStreams = data.streams;
|
|
107
|
-
if (data?.messages) this.messages = data.messages;
|
|
108
|
+
if (data?.messages) this.messages = this.assignReceivedAt(data.messages);
|
|
108
109
|
if (data?.activeModal !== undefined) this.activeModal = data.activeModal;
|
|
109
110
|
if (data?.model) this.currentModel = data.model;
|
|
110
111
|
if (data?.mode) this.currentMode = data.mode;
|
|
@@ -132,6 +133,7 @@ export class ExtensionProviderInstance implements ProviderInstance {
|
|
|
132
133
|
dispose(): void {
|
|
133
134
|
this.agentStreams = [];
|
|
134
135
|
this.messages = [];
|
|
136
|
+
this.prevMessageHashes.clear();
|
|
135
137
|
this.monitor.reset();
|
|
136
138
|
this.appliedEffectKeys.clear();
|
|
137
139
|
this.runtimeMessages = [];
|
|
@@ -315,6 +317,26 @@ export class ExtensionProviderInstance implements ProviderInstance {
|
|
|
315
317
|
);
|
|
316
318
|
}
|
|
317
319
|
|
|
320
|
+
/**
|
|
321
|
+
* Assign stable receivedAt to extension messages.
|
|
322
|
+
* Same pattern as IdeProviderInstance.readChat() prevByHash —
|
|
323
|
+
* preserves first-seen timestamp across polling cycles.
|
|
324
|
+
*/
|
|
325
|
+
private assignReceivedAt(messages: any[]): any[] {
|
|
326
|
+
const now = Date.now();
|
|
327
|
+
const nextHashes = new Map<string, number>();
|
|
328
|
+
|
|
329
|
+
for (const msg of messages) {
|
|
330
|
+
const hash = `${msg.role}:${(msg.content || '').slice(0, 100)}`;
|
|
331
|
+
const prevTime = this.prevMessageHashes.get(hash);
|
|
332
|
+
msg.receivedAt = prevTime || now;
|
|
333
|
+
nextHashes.set(hash, msg.receivedAt);
|
|
334
|
+
}
|
|
335
|
+
|
|
336
|
+
this.prevMessageHashes = nextHashes;
|
|
337
|
+
return messages;
|
|
338
|
+
}
|
|
339
|
+
|
|
318
340
|
private mergeConversationMessages(messages: any[]): ChatMessage[] {
|
|
319
341
|
if (this.runtimeMessages.length === 0) return messages;
|
|
320
342
|
return [...messages, ...this.runtimeMessages.map((entry) => entry.message)]
|
|
@@ -382,6 +404,7 @@ export class ExtensionProviderInstance implements ProviderInstance {
|
|
|
382
404
|
}
|
|
383
405
|
this.agentStreams = [];
|
|
384
406
|
this.messages = [];
|
|
407
|
+
this.prevMessageHashes.clear();
|
|
385
408
|
this.activeModal = null;
|
|
386
409
|
this.currentModel = '';
|
|
387
410
|
this.currentMode = '';
|
|
@@ -24,12 +24,19 @@ import type {
|
|
|
24
24
|
ProviderModule,
|
|
25
25
|
ProviderCategory,
|
|
26
26
|
ProviderScripts,
|
|
27
|
+
ProviderSettingDef,
|
|
27
28
|
ProviderSettingSchema,
|
|
28
29
|
ResolvedProvider,
|
|
29
30
|
} from './contracts.js';
|
|
30
31
|
|
|
32
|
+
interface ProviderAvailabilityState {
|
|
33
|
+
installed: boolean;
|
|
34
|
+
detectedPath: string | null;
|
|
35
|
+
}
|
|
36
|
+
|
|
31
37
|
export class ProviderLoader {
|
|
32
38
|
private providers = new Map<string, ProviderModule>();
|
|
39
|
+
private providerAvailability = new Map<string, ProviderAvailabilityState>();
|
|
33
40
|
private userDir: string;
|
|
34
41
|
private upstreamDir: string;
|
|
35
42
|
private disableUpstream: boolean;
|
|
@@ -152,6 +159,7 @@ export class ProviderLoader {
|
|
|
152
159
|
*/
|
|
153
160
|
loadAll(): void {
|
|
154
161
|
this.providers.clear();
|
|
162
|
+
this.providerAvailability.clear();
|
|
155
163
|
|
|
156
164
|
// 1. Load upstream (GitHub auto-download — primary source)
|
|
157
165
|
let upstreamCount = 0;
|
|
@@ -236,11 +244,12 @@ export class ProviderLoader {
|
|
|
236
244
|
const versionCommand = typeof verCmdConfig === 'object' && verCmdConfig !== null
|
|
237
245
|
? verCmdConfig[process.platform]
|
|
238
246
|
: verCmdConfig;
|
|
247
|
+
const command = this.getSpawnCommand(p.type, p.spawn.command);
|
|
239
248
|
result.push({
|
|
240
249
|
id: p.type,
|
|
241
250
|
displayName: p.displayName || p.name,
|
|
242
251
|
icon: p.icon || '🔧',
|
|
243
|
-
command
|
|
252
|
+
command,
|
|
244
253
|
category: p.category,
|
|
245
254
|
...(typeof versionCommand === 'string' && versionCommand.trim()
|
|
246
255
|
? { versionCommand: versionCommand.trim() }
|
|
@@ -386,6 +395,80 @@ export class ProviderLoader {
|
|
|
386
395
|
.map(p => p.type);
|
|
387
396
|
}
|
|
388
397
|
|
|
398
|
+
getSpawnCommand(type: string, fallback?: string): string {
|
|
399
|
+
const override = this.getOptionalStringSetting(type, 'executablePath');
|
|
400
|
+
if (override) return override;
|
|
401
|
+
return fallback || this.providers.get(type)?.spawn?.command || type;
|
|
402
|
+
}
|
|
403
|
+
|
|
404
|
+
getIdeCliCommand(type: string, fallback?: string | null): string | null {
|
|
405
|
+
const override = this.getOptionalStringSetting(type, 'cliPathOverride');
|
|
406
|
+
if (override) return override;
|
|
407
|
+
return fallback || this.providers.get(type)?.cli || null;
|
|
408
|
+
}
|
|
409
|
+
|
|
410
|
+
getIdePathCandidates(type: string, fallback?: string[]): string[] {
|
|
411
|
+
const override = this.getOptionalStringSetting(type, 'appPathOverride');
|
|
412
|
+
if (override) return [override];
|
|
413
|
+
if (fallback && fallback.length > 0) return fallback;
|
|
414
|
+
const osPaths = this.providers.get(type)?.paths?.[process.platform];
|
|
415
|
+
return Array.isArray(osPaths) ? [...osPaths] : [];
|
|
416
|
+
}
|
|
417
|
+
|
|
418
|
+
setProviderAvailability(type: string, state: { installed: boolean; detectedPath?: string | null }): void {
|
|
419
|
+
this.providerAvailability.set(type, {
|
|
420
|
+
installed: !!state.installed,
|
|
421
|
+
detectedPath: state.detectedPath ?? null,
|
|
422
|
+
});
|
|
423
|
+
}
|
|
424
|
+
|
|
425
|
+
setCliDetectionResults(results: Array<{ id: string; installed: boolean; path?: string }>, replace: boolean = true): void {
|
|
426
|
+
if (replace) {
|
|
427
|
+
for (const provider of this.providers.values()) {
|
|
428
|
+
if (provider.category === 'cli' || provider.category === 'acp') {
|
|
429
|
+
this.providerAvailability.set(provider.type, { installed: false, detectedPath: null });
|
|
430
|
+
}
|
|
431
|
+
}
|
|
432
|
+
}
|
|
433
|
+
for (const result of results) {
|
|
434
|
+
this.setProviderAvailability(result.id, {
|
|
435
|
+
installed: !!result.installed,
|
|
436
|
+
detectedPath: result.path || null,
|
|
437
|
+
});
|
|
438
|
+
}
|
|
439
|
+
}
|
|
440
|
+
|
|
441
|
+
setIdeDetectionResults(results: Array<{ id: string; installed: boolean; path?: string | null; cliCommand?: string | null }>, replace: boolean = true): void {
|
|
442
|
+
if (replace) {
|
|
443
|
+
for (const provider of this.providers.values()) {
|
|
444
|
+
if (provider.category === 'ide') {
|
|
445
|
+
this.providerAvailability.set(provider.type, { installed: false, detectedPath: null });
|
|
446
|
+
}
|
|
447
|
+
}
|
|
448
|
+
}
|
|
449
|
+
for (const result of results) {
|
|
450
|
+
this.setProviderAvailability(result.id, {
|
|
451
|
+
installed: !!result.installed,
|
|
452
|
+
detectedPath: result.cliCommand || result.path || null,
|
|
453
|
+
});
|
|
454
|
+
}
|
|
455
|
+
}
|
|
456
|
+
|
|
457
|
+
getAvailableProviderInfos(): Array<ProviderModule & { installed?: boolean; detectedPath?: string | null }> {
|
|
458
|
+
return this.getAll().map((provider) => {
|
|
459
|
+
const availability = this.providerAvailability.get(provider.type);
|
|
460
|
+
return {
|
|
461
|
+
...provider,
|
|
462
|
+
...(availability
|
|
463
|
+
? {
|
|
464
|
+
installed: availability.installed,
|
|
465
|
+
detectedPath: availability.detectedPath,
|
|
466
|
+
}
|
|
467
|
+
: {}),
|
|
468
|
+
};
|
|
469
|
+
});
|
|
470
|
+
}
|
|
471
|
+
|
|
389
472
|
/**
|
|
390
473
|
* Register IDE providers to core/detector registry
|
|
391
474
|
* → Enables detectIDEs() to detect provider.js-based IDEs
|
|
@@ -888,9 +971,8 @@ export class ProviderLoader {
|
|
|
888
971
|
* Get public settings schema for a provider (for dashboard UI rendering)
|
|
889
972
|
*/
|
|
890
973
|
getPublicSettings(type: string): ProviderSettingSchema[] {
|
|
891
|
-
const
|
|
892
|
-
|
|
893
|
-
return Object.entries(provider.settings)
|
|
974
|
+
const settings = this.getSettingsSchema(type);
|
|
975
|
+
return Object.entries(settings)
|
|
894
976
|
.filter(([, def]) => (def as any).public === true)
|
|
895
977
|
.map(([key, def]) => ({ key, ...(def as any) }));
|
|
896
978
|
}
|
|
@@ -911,8 +993,7 @@ export class ProviderLoader {
|
|
|
911
993
|
* Resolved setting value for a provider (default + user override)
|
|
912
994
|
*/
|
|
913
995
|
getSettingValue(type: string, key: string): any {
|
|
914
|
-
const
|
|
915
|
-
const schemaDef = provider?.settings?.[key];
|
|
996
|
+
const schemaDef = this.getSettingsSchema(type)[key];
|
|
916
997
|
const defaultVal = schemaDef ? (schemaDef as any).default : undefined;
|
|
917
998
|
|
|
918
999
|
// Load user-saved value
|
|
@@ -930,10 +1011,9 @@ export class ProviderLoader {
|
|
|
930
1011
|
* All resolved settings for a provider (default + user override)
|
|
931
1012
|
*/
|
|
932
1013
|
getSettings(type: string): Record<string, any> {
|
|
933
|
-
const
|
|
934
|
-
if (!provider?.settings) return {};
|
|
1014
|
+
const settings = this.getSettingsSchema(type);
|
|
935
1015
|
const result: Record<string, any> = {};
|
|
936
|
-
for (const [key
|
|
1016
|
+
for (const [key] of Object.entries(settings)) {
|
|
937
1017
|
result[key] = this.getSettingValue(type, key);
|
|
938
1018
|
}
|
|
939
1019
|
return result;
|
|
@@ -943,8 +1023,7 @@ export class ProviderLoader {
|
|
|
943
1023
|
* Save provider setting value (writes to config.json)
|
|
944
1024
|
*/
|
|
945
1025
|
setSetting(type: string, key: string, value: any): boolean {
|
|
946
|
-
const
|
|
947
|
-
const schemaDef = provider?.settings?.[key] as any;
|
|
1026
|
+
const schemaDef = this.getSettingsSchema(type)[key] as any;
|
|
948
1027
|
if (!schemaDef) return false;
|
|
949
1028
|
|
|
950
1029
|
// Non-public settings cannot be modified externally
|
|
@@ -952,6 +1031,7 @@ export class ProviderLoader {
|
|
|
952
1031
|
|
|
953
1032
|
// Type validation
|
|
954
1033
|
if (schemaDef.type === 'boolean' && typeof value !== 'boolean') return false;
|
|
1034
|
+
if (schemaDef.type === 'string' && typeof value !== 'string') return false;
|
|
955
1035
|
if (schemaDef.type === 'number') {
|
|
956
1036
|
if (typeof value !== 'number') return false;
|
|
957
1037
|
if (schemaDef.min !== undefined && value < schemaDef.min) return false;
|
|
@@ -974,6 +1054,59 @@ export class ProviderLoader {
|
|
|
974
1054
|
}
|
|
975
1055
|
}
|
|
976
1056
|
|
|
1057
|
+
private getOptionalStringSetting(type: string, key: string): string | null {
|
|
1058
|
+
const value = this.getSettingValue(type, key);
|
|
1059
|
+
if (typeof value !== 'string') return null;
|
|
1060
|
+
const trimmed = value.trim();
|
|
1061
|
+
return trimmed ? trimmed : null;
|
|
1062
|
+
}
|
|
1063
|
+
|
|
1064
|
+
private getSettingsSchema(type: string): Record<string, ProviderSettingDef> {
|
|
1065
|
+
const provider = this.providers.get(type);
|
|
1066
|
+
if (!provider) return {};
|
|
1067
|
+
return {
|
|
1068
|
+
...this.getSyntheticSettings(type, provider),
|
|
1069
|
+
...(provider.settings || {}),
|
|
1070
|
+
};
|
|
1071
|
+
}
|
|
1072
|
+
|
|
1073
|
+
private getSyntheticSettings(type: string, provider: ProviderModule): Record<string, ProviderSettingDef> {
|
|
1074
|
+
const result: Record<string, ProviderSettingDef> = {};
|
|
1075
|
+
|
|
1076
|
+
if ((provider.category === 'cli' || provider.category === 'acp') && provider.spawn?.command && !provider.settings?.executablePath) {
|
|
1077
|
+
result.executablePath = {
|
|
1078
|
+
type: 'string',
|
|
1079
|
+
default: '',
|
|
1080
|
+
public: true,
|
|
1081
|
+
label: 'Executable path',
|
|
1082
|
+
description: 'Optional absolute path for this provider binary. Leave blank to use the default PATH lookup.',
|
|
1083
|
+
};
|
|
1084
|
+
}
|
|
1085
|
+
|
|
1086
|
+
if (provider.category === 'ide') {
|
|
1087
|
+
if (provider.cli && !provider.settings?.cliPathOverride) {
|
|
1088
|
+
result.cliPathOverride = {
|
|
1089
|
+
type: 'string',
|
|
1090
|
+
default: '',
|
|
1091
|
+
public: true,
|
|
1092
|
+
label: 'CLI path override',
|
|
1093
|
+
description: 'Optional absolute path for the IDE CLI launcher. Leave blank to use the detected default.',
|
|
1094
|
+
};
|
|
1095
|
+
}
|
|
1096
|
+
if (provider.paths && !provider.settings?.appPathOverride) {
|
|
1097
|
+
result.appPathOverride = {
|
|
1098
|
+
type: 'string',
|
|
1099
|
+
default: '',
|
|
1100
|
+
public: true,
|
|
1101
|
+
label: 'App path override',
|
|
1102
|
+
description: 'Optional absolute path for the IDE app bundle or executable. Leave blank to use the default install locations.',
|
|
1103
|
+
};
|
|
1104
|
+
}
|
|
1105
|
+
}
|
|
1106
|
+
|
|
1107
|
+
return result;
|
|
1108
|
+
}
|
|
1109
|
+
|
|
977
1110
|
// ─── Private ───────────────────────────────────
|
|
978
1111
|
|
|
979
1112
|
/**
|
package/src/shared-types.ts
CHANGED
|
@@ -136,6 +136,8 @@ export interface AvailableProviderInfo {
|
|
|
136
136
|
category: 'ide' | 'extension' | 'cli' | 'acp';
|
|
137
137
|
displayName: string;
|
|
138
138
|
icon: string;
|
|
139
|
+
installed?: boolean;
|
|
140
|
+
detectedPath?: string | null;
|
|
139
141
|
}
|
|
140
142
|
|
|
141
143
|
/** ACP config option (model/mode/thought_level selection) */
|
package/src/status/normalize.ts
CHANGED
|
@@ -64,6 +64,23 @@ function trimMessageForStatus(message: unknown, stringLimit: number): unknown {
|
|
|
64
64
|
return trimStructuredStrings(message, stringLimit);
|
|
65
65
|
}
|
|
66
66
|
|
|
67
|
+
/**
|
|
68
|
+
* Collapse timestamp / createdAt into receivedAt so downstream consumers
|
|
69
|
+
* only ever need to read a single canonical time field.
|
|
70
|
+
*/
|
|
71
|
+
function normalizeMessageTime(message: unknown): unknown {
|
|
72
|
+
if (!message || typeof message !== 'object') return message;
|
|
73
|
+
const msg = message as Record<string, unknown>;
|
|
74
|
+
if (msg.receivedAt == null) {
|
|
75
|
+
const fallback = msg.timestamp ?? msg.createdAt;
|
|
76
|
+
if (fallback != null) {
|
|
77
|
+
const ts = typeof fallback === 'string' ? Date.parse(fallback as string) : Number(fallback);
|
|
78
|
+
if (Number.isFinite(ts) && ts > 0) msg.receivedAt = ts;
|
|
79
|
+
}
|
|
80
|
+
}
|
|
81
|
+
return msg;
|
|
82
|
+
}
|
|
83
|
+
|
|
67
84
|
function trimMessagesForStatus(messages: unknown[] | null | undefined): unknown[] {
|
|
68
85
|
if (!Array.isArray(messages) || messages.length === 0) return [];
|
|
69
86
|
|
|
@@ -72,11 +89,11 @@ function trimMessagesForStatus(messages: unknown[] | null | undefined): unknown[
|
|
|
72
89
|
let totalBytes = 0;
|
|
73
90
|
|
|
74
91
|
for (let i = recent.length - 1; i >= 0; i -= 1) {
|
|
75
|
-
let normalized = trimMessageForStatus(recent[i], STATUS_ACTIVE_CHAT_STRING_LIMIT);
|
|
92
|
+
let normalized = normalizeMessageTime(trimMessageForStatus(recent[i], STATUS_ACTIVE_CHAT_STRING_LIMIT));
|
|
76
93
|
let size = estimateBytes(normalized);
|
|
77
94
|
|
|
78
95
|
if (size > STATUS_ACTIVE_CHAT_TOTAL_BYTES_LIMIT) {
|
|
79
|
-
normalized = trimMessageForStatus(recent[i], STATUS_ACTIVE_CHAT_FALLBACK_STRING_LIMIT);
|
|
96
|
+
normalized = normalizeMessageTime(trimMessageForStatus(recent[i], STATUS_ACTIVE_CHAT_FALLBACK_STRING_LIMIT));
|
|
80
97
|
size = estimateBytes(normalized);
|
|
81
98
|
}
|
|
82
99
|
|
package/src/status/snapshot.ts
CHANGED
|
@@ -35,6 +35,14 @@ export interface StatusSnapshotOptions {
|
|
|
35
35
|
displayName?: string;
|
|
36
36
|
category: 'ide' | 'extension' | 'cli' | 'acp';
|
|
37
37
|
}>;
|
|
38
|
+
getAvailableProviderInfos?: () => Array<{
|
|
39
|
+
type: string;
|
|
40
|
+
icon?: string;
|
|
41
|
+
displayName?: string;
|
|
42
|
+
category: 'ide' | 'extension' | 'cli' | 'acp';
|
|
43
|
+
installed?: boolean;
|
|
44
|
+
detectedPath?: string | null;
|
|
45
|
+
}>;
|
|
38
46
|
};
|
|
39
47
|
detectedIdes: Array<{
|
|
40
48
|
id: string;
|
|
@@ -75,12 +83,22 @@ function buildDetectedIdeInfos(
|
|
|
75
83
|
function buildAvailableProviders(
|
|
76
84
|
providerLoader: StatusSnapshotOptions['providerLoader'],
|
|
77
85
|
): AvailableProviderInfo[] {
|
|
78
|
-
|
|
86
|
+
const providers: Array<{
|
|
87
|
+
type: string;
|
|
88
|
+
icon?: string;
|
|
89
|
+
displayName?: string;
|
|
90
|
+
category: 'ide' | 'extension' | 'cli' | 'acp';
|
|
91
|
+
installed?: boolean;
|
|
92
|
+
detectedPath?: string | null;
|
|
93
|
+
}> = providerLoader.getAvailableProviderInfos?.() || providerLoader.getAll();
|
|
94
|
+
return providers.map((provider) => ({
|
|
79
95
|
type: provider.type,
|
|
80
96
|
name: provider.displayName || provider.type,
|
|
81
97
|
displayName: provider.displayName || provider.type,
|
|
82
98
|
icon: provider.icon || '💻',
|
|
83
99
|
category: provider.category,
|
|
100
|
+
...(provider.installed !== undefined ? { installed: provider.installed } : {}),
|
|
101
|
+
...(provider.detectedPath !== undefined ? { detectedPath: provider.detectedPath } : {}),
|
|
84
102
|
}));
|
|
85
103
|
}
|
|
86
104
|
|
|
@@ -95,17 +113,12 @@ function parseMessageTime(value: unknown): number {
|
|
|
95
113
|
|
|
96
114
|
function getSessionMessageUpdatedAt(session: {
|
|
97
115
|
activeChat?: {
|
|
98
|
-
messages?: Array<{
|
|
116
|
+
messages?: Array<{ receivedAt?: number | string }> | null
|
|
99
117
|
} | null
|
|
100
118
|
}) {
|
|
101
119
|
const lastMessage = session.activeChat?.messages?.at?.(-1);
|
|
102
120
|
if (!lastMessage) return 0;
|
|
103
|
-
return (
|
|
104
|
-
parseMessageTime(lastMessage.timestamp)
|
|
105
|
-
|| parseMessageTime(lastMessage.receivedAt)
|
|
106
|
-
|| parseMessageTime(lastMessage.createdAt)
|
|
107
|
-
|| 0
|
|
108
|
-
);
|
|
121
|
+
return parseMessageTime(lastMessage.receivedAt) || 0;
|
|
109
122
|
}
|
|
110
123
|
|
|
111
124
|
export function getSessionCompletionMarker(session: {
|
|
@@ -114,9 +127,7 @@ export function getSessionCompletionMarker(session: {
|
|
|
114
127
|
role?: string;
|
|
115
128
|
id?: string;
|
|
116
129
|
index?: number;
|
|
117
|
-
timestamp?: number | string;
|
|
118
130
|
receivedAt?: number | string;
|
|
119
|
-
createdAt?: number | string;
|
|
120
131
|
_turnKey?: string;
|
|
121
132
|
}> | null
|
|
122
133
|
} | null
|
|
@@ -124,17 +135,17 @@ export function getSessionCompletionMarker(session: {
|
|
|
124
135
|
const lastMessage = session.activeChat?.messages?.at?.(-1) as any;
|
|
125
136
|
if (!lastMessage) return '';
|
|
126
137
|
const role = typeof lastMessage.role === 'string' ? lastMessage.role : '';
|
|
127
|
-
if (role === 'user' || role === 'human') return '';
|
|
138
|
+
if (role === 'user' || role === 'human' || role === 'system') return '';
|
|
128
139
|
if (typeof lastMessage._turnKey === 'string' && lastMessage._turnKey) return `turn:${lastMessage._turnKey}`;
|
|
129
140
|
if (typeof lastMessage.id === 'string' && lastMessage.id) return `id:${lastMessage.id}`;
|
|
130
141
|
if (typeof lastMessage.index === 'number' && Number.isFinite(lastMessage.index)) return `idx:${lastMessage.index}`;
|
|
131
|
-
const timestamp = parseMessageTime(lastMessage.
|
|
142
|
+
const timestamp = parseMessageTime(lastMessage.receivedAt);
|
|
132
143
|
return timestamp > 0 ? `ts:${timestamp}` : '';
|
|
133
144
|
}
|
|
134
145
|
|
|
135
146
|
function getSessionLastUsedAt(session: {
|
|
136
147
|
activeChat?: {
|
|
137
|
-
messages?: Array<{
|
|
148
|
+
messages?: Array<{ receivedAt?: number | string }> | null
|
|
138
149
|
} | null
|
|
139
150
|
lastUpdated?: number
|
|
140
151
|
}) {
|
|
@@ -171,7 +182,7 @@ function getUnreadState(
|
|
|
171
182
|
}
|
|
172
183
|
const unread = completionMarker
|
|
173
184
|
? completionMarker !== seenCompletionMarker
|
|
174
|
-
: hasContentChange && lastUsedAt > lastSeenAt && lastRole !== 'user' && lastRole !== 'human';
|
|
185
|
+
: hasContentChange && lastUsedAt > lastSeenAt && lastRole !== 'user' && lastRole !== 'human' && lastRole !== 'system';
|
|
175
186
|
return { unread, inboxBucket: unread ? 'task_complete' : 'idle' };
|
|
176
187
|
}
|
|
177
188
|
|