@adhdev/daemon-core 0.9.70 → 0.9.72

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.
@@ -0,0 +1,2 @@
1
+ import type { DaemonComponents } from '../boot/daemon-lifecycle.js';
2
+ export declare function setupMeshEventForwarding(components: DaemonComponents): void;
@@ -95,6 +95,7 @@ export declare class CliProviderInstance implements ProviderInstance {
95
95
  private completedDebounceTimer;
96
96
  private completedDebouncePending;
97
97
  private enforceFreshSessionLaunchIfNeeded;
98
+ private maybeAutoApproveStatus;
98
99
  private detectStatusTransition;
99
100
  private pushEvent;
100
101
  private flushEvents;
@@ -287,6 +287,7 @@ export interface SessionEntry {
287
287
  completionMarker?: string;
288
288
  seenCompletionMarker?: string;
289
289
  surfaceHidden?: boolean;
290
+ settings?: Record<string, any>;
290
291
  }
291
292
  /**
292
293
  * Compact session metadata stored in UserSessionDO and reused by server-side
@@ -324,6 +325,7 @@ export interface CompactSessionEntry {
324
325
  controlValues?: Record<string, string | number | boolean>;
325
326
  providerControls?: ProviderControlSchema[];
326
327
  summaryMetadata?: ProviderSummaryMetadata;
328
+ settings?: Record<string, any>;
327
329
  }
328
330
  export type VersionUpdateReason = 'force_update_below' | 'major_minor_mismatch' | 'patch_mismatch' | 'daemon_ahead';
329
331
  /** Available provider information */
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@adhdev/session-host-core",
3
- "version": "0.9.70",
3
+ "version": "0.9.72",
4
4
  "description": "ADHDev local session host core \u2014 session registry, protocol, buffers",
5
5
  "main": "dist/index.js",
6
6
  "types": "dist/index.d.ts",
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@adhdev/daemon-core",
3
- "version": "0.9.70",
3
+ "version": "0.9.72",
4
4
  "description": "ADHDev daemon core \u2014 CDP, IDE detection, providers, command execution",
5
5
  "main": "dist/index.js",
6
6
  "types": "dist/index.d.ts",
@@ -36,6 +36,7 @@ import { loadConfig } from '../config/config.js';
36
36
  import type { PtyTransportFactory } from '../cli-adapters/pty-transport.js';
37
37
  import type { IdeProviderInstance } from '../providers/ide-provider-instance.js';
38
38
  import { createDefaultGitCommandServices } from '../git/git-commands.js';
39
+ import { setupMeshEventForwarding } from '../mesh/mesh-events.js';
39
40
 
40
41
  // ─── Init Config ───
41
42
 
@@ -317,7 +318,7 @@ export async function initDaemonComponents(config: DaemonInitConfig): Promise<Da
317
318
  // 10. Start instance ticking
318
319
  instanceManager.startTicking(config.tickIntervalMs ?? 5_000);
319
320
 
320
- return {
321
+ const components = {
321
322
  providerLoader,
322
323
  instanceManager,
323
324
  cliManager,
@@ -331,6 +332,11 @@ export async function initDaemonComponents(config: DaemonInitConfig): Promise<Da
331
332
  detectedIdes: detectedIdesRef,
332
333
  refreshProviderAvailability,
333
334
  };
335
+
336
+ // 11. Setup Mesh Event Forwarding
337
+ setupMeshEventForwarding(components);
338
+
339
+ return components;
334
340
  }
335
341
 
336
342
  /**
@@ -2272,11 +2272,23 @@ export class ProviderCliAdapter implements CliAdapter {
2272
2272
  }
2273
2273
  }
2274
2274
 
2275
+ private getParsedDebugState(): Record<string, any> | null {
2276
+ if (this.startupParseGate || typeof this.cliScripts?.parseSession !== 'function') return null;
2277
+ try {
2278
+ const parsed = this.getScriptParsedStatus();
2279
+ return parsed && typeof parsed === 'object' ? parsed as Record<string, any> : null;
2280
+ } catch {
2281
+ return null;
2282
+ }
2283
+ }
2284
+
2275
2285
  getDebugState(): Record<string, any> {
2276
2286
  const screenText = sanitizeTerminalText(this.terminalScreen.getText());
2277
2287
  const startupModal = this.startupParseGate ? this.runParseApproval(this.recentOutputBuffer) : null;
2278
2288
  const effectiveStatus = this.projectEffectiveStatus(startupModal);
2279
2289
  const effectiveReady = this.ready || !!startupModal;
2290
+ const parsedDebugState = this.getParsedDebugState();
2291
+ const parsedMessages = Array.isArray(parsedDebugState?.messages) ? parsedDebugState.messages : [];
2280
2292
  return {
2281
2293
  type: this.cliType,
2282
2294
  name: this.cliName,
@@ -2289,8 +2301,18 @@ export class ProviderCliAdapter implements CliAdapter {
2289
2301
  startupParseGate: this.startupParseGate,
2290
2302
  spawnAt: this.spawnAt,
2291
2303
  workingDir: this.workingDir,
2292
- messages: [],
2293
- messageCount: 0,
2304
+ messages: parsedMessages,
2305
+ messageCount: parsedMessages.length,
2306
+ parsedStatus: parsedDebugState ? {
2307
+ id: parsedDebugState.id,
2308
+ status: parsedDebugState.status,
2309
+ title: parsedDebugState.title,
2310
+ providerSessionId: parsedDebugState.providerSessionId,
2311
+ transcriptAuthority: parsedDebugState.transcriptAuthority,
2312
+ coverage: parsedDebugState.coverage,
2313
+ activeModal: parsedDebugState.activeModal,
2314
+ messageCount: parsedMessages.length,
2315
+ } : null,
2294
2316
  screenText: screenText.slice(-4000),
2295
2317
  currentTurnScope: this.currentTurnScope,
2296
2318
  startupBuffer: this.startupBuffer.slice(-4000),
@@ -101,6 +101,9 @@ function getHistorySessionId(h: CommandHelpers, args: any): string | undefined {
101
101
  const explicit = typeof args?.historySessionId === 'string' ? args.historySessionId.trim() : '';
102
102
  if (explicit) return explicit;
103
103
 
104
+ const explicitProviderSessionId = typeof args?.providerSessionId === 'string' ? args.providerSessionId.trim() : '';
105
+ if (explicitProviderSessionId) return explicitProviderSessionId;
106
+
104
107
  const targetSessionId = typeof args?.targetSessionId === 'string' ? args.targetSessionId.trim() : '';
105
108
  if (!targetSessionId) return undefined;
106
109
 
@@ -779,7 +782,40 @@ export async function handleReadChat(h: CommandHelpers, args: any): Promise<Comm
779
782
  ...(coverage ? { coverage } : {}),
780
783
  }, args);
781
784
  }
782
- return { success: false, error: `${transport} adapter not found` };
785
+ const historyLimit = normalizeReadChatTailLimit(args);
786
+ try {
787
+ const agentStr = provider?.type || args?.agentType || getCurrentProviderType(h);
788
+ const workspace = typeof args?.workspace === 'string'
789
+ ? args.workspace
790
+ : typeof (h.currentSession as any)?.workspace === 'string'
791
+ ? (h.currentSession as any).workspace
792
+ : undefined;
793
+ const history = readProviderChatHistory(agentStr, {
794
+ canonicalHistory: provider?.canonicalHistory,
795
+ historySessionId,
796
+ workspace,
797
+ offset: 0,
798
+ limit: historyLimit,
799
+ excludeRecentCount: 0,
800
+ historyBehavior: provider?.historyBehavior,
801
+ scripts: provider?.scripts as any,
802
+ });
803
+ const historyProviderSessionId = typeof (history as any)?.providerSessionId === 'string'
804
+ ? (history as any).providerSessionId
805
+ : historySessionId;
806
+ return buildReadChatCommandResult({
807
+ messages: Array.isArray((history as any)?.messages) ? (history as any).messages : [],
808
+ status: 'idle',
809
+ ...(typeof (history as any)?.title === 'string' ? { title: (history as any).title } : {}),
810
+ ...(historyProviderSessionId ? { providerSessionId: historyProviderSessionId } : {}),
811
+ ...(((provider?.historyBehavior as any)?.transcriptAuthority === 'provider' || (provider?.historyBehavior as any)?.transcriptAuthority === 'daemon')
812
+ ? { transcriptAuthority: (provider?.historyBehavior as any).transcriptAuthority }
813
+ : {}),
814
+ coverage: 'tail',
815
+ }, args);
816
+ } catch (error: any) {
817
+ return { success: false, error: error?.message || `${transport} adapter not found` };
818
+ }
783
819
  }
784
820
 
785
821
  // Extension transport: evaluateInSession
@@ -480,7 +480,7 @@ export class DaemonCliManager {
480
480
  workingDir: string,
481
481
  cliArgs?: string[],
482
482
  initialModel?: string,
483
- options?: { resumeSessionId?: string },
483
+ options?: { resumeSessionId?: string, settingsOverride?: Record<string, any> },
484
484
  ): Promise<{ runtimeSessionId: string; providerSessionId?: string }> {
485
485
  const trimmed = (workingDir || '').trim();
486
486
  if (!trimmed) throw new Error('working directory required');
@@ -624,7 +624,7 @@ export class DaemonCliManager {
624
624
  resolvedDir,
625
625
  resolvedCliArgs,
626
626
  resolvedProvider,
627
- this.providerLoader.getSettings(normalizedType),
627
+ { ...this.providerLoader.getSettings(normalizedType), ...(options?.settingsOverride || {}) },
628
628
  false,
629
629
  {
630
630
  providerSessionId: sessionBinding.providerSessionId,
@@ -904,7 +904,7 @@ export class DaemonCliManager {
904
904
  dir,
905
905
  args?.cliArgs,
906
906
  args?.initialModel,
907
- { resumeSessionId: args?.resumeSessionId },
907
+ { resumeSessionId: args?.resumeSessionId, settingsOverride: args?.settings },
908
908
  );
909
909
 
910
910
  return {
@@ -1,12 +1,20 @@
1
- import { join } from 'path'
1
+ import { existsSync, realpathSync } from 'node:fs'
2
+ import { createRequire } from 'node:module'
3
+ import { dirname, join, resolve } from 'node:path'
2
4
  import type { ProviderModule, MeshCoordinatorMcpConfigFormat } from '../providers/contracts.js'
3
5
 
6
+ export interface MeshCoordinatorMcpServerLaunch {
7
+ command: string
8
+ args: string[]
9
+ }
10
+
4
11
  export type MeshCoordinatorSetup =
5
12
  | {
6
13
  kind: 'auto_import'
7
14
  serverName: string
8
15
  configPath: string
9
16
  configFormat?: MeshCoordinatorMcpConfigFormat
17
+ mcpServer: MeshCoordinatorMcpServerLaunch
10
18
  }
11
19
  | {
12
20
  kind: 'manual'
@@ -27,6 +35,8 @@ export interface ResolveMeshCoordinatorSetupOptions {
27
35
  meshId: string
28
36
  workspace: string
29
37
  adhdevMcpCommand?: string
38
+ adhdevMcpEntryPath?: string
39
+ nodeExecutable?: string
30
40
  }
31
41
 
32
42
  const DEFAULT_SERVER_NAME = 'adhdev-mesh'
@@ -56,11 +66,23 @@ export function resolveMeshCoordinatorSetup(options: ResolveMeshCoordinatorSetup
56
66
  if (!path) {
57
67
  return { kind: 'unsupported', reason: 'Provider auto-import MCP config is missing a config path' }
58
68
  }
69
+ const mcpServer = resolveAdhdevMcpServerLaunch({
70
+ meshId,
71
+ nodeExecutable: options.nodeExecutable,
72
+ adhdevMcpEntryPath: options.adhdevMcpEntryPath,
73
+ })
74
+ if (!mcpServer) {
75
+ return {
76
+ kind: 'unsupported',
77
+ reason: 'Could not resolve the ADHDev MCP server entrypoint without relying on a PATH bin shim',
78
+ }
79
+ }
59
80
  return {
60
81
  kind: 'auto_import',
61
82
  serverName,
62
83
  configPath: join(workspace, path),
63
84
  configFormat: mcpConfig.format,
85
+ mcpServer,
64
86
  }
65
87
  }
66
88
 
@@ -95,3 +117,62 @@ export function resolveMeshCoordinatorSetup(options: ResolveMeshCoordinatorSetup
95
117
  function renderMeshCoordinatorTemplate(template: string, values: Record<string, string>): string {
96
118
  return template.replace(/\{\{\s*(meshId|workspace|serverName|adhdevMcpCommand)\s*\}\}/g, (_, key: string) => values[key] || '')
97
119
  }
120
+
121
+ function resolveAdhdevMcpServerLaunch(options: {
122
+ meshId: string
123
+ nodeExecutable?: string
124
+ adhdevMcpEntryPath?: string
125
+ }): MeshCoordinatorMcpServerLaunch | null {
126
+ const entryPath = resolveAdhdevMcpEntryPath(options.adhdevMcpEntryPath)
127
+ if (!entryPath) return null
128
+ return {
129
+ command: options.nodeExecutable?.trim() || process.execPath,
130
+ args: [entryPath, '--repo-mesh', options.meshId],
131
+ }
132
+ }
133
+
134
+ function resolveAdhdevMcpEntryPath(explicitPath?: string): string | null {
135
+ const explicit = explicitPath?.trim()
136
+ if (explicit) return normalizeExistingPath(explicit) || explicit
137
+
138
+ const envPath = process.env.ADHDEV_MCP_SERVER_PATH?.trim()
139
+ if (envPath) return normalizeExistingPath(envPath) || envPath
140
+
141
+ const candidates: string[] = []
142
+ const addCandidate = (candidate: string) => {
143
+ if (!candidates.includes(candidate)) candidates.push(candidate)
144
+ }
145
+ const addPackagedCandidates = (baseFile?: string) => {
146
+ if (!baseFile) return
147
+ const realBase = normalizeExistingPath(baseFile) || baseFile
148
+ const dir = dirname(realBase)
149
+ addCandidate(resolve(dir, '../vendor/mcp-server/index.js'))
150
+ addCandidate(resolve(dir, '../../vendor/mcp-server/index.js'))
151
+ addCandidate(resolve(dir, '../../../vendor/mcp-server/index.js'))
152
+ }
153
+
154
+ addPackagedCandidates(process.argv[1])
155
+
156
+ for (const candidate of candidates) {
157
+ const normalized = normalizeExistingPath(candidate)
158
+ if (normalized) return normalized
159
+ }
160
+
161
+ try {
162
+ const requireBase = process.argv[1] ? (normalizeExistingPath(process.argv[1]) || process.argv[1]) : join(process.cwd(), 'adhdev-daemon.js')
163
+ const req = createRequire(requireBase)
164
+ const resolvedModule = req.resolve('@adhdev/mcp-server')
165
+ return normalizeExistingPath(resolvedModule) || resolvedModule
166
+ } catch {
167
+ return null
168
+ }
169
+ }
170
+
171
+ function normalizeExistingPath(filePath: string): string | null {
172
+ try {
173
+ if (!existsSync(filePath)) return null
174
+ return realpathSync.native(filePath)
175
+ } catch {
176
+ return null
177
+ }
178
+ }
@@ -1006,11 +1006,18 @@ export class DaemonCommandRouter {
1006
1006
  if (!meshId) return { success: false, error: 'meshId required' };
1007
1007
 
1008
1008
  try {
1009
- const { getMesh } = await import('../config/mesh-config.js');
1010
1009
  const { buildCoordinatorSystemPrompt } = await import('../mesh/coordinator-prompt.js');
1011
- const mesh = getMesh(meshId);
1010
+
1011
+ // Support inline mesh data from cloud (bypasses local meshes.json lookup)
1012
+ let mesh: any;
1013
+ if (args?.inlineMesh && typeof args.inlineMesh === 'object') {
1014
+ mesh = args.inlineMesh;
1015
+ } else {
1016
+ const { getMesh } = await import('../config/mesh-config.js');
1017
+ mesh = getMesh(meshId);
1018
+ }
1012
1019
  if (!mesh) return { success: false, error: 'Mesh not found' };
1013
- if (mesh.nodes.length === 0) return { success: false, error: 'No nodes in mesh' };
1020
+ if (!Array.isArray(mesh.nodes) || mesh.nodes.length === 0) return { success: false, error: 'No nodes in mesh' };
1014
1021
 
1015
1022
  const workspace = mesh.nodes[0].workspace;
1016
1023
  const providerMeta = this.deps.providerLoader.resolve?.(cliType) || this.deps.providerLoader.getMeta(cliType);
@@ -1074,8 +1081,8 @@ export class DaemonCommandRouter {
1074
1081
  mcpServers: {
1075
1082
  ...(existingMcpConfig.mcpServers || {}),
1076
1083
  [coordinatorSetup.serverName]: {
1077
- command: 'adhdev-mcp',
1078
- args: ['--repo-mesh', meshId],
1084
+ command: coordinatorSetup.mcpServer.command,
1085
+ args: coordinatorSetup.mcpServer.args,
1079
1086
  },
1080
1087
  },
1081
1088
  };
@@ -1090,11 +1097,24 @@ export class DaemonCommandRouter {
1090
1097
  systemPrompt = `You are a Repo Mesh Coordinator for "${mesh.name}". Use the adhdev-mesh MCP tools (mesh_status, mesh_list_nodes, mesh_send_task, mesh_read_chat, mesh_launch_session, etc.) to orchestrate work across ${mesh.nodes.length} node(s).`;
1091
1098
  }
1092
1099
 
1100
+ const cliArgs: string[] = [];
1101
+ if (systemPrompt) {
1102
+ cliArgs.push('--append-system-prompt', systemPrompt);
1103
+ }
1104
+ if (cliType === 'claude-cli') {
1105
+ cliArgs.push('--mcp-config', coordinatorSetup.configPath);
1106
+ }
1107
+
1093
1108
  // 3. Launch CLI session via existing cliManager
1109
+ // Pass coordinator system prompt via --append-system-prompt so the
1110
+ // CLI inherits its default behavior AND knows it is a mesh coordinator.
1094
1111
  const launchResult: any = await this.deps.cliManager.handleCliCommand('launch_cli', {
1095
1112
  cliType,
1096
1113
  dir: workspace,
1097
- initialPrompt: systemPrompt,
1114
+ cliArgs: cliArgs.length > 0 ? cliArgs : undefined,
1115
+ settings: {
1116
+ meshCoordinatorFor: meshId
1117
+ }
1098
1118
  });
1099
1119
 
1100
1120
  if (!launchResult?.success) {
@@ -0,0 +1,61 @@
1
+ import type { DaemonComponents } from '../boot/daemon-lifecycle.js';
2
+ import { getMeshByRepo } from '../config/mesh-config.js';
3
+ import { LOG } from '../logging/logger.js';
4
+
5
+ export function setupMeshEventForwarding(components: DaemonComponents) {
6
+ components.instanceManager.onEvent((event) => {
7
+ // We only care about agent sub-session completion or waiting approval
8
+ if (event.event !== 'agent:generating_completed' && event.event !== 'agent:waiting_approval') return;
9
+
10
+ const instanceId = event.instanceId as string;
11
+ if (!instanceId) return;
12
+
13
+ // Try to find the workspace of the sub-agent
14
+ const sourceInstance = components.instanceManager.getInstance(instanceId);
15
+ if (!sourceInstance || sourceInstance.category !== 'cli') return;
16
+ const state = sourceInstance.getState();
17
+ const workspace = state.workspace;
18
+ if (!workspace) return;
19
+
20
+ // Find the mesh that this workspace belongs to
21
+ const mesh = getMeshByRepo(workspace);
22
+ if (!mesh) return;
23
+
24
+ // Find the coordinator session(s)
25
+ const allInstances = components.instanceManager.getByCategory('cli');
26
+ const coordinatorInstances = allInstances.filter((inst) => {
27
+ const instState = inst.getState();
28
+
29
+ // The coordinator session was launched with meshCoordinatorFor setting
30
+ if (instState.settings?.meshCoordinatorFor !== mesh.id) return false;
31
+
32
+ // Exclude the source instance itself (just in case)
33
+ if (instState.instanceId === instanceId) return false;
34
+
35
+ return true;
36
+ });
37
+
38
+ if (coordinatorInstances.length === 0) return;
39
+
40
+ // Determine node label
41
+ const targetNode = mesh.nodes.find((n) => n.workspace === workspace);
42
+ const nodeLabel = targetNode ? `Node '${targetNode.id}'` : `Agent at ${workspace}`;
43
+
44
+ // Construct a system message in English
45
+ let messageText = '';
46
+ if (event.event === 'agent:generating_completed') {
47
+ messageText = `[System] ${nodeLabel} has completed its task and is now idle. You may use mesh_read_chat to review its progress.`;
48
+ } else if (event.event === 'agent:waiting_approval') {
49
+ messageText = `[System] ${nodeLabel} is waiting for approval to proceed. You may use mesh_read_chat and mesh_approve to handle it.`;
50
+ }
51
+
52
+ if (!messageText) return;
53
+
54
+ // Inject the message into the coordinator sessions
55
+ for (const coord of coordinatorInstances) {
56
+ const coordState = coord.getState();
57
+ LOG.info('MeshEvents', `Forwarding event from ${workspace} to coordinator ${coordState.instanceId}`);
58
+ coord.onEvent('send_message', { input: { text: messageText, textFallback: messageText } });
59
+ }
60
+ });
61
+ }
@@ -363,7 +363,7 @@ export class CliProviderInstance implements ProviderInstance {
363
363
  this.errorMessage = undefined;
364
364
  this.errorReason = undefined;
365
365
  }
366
- const autoApproveActive = adapterStatus.status === 'waiting_approval' && this.shouldAutoApprove();
366
+ const autoApproveActive = this.maybeAutoApproveStatus(adapterStatus, Date.now());
367
367
  const visibleStatus = parseErrorMessage
368
368
  ? 'error'
369
369
  : (autoApproveActive ? 'generating' : adapterStatus.status);
@@ -587,19 +587,11 @@ export class CliProviderInstance implements ProviderInstance {
587
587
  this.applyProviderResponse(parsed.payload, { phase: 'immediate' });
588
588
  }
589
589
 
590
- private detectStatusTransition(): void {
591
- const now = Date.now();
592
- // Status-change handling is a hot path: PTY output can fire it many times
593
- // during long-running CLI sessions. Keep this path on adapter-owned light
594
- // state only; rich provider parsing is reserved for getState/read_chat.
595
- const adapterStatus = this.adapter.getStatus({ allowParse: false });
596
- const parsedStatus = null;
597
- const rawStatus = adapterStatus.status;
598
- const autoApproveActive = rawStatus === 'waiting_approval' && this.shouldAutoApprove();
599
- // Guard re-entry: onStatusChange can fire multiple times while the modal
600
- // is still on screen (before the PTY absorbs the approval key). Without this
601
- // flag, we'd write the approval key repeatedly — stray keys then leak into
602
- // the text input once Claude Code dismisses the modal.
590
+ private maybeAutoApproveStatus(adapterStatus: any, now = Date.now()): boolean {
591
+ const autoApproveActive = adapterStatus?.status === 'waiting_approval' && this.shouldAutoApprove();
592
+ // Guard re-entry: onStatusChange/getState can observe the same modal multiple
593
+ // times while the PTY absorbs the approval key. Without this flag, repeated
594
+ // snapshots would write stray keys into the input once the modal dismisses.
603
595
  if (autoApproveActive && !this.autoApproveBusy) {
604
596
  this.autoApproveBusy = true;
605
597
  if (this.autoApproveBusyTimer) clearTimeout(this.autoApproveBusyTimer);
@@ -607,12 +599,25 @@ export class CliProviderInstance implements ProviderInstance {
607
599
  this.autoApproveBusy = false;
608
600
  this.autoApproveBusyTimer = null;
609
601
  }, 2000);
610
- const { index: buttonIndex, label: buttonLabel } = pickApprovalButton(adapterStatus.activeModal?.buttons, this.provider);
611
- this.recordAutoApproval(adapterStatus.activeModal?.message, buttonLabel, now);
602
+ const modal = adapterStatus.activeModal;
603
+ const { index: buttonIndex, label: buttonLabel } = pickApprovalButton(modal?.buttons, this.provider);
604
+ this.recordAutoApproval(modal?.message, buttonLabel, now);
612
605
  setTimeout(() => {
613
606
  this.adapter.resolveModal(buttonIndex);
614
607
  }, 0);
615
608
  }
609
+ return autoApproveActive;
610
+ }
611
+
612
+ private detectStatusTransition(): void {
613
+ const now = Date.now();
614
+ // Status-change handling is a hot path: PTY output can fire it many times
615
+ // during long-running CLI sessions. Keep this path on adapter-owned light
616
+ // state only; rich provider parsing is reserved for getState/read_chat.
617
+ const adapterStatus = this.adapter.getStatus({ allowParse: false });
618
+ const parsedStatus = null;
619
+ const rawStatus = adapterStatus.status;
620
+ const autoApproveActive = this.maybeAutoApproveStatus(adapterStatus, now);
616
621
  const newStatus = autoApproveActive ? 'generating' : rawStatus;
617
622
  const dirName = this.workingDir.split('/').filter(Boolean).pop() || 'session';
618
623
  const chatTitle = `${this.provider.name} · ${dirName}`;
@@ -384,6 +384,7 @@ export interface SessionEntry {
384
384
  completionMarker?: string;
385
385
  seenCompletionMarker?: string;
386
386
  surfaceHidden?: boolean;
387
+ settings?: Record<string, any>;
387
388
  }
388
389
 
389
390
  /**
@@ -422,6 +423,7 @@ export interface CompactSessionEntry {
422
423
  controlValues?: Record<string, string | number | boolean>;
423
424
  providerControls?: ProviderControlSchema[];
424
425
  summaryMetadata?: ProviderSummaryMetadata;
426
+ settings?: Record<string, any>;
425
427
  }
426
428
 
427
429
  export type VersionUpdateReason =
@@ -187,6 +187,7 @@ function buildIdeWorkspaceSession(
187
187
  errorMessage: state.errorMessage,
188
188
  errorReason: state.errorReason,
189
189
  lastUpdated: state.lastUpdated,
190
+ settings: state.settings,
190
191
  };
191
192
  }
192
193
 
@@ -227,6 +228,7 @@ function buildExtensionAgentSession(
227
228
  errorMessage: ext.errorMessage,
228
229
  errorReason: ext.errorReason,
229
230
  lastUpdated: ext.lastUpdated,
231
+ settings: ext.settings,
230
232
  };
231
233
  }
232
234
 
@@ -306,6 +308,7 @@ function buildCliSession(state: CliProviderState, options: SessionEntryBuildOpti
306
308
  errorMessage: state.errorMessage,
307
309
  errorReason: state.errorReason,
308
310
  lastUpdated: state.lastUpdated,
311
+ settings: state.settings,
309
312
  };
310
313
  }
311
314
 
@@ -341,6 +344,7 @@ function buildAcpSession(state: AcpProviderState, options: SessionEntryBuildOpti
341
344
  errorMessage: state.errorMessage,
342
345
  errorReason: state.errorReason,
343
346
  lastUpdated: state.lastUpdated,
347
+ settings: state.settings,
344
348
  };
345
349
  }
346
350
 
@@ -310,6 +310,7 @@ export class DaemonStatusReporter {
310
310
  title: session.title,
311
311
  cdpConnected: session.cdpConnected,
312
312
  summaryMetadata: session.summaryMetadata,
313
+ settings: session.settings,
313
314
  })),
314
315
  p2p: payload.p2p,
315
316
  timestamp: now,