@adhdev/daemon-core 0.9.82-rc.186 → 0.9.82-rc.188
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 +1 -0
- package/dist/commands/cli-manager.d.ts +2 -1
- package/dist/commands/mesh-coordinator.d.ts +13 -0
- package/dist/commands/router.d.ts +5 -1
- package/dist/config/chat-history.d.ts +1 -0
- package/dist/git/git-commands.d.ts +2 -0
- package/dist/git/git-types.d.ts +2 -0
- package/dist/index.d.ts +2 -2
- package/dist/index.js +15461 -14552
- package/dist/index.js.map +1 -1
- package/dist/index.mjs +14411 -13502
- package/dist/index.mjs.map +1 -1
- package/dist/mesh/beads-db.d.ts +1 -0
- package/dist/mesh/mesh-events.d.ts +46 -1
- package/dist/mesh/mesh-work-queue.d.ts +1 -0
- package/dist/providers/cli-provider-instance.d.ts +4 -0
- package/dist/providers/contracts.d.ts +32 -1
- package/dist/providers/native-history/dispatcher.d.ts +2 -0
- package/dist/providers/sdk/v1/types/common/index.d.ts +35 -1
- package/dist/providers/spec/cli-adapter.d.ts +1 -0
- package/dist/providers/spec/driver.d.ts +6 -1
- package/dist/providers/spec/native-history-executor.d.ts +2 -0
- package/dist/providers/spec/schema.gen.d.ts +22 -0
- package/dist/providers/spec/types.d.ts +10 -0
- package/dist/repo-mesh-types.d.ts +6 -0
- package/package.json +1 -1
- package/src/boot/daemon-lifecycle.ts +2 -0
- package/src/commands/chat-commands.ts +206 -18
- package/src/commands/cli-manager.ts +56 -14
- package/src/commands/mesh-coordinator.ts +110 -5
- package/src/commands/router.ts +146 -21
- package/src/config/chat-history.ts +4 -0
- package/src/git/git-commands.ts +20 -2
- package/src/git/git-status.ts +35 -6
- package/src/git/git-types.ts +2 -0
- package/src/index.ts +2 -2
- package/src/mesh/beads-db.ts +4 -0
- package/src/mesh/mesh-events.ts +264 -4
- package/src/mesh/mesh-work-queue.ts +4 -0
- package/src/providers/cli-provider-instance.ts +122 -13
- package/src/providers/contracts.d.ts +55 -0
- package/src/providers/contracts.ts +36 -1
- package/src/providers/native-history/dispatcher.ts +126 -17
- package/src/providers/provider-loader.ts +4 -7
- package/src/providers/provider-schema.ts +56 -1
- package/src/providers/sdk/v1/schemas/cli/provider.schema.json +46 -0
- package/src/providers/sdk/v1/types/common/index.ts +19 -0
- package/src/providers/spec/cli-adapter.ts +32 -5
- package/src/providers/spec/driver.ts +68 -1
- package/src/providers/spec/evaluator.ts +11 -1
- package/src/providers/spec/native-history-executor.ts +93 -27
- package/src/providers/spec/schema.gen.ts +12 -1
- package/src/providers/spec/schema.json +21 -1
- package/src/providers/spec/types.ts +10 -0
- package/src/repo-mesh-types.ts +6 -0
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import { createHash } from 'node:crypto'
|
|
2
2
|
import { existsSync, readFileSync, writeFileSync } from 'node:fs'
|
|
3
3
|
import * as os from 'node:os'
|
|
4
|
-
import { isAbsolute, join, resolve } from 'node:path'
|
|
4
|
+
import { basename, isAbsolute, join, resolve } from 'node:path'
|
|
5
5
|
import { LOG } from '../logging/logger.js'
|
|
6
6
|
import type {
|
|
7
7
|
MeshCoordinatorMcpConfigFormat,
|
|
@@ -39,6 +39,7 @@ export type MeshCoordinatorSetup =
|
|
|
39
39
|
command: string
|
|
40
40
|
requiresRestart: boolean
|
|
41
41
|
instructions: string
|
|
42
|
+
mcpServer: MeshCoordinatorMcpServerLaunch
|
|
42
43
|
}
|
|
43
44
|
| {
|
|
44
45
|
kind: 'unsupported'
|
|
@@ -71,6 +72,8 @@ function resolveHermesMeshCoordinatorSetup(options: ResolveMeshCoordinatorSetupO
|
|
|
71
72
|
const mcpServer = resolveAdhdevMcpServerLaunch({
|
|
72
73
|
meshId: options.meshId,
|
|
73
74
|
adhdevMcpCommand: options.adhdevMcpCommand,
|
|
75
|
+
adhdevMcpEntryPath: options.adhdevMcpEntryPath,
|
|
76
|
+
nodeExecutable: options.nodeExecutable,
|
|
74
77
|
adhdevMcpTransport: options.adhdevMcpTransport,
|
|
75
78
|
adhdevMcpPort: options.adhdevMcpPort,
|
|
76
79
|
})
|
|
@@ -144,6 +147,8 @@ export function resolveMeshCoordinatorSetup(options: ResolveMeshCoordinatorSetup
|
|
|
144
147
|
const mcpServer = resolveAdhdevMcpServerLaunch({
|
|
145
148
|
meshId,
|
|
146
149
|
adhdevMcpCommand: options.adhdevMcpCommand,
|
|
150
|
+
adhdevMcpEntryPath: options.adhdevMcpEntryPath,
|
|
151
|
+
nodeExecutable: options.nodeExecutable,
|
|
147
152
|
adhdevMcpTransport: options.adhdevMcpTransport,
|
|
148
153
|
adhdevMcpPort: options.adhdevMcpPort,
|
|
149
154
|
})
|
|
@@ -168,22 +173,41 @@ export function resolveMeshCoordinatorSetup(options: ResolveMeshCoordinatorSetup
|
|
|
168
173
|
if (!instructions || !template?.trim()) {
|
|
169
174
|
return { kind: 'unsupported', reason: 'Provider manual MCP setup is missing instructions or template' }
|
|
170
175
|
}
|
|
171
|
-
const
|
|
176
|
+
const mcpServer = resolveAdhdevMcpServerLaunch({
|
|
177
|
+
meshId,
|
|
178
|
+
adhdevMcpCommand: options.adhdevMcpCommand,
|
|
179
|
+
adhdevMcpEntryPath: options.adhdevMcpEntryPath,
|
|
180
|
+
nodeExecutable: options.nodeExecutable,
|
|
181
|
+
adhdevMcpTransport: options.adhdevMcpTransport,
|
|
182
|
+
adhdevMcpPort: options.adhdevMcpPort,
|
|
183
|
+
})
|
|
184
|
+
if (!mcpServer) {
|
|
185
|
+
return {
|
|
186
|
+
kind: 'unsupported',
|
|
187
|
+
reason: 'Could not resolve the ADHDev MCP server entrypoint and transport arguments',
|
|
188
|
+
}
|
|
189
|
+
}
|
|
190
|
+
let renderedTemplate = renderMeshCoordinatorTemplate(template, {
|
|
172
191
|
meshId,
|
|
173
192
|
workspace,
|
|
174
193
|
serverName,
|
|
175
|
-
adhdevMcpCommand:
|
|
194
|
+
adhdevMcpCommand: mcpServer.command,
|
|
195
|
+
adhdevMcpArgs: mcpServer.args.join(' '),
|
|
176
196
|
})
|
|
177
197
|
// Detect if the template is a runnable CLI command (single line, no YAML/JSON structure).
|
|
178
198
|
// If so, use cli_command kind so the daemon can execute it automatically.
|
|
179
199
|
const isCliCommand = !renderedTemplate.trim().includes('\n') && !renderedTemplate.trim().startsWith('{')
|
|
180
200
|
if (isCliCommand) {
|
|
201
|
+
if (!/\{\{\s*adhdevMcpArgs\s*\}\}/.test(template)) {
|
|
202
|
+
renderedTemplate = replaceLegacyCliCommandMcpArgs(renderedTemplate, mcpServer.args)
|
|
203
|
+
}
|
|
181
204
|
return {
|
|
182
205
|
kind: 'cli_command',
|
|
183
206
|
serverName,
|
|
184
207
|
command: renderedTemplate.trim(),
|
|
185
208
|
requiresRestart: mcpConfig.requiresRestart === true,
|
|
186
209
|
instructions: instructions,
|
|
210
|
+
mcpServer,
|
|
187
211
|
}
|
|
188
212
|
}
|
|
189
213
|
return {
|
|
@@ -204,7 +228,14 @@ export function resolveMeshCoordinatorSetup(options: ResolveMeshCoordinatorSetup
|
|
|
204
228
|
}
|
|
205
229
|
|
|
206
230
|
function renderMeshCoordinatorTemplate(template: string, values: Record<string, string>): string {
|
|
207
|
-
return template.replace(/\{\{\s*(meshId|workspace|serverName|adhdevMcpCommand)\s*\}\}/g, (_, key: string) => values[key] || '')
|
|
231
|
+
return template.replace(/\{\{\s*(meshId|workspace|serverName|adhdevMcpCommand|adhdevMcpArgs)\s*\}\}/g, (_, key: string) => values[key] || '')
|
|
232
|
+
}
|
|
233
|
+
|
|
234
|
+
function replaceLegacyCliCommandMcpArgs(command: string, args: string[]): string {
|
|
235
|
+
return command.replace(
|
|
236
|
+
/\bmcp\s+--mode\s+(?:ipc|local)\s+--repo-mesh\s+\S+(?:\s+--port\s+\d+)?\s*$/,
|
|
237
|
+
args.join(' '),
|
|
238
|
+
)
|
|
208
239
|
}
|
|
209
240
|
|
|
210
241
|
function resolveHermesCoordinatorHome(meshId: string, workspace: string): string {
|
|
@@ -224,12 +255,29 @@ function resolveMcpConfigPath(configPath: string, workspace: string): string {
|
|
|
224
255
|
function resolveAdhdevMcpServerLaunch(options: {
|
|
225
256
|
meshId: string
|
|
226
257
|
adhdevMcpCommand?: string
|
|
258
|
+
adhdevMcpEntryPath?: string
|
|
259
|
+
nodeExecutable?: string
|
|
227
260
|
adhdevMcpTransport?: 'local' | 'ipc'
|
|
228
261
|
adhdevMcpPort?: number
|
|
229
262
|
}): MeshCoordinatorMcpServerLaunch | null {
|
|
263
|
+
const directEntryPath = resolveAdhdevMcpEntryPath(options.adhdevMcpEntryPath)
|
|
264
|
+
if (directEntryPath) {
|
|
265
|
+
const transport = resolveMcpTransport(options.adhdevMcpTransport)
|
|
266
|
+
const args = [directEntryPath, '--mode', transport, '--repo-mesh', options.meshId]
|
|
267
|
+
const port = resolveMcpPort(options.adhdevMcpPort)
|
|
268
|
+
if (port !== undefined) args.push('--port', String(port))
|
|
269
|
+
return {
|
|
270
|
+
command: resolveNodeExecutable(options.nodeExecutable),
|
|
271
|
+
args,
|
|
272
|
+
}
|
|
273
|
+
}
|
|
274
|
+
|
|
230
275
|
const command = resolveAdhdevCommand(options.adhdevMcpCommand)
|
|
231
276
|
const transport = resolveMcpTransport(options.adhdevMcpTransport)
|
|
232
|
-
const
|
|
277
|
+
const directMcpEntrypoint = basename(command).startsWith('adhdev-mcp')
|
|
278
|
+
|| command.includes('/vendor/mcp-server/')
|
|
279
|
+
|| command.includes('\\vendor\\mcp-server\\')
|
|
280
|
+
const args = [...(directMcpEntrypoint ? [] : ['mcp']), '--mode', transport, '--repo-mesh', options.meshId]
|
|
233
281
|
const port = resolveMcpPort(options.adhdevMcpPort)
|
|
234
282
|
if (port !== undefined) args.push('--port', String(port))
|
|
235
283
|
return {
|
|
@@ -242,6 +290,17 @@ function resolveAdhdevCommand(explicitCommand?: string): string {
|
|
|
242
290
|
return explicitCommand?.trim() || process.env.ADHDEV_COORDINATOR_MCP_COMMAND?.trim() || DEFAULT_ADHDEV_MCP_COMMAND
|
|
243
291
|
}
|
|
244
292
|
|
|
293
|
+
function resolveAdhdevMcpEntryPath(explicitEntryPath?: string): string | null {
|
|
294
|
+
const entryPath = explicitEntryPath?.trim() || process.env.ADHDEV_COORDINATOR_MCP_ENTRY_PATH?.trim()
|
|
295
|
+
return entryPath || null
|
|
296
|
+
}
|
|
297
|
+
|
|
298
|
+
function resolveNodeExecutable(explicitNodeExecutable?: string): string {
|
|
299
|
+
return explicitNodeExecutable?.trim()
|
|
300
|
+
|| process.env.ADHDEV_COORDINATOR_NODE_EXECUTABLE?.trim()
|
|
301
|
+
|| process.execPath
|
|
302
|
+
}
|
|
303
|
+
|
|
245
304
|
function resolveMcpTransport(explicitTransport?: 'local' | 'ipc'): 'local' | 'ipc' {
|
|
246
305
|
if (explicitTransport === 'local' || explicitTransport === 'ipc') return explicitTransport
|
|
247
306
|
const envTransport = process.env.ADHDEV_COORDINATOR_MCP_TRANSPORT?.trim()
|
|
@@ -418,6 +477,52 @@ export interface PtyExecResult {
|
|
|
418
477
|
timedOut: boolean
|
|
419
478
|
}
|
|
420
479
|
|
|
480
|
+
export interface MeshCoordinatorRegistrationStep {
|
|
481
|
+
command: string
|
|
482
|
+
args: string[]
|
|
483
|
+
required: boolean
|
|
484
|
+
label: 'remove_existing' | 'register'
|
|
485
|
+
}
|
|
486
|
+
|
|
487
|
+
/**
|
|
488
|
+
* Codex rejects `mcp add` when a server with the same name already exists.
|
|
489
|
+
* Replace that entry before registering so transport/mesh changes do not leave
|
|
490
|
+
* a fresh coordinator attached to stale MCP launch arguments.
|
|
491
|
+
*/
|
|
492
|
+
export function buildMeshCoordinatorRegistrationPlan(
|
|
493
|
+
cliType: string,
|
|
494
|
+
serverName: string,
|
|
495
|
+
registrationCommand: string,
|
|
496
|
+
): MeshCoordinatorRegistrationStep[] {
|
|
497
|
+
const commandParts = registrationCommand.trim().split(/\s+/).filter(Boolean)
|
|
498
|
+
const [command, ...args] = commandParts
|
|
499
|
+
if (!command) return []
|
|
500
|
+
|
|
501
|
+
const register: MeshCoordinatorRegistrationStep = {
|
|
502
|
+
command,
|
|
503
|
+
args,
|
|
504
|
+
required: true,
|
|
505
|
+
label: 'register',
|
|
506
|
+
}
|
|
507
|
+
if (
|
|
508
|
+
cliType === 'codex-cli'
|
|
509
|
+
&& basename(command) === 'codex'
|
|
510
|
+
&& args[0] === 'mcp'
|
|
511
|
+
&& args[1] === 'add'
|
|
512
|
+
) {
|
|
513
|
+
return [
|
|
514
|
+
{
|
|
515
|
+
command,
|
|
516
|
+
args: ['mcp', 'remove', serverName],
|
|
517
|
+
required: false,
|
|
518
|
+
label: 'remove_existing',
|
|
519
|
+
},
|
|
520
|
+
register,
|
|
521
|
+
]
|
|
522
|
+
}
|
|
523
|
+
return [register]
|
|
524
|
+
}
|
|
525
|
+
|
|
421
526
|
/**
|
|
422
527
|
* Run a one-shot CLI command under a real PTY and collect its output.
|
|
423
528
|
*
|
package/src/commands/router.ts
CHANGED
|
@@ -849,7 +849,7 @@ function readCachedInlineMeshActiveSessions(node: any): string[] {
|
|
|
849
849
|
return sessionId ? [sessionId] : [];
|
|
850
850
|
}
|
|
851
851
|
|
|
852
|
-
function readCachedInlineMeshActiveSessionDetails(node: any): Array<Record<string, unknown>> {
|
|
852
|
+
export function readCachedInlineMeshActiveSessionDetails(node: any): Array<Record<string, unknown>> {
|
|
853
853
|
const cachedStatus = readObjectRecord(node?.cachedStatus);
|
|
854
854
|
const activeSession = readObjectRecord(cachedStatus.activeSession);
|
|
855
855
|
const fallbackSession = Object.keys(activeSession).length
|
|
@@ -877,9 +877,14 @@ function readCachedInlineMeshActiveSessionDetails(node: any): Array<Record<strin
|
|
|
877
877
|
node?.provider_type,
|
|
878
878
|
),
|
|
879
879
|
state: readStringValue(fallbackSession.status, fallbackSession.state, fallbackSession.lifecycle),
|
|
880
|
+
chatStatus: readStringValue(fallbackSession.chatStatus, fallbackSession.chat_status),
|
|
880
881
|
lifecycle: readStringValue(fallbackSession.lifecycle),
|
|
881
882
|
title: readStringValue(fallbackSession.title, fallbackSession.displayName, fallbackSession.display_name) ?? null,
|
|
882
883
|
workspace: readStringValue(fallbackSession.workspace, node?.workspace) ?? null,
|
|
884
|
+
role: readStringValue(fallbackSession.role) ?? null,
|
|
885
|
+
isSelfCoordinator: fallbackSession.isSelfCoordinator === true || fallbackSession.is_self_coordinator === true,
|
|
886
|
+
createdAt: readStringValue(fallbackSession.createdAt, fallbackSession.created_at) ?? null,
|
|
887
|
+
startedAt: readStringValue(fallbackSession.startedAt, fallbackSession.started_at) ?? null,
|
|
883
888
|
lastActivityAt: readStringValue(fallbackSession.lastActivityAt, fallbackSession.last_activity_at) ?? null,
|
|
884
889
|
recoveryState: readStringValue(fallbackSession.recoveryState, fallbackSession.recovery_state) ?? null,
|
|
885
890
|
isCached: true,
|
|
@@ -1067,15 +1072,28 @@ async function hydrateInlineMeshDirectTruth(args: {
|
|
|
1067
1072
|
}
|
|
1068
1073
|
|
|
1069
1074
|
function summarizeMeshSessionRecord(record: any): Record<string, unknown> {
|
|
1075
|
+
const meta = readObjectRecord(record?.meta);
|
|
1076
|
+
const isSelfCoordinator = Boolean(readStringValue(meta.meshCoordinatorFor));
|
|
1077
|
+
const chatStatus = readStringValue(record?.chatStatus, record?.activeChat?.status, meta.chatStatus, meta.sessionStatus);
|
|
1078
|
+
const state = readLiveMeshSessionState(record);
|
|
1079
|
+
const statusNote = isSelfCoordinator && (!chatStatus || chatStatus === 'idle' || state === 'idle')
|
|
1080
|
+
? 'Coordinator self status is sampled from the session host and may read idle while the coordinator is generating this response.'
|
|
1081
|
+
: null;
|
|
1070
1082
|
return {
|
|
1071
1083
|
sessionId: readStringValue(record?.sessionId) || 'unknown',
|
|
1072
1084
|
providerType: readStringValue(record?.providerType),
|
|
1073
|
-
state
|
|
1085
|
+
state,
|
|
1086
|
+
chatStatus,
|
|
1074
1087
|
lifecycle: readStringValue(record?.lifecycle),
|
|
1075
1088
|
surfaceKind: getSessionHostSurfaceKind(record as any),
|
|
1076
|
-
recoveryState: readStringValue(
|
|
1089
|
+
recoveryState: readStringValue(meta.runtimeRecoveryState) ?? null,
|
|
1077
1090
|
workspace: readStringValue(record?.workspace) ?? null,
|
|
1078
1091
|
title: readStringValue(record?.displayName, record?.workspaceLabel) ?? null,
|
|
1092
|
+
role: isSelfCoordinator ? 'coordinator' : readStringValue(meta.meshRole, meta.role) ?? null,
|
|
1093
|
+
isSelfCoordinator,
|
|
1094
|
+
statusNote,
|
|
1095
|
+
createdAt: toIsoTimestamp(record?.createdAt ?? record?.created_at),
|
|
1096
|
+
startedAt: toIsoTimestamp(record?.startedAt ?? record?.started_at ?? record?.spawnedAtMs ?? record?.spawned_at_ms),
|
|
1079
1097
|
lastActivityAt: toIsoTimestamp(record?.updatedAt ?? record?.lastActivityAt ?? record?.last_activity_at),
|
|
1080
1098
|
isCached: false,
|
|
1081
1099
|
};
|
|
@@ -2130,12 +2148,14 @@ export interface CommandRouterDeps {
|
|
|
2130
2148
|
/** Reference to detected IDEs array (mutable — router updates it) */
|
|
2131
2149
|
detectedIdes: { value: any[] };
|
|
2132
2150
|
sessionRegistry: SessionRegistry;
|
|
2133
|
-
/** Callback
|
|
2151
|
+
/** Callback after CDP manager created (transport-specific extras) */
|
|
2134
2152
|
onCdpManagerCreated?: (ideType: string, manager: DaemonCdpManager) => void;
|
|
2135
2153
|
/** Callback after IDE connected (e.g., startAgentStreamPolling) */
|
|
2136
2154
|
onIdeConnected?: () => void;
|
|
2137
2155
|
/** Callback after status change (stop_ide, restart) */
|
|
2138
2156
|
onStatusChange?: () => void;
|
|
2157
|
+
/** Callback when a mesh state is invalidated */
|
|
2158
|
+
onMeshStateChange?: (meshId: string) => void;
|
|
2139
2159
|
/** Callback after chat-related commands */
|
|
2140
2160
|
onPostChatCommand?: () => void;
|
|
2141
2161
|
/** Get a connected CDP manager (for agent stream reset check) */
|
|
@@ -2456,6 +2476,16 @@ export class DaemonCommandRouter {
|
|
|
2456
2476
|
return next;
|
|
2457
2477
|
}
|
|
2458
2478
|
|
|
2479
|
+
public getCachedInlineMeshNodes(): any[] {
|
|
2480
|
+
const nodes: any[] = [];
|
|
2481
|
+
for (const mesh of this.inlineMeshCache.values()) {
|
|
2482
|
+
if (Array.isArray(mesh?.nodes)) {
|
|
2483
|
+
nodes.push(...mesh.nodes);
|
|
2484
|
+
}
|
|
2485
|
+
}
|
|
2486
|
+
return nodes;
|
|
2487
|
+
}
|
|
2488
|
+
|
|
2459
2489
|
public getCachedInlineMesh(meshId: string, inlineMesh?: unknown): any | undefined {
|
|
2460
2490
|
if (inlineMesh && typeof inlineMesh === 'object') {
|
|
2461
2491
|
return this.warmInlineMeshCache(meshId, inlineMesh);
|
|
@@ -2510,6 +2540,7 @@ export class DaemonCommandRouter {
|
|
|
2510
2540
|
|
|
2511
2541
|
private invalidateAggregateMeshStatus(meshId: string): void {
|
|
2512
2542
|
this.aggregateMeshStatusCache.delete(meshId);
|
|
2543
|
+
this.deps.onMeshStateChange?.(meshId);
|
|
2513
2544
|
}
|
|
2514
2545
|
|
|
2515
2546
|
|
|
@@ -4852,6 +4883,13 @@ export class DaemonCommandRouter {
|
|
|
4852
4883
|
...(role ? { role } : {}),
|
|
4853
4884
|
});
|
|
4854
4885
|
if (!node) return { success: false, error: 'Mesh not found' };
|
|
4886
|
+
// mesh_status hands back a coordinator-memory aggregate
|
|
4887
|
+
// snapshot keyed on (meshId, queueRevision). Adding a
|
|
4888
|
+
// node touches neither, so without an explicit cache
|
|
4889
|
+
// bust the dashboard graph keeps rendering the pre-add
|
|
4890
|
+
// node list (empty for a fresh mesh) even after the
|
|
4891
|
+
// user clicks Refresh.
|
|
4892
|
+
this.invalidateAggregateMeshStatus(meshId);
|
|
4855
4893
|
return { success: true, node };
|
|
4856
4894
|
} catch (e: any) {
|
|
4857
4895
|
return { success: false, error: e.message };
|
|
@@ -4889,6 +4927,11 @@ export class DaemonCommandRouter {
|
|
|
4889
4927
|
}
|
|
4890
4928
|
const node = updateNode(meshId, nodeId, patch as any);
|
|
4891
4929
|
if (!node) return { success: false, error: 'Mesh node not found' };
|
|
4930
|
+
// Provider priority / systemPrompt changes don't touch
|
|
4931
|
+
// the queue revision, so without a manual bust the
|
|
4932
|
+
// cached aggregate keeps surfacing pre-update values
|
|
4933
|
+
// (priority chip, coordinator prompt preview, etc.).
|
|
4934
|
+
this.invalidateAggregateMeshStatus(meshId);
|
|
4892
4935
|
return { success: true, node };
|
|
4893
4936
|
} catch (e: any) {
|
|
4894
4937
|
return { success: false, error: e.message };
|
|
@@ -5057,6 +5100,11 @@ export class DaemonCommandRouter {
|
|
|
5057
5100
|
let removed = false;
|
|
5058
5101
|
if (meshRecord?.inline) {
|
|
5059
5102
|
removed = this.removeInlineMeshNode(meshId, mesh, nodeId);
|
|
5103
|
+
// Inline meshes share the same aggregate snapshot cache as
|
|
5104
|
+
// local-config meshes; without this bust the removed node
|
|
5105
|
+
// keeps showing up in the dashboard graph until the cache
|
|
5106
|
+
// ages out on its own.
|
|
5107
|
+
if (removed) this.invalidateAggregateMeshStatus(meshId);
|
|
5060
5108
|
} else {
|
|
5061
5109
|
const { removeNode } = await import('../config/mesh-config.js');
|
|
5062
5110
|
removed = removeNode(meshId, nodeId);
|
|
@@ -5278,10 +5326,8 @@ export class DaemonCommandRouter {
|
|
|
5278
5326
|
if (ownerFailure) return ownerFailure;
|
|
5279
5327
|
try {
|
|
5280
5328
|
const { triggerMeshQueue } = await import('../mesh/mesh-events.js');
|
|
5281
|
-
|
|
5282
|
-
|
|
5283
|
-
}
|
|
5284
|
-
return { success: true };
|
|
5329
|
+
const trigger = await triggerMeshQueue(this.deps as any, meshId);
|
|
5330
|
+
return { success: true, trigger };
|
|
5285
5331
|
} catch (e: any) {
|
|
5286
5332
|
return { success: false, error: e.message };
|
|
5287
5333
|
}
|
|
@@ -5434,23 +5480,102 @@ export class DaemonCommandRouter {
|
|
|
5434
5480
|
// a real PTY the registration goes through and the
|
|
5435
5481
|
// exit code tells us whether it actually persisted.
|
|
5436
5482
|
let mcpRegistrationOk = false;
|
|
5483
|
+
let mcpRegistrationFailure: {
|
|
5484
|
+
command: string;
|
|
5485
|
+
output: string;
|
|
5486
|
+
exitCode: number | null;
|
|
5487
|
+
signal: number | null;
|
|
5488
|
+
timedOut: boolean;
|
|
5489
|
+
} | null = null;
|
|
5437
5490
|
try {
|
|
5438
|
-
const { execUnderPty } = await import('./mesh-coordinator.js');
|
|
5439
|
-
const
|
|
5440
|
-
|
|
5441
|
-
|
|
5442
|
-
|
|
5443
|
-
|
|
5444
|
-
|
|
5445
|
-
|
|
5446
|
-
|
|
5447
|
-
|
|
5448
|
-
|
|
5449
|
-
|
|
5450
|
-
|
|
5491
|
+
const { buildMeshCoordinatorRegistrationPlan, execUnderPty } = await import('./mesh-coordinator.js');
|
|
5492
|
+
const registrationPlan = buildMeshCoordinatorRegistrationPlan(
|
|
5493
|
+
cliType,
|
|
5494
|
+
coordinatorSetup.serverName,
|
|
5495
|
+
coordinatorSetup.command,
|
|
5496
|
+
);
|
|
5497
|
+
for (const step of registrationPlan) {
|
|
5498
|
+
const renderedCommand = [step.command, ...step.args].join(' ');
|
|
5499
|
+
LOG.info('MeshCoordinator', `Running MCP ${step.label} (pty): ${renderedCommand}`);
|
|
5500
|
+
const ptyResult = await execUnderPty(step.command, step.args, { cwd: workspace, timeoutMs: 20_000 });
|
|
5501
|
+
if (ptyResult.exitCode === 0 && !ptyResult.timedOut) {
|
|
5502
|
+
if (step.required) mcpRegistrationOk = true;
|
|
5503
|
+
continue;
|
|
5504
|
+
}
|
|
5505
|
+
LOG.warn('MeshCoordinator', `MCP ${step.label} failed exit=${ptyResult.exitCode} signal=${ptyResult.signal} timedOut=${ptyResult.timedOut} — output:\n${ptyResult.output.slice(-2000)}`);
|
|
5506
|
+
if (step.required) {
|
|
5507
|
+
mcpRegistrationFailure = {
|
|
5508
|
+
command: renderedCommand,
|
|
5509
|
+
output: ptyResult.output.slice(-2000),
|
|
5510
|
+
exitCode: ptyResult.exitCode,
|
|
5511
|
+
signal: ptyResult.signal,
|
|
5512
|
+
timedOut: ptyResult.timedOut,
|
|
5513
|
+
};
|
|
5514
|
+
break;
|
|
5515
|
+
}
|
|
5451
5516
|
}
|
|
5452
5517
|
} catch (error: any) {
|
|
5453
5518
|
LOG.warn('MeshCoordinator', `MCP registration command failed: ${error?.message || error}`);
|
|
5519
|
+
mcpRegistrationFailure = {
|
|
5520
|
+
command: coordinatorSetup.command,
|
|
5521
|
+
output: error?.message || String(error),
|
|
5522
|
+
exitCode: null,
|
|
5523
|
+
signal: null,
|
|
5524
|
+
timedOut: false,
|
|
5525
|
+
};
|
|
5526
|
+
}
|
|
5527
|
+
|
|
5528
|
+
if (!mcpRegistrationOk) {
|
|
5529
|
+
return {
|
|
5530
|
+
success: false,
|
|
5531
|
+
code: 'mesh_coordinator_mcp_registration_failed',
|
|
5532
|
+
error: `Could not register ${coordinatorSetup.serverName}; coordinator session was not launched`,
|
|
5533
|
+
meshId,
|
|
5534
|
+
cliType,
|
|
5535
|
+
workspace,
|
|
5536
|
+
registration: mcpRegistrationFailure,
|
|
5537
|
+
};
|
|
5538
|
+
}
|
|
5539
|
+
|
|
5540
|
+
// Codex gives repo-local .mcp.json precedence over its
|
|
5541
|
+
// global `codex mcp add` registration. Refresh an
|
|
5542
|
+
// existing ADHDev entry so a stale workspace command
|
|
5543
|
+
// cannot shadow the registration we just verified.
|
|
5544
|
+
if (cliType === 'codex-cli') {
|
|
5545
|
+
const repoMcpConfigPath = pathJoin(workspace, '.mcp.json');
|
|
5546
|
+
if (fs.existsSync(repoMcpConfigPath)) {
|
|
5547
|
+
try {
|
|
5548
|
+
const repoMcpConfig = parseMeshCoordinatorMcpConfig(
|
|
5549
|
+
fs.readFileSync(repoMcpConfigPath, 'utf-8'),
|
|
5550
|
+
'claude_mcp_json',
|
|
5551
|
+
);
|
|
5552
|
+
const existingServers = repoMcpConfig.mcpServers;
|
|
5553
|
+
if (
|
|
5554
|
+
existingServers
|
|
5555
|
+
&& typeof existingServers === 'object'
|
|
5556
|
+
&& !Array.isArray(existingServers)
|
|
5557
|
+
&& existingServers[coordinatorSetup.serverName]
|
|
5558
|
+
) {
|
|
5559
|
+
fs.writeFileSync(repoMcpConfigPath, serializeMeshCoordinatorMcpConfig({
|
|
5560
|
+
...repoMcpConfig,
|
|
5561
|
+
mcpServers: {
|
|
5562
|
+
...existingServers,
|
|
5563
|
+
[coordinatorSetup.serverName]: coordinatorSetup.mcpServer,
|
|
5564
|
+
},
|
|
5565
|
+
}, 'claude_mcp_json'), 'utf-8');
|
|
5566
|
+
LOG.info('MeshCoordinator', `Refreshed repo-local ${repoMcpConfigPath} entry for ${coordinatorSetup.serverName}`);
|
|
5567
|
+
}
|
|
5568
|
+
} catch (error: any) {
|
|
5569
|
+
return {
|
|
5570
|
+
success: false,
|
|
5571
|
+
code: 'mesh_coordinator_config_write_failed',
|
|
5572
|
+
error: `Could not refresh repo-local MCP config: ${error?.message || error}`,
|
|
5573
|
+
meshId,
|
|
5574
|
+
cliType,
|
|
5575
|
+
workspace,
|
|
5576
|
+
};
|
|
5577
|
+
}
|
|
5578
|
+
}
|
|
5454
5579
|
}
|
|
5455
5580
|
|
|
5456
5581
|
// Inject system prompt declaratively from provider.v1.json.
|
|
@@ -1350,6 +1350,7 @@ type ProviderNativeHistoryReadResult = {
|
|
|
1350
1350
|
sourcePath: string;
|
|
1351
1351
|
sourceMtimeMs: number;
|
|
1352
1352
|
providerSessionId?: string;
|
|
1353
|
+
workspace?: string;
|
|
1353
1354
|
nativeHistoryCoverage?: string;
|
|
1354
1355
|
partialReason?: string;
|
|
1355
1356
|
unavailableReason?: string;
|
|
@@ -1449,6 +1450,7 @@ function callProviderNativeHistoryRead(
|
|
|
1449
1450
|
sourcePath: typeof (result as any).sourcePath === 'string' ? (result as any).sourcePath : '',
|
|
1450
1451
|
sourceMtimeMs: Number((result as any).sourceMtimeMs) || 0,
|
|
1451
1452
|
providerSessionId: typeof (result as any).providerSessionId === 'string' ? (result as any).providerSessionId.trim() : undefined,
|
|
1453
|
+
workspace: typeof (result as any).workspace === 'string' ? (result as any).workspace.trim() : undefined,
|
|
1452
1454
|
nativeHistoryCoverage: typeof (result as any).nativeHistoryCoverage === 'string' ? (result as any).nativeHistoryCoverage.trim() : undefined,
|
|
1453
1455
|
partialReason: typeof (result as any).partialReason === 'string' ? (result as any).partialReason.trim() : undefined,
|
|
1454
1456
|
unavailableReason: typeof (result as any).unavailableReason === 'string' ? (result as any).unavailableReason.trim() : undefined,
|
|
@@ -1537,6 +1539,7 @@ export function readProviderChatHistory(
|
|
|
1537
1539
|
sourcePath?: string;
|
|
1538
1540
|
sourceMtimeMs?: number;
|
|
1539
1541
|
providerSessionId?: string;
|
|
1542
|
+
workspace?: string;
|
|
1540
1543
|
nativeHistoryCoverage?: string;
|
|
1541
1544
|
partialReason?: string;
|
|
1542
1545
|
unavailableReason?: string;
|
|
@@ -1550,6 +1553,7 @@ export function readProviderChatHistory(
|
|
|
1550
1553
|
sourcePath: nativeResult.sourcePath,
|
|
1551
1554
|
sourceMtimeMs: nativeResult.sourceMtimeMs,
|
|
1552
1555
|
providerSessionId: nativeResult.providerSessionId,
|
|
1556
|
+
workspace: nativeResult.workspace,
|
|
1553
1557
|
nativeHistoryCoverage: nativeResult.nativeHistoryCoverage,
|
|
1554
1558
|
partialReason: nativeResult.partialReason,
|
|
1555
1559
|
unavailableReason: nativeResult.unavailableReason,
|
package/src/git/git-commands.ts
CHANGED
|
@@ -66,7 +66,7 @@ export interface GitPushResult extends GitRepoIdentity {
|
|
|
66
66
|
}
|
|
67
67
|
|
|
68
68
|
export interface GitCommandServices {
|
|
69
|
-
getStatus?: (params: { workspace: string; refreshUpstream?: boolean }) => Promise<GitRepoStatus> | GitRepoStatus;
|
|
69
|
+
getStatus?: (params: { workspace: string; refreshUpstream?: boolean; includeSubmodules?: boolean; submoduleIgnorePaths?: string[] }) => Promise<GitRepoStatus> | GitRepoStatus;
|
|
70
70
|
getDiffSummary?: (params: { workspace: string; staged?: boolean }) => Promise<GitDiffSummary> | GitDiffSummary;
|
|
71
71
|
getDiffFile?: (params: { workspace: string; path: string; staged?: boolean }) => Promise<GitFileDiff> | GitFileDiff;
|
|
72
72
|
createSnapshot?: (params: {
|
|
@@ -294,7 +294,16 @@ export async function handleGitCommand(
|
|
|
294
294
|
switch (command) {
|
|
295
295
|
case 'git_status': {
|
|
296
296
|
if (!services.getStatus) return serviceNotImplemented(command);
|
|
297
|
-
const
|
|
297
|
+
const submoduleIgnorePaths = Array.isArray(args?.submoduleIgnorePaths)
|
|
298
|
+
? args.submoduleIgnorePaths.filter((value: unknown): value is string => typeof value === 'string' && value.trim().length > 0)
|
|
299
|
+
: undefined;
|
|
300
|
+
const statusParams: { workspace: string; refreshUpstream?: boolean; includeSubmodules?: boolean; submoduleIgnorePaths?: string[] } = { workspace };
|
|
301
|
+
const refreshUpstream = optionalBoolean(args?.refreshUpstream);
|
|
302
|
+
const includeSubmodules = optionalBoolean(args?.includeSubmodules);
|
|
303
|
+
if (refreshUpstream !== undefined) statusParams.refreshUpstream = refreshUpstream;
|
|
304
|
+
if (includeSubmodules !== undefined) statusParams.includeSubmodules = includeSubmodules;
|
|
305
|
+
if (submoduleIgnorePaths && submoduleIgnorePaths.length > 0) statusParams.submoduleIgnorePaths = submoduleIgnorePaths;
|
|
306
|
+
const status = await runService(() => services.getStatus!(statusParams));
|
|
298
307
|
return 'success' in status ? status : { success: true, status };
|
|
299
308
|
}
|
|
300
309
|
|
|
@@ -446,6 +455,15 @@ async function gitCheckpoint(
|
|
|
446
455
|
if (statusResult.hasConflicts) {
|
|
447
456
|
throw new GitCommandError('conflict', 'Repository has conflicts — resolve before checkpointing');
|
|
448
457
|
}
|
|
458
|
+
const dirtySubmodules = (statusResult.submodules || []).filter(submodule => submodule.dirty);
|
|
459
|
+
if (dirtySubmodules.length > 0) {
|
|
460
|
+
const paths = dirtySubmodules.map(submodule => submodule.path).join(', ');
|
|
461
|
+
throw new GitCommandError(
|
|
462
|
+
'dirty_index_required',
|
|
463
|
+
`Repository has dirty submodules that must be checkpointed first: ${paths}. ` +
|
|
464
|
+
'Checkpoint or commit each dirty submodule, then checkpoint this repository to record gitlink changes.',
|
|
465
|
+
);
|
|
466
|
+
}
|
|
449
467
|
|
|
450
468
|
const addArgs = includeUntracked ? ['-A'] : ['-u'];
|
|
451
469
|
await runGit(repo, ['add', ...addArgs], { cwd: repoRoot });
|
package/src/git/git-status.ts
CHANGED
|
@@ -48,6 +48,11 @@ export async function getGitRepoStatus(
|
|
|
48
48
|
if (includeSubmodules) {
|
|
49
49
|
submodules = await getSubmoduleStatuses(repo, options);
|
|
50
50
|
}
|
|
51
|
+
const submoduleDirty = (submodules || []).some(submodule => submodule.dirty || submodule.outOfSync || !!submodule.error);
|
|
52
|
+
const dirty = parsed.staged + parsed.modified + parsed.untracked + parsed.deleted + parsed.renamed > 0
|
|
53
|
+
|| parsed.conflictFiles.length > 0
|
|
54
|
+
|| stashCount > 0
|
|
55
|
+
|| submoduleDirty;
|
|
51
56
|
|
|
52
57
|
return {
|
|
53
58
|
workspace: repo.workspace,
|
|
@@ -67,6 +72,7 @@ export async function getGitRepoStatus(
|
|
|
67
72
|
untracked: parsed.untracked,
|
|
68
73
|
deleted: parsed.deleted,
|
|
69
74
|
renamed: parsed.renamed,
|
|
75
|
+
dirty,
|
|
70
76
|
hasConflicts: parsed.conflictFiles.length > 0,
|
|
71
77
|
conflictFiles: parsed.conflictFiles,
|
|
72
78
|
stashCount,
|
|
@@ -285,6 +291,7 @@ function emptyStatus(workspace: string, lastCheckedAt: number, error: GitCommand
|
|
|
285
291
|
untracked: 0,
|
|
286
292
|
deleted: 0,
|
|
287
293
|
renamed: 0,
|
|
294
|
+
dirty: false,
|
|
288
295
|
hasConflicts: false,
|
|
289
296
|
conflictFiles: [],
|
|
290
297
|
stashCount: 0,
|
|
@@ -304,12 +311,34 @@ async function getSubmoduleStatuses(
|
|
|
304
311
|
|
|
305
312
|
try {
|
|
306
313
|
const result = await runGit(repo, ['submodule', 'status', '--recursive'], options);
|
|
307
|
-
|
|
314
|
+
const submodules = parseSubmoduleStatusOutput(result.stdout, repo.repoRoot, options.submoduleIgnorePaths);
|
|
315
|
+
await Promise.all(submodules.map(submodule => enrichSubmoduleWorktreeStatus(repo, submodule, options)));
|
|
316
|
+
return submodules;
|
|
308
317
|
} catch {
|
|
309
318
|
return [];
|
|
310
319
|
}
|
|
311
320
|
}
|
|
312
321
|
|
|
322
|
+
async function enrichSubmoduleWorktreeStatus(
|
|
323
|
+
repo: ResolvedGitRepo,
|
|
324
|
+
submodule: GitSubmoduleStatus,
|
|
325
|
+
options: GitStatusOptions,
|
|
326
|
+
): Promise<void> {
|
|
327
|
+
try {
|
|
328
|
+
const result = await runGit(repo, ['status', '--porcelain=v2', '--branch'], {
|
|
329
|
+
...options,
|
|
330
|
+
cwd: submodule.repoPath,
|
|
331
|
+
});
|
|
332
|
+
const parsed = parsePorcelainV2Status(result.stdout);
|
|
333
|
+
const dirty = parsed.staged + parsed.modified + parsed.untracked + parsed.deleted + parsed.renamed > 0
|
|
334
|
+
|| parsed.conflictFiles.length > 0;
|
|
335
|
+
submodule.dirty = submodule.dirty || dirty;
|
|
336
|
+
} catch (error) {
|
|
337
|
+
submodule.dirty = true;
|
|
338
|
+
submodule.error = formatGitError(error);
|
|
339
|
+
}
|
|
340
|
+
}
|
|
341
|
+
|
|
313
342
|
function parseSubmoduleStatusOutput(
|
|
314
343
|
output: string,
|
|
315
344
|
repoRoot: string,
|
|
@@ -321,9 +350,9 @@ function parseSubmoduleStatusOutput(
|
|
|
321
350
|
for (const line of output.split('\n')) {
|
|
322
351
|
if (!line.trim()) continue;
|
|
323
352
|
|
|
324
|
-
// Format: [+- ]<commit> <path> (<branch>)
|
|
325
|
-
// - = out of sync,
|
|
326
|
-
const match = line.match(/^([
|
|
353
|
+
// Format: [+-U ]<commit> <path> (<branch>)
|
|
354
|
+
// - = not initialized, + = gitlink out of sync, U = conflict, ' ' = aligned.
|
|
355
|
+
const match = line.match(/^([\-+U\s])([0-9a-f]{40})\s+(\S+)(?:\s+\(([^)]+)\))?/);
|
|
327
356
|
if (!match) continue;
|
|
328
357
|
|
|
329
358
|
const prefix = match[1];
|
|
@@ -336,8 +365,8 @@ function parseSubmoduleStatusOutput(
|
|
|
336
365
|
path,
|
|
337
366
|
commit,
|
|
338
367
|
repoPath: repoRoot + '/' + path,
|
|
339
|
-
dirty: prefix === '
|
|
340
|
-
outOfSync: prefix === '-',
|
|
368
|
+
dirty: prefix === 'U',
|
|
369
|
+
outOfSync: prefix === '-' || prefix === '+',
|
|
341
370
|
lastCheckedAt: Date.now(),
|
|
342
371
|
});
|
|
343
372
|
}
|
package/src/git/git-types.ts
CHANGED
|
@@ -60,6 +60,8 @@ export interface GitRepoStatus extends GitRepoIdentity {
|
|
|
60
60
|
untracked: number;
|
|
61
61
|
deleted: number;
|
|
62
62
|
renamed: number;
|
|
63
|
+
/** Aggregate dirty flag including root worktree changes, conflicts, stash, and submodule drift. */
|
|
64
|
+
dirty: boolean;
|
|
63
65
|
hasConflicts: boolean;
|
|
64
66
|
conflictFiles: string[];
|
|
65
67
|
stashCount: number;
|
package/src/index.ts
CHANGED
|
@@ -229,7 +229,7 @@ export { buildMeshHostRequiredFailure, createDefaultMeshHostMetadata, isMeshHost
|
|
|
229
229
|
// export type { MeshGraph, MeshGraphNode, MeshGraphEdge, MeshGraphNodeType, MeshGraphEdgeType } from './mesh/mesh-visualization.js';
|
|
230
230
|
|
|
231
231
|
// ── Mesh Events ──
|
|
232
|
-
export { triggerMeshQueue, drainPendingMeshCoordinatorEvents, getPendingMeshCoordinatorEvents, clearPendingMeshCoordinatorEvents, queuePendingMeshCoordinatorEvent } from './mesh/mesh-events.js';
|
|
232
|
+
export { triggerMeshQueue, drainPendingMeshCoordinatorEvents, getPendingMeshCoordinatorEvents, clearPendingMeshCoordinatorEvents, queuePendingMeshCoordinatorEvent, reconcileDirectDispatchCompletionFromTranscript } from './mesh/mesh-events.js';
|
|
233
233
|
export type { PendingMeshCoordinatorEvent } from './mesh/mesh-events.js';
|
|
234
234
|
|
|
235
235
|
// ── Mesh P2P Relay Failure Classification ──
|
|
@@ -275,7 +275,7 @@ export type { CdpInitializerConfig } from './cdp/initializer.js';
|
|
|
275
275
|
// ── Commands ──
|
|
276
276
|
export { DaemonCommandHandler } from './commands/handler.js';
|
|
277
277
|
export type { CommandResult, CommandContext } from './commands/handler.js';
|
|
278
|
-
export { DaemonCommandRouter } from './commands/router.js';
|
|
278
|
+
export { DaemonCommandRouter, readCachedInlineMeshActiveSessionDetails } from './commands/router.js';
|
|
279
279
|
export type { CommandRouterDeps, CommandRouterResult } from './commands/router.js';
|
|
280
280
|
export {
|
|
281
281
|
maybeRunDaemonUpgradeHelperFromEnv,
|
package/src/mesh/beads-db.ts
CHANGED
|
@@ -474,6 +474,10 @@ export class BeadsDB {
|
|
|
474
474
|
`).run(cutoff);
|
|
475
475
|
}
|
|
476
476
|
|
|
477
|
+
deleteDirectDispatches(meshId: string): void {
|
|
478
|
+
this.db.prepare(`DELETE FROM mesh_direct_dispatches WHERE mesh_id = ?`).run(meshId);
|
|
479
|
+
}
|
|
480
|
+
|
|
477
481
|
markStaleDirectDispatches(meshId: string, olderThanMs: number): void {
|
|
478
482
|
const cutoff = new Date(Date.now() - olderThanMs).toISOString();
|
|
479
483
|
const now = new Date().toISOString();
|