@adhdev/daemon-core 0.9.82-rc.227 → 0.9.82-rc.229

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@adhdev/daemon-core",
3
- "version": "0.9.82-rc.227",
3
+ "version": "0.9.82-rc.229",
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",
@@ -2549,12 +2549,18 @@ export async function handleReadChat(h: CommandHelpers, args: any): Promise<Comm
2549
2549
  const registrySessionWorkspace = targetSid
2550
2550
  ? (h.ctx?.sessionRegistry?.get?.(targetSid) as any)?.workspace
2551
2551
  : undefined;
2552
- const workspace = typeof (h.currentSession as any)?.workspace === 'string'
2552
+ const currentSessionWorkspace = typeof (h.currentSession as any)?.workspace === 'string'
2553
2553
  ? (h.currentSession as any).workspace
2554
- : typeof registrySessionWorkspace === 'string'
2555
- ? registrySessionWorkspace
2556
- : undefined;
2557
- const intendedWorkspace = typeof args?.workspace === 'string' ? args.workspace : undefined;
2554
+ : undefined;
2555
+ const argsWorkspace = typeof args?.workspace === 'string' ? args.workspace : undefined;
2556
+ // When reading a different session (targetSid), prefer that session's registered
2557
+ // workspace (or the caller-supplied args.workspace) over the current (coordinator)
2558
+ // session's workspace — otherwise the coordinator's cwd shadows the worker's cwd
2559
+ // and history lookups find the wrong files.
2560
+ const workspace = targetSid
2561
+ ? (typeof registrySessionWorkspace === 'string' ? registrySessionWorkspace : argsWorkspace ?? currentSessionWorkspace)
2562
+ : (typeof currentSessionWorkspace === 'string' ? currentSessionWorkspace : undefined);
2563
+ const intendedWorkspace = argsWorkspace;
2558
2564
  const supportsNative = supportsCliNativeTranscript(agentStr, provider)
2559
2565
  && isNativeSourceCanonicalHistory(provider?.nativeHistory);
2560
2566
  const history = supportsNative
@@ -5852,14 +5852,28 @@ export class DaemonCommandRouter {
5852
5852
  let submoduleIgnorePaths = Array.isArray(args?.submoduleIgnorePaths)
5853
5853
  ? args.submoduleIgnorePaths.filter((value: unknown): value is string => typeof value === 'string')
5854
5854
  : undefined;
5855
- if (!workspace && meshId && nodeId) {
5855
+ let nodeDaemonId: string | undefined;
5856
+ if (meshId && nodeId) {
5856
5857
  const meshRecord = await this.getMeshForCommand(meshId, args?.inlineMesh);
5857
5858
  const mesh = meshRecord?.mesh;
5858
5859
  const node = mesh?.nodes?.find((n: any) => n.id === nodeId || n.nodeId === nodeId);
5859
- workspace = typeof node?.workspace === 'string' ? node.workspace.trim() : '';
5860
+ if (!workspace) {
5861
+ workspace = typeof node?.workspace === 'string' ? node.workspace.trim() : '';
5862
+ }
5860
5863
  if (!submoduleIgnorePaths && Array.isArray(node?.policy?.submoduleIgnorePaths)) {
5861
5864
  submoduleIgnorePaths = node.policy.submoduleIgnorePaths.filter((value: unknown): value is string => typeof value === 'string');
5862
5865
  }
5866
+ nodeDaemonId = typeof node?.daemonId === 'string' ? node.daemonId.trim() : undefined;
5867
+ }
5868
+ // If the target node belongs to a remote daemon, forward the command there.
5869
+ const selfDaemonId = this.deps.statusInstanceId;
5870
+ const isRemote = nodeDaemonId && selfDaemonId && nodeDaemonId !== selfDaemonId;
5871
+ if (isRemote && this.deps.dispatchMeshCommand) {
5872
+ const forwarded = await this.deps.dispatchMeshCommand(nodeDaemonId!, 'fast_forward_mesh_node', {
5873
+ ...(typeof args === 'object' && args !== null ? args as Record<string, unknown> : {}),
5874
+ workspace,
5875
+ });
5876
+ return (forwarded ?? { success: false, error: 'no response from remote node' }) as CommandRouterResult;
5863
5877
  }
5864
5878
  const result = await (fastForwardMeshNode({
5865
5879
  meshId: meshId || undefined,
@@ -5901,6 +5915,15 @@ export class DaemonCommandRouter {
5901
5915
 
5902
5916
  let worktreeCleanup: Record<string, unknown> | undefined;
5903
5917
  if (node?.isLocalWorktree) {
5918
+ const nodeDaemonId = typeof node.daemonId === 'string' ? node.daemonId.trim() : undefined;
5919
+ const isRemoteWorktree = nodeDaemonId && nodeDaemonId !== this.deps.statusInstanceId && this.deps.dispatchMeshCommand;
5920
+ if (isRemoteWorktree) {
5921
+ // Worktree lives on a different machine — ask that daemon to clean it up.
5922
+ const forwarded = await this.deps.dispatchMeshCommand!(nodeDaemonId!, 'remove_mesh_node', {
5923
+ ...(typeof args === 'object' && args !== null ? args as Record<string, unknown> : {}),
5924
+ });
5925
+ return (forwarded ?? { success: false, error: 'no response from remote node' }) as CommandRouterResult;
5926
+ }
5904
5927
  const cleanupResult = await this.cleanupLocalWorktreeNode({ mesh, node, nodeId, force: args?.force === true });
5905
5928
  if (cleanupResult.success === false) {
5906
5929
  return {
@@ -5983,6 +6006,15 @@ export class DaemonCommandRouter {
5983
6006
  const sourceNode = mesh.nodes?.find((n: any) => n.id === sourceNodeId || n.nodeId === sourceNodeId);
5984
6007
  if (!sourceNode) return { success: false, error: `Source node '${sourceNodeId}' not found in mesh` };
5985
6008
 
6009
+ // Forward to the source node's daemon if it's on a different machine.
6010
+ const sourceDaemonId = typeof sourceNode.daemonId === 'string' ? sourceNode.daemonId.trim() : undefined;
6011
+ if (sourceDaemonId && sourceDaemonId !== this.deps.statusInstanceId && this.deps.dispatchMeshCommand) {
6012
+ const forwarded = await this.deps.dispatchMeshCommand(sourceDaemonId, 'clone_mesh_node', {
6013
+ ...(typeof args === 'object' && args !== null ? args as Record<string, unknown> : {}),
6014
+ });
6015
+ return (forwarded ?? { success: false, error: 'no response from remote node' }) as CommandRouterResult;
6016
+ }
6017
+
5986
6018
  const repoRoot = sourceNode.repoRoot || sourceNode.workspace;
5987
6019
  const { createWorktree } = await import('../git/git-worktree.js');
5988
6020
  const result = await createWorktree({
@@ -6238,6 +6270,15 @@ export class DaemonCommandRouter {
6238
6270
  if (!node) return { success: false, error: `Node '${nodeId}' not found in mesh` };
6239
6271
  if (!node.isLocalWorktree) return { success: false, error: 'Node is not a local worktree node' };
6240
6272
 
6273
+ // Bootstrap runs scripts in the worktree path — forward to the node's daemon if remote.
6274
+ const nodeDaemonId = typeof node.daemonId === 'string' ? node.daemonId.trim() : undefined;
6275
+ if (nodeDaemonId && nodeDaemonId !== this.deps.statusInstanceId && this.deps.dispatchMeshCommand) {
6276
+ const forwarded = await this.deps.dispatchMeshCommand(nodeDaemonId, 'retry_mesh_node_bootstrap', {
6277
+ ...(typeof args === 'object' && args !== null ? args as Record<string, unknown> : {}),
6278
+ });
6279
+ return (forwarded ?? { success: false, error: 'no response from remote node' }) as CommandRouterResult;
6280
+ }
6281
+
6241
6282
  const currentBootstrap = node.worktreeBootstrap as WorktreeBootstrapState | undefined;
6242
6283
  if (currentBootstrap?.status === 'running') {
6243
6284
  return { success: false, error: 'Bootstrap is already running for this node' };
@@ -6,7 +6,7 @@ import { detectCLI } from '../detection/cli-detector.js';
6
6
  import { LOG } from '../logging/logger.js';
7
7
  import { appendLedgerEntry, buildTaskCompletionEvidence, getSessionRecoveryContext, isIntentionalCleanupStopEntry, readLedgerEntries } from './mesh-ledger.js';
8
8
  import type { MeshLedgerKind, SessionRecoveryContext } from './mesh-ledger.js';
9
- import { buildMeshNodeCapabilityTags, claimNextTask, updateSessionTaskStatus, enqueueTask, updateTaskStatus, getQueue, recordTaskAutoLaunch, updateDirectDispatchStatus, cleanupTerminalDirectDispatches, getActiveDirectDispatches, hasPendingDependents } from './mesh-work-queue.js';
9
+ import { buildMeshNodeCapabilityTags, nodeSatisfiesRequiredTags, claimNextTask, updateSessionTaskStatus, enqueueTask, updateTaskStatus, getQueue, recordTaskAutoLaunch, updateDirectDispatchStatus, cleanupTerminalDirectDispatches, getActiveDirectDispatches, hasPendingDependents } from './mesh-work-queue.js';
10
10
  import { fastForwardMeshNode } from './mesh-fast-forward.js';
11
11
  import { createSessionDelivery, markSessionDeliveriesTerminal, updateSessionDeliveryStatus, recordCompletionConflict } from './mesh-delivery-policy.js';
12
12
  import { MeshRuntimeStore } from './mesh-runtime-store.js';
@@ -499,7 +499,12 @@ function markAutoLaunch(meshId: string, taskId: string, args: {
499
499
  });
500
500
  }
501
501
 
502
- async function resolveUsableProvider(components: DaemonComponents, nodeId: string, node: any): Promise<{ providerType?: string; reason?: string }> {
502
+ async function resolveUsableProvider(
503
+ components: DaemonComponents,
504
+ nodeId: string,
505
+ node: any,
506
+ requiredTags?: string[],
507
+ ): Promise<{ providerType?: string; reason?: string }> {
503
508
  const providerPriority = normalizeProviderPriority(node?.policy);
504
509
  if (!providerPriority.length) return { reason: 'missing_provider_priority' };
505
510
  const providerLoader = components.providerLoader;
@@ -510,6 +515,12 @@ async function resolveUsableProvider(components: DaemonComponents, nodeId: strin
510
515
  const normalizedType = typeof providerLoader.resolveAlias === 'function'
511
516
  ? providerLoader.resolveAlias(requestedType)
512
517
  : requestedType;
518
+ // Skip providers that can't satisfy the task's requiredTags (e.g. provider=hermes-cli
519
+ // means only hermes-cli qualifies, not any other type in providerPriority).
520
+ if (requiredTags?.length && !nodeSatisfiesRequiredTags(requiredTags, buildMeshNodeCapabilityTags(node, normalizedType))) {
521
+ failed.push(`${requestedType}: required_tags_mismatch`);
522
+ continue;
523
+ }
513
524
  if (typeof providerLoader.isMachineProviderEnabled === 'function' && !providerLoader.isMachineProviderEnabled(normalizedType)) {
514
525
  failed.push(`${requestedType}: disabled`);
515
526
  continue;
@@ -552,10 +563,23 @@ async function maybeAutoLaunchOneQueueSession(components: DaemonComponents, mesh
552
563
  }
553
564
 
554
565
  const candidateNodes = Array.isArray(mesh?.nodes)
555
- ? mesh.nodes.filter((node: any) => task.targetNodeId ? node?.id === task.targetNodeId : true)
566
+ ? mesh.nodes.filter((node: any) => {
567
+ if (task.targetNodeId && node?.id !== task.targetNodeId) return false;
568
+ // Skip nodes that can never satisfy requiredTags regardless of which provider
569
+ // from providerPriority is selected. A node satisfies tags if at least one
570
+ // provider in its priority list would produce matching capability tags.
571
+ if (task.requiredTags?.length) {
572
+ const priorities = normalizeProviderPriority(node?.policy);
573
+ const providerCandidates = priorities.length ? priorities : [undefined as unknown as string];
574
+ return providerCandidates.some(p =>
575
+ nodeSatisfiesRequiredTags(task.requiredTags, buildMeshNodeCapabilityTags(node, p))
576
+ );
577
+ }
578
+ return true;
579
+ })
556
580
  : [];
557
581
  if (!candidateNodes.length) {
558
- markAutoLaunch(meshId, task.id, { status: 'skipped', reason: 'no_matching_node', nodeId: task.targetNodeId });
582
+ markAutoLaunch(meshId, task.id, { status: 'skipped', reason: 'no_node_satisfies_required_tags', nodeId: task.targetNodeId });
559
583
  continue;
560
584
  }
561
585
 
@@ -599,7 +623,7 @@ async function maybeAutoLaunchOneQueueSession(components: DaemonComponents, mesh
599
623
 
600
624
  autoLaunchInProgress.add(launchKey);
601
625
  try {
602
- const resolved = await resolveUsableProvider(components, nodeId, node);
626
+ const resolved = await resolveUsableProvider(components, nodeId, node, task.requiredTags);
603
627
  if (!resolved.providerType) {
604
628
  markAutoLaunch(meshId, task.id, { status: 'skipped', reason: resolved.reason || 'provider_unusable', nodeId });
605
629
  continue;
@@ -326,7 +326,7 @@ function buildCliSession(state: CliProviderState, options: SessionEntryBuildOpti
326
326
  mode: state.mode,
327
327
  resume: state.resume,
328
328
  activeChat,
329
- ...(state.activeInteractivePrompt ? { activeInteractivePrompt: state.activeInteractivePrompt } : {}),
329
+ activeInteractivePrompt: state.activeInteractivePrompt ?? null,
330
330
  ...(summaryMetadata && { summaryMetadata }),
331
331
  ...(includeSessionMetadata && {
332
332
  capabilities: state.mode === 'terminal' ? PTY_SESSION_CAPABILITIES : CLI_CHAT_SESSION_CAPABILITIES,
@@ -342,6 +342,7 @@ function buildCliSession(state: CliProviderState, options: SessionEntryBuildOpti
342
342
  settings: state.settings,
343
343
  ...(coordinator && { coordinator }),
344
344
  ...(meshQueueStats && { meshQueueStats }),
345
+ ...(state.settings?.spawnedSessionVisibility === 'hidden' && { surfaceHidden: true }),
345
346
  };
346
347
  }
347
348
 
@@ -383,6 +384,7 @@ function buildAcpSession(state: AcpProviderState, options: SessionEntryBuildOpti
383
384
  settings: state.settings,
384
385
  ...(coordinator && { coordinator }),
385
386
  ...(meshQueueStats && { meshQueueStats }),
387
+ ...(state.settings?.spawnedSessionVisibility === 'hidden' && { surfaceHidden: true }),
386
388
  };
387
389
  }
388
390