@adhdev/daemon-core 0.9.67 → 0.9.69

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.
@@ -260,6 +260,31 @@ export interface CdpTargetFilter {
260
260
  titleExcludes?: string;
261
261
  }
262
262
  export type ProviderVersionCommand = string | Partial<Record<string, string>>;
263
+ export type MeshCoordinatorMcpConfigMode = 'auto_import' | 'manual' | 'none';
264
+ export type MeshCoordinatorMcpConfigFormat = 'claude_mcp_json' | 'hermes_config_yaml';
265
+ export interface ProviderMeshCoordinatorConfig {
266
+ /** Whether ADHDev may select this provider for Repo Mesh coordinator sessions. */
267
+ supported: boolean;
268
+ /** Human-readable reason shown when unsupported or blocked. */
269
+ reason?: string;
270
+ /** How ADHDev mesh MCP tools become visible to the launched CLI. */
271
+ mcpConfig?: {
272
+ mode: MeshCoordinatorMcpConfigMode;
273
+ format?: MeshCoordinatorMcpConfigFormat;
274
+ /** Provider-relative/project-relative config path for auto-import modes, e.g. '.mcp.json'. */
275
+ path?: string;
276
+ /** MCP server name to materialize or display. Defaults to 'adhdev-mesh'. */
277
+ serverName?: string;
278
+ /** Manual setup target path/help command, e.g. 'hermes config path'. */
279
+ configPathCommand?: string;
280
+ /** Whether users need a fresh CLI session after config changes. */
281
+ requiresRestart?: boolean;
282
+ /** User-facing setup explanation for manual modes. */
283
+ instructions?: string;
284
+ /** Copyable setup template. Supports {{meshId}}, {{adhdevMcpCommand}}, {{workspace}}, {{serverName}}. */
285
+ template?: string;
286
+ };
287
+ }
263
288
  export interface ProviderCompatibilityEntry {
264
289
  ideVersion: string;
265
290
  scriptDir: string;
@@ -271,6 +296,10 @@ export interface ProviderModule {
271
296
  name: string;
272
297
  /** Category: determines execution method */
273
298
  category: ProviderCategory;
299
+ /** When provider-owned, daemon treats provider parser output as canonical transcript authority. */
300
+ transcriptAuthority?: 'provider' | 'daemon';
301
+ /** Full context lets provider-owned parsers canonicalize retained history instead of daemon prefix stitching. */
302
+ transcriptContext?: 'full' | 'tail';
274
303
  /** Alias list — allows users to invoke by alternate names (e.g. ['claude', 'claude-code']) */
275
304
  aliases?: string[];
276
305
  /** CDP ports [primary, secondary] (IDE category only) */
@@ -448,6 +477,11 @@ export interface ProviderModule {
448
477
  spawnArgBuilder?: (config: Record<string, string>) => string[];
449
478
  /** ACP agent auth methods (multiple supported — in priority order) */
450
479
  auth?: AcpAuthMethod[];
480
+ /**
481
+ * Repo Mesh coordinator capability and MCP ingestion behavior.
482
+ * Providers must declare this rather than relying on daemon hardcoded CLI quirks.
483
+ */
484
+ meshCoordinator?: ProviderMeshCoordinatorConfig;
451
485
  contractVersion?: number;
452
486
  capabilities?: {
453
487
  input?: {
@@ -12,7 +12,7 @@ export type { ProviderState, ProviderStatus, ActiveChatData, IdeProviderState, C
12
12
  export type { ProviderErrorReason } from './providers/provider-instance.js';
13
13
  import type { ActiveChatData as _ActiveChatData, ProviderErrorReason as _ProviderErrorReason } from './providers/provider-instance.js';
14
14
  import type { WorkspaceEntry } from './config/workspaces.js';
15
- import type { ProviderResumeCapability } from './providers/contracts.js';
15
+ import type { ProviderMeshCoordinatorConfig, ProviderResumeCapability } from './providers/contracts.js';
16
16
  import type { GitCompactSummary, GitWorkspaceUpdate, WorkspaceGitSubscriptionParams } from './git/git-types.js';
17
17
  export type { GitCommandName, GitCompactSummary, GitDiffSummary, GitFailureReason, GitFileChange, GitFileChangeStatus, GitRepoIdentity, GitRepoStatus, GitSnapshot, GitSnapshotCompareSummary, GitSnapshotReason, GitWorkspaceUpdate, WorkspaceGitSubscriptionParams, } from './git/git-types.js';
18
18
  export interface SessionActiveChatData extends Omit<_ActiveChatData, 'messages'> {
@@ -343,6 +343,8 @@ export interface AvailableProviderInfo {
343
343
  lastDetection?: MachineProviderCheckResult;
344
344
  /** Last end-to-end ADHDev verification result, when available. */
345
345
  lastVerification?: MachineProviderCheckResult;
346
+ /** Provider-declared Repo Mesh coordinator/MCP behavior. */
347
+ meshCoordinator?: ProviderMeshCoordinatorConfig;
346
348
  }
347
349
  export interface MachineProviderCheckResult {
348
350
  ok: boolean;
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@adhdev/session-host-core",
3
- "version": "0.9.67",
3
+ "version": "0.9.69",
4
4
  "description": "ADHDev local session host core \u2014 session registry, protocol, buffers",
5
5
  "main": "dist/index.js",
6
6
  "types": "dist/index.d.ts",
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@adhdev/daemon-core",
3
- "version": "0.9.67",
3
+ "version": "0.9.69",
4
4
  "description": "ADHDev daemon core \u2014 CDP, IDE detection, providers, command execution",
5
5
  "main": "dist/index.js",
6
6
  "types": "dist/index.d.ts",
@@ -0,0 +1,97 @@
1
+ import { join } from 'path'
2
+ import type { ProviderModule, MeshCoordinatorMcpConfigFormat } from '../providers/contracts.js'
3
+
4
+ export type MeshCoordinatorSetup =
5
+ | {
6
+ kind: 'auto_import'
7
+ serverName: string
8
+ configPath: string
9
+ configFormat?: MeshCoordinatorMcpConfigFormat
10
+ }
11
+ | {
12
+ kind: 'manual'
13
+ serverName: string
14
+ configFormat?: MeshCoordinatorMcpConfigFormat
15
+ configPathCommand?: string
16
+ requiresRestart: boolean
17
+ instructions: string
18
+ template: string
19
+ }
20
+ | {
21
+ kind: 'unsupported'
22
+ reason: string
23
+ }
24
+
25
+ export interface ResolveMeshCoordinatorSetupOptions {
26
+ provider?: ProviderModule | null
27
+ meshId: string
28
+ workspace: string
29
+ adhdevMcpCommand?: string
30
+ }
31
+
32
+ const DEFAULT_SERVER_NAME = 'adhdev-mesh'
33
+ const DEFAULT_ADHDEV_MCP_COMMAND = 'adhdev-mcp'
34
+
35
+ export function resolveMeshCoordinatorSetup(options: ResolveMeshCoordinatorSetupOptions): MeshCoordinatorSetup {
36
+ const { provider, meshId, workspace } = options
37
+ const config = provider?.meshCoordinator
38
+ if (!config?.supported) {
39
+ return {
40
+ kind: 'unsupported',
41
+ reason: config?.reason || 'Provider does not declare Repo Mesh coordinator support',
42
+ }
43
+ }
44
+
45
+ const mcpConfig = config.mcpConfig
46
+ if (!mcpConfig || mcpConfig.mode === 'none') {
47
+ return {
48
+ kind: 'unsupported',
49
+ reason: config.reason || 'Provider does not declare a usable Repo Mesh MCP configuration mode',
50
+ }
51
+ }
52
+
53
+ const serverName = mcpConfig.serverName?.trim() || DEFAULT_SERVER_NAME
54
+ if (mcpConfig.mode === 'auto_import') {
55
+ const path = mcpConfig.path?.trim()
56
+ if (!path) {
57
+ return { kind: 'unsupported', reason: 'Provider auto-import MCP config is missing a config path' }
58
+ }
59
+ return {
60
+ kind: 'auto_import',
61
+ serverName,
62
+ configPath: join(workspace, path),
63
+ configFormat: mcpConfig.format,
64
+ }
65
+ }
66
+
67
+ if (mcpConfig.mode === 'manual') {
68
+ const instructions = mcpConfig.instructions?.trim()
69
+ const template = mcpConfig.template
70
+ if (!instructions || !template?.trim()) {
71
+ return { kind: 'unsupported', reason: 'Provider manual MCP setup is missing instructions or template' }
72
+ }
73
+ return {
74
+ kind: 'manual',
75
+ serverName,
76
+ configFormat: mcpConfig.format,
77
+ configPathCommand: mcpConfig.configPathCommand,
78
+ requiresRestart: mcpConfig.requiresRestart === true,
79
+ instructions,
80
+ template: renderMeshCoordinatorTemplate(template, {
81
+ meshId,
82
+ workspace,
83
+ serverName,
84
+ adhdevMcpCommand: options.adhdevMcpCommand || DEFAULT_ADHDEV_MCP_COMMAND,
85
+ }),
86
+ }
87
+ }
88
+
89
+ return {
90
+ kind: 'unsupported',
91
+ reason: `Unsupported Repo Mesh MCP configuration mode: ${String(mcpConfig.mode)}`,
92
+ }
93
+ }
94
+
95
+ function renderMeshCoordinatorTemplate(template: string, values: Record<string, string>): string {
96
+ return template.replace(/\{\{\s*(meshId|workspace|serverName|adhdevMcpCommand)\s*\}\}/g, (_, key: string) => values[key] || '')
97
+ }
@@ -33,6 +33,7 @@ import type { CommandLogEntry } from '../logging/command-log.js';
33
33
  import { getRecentLogs, LOG_PATH } from '../logging/logger.js';
34
34
  import { createInteractionId, getRecentDebugTrace, recordDebugTrace } from '../logging/debug-trace.js';
35
35
  import { getSessionHostSurfaceKind, partitionSessionHostRecords } from '../session-host/runtime-surface.js';
36
+ import { resolveMeshCoordinatorSetup } from './mesh-coordinator.js';
36
37
  import { buildSessionEntries } from '../status/builders.js';
37
38
  import { buildMachineInfo, buildStatusSnapshot } from '../status/snapshot.js';
38
39
  import { getSessionCompletionMarker } from '../status/snapshot.js';
@@ -920,6 +921,201 @@ export class DaemonCommandRouter {
920
921
  return { success: true };
921
922
  }
922
923
 
924
+ // ─── Mesh CRUD (local meshes.json) ───
925
+ case 'list_meshes': {
926
+ try {
927
+ const { listMeshes } = await import('../config/mesh-config.js');
928
+ return { success: true, meshes: listMeshes() };
929
+ } catch (e: any) {
930
+ return { success: false, error: e.message };
931
+ }
932
+ }
933
+
934
+ case 'get_mesh': {
935
+ const meshId = typeof args?.meshId === 'string' ? args.meshId.trim() : '';
936
+ if (!meshId) return { success: false, error: 'meshId required' };
937
+ try {
938
+ const { getMesh } = await import('../config/mesh-config.js');
939
+ const mesh = getMesh(meshId);
940
+ if (!mesh) return { success: false, error: 'Mesh not found' };
941
+ return { success: true, mesh };
942
+ } catch (e: any) {
943
+ return { success: false, error: e.message };
944
+ }
945
+ }
946
+
947
+ case 'create_mesh': {
948
+ const name = typeof args?.name === 'string' ? args.name.trim() : '';
949
+ const repoIdentity = typeof args?.repoIdentity === 'string' ? args.repoIdentity.trim() : '';
950
+ const repoRemoteUrl = typeof args?.repoRemoteUrl === 'string' ? args.repoRemoteUrl.trim() : undefined;
951
+ const defaultBranch = typeof args?.defaultBranch === 'string' ? args.defaultBranch.trim() : undefined;
952
+ if (!name) return { success: false, error: 'name required' };
953
+ try {
954
+ const { createMesh } = await import('../config/mesh-config.js');
955
+ const mesh = createMesh({ name, repoIdentity, repoRemoteUrl, defaultBranch });
956
+ return { success: true, mesh };
957
+ } catch (e: any) {
958
+ return { success: false, error: e.message };
959
+ }
960
+ }
961
+
962
+ case 'delete_mesh': {
963
+ const meshId = typeof args?.meshId === 'string' ? args.meshId.trim() : '';
964
+ if (!meshId) return { success: false, error: 'meshId required' };
965
+ try {
966
+ const { deleteMesh } = await import('../config/mesh-config.js');
967
+ const deleted = deleteMesh(meshId);
968
+ return { success: true, deleted };
969
+ } catch (e: any) {
970
+ return { success: false, error: e.message };
971
+ }
972
+ }
973
+
974
+ case 'add_mesh_node': {
975
+ const meshId = typeof args?.meshId === 'string' ? args.meshId.trim() : '';
976
+ const workspace = typeof args?.workspace === 'string' ? args.workspace.trim() : '';
977
+ if (!meshId) return { success: false, error: 'meshId required' };
978
+ if (!workspace) return { success: false, error: 'workspace required' };
979
+ try {
980
+ const { addNode } = await import('../config/mesh-config.js');
981
+ const node = addNode(meshId, { workspace });
982
+ if (!node) return { success: false, error: 'Mesh not found' };
983
+ return { success: true, node };
984
+ } catch (e: any) {
985
+ return { success: false, error: e.message };
986
+ }
987
+ }
988
+
989
+ case 'remove_mesh_node': {
990
+ const meshId = typeof args?.meshId === 'string' ? args.meshId.trim() : '';
991
+ const nodeId = typeof args?.nodeId === 'string' ? args.nodeId.trim() : '';
992
+ if (!meshId || !nodeId) return { success: false, error: 'meshId and nodeId required' };
993
+ try {
994
+ const { removeNode } = await import('../config/mesh-config.js');
995
+ const removed = removeNode(meshId, nodeId);
996
+ return { success: true, removed };
997
+ } catch (e: any) {
998
+ return { success: false, error: e.message };
999
+ }
1000
+ }
1001
+
1002
+ // ─── Mesh Coordinator Launch ───
1003
+ case 'launch_mesh_coordinator': {
1004
+ const meshId = typeof args?.meshId === 'string' ? args.meshId.trim() : '';
1005
+ const cliType = typeof args?.cliType === 'string' ? args.cliType.trim() : 'claude-cli';
1006
+ if (!meshId) return { success: false, error: 'meshId required' };
1007
+
1008
+ try {
1009
+ const { getMesh } = await import('../config/mesh-config.js');
1010
+ const { buildCoordinatorSystemPrompt } = await import('../mesh/coordinator-prompt.js');
1011
+ const mesh = getMesh(meshId);
1012
+ if (!mesh) return { success: false, error: 'Mesh not found' };
1013
+ if (mesh.nodes.length === 0) return { success: false, error: 'No nodes in mesh' };
1014
+
1015
+ const workspace = mesh.nodes[0].workspace;
1016
+ const providerMeta = this.deps.providerLoader.resolve?.(cliType) || this.deps.providerLoader.getMeta(cliType);
1017
+ const coordinatorSetup = resolveMeshCoordinatorSetup({
1018
+ provider: providerMeta,
1019
+ meshId,
1020
+ workspace,
1021
+ });
1022
+
1023
+ if (coordinatorSetup.kind === 'unsupported') {
1024
+ return {
1025
+ success: false,
1026
+ code: 'mesh_coordinator_unsupported',
1027
+ error: coordinatorSetup.reason,
1028
+ meshId,
1029
+ cliType,
1030
+ workspace,
1031
+ };
1032
+ }
1033
+
1034
+ if (coordinatorSetup.kind === 'manual') {
1035
+ return {
1036
+ success: false,
1037
+ code: 'mesh_coordinator_manual_mcp_setup_required',
1038
+ error: coordinatorSetup.instructions,
1039
+ meshId,
1040
+ cliType,
1041
+ workspace,
1042
+ meshCoordinatorSetup: coordinatorSetup,
1043
+ };
1044
+ }
1045
+
1046
+ if (coordinatorSetup.configFormat !== 'claude_mcp_json') {
1047
+ return {
1048
+ success: false,
1049
+ code: 'mesh_coordinator_unsupported',
1050
+ error: `Unsupported auto-import MCP config format: ${String(coordinatorSetup.configFormat)}`,
1051
+ meshId,
1052
+ cliType,
1053
+ workspace,
1054
+ };
1055
+ }
1056
+
1057
+ // 1. Write provider-declared MCP config to workspace for CLIs that auto-import it.
1058
+ const { existsSync, readFileSync, writeFileSync, copyFileSync } = await import('fs');
1059
+ const mcpConfigPath = coordinatorSetup.configPath;
1060
+
1061
+ // Backup existing MCP config if present.
1062
+ const hadExistingMcpConfig = existsSync(mcpConfigPath);
1063
+ let existingMcpConfig: any = {};
1064
+ if (hadExistingMcpConfig) {
1065
+ try {
1066
+ existingMcpConfig = JSON.parse(readFileSync(mcpConfigPath, 'utf-8'));
1067
+ copyFileSync(mcpConfigPath, mcpConfigPath + '.backup');
1068
+ } catch { /* empty */ }
1069
+ }
1070
+
1071
+ // Merge ADHDev mesh server into existing config.
1072
+ const mcpConfig = {
1073
+ ...existingMcpConfig,
1074
+ mcpServers: {
1075
+ ...(existingMcpConfig.mcpServers || {}),
1076
+ [coordinatorSetup.serverName]: {
1077
+ command: 'adhdev-mcp',
1078
+ args: ['--repo-mesh', meshId],
1079
+ },
1080
+ },
1081
+ };
1082
+ writeFileSync(mcpConfigPath, JSON.stringify(mcpConfig, null, 2), 'utf-8');
1083
+ LOG.info('MeshCoordinator', `Wrote ${mcpConfigPath} with ${coordinatorSetup.serverName} server`);
1084
+
1085
+ // 2. Build coordinator system prompt
1086
+ let systemPrompt = '';
1087
+ try {
1088
+ systemPrompt = buildCoordinatorSystemPrompt({ mesh });
1089
+ } catch {
1090
+ systemPrompt = `You are a Repo Mesh Coordinator for "${mesh.name}". Use the adhdev-mesh MCP tools (mesh_status, mesh_list_nodes, mesh_send_task, mesh_read_chat, mesh_launch_session, etc.) to orchestrate work across ${mesh.nodes.length} node(s).`;
1091
+ }
1092
+
1093
+ // 3. Launch CLI session via existing cliManager
1094
+ const launchResult: any = await this.deps.cliManager.handleCliCommand('launch_cli', {
1095
+ cliType,
1096
+ dir: workspace,
1097
+ initialPrompt: systemPrompt,
1098
+ });
1099
+
1100
+ if (!launchResult?.success) {
1101
+ return { success: false, error: launchResult?.error || 'Failed to launch CLI session' };
1102
+ }
1103
+
1104
+ LOG.info('MeshCoordinator', `Launched ${cliType} coordinator for mesh ${meshId} in ${workspace}`);
1105
+ return {
1106
+ success: true,
1107
+ meshId,
1108
+ cliType,
1109
+ workspace,
1110
+ sessionId: launchResult.sessionId || launchResult.id,
1111
+ mcpConfigWritten: true,
1112
+ };
1113
+ } catch (e: any) {
1114
+ LOG.error('MeshCoordinator', `Failed: ${e.message}`);
1115
+ return { success: false, error: e.message };
1116
+ }
1117
+ }
1118
+
923
1119
  default:
924
1120
  break;
925
1121
  }
@@ -345,6 +345,33 @@ export interface CdpTargetFilter {
345
345
 
346
346
  export type ProviderVersionCommand = string | Partial<Record<string, string>>;
347
347
 
348
+ export type MeshCoordinatorMcpConfigMode = 'auto_import' | 'manual' | 'none';
349
+ export type MeshCoordinatorMcpConfigFormat = 'claude_mcp_json' | 'hermes_config_yaml';
350
+
351
+ export interface ProviderMeshCoordinatorConfig {
352
+ /** Whether ADHDev may select this provider for Repo Mesh coordinator sessions. */
353
+ supported: boolean;
354
+ /** Human-readable reason shown when unsupported or blocked. */
355
+ reason?: string;
356
+ /** How ADHDev mesh MCP tools become visible to the launched CLI. */
357
+ mcpConfig?: {
358
+ mode: MeshCoordinatorMcpConfigMode;
359
+ format?: MeshCoordinatorMcpConfigFormat;
360
+ /** Provider-relative/project-relative config path for auto-import modes, e.g. '.mcp.json'. */
361
+ path?: string;
362
+ /** MCP server name to materialize or display. Defaults to 'adhdev-mesh'. */
363
+ serverName?: string;
364
+ /** Manual setup target path/help command, e.g. 'hermes config path'. */
365
+ configPathCommand?: string;
366
+ /** Whether users need a fresh CLI session after config changes. */
367
+ requiresRestart?: boolean;
368
+ /** User-facing setup explanation for manual modes. */
369
+ instructions?: string;
370
+ /** Copyable setup template. Supports {{meshId}}, {{adhdevMcpCommand}}, {{workspace}}, {{serverName}}. */
371
+ template?: string;
372
+ };
373
+ }
374
+
348
375
  export interface ProviderCompatibilityEntry {
349
376
  ideVersion: string;
350
377
  scriptDir: string;
@@ -357,6 +384,10 @@ export interface ProviderModule {
357
384
  name: string;
358
385
  /** Category: determines execution method */
359
386
  category: ProviderCategory;
387
+ /** When provider-owned, daemon treats provider parser output as canonical transcript authority. */
388
+ transcriptAuthority?: 'provider' | 'daemon';
389
+ /** Full context lets provider-owned parsers canonicalize retained history instead of daemon prefix stitching. */
390
+ transcriptContext?: 'full' | 'tail';
360
391
  /** Alias list — allows users to invoke by alternate names (e.g. ['claude', 'claude-code']) */
361
392
  aliases?: string[];
362
393
 
@@ -555,6 +586,12 @@ export interface ProviderModule {
555
586
  /** ACP agent auth methods (multiple supported — in priority order) */
556
587
  auth?: AcpAuthMethod[];
557
588
 
589
+ /**
590
+ * Repo Mesh coordinator capability and MCP ingestion behavior.
591
+ * Providers must declare this rather than relying on daemon hardcoded CLI quirks.
592
+ */
593
+ meshCoordinator?: ProviderMeshCoordinatorConfig;
594
+
558
595
  // ─── Contract version / capability declaration ───
559
596
  contractVersion?: number;
560
597
  capabilities?: {
@@ -7,6 +7,8 @@ const KNOWN_PROVIDER_FIELDS = new Set<string>([
7
7
  'type',
8
8
  'name',
9
9
  'category',
10
+ 'transcriptAuthority',
11
+ 'transcriptContext',
10
12
  'aliases',
11
13
  'cdpPorts',
12
14
  'targetFilter',
@@ -51,6 +53,7 @@ const KNOWN_PROVIDER_FIELDS = new Set<string>([
51
53
  'staticConfigOptions',
52
54
  'spawnArgBuilder',
53
55
  'auth',
56
+ 'meshCoordinator',
54
57
  'contractVersion',
55
58
  'capabilities',
56
59
  'providerVersion',
@@ -125,6 +128,7 @@ export function validateProviderDefinition(raw: unknown): ProviderValidationResu
125
128
 
126
129
  validateCapabilities(provider as unknown as ProviderModule, controls, errors)
127
130
  validateCanonicalHistory(provider.canonicalHistory, errors)
131
+ validateMeshCoordinator(provider.meshCoordinator, errors)
128
132
 
129
133
  for (const control of controls) {
130
134
  validateControl(control as ProviderControlDef, errors)
@@ -233,6 +237,69 @@ function validateCanonicalHistory(raw: unknown, errors: string[]): void {
233
237
  }
234
238
  }
235
239
 
240
+ function validateMeshCoordinator(raw: unknown, errors: string[]): void {
241
+ if (raw === undefined) return
242
+ if (!raw || typeof raw !== 'object' || Array.isArray(raw)) {
243
+ errors.push('meshCoordinator must be an object')
244
+ return
245
+ }
246
+
247
+ const meshCoordinator = raw as Record<string, unknown>
248
+ if (typeof meshCoordinator.supported !== 'boolean') {
249
+ errors.push('meshCoordinator.supported must be boolean')
250
+ }
251
+ if (meshCoordinator.reason !== undefined && (typeof meshCoordinator.reason !== 'string' || !meshCoordinator.reason.trim())) {
252
+ errors.push('meshCoordinator.reason must be a non-empty string when provided')
253
+ }
254
+
255
+ const mcpConfig = meshCoordinator.mcpConfig
256
+ if (mcpConfig === undefined) return
257
+ if (!mcpConfig || typeof mcpConfig !== 'object' || Array.isArray(mcpConfig)) {
258
+ errors.push('meshCoordinator.mcpConfig must be an object')
259
+ return
260
+ }
261
+
262
+ const config = mcpConfig as Record<string, unknown>
263
+ const mode = config.mode
264
+ if (!['auto_import', 'manual', 'none'].includes(String(mode))) {
265
+ errors.push('meshCoordinator.mcpConfig.mode must be one of: auto_import, manual, none')
266
+ }
267
+
268
+ const format = config.format
269
+ if (format !== undefined && !['claude_mcp_json', 'hermes_config_yaml'].includes(String(format))) {
270
+ errors.push('meshCoordinator.mcpConfig.format must be one of: claude_mcp_json, hermes_config_yaml')
271
+ }
272
+
273
+ for (const key of ['path', 'serverName', 'configPathCommand', 'instructions', 'template']) {
274
+ const value = config[key]
275
+ if (value !== undefined && (typeof value !== 'string' || !value.trim())) {
276
+ errors.push(`meshCoordinator.mcpConfig.${key} must be a non-empty string when provided`)
277
+ }
278
+ }
279
+
280
+ if (config.requiresRestart !== undefined && typeof config.requiresRestart !== 'boolean') {
281
+ errors.push('meshCoordinator.mcpConfig.requiresRestart must be boolean when provided')
282
+ }
283
+
284
+ if (mode === 'auto_import') {
285
+ if (format === undefined) {
286
+ errors.push('meshCoordinator.mcpConfig.format is required for auto_import MCP setup')
287
+ }
288
+ if (typeof config.path !== 'string' || !config.path.trim()) {
289
+ errors.push('meshCoordinator.mcpConfig.path is required for auto_import MCP setup')
290
+ }
291
+ }
292
+
293
+ if (mode === 'manual') {
294
+ if (typeof config.instructions !== 'string' || !config.instructions.trim()) {
295
+ errors.push('meshCoordinator.mcpConfig.instructions is required for manual MCP setup')
296
+ }
297
+ if (typeof config.template !== 'string' || !config.template.trim()) {
298
+ errors.push('meshCoordinator.mcpConfig.template is required for manual MCP setup')
299
+ }
300
+ }
301
+ }
302
+
236
303
  function validateControl(control: ProviderControlDef, errors: string[]): void {
237
304
  if (!control || typeof control !== 'object') {
238
305
  errors.push('controls: each control must be an object')
@@ -43,7 +43,7 @@ export type { ProviderErrorReason } from './providers/provider-instance.js';
43
43
  // Local import for use in Managed*Entry types below
44
44
  import type { ActiveChatData as _ActiveChatData, ProviderErrorReason as _ProviderErrorReason } from './providers/provider-instance.js';
45
45
  import type { WorkspaceEntry } from './config/workspaces.js';
46
- import type { ProviderResumeCapability } from './providers/contracts.js';
46
+ import type { ProviderMeshCoordinatorConfig, ProviderResumeCapability } from './providers/contracts.js';
47
47
  import type {
48
48
  GitCompactSummary,
49
49
  GitDiffSummary,
@@ -447,6 +447,8 @@ export interface AvailableProviderInfo {
447
447
  lastDetection?: MachineProviderCheckResult;
448
448
  /** Last end-to-end ADHDev verification result, when available. */
449
449
  lastVerification?: MachineProviderCheckResult;
450
+ /** Provider-declared Repo Mesh coordinator/MCP behavior. */
451
+ meshCoordinator?: ProviderMeshCoordinatorConfig;
450
452
  }
451
453
 
452
454
  export interface MachineProviderCheckResult {
@@ -144,6 +144,7 @@ function buildAvailableProviders(
144
144
  machineStatus?: 'disabled' | 'enabled_unchecked' | 'not_detected' | 'detected';
145
145
  lastDetection?: AvailableProviderInfo['lastDetection'];
146
146
  lastVerification?: AvailableProviderInfo['lastVerification'];
147
+ meshCoordinator?: AvailableProviderInfo['meshCoordinator'];
147
148
  }> = providerLoader.getAvailableProviderInfos?.() || providerLoader.getAll();
148
149
  return providers.map((provider) => ({
149
150
  type: provider.type,
@@ -157,6 +158,7 @@ function buildAvailableProviders(
157
158
  ...(provider.machineStatus !== undefined ? { machineStatus: provider.machineStatus } : {}),
158
159
  ...(provider.lastDetection !== undefined ? { lastDetection: provider.lastDetection } : {}),
159
160
  ...(provider.lastVerification !== undefined ? { lastVerification: provider.lastVerification } : {}),
161
+ ...(provider.meshCoordinator !== undefined ? { meshCoordinator: provider.meshCoordinator } : {}),
160
162
  }));
161
163
  }
162
164