@adhdev/daemon-core 0.9.82-rc.14 → 0.9.82-rc.141
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/chat/source-machine.d.ts +166 -0
- package/dist/chat/source-resolver.d.ts +104 -0
- package/dist/chat/subscription-updates.d.ts +1 -0
- package/dist/cli-adapter-types.d.ts +5 -1
- package/dist/cli-adapters/cli-script-runner.d.ts +45 -0
- package/dist/cli-adapters/cli-state-engine.d.ts +178 -0
- package/dist/cli-adapters/provider-cli-adapter.d.ts +87 -63
- package/dist/cli-adapters/provider-cli-parse.d.ts +4 -0
- package/dist/cli-adapters/provider-cli-shared.d.ts +21 -0
- package/dist/commands/router.d.ts +22 -0
- package/dist/config/chat-history.d.ts +5 -0
- package/dist/config/config.d.ts +5 -0
- package/dist/config/mesh-config.d.ts +68 -1
- package/dist/git/git-commands.d.ts +5 -1
- package/dist/index.d.ts +18 -6
- package/dist/index.js +10431 -2827
- package/dist/index.js.map +1 -1
- package/dist/index.mjs +10376 -2811
- package/dist/index.mjs.map +1 -1
- package/dist/installer.d.ts +1 -4
- package/dist/launch.d.ts +1 -1
- package/dist/logging/async-batch-writer.d.ts +10 -0
- package/dist/mesh/beads-db.d.ts +72 -0
- package/dist/mesh/contracts.d.ts +164 -0
- package/dist/mesh/coordinator-registry.d.ts +25 -0
- package/dist/mesh/mesh-active-work.d.ts +90 -0
- package/dist/mesh/mesh-events.d.ts +77 -5
- package/dist/mesh/mesh-fast-forward.d.ts +39 -0
- package/dist/mesh/mesh-host-ownership.d.ts +9 -0
- package/dist/mesh/mesh-ledger.d.ts +58 -1
- package/dist/mesh/mesh-refine-status.d.ts +26 -0
- package/dist/mesh/mesh-work-queue.d.ts +44 -5
- package/dist/mesh/preview-freshness.d.ts +18 -0
- package/dist/mesh/refine-config.d.ts +193 -0
- package/dist/mesh/worktree-bootstrap-config.d.ts +113 -0
- package/dist/providers/approval-utils.d.ts +9 -0
- package/dist/providers/chat-message-normalization.d.ts +1 -0
- package/dist/providers/cli-provider-instance.d.ts +6 -1
- package/dist/providers/contracts.d.ts +19 -0
- package/dist/providers/read-chat-contract.d.ts +29 -0
- package/dist/providers/transcript-v2.d.ts +176 -0
- package/dist/repo-mesh-types.d.ts +67 -0
- package/dist/shared-types.d.ts +12 -0
- package/dist/status/reporter.d.ts +2 -0
- package/dist/status/snapshot.d.ts +1 -0
- package/dist/types.d.ts +5 -0
- package/package.json +3 -1
- package/src/boot/daemon-lifecycle.ts +3 -0
- package/src/chat/source-machine.ts +534 -0
- package/src/chat/source-resolver.ts +0 -0
- package/src/chat/subscription-updates.ts +14 -1
- package/src/cli-adapter-types.d.ts +1 -0
- package/src/cli-adapter-types.ts +3 -1
- package/src/cli-adapters/cli-script-runner.ts +145 -0
- package/src/cli-adapters/cli-state-engine.ts +1083 -0
- package/src/cli-adapters/provider-cli-adapter.d.ts +1 -1
- package/src/cli-adapters/provider-cli-adapter.ts +630 -1137
- package/src/cli-adapters/provider-cli-parse.d.ts +1 -0
- package/src/cli-adapters/provider-cli-parse.ts +13 -0
- package/src/cli-adapters/provider-cli-runtime.ts +3 -1
- package/src/cli-adapters/provider-cli-shared.d.ts +2 -0
- package/src/cli-adapters/provider-cli-shared.ts +51 -11
- package/src/cli-adapters/terminal-backends/ghostty-vt-backend.ts +17 -1
- package/src/cli-adapters/terminal-backends/xterm-backend.ts +8 -1
- package/src/commands/chat-commands.ts +1428 -50
- package/src/commands/cli-manager.ts +145 -3
- package/src/commands/handler.ts +8 -1
- package/src/commands/mesh-coordinator.ts +13 -143
- package/src/commands/router.ts +3271 -427
- package/src/config/chat-history.ts +79 -24
- package/src/config/config.ts +12 -0
- package/src/config/mesh-config.ts +249 -2
- package/src/config/recent-activity.ts +8 -2
- package/src/daemon/dev-cli-debug.ts +10 -1
- package/src/detection/ide-detector.ts +26 -16
- package/src/git/git-commands.ts +17 -5
- package/src/git/git-worktree.ts +8 -1
- package/src/index.ts +45 -5
- package/src/installer.d.ts +1 -1
- package/src/installer.ts +8 -6
- package/src/launch.d.ts +1 -1
- package/src/launch.ts +37 -28
- package/src/logging/async-batch-writer.ts +55 -0
- package/src/logging/logger.ts +2 -1
- package/src/mesh/beads-db.ts +479 -0
- package/src/mesh/contracts.ts +329 -0
- package/src/mesh/coordinator-prompt.ts +40 -22
- package/src/mesh/coordinator-registry.ts +75 -0
- package/src/mesh/mesh-active-work.ts +437 -0
- package/src/mesh/mesh-events.ts +799 -56
- package/src/mesh/mesh-fast-forward.ts +430 -0
- package/src/mesh/mesh-host-ownership.ts +73 -0
- package/src/mesh/mesh-ledger.ts +457 -104
- package/src/mesh/mesh-refine-status.ts +144 -0
- package/src/mesh/mesh-work-queue.ts +216 -158
- package/src/mesh/preview-freshness.ts +118 -0
- package/src/mesh/refine-config.ts +366 -0
- package/src/mesh/worktree-bootstrap-config.ts +247 -0
- package/src/providers/approval-utils.ts +39 -5
- package/src/providers/chat-message-normalization.ts +7 -12
- package/src/providers/cli-provider-instance.ts +362 -41
- package/src/providers/contracts.ts +19 -0
- package/src/providers/ide-provider-instance.ts +17 -3
- package/src/providers/provider-loader.ts +31 -11
- package/src/providers/provider-schema.ts +12 -0
- package/src/providers/read-chat-contract.ts +76 -16
- package/src/providers/transcript-v2.ts +567 -0
- package/src/providers/version-archive.ts +38 -20
- package/src/repo-mesh-types.ts +77 -0
- package/src/shared-types.ts +9 -0
- package/src/status/builders.ts +23 -6
- package/src/status/reporter.ts +15 -0
- package/src/status/snapshot.ts +35 -11
- package/src/system/host-memory.ts +29 -12
- package/src/types.ts +5 -0
|
@@ -18,6 +18,7 @@ import { loadConfig } from '../config/config.js';
|
|
|
18
18
|
import { loadState, saveState } from '../config/state-store.js';
|
|
19
19
|
import { getWorkspaceState, resolveLaunchDirectory } from '../config/workspaces.js';
|
|
20
20
|
import { appendRecentActivity } from '../config/recent-activity.js';
|
|
21
|
+
import { unregisterMeshCoordinator } from '../mesh/coordinator-registry.js';
|
|
21
22
|
import { upsertSavedProviderSession } from '../config/saved-sessions.js';
|
|
22
23
|
import { buildLegacyModelModeSummaryMetadata, normalizeProviderSummaryMetadata } from '../providers/summary-metadata.js';
|
|
23
24
|
import { CliProviderInstance } from '../providers/cli-provider-instance.js';
|
|
@@ -80,6 +81,114 @@ export interface CliManagerDeps {
|
|
|
80
81
|
|
|
81
82
|
type CommandResult = { success: boolean;[key: string]: unknown };
|
|
82
83
|
|
|
84
|
+
const BUSY_AGENT_STATUSES = new Set(['generating', 'running', 'streaming', 'starting', 'busy', 'waiting', 'waiting_approval', 'long_generating']);
|
|
85
|
+
const ZERO_MESSAGE_STARTING_SEND_WAIT_MS = 2_000;
|
|
86
|
+
|
|
87
|
+
function normalizeAgentStatus(value: unknown): string {
|
|
88
|
+
return typeof value === 'string' ? value.trim().toLowerCase() : '';
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
function hasNonEmptyModalButtons(activeModal: unknown): boolean {
|
|
92
|
+
const buttons = (activeModal as any)?.buttons;
|
|
93
|
+
return Array.isArray(buttons) && buttons.some((button) => String(button || '').trim().length > 0);
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
function hasAdapterPendingResponse(adapter: any): boolean {
|
|
97
|
+
if (adapter?.isWaitingForResponse === true) return true;
|
|
98
|
+
if (adapter?.currentTurnScope) return true;
|
|
99
|
+
try {
|
|
100
|
+
if (typeof adapter?.isProcessing === 'function' && adapter.isProcessing()) return true;
|
|
101
|
+
} catch { /* defensive: send guard should not fail on diagnostics */ }
|
|
102
|
+
try {
|
|
103
|
+
const partial = typeof adapter?.getPartialResponse === 'function' ? adapter.getPartialResponse() : '';
|
|
104
|
+
if (typeof partial === 'string' && partial.trim()) return true;
|
|
105
|
+
} catch { /* defensive: missing partial means no pending evidence */ }
|
|
106
|
+
return false;
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
function countMessages(value: unknown): number {
|
|
110
|
+
return Array.isArray(value) ? value.length : 0;
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
function hasFinalAssistantMessage(value: unknown): boolean {
|
|
114
|
+
const messages = Array.isArray(value) ? value : [];
|
|
115
|
+
const last = messages[messages.length - 1] as any;
|
|
116
|
+
if (!last || last.role !== 'assistant') return false;
|
|
117
|
+
if (last.bubbleState === 'streaming') return false;
|
|
118
|
+
if (last.meta?.streaming === true) return false;
|
|
119
|
+
return typeof last.content === 'string' && last.content.trim().length > 0;
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
function hasZeroMessageStartingLaunch(adapter: any): boolean {
|
|
123
|
+
const adapterStatus = adapter?.getStatus?.({ allowParse: false }) ?? adapter?.getStatus?.() ?? {};
|
|
124
|
+
const parsedStatus = typeof adapter?.getScriptParsedStatus === 'function'
|
|
125
|
+
? adapter.getScriptParsedStatus()
|
|
126
|
+
: {};
|
|
127
|
+
const adapterRawStatus = normalizeAgentStatus(adapterStatus?.status);
|
|
128
|
+
const parsedRawStatus = normalizeAgentStatus(parsedStatus?.status);
|
|
129
|
+
if (adapterRawStatus !== 'starting') return false;
|
|
130
|
+
if (parsedRawStatus && parsedRawStatus !== 'starting' && parsedRawStatus !== 'generating') return false;
|
|
131
|
+
if (hasNonEmptyModalButtons(adapterStatus?.activeModal ?? adapterStatus?.modal ?? parsedStatus?.activeModal ?? parsedStatus?.modal)) return false;
|
|
132
|
+
if (countMessages(adapterStatus?.messages) > 0 || countMessages(parsedStatus?.messages) > 0) return false;
|
|
133
|
+
return !hasAdapterPendingResponse(adapter);
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
function hasCompletedStartingLaunch(adapter: any): boolean {
|
|
137
|
+
const adapterStatus = adapter?.getStatus?.({ allowParse: false }) ?? adapter?.getStatus?.() ?? {};
|
|
138
|
+
const adapterRawStatus = normalizeAgentStatus(adapterStatus?.status);
|
|
139
|
+
if (adapterRawStatus !== 'starting') return false;
|
|
140
|
+
if (hasAdapterPendingResponse(adapter)) return false;
|
|
141
|
+
|
|
142
|
+
const parsedStatus = typeof adapter?.getScriptParsedStatus === 'function'
|
|
143
|
+
? adapter.getScriptParsedStatus()
|
|
144
|
+
: {};
|
|
145
|
+
const parsedRawStatus = normalizeAgentStatus(parsedStatus?.status);
|
|
146
|
+
if (parsedRawStatus !== 'idle') return false;
|
|
147
|
+
if (hasNonEmptyModalButtons(adapterStatus?.activeModal ?? adapterStatus?.modal ?? parsedStatus?.activeModal ?? parsedStatus?.modal)) return false;
|
|
148
|
+
return hasFinalAssistantMessage(parsedStatus?.messages);
|
|
149
|
+
}
|
|
150
|
+
|
|
151
|
+
function shouldSuppressStaleParsedBusyStatus(adapterStatus: string, parsedStatus: any, adapter: any): boolean {
|
|
152
|
+
const parsedRawStatus = normalizeAgentStatus(parsedStatus?.status);
|
|
153
|
+
if (!BUSY_AGENT_STATUSES.has(parsedRawStatus)) return false;
|
|
154
|
+
if (adapterStatus !== 'idle') return false;
|
|
155
|
+
if (hasNonEmptyModalButtons(parsedStatus?.activeModal ?? parsedStatus?.modal)) return false;
|
|
156
|
+
return !hasAdapterPendingResponse(adapter);
|
|
157
|
+
}
|
|
158
|
+
|
|
159
|
+
function getEffectiveAgentSendStatus(adapter: any): string {
|
|
160
|
+
const adapterStatus = normalizeAgentStatus(adapter?.getStatus?.({ allowParse: false })?.status ?? adapter?.getStatus?.()?.status);
|
|
161
|
+
if (adapterStatus === 'starting' && hasCompletedStartingLaunch(adapter)) return 'idle';
|
|
162
|
+
if (adapterStatus && adapterStatus !== 'idle') return adapterStatus;
|
|
163
|
+
if (adapterStatus !== 'idle') return adapterStatus;
|
|
164
|
+
|
|
165
|
+
if (typeof adapter?.getScriptParsedStatus !== 'function') return adapterStatus;
|
|
166
|
+
try {
|
|
167
|
+
const parsedStatus = adapter.getScriptParsedStatus();
|
|
168
|
+
const parsedRawStatus = normalizeAgentStatus(parsedStatus?.status);
|
|
169
|
+
if (BUSY_AGENT_STATUSES.has(parsedRawStatus) && !shouldSuppressStaleParsedBusyStatus(adapterStatus, parsedStatus, adapter)) {
|
|
170
|
+
return parsedRawStatus;
|
|
171
|
+
}
|
|
172
|
+
} catch {
|
|
173
|
+
return adapterStatus;
|
|
174
|
+
}
|
|
175
|
+
return adapterStatus;
|
|
176
|
+
}
|
|
177
|
+
|
|
178
|
+
async function waitForZeroMessageStartingLaunch(adapter: any): Promise<boolean> {
|
|
179
|
+
try {
|
|
180
|
+
if (!hasZeroMessageStartingLaunch(adapter)) return false;
|
|
181
|
+
} catch {
|
|
182
|
+
return false;
|
|
183
|
+
}
|
|
184
|
+
await new Promise(resolve => setTimeout(resolve, ZERO_MESSAGE_STARTING_SEND_WAIT_MS));
|
|
185
|
+
try {
|
|
186
|
+
return hasZeroMessageStartingLaunch(adapter);
|
|
187
|
+
} catch {
|
|
188
|
+
return false;
|
|
189
|
+
}
|
|
190
|
+
}
|
|
191
|
+
|
|
83
192
|
export interface CliTransportFactoryParams {
|
|
84
193
|
runtimeId: string;
|
|
85
194
|
providerType: string;
|
|
@@ -255,10 +364,14 @@ function detectExplicitProviderSessionId(
|
|
|
255
364
|
|
|
256
365
|
const subcommands = resume?.sessionIdFromSubcommand;
|
|
257
366
|
if (Array.isArray(subcommands) && subcommands.length > 0) {
|
|
367
|
+
const hasResumeSubcommand = args.some((arg) => subcommands.includes(arg));
|
|
258
368
|
const subcommandSessionId = readSubcommandSessionId(args, subcommands);
|
|
259
369
|
if (subcommandSessionId) {
|
|
260
370
|
return { providerSessionId: subcommandSessionId, launchMode: 'resume' };
|
|
261
371
|
}
|
|
372
|
+
if (hasResumeSubcommand) {
|
|
373
|
+
return { launchMode: 'resume' };
|
|
374
|
+
}
|
|
262
375
|
}
|
|
263
376
|
|
|
264
377
|
return { launchMode: 'manual' };
|
|
@@ -292,6 +405,12 @@ export function resolveCliSessionBinding(
|
|
|
292
405
|
launchMode: explicit.launchMode,
|
|
293
406
|
};
|
|
294
407
|
}
|
|
408
|
+
if (explicit.launchMode === 'resume') {
|
|
409
|
+
return {
|
|
410
|
+
cliArgs: baseArgs,
|
|
411
|
+
launchMode: 'resume',
|
|
412
|
+
};
|
|
413
|
+
}
|
|
295
414
|
if (explicit.launchMode === 'manual' && hasArg(baseArgs || [], ['--session-id'])) {
|
|
296
415
|
return {
|
|
297
416
|
cliArgs: baseArgs,
|
|
@@ -435,7 +554,9 @@ export class DaemonCliManager {
|
|
|
435
554
|
providerSessionId,
|
|
436
555
|
attachExisting,
|
|
437
556
|
);
|
|
438
|
-
|
|
557
|
+
const adapter = new ProviderCliAdapter(resolvedProvider as CliProviderModule, workingDir, cliArgs, extraEnv || {}, transportFactory);
|
|
558
|
+
if (providerSessionId) adapter.updateRuntimeMeta({ providerSessionId });
|
|
559
|
+
return adapter;
|
|
439
560
|
}
|
|
440
561
|
|
|
441
562
|
throw new Error(`No CLI provider found for '${cliType}'. Create a provider.js in providers/cli/${cliType}/`);
|
|
@@ -457,6 +578,7 @@ export class DaemonCliManager {
|
|
|
457
578
|
this.deps.removeAgentTracking(key);
|
|
458
579
|
sessionRegistry?.unregisterByInstanceKey(key);
|
|
459
580
|
instanceManager?.removeInstance(key);
|
|
581
|
+
unregisterMeshCoordinator(key);
|
|
460
582
|
LOG.info('CLI', `🧹 Auto-cleaned ${status.status} CLI: ${cliType}`);
|
|
461
583
|
this.deps.onStatusChange();
|
|
462
584
|
}
|
|
@@ -783,6 +905,7 @@ export class DaemonCliManager {
|
|
|
783
905
|
this.deps.removeAgentTracking(key);
|
|
784
906
|
this.deps.getSessionRegistry?.()?.unregisterByInstanceKey(key);
|
|
785
907
|
this.deps.getInstanceManager()?.removeInstance(key);
|
|
908
|
+
unregisterMeshCoordinator(key);
|
|
786
909
|
LOG.info('CLI', `🛑 Agent stopped: ${adapter.cliType} in ${adapter.workingDir}`);
|
|
787
910
|
this.deps.onStatusChange();
|
|
788
911
|
} else {
|
|
@@ -792,6 +915,7 @@ export class DaemonCliManager {
|
|
|
792
915
|
this.deps.getSessionRegistry?.()?.unregisterByInstanceKey(key);
|
|
793
916
|
im.removeInstance(key);
|
|
794
917
|
this.deps.removeAgentTracking(key);
|
|
918
|
+
unregisterMeshCoordinator(key);
|
|
795
919
|
LOG.warn('CLI', `🧹 Force-removed orphan entry: ${key}`);
|
|
796
920
|
this.deps.onStatusChange();
|
|
797
921
|
}
|
|
@@ -1073,6 +1197,12 @@ export class DaemonCliManager {
|
|
|
1073
1197
|
const { adapter, key } = found;
|
|
1074
1198
|
|
|
1075
1199
|
if (action === 'send_chat') {
|
|
1200
|
+
let currentStatus = getEffectiveAgentSendStatus(adapter);
|
|
1201
|
+
if (currentStatus === 'starting' && await waitForZeroMessageStartingLaunch(adapter)) {
|
|
1202
|
+
currentStatus = 'idle';
|
|
1203
|
+
} else if (currentStatus === 'starting') {
|
|
1204
|
+
currentStatus = getEffectiveAgentSendStatus(adapter);
|
|
1205
|
+
}
|
|
1076
1206
|
const input = normalizeInputEnvelope(args?.input ? { input: args.input } : args);
|
|
1077
1207
|
const provider = this.providerLoader.resolve(agentType) || this.providerLoader.getMeta(agentType);
|
|
1078
1208
|
if (provider?.category === 'acp') {
|
|
@@ -1082,8 +1212,20 @@ export class DaemonCliManager {
|
|
|
1082
1212
|
}
|
|
1083
1213
|
const message = input.textFallback;
|
|
1084
1214
|
if (!message) throw new Error('message required for send_chat');
|
|
1085
|
-
|
|
1086
|
-
|
|
1215
|
+
const forceSend = args?.force === true || args?.forceSend === true;
|
|
1216
|
+
if (forceSend && typeof (adapter as any).forceSendMessage === 'function') {
|
|
1217
|
+
await (adapter as any).forceSendMessage(message);
|
|
1218
|
+
} else if (forceSend) {
|
|
1219
|
+
await adapter.sendMessage(message, { force: true });
|
|
1220
|
+
} else {
|
|
1221
|
+
await adapter.sendMessage(message);
|
|
1222
|
+
}
|
|
1223
|
+
return {
|
|
1224
|
+
success: true,
|
|
1225
|
+
status: BUSY_AGENT_STATUSES.has(currentStatus) ? currentStatus : 'generating',
|
|
1226
|
+
...(BUSY_AGENT_STATUSES.has(currentStatus) ? { queued: true, queuedReason: 'agent_runtime_busy' } : {}),
|
|
1227
|
+
...(forceSend ? { forceSent: true, queued: false } : {}),
|
|
1228
|
+
};
|
|
1087
1229
|
} else if (action === 'clear_history') {
|
|
1088
1230
|
if (typeof adapter.clearHistory === 'function') adapter.clearHistory();
|
|
1089
1231
|
return { success: true, cleared: true };
|
package/src/commands/handler.ts
CHANGED
|
@@ -408,12 +408,19 @@ export class DaemonCommandHandler implements CommandHelpers {
|
|
|
408
408
|
'invoke_provider_script',
|
|
409
409
|
]);
|
|
410
410
|
|
|
411
|
+
// read_chat and get_chat_debug_bundle can serve historical transcript data even
|
|
412
|
+
// when the live session record is gone (stopped/destroyed). Allow the fallback
|
|
413
|
+
// when the provider type is known and any session identity hint is present:
|
|
414
|
+
// an explicit providerSessionId/historySessionId, or the targetSessionId itself
|
|
415
|
+
// (which getHistorySessionId already uses as a fallback history key).
|
|
416
|
+
const isReadOrDebugCmd = cmd === 'read_chat' || cmd === 'get_chat_debug_bundle';
|
|
411
417
|
const allowsInactiveReadChatFallback =
|
|
412
|
-
|
|
418
|
+
isReadOrDebugCmd
|
|
413
419
|
&& !!this._currentRoute.providerType
|
|
414
420
|
&& (
|
|
415
421
|
(typeof args?.providerSessionId === 'string' && args.providerSessionId.trim().length > 0)
|
|
416
422
|
|| (typeof args?.historySessionId === 'string' && args.historySessionId.trim().length > 0)
|
|
423
|
+
|| (typeof args?.targetSessionId === 'string' && args.targetSessionId.trim().length > 0)
|
|
417
424
|
);
|
|
418
425
|
|
|
419
426
|
if (this._currentRoute.sessionLookupFailed && sessionScopedCommands.has(cmd) && !allowsInactiveReadChatFallback) {
|
|
@@ -1,9 +1,6 @@
|
|
|
1
|
-
import { execFileSync } from 'node:child_process'
|
|
2
1
|
import { createHash } from 'node:crypto'
|
|
3
|
-
import { existsSync, readdirSync, realpathSync } from 'node:fs'
|
|
4
|
-
import { createRequire } from 'node:module'
|
|
5
2
|
import * as os from 'node:os'
|
|
6
|
-
import {
|
|
3
|
+
import { isAbsolute, join, resolve } from 'node:path'
|
|
7
4
|
import type { ProviderModule, MeshCoordinatorMcpConfigFormat } from '../providers/contracts.js'
|
|
8
5
|
|
|
9
6
|
export interface MeshCoordinatorMcpServerLaunch {
|
|
@@ -55,7 +52,7 @@ export interface ResolveMeshCoordinatorSetupOptions {
|
|
|
55
52
|
}
|
|
56
53
|
|
|
57
54
|
const DEFAULT_SERVER_NAME = 'adhdev-mesh'
|
|
58
|
-
const DEFAULT_ADHDEV_MCP_COMMAND = 'adhdev
|
|
55
|
+
const DEFAULT_ADHDEV_MCP_COMMAND = 'adhdev'
|
|
59
56
|
const HERMES_CLI_TYPE = 'hermes-cli'
|
|
60
57
|
const HERMES_MCP_CONFIG_PATH = '~/.hermes/config.yaml'
|
|
61
58
|
|
|
@@ -67,8 +64,7 @@ function isHermesProvider(provider: ProviderModule | null | undefined, cliType?:
|
|
|
67
64
|
function resolveHermesMeshCoordinatorSetup(options: ResolveMeshCoordinatorSetupOptions): MeshCoordinatorSetup {
|
|
68
65
|
const mcpServer = resolveAdhdevMcpServerLaunch({
|
|
69
66
|
meshId: options.meshId,
|
|
70
|
-
|
|
71
|
-
adhdevMcpEntryPath: options.adhdevMcpEntryPath,
|
|
67
|
+
adhdevMcpCommand: options.adhdevMcpCommand,
|
|
72
68
|
adhdevMcpTransport: options.adhdevMcpTransport,
|
|
73
69
|
adhdevMcpPort: options.adhdevMcpPort,
|
|
74
70
|
})
|
|
@@ -100,7 +96,7 @@ export function createHermesManualMeshCoordinatorSetup(meshId: string, workspace
|
|
|
100
96
|
requiresRestart: true,
|
|
101
97
|
instructions: 'Hermes CLI does not auto-import repo-local .mcp.json. Add this MCP server to Hermes config under mcp_servers, then start a fresh Hermes session.',
|
|
102
98
|
template: renderMeshCoordinatorTemplate(
|
|
103
|
-
'mcp_servers:\n {{serverName}}:\n command: {{adhdevMcpCommand}}\n args:\n - --repo-mesh\n - {{meshId}}\n enabled: true\n',
|
|
99
|
+
'mcp_servers:\n {{serverName}}:\n command: {{adhdevMcpCommand}}\n args:\n - mcp\n - --mode\n - ipc\n - --repo-mesh\n - {{meshId}}\n enabled: true\n',
|
|
104
100
|
{
|
|
105
101
|
meshId,
|
|
106
102
|
workspace,
|
|
@@ -141,8 +137,7 @@ export function resolveMeshCoordinatorSetup(options: ResolveMeshCoordinatorSetup
|
|
|
141
137
|
}
|
|
142
138
|
const mcpServer = resolveAdhdevMcpServerLaunch({
|
|
143
139
|
meshId,
|
|
144
|
-
|
|
145
|
-
adhdevMcpEntryPath: options.adhdevMcpEntryPath,
|
|
140
|
+
adhdevMcpCommand: options.adhdevMcpCommand,
|
|
146
141
|
adhdevMcpTransport: options.adhdevMcpTransport,
|
|
147
142
|
adhdevMcpPort: options.adhdevMcpPort,
|
|
148
143
|
})
|
|
@@ -222,25 +217,25 @@ function resolveMcpConfigPath(configPath: string, workspace: string): string {
|
|
|
222
217
|
|
|
223
218
|
function resolveAdhdevMcpServerLaunch(options: {
|
|
224
219
|
meshId: string
|
|
225
|
-
|
|
226
|
-
adhdevMcpEntryPath?: string
|
|
220
|
+
adhdevMcpCommand?: string
|
|
227
221
|
adhdevMcpTransport?: 'local' | 'ipc'
|
|
228
222
|
adhdevMcpPort?: number
|
|
229
223
|
}): MeshCoordinatorMcpServerLaunch | null {
|
|
230
|
-
const
|
|
231
|
-
if (!entryPath) return null
|
|
232
|
-
const nodeExecutable = resolveMcpNodeExecutable(options.nodeExecutable)
|
|
233
|
-
if (!nodeExecutable) return null
|
|
224
|
+
const command = resolveAdhdevCommand(options.adhdevMcpCommand)
|
|
234
225
|
const transport = resolveMcpTransport(options.adhdevMcpTransport)
|
|
235
|
-
const args = [
|
|
226
|
+
const args = ['mcp', '--mode', transport, '--repo-mesh', options.meshId]
|
|
236
227
|
const port = resolveMcpPort(options.adhdevMcpPort)
|
|
237
228
|
if (port !== undefined) args.push('--port', String(port))
|
|
238
229
|
return {
|
|
239
|
-
command
|
|
230
|
+
command,
|
|
240
231
|
args,
|
|
241
232
|
}
|
|
242
233
|
}
|
|
243
234
|
|
|
235
|
+
function resolveAdhdevCommand(explicitCommand?: string): string {
|
|
236
|
+
return explicitCommand?.trim() || process.env.ADHDEV_COORDINATOR_MCP_COMMAND?.trim() || DEFAULT_ADHDEV_MCP_COMMAND
|
|
237
|
+
}
|
|
238
|
+
|
|
244
239
|
function resolveMcpTransport(explicitTransport?: 'local' | 'ipc'): 'local' | 'ipc' {
|
|
245
240
|
if (explicitTransport === 'local' || explicitTransport === 'ipc') return explicitTransport
|
|
246
241
|
const envTransport = process.env.ADHDEV_COORDINATOR_MCP_TRANSPORT?.trim()
|
|
@@ -254,128 +249,3 @@ function resolveMcpPort(explicitPort?: number): number | undefined {
|
|
|
254
249
|
const parsed = Number(raw)
|
|
255
250
|
return Number.isInteger(parsed) && parsed > 0 ? parsed : undefined
|
|
256
251
|
}
|
|
257
|
-
|
|
258
|
-
function resolveMcpNodeExecutable(explicitExecutable?: string): string | null {
|
|
259
|
-
const explicit = explicitExecutable?.trim()
|
|
260
|
-
if (explicit) return explicit
|
|
261
|
-
|
|
262
|
-
const candidates: string[] = []
|
|
263
|
-
const addCandidate = (candidate?: string | null) => {
|
|
264
|
-
const trimmed = candidate?.trim()
|
|
265
|
-
if (!trimmed) return
|
|
266
|
-
const normalized = normalizeExistingPath(trimmed) || trimmed
|
|
267
|
-
if (!candidates.includes(normalized)) candidates.push(normalized)
|
|
268
|
-
}
|
|
269
|
-
|
|
270
|
-
addCandidate(process.env.ADHDEV_MCP_NODE_EXECUTABLE)
|
|
271
|
-
addCandidate(process.env.ADHDEV_NODE_EXECUTABLE)
|
|
272
|
-
addCandidate(process.env.npm_node_execpath)
|
|
273
|
-
addNodeCandidatesFromPath(process.env.PATH, addCandidate)
|
|
274
|
-
addNodeCandidatesFromNvm(os.homedir(), addCandidate)
|
|
275
|
-
addCandidate('/opt/homebrew/bin/node')
|
|
276
|
-
addCandidate('/usr/local/bin/node')
|
|
277
|
-
addCandidate('/usr/bin/node')
|
|
278
|
-
addCandidate(process.execPath)
|
|
279
|
-
|
|
280
|
-
for (const candidate of candidates) {
|
|
281
|
-
if (nodeRuntimeSupportsWebSocket(candidate)) return candidate
|
|
282
|
-
}
|
|
283
|
-
return null
|
|
284
|
-
}
|
|
285
|
-
|
|
286
|
-
function addNodeCandidatesFromPath(pathValue: string | undefined, addCandidate: (candidate?: string | null) => void) {
|
|
287
|
-
for (const entry of (pathValue || '').split(':')) {
|
|
288
|
-
const dir = entry.trim()
|
|
289
|
-
if (!dir) continue
|
|
290
|
-
addCandidate(join(dir, 'node'))
|
|
291
|
-
}
|
|
292
|
-
}
|
|
293
|
-
|
|
294
|
-
function addNodeCandidatesFromNvm(homeDir: string, addCandidate: (candidate?: string | null) => void) {
|
|
295
|
-
const versionsDir = join(homeDir, '.nvm', 'versions', 'node')
|
|
296
|
-
try {
|
|
297
|
-
const versionDirs = readdirSync(versionsDir, { withFileTypes: true })
|
|
298
|
-
.filter((entry) => entry.isDirectory())
|
|
299
|
-
.map((entry) => entry.name)
|
|
300
|
-
.sort(compareNodeVersionNamesDescending)
|
|
301
|
-
for (const versionDir of versionDirs) {
|
|
302
|
-
addCandidate(join(versionsDir, versionDir, 'bin', 'node'))
|
|
303
|
-
}
|
|
304
|
-
} catch {
|
|
305
|
-
// nvm is optional; PATH and process.execPath candidates still cover normal installs.
|
|
306
|
-
}
|
|
307
|
-
}
|
|
308
|
-
|
|
309
|
-
function compareNodeVersionNamesDescending(a: string, b: string): number {
|
|
310
|
-
const parse = (value: string) => value.replace(/^v/, '').split('.').map((part) => Number.parseInt(part, 10) || 0)
|
|
311
|
-
const left = parse(a)
|
|
312
|
-
const right = parse(b)
|
|
313
|
-
for (let i = 0; i < Math.max(left.length, right.length); i++) {
|
|
314
|
-
const diff = (right[i] || 0) - (left[i] || 0)
|
|
315
|
-
if (diff !== 0) return diff
|
|
316
|
-
}
|
|
317
|
-
return b.localeCompare(a)
|
|
318
|
-
}
|
|
319
|
-
|
|
320
|
-
function nodeRuntimeSupportsWebSocket(nodeExecutable: string): boolean {
|
|
321
|
-
try {
|
|
322
|
-
execFileSync(nodeExecutable, ['-e', "process.exit(typeof WebSocket === 'function' ? 0 : 42)"], {
|
|
323
|
-
stdio: 'ignore',
|
|
324
|
-
timeout: 3000,
|
|
325
|
-
})
|
|
326
|
-
return true
|
|
327
|
-
} catch {
|
|
328
|
-
return false
|
|
329
|
-
}
|
|
330
|
-
}
|
|
331
|
-
|
|
332
|
-
function resolveAdhdevMcpEntryPath(explicitPath?: string): string | null {
|
|
333
|
-
const explicit = explicitPath?.trim()
|
|
334
|
-
if (explicit) return normalizeExistingPath(explicit) || explicit
|
|
335
|
-
|
|
336
|
-
const envPath = process.env.ADHDEV_MCP_SERVER_PATH?.trim()
|
|
337
|
-
if (envPath) return normalizeExistingPath(envPath) || envPath
|
|
338
|
-
|
|
339
|
-
const candidates: string[] = []
|
|
340
|
-
const addCandidate = (candidate: string) => {
|
|
341
|
-
if (!candidates.includes(candidate)) candidates.push(candidate)
|
|
342
|
-
}
|
|
343
|
-
const addPackagedCandidates = (baseFile?: string) => {
|
|
344
|
-
if (!baseFile) return
|
|
345
|
-
const realBase = normalizeExistingPath(baseFile) || baseFile
|
|
346
|
-
const dir = dirname(realBase)
|
|
347
|
-
addCandidate(resolve(dir, '../vendor/mcp-server/index.js'))
|
|
348
|
-
addCandidate(resolve(dir, '../../vendor/mcp-server/index.js'))
|
|
349
|
-
addCandidate(resolve(dir, '../../../vendor/mcp-server/index.js'))
|
|
350
|
-
// Source checkout/dev mode does not vendor the MCP server into daemon-standalone.
|
|
351
|
-
// Resolve the sibling workspace build directly so Repo Mesh auto-import still
|
|
352
|
-
// writes an absolute Node entrypoint instead of falling back to a PATH bin shim.
|
|
353
|
-
addCandidate(resolve(dir, '../../mcp-server/dist/index.js'))
|
|
354
|
-
addCandidate(resolve(dir, '../../../mcp-server/dist/index.js'))
|
|
355
|
-
}
|
|
356
|
-
|
|
357
|
-
addPackagedCandidates(process.argv[1])
|
|
358
|
-
|
|
359
|
-
for (const candidate of candidates) {
|
|
360
|
-
const normalized = normalizeExistingPath(candidate)
|
|
361
|
-
if (normalized) return normalized
|
|
362
|
-
}
|
|
363
|
-
|
|
364
|
-
try {
|
|
365
|
-
const requireBase = process.argv[1] ? (normalizeExistingPath(process.argv[1]) || process.argv[1]) : join(process.cwd(), 'adhdev-daemon.js')
|
|
366
|
-
const req = createRequire(requireBase)
|
|
367
|
-
const resolvedModule = req.resolve('@adhdev/mcp-server')
|
|
368
|
-
return normalizeExistingPath(resolvedModule) || resolvedModule
|
|
369
|
-
} catch {
|
|
370
|
-
return null
|
|
371
|
-
}
|
|
372
|
-
}
|
|
373
|
-
|
|
374
|
-
function normalizeExistingPath(filePath: string): string | null {
|
|
375
|
-
try {
|
|
376
|
-
if (!existsSync(filePath)) return null
|
|
377
|
-
return realpathSync.native(filePath)
|
|
378
|
-
} catch {
|
|
379
|
-
return null
|
|
380
|
-
}
|
|
381
|
-
}
|