@adhdev/daemon-core 0.9.76-rc.3 → 0.9.76-rc.31

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 (42) hide show
  1. package/dist/cli-adapters/provider-cli-adapter.d.ts +2 -1
  2. package/dist/cli-adapters/provider-cli-runtime.d.ts +1 -0
  3. package/dist/commands/cli-manager.d.ts +17 -4
  4. package/dist/commands/mesh-coordinator.d.ts +2 -0
  5. package/dist/commands/router.d.ts +4 -0
  6. package/dist/config/mesh-config.d.ts +3 -0
  7. package/dist/git/git-types.d.ts +1 -1
  8. package/dist/git/git-worktree.d.ts +64 -0
  9. package/dist/git/index.d.ts +2 -0
  10. package/dist/index.js +1058 -384
  11. package/dist/index.js.map +1 -1
  12. package/dist/index.mjs +1085 -416
  13. package/dist/index.mjs.map +1 -1
  14. package/dist/mesh/coordinator-prompt.d.ts +1 -0
  15. package/dist/providers/cli-provider-instance.d.ts +3 -0
  16. package/dist/providers/provider-instance-manager.d.ts +1 -0
  17. package/dist/providers/provider-instance.d.ts +2 -0
  18. package/dist/repo-mesh-types.d.ts +6 -0
  19. package/dist/shared-types.d.ts +22 -1
  20. package/package.json +3 -4
  21. package/src/cli-adapters/provider-cli-adapter.ts +6 -3
  22. package/src/cli-adapters/provider-cli-runtime.ts +3 -2
  23. package/src/commands/chat-commands.ts +50 -5
  24. package/src/commands/cli-manager.ts +78 -5
  25. package/src/commands/handler.ts +13 -4
  26. package/src/commands/mesh-coordinator.ts +149 -6
  27. package/src/commands/router.ts +319 -32
  28. package/src/config/mesh-config.ts +6 -0
  29. package/src/git/git-commands.ts +5 -1
  30. package/src/git/git-types.ts +1 -0
  31. package/src/git/git-worktree.ts +214 -0
  32. package/src/git/index.ts +14 -0
  33. package/src/mesh/coordinator-prompt.ts +25 -10
  34. package/src/mesh/mesh-events.ts +40 -17
  35. package/src/providers/cli-provider-instance.d.ts +2 -0
  36. package/src/providers/cli-provider-instance.ts +55 -7
  37. package/src/providers/provider-instance-manager.ts +20 -1
  38. package/src/providers/provider-instance.ts +2 -0
  39. package/src/repo-mesh-types.ts +6 -0
  40. package/src/shared-types.ts +24 -1
  41. package/src/status/builders.ts +17 -12
  42. package/src/status/reporter.ts +6 -0
@@ -14,5 +14,6 @@ export interface CoordinatorPromptContext {
14
14
  mesh: LocalMeshEntry;
15
15
  status?: RepoMeshStatus;
16
16
  userInstruction?: string;
17
+ coordinatorCliType?: string;
17
18
  }
18
19
  export declare function buildCoordinatorSystemPrompt(ctx: CoordinatorPromptContext): string;
@@ -8,6 +8,7 @@ import { type ProviderModule } from './contracts.js';
8
8
  import type { ProviderInstance, ProviderState, InstanceContext, HotChatSessionState, SessionModalState } from './provider-instance.js';
9
9
  import { ProviderCliAdapter } from '../cli-adapters/provider-cli-adapter.js';
10
10
  import type { PtyTransportFactory } from '../cli-adapters/pty-transport.js';
11
+ import type { ChatMessage } from '../types.js';
11
12
  type PersistableCliHistoryMessage = {
12
13
  role: string;
13
14
  content: string;
@@ -67,6 +68,7 @@ export declare class CliProviderInstance implements ProviderInstance {
67
68
  constructor(provider: ProviderModule, workingDir: string, cliArgs?: string[], instanceId?: string, transportFactory?: PtyTransportFactory, options?: {
68
69
  providerSessionId?: string;
69
70
  launchMode?: 'new' | 'resume' | 'manual';
71
+ extraEnv?: Record<string, string>;
70
72
  onProviderSessionResolved?: (info: {
71
73
  instanceId: string;
72
74
  providerType: string;
@@ -112,6 +114,7 @@ export declare class CliProviderInstance implements ProviderInstance {
112
114
  private maybeAppendRuntimeRecoveryMessage;
113
115
  private appendRuntimeSystemMessage;
114
116
  private appendRuntimeMessage;
117
+ mergeRuntimeChatMessages(parsedMessages: ChatMessage[]): ChatMessage[];
115
118
  private mergeConversationMessages;
116
119
  private formatApprovalRequestMessage;
117
120
  private promoteProviderSessionId;
@@ -67,6 +67,7 @@ export declare class ProviderInstanceManager {
67
67
  onEvent(listener: (event: ProviderEvent & {
68
68
  providerType: string;
69
69
  }) => void): void;
70
+ emitProviderEvent(providerType: string, instanceId: string, event: ProviderEvent): void;
70
71
  private emitPendingEvents;
71
72
  /**
72
73
  * Forward event to specific Instance
@@ -147,6 +147,8 @@ export interface InstanceContext {
147
147
  onPtyData?: (data: string) => void;
148
148
  /** Provider configvalue (resolved) */
149
149
  settings: Record<string, any>;
150
+ /** Immediate provider-originated status/event emission. Used to avoid waiting for status polling. */
151
+ emitProviderEvent?: (event: ProviderEvent) => void;
150
152
  }
151
153
  export interface ProviderInstance {
152
154
  /** Provider type */
@@ -53,6 +53,8 @@ export interface RepoMeshNodePolicy {
53
53
  readOnly?: boolean;
54
54
  canPush?: boolean;
55
55
  maxConcurrentSessions?: number;
56
+ /** Ordered provider preference used when mesh_launch_session omits an explicit type. */
57
+ providerPriority?: string[];
56
58
  }
57
59
  export declare const DEFAULT_MESH_POLICY: RepoMeshPolicy;
58
60
  export interface RepoMeshNodeCapabilities {
@@ -149,6 +151,10 @@ export interface LocalMeshNodeEntry {
149
151
  policy: RepoMeshNodePolicy;
150
152
  /** For single-machine mesh: same daemon, different worktree */
151
153
  isLocalWorktree?: boolean;
154
+ /** Branch this worktree tracks (set when created via clone_mesh_node) */
155
+ worktreeBranch?: string;
156
+ /** Node ID this worktree was cloned from */
157
+ clonedFromNodeId?: string;
152
158
  }
153
159
  export interface RepoMeshStatus {
154
160
  meshId: string;
@@ -328,6 +328,15 @@ export interface CompactSessionEntry {
328
328
  settings?: Record<string, any>;
329
329
  }
330
330
  export type VersionUpdateReason = 'force_update_below' | 'major_minor_mismatch' | 'patch_mismatch' | 'daemon_ahead';
331
+ export type ReleaseChannel = 'stable' | 'preview';
332
+ export type NpmUpdateTag = 'latest' | 'next';
333
+ export interface VersionUpdatePolicy {
334
+ channel: ReleaseChannel;
335
+ npmTag: NpmUpdateTag;
336
+ targetVersion: string;
337
+ minVersion?: string;
338
+ updateCommand: string;
339
+ }
331
340
  /** Available provider information */
332
341
  export interface AvailableProviderInfo {
333
342
  type: string;
@@ -469,6 +478,10 @@ export interface CompactDaemonEntry {
469
478
  versionMismatch?: boolean;
470
479
  versionUpdateRequired?: boolean;
471
480
  versionUpdateReason?: VersionUpdateReason;
481
+ releaseChannel?: ReleaseChannel;
482
+ updateChannel?: ReleaseChannel;
483
+ updatePolicy?: VersionUpdatePolicy;
484
+ updateCommand?: string;
472
485
  terminalBackend?: TerminalBackendStatus;
473
486
  detectedIdes?: DetectedIdeInfo[];
474
487
  availableProviders?: AvailableProviderInfo[];
@@ -490,10 +503,14 @@ export interface CloudDaemonSummaryEntry {
490
503
  versionMismatch?: boolean;
491
504
  versionUpdateRequired?: boolean;
492
505
  versionUpdateReason?: VersionUpdateReason;
506
+ releaseChannel?: ReleaseChannel;
507
+ updateChannel?: ReleaseChannel;
508
+ updatePolicy?: VersionUpdatePolicy;
509
+ updateCommand?: string;
493
510
  terminalBackend?: TerminalBackendStatus;
494
511
  }
495
512
  /** Minimal daemon bootstrap payload used by dashboard WS to initiate P2P. */
496
- export interface DashboardBootstrapDaemonEntry {
513
+ export interface DashboardBootstrapDaemonEntry extends Partial<CloudDaemonSummaryEntry> {
497
514
  id: string;
498
515
  p2p?: StatusReportPayload['p2p'];
499
516
  timestamp?: number;
@@ -505,6 +522,8 @@ export interface DaemonStatusEventPayload {
505
522
  timestamp: number;
506
523
  targetSessionId?: string;
507
524
  providerType?: string;
525
+ providerSessionId?: string;
526
+ workspaceName?: string;
508
527
  duration?: number;
509
528
  elapsedSec?: number;
510
529
  modalMessage?: string;
@@ -518,6 +537,8 @@ export interface DashboardStatusEventPayload {
518
537
  daemonId?: string;
519
538
  providerType?: string;
520
539
  targetSessionId?: string;
540
+ providerSessionId?: string;
541
+ workspaceName?: string;
521
542
  duration?: number;
522
543
  elapsedSec?: number;
523
544
  modalMessage?: string;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@adhdev/daemon-core",
3
- "version": "0.9.76-rc.3",
3
+ "version": "0.9.76-rc.31",
4
4
  "description": "ADHDev daemon core — CDP, IDE detection, providers, command execution",
5
5
  "main": "dist/index.js",
6
6
  "types": "dist/index.d.ts",
@@ -52,16 +52,15 @@
52
52
  "chalk": "^5.3.0",
53
53
  "chokidar": "^5.0.0",
54
54
  "conf": "^13.0.0",
55
+ "js-yaml": "^4.1.1",
55
56
  "node-pty": "^1.2.0-beta.12",
56
57
  "ws": "^8.19.0"
57
58
  },
58
- "bundleDependencies": [
59
- "@adhdev/session-host-core"
60
- ],
61
59
  "optionalDependencies": {
62
60
  "@adhdev/ghostty-vt-node": "*"
63
61
  },
64
62
  "devDependencies": {
63
+ "@types/js-yaml": "^4.0.9",
65
64
  "@types/node": "^22.0.0",
66
65
  "@types/ws": "^8.18.1",
67
66
  "tsup": "^8.2.0",
@@ -266,9 +266,10 @@ export class ProviderCliAdapter implements CliAdapter {
266
266
  const currentSnapshot = normalizeScreenSnapshot(screenText);
267
267
  const lastSnapshot = this.lastScreenSnapshot;
268
268
  if (!lastSnapshot || lastSnapshot === currentSnapshot) return screenText;
269
- const staleSnapshotLooksActive = /\besc to (?:interrupt|stop)\b|Enter to interrupt, Ctrl\+C to cancel/i.test(lastSnapshot);
270
- const currentScreenLooksIdle = /(?:^|\n|\r)\s*[❯›>]\s*(?:\n|\r|$)/.test(screenText)
271
- && !/\besc to (?:interrupt|stop)\b|Enter to interrupt, Ctrl\+C to cancel/i.test(screenText);
269
+ const activeScreenPattern = /\besc to (?:interrupt|stop)\b|Enter to interrupt, Ctrl\+C to cancel|Enter to confirm\s*[·•-]\s*Esc to cancel|\b(?:MCP servers?|tool calls?)\b[^\n\r]{0,160}\brequire approval\b/i;
270
+ const staleSnapshotLooksActive = activeScreenPattern.test(lastSnapshot);
271
+ const currentScreenLooksIdle = /(?:^|\n|\r)\s*[❯›>]\s*(?:Try\s+["“][^\n\r"”]+["”])?\s*(?:\n|\r|$)/.test(screenText)
272
+ && !activeScreenPattern.test(screenText);
272
273
  if (staleSnapshotLooksActive && currentScreenLooksIdle) return screenText;
273
274
  if (currentSnapshot.length >= lastSnapshot.length) return screenText;
274
275
  // Terminal screen reads can miss a just-rendered completed Hermes box while
@@ -421,6 +422,7 @@ export class ProviderCliAdapter implements CliAdapter {
421
422
  provider: CliProviderModule,
422
423
  workingDir: string,
423
424
  private extraArgs: string[] = [],
425
+ private extraEnv: Record<string, string> = {},
424
426
  transportFactory: PtyTransportFactory = new NodePtyTransportFactory(),
425
427
  ) {
426
428
  this.provider = provider;
@@ -522,6 +524,7 @@ export class ProviderCliAdapter implements CliAdapter {
522
524
  runtimeSettings: this.runtimeSettings,
523
525
  workingDir: this.workingDir,
524
526
  extraArgs: this.extraArgs,
527
+ extraEnv: this.extraEnv,
525
528
  });
526
529
 
527
530
  LOG.info('CLI', `[${this.cliType}] Spawning in ${this.workingDir}`);
@@ -27,8 +27,9 @@ export function resolveCliSpawnPlan(options: {
27
27
  runtimeSettings: Record<string, any>;
28
28
  workingDir: string;
29
29
  extraArgs: string[];
30
+ extraEnv?: Record<string, string>;
30
31
  }): CliSpawnPlan {
31
- const { provider, runtimeSettings, workingDir, extraArgs } = options;
32
+ const { provider, runtimeSettings, workingDir, extraArgs, extraEnv } = options;
32
33
  const { spawn: spawnConfig } = provider;
33
34
  const configuredCommand = typeof runtimeSettings.executablePath === 'string' && runtimeSettings.executablePath.trim()
34
35
  ? runtimeSettings.executablePath.trim()
@@ -65,7 +66,7 @@ export function resolveCliSpawnPlan(options: {
65
66
  shellArgs = allArgs;
66
67
  }
67
68
 
68
- const env = buildCliSpawnEnv(process.env, spawnConfig.env);
69
+ const env = buildCliSpawnEnv(process.env, { ...(spawnConfig.env || {}), ...(extraEnv || {}) });
69
70
  // Some CLI agents, notably Hermes, route their tools through TERMINAL_CWD
70
71
  // rather than process.cwd(). Keep the generic ADHDev launch workspace as
71
72
  // the single source of truth so PTY cwd and tool cwd cannot diverge.
@@ -28,6 +28,10 @@ interface ApprovalSelectableInstance extends ProviderInstance {
28
28
  recordApprovalSelection?(buttonText: string): void;
29
29
  }
30
30
 
31
+ interface RuntimeChatMessageMerger extends ProviderInstance {
32
+ mergeRuntimeChatMessages?(messages: ChatMessage[]): ChatMessage[];
33
+ }
34
+
31
35
  type LegacyStringScript = (params?: Record<string, unknown> | string) => string;
32
36
 
33
37
  function getCurrentProviderType(h: CommandHelpers, fallback = ''): string {
@@ -250,6 +254,40 @@ function normalizeReadChatCommandStatus(status: unknown, activeModal: unknown):
250
254
  }
251
255
  }
252
256
 
257
+ function isGeneratingLikeStatus(status: unknown): boolean {
258
+ return status === 'generating' || status === 'streaming' || status === 'long_generating' || status === 'starting';
259
+ }
260
+
261
+ function shouldTrustCliAdapterTerminalStatus(parsedStatus: unknown, activeModal: unknown, adapter: CliAdapter, adapterStatus: any): boolean {
262
+ if (!isGeneratingLikeStatus(parsedStatus)) return false;
263
+ if (hasNonEmptyModalButtons(activeModal)) return false;
264
+ const adapterRawStatus = typeof adapterStatus?.status === 'string' ? adapterStatus.status.trim() : '';
265
+ if (adapterRawStatus !== 'idle') return false;
266
+ if (typeof adapter.isProcessing === 'function' && adapter.isProcessing()) return false;
267
+ return true;
268
+ }
269
+
270
+ function normalizeCliReadChatStatus(parsedStatus: unknown, activeModal: unknown, adapter: CliAdapter, adapterStatus: any): string {
271
+ if (shouldTrustCliAdapterTerminalStatus(parsedStatus, activeModal, adapter, adapterStatus)) return 'idle';
272
+ return typeof parsedStatus === 'string' && parsedStatus.trim() ? parsedStatus : 'idle';
273
+ }
274
+
275
+ function finalizeStreamingMessagesWhenIdle(messages: ChatMessage[], status: string): ChatMessage[] {
276
+ if (status !== 'idle') return messages;
277
+ return messages.map((message) => {
278
+ const meta = message.meta && typeof message.meta === 'object'
279
+ ? message.meta as Record<string, unknown>
280
+ : undefined;
281
+ const hasStreamingMeta = meta?.streaming === true;
282
+ if (message.bubbleState !== 'streaming' && !hasStreamingMeta) return message;
283
+ return {
284
+ ...message,
285
+ ...(message.bubbleState === 'streaming' ? { bubbleState: 'final' as const } : {}),
286
+ ...(hasStreamingMeta ? { meta: { ...meta, streaming: false } } : {}),
287
+ };
288
+ });
289
+ }
290
+
253
291
  function buildReadChatCommandResult(payload: Record<string, any>, args: any): CommandResult {
254
292
  let validatedPayload: Record<string, any>;
255
293
  const debugReadChat = payload?.debugReadChat && typeof payload.debugReadChat === 'object'
@@ -720,7 +758,7 @@ export async function handleChatHistory(h: CommandHelpers, args: any): Promise<C
720
758
  }
721
759
 
722
760
  export async function handleReadChat(h: CommandHelpers, args: any): Promise<CommandResult> {
723
- const provider = h.getProvider(args?.agentType);
761
+ const provider = h.getProvider(args?.agentType || args?.providerType);
724
762
  const transport = getTargetTransport(h, provider);
725
763
  const historySessionId = getHistorySessionId(h, args);
726
764
 
@@ -760,10 +798,17 @@ export async function handleReadChat(h: CommandHelpers, args: any): Promise<Comm
760
798
  ? parsedRecord.coverage
761
799
  : undefined;
762
800
  const activeModal = parsedRecord.activeModal ?? parsedRecord.modal ?? null;
763
- const returnedStatus = parsedRecord.status || 'idle';
764
- LOG.debug('Command', `[read_chat] cli-like parsed provider=${adapter.cliType} target=${String(args?.targetSessionId || '')} adapterStatus=${String(adapterStatus.status || '')} parsedStatus=${String(parsedRecord.status || '')} parsedMsgCount=${parsedRecord.messages.length}`);
801
+ const returnedStatus = normalizeCliReadChatStatus(parsedRecord.status, activeModal, adapter, adapterStatus);
802
+ const runtimeMessageMerger = getTargetInstance(h, args) as RuntimeChatMessageMerger | null;
803
+ const parsedMessages = finalizeStreamingMessagesWhenIdle(parsedRecord.messages as ChatMessage[], returnedStatus);
804
+ const returnedMessages = runtimeMessageMerger?.category === 'cli'
805
+ && runtimeMessageMerger.type === adapter.cliType
806
+ && typeof runtimeMessageMerger.mergeRuntimeChatMessages === 'function'
807
+ ? runtimeMessageMerger.mergeRuntimeChatMessages(parsedMessages)
808
+ : parsedMessages;
809
+ LOG.debug('Command', `[read_chat] cli-like parsed provider=${adapter.cliType} target=${String(args?.targetSessionId || '')} adapterStatus=${String(adapterStatus.status || '')} parsedStatus=${String(parsedRecord.status || '')} parsedMsgCount=${parsedRecord.messages.length} returnedMsgCount=${returnedMessages.length}`);
765
810
  return buildReadChatCommandResult({
766
- messages: parsedRecord.messages,
811
+ messages: returnedMessages,
767
812
  status: returnedStatus,
768
813
  activeModal,
769
814
  debugReadChat: {
@@ -774,7 +819,7 @@ export async function handleReadChat(h: CommandHelpers, args: any): Promise<Comm
774
819
  returnedStatus: String(returnedStatus || ''),
775
820
  shouldPreferAdapterMessages: false,
776
821
  parsedMsgCount: parsedRecord.messages.length,
777
- returnedMsgCount: parsedRecord.messages.length,
822
+ returnedMsgCount: returnedMessages.length,
778
823
  },
779
824
  ...(title ? { title } : {}),
780
825
  ...(providerSessionId ? { providerSessionId } : {}),
@@ -8,7 +8,7 @@
8
8
  import * as os from 'os';
9
9
  import * as path from 'path';
10
10
  import * as crypto from 'crypto';
11
- import { existsSync } from 'fs';
11
+ import { existsSync, mkdirSync, writeFileSync } from 'fs';
12
12
  import { execFileSync } from 'child_process';
13
13
  import chalk from 'chalk';
14
14
  import { ProviderCliAdapter } from '../cli-adapters/provider-cli-adapter.js';
@@ -132,6 +132,62 @@ type CliAdapterWithExtraArgs = CliAdapter & {
132
132
  extraArgs?: string[];
133
133
  };
134
134
 
135
+ type CliStartOptions = {
136
+ resumeSessionId?: string;
137
+ settingsOverride?: Record<string, any>;
138
+ extraEnv?: Record<string, string>;
139
+ };
140
+
141
+ const COORDINATOR_DELEGATED_ENV_UNSETS: Record<string, string> = {
142
+ ADHDEV_INLINE_MESH: '',
143
+ ADHDEV_MCP_TRANSPORT: '',
144
+ ADHDEV_MESH_ID: '',
145
+ HERMES_EPHEMERAL_SYSTEM_PROMPT: '',
146
+ };
147
+
148
+ export interface CoordinatorDelegatedCliLaunchOptionsInput {
149
+ cliType: string;
150
+ workspace: string;
151
+ cliArgs?: string[];
152
+ env?: Record<string, string>;
153
+ }
154
+
155
+ export interface CoordinatorDelegatedCliLaunchOptions {
156
+ cliArgs: string[];
157
+ env: Record<string, string>;
158
+ }
159
+
160
+ function hasCliArg(args: string[], flag: string): boolean {
161
+ return args.some((arg) => arg === flag || arg.startsWith(`${flag}=`));
162
+ }
163
+
164
+ function ensureEmptyDelegatedMcpConfig(workspace: string): string {
165
+ const baseDir = path.join(os.tmpdir(), 'adhdev-delegated-agent-empty-mcp');
166
+ mkdirSync(baseDir, { recursive: true });
167
+ const workspaceHash = crypto.createHash('sha256').update(path.resolve(workspace || os.tmpdir())).digest('hex').slice(0, 16);
168
+ const filePath = path.join(baseDir, `${workspaceHash}.json`);
169
+ writeFileSync(filePath, JSON.stringify({ mcpServers: {} }, null, 2), 'utf-8');
170
+ return filePath;
171
+ }
172
+
173
+ export function buildCoordinatorDelegatedCliLaunchOptions(
174
+ input: CoordinatorDelegatedCliLaunchOptionsInput,
175
+ ): CoordinatorDelegatedCliLaunchOptions {
176
+ const cliType = String(input.cliType || '').trim();
177
+ const cliArgs = Array.isArray(input.cliArgs) ? [...input.cliArgs] : [];
178
+ const env: Record<string, string> = { ...(input.env || {}), ...COORDINATOR_DELEGATED_ENV_UNSETS };
179
+
180
+ if (cliType === 'hermes-cli' && !hasCliArg(cliArgs, '--ignore-user-config')) {
181
+ cliArgs.unshift('--ignore-user-config');
182
+ }
183
+
184
+ if (cliType === 'claude-cli' && !hasCliArg(cliArgs, '--mcp-config')) {
185
+ cliArgs.unshift('--mcp-config', ensureEmptyDelegatedMcpConfig(input.workspace));
186
+ }
187
+
188
+ return { cliArgs, env };
189
+ }
190
+
135
191
  function isUuid(value: string): boolean {
136
192
  return /^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/i.test(value);
137
193
  }
@@ -365,6 +421,7 @@ export class DaemonCliManager {
365
421
  runtimeId: string,
366
422
  providerSessionId?: string,
367
423
  attachExisting = false,
424
+ extraEnv?: Record<string, string>,
368
425
  ): CliAdapter {
369
426
  // cliType normalize (Resolve alias)
370
427
  const normalizedType = this.providerLoader.resolveAlias(cliType);
@@ -382,7 +439,7 @@ export class DaemonCliManager {
382
439
  providerSessionId,
383
440
  attachExisting,
384
441
  );
385
- return new ProviderCliAdapter(resolvedProvider as CliProviderModule, workingDir, cliArgs, transportFactory);
442
+ return new ProviderCliAdapter(resolvedProvider as CliProviderModule, workingDir, cliArgs, extraEnv || {}, transportFactory);
386
443
  }
387
444
 
388
445
  throw new Error(`No CLI provider found for '${cliType}'. Create a provider.js in providers/cli/${cliType}/`);
@@ -425,6 +482,7 @@ export class DaemonCliManager {
425
482
  options?: {
426
483
  providerSessionId?: string;
427
484
  launchMode?: CliLaunchMode;
485
+ extraEnv?: Record<string, string>;
428
486
  onProviderSessionResolved?: (info: {
429
487
  instanceId: string;
430
488
  providerType: string;
@@ -480,7 +538,7 @@ export class DaemonCliManager {
480
538
  workingDir: string,
481
539
  cliArgs?: string[],
482
540
  initialModel?: string,
483
- options?: { resumeSessionId?: string, settingsOverride?: Record<string, any> },
541
+ options?: CliStartOptions,
484
542
  ): Promise<{ runtimeSessionId: string; providerSessionId?: string }> {
485
543
  const trimmed = (workingDir || '').trim();
486
544
  if (!trimmed) throw new Error('working directory required');
@@ -629,6 +687,7 @@ export class DaemonCliManager {
629
687
  {
630
688
  providerSessionId: sessionBinding.providerSessionId,
631
689
  launchMode: sessionBinding.launchMode,
690
+ extraEnv: options?.extraEnv,
632
691
  onProviderSessionResolved: ({ providerSessionId, providerName, providerType, workspace }) => {
633
692
  this.persistRecentActivity({
634
693
  kind: 'cli',
@@ -651,6 +710,7 @@ export class DaemonCliManager {
651
710
  key,
652
711
  sessionBinding.providerSessionId,
653
712
  false,
713
+ options?.extraEnv,
654
714
  );
655
715
  try {
656
716
  await adapter.spawn();
@@ -899,12 +959,25 @@ export class DaemonCliManager {
899
959
  const launchSource = resolved.source;
900
960
  if (!cliType) throw new Error('cliType required');
901
961
 
962
+ const settingsOverride = args?.settings && typeof args.settings === 'object' ? args.settings : undefined;
963
+ const delegatedLaunch = settingsOverride?.launchedByCoordinator === true
964
+ ? buildCoordinatorDelegatedCliLaunchOptions({
965
+ cliType,
966
+ workspace: dir,
967
+ cliArgs: args?.cliArgs,
968
+ env: args?.env,
969
+ })
970
+ : null;
902
971
  const started = await this.startSession(
903
972
  cliType,
904
973
  dir,
905
- args?.cliArgs,
974
+ delegatedLaunch ? delegatedLaunch.cliArgs : args?.cliArgs,
906
975
  args?.initialModel,
907
- { resumeSessionId: args?.resumeSessionId, settingsOverride: args?.settings },
976
+ {
977
+ resumeSessionId: args?.resumeSessionId,
978
+ settingsOverride,
979
+ extraEnv: delegatedLaunch ? delegatedLaunch.env : args?.env,
980
+ },
908
981
  );
909
982
 
910
983
  return {
@@ -303,14 +303,15 @@ export class DaemonCommandHandler implements CommandHelpers {
303
303
  const sessionLookupFailed = !!targetSessionId && !session;
304
304
 
305
305
  const managerKey = this.extractIdeType(args, sessionLookupFailed);
306
- let providerType: string | undefined;
306
+ let providerType: string | undefined = args?.agentType || args?.providerType;
307
307
 
308
308
  if (!sessionLookupFailed) {
309
309
  providerType =
310
310
  session?.providerType
311
- || args?.agentType
312
- || args?.providerType
311
+ || providerType
313
312
  || this.inferProviderType(managerKey);
313
+ } else if (!providerType) {
314
+ providerType = this.inferProviderType(managerKey);
314
315
  }
315
316
 
316
317
  return { session, managerKey, providerType, sessionLookupFailed };
@@ -407,7 +408,15 @@ export class DaemonCommandHandler implements CommandHelpers {
407
408
  'invoke_provider_script',
408
409
  ]);
409
410
 
410
- if (this._currentRoute.sessionLookupFailed && sessionScopedCommands.has(cmd)) {
411
+ const allowsInactiveReadChatFallback =
412
+ cmd === 'read_chat'
413
+ && !!this._currentRoute.providerType
414
+ && (
415
+ (typeof args?.providerSessionId === 'string' && args.providerSessionId.trim().length > 0)
416
+ || (typeof args?.historySessionId === 'string' && args.historySessionId.trim().length > 0)
417
+ );
418
+
419
+ if (this._currentRoute.sessionLookupFailed && sessionScopedCommands.has(cmd) && !allowsInactiveReadChatFallback) {
411
420
  const result = {
412
421
  success: false,
413
422
  error: `Live session not found for targetSessionId: ${String(args?.targetSessionId || '').trim() || 'unknown'}`,