@adhdev/daemon-core 0.8.12 → 0.8.14

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.
@@ -148,12 +148,32 @@ export class CliProviderInstance implements ProviderInstance {
148
148
  if (this.providerSessionId) return;
149
149
 
150
150
  let probedSessionId: string | null = null;
151
- if (this.type === 'opencode-cli') {
152
- probedSessionId = this.probeOpenCodeSessionId();
153
- } else if (this.type === 'codex-cli') {
154
- probedSessionId = this.probeCodexSessionId();
155
- } else if (this.type === 'goose-cli') {
156
- probedSessionId = this.probeGooseSessionId();
151
+
152
+ // Prefer declarative probe from provider.json schema
153
+ const probeConfig = this.provider.sessionProbe;
154
+ if (probeConfig) {
155
+ probedSessionId = this.probeSessionIdFromConfig(probeConfig);
156
+ } else {
157
+ // Legacy hardcoded probes (backward compat until providers migrate)
158
+ if (this.type === 'opencode-cli') {
159
+ probedSessionId = this.probeSessionIdFromConfig({
160
+ dbPath: '~/.local/share/opencode/opencode.db',
161
+ query: 'select id from session where directory in ({dirs}) and time_created >= ? and time_archived is null order by time_updated desc limit 1',
162
+ timestampFormat: 'unix_ms',
163
+ });
164
+ } else if (this.type === 'codex-cli') {
165
+ probedSessionId = this.probeSessionIdFromConfig({
166
+ dbPath: '~/.codex/state_5.sqlite',
167
+ query: 'select id from threads where cwd in ({dirs}) and created_at >= ? and archived = 0 order by created_at desc limit 1',
168
+ timestampFormat: 'unix_s',
169
+ });
170
+ } else if (this.type === 'goose-cli') {
171
+ probedSessionId = this.probeSessionIdFromConfig({
172
+ dbPath: '~/.local/share/goose/sessions/sessions.db',
173
+ query: 'select id from sessions where working_dir in ({dirs}) and created_at >= ? order by updated_at desc limit 1',
174
+ timestampFormat: 'iso',
175
+ });
176
+ }
157
177
  }
158
178
 
159
179
  if (probedSessionId) {
@@ -161,6 +181,42 @@ export class CliProviderInstance implements ProviderInstance {
161
181
  }
162
182
  }
163
183
 
184
+ /**
185
+ * Generic session ID probe using declarative ProviderSessionProbe config.
186
+ * Replaces the previously duplicated probeOpenCode/Codex/Goose functions.
187
+ */
188
+ private probeSessionIdFromConfig(probe: {
189
+ dbPath: string;
190
+ query: string;
191
+ timestampFormat?: 'unix_ms' | 'unix_s' | 'iso';
192
+ }): string | null {
193
+ const resolvedDbPath = probe.dbPath.replace(/^~/, os.homedir());
194
+ if (!fs.existsSync(resolvedDbPath)) return null;
195
+
196
+ const directories = this.getProbeDirectories();
197
+ const minCreatedAt = Math.max(0, this.startedAt - 60_000);
198
+ const tsFormat = probe.timestampFormat || 'unix_ms';
199
+
200
+ let timestampParam: string | number;
201
+ if (tsFormat === 'unix_s') {
202
+ timestampParam = Math.floor(minCreatedAt / 1000);
203
+ } else if (tsFormat === 'iso') {
204
+ timestampParam = new Date(minCreatedAt).toISOString().slice(0, 19).replace('T', ' ');
205
+ } else {
206
+ timestampParam = minCreatedAt;
207
+ }
208
+
209
+ // Build query: replace {dirs} with SQL placeholder list
210
+ const placeholders = this.buildSqlPlaceholderList(directories.length);
211
+ const query = probe.query.replace('{dirs}', placeholders);
212
+
213
+ try {
214
+ return this.querySqliteText(resolvedDbPath, query, [...directories, timestampParam]);
215
+ } catch {
216
+ return null;
217
+ }
218
+ }
219
+
164
220
  getState(): ProviderState {
165
221
  const adapterStatus = this.adapter.getStatus();
166
222
  const parsedStatus = this.adapter.getScriptParsedStatus?.() || null;
@@ -480,36 +536,6 @@ export class CliProviderInstance implements ProviderInstance {
480
536
  LOG.info('CLI', `[${this.type}] discovered provider session id: ${nextSessionId}`);
481
537
  }
482
538
 
483
- private probeOpenCodeSessionId(): string | null {
484
- const dbPath = path.join(os.homedir(), '.local', 'share', 'opencode', 'opencode.db');
485
- if (!fs.existsSync(dbPath)) return null;
486
- const minCreatedAt = Math.max(0, this.startedAt - 60_000);
487
- const directories = this.getProbeDirectories();
488
- 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;`;
489
- return this.querySqliteText(dbPath, query, [...directories, minCreatedAt]);
490
- }
491
-
492
- private probeCodexSessionId(): string | null {
493
- const dbPath = path.join(os.homedir(), '.codex', 'state_5.sqlite');
494
- if (!fs.existsSync(dbPath)) return null;
495
- const minCreatedAt = Math.max(0, Math.floor((this.startedAt - 60_000) / 1000));
496
- const directories = this.getProbeDirectories();
497
- 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;`;
498
- return this.querySqliteText(dbPath, query, [...directories, minCreatedAt]);
499
- }
500
-
501
- private probeGooseSessionId(): string | null {
502
- const dbPath = path.join(os.homedir(), '.local', 'share', 'goose', 'sessions', 'sessions.db');
503
- if (!fs.existsSync(dbPath)) return null;
504
- const minCreatedAtIso = new Date(Math.max(0, this.startedAt - 60_000)).toISOString().slice(0, 19).replace('T', ' ');
505
- const directories = this.getProbeDirectories();
506
- const query = `select id from sessions where working_dir in (${this.buildSqlPlaceholderList(directories.length)}) and created_at >= ? order by updated_at desc limit 1;`;
507
- try {
508
- return this.querySqliteText(dbPath, query, [...directories, minCreatedAtIso]);
509
- } catch {
510
- return null;
511
- }
512
- }
513
539
 
514
540
  private getProbeDirectories(): string[] {
515
541
  const dirs = new Set<string>();
@@ -325,6 +325,8 @@ export interface ProviderModule {
325
325
  };
326
326
  cleanOutput?: (raw: string, lastUserInput?: string) => string;
327
327
  resume?: ProviderResumeCapability;
328
+ /** Session ID probe config — auto-discovers provider session ID from local SQLite DB */
329
+ sessionProbe?: ProviderSessionProbe;
328
330
 
329
331
  // ─── CDP scripts (ide/extension category) ───
330
332
  scripts?: ProviderScripts;
@@ -398,12 +400,49 @@ export interface ProviderResumeCapability {
398
400
  stopStrategy?: 'command' | 'ctrl_c';
399
401
  stopCommand?: string;
400
402
  shutdownGraceMs?: number;
403
+ /** Delay (ms) between Ctrl+C interrupt and stop command (default 500ms) */
404
+ interruptGraceMs?: number;
401
405
  resumeArgs?: string[];
402
406
  resumeSessionArgs?: string[];
403
407
  newSessionArgs?: string[];
404
408
  sessionIdFormat?: 'uuid' | 'string';
405
409
  }
406
410
 
411
+ /**
412
+ * Declarative session ID probe config for CLI providers.
413
+ * Instead of hardcoded probe functions, providers declare their SQLite schema.
414
+ *
415
+ * Example (OpenCode):
416
+ * ```
417
+ * sessionProbe: {
418
+ * dbPath: '~/.local/share/opencode/opencode.db',
419
+ * query: 'SELECT id FROM session WHERE directory IN ({dirs}) AND time_created >= ? AND time_archived IS NULL ORDER BY time_updated DESC LIMIT 1',
420
+ * timestampFormat: 'unix_ms',
421
+ * }
422
+ * ```
423
+ */
424
+ export interface ProviderSessionProbe {
425
+ /**
426
+ * Path to SQLite database. Supports ~ for home directory.
427
+ * Supports platform-specific paths via {platform} placeholder.
428
+ */
429
+ dbPath: string;
430
+ /**
431
+ * SQL query to find the session ID.
432
+ * Use {dirs} placeholder for the directory IN-clause parameters.
433
+ * The query must SELECT a column named 'id'.
434
+ * A '?' placeholder after {dirs} receives the min-created-at timestamp.
435
+ */
436
+ query: string;
437
+ /**
438
+ * How the provider stores timestamps.
439
+ * - 'unix_ms': milliseconds since epoch (default)
440
+ * - 'unix_s': seconds since epoch
441
+ * - 'iso': ISO 8601 string (YYYY-MM-DD HH:MM:SS)
442
+ */
443
+ timestampFormat?: 'unix_ms' | 'unix_s' | 'iso';
444
+ }
445
+
407
446
  // ─── ACP Auth Types ─────────────────────────────────
408
447
 
409
448
  /** ACP auth method — based on ACP official spec */