@adhdev/daemon-core 0.7.45 → 0.7.46
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/cli-adapters/provider-cli-adapter.d.ts +1 -0
- package/dist/cli-adapters/pty-transport.d.ts +1 -0
- package/dist/commands/cli-manager.d.ts +11 -2
- package/dist/config/chat-history.d.ts +29 -2
- package/dist/config/config.d.ts +4 -0
- package/dist/config/recent-activity.d.ts +3 -1
- package/dist/config/saved-sessions.d.ts +22 -0
- package/dist/index.d.ts +2 -0
- package/dist/index.js +4619 -3969
- package/dist/index.js.map +1 -1
- package/dist/index.mjs +4613 -3965
- package/dist/index.mjs.map +1 -1
- package/dist/providers/cli-provider-instance.d.ts +24 -1
- package/dist/providers/contracts.d.ts +3 -0
- package/dist/providers/provider-instance.d.ts +1 -0
- package/dist/shared-types.d.ts +2 -0
- package/node_modules/@adhdev/session-host-core/dist/index.d.mts +12 -1
- package/node_modules/@adhdev/session-host-core/dist/index.d.ts +12 -1
- package/node_modules/@adhdev/session-host-core/dist/index.js +9 -0
- package/node_modules/@adhdev/session-host-core/dist/index.js.map +1 -1
- package/node_modules/@adhdev/session-host-core/dist/index.mjs +9 -0
- package/node_modules/@adhdev/session-host-core/dist/index.mjs.map +1 -1
- package/node_modules/@adhdev/session-host-core/package.json +1 -1
- package/package.json +1 -1
- package/src/boot/daemon-lifecycle.ts +19 -15
- package/src/cli-adapters/provider-cli-adapter.ts +7 -1
- package/src/cli-adapters/pty-transport.ts +1 -0
- package/src/cli-adapters/session-host-transport.ts +19 -0
- package/src/commands/chat-commands.ts +28 -8
- package/src/commands/cli-manager.ts +259 -22
- package/src/commands/router.ts +52 -1
- package/src/config/chat-history.ts +193 -10
- package/src/config/config.d.ts +4 -0
- package/src/config/config.ts +6 -0
- package/src/config/recent-activity.ts +13 -2
- package/src/config/saved-sessions.ts +73 -0
- package/src/daemon/dev-auto-implement.ts +23 -5
- package/src/daemon/dev-server.ts +22 -4
- package/src/index.ts +2 -0
- package/src/providers/cli-provider-instance.ts +205 -4
- package/src/providers/contracts.ts +3 -0
- package/src/providers/provider-instance.d.ts +1 -0
- package/src/providers/provider-instance.ts +1 -0
- package/src/session-host/runtime-support.ts +1 -0
- package/src/shared-types.d.ts +2 -0
- package/src/shared-types.ts +2 -0
- package/src/status/builders.ts +1 -0
- package/src/status/snapshot.ts +1 -0
|
@@ -5,8 +5,11 @@
|
|
|
5
5
|
* collectCliData() + status transition logic from daemon-status.ts moved here.
|
|
6
6
|
*/
|
|
7
7
|
|
|
8
|
+
import * as os from 'os';
|
|
8
9
|
import * as path from 'path';
|
|
9
10
|
import * as crypto from 'crypto';
|
|
11
|
+
import * as fs from 'fs';
|
|
12
|
+
import { createRequire } from 'node:module';
|
|
10
13
|
import type { ProviderModule } from './contracts.js';
|
|
11
14
|
import type { ProviderInstance, ProviderState, ProviderEvent, InstanceContext } from './provider-instance.js';
|
|
12
15
|
import { ProviderCliAdapter } from '../cli-adapters/provider-cli-adapter.js';
|
|
@@ -16,6 +19,26 @@ import { StatusMonitor } from './status-monitor.js';
|
|
|
16
19
|
import { ChatHistoryWriter } from '../config/chat-history.js';
|
|
17
20
|
import { LOG } from '../logging/logger.js';
|
|
18
21
|
|
|
22
|
+
let CachedDatabaseSync: (new (path: string, options?: { readOnly?: boolean }) => {
|
|
23
|
+
prepare(sql: string): { get(...params: Array<string | number>): unknown };
|
|
24
|
+
close(): void;
|
|
25
|
+
}) | null = null;
|
|
26
|
+
|
|
27
|
+
function getDatabaseSync() {
|
|
28
|
+
if (CachedDatabaseSync) return CachedDatabaseSync;
|
|
29
|
+
const requireFn = typeof require === 'function'
|
|
30
|
+
? require
|
|
31
|
+
: createRequire(path.join(process.cwd(), '__adhdev_sqlite_loader__.js'));
|
|
32
|
+
const sqliteModule = requireFn(`node:${'sqlite'}`) as {
|
|
33
|
+
DatabaseSync: typeof CachedDatabaseSync;
|
|
34
|
+
};
|
|
35
|
+
CachedDatabaseSync = sqliteModule.DatabaseSync;
|
|
36
|
+
if (!CachedDatabaseSync) {
|
|
37
|
+
throw new Error('node:sqlite DatabaseSync unavailable');
|
|
38
|
+
}
|
|
39
|
+
return CachedDatabaseSync;
|
|
40
|
+
}
|
|
41
|
+
|
|
19
42
|
export class CliProviderInstance implements ProviderInstance {
|
|
20
43
|
readonly type: string;
|
|
21
44
|
readonly category = 'cli' as const;
|
|
@@ -34,6 +57,17 @@ export class CliProviderInstance implements ProviderInstance {
|
|
|
34
57
|
readonly instanceId: string;
|
|
35
58
|
|
|
36
59
|
private presentationMode: 'terminal' | 'chat';
|
|
60
|
+
private providerSessionId?: string;
|
|
61
|
+
private launchMode: 'new' | 'resume' | 'manual';
|
|
62
|
+
private readonly startedAt = Date.now();
|
|
63
|
+
private onProviderSessionResolved?: (info: {
|
|
64
|
+
instanceId: string;
|
|
65
|
+
providerType: string;
|
|
66
|
+
providerName: string;
|
|
67
|
+
workspace: string;
|
|
68
|
+
providerSessionId: string;
|
|
69
|
+
previousProviderSessionId?: string;
|
|
70
|
+
}) => void;
|
|
37
71
|
|
|
38
72
|
constructor(
|
|
39
73
|
private provider: ProviderModule,
|
|
@@ -41,10 +75,25 @@ export class CliProviderInstance implements ProviderInstance {
|
|
|
41
75
|
private cliArgs: string[] = [],
|
|
42
76
|
instanceId?: string,
|
|
43
77
|
transportFactory?: PtyTransportFactory,
|
|
78
|
+
options?: {
|
|
79
|
+
providerSessionId?: string;
|
|
80
|
+
launchMode?: 'new' | 'resume' | 'manual';
|
|
81
|
+
onProviderSessionResolved?: (info: {
|
|
82
|
+
instanceId: string;
|
|
83
|
+
providerType: string;
|
|
84
|
+
providerName: string;
|
|
85
|
+
workspace: string;
|
|
86
|
+
providerSessionId: string;
|
|
87
|
+
previousProviderSessionId?: string;
|
|
88
|
+
}) => void;
|
|
89
|
+
},
|
|
44
90
|
) {
|
|
45
91
|
this.type = provider.type;
|
|
46
92
|
this.instanceId = instanceId || crypto.randomUUID();
|
|
47
93
|
this.presentationMode = 'chat';
|
|
94
|
+
this.providerSessionId = options?.providerSessionId;
|
|
95
|
+
this.launchMode = options?.launchMode || 'new';
|
|
96
|
+
this.onProviderSessionResolved = options?.onProviderSessionResolved;
|
|
48
97
|
this.adapter = new ProviderCliAdapter(provider as any as CliProviderModule, workingDir, cliArgs, transportFactory);
|
|
49
98
|
this.monitor = new StatusMonitor();
|
|
50
99
|
this.historyWriter = new ChatHistoryWriter();
|
|
@@ -78,20 +127,71 @@ export class CliProviderInstance implements ProviderInstance {
|
|
|
78
127
|
|
|
79
128
|
// PTY spawn
|
|
80
129
|
await this.adapter.spawn();
|
|
130
|
+
if (this.providerSessionId && this.launchMode === 'resume') {
|
|
131
|
+
const resumedAt = Date.now();
|
|
132
|
+
this.historyWriter.appendSystemMarker(
|
|
133
|
+
this.type,
|
|
134
|
+
`Resumed saved session at ${this.formatMarkerTimestamp(resumedAt)}`,
|
|
135
|
+
{
|
|
136
|
+
instanceId: this.instanceId,
|
|
137
|
+
historySessionId: this.providerSessionId,
|
|
138
|
+
dedupKey: `resume:${this.providerSessionId}:${resumedAt}`,
|
|
139
|
+
receivedAt: resumedAt,
|
|
140
|
+
},
|
|
141
|
+
);
|
|
142
|
+
}
|
|
81
143
|
}
|
|
82
144
|
|
|
83
145
|
async onTick(): Promise<void> {
|
|
84
|
-
|
|
85
|
-
|
|
146
|
+
if (this.providerSessionId) return;
|
|
147
|
+
|
|
148
|
+
let probedSessionId: string | null = null;
|
|
149
|
+
if (this.type === 'opencode-cli') {
|
|
150
|
+
probedSessionId = this.probeOpenCodeSessionId();
|
|
151
|
+
} else if (this.type === 'codex-cli') {
|
|
152
|
+
probedSessionId = this.probeCodexSessionId();
|
|
153
|
+
} else if (this.type === 'goose-cli') {
|
|
154
|
+
probedSessionId = this.probeGooseSessionId();
|
|
155
|
+
}
|
|
156
|
+
|
|
157
|
+
if (probedSessionId) {
|
|
158
|
+
this.promoteProviderSessionId(probedSessionId);
|
|
159
|
+
}
|
|
86
160
|
}
|
|
87
161
|
|
|
88
162
|
getState(): ProviderState {
|
|
89
163
|
const adapterStatus = this.adapter.getStatus();
|
|
90
164
|
const parsedStatus = this.adapter.getScriptParsedStatus?.() || null;
|
|
165
|
+
const parsedProviderSessionId = typeof parsedStatus?.providerSessionId === 'string'
|
|
166
|
+
? parsedStatus.providerSessionId.trim()
|
|
167
|
+
: '';
|
|
168
|
+
if (parsedProviderSessionId) {
|
|
169
|
+
this.promoteProviderSessionId(parsedProviderSessionId);
|
|
170
|
+
}
|
|
91
171
|
const runtime = this.adapter.getRuntimeMetadata();
|
|
172
|
+
const parsedMessages = Array.isArray(parsedStatus?.messages) ? parsedStatus.messages : [];
|
|
92
173
|
|
|
93
174
|
const dirName = this.workingDir.split('/').filter(Boolean).pop() || 'session';
|
|
94
175
|
|
|
176
|
+
if (parsedMessages.length > 0) {
|
|
177
|
+
let messagesToSave = parsedMessages;
|
|
178
|
+
if ((parsedStatus?.status === 'generating' || parsedStatus?.status === 'long_generating')) {
|
|
179
|
+
const lastIdx = messagesToSave.length - 1;
|
|
180
|
+
if (lastIdx >= 0 && messagesToSave[lastIdx]?.role === 'assistant') {
|
|
181
|
+
messagesToSave = messagesToSave.slice(0, lastIdx);
|
|
182
|
+
}
|
|
183
|
+
}
|
|
184
|
+
if (messagesToSave.length > 0) {
|
|
185
|
+
this.historyWriter.appendNewMessages(
|
|
186
|
+
this.type,
|
|
187
|
+
messagesToSave,
|
|
188
|
+
parsedStatus?.title || dirName,
|
|
189
|
+
this.instanceId,
|
|
190
|
+
this.providerSessionId,
|
|
191
|
+
);
|
|
192
|
+
}
|
|
193
|
+
}
|
|
194
|
+
|
|
95
195
|
return {
|
|
96
196
|
type: this.type,
|
|
97
197
|
name: this.provider.name,
|
|
@@ -100,14 +200,15 @@ export class CliProviderInstance implements ProviderInstance {
|
|
|
100
200
|
mode: this.presentationMode,
|
|
101
201
|
activeChat: {
|
|
102
202
|
id: `${this.type}_${this.workingDir}`,
|
|
103
|
-
title: parsedStatus?.title ||
|
|
203
|
+
title: parsedStatus?.title || dirName,
|
|
104
204
|
status: parsedStatus?.status || adapterStatus.status,
|
|
105
|
-
messages:
|
|
205
|
+
messages: parsedMessages,
|
|
106
206
|
activeModal: parsedStatus?.activeModal ?? adapterStatus.activeModal,
|
|
107
207
|
inputContent: '',
|
|
108
208
|
},
|
|
109
209
|
workspace: this.workingDir,
|
|
110
210
|
instanceId: this.instanceId,
|
|
211
|
+
providerSessionId: this.providerSessionId,
|
|
111
212
|
lastUpdated: Date.now(),
|
|
112
213
|
settings: this.settings,
|
|
113
214
|
pendingEvents: this.flushEvents(),
|
|
@@ -274,4 +375,104 @@ export class CliProviderInstance implements ProviderInstance {
|
|
|
274
375
|
|
|
275
376
|
get cliType(): string { return this.type; }
|
|
276
377
|
get cliName(): string { return this.provider.name; }
|
|
378
|
+
|
|
379
|
+
private formatMarkerTimestamp(timestamp: number): string {
|
|
380
|
+
const date = new Date(timestamp);
|
|
381
|
+
const pad = (value: number) => String(value).padStart(2, '0');
|
|
382
|
+
return `${date.getFullYear()}-${pad(date.getMonth() + 1)}-${pad(date.getDate())} ${pad(date.getHours())}:${pad(date.getMinutes())}:${pad(date.getSeconds())}`;
|
|
383
|
+
}
|
|
384
|
+
|
|
385
|
+
private promoteProviderSessionId(sessionId: string): void {
|
|
386
|
+
const nextSessionId = String(sessionId || '').trim();
|
|
387
|
+
if (!nextSessionId || nextSessionId === this.providerSessionId) return;
|
|
388
|
+
|
|
389
|
+
const previousHistorySessionId = this.providerSessionId || this.instanceId;
|
|
390
|
+
const previousProviderSessionId = this.providerSessionId;
|
|
391
|
+
this.providerSessionId = nextSessionId;
|
|
392
|
+
this.historyWriter.promoteHistorySession(this.type, previousHistorySessionId, nextSessionId);
|
|
393
|
+
this.adapter.updateRuntimeMeta({ providerSessionId: nextSessionId });
|
|
394
|
+
this.onProviderSessionResolved?.({
|
|
395
|
+
instanceId: this.instanceId,
|
|
396
|
+
providerType: this.type,
|
|
397
|
+
providerName: this.provider.name,
|
|
398
|
+
workspace: this.workingDir,
|
|
399
|
+
providerSessionId: nextSessionId,
|
|
400
|
+
previousProviderSessionId,
|
|
401
|
+
});
|
|
402
|
+
LOG.info('CLI', `[${this.type}] discovered provider session id: ${nextSessionId}`);
|
|
403
|
+
}
|
|
404
|
+
|
|
405
|
+
private probeOpenCodeSessionId(): string | null {
|
|
406
|
+
const dbPath = path.join(os.homedir(), '.local', 'share', 'opencode', 'opencode.db');
|
|
407
|
+
if (!fs.existsSync(dbPath)) return null;
|
|
408
|
+
const minCreatedAt = Math.max(0, this.startedAt - 60_000);
|
|
409
|
+
const directories = this.getProbeDirectories();
|
|
410
|
+
const query = `select id from session where directory in (${this.buildSqlPlaceholderList(directories.length)}) and time_created >= ? and time_archived is null order by time_updated desc limit 1;`;
|
|
411
|
+
return this.querySqliteText(dbPath, query, [...directories, minCreatedAt]);
|
|
412
|
+
}
|
|
413
|
+
|
|
414
|
+
private probeCodexSessionId(): string | null {
|
|
415
|
+
const dbPath = path.join(os.homedir(), '.codex', 'state_5.sqlite');
|
|
416
|
+
if (!fs.existsSync(dbPath)) return null;
|
|
417
|
+
const minCreatedAt = Math.max(0, Math.floor((this.startedAt - 60_000) / 1000));
|
|
418
|
+
const directories = this.getProbeDirectories();
|
|
419
|
+
const query = `select id from threads where cwd in (${this.buildSqlPlaceholderList(directories.length)}) and created_at >= ? and archived = 0 order by created_at desc limit 1;`;
|
|
420
|
+
return this.querySqliteText(dbPath, query, [...directories, minCreatedAt]);
|
|
421
|
+
}
|
|
422
|
+
|
|
423
|
+
private probeGooseSessionId(): string | null {
|
|
424
|
+
const dbPath = path.join(os.homedir(), '.local', 'share', 'goose', 'sessions', 'sessions.db');
|
|
425
|
+
if (!fs.existsSync(dbPath)) return null;
|
|
426
|
+
const minCreatedAtIso = new Date(Math.max(0, this.startedAt - 60_000)).toISOString().slice(0, 19).replace('T', ' ');
|
|
427
|
+
const directories = this.getProbeDirectories();
|
|
428
|
+
const query = `select id from sessions where working_dir in (${this.buildSqlPlaceholderList(directories.length)}) and created_at >= ? order by updated_at desc limit 1;`;
|
|
429
|
+
try {
|
|
430
|
+
return this.querySqliteText(dbPath, query, [...directories, minCreatedAtIso]);
|
|
431
|
+
} catch {
|
|
432
|
+
return null;
|
|
433
|
+
}
|
|
434
|
+
}
|
|
435
|
+
|
|
436
|
+
private getProbeDirectories(): string[] {
|
|
437
|
+
const dirs = new Set<string>();
|
|
438
|
+
const addDir = (value: string | null | undefined) => {
|
|
439
|
+
const normalized = typeof value === 'string' ? value.trim() : '';
|
|
440
|
+
if (normalized) dirs.add(normalized);
|
|
441
|
+
};
|
|
442
|
+
|
|
443
|
+
addDir(this.workingDir);
|
|
444
|
+
try {
|
|
445
|
+
addDir(fs.realpathSync.native(this.workingDir));
|
|
446
|
+
} catch {
|
|
447
|
+
// noop
|
|
448
|
+
}
|
|
449
|
+
|
|
450
|
+
return Array.from(dirs);
|
|
451
|
+
}
|
|
452
|
+
|
|
453
|
+
private buildSqlPlaceholderList(count: number): string {
|
|
454
|
+
return Array.from({ length: count }, () => '?').join(', ');
|
|
455
|
+
}
|
|
456
|
+
|
|
457
|
+
private querySqliteText(dbPath: string, query: string, params: Array<string | number>): string | null {
|
|
458
|
+
let db: {
|
|
459
|
+
prepare(sql: string): { get(...values: Array<string | number>): unknown };
|
|
460
|
+
close(): void;
|
|
461
|
+
} | null = null;
|
|
462
|
+
try {
|
|
463
|
+
const DatabaseSync = getDatabaseSync();
|
|
464
|
+
db = new DatabaseSync(dbPath, { readOnly: true });
|
|
465
|
+
const row = db.prepare(query).get(...params) as { id?: unknown } | undefined;
|
|
466
|
+
const sessionId = typeof row?.id === 'string' ? row.id.trim() : '';
|
|
467
|
+
return sessionId || null;
|
|
468
|
+
} catch {
|
|
469
|
+
return null;
|
|
470
|
+
} finally {
|
|
471
|
+
try {
|
|
472
|
+
db?.close();
|
|
473
|
+
} catch {
|
|
474
|
+
// noop
|
|
475
|
+
}
|
|
476
|
+
}
|
|
477
|
+
}
|
|
277
478
|
}
|
|
@@ -399,6 +399,9 @@ export interface ProviderResumeCapability {
|
|
|
399
399
|
stopCommand?: string;
|
|
400
400
|
shutdownGraceMs?: number;
|
|
401
401
|
resumeArgs?: string[];
|
|
402
|
+
resumeSessionArgs?: string[];
|
|
403
|
+
newSessionArgs?: string[];
|
|
404
|
+
sessionIdFormat?: 'uuid' | 'string';
|
|
402
405
|
}
|
|
403
406
|
|
|
404
407
|
// ─── ACP Auth Types ─────────────────────────────────
|
|
@@ -64,6 +64,7 @@ export async function listHostedCliRuntimes(endpoint: SessionHostEndpoint): Prom
|
|
|
64
64
|
cliType: record.providerType,
|
|
65
65
|
workspace: record.workspace,
|
|
66
66
|
cliArgs: Array.isArray(record.meta?.cliArgs) ? (record.meta.cliArgs as string[]) : [],
|
|
67
|
+
providerSessionId: typeof record.meta?.providerSessionId === 'string' ? String(record.meta.providerSessionId) : undefined,
|
|
67
68
|
}));
|
|
68
69
|
} finally {
|
|
69
70
|
await client.close().catch(() => {});
|
package/src/shared-types.d.ts
CHANGED
|
@@ -41,6 +41,7 @@ export interface SessionEntry {
|
|
|
41
41
|
parentId: string | null;
|
|
42
42
|
providerType: string;
|
|
43
43
|
providerName: string;
|
|
44
|
+
providerSessionId?: string;
|
|
44
45
|
kind: SessionKind;
|
|
45
46
|
transport: SessionTransport;
|
|
46
47
|
status: 'idle' | 'generating' | 'waiting_approval' | 'error' | 'stopped' | 'starting' | 'panel_hidden' | 'not_monitored' | 'disconnected';
|
|
@@ -152,6 +153,7 @@ export interface RecentLaunchEntry {
|
|
|
152
153
|
providerType: string;
|
|
153
154
|
providerName: string;
|
|
154
155
|
kind: 'ide' | 'cli' | 'acp';
|
|
156
|
+
providerSessionId?: string;
|
|
155
157
|
title?: string;
|
|
156
158
|
workspace?: string | null;
|
|
157
159
|
currentModel?: string;
|
package/src/shared-types.ts
CHANGED
|
@@ -94,6 +94,7 @@ export interface SessionEntry {
|
|
|
94
94
|
parentId: string | null;
|
|
95
95
|
providerType: string;
|
|
96
96
|
providerName: string;
|
|
97
|
+
providerSessionId?: string;
|
|
97
98
|
kind: SessionKind;
|
|
98
99
|
transport: SessionTransport;
|
|
99
100
|
status: SessionStatus;
|
|
@@ -221,6 +222,7 @@ export interface RecentLaunchEntry {
|
|
|
221
222
|
providerType: string;
|
|
222
223
|
providerName: string;
|
|
223
224
|
kind: 'ide' | 'cli' | 'acp';
|
|
225
|
+
providerSessionId?: string;
|
|
224
226
|
title?: string;
|
|
225
227
|
workspace?: string | null;
|
|
226
228
|
currentModel?: string;
|
package/src/status/builders.ts
CHANGED
|
@@ -259,6 +259,7 @@ function buildCliSession(state: CliProviderState): SessionEntry {
|
|
|
259
259
|
parentId: null,
|
|
260
260
|
providerType: state.type,
|
|
261
261
|
providerName: state.name,
|
|
262
|
+
providerSessionId: state.providerSessionId,
|
|
262
263
|
kind: 'agent',
|
|
263
264
|
transport: 'pty',
|
|
264
265
|
status: normalizeManagedStatus(activeChat?.status || state.status, {
|
package/src/status/snapshot.ts
CHANGED
|
@@ -183,6 +183,7 @@ function buildRecentLaunches(
|
|
|
183
183
|
providerType: item.providerType,
|
|
184
184
|
providerName: item.providerName,
|
|
185
185
|
kind: item.kind,
|
|
186
|
+
providerSessionId: item.providerSessionId,
|
|
186
187
|
title: item.title || item.providerName,
|
|
187
188
|
workspace: item.workspace,
|
|
188
189
|
currentModel: item.currentModel,
|