@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/dist/index.js +64 -11
- package/dist/index.js.map +1 -1
- package/dist/index.mjs +64 -11
- package/dist/index.mjs.map +1 -1
- package/package.json +1 -1
- package/src/commands/chat-commands.ts +11 -5
- package/src/commands/router.ts +43 -2
- package/src/mesh/mesh-events-coordinator.ts +29 -5
- package/src/status/builders.ts +3 -1
package/package.json
CHANGED
|
@@ -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
|
|
2552
|
+
const currentSessionWorkspace = typeof (h.currentSession as any)?.workspace === 'string'
|
|
2553
2553
|
? (h.currentSession as any).workspace
|
|
2554
|
-
:
|
|
2555
|
-
|
|
2556
|
-
|
|
2557
|
-
|
|
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
|
package/src/commands/router.ts
CHANGED
|
@@ -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
|
-
|
|
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
|
-
|
|
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(
|
|
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) =>
|
|
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: '
|
|
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;
|
package/src/status/builders.ts
CHANGED
|
@@ -326,7 +326,7 @@ function buildCliSession(state: CliProviderState, options: SessionEntryBuildOpti
|
|
|
326
326
|
mode: state.mode,
|
|
327
327
|
resume: state.resume,
|
|
328
328
|
activeChat,
|
|
329
|
-
|
|
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
|
|