@adhdev/daemon-core 0.9.82-rc.7 → 0.9.82-rc.71
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 +2 -0
- package/dist/cli-adapters/provider-cli-adapter.d.ts +2 -0
- package/dist/cli-adapters/provider-cli-parse.d.ts +1 -0
- package/dist/cli-adapters/provider-cli-shared.d.ts +2 -0
- package/dist/commands/router.d.ts +24 -0
- package/dist/config/mesh-config.d.ts +66 -1
- package/dist/git/git-commands.d.ts +1 -0
- package/dist/git/git-status.d.ts +5 -0
- package/dist/git/git-types.d.ts +10 -0
- package/dist/index.d.ts +13 -6
- package/dist/index.js +4888 -1149
- package/dist/index.js.map +1 -1
- package/dist/index.mjs +4852 -1135
- 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 +18 -0
- package/dist/mesh/mesh-active-work.d.ts +48 -0
- package/dist/mesh/mesh-events.d.ts +28 -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 +38 -1
- package/dist/mesh/mesh-work-queue.d.ts +27 -5
- package/dist/mesh/refine-config.d.ts +119 -0
- package/dist/providers/chat-message-normalization.d.ts +1 -0
- package/dist/providers/cli-provider-instance.d.ts +1 -0
- package/dist/repo-mesh-types.d.ts +160 -0
- package/dist/status/reporter.d.ts +2 -0
- package/package.json +3 -1
- package/src/boot/daemon-lifecycle.ts +4 -0
- package/src/cli-adapters/provider-cli-adapter.ts +91 -3
- package/src/cli-adapters/provider-cli-parse.d.ts +1 -0
- package/src/cli-adapters/provider-cli-parse.ts +4 -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 +20 -10
- package/src/commands/chat-commands.ts +242 -7
- package/src/commands/cli-manager.ts +19 -0
- package/src/commands/handler.ts +8 -1
- package/src/commands/mesh-coordinator.ts +13 -143
- package/src/commands/router.ts +2518 -408
- package/src/config/chat-history.ts +9 -7
- package/src/config/mesh-config.ts +244 -1
- package/src/daemon/dev-cli-debug.ts +10 -1
- package/src/detection/ide-detector.ts +26 -16
- package/src/git/git-commands.ts +3 -3
- package/src/git/git-status.ts +97 -6
- package/src/git/git-summary.ts +3 -0
- package/src/git/git-types.ts +11 -0
- package/src/index.ts +39 -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 +176 -0
- package/src/mesh/coordinator-prompt.ts +5 -2
- package/src/mesh/mesh-active-work.ts +205 -0
- package/src/mesh/mesh-events.ts +291 -38
- package/src/mesh/mesh-fast-forward.ts +430 -0
- package/src/mesh/mesh-host-ownership.ts +73 -0
- package/src/mesh/mesh-ledger.ts +138 -1
- package/src/mesh/mesh-work-queue.ts +199 -137
- package/src/mesh/refine-config.ts +306 -0
- package/src/providers/chat-message-normalization.ts +3 -1
- package/src/providers/cli-provider-instance.ts +68 -1
- package/src/providers/ide-provider-instance.ts +17 -3
- package/src/providers/provider-loader.ts +10 -4
- package/src/providers/version-archive.ts +38 -20
- package/src/repo-mesh-types.ts +174 -0
- package/src/status/reporter.ts +15 -0
- package/src/system/host-memory.ts +29 -12
|
@@ -24,6 +24,7 @@ import { filterUserFacingChatMessages, normalizeChatMessages } from '../provider
|
|
|
24
24
|
const RECENT_SEND_WINDOW_MS = 1200;
|
|
25
25
|
export const READ_CHAT_PROVIDER_EVAL_TIMEOUT_MS = 25_000;
|
|
26
26
|
const HERMES_CLI_STARTING_SEND_SETTLE_MS = 2_000;
|
|
27
|
+
const CODEX_NATIVE_HISTORY_FRESH_MS = 5 * 60_000;
|
|
27
28
|
const recentSendByTarget = new Map<string, number>();
|
|
28
29
|
|
|
29
30
|
interface ApprovalSelectableInstance extends ProviderInstance {
|
|
@@ -221,6 +222,114 @@ function normalizeReadChatMessages(payload: Record<string, any>): ChatMessage[]
|
|
|
221
222
|
return normalizeChatMessages(messages);
|
|
222
223
|
}
|
|
223
224
|
|
|
225
|
+
function getMessageNewestReceivedAt(messages: Array<{ receivedAt?: unknown; timestamp?: unknown }>): number {
|
|
226
|
+
let newest = 0;
|
|
227
|
+
for (const message of messages) {
|
|
228
|
+
const receivedAt = Number(message?.receivedAt ?? message?.timestamp ?? 0);
|
|
229
|
+
if (Number.isFinite(receivedAt) && receivedAt > newest) newest = receivedAt;
|
|
230
|
+
}
|
|
231
|
+
return newest;
|
|
232
|
+
}
|
|
233
|
+
|
|
234
|
+
function buildCliMessageSourceProvenance(args: {
|
|
235
|
+
selected: 'native-history' | 'pty-parser';
|
|
236
|
+
provider: string;
|
|
237
|
+
nativeHandle?: string;
|
|
238
|
+
fallbackReason?: string;
|
|
239
|
+
nativeSource?: string;
|
|
240
|
+
sourcePath?: string;
|
|
241
|
+
sourceMtimeMs?: number;
|
|
242
|
+
nativeMessages?: ChatMessage[];
|
|
243
|
+
ptyMessages?: ChatMessage[];
|
|
244
|
+
returnedMessages?: ChatMessage[];
|
|
245
|
+
safeMapping?: boolean;
|
|
246
|
+
freshEnough?: boolean;
|
|
247
|
+
ptyStatusApprovalOnly?: boolean;
|
|
248
|
+
}): Record<string, unknown> {
|
|
249
|
+
const sourceMtimeMs = Number(args.sourceMtimeMs || 0);
|
|
250
|
+
const sourceMtimeAgeMs = sourceMtimeMs > 0 ? Math.max(0, Date.now() - sourceMtimeMs) : undefined;
|
|
251
|
+
const nativeMessages = args.nativeMessages || [];
|
|
252
|
+
const ptyMessages = args.ptyMessages || [];
|
|
253
|
+
const returnedMessages = args.returnedMessages || [];
|
|
254
|
+
return {
|
|
255
|
+
selected: args.selected,
|
|
256
|
+
provider: args.provider,
|
|
257
|
+
...(args.nativeHandle ? { nativeHandle: args.nativeHandle } : {}),
|
|
258
|
+
...(args.fallbackReason ? { fallbackReason: args.fallbackReason } : {}),
|
|
259
|
+
...(args.nativeSource ? { nativeSource: args.nativeSource } : {}),
|
|
260
|
+
...(args.sourcePath ? { sourcePath: args.sourcePath } : {}),
|
|
261
|
+
ptyStatusApprovalOnly: args.ptyStatusApprovalOnly === true,
|
|
262
|
+
staleness: {
|
|
263
|
+
sourceMtimeMs: sourceMtimeMs || undefined,
|
|
264
|
+
sourceMtimeAgeMs,
|
|
265
|
+
nativeNewestMessageAt: getMessageNewestReceivedAt(nativeMessages),
|
|
266
|
+
ptyNewestMessageAt: getMessageNewestReceivedAt(ptyMessages),
|
|
267
|
+
freshEnough: args.freshEnough === true,
|
|
268
|
+
},
|
|
269
|
+
coverage: {
|
|
270
|
+
nativeMessageCount: nativeMessages.length,
|
|
271
|
+
ptyMessageCount: ptyMessages.length,
|
|
272
|
+
returnedMessageCount: returnedMessages.length,
|
|
273
|
+
safeMapping: args.safeMapping === true,
|
|
274
|
+
},
|
|
275
|
+
};
|
|
276
|
+
}
|
|
277
|
+
|
|
278
|
+
function buildNativeHistoryFallbackReason(args: {
|
|
279
|
+
providerType: string;
|
|
280
|
+
nativeSource?: string;
|
|
281
|
+
nativeMessageCount: number;
|
|
282
|
+
safeMapping: boolean;
|
|
283
|
+
freshEnough: boolean;
|
|
284
|
+
}): string {
|
|
285
|
+
if (args.providerType !== 'codex-cli') return 'provider_not_codex_cli';
|
|
286
|
+
if (args.nativeSource === 'native-unavailable') return 'native_history_unavailable';
|
|
287
|
+
if (args.nativeSource && args.nativeSource !== 'provider-native') return `native_history_source_${args.nativeSource}`;
|
|
288
|
+
if (args.nativeMessageCount <= 0) return 'native_history_empty';
|
|
289
|
+
if (!args.safeMapping) return 'native_history_not_safely_mapped';
|
|
290
|
+
if (!args.freshEnough) return 'native_history_stale';
|
|
291
|
+
return 'native_history_not_selected';
|
|
292
|
+
}
|
|
293
|
+
|
|
294
|
+
function isCodexCliProvider(providerType: string): boolean {
|
|
295
|
+
return providerType === 'codex-cli';
|
|
296
|
+
}
|
|
297
|
+
|
|
298
|
+
function hasSafeNativeHistoryMapping(args: {
|
|
299
|
+
historySessionId?: string;
|
|
300
|
+
providerSessionId?: string;
|
|
301
|
+
workspace?: string;
|
|
302
|
+
nativeMessages: ChatMessage[];
|
|
303
|
+
}): boolean {
|
|
304
|
+
const explicitSessionId = String(args.historySessionId || args.providerSessionId || '').trim();
|
|
305
|
+
if (explicitSessionId) {
|
|
306
|
+
const messageSessionIds = args.nativeMessages
|
|
307
|
+
.map((message: any) => typeof message?.historySessionId === 'string' ? message.historySessionId.trim() : '')
|
|
308
|
+
.filter(Boolean);
|
|
309
|
+
if (messageSessionIds.length === 0) return true;
|
|
310
|
+
return messageSessionIds.some((id) => id === explicitSessionId);
|
|
311
|
+
}
|
|
312
|
+
const workspace = String(args.workspace || '').trim();
|
|
313
|
+
if (!workspace) return false;
|
|
314
|
+
return args.nativeMessages.some((message: any) => String(message?.workspace || '').trim() === workspace);
|
|
315
|
+
}
|
|
316
|
+
|
|
317
|
+
function isNativeHistoryFreshEnough(args: {
|
|
318
|
+
sourceMtimeMs?: number;
|
|
319
|
+
nativeMessages: ChatMessage[];
|
|
320
|
+
ptyMessages: ChatMessage[];
|
|
321
|
+
}): boolean {
|
|
322
|
+
const nativeNewest = getMessageNewestReceivedAt(args.nativeMessages);
|
|
323
|
+
const ptyNewest = getMessageNewestReceivedAt(args.ptyMessages);
|
|
324
|
+
if (nativeNewest > 0 && nativeNewest >= ptyNewest) return true;
|
|
325
|
+
const sourceMtimeMs = Number(args.sourceMtimeMs || 0);
|
|
326
|
+
if (sourceMtimeMs > 0 && Date.now() - sourceMtimeMs <= CODEX_NATIVE_HISTORY_FRESH_MS) return true;
|
|
327
|
+
return ptyNewest === 0 && nativeNewest > 0;
|
|
328
|
+
}
|
|
329
|
+
|
|
330
|
+
function shouldPreserveReadChatPayloadField(key: string): boolean {
|
|
331
|
+
return key === 'messageSource' || key === 'transcriptProvenance';
|
|
332
|
+
}
|
|
224
333
|
|
|
225
334
|
function deriveHistoryDedupKey(message: ChatMessage & { _unitKey?: string; _turnKey?: string }): string | undefined {
|
|
226
335
|
const unitKey = typeof message._unitKey === 'string' ? message._unitKey.trim() : '';
|
|
@@ -356,6 +465,7 @@ function buildReadChatCommandResult(payload: Record<string, any>, args: any): Co
|
|
|
356
465
|
return {
|
|
357
466
|
success: true,
|
|
358
467
|
...validatedPayload,
|
|
468
|
+
...Object.fromEntries(Object.entries(payload).filter(([key]) => shouldPreserveReadChatPayloadField(key))),
|
|
359
469
|
messages: sync.messages,
|
|
360
470
|
totalMessages: sync.totalMessages,
|
|
361
471
|
...(returnedDebugReadChat ? { debugReadChat: returnedDebugReadChat } : {}),
|
|
@@ -584,6 +694,8 @@ function buildChatDebugBundleSummary(bundle: Record<string, unknown>): Record<st
|
|
|
584
694
|
adapterStatus: debugReadChat.adapterStatus,
|
|
585
695
|
parsedStatus: debugReadChat.parsedStatus,
|
|
586
696
|
returnedStatus: debugReadChat.returnedStatus,
|
|
697
|
+
selectedMessageSource: debugReadChat.selectedMessageSource,
|
|
698
|
+
messageSource: debugReadChat.messageSource,
|
|
587
699
|
parsedMsgCount: debugReadChat.parsedMsgCount,
|
|
588
700
|
returnedMsgCount: debugReadChat.returnedMsgCount,
|
|
589
701
|
shouldPreferAdapterMessages: debugReadChat.shouldPreferAdapterMessages,
|
|
@@ -646,6 +758,8 @@ export async function handleGetChatDebugBundle(h: CommandHelpers, args: any): Pr
|
|
|
646
758
|
providerSessionId: readResult.providerSessionId,
|
|
647
759
|
transcriptAuthority: readResult.transcriptAuthority,
|
|
648
760
|
coverage: readResult.coverage,
|
|
761
|
+
messageSource: readResult.messageSource,
|
|
762
|
+
transcriptProvenance: readResult.transcriptProvenance,
|
|
649
763
|
activeModal: readResult.activeModal,
|
|
650
764
|
messagesTail: Array.isArray(readResult.messages) ? readResult.messages.slice(-20) : [],
|
|
651
765
|
debugReadChat: readResult.debugReadChat,
|
|
@@ -882,25 +996,146 @@ export async function handleReadChat(h: CommandHelpers, args: any): Promise<Comm
|
|
|
882
996
|
&& typeof runtimeMessageMerger.mergeRuntimeChatMessages === 'function'
|
|
883
997
|
? runtimeMessageMerger.mergeRuntimeChatMessages(parsedMessages)
|
|
884
998
|
: parsedMessages;
|
|
999
|
+
const providerType = provider?.type || adapter.cliType;
|
|
1000
|
+
let selectedMessages = returnedMessages;
|
|
1001
|
+
let selectedTitle = title;
|
|
1002
|
+
let selectedProviderSessionId = providerSessionId;
|
|
1003
|
+
let selectedTranscriptAuthority = transcriptAuthority;
|
|
1004
|
+
let selectedCoverage = coverage;
|
|
1005
|
+
let messageSource = buildCliMessageSourceProvenance({
|
|
1006
|
+
selected: 'pty-parser',
|
|
1007
|
+
provider: adapter.cliType,
|
|
1008
|
+
fallbackReason: isCodexCliProvider(providerType) ? 'native_history_not_checked' : 'provider_not_codex_cli',
|
|
1009
|
+
ptyMessages: returnedMessages,
|
|
1010
|
+
returnedMessages,
|
|
1011
|
+
ptyStatusApprovalOnly: false,
|
|
1012
|
+
});
|
|
1013
|
+
|
|
1014
|
+
if (isCodexCliProvider(providerType)) {
|
|
1015
|
+
const agentStr = provider?.type || args?.agentType || getCurrentProviderType(h, adapter.cliType);
|
|
1016
|
+
const workspace = typeof args?.workspace === 'string'
|
|
1017
|
+
? args.workspace
|
|
1018
|
+
: typeof (h.currentSession as any)?.workspace === 'string'
|
|
1019
|
+
? (h.currentSession as any).workspace
|
|
1020
|
+
: typeof adapter.workingDir === 'string'
|
|
1021
|
+
? adapter.workingDir
|
|
1022
|
+
: undefined;
|
|
1023
|
+
const nativeHistoryLimit = Math.max(
|
|
1024
|
+
normalizeReadChatTailLimit(args) || 0,
|
|
1025
|
+
returnedMessages.length,
|
|
1026
|
+
200,
|
|
1027
|
+
);
|
|
1028
|
+
let nativeHistory: ReturnType<typeof readProviderChatHistory> | null = null;
|
|
1029
|
+
try {
|
|
1030
|
+
nativeHistory = readProviderChatHistory(agentStr, {
|
|
1031
|
+
canonicalHistory: provider?.canonicalHistory,
|
|
1032
|
+
historySessionId,
|
|
1033
|
+
workspace,
|
|
1034
|
+
offset: 0,
|
|
1035
|
+
limit: nativeHistoryLimit,
|
|
1036
|
+
excludeRecentCount: 0,
|
|
1037
|
+
historyBehavior: provider?.historyBehavior,
|
|
1038
|
+
scripts: provider?.scripts as any,
|
|
1039
|
+
});
|
|
1040
|
+
} catch (error: any) {
|
|
1041
|
+
const fallbackReason = `native_history_error:${error?.message || String(error)}`;
|
|
1042
|
+
messageSource = buildCliMessageSourceProvenance({
|
|
1043
|
+
selected: 'pty-parser',
|
|
1044
|
+
provider: adapter.cliType,
|
|
1045
|
+
fallbackReason,
|
|
1046
|
+
ptyMessages: returnedMessages,
|
|
1047
|
+
returnedMessages,
|
|
1048
|
+
ptyStatusApprovalOnly: false,
|
|
1049
|
+
});
|
|
1050
|
+
nativeHistory = null;
|
|
1051
|
+
}
|
|
1052
|
+
|
|
1053
|
+
if (nativeHistory) {
|
|
1054
|
+
const nativeMessages = Array.isArray((nativeHistory as any).messages)
|
|
1055
|
+
? normalizeChatMessages((nativeHistory as any).messages as ChatMessage[])
|
|
1056
|
+
: [];
|
|
1057
|
+
const historyProviderSessionId = typeof (nativeHistory as any)?.providerSessionId === 'string'
|
|
1058
|
+
? (nativeHistory as any).providerSessionId
|
|
1059
|
+
: historySessionId;
|
|
1060
|
+
const safeMapping = hasSafeNativeHistoryMapping({
|
|
1061
|
+
historySessionId,
|
|
1062
|
+
providerSessionId,
|
|
1063
|
+
workspace,
|
|
1064
|
+
nativeMessages,
|
|
1065
|
+
});
|
|
1066
|
+
const freshEnough = isNativeHistoryFreshEnough({
|
|
1067
|
+
sourceMtimeMs: (nativeHistory as any).sourceMtimeMs,
|
|
1068
|
+
nativeMessages,
|
|
1069
|
+
ptyMessages: returnedMessages,
|
|
1070
|
+
});
|
|
1071
|
+
if ((nativeHistory as any).source === 'provider-native' && nativeMessages.length > 0 && safeMapping && freshEnough) {
|
|
1072
|
+
selectedMessages = finalizeStreamingMessagesWhenIdle(nativeMessages, returnedStatus);
|
|
1073
|
+
selectedProviderSessionId = historyProviderSessionId || providerSessionId;
|
|
1074
|
+
selectedTranscriptAuthority = 'provider';
|
|
1075
|
+
selectedCoverage = (nativeHistory as any).hasMore ? 'tail' : 'full';
|
|
1076
|
+
messageSource = buildCliMessageSourceProvenance({
|
|
1077
|
+
selected: 'native-history',
|
|
1078
|
+
provider: adapter.cliType,
|
|
1079
|
+
nativeHandle: selectedProviderSessionId || historySessionId,
|
|
1080
|
+
nativeSource: (nativeHistory as any).source,
|
|
1081
|
+
sourcePath: (nativeHistory as any).sourcePath,
|
|
1082
|
+
sourceMtimeMs: (nativeHistory as any).sourceMtimeMs,
|
|
1083
|
+
nativeMessages,
|
|
1084
|
+
ptyMessages: returnedMessages,
|
|
1085
|
+
returnedMessages: selectedMessages,
|
|
1086
|
+
safeMapping,
|
|
1087
|
+
freshEnough,
|
|
1088
|
+
ptyStatusApprovalOnly: true,
|
|
1089
|
+
});
|
|
1090
|
+
} else {
|
|
1091
|
+
const fallbackReason = buildNativeHistoryFallbackReason({
|
|
1092
|
+
providerType,
|
|
1093
|
+
nativeSource: (nativeHistory as any).source,
|
|
1094
|
+
nativeMessageCount: nativeMessages.length,
|
|
1095
|
+
safeMapping,
|
|
1096
|
+
freshEnough,
|
|
1097
|
+
});
|
|
1098
|
+
messageSource = buildCliMessageSourceProvenance({
|
|
1099
|
+
selected: 'pty-parser',
|
|
1100
|
+
provider: adapter.cliType,
|
|
1101
|
+
nativeHandle: historyProviderSessionId || historySessionId,
|
|
1102
|
+
fallbackReason,
|
|
1103
|
+
nativeSource: (nativeHistory as any).source,
|
|
1104
|
+
sourcePath: (nativeHistory as any).sourcePath,
|
|
1105
|
+
sourceMtimeMs: (nativeHistory as any).sourceMtimeMs,
|
|
1106
|
+
nativeMessages,
|
|
1107
|
+
ptyMessages: returnedMessages,
|
|
1108
|
+
returnedMessages,
|
|
1109
|
+
safeMapping,
|
|
1110
|
+
freshEnough,
|
|
1111
|
+
ptyStatusApprovalOnly: false,
|
|
1112
|
+
});
|
|
1113
|
+
}
|
|
1114
|
+
}
|
|
1115
|
+
}
|
|
885
1116
|
LOG.debug('Command', `[read_chat] cli-like parsed provider=${adapter.cliType} target=${String(args?.targetSessionId || '')} adapterStatus=${String(adapterStatus.status || '')} parsedStatus=${String(parsedRecord.status || '')} parsedMsgCount=${parsedRecord.messages.length} returnedMsgCount=${returnedMessages.length}`);
|
|
886
1117
|
return buildReadChatCommandResult({
|
|
887
|
-
messages:
|
|
1118
|
+
messages: selectedMessages,
|
|
888
1119
|
status: returnedStatus,
|
|
889
1120
|
activeModal,
|
|
1121
|
+
messageSource,
|
|
1122
|
+
transcriptProvenance: messageSource,
|
|
890
1123
|
debugReadChat: {
|
|
891
1124
|
provider: adapter.cliType,
|
|
892
1125
|
targetSessionId: String(args?.targetSessionId || ''),
|
|
893
1126
|
adapterStatus: String(adapterStatus.status || ''),
|
|
894
1127
|
parsedStatus: String(parsedRecord.status || ''),
|
|
895
1128
|
returnedStatus: String(returnedStatus || ''),
|
|
896
|
-
|
|
1129
|
+
selectedMessageSource: (messageSource as any).selected,
|
|
1130
|
+
messageSource,
|
|
1131
|
+
shouldPreferAdapterMessages: (messageSource as any).selected !== 'native-history',
|
|
897
1132
|
parsedMsgCount: parsedRecord.messages.length,
|
|
898
|
-
returnedMsgCount:
|
|
1133
|
+
returnedMsgCount: selectedMessages.length,
|
|
899
1134
|
},
|
|
900
|
-
...(
|
|
901
|
-
...(
|
|
902
|
-
...(
|
|
903
|
-
...(
|
|
1135
|
+
...(selectedTitle ? { title: selectedTitle } : {}),
|
|
1136
|
+
...(selectedProviderSessionId ? { providerSessionId: selectedProviderSessionId } : {}),
|
|
1137
|
+
...(selectedTranscriptAuthority ? { transcriptAuthority: selectedTranscriptAuthority } : {}),
|
|
1138
|
+
...(selectedCoverage ? { coverage: selectedCoverage } : {}),
|
|
904
1139
|
}, args);
|
|
905
1140
|
}
|
|
906
1141
|
const historyLimit = normalizeReadChatTailLimit(args);
|
|
@@ -80,6 +80,12 @@ export interface CliManagerDeps {
|
|
|
80
80
|
|
|
81
81
|
type CommandResult = { success: boolean;[key: string]: unknown };
|
|
82
82
|
|
|
83
|
+
const BUSY_AGENT_STATUSES = new Set(['generating', 'running', 'streaming', 'starting', 'busy', 'waiting', 'waiting_approval', 'long_generating']);
|
|
84
|
+
|
|
85
|
+
function normalizeAgentStatus(value: unknown): string {
|
|
86
|
+
return typeof value === 'string' ? value.trim().toLowerCase() : '';
|
|
87
|
+
}
|
|
88
|
+
|
|
83
89
|
export interface CliTransportFactoryParams {
|
|
84
90
|
runtimeId: string;
|
|
85
91
|
providerType: string;
|
|
@@ -1073,6 +1079,19 @@ export class DaemonCliManager {
|
|
|
1073
1079
|
const { adapter, key } = found;
|
|
1074
1080
|
|
|
1075
1081
|
if (action === 'send_chat') {
|
|
1082
|
+
const currentStatus = normalizeAgentStatus(adapter.getStatus?.()?.status);
|
|
1083
|
+
if (BUSY_AGENT_STATUSES.has(currentStatus)) {
|
|
1084
|
+
return {
|
|
1085
|
+
success: false,
|
|
1086
|
+
code: 'agent_runtime_busy',
|
|
1087
|
+
reason: 'agent_runtime_busy',
|
|
1088
|
+
retryable: true,
|
|
1089
|
+
retryRecommended: true,
|
|
1090
|
+
status: currentStatus,
|
|
1091
|
+
targetSessionId: args?.targetSessionId,
|
|
1092
|
+
error: `CLI agent '${agentType}' is currently ${currentStatus}; retry after the current turn finishes.`,
|
|
1093
|
+
};
|
|
1094
|
+
}
|
|
1076
1095
|
const input = normalizeInputEnvelope(args?.input ? { input: args.input } : args);
|
|
1077
1096
|
const provider = this.providerLoader.resolve(agentType) || this.providerLoader.getMeta(agentType);
|
|
1078
1097
|
if (provider?.category === 'acp') {
|
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
|
-
}
|