@adhdev/daemon-core 0.9.82-rc.187 → 0.9.82-rc.189
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/router.d.ts +5 -1
- package/dist/git/git-commands.d.ts +2 -0
- package/dist/git/git-types.d.ts +2 -0
- package/dist/index.d.ts +1 -1
- package/dist/index.js +459 -38
- package/dist/index.js.map +1 -1
- package/dist/index.mjs +458 -38
- package/dist/index.mjs.map +1 -1
- package/dist/providers/cli-provider-instance.d.ts +4 -0
- package/dist/providers/contracts.d.ts +31 -0
- package/dist/providers/sdk/v1/types/common/index.d.ts +35 -1
- package/dist/providers/spec/adapter.d.ts +4 -0
- package/dist/providers/spec/driver.d.ts +10 -1
- package/dist/providers/spec/evaluator.d.ts +9 -1
- package/dist/providers/spec/schema.gen.d.ts +38 -0
- package/dist/providers/spec/types.d.ts +25 -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 +26 -0
- package/src/commands/cli-manager.ts +52 -14
- package/src/commands/router.ts +35 -4
- 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 +1 -1
- package/src/mesh/mesh-events.ts +7 -0
- package/src/providers/cli-provider-instance.ts +110 -9
- package/src/providers/contracts.d.ts +55 -0
- package/src/providers/contracts.ts +35 -0
- 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/adapter.ts +8 -0
- package/src/providers/spec/driver.ts +74 -2
- package/src/providers/spec/evaluator.ts +39 -3
- package/src/providers/spec/schema.gen.ts +28 -1
- package/src/providers/spec/schema.json +26 -2
- package/src/providers/spec/types.ts +25 -0
- package/src/repo-mesh-types.ts +6 -0
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
|
@@ -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/mesh-events.ts
CHANGED
|
@@ -1962,6 +1962,11 @@ export function handleMeshForwardEvent(components: DaemonComponents, payload: Re
|
|
|
1962
1962
|
const nodeId = readNonEmptyString(payload.nodeId);
|
|
1963
1963
|
const workspace = readNonEmptyString(payload.workspace);
|
|
1964
1964
|
const nodeLabel = nodeId ? `Node '${nodeId}'` : workspace ? `Agent at ${workspace}` : 'Remote agent';
|
|
1965
|
+
const relayModalMessage = readNonEmptyString(payload.modalMessage);
|
|
1966
|
+
const relayModalButtons = Array.isArray(payload.modalButtons)
|
|
1967
|
+
? (payload.modalButtons as unknown[]).filter((b): b is string => typeof b === 'string' && b.trim().length > 0)
|
|
1968
|
+
: null;
|
|
1969
|
+
|
|
1965
1970
|
return injectMeshSystemMessage(components, {
|
|
1966
1971
|
meshId,
|
|
1967
1972
|
nodeId,
|
|
@@ -1979,6 +1984,8 @@ export function handleMeshForwardEvent(components: DaemonComponents, payload: Re
|
|
|
1979
1984
|
startedAt: readNonEmptyString(payload.startedAt),
|
|
1980
1985
|
completedAt: readNonEmptyString(payload.completedAt),
|
|
1981
1986
|
retryOfJobId: readNonEmptyString(payload.retryOfJobId),
|
|
1987
|
+
...(relayModalMessage ? { modalMessage: relayModalMessage } : {}),
|
|
1988
|
+
...(relayModalButtons && relayModalButtons.length > 0 ? { modalButtons: relayModalButtons } : {}),
|
|
1982
1989
|
...(payload.result && typeof payload.result === 'object' && !Array.isArray(payload.result) ? { result: payload.result } : {}),
|
|
1983
1990
|
...(payload.completionDiagnostic && typeof payload.completionDiagnostic === 'object' && !Array.isArray(payload.completionDiagnostic) ? { completionDiagnostic: payload.completionDiagnostic } : {}),
|
|
1984
1991
|
...(payload.workerResult && typeof payload.workerResult === 'object' && !Array.isArray(payload.workerResult) ? { workerResult: payload.workerResult } : {}),
|
|
@@ -73,6 +73,12 @@ type CompletionFinalAssistantEvidence = {
|
|
|
73
73
|
source: 'parsed' | 'external-native' | 'unavailable';
|
|
74
74
|
};
|
|
75
75
|
|
|
76
|
+
type ExternalNativeFinalReconciliation = {
|
|
77
|
+
fingerprint: string;
|
|
78
|
+
finalSummary: string;
|
|
79
|
+
evidence: CompletionFinalAssistantEvidence;
|
|
80
|
+
};
|
|
81
|
+
|
|
76
82
|
type ExternalTranscriptProbe = {
|
|
77
83
|
readAt: number;
|
|
78
84
|
msgCount: number;
|
|
@@ -369,6 +375,8 @@ export class CliProviderInstance implements ProviderInstance {
|
|
|
369
375
|
private historyWriter: ChatHistoryWriter;
|
|
370
376
|
private runtimeMessages: Array<{ key: string; message: ChatMessage }> = [];
|
|
371
377
|
private lastPersistedHistoryMessages: PersistableCliHistoryMessage[] = [];
|
|
378
|
+
private lastAcknowledgedUserInputAt = 0;
|
|
379
|
+
private externalBusyIdleFingerprint = '';
|
|
372
380
|
private lastNativeSourceCanonicalCheckAt = 0;
|
|
373
381
|
private lastNativeSourceCanonicalCacheKey: string | undefined = undefined;
|
|
374
382
|
private cachedSqliteDb: {
|
|
@@ -587,9 +595,13 @@ export class CliProviderInstance implements ProviderInstance {
|
|
|
587
595
|
typeof adapterStatus?.providerSessionId === 'string' ? adapterStatus.providerSessionId : '',
|
|
588
596
|
);
|
|
589
597
|
const autoApproveActive = this.maybeAutoApproveStatus(adapterStatus, Date.now());
|
|
590
|
-
|
|
598
|
+
let visibleStatus = parseErrorMessage || parsedStatus?.status === 'error'
|
|
591
599
|
? 'error'
|
|
592
600
|
: (autoApproveActive ? 'generating' : adapterStatus.status);
|
|
601
|
+
const externalNativeFinal = this.getExternalNativeFinalReconciliation(parsedStatus?.messages, adapterStatus);
|
|
602
|
+
if (externalNativeFinal && isCliGeneratingLikeStatus(visibleStatus)) {
|
|
603
|
+
visibleStatus = 'idle';
|
|
604
|
+
}
|
|
593
605
|
const runtime = this.adapter.getRuntimeMetadata();
|
|
594
606
|
this.maybeAppendRuntimeRecoveryMessage(runtime);
|
|
595
607
|
let parsedMessages = Array.isArray(parsedStatus?.messages)
|
|
@@ -788,7 +800,22 @@ export class CliProviderInstance implements ProviderInstance {
|
|
|
788
800
|
}
|
|
789
801
|
|
|
790
802
|
updateSettings(newSettings: Record<string, any>): void {
|
|
791
|
-
|
|
803
|
+
const runtimeMeshSettings: Record<string, any> = {};
|
|
804
|
+
for (const key of [
|
|
805
|
+
'meshNodeFor',
|
|
806
|
+
'meshNodeId',
|
|
807
|
+
'meshActiveTaskId',
|
|
808
|
+
'meshCoordinatorFor',
|
|
809
|
+
'meshCoordinatorDaemonId',
|
|
810
|
+
'meshCoordinatorNodeId',
|
|
811
|
+
'spawnedSessionVisibility',
|
|
812
|
+
'launchedByCoordinator',
|
|
813
|
+
]) {
|
|
814
|
+
if (this.settings[key] !== undefined && newSettings[key] === undefined) {
|
|
815
|
+
runtimeMeshSettings[key] = this.settings[key];
|
|
816
|
+
}
|
|
817
|
+
}
|
|
818
|
+
this.settings = { ...newSettings, ...runtimeMeshSettings };
|
|
792
819
|
this.adapter.updateRuntimeSettings?.(this.settings);
|
|
793
820
|
this.monitor.updateConfig({
|
|
794
821
|
approvalAlert: this.settings.approvalAlert !== false,
|
|
@@ -884,6 +911,8 @@ export class CliProviderInstance implements ProviderInstance {
|
|
|
884
911
|
if (!content) return;
|
|
885
912
|
|
|
886
913
|
const receivedAt = Date.now();
|
|
914
|
+
this.lastAcknowledgedUserInputAt = receivedAt;
|
|
915
|
+
this.externalBusyIdleFingerprint = '';
|
|
887
916
|
const dedupKey = `user_input_ack:${crypto
|
|
888
917
|
.createHash('sha256')
|
|
889
918
|
.update(`${this.instanceId}:${content}:${receivedAt}`)
|
|
@@ -1049,6 +1078,59 @@ export class CliProviderInstance implements ProviderInstance {
|
|
|
1049
1078
|
return extractFinalSummaryFromMessages(evidence.messages as any);
|
|
1050
1079
|
}
|
|
1051
1080
|
|
|
1081
|
+
private externalNativeFinalFingerprint(evidence: CompletionFinalAssistantEvidence): string {
|
|
1082
|
+
const messages = Array.isArray(evidence.messages) ? evidence.messages : [];
|
|
1083
|
+
const visibleMessages = messages.filter((message: any) => isUserFacingChatMessage(message as ChatMessage));
|
|
1084
|
+
const lastVisible = visibleMessages[visibleMessages.length - 1] as ChatMessage | undefined;
|
|
1085
|
+
const content = lastVisible ? flattenContent(lastVisible.content).trim() : '';
|
|
1086
|
+
const receivedAt = lastVisible ? getMessageTime(lastVisible) : 0;
|
|
1087
|
+
const probe = this.lastExternalCompletionProbe;
|
|
1088
|
+
return crypto
|
|
1089
|
+
.createHash('sha256')
|
|
1090
|
+
.update([
|
|
1091
|
+
this.type,
|
|
1092
|
+
this.providerSessionId || '',
|
|
1093
|
+
probe?.sourcePath || '',
|
|
1094
|
+
String(probe?.sourceMtimeMs || 0),
|
|
1095
|
+
String(receivedAt || 0),
|
|
1096
|
+
content.slice(-500),
|
|
1097
|
+
].join('\0'))
|
|
1098
|
+
.digest('hex')
|
|
1099
|
+
.slice(0, 24);
|
|
1100
|
+
}
|
|
1101
|
+
|
|
1102
|
+
private getExternalNativeFinalReconciliation(parsedMessages: unknown, adapterStatus: any): ExternalNativeFinalReconciliation | null {
|
|
1103
|
+
const rawStatus = typeof adapterStatus?.status === 'string' ? adapterStatus.status.trim() : '';
|
|
1104
|
+
if (!isCliGeneratingLikeStatus(rawStatus)) return null;
|
|
1105
|
+
if (hasNonEmptyCliModalButtons(adapterStatus?.activeModal ?? adapterStatus?.modal)) return null;
|
|
1106
|
+
|
|
1107
|
+
const evidence = this.completionFinalAssistantEvidence(parsedMessages);
|
|
1108
|
+
if (evidence.source !== 'external-native' || !evidence.present) return null;
|
|
1109
|
+
|
|
1110
|
+
const messages = Array.isArray(evidence.messages) ? evidence.messages : [];
|
|
1111
|
+
const visibleMessages = messages.filter((message: any) => isUserFacingChatMessage(message as ChatMessage));
|
|
1112
|
+
const lastVisible = visibleMessages[visibleMessages.length - 1] as ChatMessage | undefined;
|
|
1113
|
+
const lastMessageAt = lastVisible ? getMessageTime(lastVisible) : 0;
|
|
1114
|
+
const sourceMtimeMs = Number(this.lastExternalCompletionProbe?.sourceMtimeMs || 0);
|
|
1115
|
+
const minEvidenceAt = Math.max(
|
|
1116
|
+
this.startedAt > 0 ? this.startedAt - 5_000 : 0,
|
|
1117
|
+
this.generatingStartedAt > 0 ? this.generatingStartedAt - 5_000 : 0,
|
|
1118
|
+
this.lastAcknowledgedUserInputAt > 0 ? this.lastAcknowledgedUserInputAt - 1_000 : 0,
|
|
1119
|
+
);
|
|
1120
|
+
if (minEvidenceAt > 0 && lastMessageAt > 0 && lastMessageAt < minEvidenceAt && sourceMtimeMs < minEvidenceAt) {
|
|
1121
|
+
return null;
|
|
1122
|
+
}
|
|
1123
|
+
|
|
1124
|
+
const finalSummary = extractFinalSummaryFromMessages(evidence.messages as any);
|
|
1125
|
+
if (!finalSummary) return null;
|
|
1126
|
+
const fingerprint = this.externalNativeFinalFingerprint(evidence);
|
|
1127
|
+
if (fingerprint === this.externalBusyIdleFingerprint) {
|
|
1128
|
+
return { fingerprint, finalSummary, evidence };
|
|
1129
|
+
}
|
|
1130
|
+
this.externalBusyIdleFingerprint = fingerprint;
|
|
1131
|
+
return { fingerprint, finalSummary, evidence };
|
|
1132
|
+
}
|
|
1133
|
+
|
|
1052
1134
|
private buildCompletedFinalizationDiagnostic(args: {
|
|
1053
1135
|
blockReason: string;
|
|
1054
1136
|
latestStatus?: any;
|
|
@@ -1138,12 +1220,13 @@ export class CliProviderInstance implements ProviderInstance {
|
|
|
1138
1220
|
return true;
|
|
1139
1221
|
}
|
|
1140
1222
|
|
|
1141
|
-
private getCompletedFinalizationBlock(latestVisibleStatus: string, pending: CompletedDebouncePending): CompletedFinalizationBlock | null {
|
|
1223
|
+
private getCompletedFinalizationBlock(latestVisibleStatus: string, pending: CompletedDebouncePending, opts?: { externalNativeFinal?: ExternalNativeFinalReconciliation | null }): CompletedFinalizationBlock | null {
|
|
1142
1224
|
if (latestVisibleStatus !== 'idle') return { reason: `status:${latestVisibleStatus}`, terminal: true };
|
|
1143
1225
|
|
|
1144
1226
|
const adapterAny = this.adapter as any;
|
|
1145
1227
|
const approvalResolvedIdle = pending.previousStatus === 'waiting_approval';
|
|
1146
|
-
|
|
1228
|
+
const externalNativeFinal = opts?.externalNativeFinal || null;
|
|
1229
|
+
if (!approvalResolvedIdle && !externalNativeFinal) {
|
|
1147
1230
|
if (adapterAny?.isWaitingForResponse === true) return { reason: 'adapter_waiting_for_response', terminal: true };
|
|
1148
1231
|
if (adapterAny?.currentTurnScope) return { reason: 'adapter_turn_scope_active', terminal: true };
|
|
1149
1232
|
if (this.hasAdapterPendingResponse()) return { reason: 'adapter_pending_response', terminal: true };
|
|
@@ -1152,7 +1235,7 @@ export class CliProviderInstance implements ProviderInstance {
|
|
|
1152
1235
|
const partial = typeof this.adapter.getPartialResponse === 'function'
|
|
1153
1236
|
? this.adapter.getPartialResponse()
|
|
1154
1237
|
: '';
|
|
1155
|
-
if (typeof partial === 'string' && partial.trim()) return { reason: 'partial_response_pending', terminal: true };
|
|
1238
|
+
if (!externalNativeFinal && typeof partial === 'string' && partial.trim()) return { reason: 'partial_response_pending', terminal: true };
|
|
1156
1239
|
|
|
1157
1240
|
let parsed: any;
|
|
1158
1241
|
try {
|
|
@@ -1164,6 +1247,7 @@ export class CliProviderInstance implements ProviderInstance {
|
|
|
1164
1247
|
const parsedStatus = typeof parsed?.status === 'string' ? parsed.status : 'unknown';
|
|
1165
1248
|
if (parsedStatus !== 'idle') {
|
|
1166
1249
|
const adapterStatus = this.adapter.getStatus({ allowParse: false });
|
|
1250
|
+
if (externalNativeFinal && isCliGeneratingLikeStatus(parsedStatus)) return null;
|
|
1167
1251
|
if (this.shouldSuppressStaleParsedBusyStatus(parsed, adapterStatus)) return null;
|
|
1168
1252
|
return { reason: `parsed_status:${parsedStatus}`, terminal: isCliGeneratingLikeStatus(parsedStatus) };
|
|
1169
1253
|
}
|
|
@@ -1230,7 +1314,10 @@ export class CliProviderInstance implements ProviderInstance {
|
|
|
1230
1314
|
|
|
1231
1315
|
const latestStatus = this.adapter.getStatus({ allowParse: false });
|
|
1232
1316
|
const latestAutoApproveActive = latestStatus.status === 'waiting_approval' && this.shouldAutoApprove();
|
|
1233
|
-
const
|
|
1317
|
+
const externalNativeFinal = this.getExternalNativeFinalReconciliation(undefined, latestStatus);
|
|
1318
|
+
const latestVisibleStatus = externalNativeFinal && isCliGeneratingLikeStatus(latestStatus.status)
|
|
1319
|
+
? 'idle'
|
|
1320
|
+
: (latestAutoApproveActive ? 'generating' : latestStatus.status);
|
|
1234
1321
|
if (latestVisibleStatus !== 'idle') {
|
|
1235
1322
|
LOG.info('CLI', `[${this.type}] cancelled pending completed (resumed ${latestVisibleStatus})`);
|
|
1236
1323
|
this.completedDebouncePending = null;
|
|
@@ -1238,7 +1325,7 @@ export class CliProviderInstance implements ProviderInstance {
|
|
|
1238
1325
|
return;
|
|
1239
1326
|
}
|
|
1240
1327
|
|
|
1241
|
-
const block = this.getCompletedFinalizationBlock(latestVisibleStatus, pending);
|
|
1328
|
+
const block = this.getCompletedFinalizationBlock(latestVisibleStatus, pending, { externalNativeFinal });
|
|
1242
1329
|
if (block) {
|
|
1243
1330
|
const blockReason = block.reason;
|
|
1244
1331
|
const waitedMs = Date.now() - pending.firstObservedAt;
|
|
@@ -1282,7 +1369,18 @@ export class CliProviderInstance implements ProviderInstance {
|
|
|
1282
1369
|
chatTitle: pending.chatTitle,
|
|
1283
1370
|
duration: pending.duration,
|
|
1284
1371
|
timestamp: pending.timestamp,
|
|
1285
|
-
finalSummary: this.completionFinalSummary(this.adapter?.getScriptParsedStatus()?.messages),
|
|
1372
|
+
finalSummary: externalNativeFinal?.finalSummary || this.completionFinalSummary(this.adapter?.getScriptParsedStatus()?.messages),
|
|
1373
|
+
...(externalNativeFinal ? {
|
|
1374
|
+
completionDiagnostic: {
|
|
1375
|
+
providerType: this.type,
|
|
1376
|
+
sessionId: this.instanceId,
|
|
1377
|
+
providerSessionId: this.providerSessionId || null,
|
|
1378
|
+
reconciliationReason: 'external_native_final_assistant_while_adapter_busy',
|
|
1379
|
+
finalAssistantPresent: true,
|
|
1380
|
+
finalAssistantEvidenceSource: externalNativeFinal.evidence.source,
|
|
1381
|
+
externalFinalFingerprint: externalNativeFinal.fingerprint,
|
|
1382
|
+
},
|
|
1383
|
+
} : {}),
|
|
1286
1384
|
});
|
|
1287
1385
|
this.completedDebouncePending = null;
|
|
1288
1386
|
this.completedDebounceTimer = null;
|
|
@@ -1359,7 +1457,10 @@ export class CliProviderInstance implements ProviderInstance {
|
|
|
1359
1457
|
const parsedStatus = null;
|
|
1360
1458
|
const rawStatus = adapterStatus.status;
|
|
1361
1459
|
const autoApproveActive = this.maybeAutoApproveStatus(adapterStatus, now);
|
|
1362
|
-
const
|
|
1460
|
+
const externalNativeFinal = this.getExternalNativeFinalReconciliation(undefined, adapterStatus);
|
|
1461
|
+
const newStatus = externalNativeFinal && isCliGeneratingLikeStatus(rawStatus)
|
|
1462
|
+
? 'idle'
|
|
1463
|
+
: (autoApproveActive ? 'generating' : rawStatus);
|
|
1363
1464
|
const dirName = this.workingDir.split('/').filter(Boolean).pop() || 'session';
|
|
1364
1465
|
const chatTitle = `${this.provider.name} · ${dirName}`;
|
|
1365
1466
|
const partial = this.adapter.getPartialResponse();
|
|
@@ -401,7 +401,62 @@ export interface ProviderModule {
|
|
|
401
401
|
spawnArgBuilder?: (config: Record<string, string>) => string[];
|
|
402
402
|
/** ACP agent auth methods (multiple supported — in priority order) */
|
|
403
403
|
auth?: AcpAuthMethod[];
|
|
404
|
+
/**
|
|
405
|
+
* Repo Mesh coordinator capability and MCP ingestion behavior.
|
|
406
|
+
* Providers must declare this rather than relying on daemon hardcoded CLI quirks.
|
|
407
|
+
*/
|
|
408
|
+
meshCoordinator?: ProviderMeshCoordinatorConfig;
|
|
409
|
+
}
|
|
410
|
+
export type MeshCoordinatorMcpConfigMode = 'auto_import' | 'manual' | 'none';
|
|
411
|
+
export type MeshCoordinatorMcpConfigFormat = 'claude_mcp_json' | 'hermes_config_yaml';
|
|
412
|
+
export interface ProviderMeshCoordinatorConfig {
|
|
413
|
+
supported: boolean;
|
|
414
|
+
reason?: string;
|
|
415
|
+
mcpConfig?: {
|
|
416
|
+
mode: MeshCoordinatorMcpConfigMode;
|
|
417
|
+
format?: MeshCoordinatorMcpConfigFormat;
|
|
418
|
+
path?: string;
|
|
419
|
+
serverName?: string;
|
|
420
|
+
configPathCommand?: string;
|
|
421
|
+
requiresRestart?: boolean;
|
|
422
|
+
instructions?: string;
|
|
423
|
+
template?: string;
|
|
424
|
+
};
|
|
425
|
+
systemPromptInjection?: MeshCoordinatorSystemPromptInjection;
|
|
426
|
+
delegatedWorkerIsolation?: MeshCoordinatorDelegatedWorkerIsolation;
|
|
404
427
|
}
|
|
428
|
+
export type MeshCoordinatorSystemPromptInjection = {
|
|
429
|
+
mode: 'cli_arg';
|
|
430
|
+
flag: string;
|
|
431
|
+
} | {
|
|
432
|
+
mode: 'config_override';
|
|
433
|
+
flag: string;
|
|
434
|
+
template: string;
|
|
435
|
+
} | {
|
|
436
|
+
mode: 'context_file';
|
|
437
|
+
path: string;
|
|
438
|
+
wrapper?: string;
|
|
439
|
+
} | {
|
|
440
|
+
mode: 'env_var';
|
|
441
|
+
name: string;
|
|
442
|
+
};
|
|
443
|
+
export interface MeshCoordinatorDelegatedWorkerIsolation {
|
|
444
|
+
env?: {
|
|
445
|
+
unset?: string[];
|
|
446
|
+
};
|
|
447
|
+
args?: MeshCoordinatorDelegatedWorkerArgRule[];
|
|
448
|
+
}
|
|
449
|
+
export type MeshCoordinatorDelegatedWorkerArgRule = {
|
|
450
|
+
mode: 'empty_mcp_config';
|
|
451
|
+
flag: string;
|
|
452
|
+
strictFlag?: string;
|
|
453
|
+
} | {
|
|
454
|
+
mode: 'config_override';
|
|
455
|
+
flag: string;
|
|
456
|
+
key: string;
|
|
457
|
+
value: string;
|
|
458
|
+
dedupeKey?: string;
|
|
459
|
+
};
|
|
405
460
|
export interface ProviderResumeCapability {
|
|
406
461
|
supported: boolean;
|
|
407
462
|
stopStrategy?: 'command' | 'ctrl_c';
|
|
@@ -403,8 +403,43 @@ export interface ProviderMeshCoordinatorConfig {
|
|
|
403
403
|
* the CLI doesn't recognize).
|
|
404
404
|
*/
|
|
405
405
|
systemPromptInjection?: MeshCoordinatorSystemPromptInjection;
|
|
406
|
+
/**
|
|
407
|
+
* How coordinator-launched worker sessions are isolated from coordinator-only
|
|
408
|
+
* MCP/tools/config. Provider-specific CLI quirks belong here, not in daemon
|
|
409
|
+
* launch code.
|
|
410
|
+
*/
|
|
411
|
+
delegatedWorkerIsolation?: MeshCoordinatorDelegatedWorkerIsolation;
|
|
406
412
|
}
|
|
407
413
|
|
|
414
|
+
export interface MeshCoordinatorDelegatedWorkerIsolation {
|
|
415
|
+
/** Environment variables to unset for delegated worker sessions. */
|
|
416
|
+
env?: {
|
|
417
|
+
unset?: string[];
|
|
418
|
+
};
|
|
419
|
+
/** Spawn-argument rules applied before launching a delegated worker. */
|
|
420
|
+
args?: MeshCoordinatorDelegatedWorkerArgRule[];
|
|
421
|
+
}
|
|
422
|
+
|
|
423
|
+
export type MeshCoordinatorDelegatedWorkerArgRule =
|
|
424
|
+
| {
|
|
425
|
+
mode: 'empty_mcp_config';
|
|
426
|
+
/** CLI flag that points at an MCP config file, e.g. '--mcp-config'. */
|
|
427
|
+
flag: string;
|
|
428
|
+
/** Optional CLI flag that forces only the provided MCP config to be used. */
|
|
429
|
+
strictFlag?: string;
|
|
430
|
+
}
|
|
431
|
+
| {
|
|
432
|
+
mode: 'config_override';
|
|
433
|
+
/** CLI config flag, e.g. '-c' or '--config'. */
|
|
434
|
+
flag: string;
|
|
435
|
+
/** Config key to set for worker isolation. */
|
|
436
|
+
key: string;
|
|
437
|
+
/** Config value to set. */
|
|
438
|
+
value: string;
|
|
439
|
+
/** Optional broader key prefix used for duplicate detection. */
|
|
440
|
+
dedupeKey?: string;
|
|
441
|
+
};
|
|
442
|
+
|
|
408
443
|
/**
|
|
409
444
|
* Declarative description of how a CLI accepts a session-scoped system prompt.
|
|
410
445
|
*
|
|
@@ -300,7 +300,11 @@ function validateMeshCoordinator(raw: unknown, errors: string[]): void {
|
|
|
300
300
|
errors.push('meshCoordinator.reason must be a non-empty string when provided')
|
|
301
301
|
}
|
|
302
302
|
|
|
303
|
-
|
|
303
|
+
validateMeshCoordinatorMcpConfig(meshCoordinator.mcpConfig, errors)
|
|
304
|
+
validateMeshCoordinatorDelegatedWorkerIsolation(meshCoordinator.delegatedWorkerIsolation, errors)
|
|
305
|
+
}
|
|
306
|
+
|
|
307
|
+
function validateMeshCoordinatorMcpConfig(mcpConfig: unknown, errors: string[]): void {
|
|
304
308
|
if (mcpConfig === undefined) return
|
|
305
309
|
if (!mcpConfig || typeof mcpConfig !== 'object' || Array.isArray(mcpConfig)) {
|
|
306
310
|
errors.push('meshCoordinator.mcpConfig must be an object')
|
|
@@ -348,6 +352,57 @@ function validateMeshCoordinator(raw: unknown, errors: string[]): void {
|
|
|
348
352
|
}
|
|
349
353
|
}
|
|
350
354
|
|
|
355
|
+
function validateMeshCoordinatorDelegatedWorkerIsolation(raw: unknown, errors: string[]): void {
|
|
356
|
+
if (raw === undefined) return
|
|
357
|
+
if (!raw || typeof raw !== 'object' || Array.isArray(raw)) {
|
|
358
|
+
errors.push('meshCoordinator.delegatedWorkerIsolation must be an object')
|
|
359
|
+
return
|
|
360
|
+
}
|
|
361
|
+
const isolation = raw as Record<string, unknown>
|
|
362
|
+
const env = isolation.env
|
|
363
|
+
if (env !== undefined) {
|
|
364
|
+
if (!env || typeof env !== 'object' || Array.isArray(env)) {
|
|
365
|
+
errors.push('meshCoordinator.delegatedWorkerIsolation.env must be an object')
|
|
366
|
+
} else {
|
|
367
|
+
const unset = (env as Record<string, unknown>).unset
|
|
368
|
+
if (unset !== undefined && (!Array.isArray(unset) || unset.some((key) => typeof key !== 'string' || !key.trim()))) {
|
|
369
|
+
errors.push('meshCoordinator.delegatedWorkerIsolation.env.unset must be an array of non-empty strings')
|
|
370
|
+
}
|
|
371
|
+
}
|
|
372
|
+
}
|
|
373
|
+
const args = isolation.args
|
|
374
|
+
if (args === undefined) return
|
|
375
|
+
if (!Array.isArray(args)) {
|
|
376
|
+
errors.push('meshCoordinator.delegatedWorkerIsolation.args must be an array')
|
|
377
|
+
return
|
|
378
|
+
}
|
|
379
|
+
for (const [index, rule] of args.entries()) {
|
|
380
|
+
const prefix = `meshCoordinator.delegatedWorkerIsolation.args[${index}]`
|
|
381
|
+
if (!rule || typeof rule !== 'object' || Array.isArray(rule)) {
|
|
382
|
+
errors.push(`${prefix} must be an object`)
|
|
383
|
+
continue
|
|
384
|
+
}
|
|
385
|
+
const item = rule as Record<string, unknown>
|
|
386
|
+
const mode = item.mode
|
|
387
|
+
if (mode !== 'empty_mcp_config' && mode !== 'config_override') {
|
|
388
|
+
errors.push(`${prefix}.mode must be one of: empty_mcp_config, config_override`)
|
|
389
|
+
continue
|
|
390
|
+
}
|
|
391
|
+
for (const key of mode === 'empty_mcp_config' ? ['flag'] : ['flag', 'key', 'value']) {
|
|
392
|
+
const value = item[key]
|
|
393
|
+
if (typeof value !== 'string' || !value.trim()) {
|
|
394
|
+
errors.push(`${prefix}.${key} must be a non-empty string`)
|
|
395
|
+
}
|
|
396
|
+
}
|
|
397
|
+
for (const key of ['strictFlag', 'dedupeKey']) {
|
|
398
|
+
const value = item[key]
|
|
399
|
+
if (value !== undefined && (typeof value !== 'string' || !value.trim())) {
|
|
400
|
+
errors.push(`${prefix}.${key} must be a non-empty string when provided`)
|
|
401
|
+
}
|
|
402
|
+
}
|
|
403
|
+
}
|
|
404
|
+
}
|
|
405
|
+
|
|
351
406
|
function validateControl(control: ProviderControlDef, errors: string[]): void {
|
|
352
407
|
if (!control || typeof control !== 'object') {
|
|
353
408
|
errors.push('controls: each control must be an object')
|
|
@@ -320,6 +320,52 @@
|
|
|
320
320
|
{ "type": "object", "additionalProperties": false, "required": ["mode", "name"],
|
|
321
321
|
"properties": { "mode": { "const": "env_var" }, "name": { "type": "string" } } }
|
|
322
322
|
]
|
|
323
|
+
},
|
|
324
|
+
"delegatedWorkerIsolation": {
|
|
325
|
+
"description": "Provider-declared launch isolation for coordinator-spawned worker sessions. Keeps worker-only sessions from inheriting coordinator MCP/tools/config.",
|
|
326
|
+
"type": "object",
|
|
327
|
+
"additionalProperties": false,
|
|
328
|
+
"properties": {
|
|
329
|
+
"env": {
|
|
330
|
+
"type": "object",
|
|
331
|
+
"additionalProperties": false,
|
|
332
|
+
"properties": {
|
|
333
|
+
"unset": {
|
|
334
|
+
"type": "array",
|
|
335
|
+
"items": { "type": "string", "minLength": 1 }
|
|
336
|
+
}
|
|
337
|
+
}
|
|
338
|
+
},
|
|
339
|
+
"args": {
|
|
340
|
+
"type": "array",
|
|
341
|
+
"items": {
|
|
342
|
+
"oneOf": [
|
|
343
|
+
{
|
|
344
|
+
"type": "object",
|
|
345
|
+
"additionalProperties": false,
|
|
346
|
+
"required": ["mode", "flag"],
|
|
347
|
+
"properties": {
|
|
348
|
+
"mode": { "const": "empty_mcp_config" },
|
|
349
|
+
"flag": { "type": "string", "minLength": 1 },
|
|
350
|
+
"strictFlag": { "type": "string", "minLength": 1 }
|
|
351
|
+
}
|
|
352
|
+
},
|
|
353
|
+
{
|
|
354
|
+
"type": "object",
|
|
355
|
+
"additionalProperties": false,
|
|
356
|
+
"required": ["mode", "flag", "key", "value"],
|
|
357
|
+
"properties": {
|
|
358
|
+
"mode": { "const": "config_override" },
|
|
359
|
+
"flag": { "type": "string", "minLength": 1 },
|
|
360
|
+
"key": { "type": "string", "minLength": 1 },
|
|
361
|
+
"value": { "type": "string", "minLength": 1 },
|
|
362
|
+
"dedupeKey": { "type": "string", "minLength": 1 }
|
|
363
|
+
}
|
|
364
|
+
}
|
|
365
|
+
]
|
|
366
|
+
}
|
|
367
|
+
}
|
|
368
|
+
}
|
|
323
369
|
}
|
|
324
370
|
}
|
|
325
371
|
},
|
|
@@ -169,8 +169,27 @@ export interface McpConfigDef {
|
|
|
169
169
|
export interface MeshCoordinatorDef {
|
|
170
170
|
supported: boolean;
|
|
171
171
|
mcpConfig?: McpConfigDef;
|
|
172
|
+
systemPromptInjection?: MeshCoordinatorSystemPromptInjectionDef;
|
|
173
|
+
delegatedWorkerIsolation?: MeshCoordinatorDelegatedWorkerIsolationDef;
|
|
172
174
|
}
|
|
173
175
|
|
|
176
|
+
export type MeshCoordinatorSystemPromptInjectionDef =
|
|
177
|
+
| { mode: 'cli_arg'; flag: string }
|
|
178
|
+
| { mode: 'config_override'; flag: string; template: string }
|
|
179
|
+
| { mode: 'context_file'; path: string; wrapper?: string }
|
|
180
|
+
| { mode: 'env_var'; name: string };
|
|
181
|
+
|
|
182
|
+
export interface MeshCoordinatorDelegatedWorkerIsolationDef {
|
|
183
|
+
env?: {
|
|
184
|
+
unset?: ReadonlyArray<string>;
|
|
185
|
+
};
|
|
186
|
+
args?: ReadonlyArray<MeshCoordinatorDelegatedWorkerArgRuleDef>;
|
|
187
|
+
}
|
|
188
|
+
|
|
189
|
+
export type MeshCoordinatorDelegatedWorkerArgRuleDef =
|
|
190
|
+
| { mode: 'empty_mcp_config'; flag: string; strictFlag?: string }
|
|
191
|
+
| { mode: 'config_override'; flag: string; key: string; value: string; dedupeKey?: string };
|
|
192
|
+
|
|
174
193
|
// ─── Compatibility ──────────────────────────────────────────────────────
|
|
175
194
|
|
|
176
195
|
export interface CompatibilityEntryDef {
|
|
@@ -128,6 +128,14 @@ export class TerminalAdapter {
|
|
|
128
128
|
return this.lastScreen || this.computeScreen();
|
|
129
129
|
}
|
|
130
130
|
|
|
131
|
+
getCursorPosition(): { row: number; col: number } {
|
|
132
|
+
const buf = this.term.buffer.active;
|
|
133
|
+
return {
|
|
134
|
+
row: Math.max(0, (buf as any).cursorY ?? 0),
|
|
135
|
+
col: Math.max(0, (buf as any).cursorX ?? 0),
|
|
136
|
+
};
|
|
137
|
+
}
|
|
138
|
+
|
|
131
139
|
kill(): void {
|
|
132
140
|
this.stopTimers();
|
|
133
141
|
try { this.pty?.kill(); } catch { /* ignore */ }
|