@adhdev/daemon-core 0.9.77-rc.4 → 0.9.77-rc.40
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/boot/daemon-lifecycle.d.ts +3 -0
- package/dist/cli-adapters/provider-cli-adapter.d.ts +3 -0
- package/dist/cli-adapters/provider-cli-shared.d.ts +14 -4
- package/dist/commands/mesh-coordinator.d.ts +10 -0
- package/dist/commands/router.d.ts +1 -1
- package/dist/index.d.ts +2 -2
- package/dist/index.js +389 -44
- package/dist/index.js.map +1 -1
- package/dist/index.mjs +386 -44
- package/dist/index.mjs.map +1 -1
- package/dist/mesh/mesh-events.d.ts +2 -7
- package/dist/mesh/mesh-work-queue.d.ts +35 -1
- package/dist/shared-types.d.ts +14 -0
- package/package.json +1 -1
- package/src/boot/daemon-lifecycle.ts +5 -0
- package/src/cli-adapters/provider-cli-adapter.ts +25 -4
- package/src/cli-adapters/provider-cli-shared.ts +14 -4
- package/src/commands/cli-manager.ts +0 -4
- package/src/commands/mesh-coordinator.ts +55 -7
- package/src/commands/router.ts +160 -3
- package/src/commands/stream-commands.ts +8 -1
- package/src/index.ts +2 -2
- package/src/mesh/mesh-events.ts +147 -20
- package/src/mesh/mesh-work-queue.ts +103 -6
- package/src/providers/cli-provider-instance.ts +2 -0
- package/src/shared-types.ts +14 -0
|
@@ -8,17 +8,12 @@ export interface PendingMeshCoordinatorEvent {
|
|
|
8
8
|
}
|
|
9
9
|
/** Drain and return all pending coordinator events, clearing the queue. */
|
|
10
10
|
export declare function drainPendingMeshCoordinatorEvents(): PendingMeshCoordinatorEvent[];
|
|
11
|
-
export declare function tryAssignQueueTask(components:
|
|
12
|
-
cliManager: any;
|
|
13
|
-
}, meshId: string, nodeId: string, sessionId: string, providerType: string): boolean;
|
|
11
|
+
export declare function tryAssignQueueTask(components: DaemonComponents, meshId: string, nodeId: string, sessionId: string, providerType: string): boolean;
|
|
14
12
|
/**
|
|
15
13
|
* Triggers a queue check for all nodes in the mesh.
|
|
16
14
|
* Called when a new task is enqueued, in case nodes are already idle.
|
|
17
15
|
*/
|
|
18
|
-
export declare function triggerMeshQueue(components:
|
|
19
|
-
instanceManager: any;
|
|
20
|
-
cliManager: any;
|
|
21
|
-
}, meshId: string): void;
|
|
16
|
+
export declare function triggerMeshQueue(components: DaemonComponents, meshId: string): void;
|
|
22
17
|
export declare function handleMeshForwardEvent(components: DaemonComponents, payload: Record<string, unknown>): {
|
|
23
18
|
success: boolean;
|
|
24
19
|
forwarded: number;
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
export type MeshTaskStatus = 'pending' | 'assigned' | 'completed' | 'failed';
|
|
1
|
+
export type MeshTaskStatus = 'pending' | 'assigned' | 'completed' | 'failed' | 'cancelled';
|
|
2
2
|
export interface MeshWorkQueueEntry {
|
|
3
3
|
id: string;
|
|
4
4
|
meshId: string;
|
|
@@ -6,10 +6,19 @@ export interface MeshWorkQueueEntry {
|
|
|
6
6
|
status: MeshTaskStatus;
|
|
7
7
|
/** If specified, only this node can claim the task (used by legacy mesh_send_task) */
|
|
8
8
|
targetNodeId?: string;
|
|
9
|
+
/** If specified, only this runtime session can claim the task */
|
|
10
|
+
targetSessionId?: string;
|
|
9
11
|
/** The node that actually claimed and is executing the task */
|
|
10
12
|
assignedNodeId?: string;
|
|
11
13
|
/** The session currently executing the task */
|
|
12
14
|
assignedSessionId?: string;
|
|
15
|
+
/** Human/operator reason for terminal cancellation. */
|
|
16
|
+
cancelReason?: string;
|
|
17
|
+
cancelledAt?: string;
|
|
18
|
+
/** Human/operator reason for manually requeueing a task. */
|
|
19
|
+
requeueReason?: string;
|
|
20
|
+
requeuedAt?: string;
|
|
21
|
+
requeueCount?: number;
|
|
13
22
|
createdAt: string;
|
|
14
23
|
updatedAt: string;
|
|
15
24
|
}
|
|
@@ -18,6 +27,7 @@ export interface MeshWorkQueueEntry {
|
|
|
18
27
|
*/
|
|
19
28
|
export declare function enqueueTask(meshId: string, message: string, opts?: {
|
|
20
29
|
targetNodeId?: string;
|
|
30
|
+
targetSessionId?: string;
|
|
21
31
|
}): MeshWorkQueueEntry;
|
|
22
32
|
/**
|
|
23
33
|
* Get all tasks in the queue, optionally filtered by status.
|
|
@@ -34,6 +44,23 @@ export declare function claimNextTask(meshId: string, nodeId: string, sessionId:
|
|
|
34
44
|
* Used when a session completes, fails, or stalls.
|
|
35
45
|
*/
|
|
36
46
|
export declare function updateTaskStatus(meshId: string, taskId: string, status: MeshTaskStatus): MeshWorkQueueEntry | null;
|
|
47
|
+
/**
|
|
48
|
+
* Mark a queue task as manually cancelled without deleting audit history.
|
|
49
|
+
*/
|
|
50
|
+
export declare function cancelTask(meshId: string, taskId: string, opts?: {
|
|
51
|
+
reason?: string;
|
|
52
|
+
}): MeshWorkQueueEntry | null;
|
|
53
|
+
/**
|
|
54
|
+
* Return a queue task to pending for retry. By default, dead session targeting
|
|
55
|
+
* and assigned ownership are cleared so stale assignments do not strand again.
|
|
56
|
+
*/
|
|
57
|
+
export declare function requeueTask(meshId: string, taskId: string, opts?: {
|
|
58
|
+
reason?: string;
|
|
59
|
+
targetNodeId?: string;
|
|
60
|
+
targetSessionId?: string;
|
|
61
|
+
clearTargetNode?: boolean;
|
|
62
|
+
clearTargetSession?: boolean;
|
|
63
|
+
}): MeshWorkQueueEntry | null;
|
|
37
64
|
/**
|
|
38
65
|
* Update the status of the task currently assigned to a specific session.
|
|
39
66
|
*/
|
|
@@ -43,6 +70,13 @@ export interface MeshWorkQueueStats {
|
|
|
43
70
|
assigned: number;
|
|
44
71
|
completed: number;
|
|
45
72
|
failed: number;
|
|
73
|
+
cancelled: number;
|
|
74
|
+
activeAssignments: Array<{
|
|
75
|
+
id: string;
|
|
76
|
+
nodeId?: string;
|
|
77
|
+
sessionId?: string;
|
|
78
|
+
message: string;
|
|
79
|
+
}>;
|
|
46
80
|
}
|
|
47
81
|
/**
|
|
48
82
|
* Return aggregate queue statistics for the given mesh.
|
package/dist/shared-types.d.ts
CHANGED
|
@@ -297,6 +297,13 @@ export interface SessionEntry {
|
|
|
297
297
|
assigned: number;
|
|
298
298
|
completed: number;
|
|
299
299
|
failed: number;
|
|
300
|
+
cancelled?: number;
|
|
301
|
+
activeAssignments?: Array<{
|
|
302
|
+
id: string;
|
|
303
|
+
nodeId?: string;
|
|
304
|
+
sessionId?: string;
|
|
305
|
+
message: string;
|
|
306
|
+
}>;
|
|
300
307
|
};
|
|
301
308
|
}
|
|
302
309
|
/**
|
|
@@ -341,6 +348,13 @@ export interface CompactSessionEntry {
|
|
|
341
348
|
assigned: number;
|
|
342
349
|
completed: number;
|
|
343
350
|
failed: number;
|
|
351
|
+
cancelled?: number;
|
|
352
|
+
activeAssignments?: Array<{
|
|
353
|
+
id: string;
|
|
354
|
+
nodeId?: string;
|
|
355
|
+
sessionId?: string;
|
|
356
|
+
message: string;
|
|
357
|
+
}>;
|
|
344
358
|
};
|
|
345
359
|
}
|
|
346
360
|
export type VersionUpdateReason = 'force_update_below' | 'major_minor_mismatch' | 'patch_mismatch' | 'daemon_ahead';
|
package/package.json
CHANGED
|
@@ -83,6 +83,9 @@ export interface DaemonInitConfig {
|
|
|
83
83
|
|
|
84
84
|
/** Fired before send_chat is dispatched — used for turn snapshot hooks */
|
|
85
85
|
onBeforeSendChat?: (params: { workspace: string; sessionId: string }) => void;
|
|
86
|
+
|
|
87
|
+
/** Relays a command to a remote mesh node daemon */
|
|
88
|
+
dispatchMeshCommand?: (daemonId: string, command: string, args: Record<string, unknown>) => Promise<any>;
|
|
86
89
|
}
|
|
87
90
|
|
|
88
91
|
// ─── Result ───
|
|
@@ -100,6 +103,7 @@ export interface DaemonComponents {
|
|
|
100
103
|
sessionRegistry: SessionRegistry;
|
|
101
104
|
detectedIdes: { value: IDEInfo[] };
|
|
102
105
|
refreshProviderAvailability: (providerType?: string) => Promise<void>;
|
|
106
|
+
dispatchMeshCommand?: (daemonId: string, command: string, args: Record<string, unknown>) => Promise<any>;
|
|
103
107
|
}
|
|
104
108
|
|
|
105
109
|
export interface DaemonDevSupportOptions {
|
|
@@ -331,6 +335,7 @@ export async function initDaemonComponents(config: DaemonInitConfig): Promise<Da
|
|
|
331
335
|
sessionRegistry,
|
|
332
336
|
detectedIdes: detectedIdesRef,
|
|
333
337
|
refreshProviderAvailability,
|
|
338
|
+
dispatchMeshCommand: config.dispatchMeshCommand,
|
|
334
339
|
};
|
|
335
340
|
|
|
336
341
|
// 11. Setup Mesh Event Forwarding
|
|
@@ -195,6 +195,8 @@ export class ProviderCliAdapter implements CliAdapter {
|
|
|
195
195
|
|
|
196
196
|
// ─── CLI Scripts (script-based parsing) ───
|
|
197
197
|
private cliScripts: CliScripts;
|
|
198
|
+
/** Per-session opaque state object created by cliScripts.createState(), reset on stop. */
|
|
199
|
+
private scriptState: unknown = null;
|
|
198
200
|
private runtimeSettings: Record<string, any> = {};
|
|
199
201
|
/** Full accumulated rendered PTY transcript for parser/readback use */
|
|
200
202
|
private accumulatedBuffer: string = '';
|
|
@@ -223,6 +225,7 @@ export class ProviderCliAdapter implements CliAdapter {
|
|
|
223
225
|
currentTurnScope: TurnParseScope | null;
|
|
224
226
|
recentOutputBuffer: string;
|
|
225
227
|
accumulatedBuffer: string;
|
|
228
|
+
accumulatedRawBuffer: string;
|
|
226
229
|
screenText: string;
|
|
227
230
|
currentStatus: CliSessionStatus['status'];
|
|
228
231
|
activeModal: { message: string; buttons: string[] } | null;
|
|
@@ -305,6 +308,7 @@ export class ProviderCliAdapter implements CliAdapter {
|
|
|
305
308
|
&& cached.currentTurnScope === this.currentTurnScope
|
|
306
309
|
&& cached.recentOutputBuffer === this.recentOutputBuffer
|
|
307
310
|
&& cached.accumulatedBuffer === this.accumulatedBuffer
|
|
311
|
+
&& cached.accumulatedRawBuffer === this.accumulatedRawBuffer
|
|
308
312
|
&& cached.screenText === this.lastScreenText
|
|
309
313
|
&& cached.currentStatus === this.currentStatus
|
|
310
314
|
&& cached.activeModal === this.activeModal
|
|
@@ -477,6 +481,9 @@ export class ProviderCliAdapter implements CliAdapter {
|
|
|
477
481
|
this.cliScripts = scripts;
|
|
478
482
|
this.parsedStatusCache = null;
|
|
479
483
|
this.parseErrorMessage = null;
|
|
484
|
+
// Initialize per-session state: createState() is called once here and on script reload.
|
|
485
|
+
// The returned object lives until the PTY exits (scriptState = null on exit).
|
|
486
|
+
this.scriptState = typeof scripts.createState === 'function' ? scripts.createState() : null;
|
|
480
487
|
const scriptNames = listCliScriptNames(scripts);
|
|
481
488
|
LOG.info('CLI', `[${this.cliType}] CLI scripts injected: [${scriptNames.join(', ')}]`);
|
|
482
489
|
}
|
|
@@ -610,6 +617,7 @@ export class ProviderCliAdapter implements CliAdapter {
|
|
|
610
617
|
this.ready = false;
|
|
611
618
|
this.startupParseGate = false;
|
|
612
619
|
this.spawnAt = 0;
|
|
620
|
+
this.scriptState = null;
|
|
613
621
|
this.onStatusChange?.();
|
|
614
622
|
});
|
|
615
623
|
|
|
@@ -1450,6 +1458,14 @@ export class ProviderCliAdapter implements CliAdapter {
|
|
|
1450
1458
|
|
|
1451
1459
|
// ─── Script Execution ──────────────────────────
|
|
1452
1460
|
|
|
1461
|
+
private invokeCliScript<T>(script: Function, input: any): T {
|
|
1462
|
+
const hasStateFactory = typeof this.cliScripts?.createState === 'function';
|
|
1463
|
+
const expectsStateArgument = hasStateFactory || this.scriptState !== null || script.length >= 2;
|
|
1464
|
+
return expectsStateArgument
|
|
1465
|
+
? script(this.scriptState, input)
|
|
1466
|
+
: script(input);
|
|
1467
|
+
}
|
|
1468
|
+
|
|
1453
1469
|
private runParseSession(): ParsedSession | null {
|
|
1454
1470
|
if (typeof this.cliScripts?.parseSession !== 'function') {
|
|
1455
1471
|
this.parseErrorMessage = `${this.cliType} parseSession unavailable`;
|
|
@@ -1470,7 +1486,10 @@ export class ProviderCliAdapter implements CliAdapter {
|
|
|
1470
1486
|
scope: this.currentTurnScope,
|
|
1471
1487
|
runtimeSettings: this.runtimeSettings,
|
|
1472
1488
|
});
|
|
1473
|
-
const session = this.
|
|
1489
|
+
const session = this.invokeCliScript<ParsedSession | null>(
|
|
1490
|
+
this.cliScripts.parseSession,
|
|
1491
|
+
{ ...input, tail, tailScreen: buildCliScreenSnapshot(tail) },
|
|
1492
|
+
);
|
|
1474
1493
|
this.parseErrorMessage = null;
|
|
1475
1494
|
return session && typeof session === 'object' ? session : null;
|
|
1476
1495
|
} catch (e: any) {
|
|
@@ -1485,7 +1504,7 @@ export class ProviderCliAdapter implements CliAdapter {
|
|
|
1485
1504
|
if (!this.cliScripts?.detectStatus) return null;
|
|
1486
1505
|
try {
|
|
1487
1506
|
const screenText = this.terminalScreen.getText();
|
|
1488
|
-
const status = this.cliScripts.detectStatus
|
|
1507
|
+
const status = this.invokeCliScript<string | null>(this.cliScripts.detectStatus, {
|
|
1489
1508
|
tail: text.slice(-500),
|
|
1490
1509
|
screenText,
|
|
1491
1510
|
rawBuffer: this.accumulatedRawBuffer,
|
|
@@ -1505,7 +1524,7 @@ export class ProviderCliAdapter implements CliAdapter {
|
|
|
1505
1524
|
try {
|
|
1506
1525
|
const screenText = this.terminalScreen.getText();
|
|
1507
1526
|
const buffer = screenText || this.accumulatedBuffer;
|
|
1508
|
-
return this.cliScripts.parseApproval
|
|
1527
|
+
return this.invokeCliScript<{ message: string; buttons: string[] } | null>(this.cliScripts.parseApproval, {
|
|
1509
1528
|
buffer,
|
|
1510
1529
|
screenText,
|
|
1511
1530
|
rawBuffer: this.accumulatedRawBuffer,
|
|
@@ -1576,6 +1595,7 @@ export class ProviderCliAdapter implements CliAdapter {
|
|
|
1576
1595
|
&& cached.currentTurnScope === this.currentTurnScope
|
|
1577
1596
|
&& cached.recentOutputBuffer === this.recentOutputBuffer
|
|
1578
1597
|
&& cached.accumulatedBuffer === this.accumulatedBuffer
|
|
1598
|
+
&& cached.accumulatedRawBuffer === this.accumulatedRawBuffer
|
|
1579
1599
|
&& cached.screenText === parseScreenText
|
|
1580
1600
|
&& cached.currentStatus === this.currentStatus
|
|
1581
1601
|
&& cached.activeModal === this.activeModal
|
|
@@ -1615,6 +1635,7 @@ export class ProviderCliAdapter implements CliAdapter {
|
|
|
1615
1635
|
currentTurnScope: this.currentTurnScope,
|
|
1616
1636
|
recentOutputBuffer: this.recentOutputBuffer,
|
|
1617
1637
|
accumulatedBuffer: this.accumulatedBuffer,
|
|
1638
|
+
accumulatedRawBuffer: this.accumulatedRawBuffer,
|
|
1618
1639
|
screenText: parseScreenText,
|
|
1619
1640
|
currentStatus: this.currentStatus,
|
|
1620
1641
|
activeModal: this.activeModal,
|
|
@@ -1640,7 +1661,7 @@ export class ProviderCliAdapter implements CliAdapter {
|
|
|
1640
1661
|
scope: this.currentTurnScope,
|
|
1641
1662
|
runtimeSettings: this.runtimeSettings,
|
|
1642
1663
|
});
|
|
1643
|
-
return await Promise.resolve(fn({
|
|
1664
|
+
return await Promise.resolve(fn(this.scriptState, {
|
|
1644
1665
|
...input,
|
|
1645
1666
|
args: args && typeof args === 'object' ? { ...args } : {},
|
|
1646
1667
|
}));
|
|
@@ -48,11 +48,21 @@ export interface ParsedSession {
|
|
|
48
48
|
}
|
|
49
49
|
|
|
50
50
|
export interface CliScripts {
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
51
|
+
/**
|
|
52
|
+
* Optional state factory. Called once per CLI session start (or script reload).
|
|
53
|
+
* The returned object is passed as the first argument to detectStatus, parseApproval,
|
|
54
|
+
* and parseSession on every invocation, allowing scripts to maintain per-session state
|
|
55
|
+
* (e.g. last-seen status, approval fingerprints, stability counters).
|
|
56
|
+
*
|
|
57
|
+
* Scripts that don't define createState() receive null as the state argument,
|
|
58
|
+
* making this change fully backward compatible.
|
|
59
|
+
*/
|
|
60
|
+
createState?: () => unknown;
|
|
61
|
+
parseSession?: (state: unknown, input: CliScriptInput & { tail?: string; tailScreen?: CliScreenSnapshot }) => ParsedSession | null;
|
|
62
|
+
detectStatus?: (state: unknown, input: CliStatusInput) => string | null;
|
|
63
|
+
parseApproval?: (state: unknown, input: CliApprovalInput) => { message: string; buttons: string[] } | null;
|
|
54
64
|
resolveAction?: (data: any) => string;
|
|
55
|
-
[name: string]: ((input: any) => any) | undefined;
|
|
65
|
+
[name: string]: ((state: unknown, input: any) => any) | ((data: any) => any) | (() => unknown) | undefined;
|
|
56
66
|
}
|
|
57
67
|
|
|
58
68
|
export interface CliScreenLine {
|
|
@@ -177,10 +177,6 @@ export function buildCoordinatorDelegatedCliLaunchOptions(
|
|
|
177
177
|
const cliArgs = Array.isArray(input.cliArgs) ? [...input.cliArgs] : [];
|
|
178
178
|
const env: Record<string, string> = { ...(input.env || {}), ...COORDINATOR_DELEGATED_ENV_UNSETS };
|
|
179
179
|
|
|
180
|
-
if (cliType === 'hermes-cli' && !hasCliArg(cliArgs, '--ignore-user-config')) {
|
|
181
|
-
cliArgs.unshift('--ignore-user-config');
|
|
182
|
-
}
|
|
183
|
-
|
|
184
180
|
if (cliType === 'claude-cli' && !hasCliArg(cliArgs, '--mcp-config')) {
|
|
185
181
|
cliArgs.unshift('--mcp-config', ensureEmptyDelegatedMcpConfig(input.workspace));
|
|
186
182
|
}
|
|
@@ -28,6 +28,15 @@ export type MeshCoordinatorSetup =
|
|
|
28
28
|
instructions: string
|
|
29
29
|
template: string
|
|
30
30
|
}
|
|
31
|
+
| {
|
|
32
|
+
/** Provider registers MCP via its own CLI command (e.g. `codex mcp add` / `gemini mcp add`). */
|
|
33
|
+
kind: 'cli_command'
|
|
34
|
+
serverName: string
|
|
35
|
+
/** The rendered shell command to execute before launching the coordinator session. */
|
|
36
|
+
command: string
|
|
37
|
+
requiresRestart: boolean
|
|
38
|
+
instructions: string
|
|
39
|
+
}
|
|
31
40
|
| {
|
|
32
41
|
kind: 'unsupported'
|
|
33
42
|
reason: string
|
|
@@ -41,6 +50,8 @@ export interface ResolveMeshCoordinatorSetupOptions {
|
|
|
41
50
|
adhdevMcpCommand?: string
|
|
42
51
|
adhdevMcpEntryPath?: string
|
|
43
52
|
nodeExecutable?: string
|
|
53
|
+
adhdevMcpTransport?: 'local' | 'ipc'
|
|
54
|
+
adhdevMcpPort?: number
|
|
44
55
|
}
|
|
45
56
|
|
|
46
57
|
const DEFAULT_SERVER_NAME = 'adhdev-mesh'
|
|
@@ -58,6 +69,8 @@ function resolveHermesMeshCoordinatorSetup(options: ResolveMeshCoordinatorSetupO
|
|
|
58
69
|
meshId: options.meshId,
|
|
59
70
|
nodeExecutable: options.nodeExecutable,
|
|
60
71
|
adhdevMcpEntryPath: options.adhdevMcpEntryPath,
|
|
72
|
+
adhdevMcpTransport: options.adhdevMcpTransport,
|
|
73
|
+
adhdevMcpPort: options.adhdevMcpPort,
|
|
61
74
|
})
|
|
62
75
|
if (!mcpServer) {
|
|
63
76
|
return {
|
|
@@ -130,6 +143,8 @@ export function resolveMeshCoordinatorSetup(options: ResolveMeshCoordinatorSetup
|
|
|
130
143
|
meshId,
|
|
131
144
|
nodeExecutable: options.nodeExecutable,
|
|
132
145
|
adhdevMcpEntryPath: options.adhdevMcpEntryPath,
|
|
146
|
+
adhdevMcpTransport: options.adhdevMcpTransport,
|
|
147
|
+
adhdevMcpPort: options.adhdevMcpPort,
|
|
133
148
|
})
|
|
134
149
|
if (!mcpServer) {
|
|
135
150
|
return {
|
|
@@ -152,6 +167,24 @@ export function resolveMeshCoordinatorSetup(options: ResolveMeshCoordinatorSetup
|
|
|
152
167
|
if (!instructions || !template?.trim()) {
|
|
153
168
|
return { kind: 'unsupported', reason: 'Provider manual MCP setup is missing instructions or template' }
|
|
154
169
|
}
|
|
170
|
+
const renderedTemplate = renderMeshCoordinatorTemplate(template, {
|
|
171
|
+
meshId,
|
|
172
|
+
workspace,
|
|
173
|
+
serverName,
|
|
174
|
+
adhdevMcpCommand: options.adhdevMcpCommand || DEFAULT_ADHDEV_MCP_COMMAND,
|
|
175
|
+
})
|
|
176
|
+
// Detect if the template is a runnable CLI command (single line, no YAML/JSON structure).
|
|
177
|
+
// If so, use cli_command kind so the daemon can execute it automatically.
|
|
178
|
+
const isCliCommand = !renderedTemplate.trim().includes('\n') && !renderedTemplate.trim().startsWith('{')
|
|
179
|
+
if (isCliCommand) {
|
|
180
|
+
return {
|
|
181
|
+
kind: 'cli_command',
|
|
182
|
+
serverName,
|
|
183
|
+
command: renderedTemplate.trim(),
|
|
184
|
+
requiresRestart: mcpConfig.requiresRestart === true,
|
|
185
|
+
instructions: instructions,
|
|
186
|
+
}
|
|
187
|
+
}
|
|
155
188
|
return {
|
|
156
189
|
kind: 'manual',
|
|
157
190
|
serverName,
|
|
@@ -159,12 +192,7 @@ export function resolveMeshCoordinatorSetup(options: ResolveMeshCoordinatorSetup
|
|
|
159
192
|
configPathCommand: mcpConfig.configPathCommand,
|
|
160
193
|
requiresRestart: mcpConfig.requiresRestart === true,
|
|
161
194
|
instructions,
|
|
162
|
-
template:
|
|
163
|
-
meshId,
|
|
164
|
-
workspace,
|
|
165
|
-
serverName,
|
|
166
|
-
adhdevMcpCommand: options.adhdevMcpCommand || DEFAULT_ADHDEV_MCP_COMMAND,
|
|
167
|
-
}),
|
|
195
|
+
template: renderedTemplate,
|
|
168
196
|
}
|
|
169
197
|
}
|
|
170
198
|
|
|
@@ -196,17 +224,37 @@ function resolveAdhdevMcpServerLaunch(options: {
|
|
|
196
224
|
meshId: string
|
|
197
225
|
nodeExecutable?: string
|
|
198
226
|
adhdevMcpEntryPath?: string
|
|
227
|
+
adhdevMcpTransport?: 'local' | 'ipc'
|
|
228
|
+
adhdevMcpPort?: number
|
|
199
229
|
}): MeshCoordinatorMcpServerLaunch | null {
|
|
200
230
|
const entryPath = resolveAdhdevMcpEntryPath(options.adhdevMcpEntryPath)
|
|
201
231
|
if (!entryPath) return null
|
|
202
232
|
const nodeExecutable = resolveMcpNodeExecutable(options.nodeExecutable)
|
|
203
233
|
if (!nodeExecutable) return null
|
|
234
|
+
const transport = resolveMcpTransport(options.adhdevMcpTransport)
|
|
235
|
+
const args = [entryPath, '--mode', transport, '--repo-mesh', options.meshId]
|
|
236
|
+
const port = resolveMcpPort(options.adhdevMcpPort)
|
|
237
|
+
if (port !== undefined) args.push('--port', String(port))
|
|
204
238
|
return {
|
|
205
239
|
command: nodeExecutable,
|
|
206
|
-
args
|
|
240
|
+
args,
|
|
207
241
|
}
|
|
208
242
|
}
|
|
209
243
|
|
|
244
|
+
function resolveMcpTransport(explicitTransport?: 'local' | 'ipc'): 'local' | 'ipc' {
|
|
245
|
+
if (explicitTransport === 'local' || explicitTransport === 'ipc') return explicitTransport
|
|
246
|
+
const envTransport = process.env.ADHDEV_COORDINATOR_MCP_TRANSPORT?.trim()
|
|
247
|
+
return envTransport === 'local' ? 'local' : 'ipc'
|
|
248
|
+
}
|
|
249
|
+
|
|
250
|
+
function resolveMcpPort(explicitPort?: number): number | undefined {
|
|
251
|
+
if (typeof explicitPort === 'number' && Number.isInteger(explicitPort) && explicitPort > 0) return explicitPort
|
|
252
|
+
const raw = process.env.ADHDEV_COORDINATOR_MCP_PORT?.trim()
|
|
253
|
+
if (!raw) return undefined
|
|
254
|
+
const parsed = Number(raw)
|
|
255
|
+
return Number.isInteger(parsed) && parsed > 0 ? parsed : undefined
|
|
256
|
+
}
|
|
257
|
+
|
|
210
258
|
function resolveMcpNodeExecutable(explicitExecutable?: string): string | null {
|
|
211
259
|
const explicit = explicitExecutable?.trim()
|
|
212
260
|
if (explicit) return explicit
|
package/src/commands/router.ts
CHANGED
|
@@ -332,7 +332,7 @@ export class DaemonCommandRouter {
|
|
|
332
332
|
this.deps = deps;
|
|
333
333
|
}
|
|
334
334
|
|
|
335
|
-
|
|
335
|
+
public getCachedInlineMesh(meshId: string, inlineMesh?: unknown): any | undefined {
|
|
336
336
|
if (inlineMesh && typeof inlineMesh === 'object') {
|
|
337
337
|
this.inlineMeshCache.set(meshId, inlineMesh as any);
|
|
338
338
|
return inlineMesh as any;
|
|
@@ -1331,6 +1331,56 @@ export class DaemonCommandRouter {
|
|
|
1331
1331
|
}
|
|
1332
1332
|
}
|
|
1333
1333
|
|
|
1334
|
+
case 'get_mesh_queue': {
|
|
1335
|
+
const meshId = typeof args?.meshId === 'string' ? args.meshId.trim() : '';
|
|
1336
|
+
if (!meshId) return { success: false, error: 'meshId required' };
|
|
1337
|
+
try {
|
|
1338
|
+
const { getQueue } = await import('../mesh/mesh-work-queue.js');
|
|
1339
|
+
const status = Array.isArray(args?.status)
|
|
1340
|
+
? args.status.map((s: any) => typeof s === 'string' ? s.trim() : '').filter(Boolean)
|
|
1341
|
+
: undefined;
|
|
1342
|
+
const queue = getQueue(meshId, { status: status as any });
|
|
1343
|
+
return { success: true, queue };
|
|
1344
|
+
} catch (e: any) {
|
|
1345
|
+
return { success: false, error: e.message };
|
|
1346
|
+
}
|
|
1347
|
+
}
|
|
1348
|
+
|
|
1349
|
+
case 'cancel_mesh_queue_task': {
|
|
1350
|
+
const meshId = typeof args?.meshId === 'string' ? args.meshId.trim() : '';
|
|
1351
|
+
const taskId = typeof args?.taskId === 'string' ? args.taskId.trim() : '';
|
|
1352
|
+
if (!meshId || !taskId) return { success: false, error: 'meshId and taskId required' };
|
|
1353
|
+
try {
|
|
1354
|
+
const { cancelTask } = await import('../mesh/mesh-work-queue.js');
|
|
1355
|
+
const reason = typeof args?.reason === 'string' ? args.reason : undefined;
|
|
1356
|
+
const task = cancelTask(meshId, taskId, { reason });
|
|
1357
|
+
if (!task) return { success: false, error: `Queue task '${taskId}' not found` };
|
|
1358
|
+
return { success: true, task };
|
|
1359
|
+
} catch (e: any) {
|
|
1360
|
+
return { success: false, error: e.message };
|
|
1361
|
+
}
|
|
1362
|
+
}
|
|
1363
|
+
|
|
1364
|
+
case 'requeue_mesh_queue_task': {
|
|
1365
|
+
const meshId = typeof args?.meshId === 'string' ? args.meshId.trim() : '';
|
|
1366
|
+
const taskId = typeof args?.taskId === 'string' ? args.taskId.trim() : '';
|
|
1367
|
+
if (!meshId || !taskId) return { success: false, error: 'meshId and taskId required' };
|
|
1368
|
+
try {
|
|
1369
|
+
const { requeueTask } = await import('../mesh/mesh-work-queue.js');
|
|
1370
|
+
const task = requeueTask(meshId, taskId, {
|
|
1371
|
+
reason: typeof args?.reason === 'string' ? args.reason : undefined,
|
|
1372
|
+
targetNodeId: typeof args?.targetNodeId === 'string' ? args.targetNodeId.trim() : undefined,
|
|
1373
|
+
targetSessionId: typeof args?.targetSessionId === 'string' ? args.targetSessionId.trim() : undefined,
|
|
1374
|
+
clearTargetNode: args?.clearTargetNode === true,
|
|
1375
|
+
clearTargetSession: args?.clearTargetSession !== false,
|
|
1376
|
+
});
|
|
1377
|
+
if (!task) return { success: false, error: `Queue task '${taskId}' not found` };
|
|
1378
|
+
return { success: true, task };
|
|
1379
|
+
} catch (e: any) {
|
|
1380
|
+
return { success: false, error: e.message };
|
|
1381
|
+
}
|
|
1382
|
+
}
|
|
1383
|
+
|
|
1334
1384
|
case 'add_mesh_node': {
|
|
1335
1385
|
const meshId = typeof args?.meshId === 'string' ? args.meshId.trim() : '';
|
|
1336
1386
|
const workspace = typeof args?.workspace === 'string' ? args.workspace.trim() : '';
|
|
@@ -1519,7 +1569,13 @@ export class DaemonCommandRouter {
|
|
|
1519
1569
|
appendLedgerEntry(meshId, {
|
|
1520
1570
|
kind: 'node_removed',
|
|
1521
1571
|
nodeId,
|
|
1522
|
-
payload: {
|
|
1572
|
+
payload: {
|
|
1573
|
+
worktree: !!node?.isLocalWorktree,
|
|
1574
|
+
sessionCleanupMode,
|
|
1575
|
+
workspace: typeof node?.workspace === 'string' ? node.workspace : undefined,
|
|
1576
|
+
daemonId: typeof node?.daemonId === 'string' ? node.daemonId : undefined,
|
|
1577
|
+
worktreeBranch: typeof node?.worktreeBranch === 'string' ? node.worktreeBranch : undefined,
|
|
1578
|
+
},
|
|
1523
1579
|
});
|
|
1524
1580
|
} catch { /* ledger append is best-effort */ }
|
|
1525
1581
|
}
|
|
@@ -1711,6 +1767,105 @@ export class DaemonCommandRouter {
|
|
|
1711
1767
|
};
|
|
1712
1768
|
}
|
|
1713
1769
|
|
|
1770
|
+
// ─── CLI-command MCP registration (Codex, Gemini CLI) ───────────
|
|
1771
|
+
if (coordinatorSetup.kind === 'cli_command') {
|
|
1772
|
+
// Build coordinator prompt first — fail closed on errors.
|
|
1773
|
+
let cliCmdSystemPrompt = '';
|
|
1774
|
+
try {
|
|
1775
|
+
cliCmdSystemPrompt = buildCoordinatorSystemPrompt({ mesh, coordinatorCliType: cliType });
|
|
1776
|
+
} catch (error: any) {
|
|
1777
|
+
const message = error?.message || String(error);
|
|
1778
|
+
LOG.error('MeshCoordinator', `Failed to build coordinator prompt: ${message}`);
|
|
1779
|
+
return {
|
|
1780
|
+
success: false,
|
|
1781
|
+
code: 'mesh_coordinator_prompt_failed',
|
|
1782
|
+
error: `Failed to build Repo Mesh coordinator prompt: ${message}`,
|
|
1783
|
+
meshId, cliType, workspace,
|
|
1784
|
+
};
|
|
1785
|
+
}
|
|
1786
|
+
|
|
1787
|
+
// Run the provider's MCP registration command.
|
|
1788
|
+
try {
|
|
1789
|
+
const { execFileSync: execCmdSync } = await import('node:child_process');
|
|
1790
|
+
const cmdParts = coordinatorSetup.command.trim().split(/\s+/);
|
|
1791
|
+
const [regCmd, ...regArgs] = cmdParts;
|
|
1792
|
+
LOG.info('MeshCoordinator', `Running MCP registration: ${coordinatorSetup.command}`);
|
|
1793
|
+
execCmdSync(regCmd, regArgs, { stdio: 'pipe', timeout: 15_000 });
|
|
1794
|
+
} catch (error: any) {
|
|
1795
|
+
// Non-fatal — server may already be registered (providers return exit 1 on duplicate).
|
|
1796
|
+
LOG.warn('MeshCoordinator', `MCP registration command failed (may be pre-registered): ${error?.message || error}`);
|
|
1797
|
+
}
|
|
1798
|
+
|
|
1799
|
+
// Inject system prompt using provider-native methods.
|
|
1800
|
+
// Codex: -c 'instructions="..."' CLI config override
|
|
1801
|
+
// Gemini: write GEMINI.md to workspace (auto-loaded as context)
|
|
1802
|
+
const cliCmdArgs: string[] = [];
|
|
1803
|
+
const cliCmdEnv: Record<string, string> = {};
|
|
1804
|
+
if (cliCmdSystemPrompt) {
|
|
1805
|
+
if (cliType === 'codex-cli') {
|
|
1806
|
+
// Codex reads `developer_instructions` from config.toml as system instructions.
|
|
1807
|
+
// The -c flag overrides a config key for this session only.
|
|
1808
|
+
cliCmdArgs.push('-c', `developer_instructions=${JSON.stringify(cliCmdSystemPrompt)}`);
|
|
1809
|
+
} else if (cliType === 'gemini-cli') {
|
|
1810
|
+
// Gemini CLI auto-loads GEMINI.md from CWD as project context.
|
|
1811
|
+
// Write a temporary GEMINI.md to the workspace before launch.
|
|
1812
|
+
try {
|
|
1813
|
+
const { writeFileSync: wfs, existsSync: efs, readFileSync: rfs } = await import('node:fs');
|
|
1814
|
+
const geminiMdPath = `${workspace}/GEMINI.md`;
|
|
1815
|
+
const marker = '<!-- adhdev-mesh-coordinator-prompt -->';
|
|
1816
|
+
const markerEnd = '<!-- /adhdev-mesh-coordinator-prompt -->';
|
|
1817
|
+
const block = `${marker}\n${cliCmdSystemPrompt}\n${markerEnd}`;
|
|
1818
|
+
if (efs(geminiMdPath)) {
|
|
1819
|
+
const existing = rfs(geminiMdPath, 'utf-8');
|
|
1820
|
+
// Replace existing block or append
|
|
1821
|
+
const replaced = existing.replace(
|
|
1822
|
+
new RegExp(`${marker}[\\s\\S]*?${markerEnd}`, 'g'),
|
|
1823
|
+
block,
|
|
1824
|
+
);
|
|
1825
|
+
wfs(geminiMdPath, replaced.includes(marker) ? replaced : `${existing}\n\n${block}`);
|
|
1826
|
+
} else {
|
|
1827
|
+
wfs(geminiMdPath, block);
|
|
1828
|
+
}
|
|
1829
|
+
LOG.info('MeshCoordinator', `Wrote coordinator prompt to ${workspace}/GEMINI.md`);
|
|
1830
|
+
} catch (e: any) {
|
|
1831
|
+
LOG.warn('MeshCoordinator', `Could not write GEMINI.md: ${e?.message || e}`);
|
|
1832
|
+
}
|
|
1833
|
+
}
|
|
1834
|
+
}
|
|
1835
|
+
|
|
1836
|
+
const cliCmdLaunch: any = await this.deps.cliManager.handleCliCommand('launch_cli', {
|
|
1837
|
+
cliType,
|
|
1838
|
+
dir: workspace,
|
|
1839
|
+
cliArgs: cliCmdArgs.length > 0 ? cliCmdArgs : undefined,
|
|
1840
|
+
env: Object.keys(cliCmdEnv).length > 0 ? cliCmdEnv : undefined,
|
|
1841
|
+
settings: { meshCoordinatorFor: meshId },
|
|
1842
|
+
});
|
|
1843
|
+
|
|
1844
|
+
if (!cliCmdLaunch?.success) {
|
|
1845
|
+
return { success: false, error: cliCmdLaunch?.error || 'Failed to launch CLI session' };
|
|
1846
|
+
}
|
|
1847
|
+
|
|
1848
|
+
LOG.info('MeshCoordinator', `Launched ${cliType} coordinator (cli_command) for mesh ${meshId}`);
|
|
1849
|
+
try {
|
|
1850
|
+
const { appendLedgerEntry } = await import('../mesh/mesh-ledger.js');
|
|
1851
|
+
appendLedgerEntry(meshId, {
|
|
1852
|
+
kind: 'coordinator_started',
|
|
1853
|
+
sessionId: cliCmdLaunch.sessionId || cliCmdLaunch.id,
|
|
1854
|
+
providerType: cliType,
|
|
1855
|
+
payload: { workspace },
|
|
1856
|
+
});
|
|
1857
|
+
} catch { /* best-effort */ }
|
|
1858
|
+
|
|
1859
|
+
return {
|
|
1860
|
+
success: true,
|
|
1861
|
+
meshId,
|
|
1862
|
+
cliType,
|
|
1863
|
+
workspace,
|
|
1864
|
+
sessionId: cliCmdLaunch.sessionId || cliCmdLaunch.id,
|
|
1865
|
+
mcpRegistered: true,
|
|
1866
|
+
};
|
|
1867
|
+
}
|
|
1868
|
+
|
|
1714
1869
|
const configFormat = coordinatorSetup.configFormat as MeshCoordinatorConfigFormat;
|
|
1715
1870
|
if (configFormat !== 'claude_mcp_json' && configFormat !== 'hermes_config_yaml') {
|
|
1716
1871
|
return {
|
|
@@ -1777,9 +1932,11 @@ export class DaemonCommandRouter {
|
|
|
1777
1932
|
args: coordinatorSetup.mcpServer.args,
|
|
1778
1933
|
};
|
|
1779
1934
|
if (args?.inlineMesh) {
|
|
1935
|
+
const modeArgIndex = coordinatorSetup.mcpServer.args.findIndex((value: string) => value === '--mode');
|
|
1936
|
+
const mcpTransport = modeArgIndex >= 0 ? coordinatorSetup.mcpServer.args[modeArgIndex + 1] : 'ipc';
|
|
1780
1937
|
mcpServerEntry.env = {
|
|
1781
1938
|
ADHDEV_INLINE_MESH: JSON.stringify(mesh),
|
|
1782
|
-
ADHDEV_MCP_TRANSPORT: 'ipc',
|
|
1939
|
+
ADHDEV_MCP_TRANSPORT: mcpTransport === 'local' ? 'local' : 'ipc',
|
|
1783
1940
|
};
|
|
1784
1941
|
}
|
|
1785
1942
|
|
|
@@ -113,11 +113,18 @@ export async function handleOpenPanel(h: CommandHelpers, args: any): Promise<Com
|
|
|
113
113
|
export async function handlePtyInput(h: CommandHelpers, args: any): Promise<CommandResult> {
|
|
114
114
|
const { cliType, data, targetSessionId } = args || {};
|
|
115
115
|
if (!data) return { success: false, error: 'data required' };
|
|
116
|
+
|
|
117
|
+
// Filter out VT100/VT420 Device Attributes responses (e.g. \x1b[?1;2c or \x1b[>0;276;0c)
|
|
118
|
+
// These are echoed by xterm.js in the dashboard in response to \x1b[c queries
|
|
119
|
+
// and pollute the CLI input buffer.
|
|
120
|
+
const cleanData = typeof data === 'string' ? data.replace(/\x1b\[[?>][0-9;]*c/g, '') : data;
|
|
121
|
+
if (!cleanData) return { success: true };
|
|
122
|
+
|
|
116
123
|
const adapter = h.getCliAdapter(targetSessionId || cliType);
|
|
117
124
|
if (!adapter || typeof adapter.writeRaw !== 'function') {
|
|
118
125
|
return { success: false, error: `CLI adapter not found: ${targetSessionId || cliType || 'unknown'}` };
|
|
119
126
|
}
|
|
120
|
-
await adapter.writeRaw(
|
|
127
|
+
await adapter.writeRaw(cleanData);
|
|
121
128
|
return { success: true };
|
|
122
129
|
}
|
|
123
130
|
|
package/src/index.ts
CHANGED
|
@@ -154,8 +154,8 @@ export { appendLedgerEntry, readLedgerEntries, getLedgerSummary, getLedgerDir, g
|
|
|
154
154
|
export type { MeshLedgerEntry, MeshLedgerKind, MeshLedgerSummary, ReadLedgerOptions, SessionRecoveryContext } from './mesh/mesh-ledger.js';
|
|
155
155
|
|
|
156
156
|
// ── Mesh Work Queue (GUPP) ──
|
|
157
|
-
export { enqueueTask, getQueue, claimNextTask, updateTaskStatus, updateSessionTaskStatus } from './mesh/mesh-work-queue.js';
|
|
158
|
-
export type { MeshWorkQueueEntry, MeshTaskStatus } from './mesh/mesh-work-queue.js';
|
|
157
|
+
export { enqueueTask, getQueue, claimNextTask, updateTaskStatus, updateSessionTaskStatus, cancelTask, requeueTask, getMeshQueueStats } from './mesh/mesh-work-queue.js';
|
|
158
|
+
export type { MeshWorkQueueEntry, MeshTaskStatus, MeshWorkQueueStats } from './mesh/mesh-work-queue.js';
|
|
159
159
|
|
|
160
160
|
// ── Mesh Events ──
|
|
161
161
|
export { triggerMeshQueue } from './mesh/mesh-events.js';
|