@adhdev/daemon-core 0.9.82-rc.360 → 0.9.82-rc.361
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/commands/cli-manager.d.ts +13 -0
- package/dist/commands/low-family/index.d.ts +3 -0
- package/dist/commands/low-family/refine-config.d.ts +2 -0
- package/dist/commands/low-family/session-host.d.ts +2 -0
- package/dist/commands/low-family/spec-providerdev.d.ts +11 -0
- package/dist/commands/low-family/types.d.ts +16 -0
- package/dist/commands/router.d.ts +0 -1
- package/dist/index.js +2074 -2008
- package/dist/index.js.map +1 -1
- package/dist/index.mjs +2073 -2007
- package/dist/index.mjs.map +1 -1
- package/dist/mesh/mesh-events-utils.d.ts +0 -2
- package/package.json +2 -2
- package/src/commands/cli-manager.ts +69 -4
- package/src/commands/low-family/index.ts +23 -0
- package/src/commands/low-family/refine-config.ts +106 -0
- package/src/commands/low-family/session-host.ts +274 -0
- package/src/commands/low-family/spec-providerdev.ts +217 -0
- package/src/commands/low-family/types.ts +19 -0
- package/src/commands/router.ts +15 -555
- package/src/mesh/contracts.ts +12 -3
- package/src/mesh/mesh-events-coordinator.ts +1 -1
- package/src/mesh/mesh-events-utils.ts +0 -20
- package/src/providers/cli-provider-instance.ts +16 -3
package/src/commands/router.ts
CHANGED
|
@@ -12,6 +12,7 @@
|
|
|
12
12
|
import { DaemonCdpManager } from '../cdp/manager.js';
|
|
13
13
|
import { registerExtensionProviders } from '../cdp/setup.js';
|
|
14
14
|
import { DaemonCommandHandler } from './handler.js';
|
|
15
|
+
import { lowFamilyRegistry } from './low-family/index.js';
|
|
15
16
|
import { DaemonCliManager } from './cli-manager.js';
|
|
16
17
|
import { supportsExplicitSessionResume } from './cli-manager.js';
|
|
17
18
|
import type { HostedCliRuntimeDescriptor } from './cli-manager.js';
|
|
@@ -336,7 +337,7 @@ function buildMeshNodeMachineIdentity(node: Record<string, unknown>, opts: {
|
|
|
336
337
|
const machineName = readMeshNodeDisplayMachineName(node);
|
|
337
338
|
const coordinatorHostname = readStringValue(opts.coordinatorHostname);
|
|
338
339
|
const machineIdMatches = Boolean(opts.localMachineId && machineId && opts.localMachineId === machineId);
|
|
339
|
-
const daemonIdMatches = Boolean(opts.localDaemonId && daemonId && opts.localDaemonId
|
|
340
|
+
const daemonIdMatches = Boolean(opts.localDaemonId && daemonId && daemonIdsEquivalent(opts.localDaemonId, daemonId));
|
|
340
341
|
const hostnameMatches = Boolean(
|
|
341
342
|
normalizeMeshHostname(hostname)
|
|
342
343
|
&& normalizeMeshHostname(coordinatorHostname)
|
|
@@ -1403,7 +1404,7 @@ async function hydrateInlineMeshDirectTruth(args: {
|
|
|
1403
1404
|
const isSelfNode = Boolean(
|
|
1404
1405
|
nodeId && selectedCoordinatorNodeId && nodeId === selectedCoordinatorNodeId,
|
|
1405
1406
|
) || Boolean(
|
|
1406
|
-
daemonId && (daemonId
|
|
1407
|
+
daemonId && (daemonIdsEquivalent(daemonId, args.localMachineId) || daemonIdsEquivalent(daemonId, args.statusInstanceId)),
|
|
1407
1408
|
) || Boolean(args.meshSource !== 'local_config' && nodeIndex === 0);
|
|
1408
1409
|
|
|
1409
1410
|
// A dead local worktree owned by this coordinator (isLocalWorktree, the
|
|
@@ -1415,7 +1416,7 @@ async function hydrateInlineMeshDirectTruth(args: {
|
|
|
1415
1416
|
// strictly self + isLocalWorktree + absent-path; remote peers and nodes
|
|
1416
1417
|
// whose workspace still exists are unaffected and stay classifiable.
|
|
1417
1418
|
const isSelfDaemonNode = Boolean(
|
|
1418
|
-
daemonId && (daemonId
|
|
1419
|
+
daemonId && (daemonIdsEquivalent(daemonId, args.localMachineId) || daemonIdsEquivalent(daemonId, args.statusInstanceId)),
|
|
1419
1420
|
);
|
|
1420
1421
|
if ((isSelfNode || isSelfDaemonNode) && isDeadLocalWorktreeNode(node)) {
|
|
1421
1422
|
deadNodeIds.push(nodeId);
|
|
@@ -3501,117 +3502,6 @@ function normalizeCommandArgsWithInteractionId(args: any): Record<string, unknow
|
|
|
3501
3502
|
* basename to be a literal `*.json`, and re-joins under the verified parent so
|
|
3502
3503
|
* the returned path can't point outside the tree. Used by get/write_spec_source.
|
|
3503
3504
|
*/
|
|
3504
|
-
function resolveSpecPathInProviders(
|
|
3505
|
-
specPath: string,
|
|
3506
|
-
fsm: typeof import('node:fs'),
|
|
3507
|
-
pathm: typeof import('node:path'),
|
|
3508
|
-
osm: typeof import('node:os'),
|
|
3509
|
-
): { ok: true; path: string } | { ok: false; error: string } {
|
|
3510
|
-
let rootReal: string;
|
|
3511
|
-
try {
|
|
3512
|
-
rootReal = fsm.realpathSync(pathm.join(osm.homedir(), '.adhdev', 'providers'));
|
|
3513
|
-
} catch (e) {
|
|
3514
|
-
return { ok: false, error: `providers root unavailable: ${(e as Error).message}` };
|
|
3515
|
-
}
|
|
3516
|
-
const resolved = pathm.resolve(specPath);
|
|
3517
|
-
const base = pathm.basename(resolved);
|
|
3518
|
-
if (!/^[\w.-]+\.json$/.test(base)) {
|
|
3519
|
-
return { ok: false, error: 'refused: spec file must be a *.json basename' };
|
|
3520
|
-
}
|
|
3521
|
-
let parentReal: string;
|
|
3522
|
-
try {
|
|
3523
|
-
parentReal = fsm.realpathSync(pathm.dirname(resolved));
|
|
3524
|
-
} catch (e) {
|
|
3525
|
-
return { ok: false, error: `spec directory not found: ${(e as Error).message}` };
|
|
3526
|
-
}
|
|
3527
|
-
if (parentReal !== rootReal && !parentReal.startsWith(rootReal + pathm.sep)) {
|
|
3528
|
-
return { ok: false, error: 'refused: spec path must be under the providers root' };
|
|
3529
|
-
}
|
|
3530
|
-
const safe = pathm.join(parentReal, base);
|
|
3531
|
-
// Reject if the final file itself is a symlink pointing elsewhere.
|
|
3532
|
-
try {
|
|
3533
|
-
const st = fsm.lstatSync(safe);
|
|
3534
|
-
if (st.isSymbolicLink()) return { ok: false, error: 'refused: spec path is a symlink' };
|
|
3535
|
-
} catch { /* file may not exist yet (write case) — fine */ }
|
|
3536
|
-
return { ok: true, path: safe };
|
|
3537
|
-
}
|
|
3538
|
-
|
|
3539
|
-
function toHostedCliRuntimeDescriptor(record: any): HostedCliRuntimeDescriptor | null {
|
|
3540
|
-
if (!record || typeof record !== 'object') return null;
|
|
3541
|
-
const runtimeId = typeof record.sessionId === 'string' ? record.sessionId : '';
|
|
3542
|
-
const cliType = typeof record.providerType === 'string' ? record.providerType : '';
|
|
3543
|
-
const workspace = typeof record.workspace === 'string' ? record.workspace : '';
|
|
3544
|
-
if (!runtimeId || !cliType || !workspace) return null;
|
|
3545
|
-
return {
|
|
3546
|
-
runtimeId,
|
|
3547
|
-
runtimeKey: typeof record.runtimeKey === 'string' ? record.runtimeKey : undefined,
|
|
3548
|
-
displayName: typeof record.displayName === 'string' ? record.displayName : undefined,
|
|
3549
|
-
workspaceLabel: typeof record.workspaceLabel === 'string' ? record.workspaceLabel : undefined,
|
|
3550
|
-
lifecycle: typeof record.lifecycle === 'string' ? record.lifecycle as HostedCliRuntimeDescriptor['lifecycle'] : undefined,
|
|
3551
|
-
recoveryState: typeof record.meta?.runtimeRecoveryState === 'string'
|
|
3552
|
-
? String(record.meta.runtimeRecoveryState)
|
|
3553
|
-
: null,
|
|
3554
|
-
cliType,
|
|
3555
|
-
workspace,
|
|
3556
|
-
cliArgs: Array.isArray(record.meta?.cliArgs) ? record.meta.cliArgs as string[] : [],
|
|
3557
|
-
providerSessionId: typeof record.meta?.providerSessionId === 'string'
|
|
3558
|
-
? String(record.meta.providerSessionId)
|
|
3559
|
-
: undefined,
|
|
3560
|
-
};
|
|
3561
|
-
}
|
|
3562
|
-
|
|
3563
|
-
function getWriteConflictOwnerClientId(error: unknown): string | undefined {
|
|
3564
|
-
const message = typeof error === 'string'
|
|
3565
|
-
? error
|
|
3566
|
-
: error instanceof Error
|
|
3567
|
-
? error.message
|
|
3568
|
-
: '';
|
|
3569
|
-
const match = /^Write owned by\s+(.+)$/.exec(message.trim());
|
|
3570
|
-
return match?.[1]?.trim() || undefined;
|
|
3571
|
-
}
|
|
3572
|
-
|
|
3573
|
-
function summarizeSessionHostRecord(result: unknown): Record<string, unknown> {
|
|
3574
|
-
if (!result || typeof result !== 'object') return {};
|
|
3575
|
-
const record = result as Record<string, any>;
|
|
3576
|
-
return {
|
|
3577
|
-
runtimeKey: typeof record.runtimeKey === 'string' ? record.runtimeKey : undefined,
|
|
3578
|
-
lifecycle: typeof record.lifecycle === 'string' ? record.lifecycle : undefined,
|
|
3579
|
-
surfaceKind: getSessionHostSurfaceKind(record as any),
|
|
3580
|
-
attachedClientCount: Array.isArray(record.attachedClients) ? record.attachedClients.length : undefined,
|
|
3581
|
-
hasWriteOwner: !!record.writeOwner,
|
|
3582
|
-
writeOwnerClientId: typeof record.writeOwner?.clientId === 'string' ? record.writeOwner.clientId : undefined,
|
|
3583
|
-
};
|
|
3584
|
-
}
|
|
3585
|
-
|
|
3586
|
-
function summarizeSessionHostRecords(result: unknown): Record<string, unknown> {
|
|
3587
|
-
const records = Array.isArray(result) ? result : [];
|
|
3588
|
-
const groups = partitionSessionHostRecords(records as any[]);
|
|
3589
|
-
return {
|
|
3590
|
-
sessionCount: records.length,
|
|
3591
|
-
liveRuntimeCount: groups.liveRuntimes.length,
|
|
3592
|
-
recoverySnapshotCount: groups.recoverySnapshots.length,
|
|
3593
|
-
inactiveRecordCount: groups.inactiveRecords.length,
|
|
3594
|
-
};
|
|
3595
|
-
}
|
|
3596
|
-
|
|
3597
|
-
function summarizeSessionHostDiagnostics(result: unknown): Record<string, unknown> {
|
|
3598
|
-
const diagnostics = result && typeof result === 'object' ? result as Record<string, any> : {};
|
|
3599
|
-
const sessions = Array.isArray(diagnostics.sessions) ? diagnostics.sessions : [];
|
|
3600
|
-
return {
|
|
3601
|
-
runtimeCount: typeof diagnostics.runtimeCount === 'number' ? diagnostics.runtimeCount : undefined,
|
|
3602
|
-
...summarizeSessionHostRecords(sessions),
|
|
3603
|
-
};
|
|
3604
|
-
}
|
|
3605
|
-
|
|
3606
|
-
function summarizeSessionHostPruneResult(result: unknown): Record<string, unknown> {
|
|
3607
|
-
const value = result && typeof result === 'object' ? result as Record<string, any> : {};
|
|
3608
|
-
return {
|
|
3609
|
-
duplicateGroupCount: typeof value.duplicateGroupCount === 'number' ? value.duplicateGroupCount : undefined,
|
|
3610
|
-
prunedCount: Array.isArray(value.prunedSessionIds) ? value.prunedSessionIds.length : undefined,
|
|
3611
|
-
keptCount: Array.isArray(value.keptSessionIds) ? value.keptSessionIds.length : undefined,
|
|
3612
|
-
};
|
|
3613
|
-
}
|
|
3614
|
-
|
|
3615
3505
|
function normalizeStandaloneHostCommandUrl(hostAddress: string): string {
|
|
3616
3506
|
const raw = hostAddress.trim();
|
|
3617
3507
|
if (!raw) throw new Error('hostAddress required');
|
|
@@ -4671,65 +4561,6 @@ export class DaemonCommandRouter {
|
|
|
4671
4561
|
...(errors.length ? { errors } : {}),
|
|
4672
4562
|
};
|
|
4673
4563
|
}
|
|
4674
|
-
|
|
4675
|
-
private async traceSessionHostAction<T>(
|
|
4676
|
-
action: string,
|
|
4677
|
-
args: any,
|
|
4678
|
-
run: () => Promise<T>,
|
|
4679
|
-
summarizeResult?: (result: T) => Record<string, unknown>,
|
|
4680
|
-
): Promise<T> {
|
|
4681
|
-
const interactionId = typeof args?._interactionId === 'string' ? args._interactionId : undefined;
|
|
4682
|
-
const sessionId = typeof args?.sessionId === 'string' ? args.sessionId : undefined;
|
|
4683
|
-
const requestedPayload: Record<string, unknown> = { action };
|
|
4684
|
-
if (sessionId) requestedPayload.sessionId = sessionId;
|
|
4685
|
-
if (typeof args?.clientId === 'string') requestedPayload.clientId = args.clientId;
|
|
4686
|
-
if (typeof args?.signal === 'string') requestedPayload.signal = args.signal;
|
|
4687
|
-
if (typeof args?.providerType === 'string') requestedPayload.providerType = args.providerType;
|
|
4688
|
-
if (typeof args?.workspace === 'string') requestedPayload.workspace = args.workspace;
|
|
4689
|
-
if (typeof args?.dryRun === 'boolean') requestedPayload.dryRun = args.dryRun;
|
|
4690
|
-
|
|
4691
|
-
recordDebugTrace({
|
|
4692
|
-
interactionId,
|
|
4693
|
-
category: 'session_host',
|
|
4694
|
-
stage: 'action_requested',
|
|
4695
|
-
level: 'info',
|
|
4696
|
-
sessionId,
|
|
4697
|
-
payload: requestedPayload,
|
|
4698
|
-
});
|
|
4699
|
-
|
|
4700
|
-
try {
|
|
4701
|
-
const result = await run();
|
|
4702
|
-
recordDebugTrace({
|
|
4703
|
-
interactionId,
|
|
4704
|
-
category: 'session_host',
|
|
4705
|
-
stage: 'action_result',
|
|
4706
|
-
level: 'info',
|
|
4707
|
-
sessionId,
|
|
4708
|
-
payload: {
|
|
4709
|
-
...requestedPayload,
|
|
4710
|
-
success: true,
|
|
4711
|
-
...(summarizeResult ? summarizeResult(result) : {}),
|
|
4712
|
-
},
|
|
4713
|
-
});
|
|
4714
|
-
return result;
|
|
4715
|
-
} catch (error: any) {
|
|
4716
|
-
recordDebugTrace({
|
|
4717
|
-
interactionId,
|
|
4718
|
-
category: 'session_host',
|
|
4719
|
-
stage: 'action_failed',
|
|
4720
|
-
level: 'error',
|
|
4721
|
-
sessionId,
|
|
4722
|
-
payload: {
|
|
4723
|
-
...requestedPayload,
|
|
4724
|
-
error: error?.message || String(error),
|
|
4725
|
-
failureKind: getWriteConflictOwnerClientId(error) ? 'write_conflict' : 'request_failed',
|
|
4726
|
-
conflictOwnerClientId: getWriteConflictOwnerClientId(error),
|
|
4727
|
-
},
|
|
4728
|
-
});
|
|
4729
|
-
throw error;
|
|
4730
|
-
}
|
|
4731
|
-
}
|
|
4732
|
-
|
|
4733
4564
|
/**
|
|
4734
4565
|
* Unified command routing.
|
|
4735
4566
|
* Returns result for all commands:
|
|
@@ -6372,6 +6203,15 @@ export class DaemonCommandRouter {
|
|
|
6372
6203
|
}
|
|
6373
6204
|
}
|
|
6374
6205
|
|
|
6206
|
+
// RF-ROUTER LOW family: low-coupling commands (session-host control, spec
|
|
6207
|
+
// provider-dev, refine/change-impact config) are handled by the registry
|
|
6208
|
+
// before the switch. A hit returns the same CommandRouterResult the inlined
|
|
6209
|
+
// case used to; a miss falls through to the switch unchanged.
|
|
6210
|
+
const lowFamilyHandler = lowFamilyRegistry.get(cmd);
|
|
6211
|
+
if (lowFamilyHandler) {
|
|
6212
|
+
return await lowFamilyHandler({ deps: this.deps }, args);
|
|
6213
|
+
}
|
|
6214
|
+
|
|
6375
6215
|
switch (cmd) {
|
|
6376
6216
|
// ─── CLI / ACP commands ───
|
|
6377
6217
|
case 'mesh_forward_event': {
|
|
@@ -6411,7 +6251,7 @@ export class DaemonCommandRouter {
|
|
|
6411
6251
|
// fail-closes for a remote node when none resolve. We deliberately do NOT
|
|
6412
6252
|
// self-stamp this daemon's own id when the field is missing: for a
|
|
6413
6253
|
// P2P-relayed remote worker launch, stamping the worker's own id would make
|
|
6414
|
-
// the self-forward gate (mesh-events-coordinator:
|
|
6254
|
+
// the self-forward gate (mesh-events-coordinator: daemonIdsEquivalent) treat the
|
|
6415
6255
|
// worker as its own coordinator, suppressing the spontaneous completion-event
|
|
6416
6256
|
// forward and leaving the event in the pending inbox until a read_chat
|
|
6417
6257
|
// reconcile drains it. If the anchor is genuinely absent here, leave it
|
|
@@ -6549,131 +6389,6 @@ export class DaemonCommandRouter {
|
|
|
6549
6389
|
return { success: true, trace, count: trace.length };
|
|
6550
6390
|
}
|
|
6551
6391
|
|
|
6552
|
-
case 'session_host_get_diagnostics': {
|
|
6553
|
-
if (!this.deps.sessionHostControl) return { success: false, error: 'Session host control unavailable' };
|
|
6554
|
-
const diagnostics = await this.traceSessionHostAction('session_host_get_diagnostics', args, () => this.deps.sessionHostControl!.getDiagnostics({
|
|
6555
|
-
includeSessions: args?.includeSessions !== false,
|
|
6556
|
-
limit: Number(args?.limit) || undefined,
|
|
6557
|
-
}), (result) => ({
|
|
6558
|
-
includeSessions: args?.includeSessions !== false,
|
|
6559
|
-
limit: Number(args?.limit) || undefined,
|
|
6560
|
-
...summarizeSessionHostDiagnostics(result),
|
|
6561
|
-
}));
|
|
6562
|
-
return { success: true, diagnostics };
|
|
6563
|
-
}
|
|
6564
|
-
|
|
6565
|
-
case 'session_host_list_sessions': {
|
|
6566
|
-
if (!this.deps.sessionHostControl) return { success: false, error: 'Session host control unavailable' };
|
|
6567
|
-
const sessions = await this.traceSessionHostAction('session_host_list_sessions', args, () => this.deps.sessionHostControl!.listSessions(), (records) => summarizeSessionHostRecords(records));
|
|
6568
|
-
return { success: true, sessions };
|
|
6569
|
-
}
|
|
6570
|
-
|
|
6571
|
-
case 'session_host_stop_session': {
|
|
6572
|
-
if (!this.deps.sessionHostControl) return { success: false, error: 'Session host control unavailable' };
|
|
6573
|
-
const sessionId = typeof args?.sessionId === 'string' ? args.sessionId : '';
|
|
6574
|
-
if (!sessionId) return { success: false, error: 'sessionId required' };
|
|
6575
|
-
const record = await this.traceSessionHostAction('session_host_stop_session', args, () => this.deps.sessionHostControl!.stopSession(sessionId), (result) => summarizeSessionHostRecord(result));
|
|
6576
|
-
return { success: true, record };
|
|
6577
|
-
}
|
|
6578
|
-
|
|
6579
|
-
case 'session_host_resume_session': {
|
|
6580
|
-
if (!this.deps.sessionHostControl) return { success: false, error: 'Session host control unavailable' };
|
|
6581
|
-
const sessionId = typeof args?.sessionId === 'string' ? args.sessionId : '';
|
|
6582
|
-
if (!sessionId) return { success: false, error: 'sessionId required' };
|
|
6583
|
-
const record = await this.traceSessionHostAction('session_host_resume_session', args, async () => {
|
|
6584
|
-
const nextRecord = await this.deps.sessionHostControl!.resumeSession(sessionId);
|
|
6585
|
-
const hosted = toHostedCliRuntimeDescriptor(nextRecord);
|
|
6586
|
-
if (hosted) {
|
|
6587
|
-
await this.deps.cliManager.restoreHostedSessions([hosted]);
|
|
6588
|
-
}
|
|
6589
|
-
return nextRecord;
|
|
6590
|
-
}, (result) => ({
|
|
6591
|
-
...summarizeSessionHostRecord(result),
|
|
6592
|
-
restoredHostedSession: !!toHostedCliRuntimeDescriptor(result),
|
|
6593
|
-
}));
|
|
6594
|
-
return { success: true, record };
|
|
6595
|
-
}
|
|
6596
|
-
|
|
6597
|
-
case 'session_host_restart_session': {
|
|
6598
|
-
if (!this.deps.sessionHostControl) return { success: false, error: 'Session host control unavailable' };
|
|
6599
|
-
const sessionId = typeof args?.sessionId === 'string' ? args.sessionId : '';
|
|
6600
|
-
if (!sessionId) return { success: false, error: 'sessionId required' };
|
|
6601
|
-
const record = await this.traceSessionHostAction('session_host_restart_session', args, async () => {
|
|
6602
|
-
const nextRecord = await this.deps.sessionHostControl!.restartSession(sessionId);
|
|
6603
|
-
const hosted = toHostedCliRuntimeDescriptor(nextRecord);
|
|
6604
|
-
if (hosted) {
|
|
6605
|
-
await this.deps.cliManager.restoreHostedSessions([hosted]);
|
|
6606
|
-
}
|
|
6607
|
-
return nextRecord;
|
|
6608
|
-
}, (result) => ({
|
|
6609
|
-
...summarizeSessionHostRecord(result),
|
|
6610
|
-
restoredHostedSession: !!toHostedCliRuntimeDescriptor(result),
|
|
6611
|
-
}));
|
|
6612
|
-
return { success: true, record };
|
|
6613
|
-
}
|
|
6614
|
-
|
|
6615
|
-
case 'session_host_send_signal': {
|
|
6616
|
-
if (!this.deps.sessionHostControl) return { success: false, error: 'Session host control unavailable' };
|
|
6617
|
-
const sessionId = typeof args?.sessionId === 'string' ? args.sessionId : '';
|
|
6618
|
-
const signal = typeof args?.signal === 'string' ? args.signal : '';
|
|
6619
|
-
if (!sessionId) return { success: false, error: 'sessionId required' };
|
|
6620
|
-
if (!signal) return { success: false, error: 'signal required' };
|
|
6621
|
-
const record = await this.traceSessionHostAction('session_host_send_signal', args, () => this.deps.sessionHostControl!.sendSignal(sessionId, signal), (result) => summarizeSessionHostRecord(result));
|
|
6622
|
-
return { success: true, record };
|
|
6623
|
-
}
|
|
6624
|
-
|
|
6625
|
-
case 'session_host_force_detach_client': {
|
|
6626
|
-
if (!this.deps.sessionHostControl) return { success: false, error: 'Session host control unavailable' };
|
|
6627
|
-
const sessionId = typeof args?.sessionId === 'string' ? args.sessionId : '';
|
|
6628
|
-
const clientId = typeof args?.clientId === 'string' ? args.clientId : '';
|
|
6629
|
-
if (!sessionId) return { success: false, error: 'sessionId required' };
|
|
6630
|
-
if (!clientId) return { success: false, error: 'clientId required' };
|
|
6631
|
-
const record = await this.traceSessionHostAction('session_host_force_detach_client', args, () => this.deps.sessionHostControl!.forceDetachClient(sessionId, clientId), (result) => summarizeSessionHostRecord(result));
|
|
6632
|
-
return { success: true, record };
|
|
6633
|
-
}
|
|
6634
|
-
|
|
6635
|
-
case 'session_host_prune_duplicate_sessions': {
|
|
6636
|
-
if (!this.deps.sessionHostControl) return { success: false, error: 'Session host control unavailable' };
|
|
6637
|
-
const result = await this.traceSessionHostAction('session_host_prune_duplicate_sessions', args, () => this.deps.sessionHostControl!.pruneDuplicateSessions({
|
|
6638
|
-
providerType: typeof args?.providerType === 'string' ? args.providerType : undefined,
|
|
6639
|
-
workspace: typeof args?.workspace === 'string' ? args.workspace : undefined,
|
|
6640
|
-
dryRun: args?.dryRun === true,
|
|
6641
|
-
}), (value) => summarizeSessionHostPruneResult(value));
|
|
6642
|
-
return { success: true, result };
|
|
6643
|
-
}
|
|
6644
|
-
|
|
6645
|
-
case 'session_host_acquire_write': {
|
|
6646
|
-
if (!this.deps.sessionHostControl) return { success: false, error: 'Session host control unavailable' };
|
|
6647
|
-
const sessionId = typeof args?.sessionId === 'string' ? args.sessionId : '';
|
|
6648
|
-
const clientId = typeof args?.clientId === 'string' ? args.clientId : '';
|
|
6649
|
-
const ownerType = args?.ownerType === 'agent' ? 'agent' : 'user';
|
|
6650
|
-
if (!sessionId) return { success: false, error: 'sessionId required' };
|
|
6651
|
-
if (!clientId) return { success: false, error: 'clientId required' };
|
|
6652
|
-
const record = await this.traceSessionHostAction('session_host_acquire_write', args, () => this.deps.sessionHostControl!.acquireWrite({
|
|
6653
|
-
sessionId,
|
|
6654
|
-
clientId,
|
|
6655
|
-
ownerType,
|
|
6656
|
-
force: args?.force !== false,
|
|
6657
|
-
}), (result) => ({
|
|
6658
|
-
...summarizeSessionHostRecord(result),
|
|
6659
|
-
ownerType,
|
|
6660
|
-
}));
|
|
6661
|
-
return { success: true, record };
|
|
6662
|
-
}
|
|
6663
|
-
|
|
6664
|
-
case 'session_host_release_write': {
|
|
6665
|
-
if (!this.deps.sessionHostControl) return { success: false, error: 'Session host control unavailable' };
|
|
6666
|
-
const sessionId = typeof args?.sessionId === 'string' ? args.sessionId : '';
|
|
6667
|
-
const clientId = typeof args?.clientId === 'string' ? args.clientId : '';
|
|
6668
|
-
if (!sessionId) return { success: false, error: 'sessionId required' };
|
|
6669
|
-
if (!clientId) return { success: false, error: 'clientId required' };
|
|
6670
|
-
const record = await this.traceSessionHostAction('session_host_release_write', args, () => this.deps.sessionHostControl!.releaseWrite({
|
|
6671
|
-
sessionId,
|
|
6672
|
-
clientId,
|
|
6673
|
-
}), (result) => summarizeSessionHostRecord(result));
|
|
6674
|
-
return { success: true, record };
|
|
6675
|
-
}
|
|
6676
|
-
|
|
6677
6392
|
case 'list_saved_sessions': {
|
|
6678
6393
|
const providerType = typeof args?.providerType === 'string'
|
|
6679
6394
|
? args.providerType.trim()
|
|
@@ -6986,184 +6701,6 @@ export class DaemonCommandRouter {
|
|
|
6986
6701
|
};
|
|
6987
6702
|
}
|
|
6988
6703
|
|
|
6989
|
-
case 'get_spec_debug': {
|
|
6990
|
-
const sessionId = typeof args?.targetSessionId === 'string' ? args.targetSessionId.trim()
|
|
6991
|
-
: typeof args?.sessionId === 'string' ? args.sessionId.trim() : '';
|
|
6992
|
-
if (!sessionId) return { success: false, error: 'targetSessionId required' };
|
|
6993
|
-
const target = this.deps.sessionRegistry.get(sessionId);
|
|
6994
|
-
if (!target) return { success: false, error: 'Session not found', sessionId };
|
|
6995
|
-
const adapterObj = this.deps.cliManager.findAdapter(target.providerType, { instanceKey: sessionId })?.adapter;
|
|
6996
|
-
const snapshot = adapterObj
|
|
6997
|
-
? (typeof (adapterObj as any).getDebugSnapshot === 'function'
|
|
6998
|
-
? (adapterObj as any).getDebugSnapshot()
|
|
6999
|
-
: typeof (adapterObj as any).getDebugState === 'function'
|
|
7000
|
-
? (adapterObj as any).getDebugState()
|
|
7001
|
-
: null)
|
|
7002
|
-
: null;
|
|
7003
|
-
return {
|
|
7004
|
-
success: true,
|
|
7005
|
-
sessionId,
|
|
7006
|
-
providerType: target.providerType,
|
|
7007
|
-
isSpecProvider: snapshot !== null,
|
|
7008
|
-
snapshot,
|
|
7009
|
-
};
|
|
7010
|
-
}
|
|
7011
|
-
|
|
7012
|
-
// ── Spec source read/write for the debug panel's live editor.
|
|
7013
|
-
// Lets the dashboard load a session's spec.json, edit it, and
|
|
7014
|
-
// save it back — the driver's fs.watch picks up the change and
|
|
7015
|
-
// hot-reloads the FSM with no restart. Writes are confined to
|
|
7016
|
-
// files under ~/.adhdev/providers to avoid arbitrary fs access.
|
|
7017
|
-
case 'get_spec_source': {
|
|
7018
|
-
const fsm = await import('node:fs');
|
|
7019
|
-
const pathm = await import('node:path');
|
|
7020
|
-
const osm = await import('node:os');
|
|
7021
|
-
const sessionId = typeof args?.targetSessionId === 'string' ? args.targetSessionId.trim()
|
|
7022
|
-
: typeof args?.sessionId === 'string' ? args.sessionId.trim() : '';
|
|
7023
|
-
let specPath = typeof args?.specPath === 'string' ? args.specPath : '';
|
|
7024
|
-
if (!specPath && sessionId) {
|
|
7025
|
-
const target = this.deps.sessionRegistry.get(sessionId);
|
|
7026
|
-
const adapterObj = target ? this.deps.cliManager.findAdapter(target.providerType, { instanceKey: sessionId })?.adapter : null;
|
|
7027
|
-
const snap = adapterObj && typeof (adapterObj as any).getDebugSnapshot === 'function' ? (adapterObj as any).getDebugSnapshot() : null;
|
|
7028
|
-
specPath = snap?.specPath ?? '';
|
|
7029
|
-
}
|
|
7030
|
-
if (!specPath) return { success: false, error: 'specPath or resolvable targetSessionId required' };
|
|
7031
|
-
// Confine reads to the providers tree, resolving symlinks so a
|
|
7032
|
-
// crafted path can't escape via a symlinked spec file.
|
|
7033
|
-
const safe = resolveSpecPathInProviders(specPath, fsm, pathm, osm);
|
|
7034
|
-
if (!safe.ok) return { success: false, error: safe.error, specPath };
|
|
7035
|
-
try {
|
|
7036
|
-
const content = fsm.readFileSync(safe.path, 'utf8');
|
|
7037
|
-
return { success: true, specPath: safe.path, content };
|
|
7038
|
-
} catch (e) {
|
|
7039
|
-
return { success: false, error: `read failed: ${(e as Error).message}`, specPath };
|
|
7040
|
-
}
|
|
7041
|
-
}
|
|
7042
|
-
|
|
7043
|
-
case 'write_spec_source': {
|
|
7044
|
-
const fsm = await import('node:fs');
|
|
7045
|
-
const pathm = await import('node:path');
|
|
7046
|
-
const osm = await import('node:os');
|
|
7047
|
-
const specPath = typeof args?.specPath === 'string' ? args.specPath : '';
|
|
7048
|
-
const content = typeof args?.content === 'string' ? args.content : '';
|
|
7049
|
-
if (!specPath) return { success: false, error: 'specPath required' };
|
|
7050
|
-
if (!content) return { success: false, error: 'content required' };
|
|
7051
|
-
// Confine writes to the providers tree (symlink-safe — see helper).
|
|
7052
|
-
const safe = resolveSpecPathInProviders(specPath, fsm, pathm, osm);
|
|
7053
|
-
if (!safe.ok) return { success: false, error: safe.error };
|
|
7054
|
-
// Validate JSON + (if v4) FSM structure before writing so a bad
|
|
7055
|
-
// edit can't break the live session — return precise errors.
|
|
7056
|
-
let parsed: unknown;
|
|
7057
|
-
try { parsed = JSON.parse(content); }
|
|
7058
|
-
catch (e) { return { success: false, error: `invalid JSON: ${(e as Error).message}` }; }
|
|
7059
|
-
if ((parsed as any)?.$schema === 'adhdev:cli/spec@4') {
|
|
7060
|
-
const { validateFsmSpec } = await import('../providers/spec/fsm-loader.js');
|
|
7061
|
-
const errs = validateFsmSpec(parsed);
|
|
7062
|
-
if (errs.length) return { success: false, error: 'spec invalid', validationErrors: errs };
|
|
7063
|
-
}
|
|
7064
|
-
try {
|
|
7065
|
-
fsm.writeFileSync(safe.path, content, 'utf8');
|
|
7066
|
-
return { success: true, specPath: safe.path };
|
|
7067
|
-
} catch (e) {
|
|
7068
|
-
return { success: false, error: `write failed: ${(e as Error).message}` };
|
|
7069
|
-
}
|
|
7070
|
-
}
|
|
7071
|
-
|
|
7072
|
-
// ── Validate an in-progress spec (string or object) without writing.
|
|
7073
|
-
// The form builder calls this on every change so Save can stay
|
|
7074
|
-
// disabled while there are structural / reference / regex errors.
|
|
7075
|
-
case 'validate_spec': {
|
|
7076
|
-
let parsed: unknown = args?.spec;
|
|
7077
|
-
if (typeof args?.content === 'string') {
|
|
7078
|
-
try { parsed = JSON.parse(args.content); }
|
|
7079
|
-
catch (e) { return { success: true, valid: false, errors: [`invalid JSON: ${(e as Error).message}`] }; }
|
|
7080
|
-
}
|
|
7081
|
-
if (!parsed || typeof parsed !== 'object') {
|
|
7082
|
-
return { success: true, valid: false, errors: ['spec must be an object or content string'] };
|
|
7083
|
-
}
|
|
7084
|
-
const schema = (parsed as any).$schema;
|
|
7085
|
-
if (schema === 'adhdev:cli/spec@4') {
|
|
7086
|
-
const { validateFsmSpec } = await import('../providers/spec/fsm-loader.js');
|
|
7087
|
-
const errors = validateFsmSpec(parsed);
|
|
7088
|
-
return { success: true, valid: errors.length === 0, errors };
|
|
7089
|
-
}
|
|
7090
|
-
// v1/v3 left to the legacy loader path; the builder is v4-only.
|
|
7091
|
-
return { success: true, valid: false, errors: [`unsupported $schema "${schema}" — form builder is v4-only`] };
|
|
7092
|
-
}
|
|
7093
|
-
|
|
7094
|
-
// ── Evaluate a single condition against a live session's current
|
|
7095
|
-
// screen — powers the editor's "does this match right now?"
|
|
7096
|
-
// preview. Returns the recursive match tree.
|
|
7097
|
-
case 'eval_condition_preview': {
|
|
7098
|
-
const sessionId = typeof args?.targetSessionId === 'string' ? args.targetSessionId.trim()
|
|
7099
|
-
: typeof args?.sessionId === 'string' ? args.sessionId.trim() : '';
|
|
7100
|
-
if (!sessionId) return { success: false, error: 'targetSessionId required' };
|
|
7101
|
-
if (!args?.condition || typeof args.condition !== 'object') return { success: false, error: 'condition required' };
|
|
7102
|
-
const target = this.deps.sessionRegistry.get(sessionId);
|
|
7103
|
-
if (!target) return { success: false, error: 'Session not found', sessionId };
|
|
7104
|
-
const adapterObj = this.deps.cliManager.findAdapter(target.providerType, { instanceKey: sessionId })?.adapter as any;
|
|
7105
|
-
const snap = adapterObj && typeof adapterObj.getDebugSnapshot === 'function' ? adapterObj.getDebugSnapshot() : null;
|
|
7106
|
-
if (!snap?.screen) return { success: false, error: 'no live screen for session' };
|
|
7107
|
-
// Reconstruct the sections map the spec would resolve. We pass
|
|
7108
|
-
// the spec's sections so section-scoped regexes resolve the same
|
|
7109
|
-
// way they do at runtime; fall back to the on-disk spec.
|
|
7110
|
-
let sectionsDef: Record<string, unknown> | undefined;
|
|
7111
|
-
try {
|
|
7112
|
-
const fsm2 = await import('node:fs');
|
|
7113
|
-
if (snap.specPath) {
|
|
7114
|
-
const raw = JSON.parse(fsm2.readFileSync(snap.specPath, 'utf8'));
|
|
7115
|
-
sectionsDef = raw?.sections;
|
|
7116
|
-
}
|
|
7117
|
-
} catch { /* fall back to whole-screen matching */ }
|
|
7118
|
-
const { evaluateConditionPreview } = await import('../providers/spec/fsm-evaluator.js');
|
|
7119
|
-
try {
|
|
7120
|
-
const result = evaluateConditionPreview(
|
|
7121
|
-
args.condition,
|
|
7122
|
-
sectionsDef as any,
|
|
7123
|
-
snap.screen,
|
|
7124
|
-
snap.cursorPosition ?? undefined,
|
|
7125
|
-
);
|
|
7126
|
-
return { success: true, result, sections: snap.sections ?? null };
|
|
7127
|
-
} catch (e) {
|
|
7128
|
-
return { success: false, error: `eval failed: ${(e as Error).message}` };
|
|
7129
|
-
}
|
|
7130
|
-
}
|
|
7131
|
-
|
|
7132
|
-
// ── Resolve a sections map against a live session's screen — the
|
|
7133
|
-
// section editor's "test" button. Returns, for each section id,
|
|
7134
|
-
// the line range + the text it captures, so the author can SEE
|
|
7135
|
-
// whether a from_top/until/anchor definition carves the screen
|
|
7136
|
-
// the way they intend. Accepts an in-progress sections map so it
|
|
7137
|
-
// previews unsaved edits.
|
|
7138
|
-
case 'resolve_section_preview': {
|
|
7139
|
-
const sessionId = typeof args?.targetSessionId === 'string' ? args.targetSessionId.trim()
|
|
7140
|
-
: typeof args?.sessionId === 'string' ? args.sessionId.trim() : '';
|
|
7141
|
-
if (!sessionId) return { success: false, error: 'targetSessionId required' };
|
|
7142
|
-
if (!args?.sections || typeof args.sections !== 'object') return { success: false, error: 'sections map required' };
|
|
7143
|
-
const target = this.deps.sessionRegistry.get(sessionId);
|
|
7144
|
-
if (!target) return { success: false, error: 'Session not found', sessionId };
|
|
7145
|
-
const adapterObj = this.deps.cliManager.findAdapter(target.providerType, { instanceKey: sessionId })?.adapter as any;
|
|
7146
|
-
const snap = adapterObj && typeof adapterObj.getDebugSnapshot === 'function' ? adapterObj.getDebugSnapshot() : null;
|
|
7147
|
-
if (!snap?.screen) return { success: false, error: 'no live screen for session' };
|
|
7148
|
-
const { resolveSections } = await import('../providers/spec/evaluator.js');
|
|
7149
|
-
try {
|
|
7150
|
-
const lines = String(snap.screen).split('\n').map((l: string) => l.endsWith('\r') ? l.slice(0, -1) : l);
|
|
7151
|
-
const resolved = resolveSections(args.sections as any, lines);
|
|
7152
|
-
return {
|
|
7153
|
-
success: true,
|
|
7154
|
-
screenLineCount: lines.length,
|
|
7155
|
-
sections: resolved.map(s => ({ id: s.id, fromLine: s.fromLine, toLine: s.toLine, text: s.text })),
|
|
7156
|
-
};
|
|
7157
|
-
} catch (e) {
|
|
7158
|
-
return { success: false, error: `resolve failed: ${(e as Error).message}` };
|
|
7159
|
-
}
|
|
7160
|
-
}
|
|
7161
|
-
|
|
7162
|
-
// ── User-level coordinator-prompt files (~/.adhdev/coordinator-prompts/).
|
|
7163
|
-
// These live on this daemon's filesystem and never sync to the
|
|
7164
|
-
// cloud / other daemons — they're per-machine config. The
|
|
7165
|
-
// Settings page in the dashboard reads/writes via these two
|
|
7166
|
-
// commands instead of going through fs from the browser.
|
|
7167
6704
|
case 'list_coordinator_prompts': {
|
|
7168
6705
|
const fs = await import('node:fs');
|
|
7169
6706
|
const path = await import('node:path');
|
|
@@ -7977,83 +7514,6 @@ export class DaemonCommandRouter {
|
|
|
7977
7514
|
}
|
|
7978
7515
|
}
|
|
7979
7516
|
|
|
7980
|
-
case 'get_mesh_refine_config_schema': {
|
|
7981
|
-
return {
|
|
7982
|
-
success: true,
|
|
7983
|
-
schema: MESH_REFINE_CONFIG_SCHEMA,
|
|
7984
|
-
locations: MESH_REFINE_CONFIG_LOCATIONS,
|
|
7985
|
-
worktreeBootstrap: {
|
|
7986
|
-
schema: MESH_WORKTREE_BOOTSTRAP_CONFIG_SCHEMA,
|
|
7987
|
-
locations: MESH_WORKTREE_BOOTSTRAP_CONFIG_LOCATIONS,
|
|
7988
|
-
sourceOfTruth: 'repo worktree bootstrap config',
|
|
7989
|
-
runBehavior: 'When present and enabled, clone_mesh_node runs commands after submodule initialization and records status on the worktree node.',
|
|
7990
|
-
},
|
|
7991
|
-
sourceOfTruth: 'repo mesh/refine config',
|
|
7992
|
-
heuristicRole: 'suggestions_only_not_execution_path',
|
|
7993
|
-
};
|
|
7994
|
-
}
|
|
7995
|
-
|
|
7996
|
-
case 'validate_mesh_refine_config': {
|
|
7997
|
-
const workspace = typeof args?.workspace === 'string' ? args.workspace : process.cwd();
|
|
7998
|
-
const mesh = args?.inlineMesh || {};
|
|
7999
|
-
const loaded = args?.config !== undefined
|
|
8000
|
-
? { config: args.config, source: 'inline', sourceType: 'mesh_policy' as const }
|
|
8001
|
-
: loadMeshRefineConfig(mesh, workspace);
|
|
8002
|
-
const validation = loaded.config
|
|
8003
|
-
? validateMeshRefineConfig(loaded.config, loaded.source)
|
|
8004
|
-
: { valid: false, errors: [((loaded as { error?: string }).error) || 'repo mesh/refine config unavailable'], commands: [], rejectedCommands: [] };
|
|
8005
|
-
return { success: validation.valid, ...loaded, ...validation };
|
|
8006
|
-
}
|
|
8007
|
-
|
|
8008
|
-
case 'suggest_mesh_refine_config': {
|
|
8009
|
-
const workspace = typeof args?.workspace === 'string' ? args.workspace : process.cwd();
|
|
8010
|
-
const mesh = args?.inlineMesh || {};
|
|
8011
|
-
return {
|
|
8012
|
-
success: true,
|
|
8013
|
-
...suggestMeshRefineConfig(mesh, workspace),
|
|
8014
|
-
note: 'Suggestions are heuristic scaffold only; Refinery will not execute them until saved into repo mesh/refine config.',
|
|
8015
|
-
};
|
|
8016
|
-
}
|
|
8017
|
-
|
|
8018
|
-
case 'get_mesh_change_impact_config_schema': {
|
|
8019
|
-
return {
|
|
8020
|
-
success: true,
|
|
8021
|
-
schema: CHANGE_IMPACT_CONFIG_SCHEMA,
|
|
8022
|
-
locations: CHANGE_IMPACT_CONFIG_LOCATIONS,
|
|
8023
|
-
sourceOfTruth: 'repo change-impact config',
|
|
8024
|
-
heuristicRole: 'suggestions_only_not_execution_path',
|
|
8025
|
-
note: 'Declarative config only — JSON/YAML are parsed but never executed. Defines which package/file changes require a daemon rebuild/restart vs. a web-only redeploy vs. nothing.',
|
|
8026
|
-
};
|
|
8027
|
-
}
|
|
8028
|
-
|
|
8029
|
-
case 'validate_mesh_change_impact_config': {
|
|
8030
|
-
const workspace = typeof args?.workspace === 'string' ? args.workspace : process.cwd();
|
|
8031
|
-
if (args?.config !== undefined) {
|
|
8032
|
-
const validation = validateChangeImpactConfig(args.config, 'inline');
|
|
8033
|
-
return { success: validation.valid, source: 'inline', sourceType: 'mesh_policy', ...validation };
|
|
8034
|
-
}
|
|
8035
|
-
const loaded = loadChangeImpactConfig(workspace);
|
|
8036
|
-
if (loaded.sourceType === 'repo_file') {
|
|
8037
|
-
const validation = validateChangeImpactConfig(loaded.config, loaded.source);
|
|
8038
|
-
return { success: validation.valid, ...loaded, ...validation };
|
|
8039
|
-
}
|
|
8040
|
-
return {
|
|
8041
|
-
success: false,
|
|
8042
|
-
...loaded,
|
|
8043
|
-
valid: false,
|
|
8044
|
-
errors: [loaded.error || 'repo change-impact config unavailable'],
|
|
8045
|
-
};
|
|
8046
|
-
}
|
|
8047
|
-
|
|
8048
|
-
case 'suggest_mesh_change_impact_config': {
|
|
8049
|
-
const workspace = typeof args?.workspace === 'string' ? args.workspace : process.cwd();
|
|
8050
|
-
return {
|
|
8051
|
-
success: true,
|
|
8052
|
-
...suggestChangeImpactConfig(workspace),
|
|
8053
|
-
note: 'Suggestions are heuristic scaffold only; the draft must be reviewed and saved into repo change-impact config before it takes effect. Nothing is executed.',
|
|
8054
|
-
};
|
|
8055
|
-
}
|
|
8056
|
-
|
|
8057
7517
|
case 'mesh_init': {
|
|
8058
7518
|
const workspace = typeof args?.workspace === 'string' && args.workspace.trim() ? args.workspace.trim() : process.cwd();
|
|
8059
7519
|
const mesh = args?.inlineMesh || {};
|
|
@@ -9592,7 +9052,7 @@ export class DaemonCommandRouter {
|
|
|
9592
9052
|
const isSelfNode = Boolean(
|
|
9593
9053
|
nodeId && inlineCoordinatorNodeId && nodeId === inlineCoordinatorNodeId,
|
|
9594
9054
|
) || Boolean(
|
|
9595
|
-
daemonId && (daemonId
|
|
9055
|
+
daemonId && (daemonIdsEquivalent(daemonId, localMachineId) || daemonIdsEquivalent(daemonId, this.deps.statusInstanceId)),
|
|
9596
9056
|
) || Boolean(meshRecord?.inline && nodeIndex === 0)
|
|
9597
9057
|
|| sparseConfiguredCoordinatorNode;
|
|
9598
9058
|
const machineIdentity = buildMeshNodeMachineIdentity(node as Record<string, unknown>, {
|