@adhdev/daemon-core 0.7.39 → 0.7.40
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/config/config.d.ts +4 -2
- package/dist/config/recent-activity.d.ts +6 -6
- package/dist/index.d.ts +2 -2
- package/dist/index.js +187 -214
- package/dist/index.js.map +1 -1
- package/dist/index.mjs +187 -214
- package/dist/index.mjs.map +1 -1
- package/dist/shared-types-extra.d.ts +1 -1
- package/dist/shared-types.d.ts +4 -12
- package/dist/status/snapshot.d.ts +13 -0
- package/node_modules/@adhdev/session-host-core/package.json +1 -1
- package/package.json +1 -1
- package/src/commands/router.ts +26 -16
- package/src/config/config.ts +6 -3
- package/src/config/recent-activity.ts +24 -12
- package/src/index.ts +2 -2
- package/src/shared-types-extra.ts +1 -1
- package/src/shared-types.d.ts +4 -6
- package/src/shared-types.ts +4 -12
- package/src/status/reporter.ts +1 -1
- package/src/status/snapshot.ts +61 -91
|
@@ -17,7 +17,7 @@ export interface RuntimeAttachedClient {
|
|
|
17
17
|
type: 'daemon' | 'web' | 'local-terminal';
|
|
18
18
|
readOnly: boolean;
|
|
19
19
|
}
|
|
20
|
-
/** Session status union (used by SessionEntry.status,
|
|
20
|
+
/** Session status union (used by SessionEntry.status, legacy recent-launch metadata, etc.) */
|
|
21
21
|
export type SessionStatus = 'idle' | 'generating' | 'waiting_approval' | 'error' | 'stopped' | 'starting' | 'panel_hidden' | 'not_monitored' | 'disconnected';
|
|
22
22
|
/** Inbox bucket categories for recent sessions */
|
|
23
23
|
export type RecentSessionBucket = 'needs_attention' | 'working' | 'task_complete' | 'idle';
|
package/dist/shared-types.d.ts
CHANGED
|
@@ -69,7 +69,6 @@ export interface SessionEntry {
|
|
|
69
69
|
errorMessage?: string;
|
|
70
70
|
errorReason?: _ProviderErrorReason;
|
|
71
71
|
lastUpdated?: number;
|
|
72
|
-
recentKey?: string;
|
|
73
72
|
unread?: boolean;
|
|
74
73
|
lastSeenAt?: number;
|
|
75
74
|
inboxBucket?: RecentSessionBucket;
|
|
@@ -161,22 +160,15 @@ export interface DetectedIdeInfo {
|
|
|
161
160
|
export type { RecentSessionBucket, TerminalBackendStatus } from './shared-types-extra.js';
|
|
162
161
|
import type { RecentSessionBucket } from './shared-types-extra.js';
|
|
163
162
|
import type { TerminalBackendStatus } from './shared-types-extra.js';
|
|
164
|
-
export interface
|
|
163
|
+
export interface RecentLaunchEntry {
|
|
165
164
|
id: string;
|
|
166
|
-
recentKey: string;
|
|
167
|
-
sessionId?: string | null;
|
|
168
165
|
providerType: string;
|
|
169
166
|
providerName: string;
|
|
170
167
|
kind: 'ide' | 'cli' | 'acp';
|
|
171
|
-
title
|
|
168
|
+
title?: string;
|
|
172
169
|
workspace?: string | null;
|
|
173
170
|
currentModel?: string;
|
|
174
|
-
|
|
175
|
-
lastUsedAt: number;
|
|
176
|
-
unread?: boolean;
|
|
177
|
-
lastSeenAt?: number;
|
|
178
|
-
inboxBucket?: RecentSessionBucket;
|
|
179
|
-
surfaceHidden?: boolean;
|
|
171
|
+
lastLaunchedAt: number;
|
|
180
172
|
}
|
|
181
173
|
export interface StatusReportPayload {
|
|
182
174
|
/** Unique daemon instance identifier */
|
|
@@ -206,7 +198,7 @@ export interface StatusReportPayload {
|
|
|
206
198
|
workspaces?: WorkspaceEntry[];
|
|
207
199
|
defaultWorkspaceId?: string | null;
|
|
208
200
|
defaultWorkspacePath?: string | null;
|
|
209
|
-
|
|
201
|
+
recentLaunches?: RecentLaunchEntry[];
|
|
210
202
|
terminalBackend?: TerminalBackendStatus;
|
|
211
203
|
/** Available providers (present in StatusSnapshot, optional in raw payload) */
|
|
212
204
|
availableProviders?: AvailableProviderInfo[];
|
|
@@ -35,4 +35,17 @@ export interface StatusSnapshotOptions {
|
|
|
35
35
|
export interface StatusSnapshot extends StatusReportPayload {
|
|
36
36
|
availableProviders: AvailableProviderInfo[];
|
|
37
37
|
}
|
|
38
|
+
export declare function getSessionCompletionMarker(session: {
|
|
39
|
+
activeChat?: {
|
|
40
|
+
messages?: Array<{
|
|
41
|
+
role?: string;
|
|
42
|
+
id?: string;
|
|
43
|
+
index?: number;
|
|
44
|
+
timestamp?: number | string;
|
|
45
|
+
receivedAt?: number | string;
|
|
46
|
+
createdAt?: number | string;
|
|
47
|
+
_turnKey?: string;
|
|
48
|
+
}> | null;
|
|
49
|
+
} | null;
|
|
50
|
+
}): string;
|
|
38
51
|
export declare function buildStatusSnapshot(options: StatusSnapshotOptions): StatusSnapshot;
|
package/package.json
CHANGED
package/src/commands/router.ts
CHANGED
|
@@ -18,12 +18,14 @@ import type { ProviderInstanceManager } from '../providers/provider-instance-man
|
|
|
18
18
|
import { launchWithCdp, killIdeProcess, isIdeRunning } from '../launch.js';
|
|
19
19
|
import { loadConfig, saveConfig, updateConfig } from '../config/config.js';
|
|
20
20
|
import { resolveIdeLaunchWorkspace } from '../config/workspaces.js';
|
|
21
|
-
import { appendRecentActivity,
|
|
21
|
+
import { appendRecentActivity, markSessionSeen } from '../config/recent-activity.js';
|
|
22
22
|
import { detectIDEs } from '../detection/ide-detector.js';
|
|
23
23
|
import { SessionRegistry } from '../sessions/registry.js';
|
|
24
24
|
import { LOG } from '../logging/logger.js';
|
|
25
25
|
import { logCommand } from '../logging/command-log.js';
|
|
26
26
|
import { getRecentLogs, LOG_PATH } from '../logging/logger.js';
|
|
27
|
+
import { buildSessionEntries } from '../status/builders.js';
|
|
28
|
+
import { getSessionCompletionMarker } from '../status/snapshot.js';
|
|
27
29
|
import * as fs from 'fs';
|
|
28
30
|
|
|
29
31
|
// ─── Types ───
|
|
@@ -61,6 +63,7 @@ const CHAT_COMMANDS = [
|
|
|
61
63
|
'send_chat', 'new_chat', 'switch_chat', 'set_mode',
|
|
62
64
|
'change_model',
|
|
63
65
|
];
|
|
66
|
+
const READ_DEBUG_ENABLED = process.argv.includes('--dev') || process.env.ADHDEV_READ_DEBUG === '1';
|
|
64
67
|
|
|
65
68
|
export class DaemonCommandRouter {
|
|
66
69
|
private deps: CommandRouterDeps;
|
|
@@ -265,28 +268,35 @@ export class DaemonCommandRouter {
|
|
|
265
268
|
return { success: true, userName: name };
|
|
266
269
|
}
|
|
267
270
|
|
|
268
|
-
case '
|
|
269
|
-
const
|
|
270
|
-
|
|
271
|
-
|
|
272
|
-
return { success: false, error: 'kind and providerType are required' };
|
|
271
|
+
case 'mark_session_seen': {
|
|
272
|
+
const sessionId = args?.sessionId;
|
|
273
|
+
if (!sessionId || typeof sessionId !== 'string') {
|
|
274
|
+
return { success: false, error: 'sessionId is required' };
|
|
273
275
|
}
|
|
274
|
-
const
|
|
275
|
-
|
|
276
|
-
|
|
277
|
-
|
|
278
|
-
|
|
279
|
-
|
|
280
|
-
|
|
281
|
-
|
|
276
|
+
const currentConfig = loadConfig();
|
|
277
|
+
const prevSeenAt = currentConfig.sessionReads?.[sessionId] || 0;
|
|
278
|
+
const sessionEntries = buildSessionEntries(
|
|
279
|
+
this.deps.instanceManager.collectAllStates(),
|
|
280
|
+
this.deps.cdpManagers as Map<string, any>,
|
|
281
|
+
);
|
|
282
|
+
const targetSession = sessionEntries.find((entry) => entry.id === sessionId);
|
|
283
|
+
const completionMarker = targetSession ? getSessionCompletionMarker(targetSession) : '';
|
|
284
|
+
const next = markSessionSeen(
|
|
285
|
+
currentConfig,
|
|
286
|
+
sessionId,
|
|
282
287
|
typeof args?.seenAt === 'number' ? args.seenAt : Date.now(),
|
|
288
|
+
completionMarker,
|
|
283
289
|
);
|
|
290
|
+
if (READ_DEBUG_ENABLED) {
|
|
291
|
+
LOG.info('RecentRead', `mark_session_seen sessionId=${sessionId} seenAt=${String(args?.seenAt || '')} prevSeenAt=${String(prevSeenAt)} nextSeenAt=${String(next.sessionReads?.[sessionId] || 0)} marker=${completionMarker || '-'}`);
|
|
292
|
+
}
|
|
284
293
|
saveConfig(next);
|
|
285
294
|
this.deps.onStatusChange?.();
|
|
286
295
|
return {
|
|
287
296
|
success: true,
|
|
288
|
-
|
|
289
|
-
seenAt: next.
|
|
297
|
+
sessionId,
|
|
298
|
+
seenAt: next.sessionReads?.[sessionId] || Date.now(),
|
|
299
|
+
completionMarker,
|
|
290
300
|
};
|
|
291
301
|
}
|
|
292
302
|
|
package/src/config/config.ts
CHANGED
|
@@ -59,8 +59,10 @@ export interface ADHDevConfig {
|
|
|
59
59
|
|
|
60
60
|
/** Unified recent activity across IDE / CLI / ACP launch flows */
|
|
61
61
|
recentActivity?: RecentActivityEntry[];
|
|
62
|
-
/** Last seen timestamps for
|
|
63
|
-
|
|
62
|
+
/** Last seen timestamps for live sessions, keyed by sessionId */
|
|
63
|
+
sessionReads?: Record<string, number>;
|
|
64
|
+
/** Last seen completion marker for live sessions, keyed by sessionId */
|
|
65
|
+
sessionReadMarkers?: Record<string, string>;
|
|
64
66
|
|
|
65
67
|
// Machine nickname (user-customizable label for this machine)
|
|
66
68
|
machineNickname: string | null;
|
|
@@ -122,7 +124,8 @@ const DEFAULT_CONFIG: ADHDevConfig = {
|
|
|
122
124
|
workspaces: [],
|
|
123
125
|
defaultWorkspaceId: null,
|
|
124
126
|
recentActivity: [],
|
|
125
|
-
|
|
127
|
+
sessionReads: {},
|
|
128
|
+
sessionReadMarkers: {},
|
|
126
129
|
machineNickname: null,
|
|
127
130
|
machineId: undefined,
|
|
128
131
|
machineSecret: null,
|
|
@@ -1,10 +1,10 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* Unified recent activity —
|
|
2
|
+
* Unified recent activity — launcher-facing "pick up where you launched".
|
|
3
3
|
*
|
|
4
|
-
* Unlike
|
|
4
|
+
* Unlike live session state, this is launch oriented:
|
|
5
5
|
* - one normalized row shape for IDE / CLI / ACP
|
|
6
6
|
* - deduped by kind + providerType + workspace
|
|
7
|
-
* -
|
|
7
|
+
* - used only for quick-launch shortcuts
|
|
8
8
|
*/
|
|
9
9
|
|
|
10
10
|
import * as path from 'path';
|
|
@@ -18,7 +18,6 @@ export interface RecentActivityEntry {
|
|
|
18
18
|
providerName: string;
|
|
19
19
|
workspace?: string | null;
|
|
20
20
|
currentModel?: string;
|
|
21
|
-
sessionId?: string | null;
|
|
22
21
|
title?: string;
|
|
23
22
|
lastUsedAt: number;
|
|
24
23
|
}
|
|
@@ -62,22 +61,35 @@ export function getRecentActivity(config: ADHDevConfig, limit = 20): RecentActiv
|
|
|
62
61
|
.slice(0, limit);
|
|
63
62
|
}
|
|
64
63
|
|
|
65
|
-
export function
|
|
66
|
-
return config.
|
|
64
|
+
export function getSessionSeenAt(config: ADHDevConfig, sessionId: string): number {
|
|
65
|
+
return config.sessionReads?.[sessionId] || 0;
|
|
67
66
|
}
|
|
68
67
|
|
|
69
|
-
export function
|
|
68
|
+
export function getSessionSeenMarker(config: ADHDevConfig, sessionId: string): string {
|
|
69
|
+
return config.sessionReadMarkers?.[sessionId] || '';
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
export function markSessionSeen(
|
|
70
73
|
config: ADHDevConfig,
|
|
71
|
-
|
|
74
|
+
sessionId: string,
|
|
72
75
|
seenAt = Date.now(),
|
|
76
|
+
completionMarker?: string | null,
|
|
73
77
|
): ADHDevConfig {
|
|
74
|
-
const prev = config.
|
|
75
|
-
const nextSeenAt = Math.max(prev[
|
|
78
|
+
const prev = config.sessionReads || {};
|
|
79
|
+
const nextSeenAt = Math.max(prev[sessionId] || 0, seenAt);
|
|
80
|
+
const prevMarkers = config.sessionReadMarkers || {};
|
|
81
|
+
const nextMarker = typeof completionMarker === 'string' ? completionMarker : '';
|
|
76
82
|
return {
|
|
77
83
|
...config,
|
|
78
|
-
|
|
84
|
+
sessionReads: {
|
|
79
85
|
...prev,
|
|
80
|
-
[
|
|
86
|
+
[sessionId]: nextSeenAt,
|
|
81
87
|
},
|
|
88
|
+
sessionReadMarkers: nextMarker
|
|
89
|
+
? {
|
|
90
|
+
...prevMarkers,
|
|
91
|
+
[sessionId]: nextMarker,
|
|
92
|
+
}
|
|
93
|
+
: prevMarkers,
|
|
82
94
|
};
|
|
83
95
|
}
|
package/src/index.ts
CHANGED
|
@@ -46,11 +46,11 @@ export type {
|
|
|
46
46
|
// rollup-dts cannot resolve re-exports from shared-types.ts for them.
|
|
47
47
|
import type { RuntimeWriteOwner as _RuntimeWriteOwner } from './shared-types-extra.js';
|
|
48
48
|
import type { RuntimeAttachedClient as _RuntimeAttachedClient } from './shared-types-extra.js';
|
|
49
|
-
import type {
|
|
49
|
+
import type { RecentLaunchEntry as _RecentLaunchEntry } from './shared-types.js';
|
|
50
50
|
import type { TerminalBackendStatus as _TerminalBackendStatus } from './shared-types-extra.js';
|
|
51
51
|
export type RuntimeWriteOwner = _RuntimeWriteOwner;
|
|
52
52
|
export type RuntimeAttachedClient = _RuntimeAttachedClient;
|
|
53
|
-
export type
|
|
53
|
+
export type RecentLaunchEntry = _RecentLaunchEntry;
|
|
54
54
|
export type TerminalBackendStatus = _TerminalBackendStatus;
|
|
55
55
|
|
|
56
56
|
// Type aliases — rollup-dts cannot bundle re-exported type aliases at all.
|
|
@@ -20,7 +20,7 @@ export interface RuntimeAttachedClient {
|
|
|
20
20
|
readOnly: boolean;
|
|
21
21
|
}
|
|
22
22
|
|
|
23
|
-
/** Session status union (used by SessionEntry.status,
|
|
23
|
+
/** Session status union (used by SessionEntry.status, legacy recent-launch metadata, etc.) */
|
|
24
24
|
export type SessionStatus = 'idle' | 'generating' | 'waiting_approval' | 'error' | 'stopped' | 'starting' | 'panel_hidden' | 'not_monitored' | 'disconnected';
|
|
25
25
|
|
|
26
26
|
/** Inbox bucket categories for recent sessions */
|
package/src/shared-types.d.ts
CHANGED
|
@@ -147,17 +147,15 @@ export interface WorkspaceActivity {
|
|
|
147
147
|
kind?: string;
|
|
148
148
|
agentType?: string;
|
|
149
149
|
}
|
|
150
|
-
export interface
|
|
150
|
+
export interface RecentLaunchEntry {
|
|
151
151
|
id: string;
|
|
152
|
-
sessionId?: string | null;
|
|
153
152
|
providerType: string;
|
|
154
153
|
providerName: string;
|
|
155
154
|
kind: 'ide' | 'cli' | 'acp';
|
|
156
|
-
title
|
|
155
|
+
title?: string;
|
|
157
156
|
workspace?: string | null;
|
|
158
157
|
currentModel?: string;
|
|
159
|
-
|
|
160
|
-
lastUsedAt: number;
|
|
158
|
+
lastLaunchedAt: number;
|
|
161
159
|
}
|
|
162
160
|
export interface StatusReportPayload {
|
|
163
161
|
/** Daemon instance ID */
|
|
@@ -188,5 +186,5 @@ export interface StatusReportPayload {
|
|
|
188
186
|
defaultWorkspaceId?: string | null;
|
|
189
187
|
defaultWorkspacePath?: string | null;
|
|
190
188
|
workspaceActivity?: WorkspaceActivity[];
|
|
191
|
-
|
|
189
|
+
recentLaunches?: RecentLaunchEntry[];
|
|
192
190
|
}
|
package/src/shared-types.ts
CHANGED
|
@@ -120,7 +120,6 @@ export interface SessionEntry {
|
|
|
120
120
|
errorMessage?: string;
|
|
121
121
|
errorReason?: _ProviderErrorReason;
|
|
122
122
|
lastUpdated?: number;
|
|
123
|
-
recentKey?: string;
|
|
124
123
|
unread?: boolean;
|
|
125
124
|
lastSeenAt?: number;
|
|
126
125
|
inboxBucket?: RecentSessionBucket;
|
|
@@ -215,22 +214,15 @@ export type { RecentSessionBucket, TerminalBackendStatus } from './shared-types-
|
|
|
215
214
|
import type { RecentSessionBucket } from './shared-types-extra.js';
|
|
216
215
|
import type { TerminalBackendStatus } from './shared-types-extra.js';
|
|
217
216
|
|
|
218
|
-
export interface
|
|
217
|
+
export interface RecentLaunchEntry {
|
|
219
218
|
id: string;
|
|
220
|
-
recentKey: string;
|
|
221
|
-
sessionId?: string | null;
|
|
222
219
|
providerType: string;
|
|
223
220
|
providerName: string;
|
|
224
221
|
kind: 'ide' | 'cli' | 'acp';
|
|
225
|
-
title
|
|
222
|
+
title?: string;
|
|
226
223
|
workspace?: string | null;
|
|
227
224
|
currentModel?: string;
|
|
228
|
-
|
|
229
|
-
lastUsedAt: number;
|
|
230
|
-
unread?: boolean;
|
|
231
|
-
lastSeenAt?: number;
|
|
232
|
-
inboxBucket?: RecentSessionBucket;
|
|
233
|
-
surfaceHidden?: boolean;
|
|
225
|
+
lastLaunchedAt: number;
|
|
234
226
|
}
|
|
235
227
|
|
|
236
228
|
// ─── Status Report Payload (daemon → server) ────────────────────────
|
|
@@ -259,7 +251,7 @@ export interface StatusReportPayload {
|
|
|
259
251
|
workspaces?: WorkspaceEntry[];
|
|
260
252
|
defaultWorkspaceId?: string | null;
|
|
261
253
|
defaultWorkspacePath?: string | null;
|
|
262
|
-
|
|
254
|
+
recentLaunches?: RecentLaunchEntry[];
|
|
263
255
|
terminalBackend?: TerminalBackendStatus;
|
|
264
256
|
/** Available providers (present in StatusSnapshot, optional in raw payload) */
|
|
265
257
|
availableProviders?: AvailableProviderInfo[];
|
package/src/status/reporter.ts
CHANGED
|
@@ -203,7 +203,7 @@ export class DaemonStatusReporter {
|
|
|
203
203
|
currentModel: session.currentModel,
|
|
204
204
|
currentPlan: session.currentPlan,
|
|
205
205
|
currentAutoApprove: session.currentAutoApprove,
|
|
206
|
-
|
|
206
|
+
lastUpdated: session.lastUpdated,
|
|
207
207
|
unread: (session as any).unread,
|
|
208
208
|
lastSeenAt: (session as any).lastSeenAt,
|
|
209
209
|
inboxBucket: (session as any).inboxBucket,
|
package/src/status/snapshot.ts
CHANGED
|
@@ -8,17 +8,18 @@
|
|
|
8
8
|
|
|
9
9
|
import * as os from 'os';
|
|
10
10
|
import { loadConfig } from '../config/config.js';
|
|
11
|
-
import {
|
|
11
|
+
import { getRecentActivity, getSessionSeenAt, getSessionSeenMarker } from '../config/recent-activity.js';
|
|
12
12
|
import { getWorkspaceState } from '../config/workspaces.js';
|
|
13
13
|
import { getHostMemorySnapshot } from '../system/host-memory.js';
|
|
14
14
|
import { getTerminalBackendRuntimeStatus } from '../cli-adapters/terminal-screen.js';
|
|
15
|
+
import { LOG } from '../logging/logger.js';
|
|
15
16
|
import { buildSessionEntries, isCdpConnected } from './builders.js';
|
|
16
17
|
import type { ProviderState } from '../providers/provider-instance.js';
|
|
17
18
|
import type {
|
|
18
19
|
AvailableProviderInfo,
|
|
19
20
|
DetectedIdeInfo,
|
|
21
|
+
RecentLaunchEntry,
|
|
20
22
|
RecentSessionBucket,
|
|
21
|
-
RecentSessionEntry,
|
|
22
23
|
SessionEntry,
|
|
23
24
|
StatusReportPayload,
|
|
24
25
|
} from '../shared-types.js';
|
|
@@ -53,6 +54,8 @@ export interface StatusSnapshot extends StatusReportPayload {
|
|
|
53
54
|
availableProviders: AvailableProviderInfo[];
|
|
54
55
|
}
|
|
55
56
|
|
|
57
|
+
const READ_DEBUG_ENABLED = process.argv.includes('--dev') || process.env.ADHDEV_READ_DEBUG === '1';
|
|
58
|
+
|
|
56
59
|
function buildDetectedIdeInfos(
|
|
57
60
|
detectedIdes: StatusSnapshotOptions['detectedIdes'],
|
|
58
61
|
cdpManagers: StatusSnapshotOptions['cdpManagers'],
|
|
@@ -104,6 +107,30 @@ function getSessionMessageUpdatedAt(session: {
|
|
|
104
107
|
);
|
|
105
108
|
}
|
|
106
109
|
|
|
110
|
+
export function getSessionCompletionMarker(session: {
|
|
111
|
+
activeChat?: {
|
|
112
|
+
messages?: Array<{
|
|
113
|
+
role?: string;
|
|
114
|
+
id?: string;
|
|
115
|
+
index?: number;
|
|
116
|
+
timestamp?: number | string;
|
|
117
|
+
receivedAt?: number | string;
|
|
118
|
+
createdAt?: number | string;
|
|
119
|
+
_turnKey?: string;
|
|
120
|
+
}> | null
|
|
121
|
+
} | null
|
|
122
|
+
}) {
|
|
123
|
+
const lastMessage = session.activeChat?.messages?.at?.(-1) as any;
|
|
124
|
+
if (!lastMessage) return '';
|
|
125
|
+
const role = typeof lastMessage.role === 'string' ? lastMessage.role : '';
|
|
126
|
+
if (role === 'user' || role === 'human') return '';
|
|
127
|
+
if (typeof lastMessage._turnKey === 'string' && lastMessage._turnKey) return `turn:${lastMessage._turnKey}`;
|
|
128
|
+
if (typeof lastMessage.id === 'string' && lastMessage.id) return `id:${lastMessage.id}`;
|
|
129
|
+
if (typeof lastMessage.index === 'number' && Number.isFinite(lastMessage.index)) return `idx:${lastMessage.index}`;
|
|
130
|
+
const timestamp = parseMessageTime(lastMessage.timestamp) || parseMessageTime(lastMessage.receivedAt) || parseMessageTime(lastMessage.createdAt);
|
|
131
|
+
return timestamp > 0 ? `ts:${timestamp}` : '';
|
|
132
|
+
}
|
|
133
|
+
|
|
107
134
|
function getSessionLastUsedAt(session: {
|
|
108
135
|
activeChat?: {
|
|
109
136
|
messages?: Array<{ timestamp?: number | string; receivedAt?: number | string; createdAt?: number | string }> | null
|
|
@@ -113,7 +140,7 @@ function getSessionLastUsedAt(session: {
|
|
|
113
140
|
return getSessionMessageUpdatedAt(session) || session.lastUpdated || Date.now();
|
|
114
141
|
}
|
|
115
142
|
|
|
116
|
-
function getSessionKind(session: SessionEntry):
|
|
143
|
+
function getSessionKind(session: SessionEntry): RecentLaunchEntry['kind'] {
|
|
117
144
|
return session.transport === 'cdp-page' || session.transport === 'cdp-webview'
|
|
118
145
|
? 'ide'
|
|
119
146
|
: session.transport === 'acp'
|
|
@@ -132,6 +159,8 @@ function getUnreadState(
|
|
|
132
159
|
lastUsedAt: number,
|
|
133
160
|
lastSeenAt: number,
|
|
134
161
|
lastRole: string,
|
|
162
|
+
completionMarker: string,
|
|
163
|
+
seenCompletionMarker: string,
|
|
135
164
|
): { unread: boolean; inboxBucket: RecentSessionBucket } {
|
|
136
165
|
if (status === 'waiting_approval') {
|
|
137
166
|
return { unread: false, inboxBucket: 'needs_attention' };
|
|
@@ -139,88 +168,27 @@ function getUnreadState(
|
|
|
139
168
|
if (status === 'generating' || status === 'starting') {
|
|
140
169
|
return { unread: false, inboxBucket: 'working' };
|
|
141
170
|
}
|
|
142
|
-
const unread =
|
|
171
|
+
const unread = completionMarker
|
|
172
|
+
? completionMarker !== seenCompletionMarker
|
|
173
|
+
: hasContentChange && lastUsedAt > lastSeenAt && lastRole !== 'user' && lastRole !== 'human';
|
|
143
174
|
return { unread, inboxBucket: unread ? 'task_complete' : 'idle' };
|
|
144
175
|
}
|
|
145
176
|
|
|
146
|
-
function
|
|
147
|
-
sessions: ReturnType<typeof buildSessionEntries>,
|
|
177
|
+
function buildRecentLaunches(
|
|
148
178
|
recentActivity: ReturnType<typeof getRecentActivity>,
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
const lastSeenAt = readState[recentKey] || 0;
|
|
163
|
-
const lastUsedAt = getSessionLastUsedAt(session);
|
|
164
|
-
const { unread, inboxBucket } = getUnreadState(
|
|
165
|
-
getSessionMessageUpdatedAt(session) > 0,
|
|
166
|
-
session.status,
|
|
167
|
-
lastUsedAt,
|
|
168
|
-
lastSeenAt,
|
|
169
|
-
getLastMessageRole(session),
|
|
170
|
-
);
|
|
171
|
-
return {
|
|
172
|
-
id: session.id,
|
|
173
|
-
recentKey,
|
|
174
|
-
sessionId: session.id,
|
|
175
|
-
providerType: session.providerType,
|
|
176
|
-
providerName: session.providerName,
|
|
177
|
-
kind,
|
|
178
|
-
title: session.activeChat?.title || session.title || session.providerName,
|
|
179
|
-
workspace: session.workspace,
|
|
180
|
-
currentModel: session.currentModel,
|
|
181
|
-
status: session.status,
|
|
182
|
-
lastUsedAt,
|
|
183
|
-
unread,
|
|
184
|
-
lastSeenAt,
|
|
185
|
-
inboxBucket,
|
|
186
|
-
surfaceHidden: false,
|
|
187
|
-
};
|
|
188
|
-
});
|
|
189
|
-
for (const item of live) {
|
|
190
|
-
visibleKeys.add(`${item.kind}:${item.providerType}:${item.workspace || ''}`);
|
|
191
|
-
}
|
|
192
|
-
for (const session of sessions) {
|
|
193
|
-
if (!session.surfaceHidden) continue;
|
|
194
|
-
hiddenKeys.add(`${getSessionKind(session)}:${session.providerType}:${session.workspace || ''}`);
|
|
195
|
-
}
|
|
196
|
-
const persisted = recentActivity
|
|
197
|
-
.filter((item) => {
|
|
198
|
-
const key = `${item.kind}:${item.providerType}:${item.workspace || ''}`;
|
|
199
|
-
return !visibleKeys.has(key) && !hiddenKeys.has(key);
|
|
200
|
-
})
|
|
201
|
-
.map((item) => {
|
|
202
|
-
const lastSeenAt = readState[item.id] || 0;
|
|
203
|
-
const unread = item.lastUsedAt > lastSeenAt;
|
|
204
|
-
return {
|
|
205
|
-
id: item.id,
|
|
206
|
-
recentKey: item.id,
|
|
207
|
-
sessionId: item.sessionId || null,
|
|
208
|
-
providerType: item.providerType,
|
|
209
|
-
providerName: item.providerName,
|
|
210
|
-
kind: item.kind,
|
|
211
|
-
title: item.title || item.providerName,
|
|
212
|
-
workspace: item.workspace,
|
|
213
|
-
currentModel: item.currentModel,
|
|
214
|
-
lastUsedAt: item.lastUsedAt,
|
|
215
|
-
unread,
|
|
216
|
-
lastSeenAt,
|
|
217
|
-
inboxBucket: unread ? 'task_complete' : 'idle' as RecentSessionBucket,
|
|
218
|
-
surfaceHidden: false,
|
|
219
|
-
};
|
|
220
|
-
});
|
|
221
|
-
|
|
222
|
-
return [...live, ...persisted]
|
|
223
|
-
.sort((a, b) => b.lastUsedAt - a.lastUsedAt)
|
|
179
|
+
): RecentLaunchEntry[] {
|
|
180
|
+
return recentActivity
|
|
181
|
+
.map((item) => ({
|
|
182
|
+
id: item.id,
|
|
183
|
+
providerType: item.providerType,
|
|
184
|
+
providerName: item.providerName,
|
|
185
|
+
kind: item.kind,
|
|
186
|
+
title: item.title || item.providerName,
|
|
187
|
+
workspace: item.workspace,
|
|
188
|
+
currentModel: item.currentModel,
|
|
189
|
+
lastLaunchedAt: item.lastUsedAt,
|
|
190
|
+
}))
|
|
191
|
+
.sort((a, b) => b.lastLaunchedAt - a.lastLaunchedAt)
|
|
224
192
|
.slice(0, 12);
|
|
225
193
|
}
|
|
226
194
|
|
|
@@ -233,16 +201,11 @@ export function buildStatusSnapshot(options: StatusSnapshotOptions): StatusSnaps
|
|
|
233
201
|
options.allStates,
|
|
234
202
|
options.cdpManagers as Map<string, any>,
|
|
235
203
|
);
|
|
236
|
-
const readState = cfg.recentSessionReads || {};
|
|
237
204
|
for (const session of sessions) {
|
|
238
|
-
const
|
|
239
|
-
const
|
|
240
|
-
kind,
|
|
241
|
-
providerType: session.providerType,
|
|
242
|
-
workspace: session.workspace,
|
|
243
|
-
});
|
|
244
|
-
const lastSeenAt = getRecentSessionSeenAt(cfg, recentKey);
|
|
205
|
+
const lastSeenAt = getSessionSeenAt(cfg, session.id);
|
|
206
|
+
const seenCompletionMarker = getSessionSeenMarker(cfg, session.id);
|
|
245
207
|
const lastUsedAt = getSessionLastUsedAt(session);
|
|
208
|
+
const completionMarker = getSessionCompletionMarker(session);
|
|
246
209
|
const { unread, inboxBucket } = session.surfaceHidden
|
|
247
210
|
? { unread: false, inboxBucket: 'idle' as RecentSessionBucket }
|
|
248
211
|
: getUnreadState(
|
|
@@ -251,11 +214,18 @@ export function buildStatusSnapshot(options: StatusSnapshotOptions): StatusSnaps
|
|
|
251
214
|
lastUsedAt,
|
|
252
215
|
lastSeenAt,
|
|
253
216
|
getLastMessageRole(session),
|
|
217
|
+
completionMarker,
|
|
218
|
+
seenCompletionMarker,
|
|
254
219
|
);
|
|
255
|
-
session.recentKey = recentKey;
|
|
256
220
|
session.lastSeenAt = lastSeenAt;
|
|
257
221
|
session.unread = unread;
|
|
258
222
|
session.inboxBucket = inboxBucket;
|
|
223
|
+
if (READ_DEBUG_ENABLED && (session.unread || session.inboxBucket !== 'idle' || session.providerType.includes('codex'))) {
|
|
224
|
+
LOG.info(
|
|
225
|
+
'RecentRead',
|
|
226
|
+
`snapshot session id=${session.id} provider=${session.providerType} status=${String(session.status || '')} bucket=${inboxBucket} unread=${String(unread)} lastSeenAt=${lastSeenAt} completionMarker=${completionMarker || '-'} seenMarker=${seenCompletionMarker || '-'} lastUpdated=${String(session.lastUpdated || 0)} lastUsedAt=${lastUsedAt} lastRole=${getLastMessageRole(session)} msgUpdatedAt=${getSessionMessageUpdatedAt(session)}`,
|
|
227
|
+
);
|
|
228
|
+
}
|
|
259
229
|
}
|
|
260
230
|
const terminalBackend = getTerminalBackendRuntimeStatus();
|
|
261
231
|
|
|
@@ -283,7 +253,7 @@ export function buildStatusSnapshot(options: StatusSnapshotOptions): StatusSnaps
|
|
|
283
253
|
workspaces: wsState.workspaces,
|
|
284
254
|
defaultWorkspaceId: wsState.defaultWorkspaceId,
|
|
285
255
|
defaultWorkspacePath: wsState.defaultWorkspacePath,
|
|
286
|
-
|
|
256
|
+
recentLaunches: buildRecentLaunches(recentActivity),
|
|
287
257
|
terminalBackend,
|
|
288
258
|
availableProviders: buildAvailableProviders(options.providerLoader),
|
|
289
259
|
};
|