@adhdev/daemon-core 0.8.57 → 0.8.59

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.
Files changed (75) hide show
  1. package/dist/agent-stream/types.d.ts +3 -4
  2. package/dist/boot/daemon-lifecycle.d.ts +1 -0
  3. package/dist/commands/handler.d.ts +1 -0
  4. package/dist/commands/router.d.ts +1 -0
  5. package/dist/commands/stream-commands.d.ts +3 -0
  6. package/dist/config/chat-history.d.ts +3 -0
  7. package/dist/config/config.d.ts +3 -0
  8. package/dist/config/provider-source-config.d.ts +23 -0
  9. package/dist/config/recent-activity.d.ts +2 -1
  10. package/dist/config/saved-sessions.d.ts +2 -1
  11. package/dist/daemon/dev-server-types.d.ts +1 -0
  12. package/dist/daemon/dev-server.d.ts +4 -0
  13. package/dist/index.d.ts +3 -1
  14. package/dist/index.js +876 -337
  15. package/dist/index.js.map +1 -1
  16. package/dist/index.mjs +875 -337
  17. package/dist/index.mjs.map +1 -1
  18. package/dist/providers/acp-provider-instance.d.ts +8 -2
  19. package/dist/providers/cli-provider-instance.d.ts +10 -0
  20. package/dist/providers/contracts.d.ts +4 -2
  21. package/dist/providers/extension-provider-instance.d.ts +1 -2
  22. package/dist/providers/provider-instance.d.ts +3 -4
  23. package/dist/providers/provider-loader.d.ts +14 -1
  24. package/dist/providers/provider-patch-state.d.ts +23 -0
  25. package/dist/providers/summary-metadata.d.ts +22 -0
  26. package/dist/shared-types.d.ts +15 -9
  27. package/dist/status/snapshot.d.ts +16 -1
  28. package/node_modules/@adhdev/session-host-core/package.json +1 -1
  29. package/package.json +1 -1
  30. package/src/agent-stream/forward.ts +1 -2
  31. package/src/agent-stream/manager.ts +2 -1
  32. package/src/agent-stream/provider-adapter.ts +7 -3
  33. package/src/agent-stream/types.d.ts +3 -4
  34. package/src/agent-stream/types.ts +3 -4
  35. package/src/boot/daemon-lifecycle.ts +13 -3
  36. package/src/commands/cli-manager.ts +10 -5
  37. package/src/commands/handler.ts +3 -0
  38. package/src/commands/router.ts +160 -26
  39. package/src/commands/stream-commands.ts +60 -2
  40. package/src/config/chat-history.ts +39 -0
  41. package/src/config/config.d.ts +3 -0
  42. package/src/config/config.ts +19 -3
  43. package/src/config/provider-source-config.ts +42 -0
  44. package/src/config/recent-activity.d.ts +2 -1
  45. package/src/config/recent-activity.ts +12 -1
  46. package/src/config/saved-sessions.d.ts +2 -1
  47. package/src/config/saved-sessions.ts +12 -2
  48. package/src/daemon/dev-auto-implement.ts +1 -14
  49. package/src/daemon/dev-cli-debug.ts +0 -1
  50. package/src/daemon/dev-server-types.ts +1 -0
  51. package/src/daemon/dev-server.ts +46 -21
  52. package/src/daemon/scaffold-template.ts +8 -1
  53. package/src/index.d.ts +1 -1
  54. package/src/index.ts +4 -0
  55. package/src/providers/acp-provider-instance.d.ts +8 -2
  56. package/src/providers/acp-provider-instance.ts +80 -23
  57. package/src/providers/cli-provider-instance.ts +42 -22
  58. package/src/providers/contracts.d.ts +3 -2
  59. package/src/providers/contracts.ts +7 -4
  60. package/src/providers/control-effects.ts +3 -4
  61. package/src/providers/extension-provider-instance.d.ts +1 -2
  62. package/src/providers/extension-provider-instance.ts +26 -14
  63. package/src/providers/ide-provider-instance.ts +28 -15
  64. package/src/providers/provider-instance.d.ts +3 -4
  65. package/src/providers/provider-instance.ts +6 -7
  66. package/src/providers/provider-loader.d.ts +4 -1
  67. package/src/providers/provider-loader.ts +61 -23
  68. package/src/providers/provider-patch-state.ts +91 -0
  69. package/src/providers/provider-schema.ts +3 -0
  70. package/src/providers/summary-metadata.ts +118 -0
  71. package/src/shared-types.d.ts +15 -9
  72. package/src/shared-types.ts +17 -9
  73. package/src/status/builders.ts +18 -13
  74. package/src/status/reporter.ts +2 -4
  75. package/src/status/snapshot.ts +60 -2
@@ -31,6 +31,7 @@ import { logCommand } from '../logging/command-log.js';
31
31
  import type { CommandLogEntry } from '../logging/command-log.js';
32
32
  import { getRecentLogs, LOG_PATH } from '../logging/logger.js';
33
33
  import { createInteractionId, getRecentDebugTrace, recordDebugTrace } from '../logging/debug-trace.js';
34
+ import { getSessionHostSurfaceKind, partitionSessionHostRecords } from '../session-host/runtime-surface.js';
34
35
  import { buildSessionEntries } from '../status/builders.js';
35
36
  import { buildMachineInfo, buildStatusSnapshot } from '../status/snapshot.js';
36
37
  import { getSessionCompletionMarker } from '../status/snapshot.js';
@@ -137,6 +138,58 @@ function toHostedCliRuntimeDescriptor(record: any): HostedCliRuntimeDescriptor |
137
138
  };
138
139
  }
139
140
 
141
+ function getWriteConflictOwnerClientId(error: unknown): string | undefined {
142
+ const message = typeof error === 'string'
143
+ ? error
144
+ : error instanceof Error
145
+ ? error.message
146
+ : '';
147
+ const match = /^Write owned by\s+(.+)$/.exec(message.trim());
148
+ return match?.[1]?.trim() || undefined;
149
+ }
150
+
151
+ function summarizeSessionHostRecord(result: unknown): Record<string, unknown> {
152
+ if (!result || typeof result !== 'object') return {};
153
+ const record = result as Record<string, any>;
154
+ return {
155
+ runtimeKey: typeof record.runtimeKey === 'string' ? record.runtimeKey : undefined,
156
+ lifecycle: typeof record.lifecycle === 'string' ? record.lifecycle : undefined,
157
+ surfaceKind: getSessionHostSurfaceKind(record as any),
158
+ attachedClientCount: Array.isArray(record.attachedClients) ? record.attachedClients.length : undefined,
159
+ hasWriteOwner: !!record.writeOwner,
160
+ writeOwnerClientId: typeof record.writeOwner?.clientId === 'string' ? record.writeOwner.clientId : undefined,
161
+ };
162
+ }
163
+
164
+ function summarizeSessionHostRecords(result: unknown): Record<string, unknown> {
165
+ const records = Array.isArray(result) ? result : [];
166
+ const groups = partitionSessionHostRecords(records as any[]);
167
+ return {
168
+ sessionCount: records.length,
169
+ liveRuntimeCount: groups.liveRuntimes.length,
170
+ recoverySnapshotCount: groups.recoverySnapshots.length,
171
+ inactiveRecordCount: groups.inactiveRecords.length,
172
+ };
173
+ }
174
+
175
+ function summarizeSessionHostDiagnostics(result: unknown): Record<string, unknown> {
176
+ const diagnostics = result && typeof result === 'object' ? result as Record<string, any> : {};
177
+ const sessions = Array.isArray(diagnostics.sessions) ? diagnostics.sessions : [];
178
+ return {
179
+ runtimeCount: typeof diagnostics.runtimeCount === 'number' ? diagnostics.runtimeCount : undefined,
180
+ ...summarizeSessionHostRecords(sessions),
181
+ };
182
+ }
183
+
184
+ function summarizeSessionHostPruneResult(result: unknown): Record<string, unknown> {
185
+ const value = result && typeof result === 'object' ? result as Record<string, any> : {};
186
+ return {
187
+ duplicateGroupCount: typeof value.duplicateGroupCount === 'number' ? value.duplicateGroupCount : undefined,
188
+ prunedCount: Array.isArray(value.prunedSessionIds) ? value.prunedSessionIds.length : undefined,
189
+ keptCount: Array.isArray(value.keptSessionIds) ? value.keptSessionIds.length : undefined,
190
+ };
191
+ }
192
+
140
193
  export class DaemonCommandRouter {
141
194
  private deps: CommandRouterDeps;
142
195
 
@@ -144,6 +197,64 @@ export class DaemonCommandRouter {
144
197
  this.deps = deps;
145
198
  }
146
199
 
200
+ private async traceSessionHostAction<T>(
201
+ action: string,
202
+ args: any,
203
+ run: () => Promise<T>,
204
+ summarizeResult?: (result: T) => Record<string, unknown>,
205
+ ): Promise<T> {
206
+ const interactionId = typeof args?._interactionId === 'string' ? args._interactionId : undefined;
207
+ const sessionId = typeof args?.sessionId === 'string' ? args.sessionId : undefined;
208
+ const requestedPayload: Record<string, unknown> = { action };
209
+ if (sessionId) requestedPayload.sessionId = sessionId;
210
+ if (typeof args?.clientId === 'string') requestedPayload.clientId = args.clientId;
211
+ if (typeof args?.signal === 'string') requestedPayload.signal = args.signal;
212
+ if (typeof args?.providerType === 'string') requestedPayload.providerType = args.providerType;
213
+ if (typeof args?.workspace === 'string') requestedPayload.workspace = args.workspace;
214
+ if (typeof args?.dryRun === 'boolean') requestedPayload.dryRun = args.dryRun;
215
+
216
+ recordDebugTrace({
217
+ interactionId,
218
+ category: 'session_host',
219
+ stage: 'action_requested',
220
+ level: 'info',
221
+ sessionId,
222
+ payload: requestedPayload,
223
+ });
224
+
225
+ try {
226
+ const result = await run();
227
+ recordDebugTrace({
228
+ interactionId,
229
+ category: 'session_host',
230
+ stage: 'action_result',
231
+ level: 'info',
232
+ sessionId,
233
+ payload: {
234
+ ...requestedPayload,
235
+ success: true,
236
+ ...(summarizeResult ? summarizeResult(result) : {}),
237
+ },
238
+ });
239
+ return result;
240
+ } catch (error: any) {
241
+ recordDebugTrace({
242
+ interactionId,
243
+ category: 'session_host',
244
+ stage: 'action_failed',
245
+ level: 'error',
246
+ sessionId,
247
+ payload: {
248
+ ...requestedPayload,
249
+ error: error?.message || String(error),
250
+ failureKind: getWriteConflictOwnerClientId(error) ? 'write_conflict' : 'request_failed',
251
+ conflictOwnerClientId: getWriteConflictOwnerClientId(error),
252
+ },
253
+ });
254
+ throw error;
255
+ }
256
+ }
257
+
147
258
  /**
148
259
  * Unified command routing.
149
260
  * Returns result for all commands:
@@ -270,16 +381,20 @@ export class DaemonCommandRouter {
270
381
 
271
382
  case 'session_host_get_diagnostics': {
272
383
  if (!this.deps.sessionHostControl) return { success: false, error: 'Session host control unavailable' };
273
- const diagnostics = await this.deps.sessionHostControl.getDiagnostics({
384
+ const diagnostics = await this.traceSessionHostAction('session_host_get_diagnostics', args, () => this.deps.sessionHostControl!.getDiagnostics({
274
385
  includeSessions: args?.includeSessions !== false,
275
386
  limit: Number(args?.limit) || undefined,
276
- });
387
+ }), (result) => ({
388
+ includeSessions: args?.includeSessions !== false,
389
+ limit: Number(args?.limit) || undefined,
390
+ ...summarizeSessionHostDiagnostics(result),
391
+ }));
277
392
  return { success: true, diagnostics };
278
393
  }
279
394
 
280
395
  case 'session_host_list_sessions': {
281
396
  if (!this.deps.sessionHostControl) return { success: false, error: 'Session host control unavailable' };
282
- const sessions = await this.deps.sessionHostControl.listSessions();
397
+ const sessions = await this.traceSessionHostAction('session_host_list_sessions', args, () => this.deps.sessionHostControl!.listSessions(), (records) => summarizeSessionHostRecords(records));
283
398
  return { success: true, sessions };
284
399
  }
285
400
 
@@ -287,7 +402,7 @@ export class DaemonCommandRouter {
287
402
  if (!this.deps.sessionHostControl) return { success: false, error: 'Session host control unavailable' };
288
403
  const sessionId = typeof args?.sessionId === 'string' ? args.sessionId : '';
289
404
  if (!sessionId) return { success: false, error: 'sessionId required' };
290
- const record = await this.deps.sessionHostControl.stopSession(sessionId);
405
+ const record = await this.traceSessionHostAction('session_host_stop_session', args, () => this.deps.sessionHostControl!.stopSession(sessionId), (result) => summarizeSessionHostRecord(result));
291
406
  return { success: true, record };
292
407
  }
293
408
 
@@ -295,11 +410,17 @@ export class DaemonCommandRouter {
295
410
  if (!this.deps.sessionHostControl) return { success: false, error: 'Session host control unavailable' };
296
411
  const sessionId = typeof args?.sessionId === 'string' ? args.sessionId : '';
297
412
  if (!sessionId) return { success: false, error: 'sessionId required' };
298
- const record = await this.deps.sessionHostControl.resumeSession(sessionId);
299
- const hosted = toHostedCliRuntimeDescriptor(record);
300
- if (hosted) {
301
- await this.deps.cliManager.restoreHostedSessions([hosted]);
302
- }
413
+ const record = await this.traceSessionHostAction('session_host_resume_session', args, async () => {
414
+ const nextRecord = await this.deps.sessionHostControl!.resumeSession(sessionId);
415
+ const hosted = toHostedCliRuntimeDescriptor(nextRecord);
416
+ if (hosted) {
417
+ await this.deps.cliManager.restoreHostedSessions([hosted]);
418
+ }
419
+ return nextRecord;
420
+ }, (result) => ({
421
+ ...summarizeSessionHostRecord(result),
422
+ restoredHostedSession: !!toHostedCliRuntimeDescriptor(result),
423
+ }));
303
424
  return { success: true, record };
304
425
  }
305
426
 
@@ -307,11 +428,17 @@ export class DaemonCommandRouter {
307
428
  if (!this.deps.sessionHostControl) return { success: false, error: 'Session host control unavailable' };
308
429
  const sessionId = typeof args?.sessionId === 'string' ? args.sessionId : '';
309
430
  if (!sessionId) return { success: false, error: 'sessionId required' };
310
- const record = await this.deps.sessionHostControl.restartSession(sessionId);
311
- const hosted = toHostedCliRuntimeDescriptor(record);
312
- if (hosted) {
313
- await this.deps.cliManager.restoreHostedSessions([hosted]);
314
- }
431
+ const record = await this.traceSessionHostAction('session_host_restart_session', args, async () => {
432
+ const nextRecord = await this.deps.sessionHostControl!.restartSession(sessionId);
433
+ const hosted = toHostedCliRuntimeDescriptor(nextRecord);
434
+ if (hosted) {
435
+ await this.deps.cliManager.restoreHostedSessions([hosted]);
436
+ }
437
+ return nextRecord;
438
+ }, (result) => ({
439
+ ...summarizeSessionHostRecord(result),
440
+ restoredHostedSession: !!toHostedCliRuntimeDescriptor(result),
441
+ }));
315
442
  return { success: true, record };
316
443
  }
317
444
 
@@ -321,7 +448,7 @@ export class DaemonCommandRouter {
321
448
  const signal = typeof args?.signal === 'string' ? args.signal : '';
322
449
  if (!sessionId) return { success: false, error: 'sessionId required' };
323
450
  if (!signal) return { success: false, error: 'signal required' };
324
- const record = await this.deps.sessionHostControl.sendSignal(sessionId, signal);
451
+ const record = await this.traceSessionHostAction('session_host_send_signal', args, () => this.deps.sessionHostControl!.sendSignal(sessionId, signal), (result) => summarizeSessionHostRecord(result));
325
452
  return { success: true, record };
326
453
  }
327
454
 
@@ -331,17 +458,17 @@ export class DaemonCommandRouter {
331
458
  const clientId = typeof args?.clientId === 'string' ? args.clientId : '';
332
459
  if (!sessionId) return { success: false, error: 'sessionId required' };
333
460
  if (!clientId) return { success: false, error: 'clientId required' };
334
- const record = await this.deps.sessionHostControl.forceDetachClient(sessionId, clientId);
461
+ const record = await this.traceSessionHostAction('session_host_force_detach_client', args, () => this.deps.sessionHostControl!.forceDetachClient(sessionId, clientId), (result) => summarizeSessionHostRecord(result));
335
462
  return { success: true, record };
336
463
  }
337
464
 
338
465
  case 'session_host_prune_duplicate_sessions': {
339
466
  if (!this.deps.sessionHostControl) return { success: false, error: 'Session host control unavailable' };
340
- const result = await this.deps.sessionHostControl.pruneDuplicateSessions({
467
+ const result = await this.traceSessionHostAction('session_host_prune_duplicate_sessions', args, () => this.deps.sessionHostControl!.pruneDuplicateSessions({
341
468
  providerType: typeof args?.providerType === 'string' ? args.providerType : undefined,
342
469
  workspace: typeof args?.workspace === 'string' ? args.workspace : undefined,
343
470
  dryRun: args?.dryRun === true,
344
- });
471
+ }), (value) => summarizeSessionHostPruneResult(value));
345
472
  return { success: true, result };
346
473
  }
347
474
 
@@ -352,12 +479,15 @@ export class DaemonCommandRouter {
352
479
  const ownerType = args?.ownerType === 'agent' ? 'agent' : 'user';
353
480
  if (!sessionId) return { success: false, error: 'sessionId required' };
354
481
  if (!clientId) return { success: false, error: 'clientId required' };
355
- const record = await this.deps.sessionHostControl.acquireWrite({
482
+ const record = await this.traceSessionHostAction('session_host_acquire_write', args, () => this.deps.sessionHostControl!.acquireWrite({
356
483
  sessionId,
357
484
  clientId,
358
485
  ownerType,
359
486
  force: args?.force !== false,
360
- });
487
+ }), (result) => ({
488
+ ...summarizeSessionHostRecord(result),
489
+ ownerType,
490
+ }));
361
491
  return { success: true, record };
362
492
  }
363
493
 
@@ -367,7 +497,10 @@ export class DaemonCommandRouter {
367
497
  const clientId = typeof args?.clientId === 'string' ? args.clientId : '';
368
498
  if (!sessionId) return { success: false, error: 'sessionId required' };
369
499
  if (!clientId) return { success: false, error: 'clientId required' };
370
- const record = await this.deps.sessionHostControl.releaseWrite({ sessionId, clientId });
500
+ const record = await this.traceSessionHostAction('session_host_release_write', args, () => this.deps.sessionHostControl!.releaseWrite({
501
+ sessionId,
502
+ clientId,
503
+ }), (result) => summarizeSessionHostRecord(result));
371
504
  return { success: true, record };
372
505
  }
373
506
 
@@ -382,8 +515,9 @@ export class DaemonCommandRouter {
382
515
  return { success: false, error: 'providerType required' };
383
516
  }
384
517
 
385
- const offset = Math.max(0, Number(args?.offset) || 0);
386
- const limit = Math.max(1, Math.min(100, Number(args?.limit) || 30));
518
+ const wantsAll = args?.all === true;
519
+ const offset = wantsAll ? 0 : Math.max(0, Number(args?.offset) || 0);
520
+ const limit = wantsAll ? Number.MAX_SAFE_INTEGER : Math.max(1, Math.min(100, Number(args?.limit) || 30));
387
521
  const { sessions: historySessions, hasMore } = listSavedHistorySessions(providerType, { offset, limit });
388
522
  const state = loadState();
389
523
  const savedSessions = getSavedProviderSessions(state, { providerType, kind });
@@ -406,13 +540,13 @@ export class DaemonCommandRouter {
406
540
  providerName: saved?.providerName || recent?.providerName || providerType,
407
541
  kind: saved?.kind || recent?.kind || kind,
408
542
  title: saved?.title || recent?.title || session.sessionTitle || session.preview || providerType,
409
- workspace: saved?.workspace || recent?.workspace,
410
- currentModel: saved?.currentModel || recent?.currentModel,
543
+ workspace: saved?.workspace || recent?.workspace || session.workspace,
544
+ summaryMetadata: saved?.summaryMetadata || recent?.summaryMetadata,
411
545
  preview: session.preview,
412
546
  messageCount: session.messageCount,
413
547
  firstMessageAt: session.firstMessageAt,
414
548
  lastMessageAt: session.lastMessageAt,
415
- canResume: !!(saved?.workspace || recent?.workspace) && canResumeById,
549
+ canResume: !!(saved?.workspace || recent?.workspace || session.workspace) && canResumeById,
416
550
  };
417
551
  }),
418
552
  hasMore,
@@ -6,6 +6,8 @@
6
6
  import type { CommandResult, CommandHelpers } from './handler.js';
7
7
  import type { ProviderLoader } from '../providers/provider-loader.js';
8
8
  import type { ProviderInstance } from '../providers/provider-instance.js';
9
+ import { loadConfig, saveConfig } from '../config/config.js';
10
+ import { parseProviderSourceConfigUpdate } from '../config/provider-source-config.js';
9
11
  import { getCliScriptCommand, parseCliScriptResult } from '../providers/cli-script-results.js';
10
12
  import {
11
13
  normalizeControlInvokeResult,
@@ -107,10 +109,66 @@ export async function handleSetProviderSetting(h: CommandHelpers, args: any): Pr
107
109
  return { success: false, error: `Failed to set ${providerType}.${key} — invalid key, value, or not a public setting` };
108
110
  }
109
111
 
112
+ export function handleGetProviderSourceConfig(h: CommandHelpers, _args: any): CommandResult {
113
+ const loader = h.ctx.providerLoader as ProviderLoader | undefined;
114
+ if (!loader) return { success: false, error: 'providerLoader not available' };
115
+ return { success: true, ...loader.getSourceConfig() };
116
+ }
117
+
118
+ export async function handleSetProviderSourceConfig(h: CommandHelpers, args: any): Promise<CommandResult> {
119
+ const loader = h.ctx.providerLoader as ProviderLoader | undefined;
120
+ if (!loader) return { success: false, error: 'providerLoader not available' };
121
+
122
+ const parsed = parseProviderSourceConfigUpdate(args || {});
123
+ if ('error' in parsed) {
124
+ return { success: false, error: parsed.error };
125
+ }
126
+
127
+ const currentConfig = loadConfig();
128
+ const nextConfig = {
129
+ ...currentConfig,
130
+ ...(parsed.updates.providerSourceMode ? { providerSourceMode: parsed.updates.providerSourceMode } : {}),
131
+ ...(Object.prototype.hasOwnProperty.call(parsed.updates, 'providerDir') ? { providerDir: parsed.updates.providerDir } : {}),
132
+ };
133
+ saveConfig(nextConfig);
134
+
135
+ const sourceConfig = loader.applySourceConfig({
136
+ sourceMode: nextConfig.providerSourceMode,
137
+ userDir: Object.prototype.hasOwnProperty.call(parsed.updates, 'providerDir') ? parsed.updates.providerDir : loader.getSourceConfig().explicitProviderDir || undefined,
138
+ });
139
+ loader.reload();
140
+ loader.registerToDetector();
141
+ await h.ctx.onProviderSourceConfigChanged?.();
142
+
143
+ LOG.info(
144
+ 'Command',
145
+ `[set_provider_source_config] mode=${sourceConfig.sourceMode} explicitProviderDir=${sourceConfig.explicitProviderDir || '-'} userDir=${sourceConfig.userDir}`,
146
+ );
147
+
148
+ return { success: true, reloaded: true, ...sourceConfig };
149
+ }
150
+
110
151
  // ─── Extension Script Execution (Model/Mode) ─────
111
152
 
112
- function normalizeProviderScriptArgs(args: any): Record<string, any> {
153
+ export function normalizeProviderScriptArgs(args: any, scriptName?: string): Record<string, any> {
113
154
  const normalizedArgs = { ...(args || {}) };
155
+ const normalizedScriptName = String(scriptName || '').toLowerCase();
156
+
157
+ if (Object.prototype.hasOwnProperty.call(normalizedArgs, 'value')) {
158
+ if (
159
+ normalizedArgs.model === undefined
160
+ && (normalizedScriptName === 'setmodel' || normalizedScriptName === 'setmodelgui' || normalizedScriptName === 'webviewsetmodel')
161
+ ) {
162
+ normalizedArgs.model = normalizedArgs.value;
163
+ }
164
+ if (
165
+ normalizedArgs.mode === undefined
166
+ && (normalizedScriptName === 'setmode' || normalizedScriptName === 'webviewsetmode')
167
+ ) {
168
+ normalizedArgs.mode = normalizedArgs.value;
169
+ }
170
+ }
171
+
114
172
  for (const key of ['mode', 'model', 'message', 'action', 'button', 'text', 'sessionId', 'value']) {
115
173
  if (key in normalizedArgs && !(key.toUpperCase() in normalizedArgs)) {
116
174
  normalizedArgs[key.toUpperCase()] = normalizedArgs[key];
@@ -172,7 +230,7 @@ async function executeProviderScript(h: CommandHelpers, args: any, scriptName: s
172
230
  return { success: false, error: `Script '${actualScriptName}' not available for ${resolvedProviderType}` };
173
231
  }
174
232
 
175
- const normalizedArgs = normalizeProviderScriptArgs(args);
233
+ const normalizedArgs = normalizeProviderScriptArgs(args, actualScriptName);
176
234
 
177
235
  if (provider.category === 'cli') {
178
236
  const adapter = h.getCliAdapter(args?.targetSessionId || resolvedProviderType);
@@ -27,6 +27,7 @@ interface HistoryMessage {
27
27
  instanceId?: string; // IDE instance UUID (distinguishes windows of the same agent type)
28
28
  historySessionId?: string; // Persistent provider-side conversation/session key
29
29
  sessionTitle?: string;
30
+ workspace?: string; // Working directory at session start (kind: 'session_start' only)
30
31
  }
31
32
 
32
33
  const CODEX_STARTER_PROMPT_RE = /^(?:[›❯]\s*)?(?:Find and fix a bug in @filename|Improve documentation in @filename|Write tests for @filename|Explain this codebase|Summarize recent commits|Implement \{feature\}|Use \/skills(?: to list available skills)?|Run \/review on my current changes)$/i;
@@ -123,6 +124,7 @@ export interface SavedHistorySessionSummary {
123
124
  firstMessageAt: number;
124
125
  lastMessageAt: number;
125
126
  preview?: string;
127
+ workspace?: string;
126
128
  }
127
129
 
128
130
  export class ChatHistoryWriter {
@@ -325,6 +327,37 @@ export class ChatHistoryWriter {
325
327
  );
326
328
  }
327
329
 
330
+ writeSessionStart(
331
+ agentType: string,
332
+ historySessionId: string,
333
+ workspace: string,
334
+ instanceId?: string,
335
+ ): void {
336
+ const id = String(historySessionId || '').trim();
337
+ const ws = String(workspace || '').trim();
338
+ if (!id || !ws) return;
339
+ try {
340
+ const dir = path.join(HISTORY_DIR, this.sanitize(agentType));
341
+ fs.mkdirSync(dir, { recursive: true });
342
+ const date = new Date().toISOString().slice(0, 10);
343
+ const filePath = path.join(dir, `${this.sanitize(id)}_${date}.jsonl`);
344
+ const record: HistoryMessage = {
345
+ ts: new Date().toISOString(),
346
+ receivedAt: Date.now(),
347
+ role: 'system',
348
+ kind: 'session_start',
349
+ content: ws,
350
+ agent: agentType,
351
+ instanceId,
352
+ historySessionId: id,
353
+ workspace: ws,
354
+ };
355
+ fs.appendFileSync(filePath, JSON.stringify(record) + '\n', 'utf-8');
356
+ } catch {
357
+ // Ignore — must not affect main functionality
358
+ }
359
+ }
360
+
328
361
  promoteHistorySession(
329
362
  agentType: string,
330
363
  previousHistorySessionId: string,
@@ -606,6 +639,7 @@ export function listSavedHistorySessions(
606
639
  let lastMessageAt = 0;
607
640
  let sessionTitle = '';
608
641
  let preview = '';
642
+ let workspace = '';
609
643
 
610
644
  for (const file of files.sort()) {
611
645
  const filePath = path.join(dir, file);
@@ -619,6 +653,10 @@ export function listSavedHistorySessions(
619
653
  parsed = null;
620
654
  }
621
655
  if (!parsed || parsed.historySessionId !== historySessionId) continue;
656
+ if (parsed.kind === 'session_start') {
657
+ if (!workspace && parsed.workspace) workspace = parsed.workspace;
658
+ continue;
659
+ }
622
660
  messageCount += 1;
623
661
  if (!firstMessageAt || parsed.receivedAt < firstMessageAt) firstMessageAt = parsed.receivedAt;
624
662
  if (!lastMessageAt || parsed.receivedAt > lastMessageAt) lastMessageAt = parsed.receivedAt;
@@ -635,6 +673,7 @@ export function listSavedHistorySessions(
635
673
  firstMessageAt,
636
674
  lastMessageAt,
637
675
  preview: preview || undefined,
676
+ workspace: workspace || undefined,
638
677
  });
639
678
  }
640
679
 
@@ -8,6 +8,8 @@ export type { WorkspaceEntry } from './workspaces.js';
8
8
  export type { RecentActivityEntry } from './recent-activity.js';
9
9
  export type { SavedProviderSessionEntry } from './saved-sessions.js';
10
10
  export type { DaemonState } from './state-store.js';
11
+ export type ProviderSourceMode = 'normal' | 'no-upstream';
12
+ export declare function resolveProviderSourceMode(providerSourceMode: unknown, legacyDisableUpstream: unknown): ProviderSourceMode;
11
13
  export interface ADHDevConfig {
12
14
  serverUrl: string;
13
15
  selectedIde: string | null;
@@ -45,6 +47,7 @@ export interface ADHDevConfig {
45
47
  }>;
46
48
  }>;
47
49
  disableUpstream?: boolean;
50
+ providerSourceMode?: ProviderSourceMode;
48
51
  providerDir?: string;
49
52
  /**
50
53
  * Browser terminal sizing behavior for dashboard CLI panes.
@@ -14,6 +14,18 @@ export type { RecentActivityEntry } from './recent-activity.js';
14
14
  export type { SavedProviderSessionEntry } from './saved-sessions.js';
15
15
  export type { DaemonState } from './state-store.js';
16
16
 
17
+ export type ProviderSourceMode = 'normal' | 'no-upstream';
18
+
19
+ export function resolveProviderSourceMode(
20
+ providerSourceMode: unknown,
21
+ legacyDisableUpstream: unknown,
22
+ ): ProviderSourceMode {
23
+ if (providerSourceMode === 'normal' || providerSourceMode === 'no-upstream') {
24
+ return providerSourceMode;
25
+ }
26
+ return legacyDisableUpstream === true ? 'no-upstream' : 'normal';
27
+ }
28
+
17
29
  export interface ADHDevConfig {
18
30
  // Server connection
19
31
  serverUrl: string;
@@ -81,9 +93,13 @@ export interface ADHDevConfig {
81
93
 
82
94
  // Disable upstream provider auto-download (use builtin only)
83
95
  // Controllable from CLI (--no-upstream) and dashboard (machine page)
96
+ // Deprecated legacy boolean; prefer providerSourceMode.
84
97
  disableUpstream?: boolean;
85
98
 
86
- // Optional custom provider directory for local development
99
+ // Explicit machine-level provider source policy.
100
+ providerSourceMode?: ProviderSourceMode;
101
+
102
+ // Optional explicit provider override root (for example a local adhdev-providers checkout)
87
103
  providerDir?: string;
88
104
 
89
105
  /**
@@ -113,7 +129,7 @@ const DEFAULT_CONFIG: ADHDevConfig = {
113
129
  registeredMachineId: undefined,
114
130
  providerSettings: {},
115
131
  ideSettings: {},
116
- disableUpstream: false,
132
+ providerSourceMode: 'normal',
117
133
  terminalSizingMode: 'measured',
118
134
  };
119
135
 
@@ -164,7 +180,7 @@ function normalizeConfig(raw: unknown): ADHDevConfig & { activeWorkspaceId?: str
164
180
  registeredMachineId: asOptionalString(parsed.registeredMachineId),
165
181
  providerSettings: isPlainObject(parsed.providerSettings) ? parsed.providerSettings : {},
166
182
  ideSettings: isPlainObject(parsed.ideSettings) ? parsed.ideSettings : {},
167
- disableUpstream: asBoolean(parsed.disableUpstream, DEFAULT_CONFIG.disableUpstream ?? false),
183
+ providerSourceMode: resolveProviderSourceMode(parsed.providerSourceMode, parsed.disableUpstream),
168
184
  providerDir: asOptionalString(parsed.providerDir),
169
185
  terminalSizingMode: parsed.terminalSizingMode === 'fit' ? 'fit' : 'measured',
170
186
  };
@@ -0,0 +1,42 @@
1
+ import type { ProviderSourceMode } from './config.js'
2
+
3
+ export interface ProviderSourceConfigSnapshot {
4
+ sourceMode: ProviderSourceMode
5
+ disableUpstream: boolean
6
+ explicitProviderDir: string | null
7
+ userDir: string
8
+ upstreamDir: string
9
+ providerRoots: string[]
10
+ }
11
+
12
+ export interface ProviderSourceConfigUpdate {
13
+ providerSourceMode?: ProviderSourceMode
14
+ providerDir?: string | undefined
15
+ }
16
+
17
+ function normalizeProviderDir(value: unknown): string | undefined {
18
+ if (typeof value !== 'string') return undefined
19
+ const trimmed = value.trim()
20
+ return trimmed ? trimmed : undefined
21
+ }
22
+
23
+ export function parseProviderSourceConfigUpdate(input: {
24
+ providerSourceMode?: unknown
25
+ providerDir?: unknown
26
+ }): { ok: true; updates: ProviderSourceConfigUpdate } | { ok: false; error: string } {
27
+ const updates: ProviderSourceConfigUpdate = {}
28
+
29
+ if (Object.prototype.hasOwnProperty.call(input, 'providerSourceMode')) {
30
+ const { providerSourceMode } = input
31
+ if (providerSourceMode !== 'normal' && providerSourceMode !== 'no-upstream') {
32
+ return { ok: false, error: "providerSourceMode must be 'normal' or 'no-upstream'" }
33
+ }
34
+ updates.providerSourceMode = providerSourceMode
35
+ }
36
+
37
+ if (Object.prototype.hasOwnProperty.call(input, 'providerDir')) {
38
+ updates.providerDir = normalizeProviderDir(input.providerDir)
39
+ }
40
+
41
+ return { ok: true, updates }
42
+ }
@@ -6,6 +6,7 @@
6
6
  * - deduped by provider session when available, else by kind + providerType + workspace
7
7
  * - used only for quick-launch shortcuts
8
8
  */
9
+ import type { ProviderSummaryMetadata } from '../shared-types.js';
9
10
  import type { DaemonState } from './state-store.js';
10
11
  export interface RecentActivityEntry {
11
12
  id: string;
@@ -14,7 +15,7 @@ export interface RecentActivityEntry {
14
15
  providerName: string;
15
16
  providerSessionId?: string;
16
17
  workspace?: string | null;
17
- currentModel?: string;
18
+ summaryMetadata?: ProviderSummaryMetadata;
18
19
  title?: string;
19
20
  lastUsedAt: number;
20
21
  }
@@ -8,8 +8,10 @@
8
8
  */
9
9
 
10
10
  import * as path from 'path';
11
+ import type { ProviderSummaryMetadata } from '../shared-types.js';
11
12
  import type { DaemonState } from './state-store.js';
12
13
  import { expandPath } from './workspaces.js';
14
+ import { normalizePersistedSummaryMetadata } from '../providers/summary-metadata.js';
13
15
 
14
16
  export interface RecentActivityEntry {
15
17
  id: string;
@@ -18,7 +20,7 @@ export interface RecentActivityEntry {
18
20
  providerName: string;
19
21
  providerSessionId?: string;
20
22
  workspace?: string | null;
21
- currentModel?: string;
23
+ summaryMetadata?: ProviderSummaryMetadata;
22
24
  title?: string;
23
25
  lastUsedAt: number;
24
26
  }
@@ -55,6 +57,9 @@ export function appendRecentActivity(
55
57
  const nextEntry: RecentActivityEntry = {
56
58
  ...entry,
57
59
  workspace: entry.workspace ? normalizeWorkspace(entry.workspace) : undefined,
60
+ summaryMetadata: normalizePersistedSummaryMetadata({
61
+ summaryMetadata: entry.summaryMetadata,
62
+ }),
58
63
  id: buildRecentActivityKeyForEntry(entry),
59
64
  lastUsedAt: entry.lastUsedAt || Date.now(),
60
65
  };
@@ -68,6 +73,12 @@ export function appendRecentActivity(
68
73
 
69
74
  export function getRecentActivity(state: DaemonState, limit = 20): RecentActivityEntry[] {
70
75
  return [...(state.recentActivity || [])]
76
+ .map(entry => ({
77
+ ...entry,
78
+ summaryMetadata: normalizePersistedSummaryMetadata({
79
+ summaryMetadata: entry.summaryMetadata,
80
+ }),
81
+ }))
71
82
  .sort((a, b) => b.lastUsedAt - a.lastUsedAt)
72
83
  .slice(0, limit);
73
84
  }
@@ -1,3 +1,4 @@
1
+ import type { ProviderSummaryMetadata } from '../shared-types.js';
1
2
  import type { DaemonState } from './state-store.js';
2
3
  export interface SavedProviderSessionEntry {
3
4
  id: string;
@@ -6,7 +7,7 @@ export interface SavedProviderSessionEntry {
6
7
  providerName: string;
7
8
  providerSessionId: string;
8
9
  workspace?: string | null;
9
- currentModel?: string;
10
+ summaryMetadata?: ProviderSummaryMetadata;
10
11
  title?: string;
11
12
  createdAt: number;
12
13
  lastUsedAt: number;